#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
# Copyright (c) 2022 Univ. Lyon, ENS de Lyon, UCB Lyon 1, CNRS, INRAe, Inria
# All rights reserved.
# This file is part of the TimageTK library, and is released under the "GPLv3"
# license. Please see the LICENSE.md file that should have been included as
# part of this package.
# ------------------------------------------------------------------------------
"""Image transformation class.
This is basically a linear matrix or a dense vectorfield.
They can be generated:
- manually with `timagetk.algorithms.trsf.create_trsf`
- automatically with `timagetk.algorithms.blockmathing` or `timagetk.algorithms.pointmatching` algorithms
"""
import tempfile
import time
import numpy as np
from vt import vtTransformation
from timagetk.bin.logger import get_logger
log = get_logger(__name__)
#: List of valid transformation types for ``Trsf`` class from ``timagetk.components.trsf``.
TRSF_TYPE = ["null", "similitude", "rigid", "affine", "vectorfield"]
#: Default transformation type.
DEF_TRSF_TYPE = "affine"
#: List of valid transformation unit for ``Trsf`` class from ``timagetk.components.trsf``.
TRSF_UNIT = ['real', 'voxel']
#: Default transformation unit.
DEF_TRSF_UNIT = "real"
[docs]
class Trsf(vtTransformation):
"""Transformation objects describe linear and non-linear matrix for image registration.
Examples
--------
>>> from timagetk import Trsf
>>> trsf = Trsf()
>>> # Test if "type" of transformation is null:
>>> trsf.is_null()
True
>>> # Get the "unit" of transformation object:
>>> trsf.get_unit()
'real'
>>> # Use `create_trsf` to create a random transformation:
>>> from timagetk.algorithms.trsf import create_trsf
>>> trsf = create_trsf('random', trsf_type="rigid")
>>> print(trsf)
>>> trsf.get_type()
'rigid'
>>> # Change the unit from "real" (default) to "voxel":
>>> trsf.set_unit("voxel")
>>> trsf.get_unit()
'voxel'
>>> # Manual creation of linear transformation
>>> import numpy as np
>>> arr = np.array([[1., 0., 0., 0.], [0., 2., 0., 0.], [0., 0., 3., 0.], [0., 0., 0., 4.]])
>>> man_trsf = Trsf(arr)
>>> print(man_trsf)
vtTransformation : {
type : AFFINE_3D,
unit : real,
}
>>> print(man_trsf.get_array())
[[1. 0. 0. 0.]
[0. 2. 0. 0.]
[0. 0. 3. 0.]
[0. 0. 0. 4.]]
>>> # Manual creation of linear transformation
>>> import numpy as np
>>> arr = np.array([[1., 0., 0., 0.], [0., 2., 0., 0.], [0., 0., 3., 0.], [0., 0., 0., 4.]])
>>> man_trsf = Trsf(arr, trsf_type='rigid', trsf_unit='voxel')
>>> # Manual creation of non-linear transformation
>>> import numpy as np
>>> arr = np.array([np.random.random((4, 5, 5)), np.random.random((4, 5, 5)), np.random.random((4, 5, 5))])
>>> arr.shape
(3, 4, 5, 5)
>>> from vt import vtImage
>>> vtim = vtImage(arr, [0.5, 0.1, 0.1])
"""
[docs]
def __init__(self, trsf=None, **kwargs):
"""Transformation initialisation method.
Parameters
----------
trsf : vtTransformation or str or ndarray, optional
The name of the transformation to load, or the matrix to use.
Other Parameters
----------------
trsf_unit : {'real', 'voxel'}
Use 'real' to change the transformation unit to real world metrics.
Use 'voxel' to change the transformation unit to be voxel based.
trsf_type : str in {"rigid", "similitude", "affine", "vectorfield"}, optional
Use this to set the transformation type.
name : str
Use this to give a name to the transformation, use the ``name`` attribute to access it.
"""
if trsf is not None:
if isinstance(trsf, vtTransformation):
# FIXME: temporary hack since we cannot initialize
with tempfile.NamedTemporaryFile(suffix=".trsf") as tmp_file:
trsf.write(tmp_file.name)
log.debug(f"Used temporary file: '{tmp_file.name}'")
time.sleep(0.001) # Hack to pause before reading the temporary file to avoid errors
super().__init__(tmp_file.name)
tmp_file.close()
else:
super().__init__(trsf)
else:
super().__init__()
# Transformation type keyword argument:
trsf_unit = kwargs.get("trsf_unit", DEF_TRSF_UNIT)
try:
assert trsf_unit in TRSF_UNIT
except AssertionError:
msg = f"Transformation unit should be in {TRSF_UNIT}, got {trsf_unit}!"
raise ValueError(msg)
else:
if trsf_unit != DEF_TRSF_UNIT:
self.set_unit(trsf_unit)
# Transformation type keyword argument:
trsf_type = kwargs.get("trsf_type", "null")
try:
assert trsf_type in TRSF_TYPE
except AssertionError:
msg = f"Transformation type should be in {TRSF_TYPE}, got {trsf_type}!"
raise ValueError(msg)
else:
self.trsf_type = trsf_type
# Transformation name keyword argument:
self.name = kwargs.get("name", "")
[docs]
def get_unit(self):
"""Get the transformation unit, either 'real' or 'voxel'.
Returns
-------
{'real', 'voxel'}
The transformation unit.
Examples
--------
>>> from timagetk import Trsf
>>> trsf = Trsf()
>>> trsf.get_unit()
'real'
"""
return "real" if "real" in vtTransformation.__str__(self) else "voxel"
[docs]
def set_unit(self, unit):
"""Change the unit of the transformation, either 'real' or 'voxel'.
Parameters
----------
unit : {'real', 'voxel'}
Use 'real' to change the transformation unit to real world metrics.
Use 'voxel' to change the transformation unit to be voxel based.
Examples
--------
>>> from timagetk import Trsf
>>> trsf = Trsf()
>>> trsf.set_unit("voxel")
>>> trsf.get_unit()
'voxel'
"""
if unit != self.get_unit():
vtTransformation.setUnit(self, unit)
else:
log.info(f"The transformation unit is already set as {unit}!")
return
[docs]
def get_type(self):
"""Return the transformation type.
Returns
-------
str
The type of transformation. Should be in {"null", "rigid", "similitude", "affine", "vectorfield"}.
Examples
--------
>>> from timagetk import Trsf
>>> trsf = Trsf()
>>> trsf.get_type()
'null'
"""
trsf_type = "unknown"
if self.is_null():
trsf_type = "null"
elif self.is_similitude():
trsf_type = "similitude"
elif self.is_rigid():
trsf_type = "rigid"
elif self.is_affine():
trsf_type = "affine"
elif self.is_vectorfield():
trsf_type = "vectorfield"
return trsf_type
[docs]
def is_null(self):
"""Test if the transformation is empty.
Returns
-------
bool
``True`` if the transformation is empty, else ``False``.
Examples
--------
>>> from timagetk import Trsf
>>> trsf = Trsf()
>>> trsf.is_null()
True
"""
return "UNDEF_TRANSFORMATION" in vtTransformation.__str__(self)
[docs]
def is_identity(self):
"""Test if the transformation is the identity.
Returns
-------
bool
``True`` if the transformation is the identity, else ``False``.
Examples
--------
>>> from timagetk import Trsf
>>> from timagetk.algorithms.trsf import create_trsf
>>> trsf = create_trsf('identity')
>>> trsf.is_identity()
True
"""
from timagetk.algorithms.trsf import create_trsf
return np.array_equal(self.get_array(), create_trsf('identity').get_array())
[docs]
def is_similitude(self):
"""Test if the transformation is similitude.
Returns
-------
bool
``True`` if the transformation is similitude, else ``False``.
Examples
--------
>>> from timagetk import Trsf
>>> trsf = Trsf()
>>> trsf.is_similitude()
True
"""
return "SIMILITUDE" in vtTransformation.__str__(self)
[docs]
def is_rigid(self):
"""Test if the transformation is rigid.
Returns
-------
bool
``True`` if the transformation is rigid, else ``False``.
Examples
--------
>>> # Use `create_trsf` to create a random transformation:
>>> from timagetk.algorithms.trsf import create_trsf
>>> trsf = create_trsf('random', trsf_type="rigid")
>>> trsf.is_rigid()
True
# FIXME: not working because of `create_trsf`...
"""
return "RIGID" in vtTransformation.__str__(self)
[docs]
def is_affine(self):
"""Test if the transformation is affine.
Returns
-------
bool
``True`` if the transformation is affine, else ``False``.
Examples
--------
>>> # Use `create_trsf` to create a random transformation:
>>> from timagetk.algorithms.trsf import create_trsf
>>> trsf = create_trsf('random', trsf_type="affine")
>>> trsf.is_affine()
True
"""
return "AFFINE" in vtTransformation.__str__(self)
[docs]
def is_vectorfield(self):
"""Test if the transformation is vectorfield.
Returns
-------
bool
``True`` if the transformation is vectorfield, else ``False``.
Examples
--------
>>> # Use `create_trsf` to create a random transformation:
>>> from timagetk.algorithms.trsf import create_trsf
>>> from timagetk.array_util import random_spatial_image
>>> # A template image is required to initialize a "random" vectorfield transformation:
>>> img = random_spatial_image([15, 40, 40], voxelsize=[0.5, 0.21, 0.21])
>>> trsf = create_trsf('sinus3D', template_img=img, trsf_type="vectorfield")
>>> trsf.is_vectorfield()
True
# FIXME: not working because of `create_trsf`...
"""
return "VECTORFIELD" in vtTransformation.__str__(self)
def is_linear(self):
return self.is_rigid() or self.is_affine()
[docs]
def get_array(self):
"""Return the linear part of transformation.
Returns
-------
numpy.ndarray
The linear part of the transformation if any.
"""
return self.copy_to_array()
[docs]
def write(self, fname):
"""Write the transformation to given file path.
Parameters
----------
fname : str or pathlib.Path
File path where to save the transformation.
"""
vtTransformation.write(self, str(fname))
return