Source code for ants.contrib.sampling.affine3d

"""
Affine transforms

See http://www.cs.cornell.edu/courses/cs4620/2010fa/lectures/03transforms3d.pdf
"""

__all__ = [
    "Zoom3D",
    "RandomZoom3D",
    "Rotate3D",
    "RandomRotate3D",
    "Shear3D",
    "RandomShear3D",
    "Translate3D",
    "RandomTranslate3D",
    "Affine3D",
]

import random
import math
import numpy as np

from ...core import ants_transform as tio

[docs]class Affine3D(object): """ Create a specified ANTs Affine Transform """ def __init__(self, transformation, reference=None, lazy=False): """ Initialize a Affine object Arguments --------- transformation : array affine transformation array (3x4) reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(transformation, np.ndarray) or transformation.shape != (3,4)): raise ValueError( "transformation argument must be 3x4 Numpy array!" ) self.transformation = transformation self.lazy = lazy self.reference = reference self.tx = tio.ANTsTransform( precision="float", dimension=3, transform_type="AffineTransform" ) if self.reference is not None: self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with the given translation parameters. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.Affine3D(transformation=np.array([[1, 0, 0, dx], [0, 1, 0, dy],[0, 0, 1, dz]]) >>> img2_x = tx.transform(img)# image translated by (dx, dy, dz) """ # unpack transformation_matrix = self.transformation self.tx.set_parameters(transformation_matrix) if self.lazy or X is None: return self.tx else: if y is None: return self.tx.apply_to_image(X, reference=self.reference) else: return ( self.tx.apply_to_image(X, reference=self.reference), self.tx.apply_to_image(y, reference=self.reference), )
[docs]class Translate3D(object): """ Create an ANTs Affine Transform with a specified translation. """ def __init__(self, translation, reference=None, lazy=False): """ Initialize a Translate3D object Arguments --------- translation : list or tuple translation values for each axis, in degrees. Negative values can be used for translation in the other direction reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(translation, (list, tuple))) or (len(translation) != 3): raise ValueError( "translation argument must be list/tuple with three values!" ) self.translation = translation self.lazy = lazy self.reference = reference self.tx = tio.ANTsTransform( precision="float", dimension=3, transform_type="AffineTransform" ) if self.reference is not None: self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with the given translation parameters. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.Translate3D(translation=(10,0,0)) >>> img2_x = tx.transform(img)# x axis stays same >>> tx = ants.contrib.Translate3D(translation=(-10,0,0)) # other direction >>> img2_x = tx.transform(img)# x axis stays same >>> tx = ants.contrib.Translate3D(translation=(0,10,0)) >>> img2_y = tx.transform(img) # y axis stays same >>> tx = ants.contrib.Translate3D(translation=(0,0,10)) >>> img2_z = tx.transform(img) # z axis stays same >>> tx = ants.contrib.Translate3D(translation=(10,10,10)) >>> img2 = tx.transform(img) """ # unpack translation_x, translation_y, translation_z = self.translation translation_matrix = np.array( [ [1, 0, 0, translation_x], [0, 1, 0, translation_y], [0, 0, 1, translation_z], ] ) self.tx.set_parameters(translation_matrix) if self.lazy or X is None: return self.tx else: if y is None: return self.tx.apply_to_image(X, reference=self.reference) else: return ( self.tx.apply_to_image(X, reference=self.reference), self.tx.apply_to_image(y, reference=self.reference), )
[docs]class RandomTranslate3D(object): """ Apply a Translate3D transform to an image, but with the shear parameters randomly generated from a user-specified range. The range is determined by a mean (first parameter) and standard deviation (second parameter) via calls to random.gauss. """ def __init__(self, translation_range, reference=None, lazy=False): """ Initialize a RandomTranslate3D object Arguments --------- translation_range : list or tuple Lower and Upper bounds on rotation parameter, in degrees. e.g. translation_range = (-10,10) will result in a random draw of the rotation parameters between -10 and 10 degrees reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(translation_range, (list, tuple))) or ( len(translation_range) != 2 ): raise ValueError("shear_range argument must be list/tuple with two values!") self.translation_range = translation_range self.reference = reference self.lazy = lazy
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with translation parameters randomly generated from the user-specified range. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.RandomShear3D(translation_range=(-10,10)) >>> img2 = tx.transform(img) """ # random draw in translation range translation_x = random.gauss( self.translation_range[0], self.translation_range[1] ) translation_y = random.gauss( self.translation_range[0], self.translation_range[1] ) translation_z = random.gauss( self.translation_range[0], self.translation_range[1] ) self.params = (translation_x, translation_y, translation_z) tx = Translate3D( (translation_x, translation_y, translation_z), reference=self.reference, lazy=self.lazy, ) return tx.transform(X, y)
[docs]class Shear3D(object): """ Create an ANTs Affine Transform with a specified shear. """ def __init__(self, shear, reference=None, lazy=False): """ Initialize a Shear3D object Arguments --------- shear : list or tuple shear values for each axis, in degrees. Negative values can be used for shear in the other direction reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(shear, (list, tuple))) or (len(shear) != 3): raise ValueError("shear argument must be list/tuple with three values!") self.shear = shear self.lazy = lazy self.reference = reference self.tx = tio.ANTsTransform( precision="float", dimension=3, transform_type="AffineTransform" ) if self.reference is not None: self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with the given shear parameters. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.Shear3D(shear=(10,0,0)) >>> img2_x = tx.transform(img)# x axis stays same >>> tx = ants.contrib.Shear3D(shear=(-10,0,0)) # other direction >>> img2_x = tx.transform(img)# x axis stays same >>> tx = ants.contrib.Shear3D(shear=(0,10,0)) >>> img2_y = tx.transform(img) # y axis stays same >>> tx = ants.contrib.Shear3D(shear=(0,0,10)) >>> img2_z = tx.transform(img) # z axis stays same >>> tx = ants.contrib.Shear3D(shear=(10,10,10)) >>> img2 = tx.transform(img) """ # convert to radians and unpack shear = [math.pi / 180 * s for s in self.shear] shear_x, shear_y, shear_z = shear shear_matrix = np.array( [ [1, shear_x, shear_x, 0], [shear_y, 1, shear_y, 0], [shear_z, shear_z, 1, 0], ] ) self.tx.set_parameters(shear_matrix) if self.lazy or X is None: return self.tx else: if y is None: return self.tx.apply_to_image(X, reference=self.reference) else: return ( self.tx.apply_to_image(X, reference=self.reference), self.tx.apply_to_image(y, reference=self.reference), )
[docs]class RandomShear3D(object): """ Apply a Shear3D transform to an image, but with the shear parameters randomly generated from a user-specified range. The range is determined by a mean (first parameter) and standard deviation (second parameter) via calls to random.gauss. """ def __init__(self, shear_range, reference=None, lazy=False): """ Initialize a RandomShear3D object Arguments --------- shear_range : list or tuple Lower and Upper bounds on rotation parameter, in degrees. e.g. shear_range = (-10,10) will result in a random draw of the rotation parameters between -10 and 10 degrees reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(shear_range, (list, tuple))) or (len(shear_range) != 2): raise ValueError("shear_range argument must be list/tuple with two values!") self.shear_range = shear_range self.reference = reference self.lazy = lazy
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with shear parameters randomly generated from the user-specified range. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.RandomShear3D(shear_range=(-10,10)) >>> img2 = tx.transform(img) """ # random draw in shear range shear_x = random.gauss(self.shear_range[0], self.shear_range[1]) shear_y = random.gauss(self.shear_range[0], self.shear_range[1]) shear_z = random.gauss(self.shear_range[0], self.shear_range[1]) self.params = (shear_x, shear_y, shear_z) tx = Shear3D( (shear_x, shear_y, shear_z), reference=self.reference, lazy=self.lazy ) return tx.transform(X, y)
[docs]class Rotate3D(object): """ Create an ANTs Affine Transform with a specified level of rotation. """ def __init__(self, rotation, reference=None, lazy=False): """ Initialize a Rotate3D object Arguments --------- rotation : list or tuple rotation values for each axis, in degrees. Negative values can be used for rotation in the other direction reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(rotation, (list, tuple))) or (len(rotation) != 3): raise ValueError("rotation argument must be list/tuple with three values!") self.rotation = rotation self.lazy = lazy self.reference = reference self.tx = tio.ANTsTransform( precision="float", dimension=3, transform_type="AffineTransform" ) if self.reference is not None: self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with the given rotation parameters. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.Rotate3D(rotation=(10,-5,12)) >>> img2 = tx.transform(img) """ # unpack zoom range rotation_x, rotation_y, rotation_z = self.rotation # Rotation about X axis theta_x = math.pi / 180 * rotation_x rotate_matrix_x = np.array( [ [1, 0, 0, 0], [0, math.cos(theta_x), -math.sin(theta_x), 0], [0, math.sin(theta_x), math.cos(theta_x), 0], [0, 0, 0, 1], ] ) # Rotation about Y axis theta_y = math.pi / 180 * rotation_y rotate_matrix_y = np.array( [ [math.cos(theta_y), 0, math.sin(theta_y), 0], [0, 1, 0, 0], [-math.sin(theta_y), 0, math.cos(theta_y), 0], [0, 0, 0, 1], ] ) # Rotation about Z axis theta_z = math.pi / 180 * rotation_z rotate_matrix_z = np.array( [ [math.cos(theta_z), -math.sin(theta_z), 0, 0], [math.sin(theta_z), math.cos(theta_z), 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ] ) rotate_matrix = rotate_matrix_x.dot(rotate_matrix_y).dot(rotate_matrix_z)[:3, :] self.tx.set_parameters(rotate_matrix) if self.lazy or X is None: return self.tx else: if y is None: return self.tx.apply_to_image(X, reference=self.reference) else: return ( self.tx.apply_to_image(X, reference=self.reference), self.tx.apply_to_image(y, reference=self.reference), )
[docs]class RandomRotate3D(object): """ Apply a Rotate3D transform to an image, but with the zoom parameters randomly generated from a user-specified range. The range is determined by a mean (first parameter) and standard deviation (second parameter) via calls to random.gauss. """ def __init__(self, rotation_range, reference=None, lazy=False): """ Initialize a RandomRotate3D object Arguments --------- rotation_range : list or tuple Lower and Upper bounds on rotation parameter, in degrees. e.g. rotation_range = (-10,10) will result in a random draw of the rotation parameters between -10 and 10 degrees reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(rotation_range, (list, tuple))) or ( len(rotation_range) != 2 ): raise ValueError( "rotation_range argument must be list/tuple with two values!" ) self.rotation_range = rotation_range self.reference = reference self.lazy = lazy
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with rotation parameters randomly generated from the user-specified range. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.RandomRotate3D(rotation_range=(-10,10)) >>> img2 = tx.transform(img) """ # random draw in rotation range rotation_x = random.gauss(self.rotation_range[0], self.rotation_range[1]) rotation_y = random.gauss(self.rotation_range[0], self.rotation_range[1]) rotation_z = random.gauss(self.rotation_range[0], self.rotation_range[1]) self.params = (rotation_x, rotation_y, rotation_z) tx = Rotate3D( (rotation_x, rotation_y, rotation_z), reference=self.reference, lazy=self.lazy, ) return tx.transform(X, y)
[docs]class Zoom3D(object): """ Create an ANTs Affine Transform with a specified level of zoom. Any value greater than 1 implies a "zoom-out" and anything less than 1 implies a "zoom-in". """ def __init__(self, zoom, reference=None, lazy=False): """ Initialize a Zoom3D object Arguments --------- zoom_range : list or tuple Lower and Upper bounds on zoom parameter. e.g. zoom_range = (0.7,0.9) will result in a random draw of the zoom parameters between 0.7 and 0.9 reference : ANTsImage (optional but recommended) image providing the reference space for the transform. this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(zoom, (list, tuple))) or (len(zoom) != 3): raise ValueError( "zoom_range argument must be list/tuple with three values!" ) self.zoom = zoom self.lazy = lazy self.reference = reference self.tx = tio.ANTsTransform( precision="float", dimension=3, transform_type="AffineTransform" ) if self.reference is not None: self.tx.set_fixed_parameters(self.reference.get_center_of_mass())
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with the given zoom parameters. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.Zoom3D(zoom=(0.8,0.8,0.8)) >>> img2 = tx.transform(img) """ # unpack zoom range zoom_x, zoom_y, zoom_z = self.zoom self.params = (zoom_x, zoom_y, zoom_z) zoom_matrix = np.array( [[zoom_x, 0, 0, 0], [0, zoom_y, 0, 0], [0, 0, zoom_z, 0]] ) self.tx.set_parameters(zoom_matrix) if self.lazy or X is None: return self.tx else: if y is None: return self.tx.apply_to_image(X, reference=self.reference) else: return ( self.tx.apply_to_image(X, reference=self.reference), self.tx.apply_to_image(y, reference=self.reference), )
[docs]class RandomZoom3D(object): """ Apply a Zoom3D transform to an image, but with the zoom parameters randomly generated from a user-specified range. The range is determined by a mean (first parameter) and standard deviation (second parameter) via calls to random.gauss. """ def __init__(self, zoom_range, reference=None, lazy=False): """ Initialize a RandomZoom3D object Arguments --------- zoom_range : list or tuple Lower and Upper bounds on zoom parameter. e.g. zoom_range = (0.7,0.9) will result in a random draw of the zoom parameters between 0.7 and 0.9 reference : ANTsImage (optional but recommended) image providing the reference space for the transform this will also set the transform fixed parameters. lazy : boolean (default = False) if True, calling the `transform` method only returns the randomly generated transform and does not actually transform the image """ if (not isinstance(zoom_range, (list, tuple))) or (len(zoom_range) != 2): raise ValueError("zoom_range argument must be list/tuple with two values!") self.zoom_range = zoom_range self.reference = reference self.lazy = lazy
[docs] def transform(self, X=None, y=None): """ Transform an image using an Affine transform with zoom parameters randomly generated from the user-specified range. Return the transform if X=None. Arguments --------- X : ANTsImage Image to transform y : ANTsImage (optional) Another image to transform Returns ------- ANTsImage if y is None, else a tuple of ANTsImage types Examples -------- >>> import ants >>> img = ants.image_read(ants.get_data('ch2')) >>> tx = ants.contrib.RandomZoom3D(zoom_range=(0.8,0.9)) >>> img2 = tx.transform(img) """ # random draw in zoom range zoom_x = np.exp( random.gauss(np.log(self.zoom_range[0]), np.log(self.zoom_range[1])) ) zoom_y = np.exp( random.gauss(np.log(self.zoom_range[0]), np.log(self.zoom_range[1])) ) zoom_z = np.exp( random.gauss(np.log(self.zoom_range[0]), np.log(self.zoom_range[1])) ) self.params = (zoom_x, zoom_y, zoom_z) tx = Zoom3D((zoom_x, zoom_y, zoom_z), reference=self.reference, lazy=self.lazy) return tx.transform(X, y)