Source code for timagetk.components.trsf

#!/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