diff --git a/MindEyeV2/antspy/ants/contrib/__init__.py b/MindEyeV2/antspy/ants/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c371aff020d3f4912f0690fa6d861ebbaa4e8beb --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/__init__.py @@ -0,0 +1,4 @@ + +from .sampling import * +# from .sklearn_interface import * + diff --git a/MindEyeV2/antspy/ants/contrib/sampling/__init__.py b/MindEyeV2/antspy/ants/contrib/sampling/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b5083171790378fa8763a5efd211ddc0bb27045c --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/sampling/__init__.py @@ -0,0 +1,4 @@ + +from .transforms import * +from .affine2d import * +from .affine3d import * diff --git a/MindEyeV2/antspy/ants/contrib/sampling/affine2d.py b/MindEyeV2/antspy/ants/contrib/sampling/affine2d.py new file mode 100644 index 0000000000000000000000000000000000000000..25dae0b8fc6b84cc26ca3aa678cd956ceb751600 --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/sampling/affine2d.py @@ -0,0 +1,654 @@ +""" +Affine transforms + +See http://www.cs.cornell.edu/courses/cs4620/2010fa/lectures/03transforms3D.pdf +""" + +__all__ = [ + "Zoom2D", + "RandomZoom2D", + "Rotate2D", + "RandomRotate2D", + "Shear2D", + "RandomShear2D", + "Translate2D", + "RandomTranslate2D", +] + +import random +import math +import numpy as np + +from ...core import ants_transform as tio + + +class Translate2D(object): + """ + Create an ANTs Affine Transform with a specified translation. + """ + + def __init__(self, translation, reference=None, lazy=False): + """ + Initialize a Translate2D 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) != 2): + raise ValueError("translation argument must be list/tuple with two values!") + + self.translation = translation + self.lazy = lazy + self.reference = reference + + self.tx = tio.ANTsTransform( + precision="float", dimension=2, transform_type="AffineTransform" + ) + if self.reference is not None: + self.tx.set_fixed_parameters(self.reference.get_center_of_mass()) + + 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('r16')) + >>> tx = ants.contrib.Translate2D(translation=(10,0)) + >>> img2_x = tx.transform(img) + >>> tx = ants.contrib.Translate2D(translation=(-10,0)) # other direction + >>> img2_x = tx.transform(img) + >>> tx = ants.contrib.Translate2D(translation=(0,10)) + >>> img2_z = tx.transform(img) + >>> tx = ants.contrib.Translate2D(translation=(10,10)) + >>> img2 = tx.transform(img) + """ + # convert to radians and unpack + translation_x, translation_y = self.translation + + translation_matrix = np.array([[1, 0, translation_x], [0, 1, translation_y]]) + 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), + ) + + +class RandomTranslate2D(object): + """ + Apply a Translate2D transform to an image, but with the + 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 RandomTranslate2D 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 + + 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('r16')) + >>> tx = ants.contrib.RandomShear2D(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] + ) + self.params = (translation_x, translation_y) + + tx = Translate2D( + (translation_x, translation_y), reference=self.reference, lazy=self.lazy + ) + + return tx.transform(X, y) + + +class Shear2D(object): + """ + Create an ANTs Affine Transform with a specified shear. + """ + + def __init__(self, shear, reference=None, lazy=False): + """ + Initialize a Shear2D 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) != 2): + raise ValueError("shear argument must be list/tuple with two values!") + + self.shear = shear + self.lazy = lazy + self.reference = reference + + self.tx = tio.ANTsTransform( + precision="float", dimension=2, transform_type="AffineTransform" + ) + if self.reference is not None: + self.tx.set_fixed_parameters(self.reference.get_center_of_mass()) + + 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('r16')) + >>> tx = ants.contrib.Shear2D(shear=(10,0,0)) + >>> img2_x = tx.transform(img)# x axis stays same + >>> tx = ants.contrib.Shear2D(shear=(-10,0,0)) # other direction + >>> img2_x = tx.transform(img)# x axis stays same + >>> tx = ants.contrib.Shear2D(shear=(0,10,0)) + >>> img2_y = tx.transform(img) # y axis stays same + >>> tx = ants.contrib.Shear2D(shear=(0,0,10)) + >>> img2_z = tx.transform(img) # z axis stays same + >>> tx = ants.contrib.Shear2D(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 + + shear_matrix = np.array([[1, shear_x, 0], [shear_y, 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), + ) + + +class RandomShear2D(object): + """ + Apply a Shear2D 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 RandomShear2D 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 + + 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('r16')) + >>> tx = ants.contrib.RandomShear2D(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]) + self.params = (shear_x, shear_y) + + tx = Shear2D((shear_x, shear_y), reference=self.reference, lazy=self.lazy) + + return tx.transform(X, y) + + +class Rotate2D(object): + """ + Create an ANTs Affine Transform with a specified level + of rotation. + """ + + def __init__(self, rotation, reference=None, lazy=False): + """ + Initialize a Rotate2D object + + Arguments + --------- + rotation : scalar + rotation value 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 + """ + self.rotation = rotation + self.lazy = lazy + self.reference = reference + + self.tx = tio.ANTsTransform( + precision="float", dimension=2, transform_type="AffineTransform" + ) + if self.reference is not None: + self.tx.set_fixed_parameters(self.reference.get_center_of_mass()) + + 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('r16')) + >>> tx = ants.contrib.Rotate2D(rotation=(10,-5,12)) + >>> img2 = tx.transform(img) + """ + # unpack zoom range + rotation = self.rotation + + # Rotation about X axis + theta = math.pi / 180 * rotation + rotation_matrix = np.array( + [[np.cos(theta), -np.sin(theta), 0], [np.sin(theta), np.cos(theta), 0]] + ) + + self.tx.set_parameters(rotation_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), + ) + + +class RandomRotate2D(object): + """ + Apply a Rotated2D 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 RandomRotate2D 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 + + 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('r16')) + >>> tx = ants.contrib.RandomRotate2D(rotation_range=(-10,10)) + >>> img2 = tx.transform(img) + """ + # random draw in rotation range + rotation = random.gauss(self.rotation_range[0], self.rotation_range[1]) + self.params = rotation + + tx = Rotate2D(rotation, reference=self.reference, lazy=self.lazy) + + return tx.transform(X, y) + + +class Zoom2D(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 Zoom2D 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) != 2): + raise ValueError("zoom_range argument must be list/tuple with two values!") + + self.zoom = zoom + self.lazy = lazy + self.reference = reference + + self.tx = tio.ANTsTransform( + precision="float", dimension=2, transform_type="AffineTransform" + ) + if self.reference is not None: + self.tx.set_fixed_parameters(self.reference.get_center_of_mass()) + + 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('r16')) + >>> tx = ants.contrib.Zoom2D(zoom=(0.8,0.8,0.8)) + >>> img2 = tx.transform(img) + """ + # unpack zoom range + zoom_x, zoom_y = self.zoom + + self.params = (zoom_x, zoom_y) + zoom_matrix = np.array([[zoom_x, 0, 0], [0, zoom_y, 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), + ) + + +class RandomZoom2D(object): + """ + Apply a Zoom2D 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 RandomZoom2D 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 + + 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('r16')) + >>> tx = ants.contrib.RandomZoom2D(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])) + ) + self.params = (zoom_x, zoom_y) + + tx = Zoom2D((zoom_x, zoom_y), reference=self.reference, lazy=self.lazy) + + return tx.transform(X, y) diff --git a/MindEyeV2/antspy/ants/contrib/sampling/affine3d.py b/MindEyeV2/antspy/ants/contrib/sampling/affine3d.py new file mode 100644 index 0000000000000000000000000000000000000000..98b93b538bb07db6f82515d4224f81e2ffed4d95 --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/sampling/affine3d.py @@ -0,0 +1,802 @@ +""" +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 + +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()) + + 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), + ) + + +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()) + + 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), + ) + + +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 + + 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) + + +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()) + + 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), + ) + + +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 + + 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) + + +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()) + + 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), + ) + + +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 + + 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) + + +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()) + + 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), + ) + + +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 + + 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) diff --git a/MindEyeV2/antspy/ants/contrib/sampling/transforms.py b/MindEyeV2/antspy/ants/contrib/sampling/transforms.py new file mode 100644 index 0000000000000000000000000000000000000000..753542ed0881783245cf47b8663f574fa9a8f64a --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/sampling/transforms.py @@ -0,0 +1,766 @@ +""" +Various data augmentation transforms for ANTsImage types + +List of Transformations: +====================== +- CastIntensity +- BlurIntensity +- NormalizeIntensity +- RescaleIntensity +- ShiftScaleIntensity +- SigmoidIntensity +====================== +- FlipImage +- TranslateImage + +TODO +---- +- RotateImage +- ShearImage +- ScaleImage +- DeformImage +- PadImage +- HistogramEqualizeIntensity +- TruncateIntensity +- SharpenIntensity +- MorpholigicalIntensity + - MD + - ME + - MO + - MC + - GD + - GE + - GO + - GC +""" +__all__ = ['CastIntensity', + 'BlurIntensity', + 'LocallyBlurIntensity', + 'NormalizeIntensity', + 'RescaleIntensity', + 'ShiftScaleIntensity', + 'SigmoidIntensity', + 'FlipImage', + 'ScaleImage', + 'TranslateImage', + 'MultiResolutionImage'] + +from ... import utils +from ...core import ants_image as iio + + +class MultiResolutionImage(object): + """ + Generate a set of images at multiple resolutions from an original image + """ + def __init__(self, levels=4, keep_shape=False): + self.levels = levels + self.keep_shape = keep_shape + + def transform(self, X, y=None): + """ + Generate a set of multi-resolution ANTsImage types + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform + + Example + ------- + >>> import ants + >>> multires = ants.contrib.MultiResolutionImage(levels=4) + >>> img = ants.image_read(ants.get_data('r16')) + >>> imgs = multires.transform(img) + """ + insuffix = X._libsuffix + multires_fn = utils.get_lib_fn('multiResolutionAntsImage%s' % (insuffix)) + casted_ptrs = multires_fn(X.pointer, self.levels) + + imgs = [] + for casted_ptr in casted_ptrs: + img = iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + if self.keep_shape: + img = img.resample_image_to_target(X) + imgs.append(img) + + return imgs + + +## Intensity Transforms ## + +class CastIntensity(object): + """ + Cast the pixeltype of an ANTsImage to a given type. + This code uses the C++ ITK library directly, so it is fast. + + NOTE: This offers a ~2.5x speedup over using img.clone(pixeltype): + + Timings vs Cloning + ------------------ + >>> import ants + >>> import time + >>> caster = ants.contrib.CastIntensity('float') + >>> img = ants.image_read(ants.get_data('mni')).clone('unsigned int') + >>> s = time.time() + >>> for i in range(1000): + ... img_float = caster.transform(img) + >>> e = time.time() + >>> print(e - s) # 9.6s + >>> s = time.time() + >>> for i in range(1000): + ... img_float = img.clone('float') + >>> e = time.time() + >>> print(e - s) # 25.3s + """ + def __init__(self, pixeltype): + """ + Initialize a CastIntensity transform + + Arguments + --------- + pixeltype : string + pixeltype to which images will be casted + + Example + ------- + >>> import ants + >>> caster = ants.contrib.CastIntensity('float') + """ + self.pixeltype = pixeltype + + def transform(self, X, y=None): + """ + Transform an image by casting its type + + Arguments + --------- + X : ANTsImage + image to cast + + y : ANTsImage (optional) + another image to cast. + + Example + ------- + >>> import ants + >>> caster = ants.contrib.CastIntensity('float') + >>> img2d = ants.image_read(ants.get_data('r16')).clone('unsigned int') + >>> img2d_float = caster.transform(img2d) + >>> print(img2d.pixeltype, '- ', img2d_float.pixeltype) + >>> img3d = ants.image_read(ants.get_data('mni')).clone('unsigned int') + >>> img3d_float = caster.transform(img3d) + >>> print(img3d.pixeltype, ' - ' , img3d_float.pixeltype) + """ + insuffix = X._libsuffix + outsuffix = '%s%i' % (utils.short_ptype(self.pixeltype), X.dimension) + cast_fn = utils.get_lib_fn('castAntsImage%s%s' % (insuffix, outsuffix)) + casted_ptr = cast_fn(X.pointer) + return iio.ANTsImage(pixeltype=self.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +class BlurIntensity(object): + """ + Transform for blurring the intensity of an ANTsImage + using a Gaussian Filter + """ + def __init__(self, sigma, width): + """ + Initialize a BlurIntensity transform + + Arguments + --------- + sigma : float + variance of gaussian kernel intensity + increasing this value increasing the amount + of blur + + width : int + width of gaussian kernel shape + increasing this value increase the number of + neighboring voxels which are used for blurring + + Example + ------- + >>> import ants + >>> blur = ants.contrib.BlurIntensity(2,3) + """ + self.sigma = sigma + self.width = width + + def transform(self, X, y=None): + """ + Blur an image by applying a gaussian filter. + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> blur = ants.contrib.BlurIntensity(2,3) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_b = blur.transform(img2d) + >>> ants.plot(img2d) + >>> ants.plot(img2d_b) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_b = blur.transform(img3d) + >>> ants.plot(img3d) + >>> ants.plot(img3d_b) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('blurAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer, self.sigma, self.width) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr, + origin=X.origin) + + +class LocallyBlurIntensity(object): + """ + Blur an ANTsImage locally using a gradient anisotropic + diffusion filter, thereby preserving the sharpeness of edges as best + as possible. + """ + def __init__(self, conductance=1, iters=5): + self.conductance = conductance + self.iters = iters + + def transform(self, X, y=None): + """ + Locally blur an image by applying a gradient anisotropic diffusion filter. + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> blur = ants.contrib.LocallyBlurIntensity(1,5) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_b = blur.transform(img2d) + >>> ants.plot(img2d) + >>> ants.plot(img2d_b) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_b = blur.transform(img3d) + >>> ants.plot(img3d) + >>> ants.plot(img3d_b) + """ + #if X.pixeltype != 'float': + # raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('locallyBlurAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer, self.iters, self.conductance) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +class NormalizeIntensity(object): + """ + Normalize the intensity values of an ANTsImage to have + zero mean and unit variance + + NOTE: this transform is more-or-less the same in speed + as an equivalent numpy+scikit-learn solution. + + Timing vs Numpy+Scikit-Learn + ---------------------------- + >>> import ants + >>> import numpy as np + >>> from sklearn.preprocessing import StandardScaler + >>> import time + >>> img = ants.image_read(ants.get_data('mni')) + >>> arr = img.numpy().reshape(1,-1) + >>> normalizer = ants.contrib.NormalizeIntensity() + >>> normalizer2 = StandardScaler() + >>> s = time.time() + >>> for i in range(100): + ... img_scaled = normalizer.transform(img) + >>> e = time.time() + >>> print(e - s) # 3.3s + >>> s = time.time() + >>> for i in range(100): + ... arr_scaled = normalizer2.fit_transform(arr) + >>> e = time.time() + >>> print(e - s) # 3.5s + """ + def __init__(self): + """ + Initialize a NormalizeIntensity transform + """ + pass + + def transform(self, X, y=None): + """ + Transform an image by normalizing its intensity values to + have zero mean and unit variance. + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> normalizer = ants.contrib.NormalizeIntensity() + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = normalizer.transform(img2d) + >>> print(img2d.mean(), ',', img2d.std(), ' -> ', img2d_r.mean(), ',', img2d_r.std()) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_r = normalizer.transform(img3d) + >>> print(img3d.mean(), ',' , img3d.std(), ',', ' -> ', img3d_r.mean(), ',' , img3d_r.std()) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('normalizeAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +class RescaleIntensity(object): + """ + Rescale the pixeltype of an ANTsImage linearly to be between a given + minimum and maximum value. + This code uses the C++ ITK library directly, so it is fast. + + NOTE: this offered a ~5x speedup over using built-in arithmetic operations in ANTs. + It is also more-or-less the same in speed as an equivalent numpy+scikit-learn + solution. + + Timing vs Built-in Operations + ----------------------------- + >>> import ants + >>> import time + >>> rescaler = ants.contrib.RescaleIntensity(0,1) + >>> img = ants.image_read(ants.get_data('mni')) + >>> s = time.time() + >>> for i in range(100): + ... img_float = rescaler.transform(img) + >>> e = time.time() + >>> print(e - s) # 2.8s + >>> s = time.time() + >>> for i in range(100): + ... maxval = img.max() + ... img_float = (img - maxval) / (maxval - img.min()) + >>> e = time.time() + >>> print(e - s) # 13.9s + + Timing vs Numpy+Scikit-Learn + ---------------------------- + >>> import ants + >>> import numpy as np + >>> from sklearn.preprocessing import MinMaxScaler + >>> import time + >>> img = ants.image_read(ants.get_data('mni')) + >>> arr = img.numpy().reshape(1,-1) + >>> rescaler = ants.contrib.RescaleIntensity(-1,1) + >>> rescaler2 = MinMaxScaler((-1,1)).fit(arr) + >>> s = time.time() + >>> for i in range(100): + ... img_scaled = rescaler.transform(img) + >>> e = time.time() + >>> print(e - s) # 2.8s + >>> s = time.time() + >>> for i in range(100): + ... arr_scaled = rescaler2.transform(arr) + >>> e = time.time() + >>> print(e - s) # 3s + """ + + def __init__(self, min_val, max_val): + """ + Initialize a RescaleIntensity transform. + + Arguments + --------- + min_val : float + minimum value to which image(s) will be rescaled + + max_val : float + maximum value to which image(s) will be rescaled + + Example + ------- + >>> import ants + >>> rescaler = ants.contrib.RescaleIntensity(0,1) + """ + self.min_val = min_val + self.max_val = max_val + + def transform(self, X, y=None): + """ + Transform an image by linearly rescaling its intensity to + be between a minimum and maximum value + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> rescaler = ants.contrib.RescaleIntensity(0,1) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = rescaler.transform(img2d) + >>> print(img2d.min(), ',', img2d.max(), ' -> ', img2d_r.min(), ',', img2d_r.max()) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_r = rescaler.transform(img3d) + >>> print(img3d.min(), ',' , img3d.max(), ' -> ', img3d_r.min(), ',' , img3d_r.max()) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('rescaleAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer, self.min_val, self.max_val) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +class ShiftScaleIntensity(object): + """ + Shift and scale the intensity of an ANTsImage + """ + def __init__(self, shift, scale): + """ + Initialize a ShiftScaleIntensity transform + + Arguments + --------- + shift : float + shift all of the intensity values by the given amount through addition. + For example, if the minimum image value is 0.0 and the shift + is 10.0, then the new minimum value (before scaling) will be 10.0 + + scale : float + scale all the intensity values by the given amount through multiplication. + For example, if the min/max image values are 10/20 and the scale + is 2.0, then then new min/max values will be 20/40 + + Example + ------- + >>> import ants + >>> shiftscaler = ants.contrib.ShiftScaleIntensity(shift=10, scale=2) + """ + self.shift = shift + self.scale = scale + + def transform(self, X, y=None): + """ + Transform an image by shifting and scaling its intensity values. + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> shiftscaler = ants.contrib.ShiftScaleIntensity(10,2.) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = shiftscaler.transform(img2d) + >>> print(img2d.min(), ',', img2d.max(), ' -> ', img2d_r.min(), ',', img2d_r.max()) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_r = shiftscaler.transform(img3d) + >>> print(img3d.min(), ',' , img3d.max(), ',', ' -> ', img3d_r.min(), ',' , img3d_r.max()) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('shiftScaleAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer, self.scale, self.shift) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +class SigmoidIntensity(object): + """ + Transform an image using a sigmoid function + """ + def __init__(self, min_val, max_val, alpha, beta): + """ + Initialize a SigmoidIntensity transform + + Arguments + --------- + min_val : float + minimum value + + max_val : float + maximum value + + alpha : float + alpha value for sigmoid + + beta : flaot + beta value for sigmoid + + Example + ------- + >>> import ants + >>> sigscaler = ants.contrib.SigmoidIntensity(0,1,1,1) + """ + self.min_val = min_val + self.max_val = max_val + self.alpha = alpha + self.beta = beta + + def transform(self, X, y=None): + """ + Transform an image by applying a sigmoid function. + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> sigscaler = ants.contrib.SigmoidIntensity(0,1,1,1) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = sigscaler.transform(img2d) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_r = sigscaler.transform(img3d) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('sigmoidAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer, self.min_val, self.max_val, self.alpha, self.beta) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +## Physical Transforms ## + +class FlipImage(object): + """ + Transform an image by flipping two axes. + """ + def __init__(self, axis1, axis2): + """ + Initialize a SigmoidIntensity transform + + Arguments + --------- + axis1 : int + axis to flip + + axis2 : int + other axis to flip + + Example + ------- + >>> import ants + >>> flipper = ants.contrib.FlipImage(0,1) + """ + self.axis1 = axis1 + self.axis2 = axis2 + + def transform(self, X, y=None): + """ + Transform an image by applying a sigmoid function. + + Arguments + --------- + X : ANTsImage + image to transform + + y : ANTsImage (optional) + another image to transform. + + Example + ------- + >>> import ants + >>> flipper = ants.contrib.FlipImage(0,1) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = flipper.transform(img2d) + >>> ants.plot(img2d) + >>> ants.plot(img2d_r) + >>> flipper2 = ants.contrib.FlipImage(1,0) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = flipper2.transform(img2d) + >>> ants.plot(img2d) + >>> ants.plot(img2d_r) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('flipAntsImage%s' % (insuffix)) + casted_ptr = cast_fn(X.pointer, self.axis1, self.axis2) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr, + origin=X.origin) + + +class TranslateImage(object): + """ + Translate an image in physical space. This function calls + highly optimized ITK/C++ code. + """ + def __init__(self, translation, reference=None, interp='linear'): + """ + Initialize a TranslateImage transform + + Arguments + --------- + translation : list, tuple, or numpy.ndarray + absolute pixel transformation in each axis + + reference : ANTsImage (optional) + image which provides the reference physical space in which + to perform the transform + + interp : string + type of interpolation to use + options: linear, nearest + + Example + ------- + >>> import ants + >>> translater = ants.contrib.TranslateImage((10,10), interp='linear') + """ + if interp not in {'linear', 'nearest'}: + raise ValueError('interp must be one of {linear, nearest}') + + self.translation = list(translation) + self.reference = reference + self.interp = interp + + def transform(self, X, y=None): + """ + Example + ------- + >>> import ants + >>> translater = ants.contrib.TranslateImage((40,0)) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = translater.transform(img2d) + >>> ants.plot(img2d, img2d_r) + >>> translater = ants.contrib.TranslateImage((40,0,0)) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_r = translater.transform(img3d) + >>> ants.plot(img3d, img3d_r, axis=2) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + if len(self.translation) != X.dimension: + raise ValueError('must give a translation value for each image dimension') + + if self.reference is None: + reference = X + else: + reference = self.reference + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('translateAntsImage%s_%s' % (insuffix, self.interp)) + casted_ptr = cast_fn(X.pointer, reference.pointer, self.translation) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + + +class ScaleImage(object): + """ + Scale an image in physical space. This function calls + highly optimized ITK/C++ code. + """ + def __init__(self, scale, reference=None, interp='linear'): + """ + Initialize a TranslateImage transform + + Arguments + --------- + scale : list, tuple, or numpy.ndarray + relative scaling along each axis + + reference : ANTsImage (optional) + image which provides the reference physical space in which + to perform the transform + + interp : string + type of interpolation to use + options: linear, nearest + + Example + ------- + >>> import ants + >>> translater = ants.contrib.TranslateImage((10,10), interp='linear') + """ + if interp not in {'linear', 'nearest'}: + raise ValueError('interp must be one of {linear, nearest}') + + self.scale = list(scale) + self.reference = reference + self.interp = interp + + def transform(self, X, y=None): + """ + Example + ------- + >>> import ants + >>> scaler = ants.contrib.ScaleImage((1.2,1.2)) + >>> img2d = ants.image_read(ants.get_data('r16')) + >>> img2d_r = scaler.transform(img2d) + >>> ants.plot(img2d, img2d_r) + >>> scaler = ants.contrib.ScaleImage((1.2,1.2,1.2)) + >>> img3d = ants.image_read(ants.get_data('mni')) + >>> img3d_r = scaler.transform(img3d) + >>> ants.plot(img3d, img3d_r) + """ + if X.pixeltype != 'float': + raise ValueError('image.pixeltype must be float ... use TypeCast transform or clone to float') + + if len(self.scale) != X.dimension: + raise ValueError('must give a scale value for each image dimension') + + if self.reference is None: + reference = X + else: + reference = self.reference + + insuffix = X._libsuffix + cast_fn = utils.get_lib_fn('scaleAntsImage%s_%s' % (insuffix, self.interp)) + casted_ptr = cast_fn(X.pointer, reference.pointer, self.scale) + return iio.ANTsImage(pixeltype=X.pixeltype, dimension=X.dimension, + components=X.components, pointer=casted_ptr) + diff --git a/MindEyeV2/antspy/ants/contrib/sklearn_interface/__init__.py b/MindEyeV2/antspy/ants/contrib/sklearn_interface/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..68cc3fd31ee9e89490a193c0554c020aafdbe9ba --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/sklearn_interface/__init__.py @@ -0,0 +1,3 @@ + + +from .sklearn_registration import * \ No newline at end of file diff --git a/MindEyeV2/antspy/ants/contrib/sklearn_interface/sklearn_registration.py b/MindEyeV2/antspy/ants/contrib/sklearn_interface/sklearn_registration.py new file mode 100644 index 0000000000000000000000000000000000000000..2621a79ed75832ecf24ca428dd127a1cb7414483 --- /dev/null +++ b/MindEyeV2/antspy/ants/contrib/sklearn_interface/sklearn_registration.py @@ -0,0 +1,149 @@ + + +__all__ = ['RigidRegistration'] + +from ...registration import interface, apply_transforms + + +class Registration(object): + """ + How would it work: + + # Co-registration within-visit + reg = Registration('Rigid', fixed_image=t1_template, + save_dir=save_dir, save_suffix='_coreg') + for img in other_imgs: + reg.fit(img) + + for img in [flair_img, t2_img]: + reg = Registration('Rigid', fixed_image=t1_template, + save_dir=save_dir, save_suffix='_coreg') + reg.fit(img) + + # Co-registration across-visit + reg = Registration('Rigid', fixed_image=t1) + reg.fit(moving=t1_followup) + + # now align all followups with first visit + for img in [flair_follwup, t2_followup]: + img_reg = reg.transform(img) + + # conversly, align all first visits with followups + for img in [flair, t2]: + img_reg = reg.inverse_transform(img) + """ + + def __init__(self, type_of_transform, fixed_image): + """ + Properties: + type_of_transform + fixed_image (template) + save_dir (where to save outputs) + save_suffix (what to append to saved outputs) + save_prefix (what to preppend to saved outputs) + """ + self.type_of_transform = type_of_transform + self.fixed_image = fixed_image + + def fit(self, X, y=None): + """ + X : ANTsImage | string | list of ANTsImage types | list of strings + images to register to fixed image + + y : string | list of strings + labels for images + """ + moving_images = X if isinstance(X, (list,tuple)) else [X] + moving_labels = y if y is not None else [i for i in range(len(moving_images))] + fixed_image = self.fixed_image + + self.fwdtransforms_ = {} + self.invtransforms_ = {} + self.warpedmovout_ = {} + self.warpedfixout_ = {} + + for moving_image, moving_label in zip(moving_images, moving_labels): + fit_result = interface.registration(fixed_image, + moving_image, + type_of_transform=self.type_of_transform, + initial_transform=None, + outprefix='', + mask=None, + grad_step=0.2, + flow_sigma=3, + total_sigma=0, + aff_metric='mattes', + aff_sampling=32, + syn_metric='mattes', + syn_sampling=32, + reg_iterations=(40,20,0), + verbose=False) + + self.fwdtransforms_[moving_label] = fit_result['fwdtransforms'] + self.invtransforms_[moving_label] = fit_result['invtransforms'] + self.warpedmovout_[moving_label] = fit_result['warpedmovout'] + self.warpedfixout_[moving_label] = fit_result['warpedfixout'] + + return self + + def transform(self, X, y=None): + pass + + +class RigidRegistration(object): + """ + Rigid Registration as a Scikit-Learn compatible transform class + + Example + ------- + >>> import ants + >>> import ants.extra as extrants + >>> fi = ants.image_read(ants.get_data('r16')) + >>> mi = ants.image_read(ants.get_data('r64')) + >>> regtx = extrants.RigidRegistration() + >>> regtx.fit(fi, mi) + >>> mi_r = regtx.transform(mi) + >>> ants.plot(fi, mi_r.iMath_Canny(1, 2, 4).iMath('MD',1)) + """ + def __init__(self, fixed_image=None): + self.type_of_transform = 'Rigid' + self.fixed_image = fixed_image + + def fit(self, moving_image, fixed_image=None): + if fixed_image is None: + if self.fixed_image is None: + raise ValueError('must give fixed_image in fit() or set it in __init__') + fixed_image = self.fixed_image + + fit_result = interface.registration(fixed_image, + moving_image, + type_of_transform=self.type_of_transform, + initial_transform=None, + outprefix='', + mask=None, + grad_step=0.2, + flow_sigma=3, + total_sigma=0, + aff_metric='mattes', + aff_sampling=32, + syn_metric='mattes', + syn_sampling=32, + reg_iterations=(40,20,0), + verbose=False) + self._fit_result = fit_result + self.fwdtransforms_ = fit_result['fwdtransforms'] + self.invtransforms_ = fit_result['invtransforms'] + self.warpedmovout_ = fit_result['warpedmovout'] + self.warpedfiout_ = fit_result['warpedfixout'] + + def transform(self, moving_image, fixed_image=None): + result = apply_transforms(fixed=fixed_image, moving=moving_image, + transformlist=self.fwdtransforms) + return result + + def inverse_transform(self, moving_image, fixed_image=None): + result = apply_transforms(fixed=fixed_image, moving=moving_image, + transformlist=self.invtransforms) + return result + + diff --git a/MindEyeV2/antspy/ants/plotting/__init__.py b/MindEyeV2/antspy/ants/plotting/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..65718cfeea24be1092930930c7222f8c0b33bbf5 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/__init__.py @@ -0,0 +1,8 @@ + +from .plot import plot +from .movie import movie +from .plot_hist import plot_hist +from .plot_grid import plot_grid +from .plot_ortho import plot_ortho +from .plot_ortho_stack import plot_ortho_stack +from .plot_directory import plot_directory diff --git a/MindEyeV2/antspy/ants/plotting/movie.py b/MindEyeV2/antspy/ants/plotting/movie.py new file mode 100644 index 0000000000000000000000000000000000000000..defb349aecd863733ab94e68744029c77dffaa74 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/movie.py @@ -0,0 +1,87 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "movie" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +import numpy as np +from ants.decorators import image_method + +@image_method +def movie(image, filename=None, writer=None, fps=30): + """ + Create and save a movie - mp4, gif, etc - of the various + 2D slices of a 3D ants image + + Try this: + conda install -c conda-forge ffmpeg + + Example + ------- + >>> import ants + >>> mni = ants.image_read(ants.get_data('mni')) + >>> ants.movie(mni, filename='~/desktop/movie.mp4') + """ + + image = image.pad_image() + img_arr = image.numpy() + + minidx = max(0, np.where(image > 0)[0][0] - 5) + maxidx = max(image.shape[0], np.where(image > 0)[0][-1] + 5) + + # Creare your figure and axes + fig, ax = plt.subplots(1) + + im = ax.imshow( + img_arr[minidx, :, :], + animated=True, + cmap="Greys_r", + vmin=image.quantile(0.05), + vmax=image.quantile(0.95), + ) + + ax.axis("off") + + def init(): + fig.axes("off") + return (im,) + + def updatefig(frame): + im.set_array(img_arr[frame, :, :]) + return (im,) + + ani = animation.FuncAnimation( + fig, + updatefig, + frames=np.arange(minidx, maxidx), + # init_func=init, + interval=50, + blit=True, + ) + + if writer is None: + writer = animation.FFMpegWriter(fps=fps) + + if filename is not None: + filename = os.path.expanduser(filename) + ani.save(filename, writer=writer) + else: + plt.show() diff --git a/MindEyeV2/antspy/ants/plotting/plot.py b/MindEyeV2/antspy/ants/plotting/plot.py new file mode 100644 index 0000000000000000000000000000000000000000..5771c30e09c3990cda3c7b2cb5ed42aa085027b2 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/plot.py @@ -0,0 +1,486 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "plot" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + +import numpy as np +import ants +from ants.decorators import image_method + +@image_method +def plot( + image, + overlay=None, + blend=False, + alpha=1, + cmap="Greys_r", + overlay_cmap="turbo", + overlay_alpha=0.9, + vminol=None, + vmaxol=None, + cbar=False, + cbar_length=0.8, + cbar_dx=0.0, + cbar_vertical=True, + axis=0, + nslices=12, + slices=None, + ncol=None, + slice_buffer=None, + black_bg=True, + bg_thresh_quant=0.01, + bg_val_quant=0.99, + domain_image_map=None, + crop=False, + scale=False, + reverse=False, + title=None, + title_fontsize=20, + title_dx=0.0, + title_dy=0.0, + filename=None, + dpi=500, + figsize=1.5, + reorient=True, + resample=True, +): + """ + Plot an ANTsImage. + + Use mask_image and/or threshold_image to preprocess images to be be + overlaid and display the overlays in a given range. See the wiki examples. + + By default, images will be reoriented to 'LAI' orientation before plotting. + So, if axis == 0, the images will be ordered from the + left side of the brain to the right side of the brain. If axis == 1, + the images will be ordered from the anterior (front) of the brain to + the posterior (back) of the brain. And if axis == 2, the images will + be ordered from the inferior (bottom) of the brain to the superior (top) + of the brain. + + ANTsR function: `plot.antsImage` + + Arguments + --------- + image : ANTsImage + image to plot + + overlay : ANTsImage + image to overlay on base image + + cmap : string + colormap to use for base image. See matplotlib. + + overlay_cmap : string + colormap to use for overlay images, if applicable. See matplotlib. + + overlay_alpha : float + level of transparency for any overlays. Smaller value means + the overlay is more transparent. See matplotlib. + + axis : integer + which axis to plot along if image is 3D + + nslices : integer + number of slices to plot if image is 3D + + slices : list or tuple of integers + specific slice indices to plot if image is 3D. + If given, this will override `nslices`. + This can be absolute array indices (e.g. (80,100,120)), or + this can be relative array indices (e.g. (0.4,0.5,0.6)) + + ncol : integer + Number of columns to have on the plot if image is 3D. + + slice_buffer : integer + how many slices to buffer when finding the non-zero slices of + a 3D images. So, if slice_buffer = 10, then the first slice + in a 3D image will be the first non-zero slice index plus 10 more + slices. + + black_bg : boolean + if True, the background of the image(s) will be black. + if False, the background of the image(s) will be determined by the + values `bg_thresh_quant` and `bg_val_quant`. + + bg_thresh_quant : float + if white_bg=True, the background will be determined by thresholding + the image at the `bg_thresh` quantile value and setting the background + intensity to the `bg_val` quantile value. + This value should be in [0, 1] - somewhere around 0.01 is recommended. + - equal to 1 will threshold the entire image + - equal to 0 will threshold none of the image + + bg_val_quant : float + if white_bg=True, the background will be determined by thresholding + the image at the `bg_thresh` quantile value and setting the background + intensity to the `bg_val` quantile value. + This value should be in [0, 1] + - equal to 1 is pure white + - equal to 0 is pure black + - somewhere in between is gray + + domain_image_map : ANTsImage + this input ANTsImage or list of ANTsImage types contains a reference image + `domain_image` and optional reference mapping named `domainMap`. + If supplied, the image(s) to be plotted will be mapped to the domain + image space before plotting - useful for non-standard image orientations. + + crop : boolean + if true, the image(s) will be cropped to their bounding boxes, resulting + in a potentially smaller image size. + if false, the image(s) will not be cropped + + scale : boolean or 2-tuple + if true, nothing will happen to intensities of image(s) and overlay(s) + if false, dynamic range will be maximized when visualizing overlays + if 2-tuple, the image will be dynamically scaled between these quantiles + + reverse : boolean + if true, the order in which the slices are plotted will be reversed. + This is useful if you want to plot from the front of the brain first + to the back of the brain, or vice-versa + + title : string + add a title to the plot + + filename : string + if given, the resulting image will be saved to this file + + dpi : integer + determines resolution of image if saved to file. Higher values + result in higher resolution images, but at a cost of having a + larger file size + + resample : bool + if true, resample image if spacing is very unbalanced. + + Example + ------- + >>> import ants + >>> import numpy as np + >>> img = ants.image_read(ants.get_data('r16')) + >>> segs = img.kmeans_segmentation(k=3)['segmentation'] + >>> ants.plot(img, segs*(segs==1), crop=True) + >>> ants.plot(img, segs*(segs==1), crop=False) + >>> mni = ants.image_read(ants.get_data('mni')) + >>> segs = mni.kmeans_segmentation(k=3)['segmentation'] + >>> ants.plot(mni, segs*(segs==1), crop=False) + """ + if (axis == "x") or (axis == "saggittal"): + axis = 0 + if (axis == "y") or (axis == "coronal"): + axis = 1 + if (axis == "z") or (axis == "axial"): + axis = 2 + + def mirror_matrix(x): + return x[::-1, :] + + def rotate270_matrix(x): + return mirror_matrix(x.T) + + def rotate180_matrix(x): + return x[::-1, ::-1] + + def rotate90_matrix(x): + return x.T + + def reorient_slice(x, axis): + if axis != 2: + x = rotate90_matrix(x) + if axis == 2: + x = rotate270_matrix(x) + x = mirror_matrix(x) + return x + + + # handle `image` argument + if isinstance(image, str): + image = ants.image_read(image) + if not ants.is_image(image): + raise ValueError("image argument must be an ANTsImage") + + if np.all(np.equal(image.numpy(), 0.0)): + warnings.warn("Image must be non-zero. will not plot.") + return + + # need this hack because of a weird NaN warning from matplotlib with overlays + warnings.simplefilter("ignore") + + if (image.pixeltype not in {"float", "double"}) or (image.is_rgb): + scale = False # turn off scaling if image is discrete + + # handle `overlay` argument + if overlay is not None: + if isinstance(overlay, str): + overlay = ants.image_read(overlay) + if vminol is None: + vminol = overlay.min() + if vmaxol is None: + vmaxol = overlay.max() + if not ants.is_image(overlay): + raise ValueError("overlay argument must be an ANTsImage") + if overlay.components > 1: + raise ValueError("overlay cannot have more than one voxel component") + + if not ants.image_physical_space_consistency(image, overlay): + overlay = ants.resample_image_to_target(overlay, image, interp_type="nearestNeighbor") + + if blend: + if alpha == 1: + alpha = 0.5 + image = image * alpha + overlay * (1 - alpha) + overlay = None + alpha = 1.0 + + # handle `domain_image_map` argument + if domain_image_map is not None: + tx = ants.new_ants_transform( + precision="float", + transform_type="AffineTransform", + dimension=image.dimension, + ) + image = ants.apply_ants_transform_to_image(tx, image, domain_image_map) + if overlay is not None: + overlay = ants.apply_ants_transform_to_image( + tx, overlay, domain_image_map, interpolation="nearestNeighbor" + ) + + ## single-channel images ## + if image.components == 1: + + # potentially crop image + if crop: + plotmask = image.get_mask(cleanup=0) + if plotmask.max() == 0: + plotmask += 1 + image = image.crop_image(plotmask) + if overlay is not None: + overlay = overlay.crop_image(plotmask) + + # potentially find dynamic range + if scale == True: + vmin, vmax = image.quantile((0.05, 0.95)) + elif isinstance(scale, (list, tuple)): + if len(scale) != 2: + raise ValueError( + "scale argument must be boolean or list/tuple with two values" + ) + vmin, vmax = image.quantile(scale) + else: + vmin = None + vmax = None + + # Plot 2D image + if image.dimension == 2: + + img_arr = image.numpy() + img_arr = rotate90_matrix(img_arr) + + if not black_bg: + img_arr[img_arr < image.quantile(bg_thresh_quant)] = image.quantile( + bg_val_quant + ) + + if overlay is not None: + ov_arr = overlay.numpy() + mask = ov_arr == 0 + mask = np.ma.masked_where(mask == 0, mask) + ov_arr = np.ma.masked_array(ov_arr, mask) + ov_arr = rotate90_matrix(ov_arr) + + fig = plt.figure() + if title is not None: + fig.suptitle( + title, fontsize=title_fontsize, x=0.5 + title_dx, y=0.95 + title_dy + ) + + ax = plt.subplot(111) + + # plot main image + im = ax.imshow(img_arr, cmap=cmap, alpha=alpha, vmin=vmin, vmax=vmax) + + if overlay is not None: + im = ax.imshow(ov_arr, alpha=overlay_alpha, cmap=overlay_cmap, + vmin=vminol, vmax=vmaxol ) + + if cbar: + cbar_orient = "vertical" if cbar_vertical else "horizontal" + fig.colorbar(im, orientation=cbar_orient) + + plt.axis("off") + + # Plot 3D image + elif image.dimension == 3: + # resample image if spacing is very unbalanced + spacing = [s for i, s in enumerate(image.spacing) if i != axis] + was_resampled = False + if (max(spacing) / min(spacing)) > 3.0 and resample: + was_resampled = True + new_spacing = (1, 1, 1) + image = image.resample_image(tuple(new_spacing)) + if overlay is not None: + overlay = overlay.resample_image(tuple(new_spacing)) + + if reorient: + image = image.reorient_image2("LAI") + img_arr = image.numpy() + # reorder dims so that chosen axis is first + img_arr = np.rollaxis(img_arr, axis) + + if overlay is not None: + if reorient: + overlay = overlay.reorient_image2("LAI") + ov_arr = overlay.numpy() + mask = ov_arr == 0 + mask = np.ma.masked_where(mask == 0, mask) + ov_arr = np.ma.masked_array(ov_arr, mask) + ov_arr = np.rollaxis(ov_arr, axis) + + if slices is None: + if not isinstance(slice_buffer, (list, tuple)): + if slice_buffer is None: + slice_buffer = ( + int(img_arr.shape[1] * 0.1), + int(img_arr.shape[2] * 0.1), + ) + else: + slice_buffer = (slice_buffer, slice_buffer) + nonzero = np.where(img_arr.sum(axis=(1, 2)) > 0.01)[0] + min_idx = nonzero[0] + slice_buffer[0] + max_idx = nonzero[-1] - slice_buffer[1] + if min_idx > max_idx: + temp = min_idx + min_idx = max_idx + max_idx = temp + if max_idx > nonzero.max(): + max_idx = nonzero.max() + if min_idx < 0: + min_idx = 0 + slice_idxs = np.linspace(min_idx, max_idx, nslices).astype("int") + if reverse: + slice_idxs = np.array(list(reversed(slice_idxs))) + else: + if isinstance(slices, (int, float)): + slices = [slices] + # if all slices are less than 1, infer that they are relative slices + if sum([s > 1 for s in slices]) == 0: + slices = [int(s * img_arr.shape[0]) for s in slices] + slice_idxs = slices + nslices = len(slices) + + if was_resampled: + # re-calculate slices to account for new image shape + slice_idxs = np.unique( + np.array( + [ + int(s * (image.shape[axis] / img_arr.shape[0])) + for s in slice_idxs + ] + ) + ) + + # only have one row if nslices <= 6 and user didnt specify ncol + if ncol is None: + if nslices <= 6: + ncol = nslices + else: + ncol = int(round(math.sqrt(nslices))) + + # calculate grid size + nrow = math.ceil(nslices / ncol) + xdim = img_arr.shape[2] + ydim = img_arr.shape[1] + + dim_ratio = ydim / xdim + fig = plt.figure( + figsize=((ncol + 1) * figsize * dim_ratio, (nrow + 1) * figsize) + ) + if title is not None: + fig.suptitle( + title, fontsize=title_fontsize, x=0.5 + title_dx, y=0.95 + title_dy + ) + + gs = gridspec.GridSpec( + nrow, + ncol, + wspace=0.0, + hspace=0.0, + top=1.0 - 0.5 / (nrow + 1), + bottom=0.5 / (nrow + 1), + left=0.5 / (ncol + 1), + right=1 - 0.5 / (ncol + 1), + ) + + slice_idx_idx = 0 + for i in range(nrow): + for j in range(ncol): + if slice_idx_idx < len(slice_idxs): + imslice = img_arr[slice_idxs[slice_idx_idx]] + imslice = reorient_slice(imslice, axis) + if not black_bg: + imslice[ + imslice < image.quantile(bg_thresh_quant) + ] = image.quantile(bg_val_quant) + else: + imslice = np.zeros_like(img_arr[0]) + imslice = reorient_slice(imslice, axis) + + ax = plt.subplot(gs[i, j]) + im = ax.imshow(imslice, cmap=cmap, vmin=vmin, vmax=vmax) + + if overlay is not None: + if slice_idx_idx < len(slice_idxs): + ovslice = ov_arr[slice_idxs[slice_idx_idx]] + ovslice = reorient_slice(ovslice, axis) + im = ax.imshow( + ovslice, alpha=overlay_alpha, cmap=overlay_cmap, + vmin=vminol, vmax=vmaxol ) + ax.axis("off") + slice_idx_idx += 1 + + if cbar: + cbar_start = (1 - cbar_length) / 2 + if cbar_vertical: + cax = fig.add_axes([0.9 + cbar_dx, cbar_start, 0.03, cbar_length]) + cbar_orient = "vertical" + else: + cax = fig.add_axes([cbar_start, 0.08 + cbar_dx, cbar_length, 0.03]) + cbar_orient = "horizontal" + fig.colorbar(im, cax=cax, orientation=cbar_orient) + + ## multi-channel images ## + elif image.has_components: + raise Exception('Plotting images with components is not currently supported.') + + if filename is not None: + filename = os.path.expanduser(filename) + plt.savefig(filename, dpi=dpi, transparent=True, bbox_inches="tight") + plt.close(fig) + else: + plt.show() + + # turn warnings back to default + warnings.simplefilter("default") + + diff --git a/MindEyeV2/antspy/ants/plotting/plot_directory.py b/MindEyeV2/antspy/ants/plotting/plot_directory.py new file mode 100644 index 0000000000000000000000000000000000000000..6790acb04be58080e8fd31776bd20178ebbf763c --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/plot_directory.py @@ -0,0 +1,118 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "plot_directory" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +import numpy as np + +import ants + + +def plot_directory( + directory, + recursive=False, + regex="*", + save_prefix="", + save_suffix="", + axis=None, + **kwargs +): + """ + Create and save an ANTsPy plot for every image matching a given regular + expression in a directory, optionally recursively. This is a good function + for quick visualize exploration of all of images in a directory + + ANTsR function: N/A + + Arguments + --------- + directory : string + directory in which to search for images and plot them + + recursive : boolean + If true, this function will search through all directories under + the given directory recursively to make plots. + If false, this function will only create plots for images in the + given directory + + regex : string + regular expression used to filter out certain filenames or suffixes + + save_prefix : string + sub-string that will be appended to the beginning of all saved plot filenames. + Default is to add nothing. + + save_suffix : string + sub-string that will be appended to the end of all saved plot filenames. + Default is add nothing. + + kwargs : keyword arguments + any additional arguments to pass onto the `ants.plot` function. + e.g. overlay, alpha, cmap, etc. See `ants.plot` for more options. + + Example + ------- + >>> import ants + >>> ants.plot_directory(directory='~/desktop/testdir', + recursive=False, regex='*') + """ + + def has_acceptable_suffix(fname): + suffixes = {".nii.gz"} + return sum([fname.endswith(sx) for sx in suffixes]) > 0 + + if directory.startswith("~"): + directory = os.path.expanduser(directory) + + if not os.path.isdir(directory): + raise ValueError("directory %s does not exist!" % directory) + + for root, dirnames, fnames in os.walk(directory): + for fname in fnames: + if fnmatch.fnmatch(fname, regex) and has_acceptable_suffix(fname): + load_fname = os.path.join(root, fname) + fname = fname.replace(".".join(fname.split(".")[1:]), "png") + fname = fname.replace(".png", "%s.png" % save_suffix) + fname = "%s%s" % (save_prefix, fname) + save_fname = os.path.join(root, fname) + img = ants.image_read(load_fname) + + if axis is None: + axis_range = [i for i in range(img.dimension)] + else: + axis_range = axis if isinstance(axis, (list, tuple)) else [axis] + + if img.dimension > 2: + for axis_idx in axis_range: + filename = save_fname.replace(".png", "_axis%i.png" % axis_idx) + ncol = int(math.sqrt(img.shape[axis_idx])) + ants.plot( + img, + axis=axis_idx, + nslices=img.shape[axis_idx], + ncol=ncol, + filename=filename, + **kwargs + ) + else: + filename = save_fname + ants.plot(img, filename=filename, **kwargs) diff --git a/MindEyeV2/antspy/ants/plotting/plot_grid.py b/MindEyeV2/antspy/ants/plotting/plot_grid.py new file mode 100644 index 0000000000000000000000000000000000000000..54124c57277ad266571893f7be423511bf9ea493 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/plot_grid.py @@ -0,0 +1,355 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "plot_grid" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +import numpy as np + + +def plot_grid( + images, + slices=None, + axes=2, + # general figure arguments + figsize=1.0, + rpad=0, + cpad=0, + vmin=None, + vmax=None, + colorbar=True, + cmap="Greys_r", + # title arguments + title=None, + tfontsize=20, + title_dx=0, + title_dy=0, + # row arguments + rlabels=None, + rfontsize=14, + rfontcolor="white", + rfacecolor="black", + # column arguments + clabels=None, + cfontsize=14, + cfontcolor="white", + cfacecolor="black", + # save arguments + filename=None, + dpi=400, + transparent=True, + # other args + **kwargs +): + """ + Plot a collection of images in an arbitrarily-defined grid + + Matplotlib named colors: https://matplotlib.org/examples/color/named_colors.html + + Arguments + --------- + images : list of ANTsImage types + image(s) to plot. + if one image, this image will be used for all grid locations. + if multiple images, they should be arrange in a list the same + shape as the `gridsize` argument. + + slices : integer or list of integers + slice indices to plot + if one integer, this slice index will be used for all images + if multiple integers, they should be arranged in a list the same + shape as the `gridsize` argument + + axes : integer or list of integers + axis or axes along which to plot image slices + if one integer, this axis will be used for all images + if multiple integers, they should be arranged in a list the same + shape as the `gridsize` argument + + Example + ------- + >>> import ants + >>> import numpy as np + >>> mni1 = ants.image_read(ants.get_data('mni')) + >>> mni2 = mni1.smooth_image(1.) + >>> mni3 = mni1.smooth_image(2.) + >>> mni4 = mni1.smooth_image(3.) + >>> images = np.asarray([[mni1, mni2], + ... [mni3, mni4]]) + >>> slices = np.asarray([[100, 100], + ... [100, 100]]) + >>> ants.plot_grid(images=images, slices=slices, title='2x2 Grid') + >>> images2d = np.asarray([[mni1.slice_image(2,100), mni2.slice_image(2,100)], + ... [mni3.slice_image(2,100), mni4.slice_image(2,100)]]) + >>> ants.plot_grid(images=images2d, title='2x2 Grid Pre-Sliced') + >>> ants.plot_grid(images.reshape(1,4), slices.reshape(1,4), title='1x4 Grid') + >>> ants.plot_grid(images.reshape(4,1), slices.reshape(4,1), title='4x1 Grid') + + >>> # Padding between rows and/or columns + >>> ants.plot_grid(images, slices, cpad=0.02, title='Col Padding') + >>> ants.plot_grid(images, slices, rpad=0.02, title='Row Padding') + >>> ants.plot_grid(images, slices, rpad=0.02, cpad=0.02, title='Row and Col Padding') + + >>> # Adding plain row and/or column labels + >>> ants.plot_grid(images, slices, title='Adding Row Labels', rlabels=['Row #1', 'Row #2']) + >>> ants.plot_grid(images, slices, title='Adding Col Labels', clabels=['Col #1', 'Col #2']) + >>> ants.plot_grid(images, slices, title='Row and Col Labels', + rlabels=['Row 1', 'Row 2'], clabels=['Col 1', 'Col 2']) + + >>> # Making a publication-quality image + >>> images = np.asarray([[mni1, mni2, mni2], + ... [mni3, mni4, mni4]]) + >>> slices = np.asarray([[100, 100, 100], + ... [100, 100, 100]]) + >>> axes = np.asarray([[0, 1, 2], + [0, 1, 2]]) + >>> ants.plot_grid(images, slices, axes, title='Publication Figures with ANTsPy', + tfontsize=20, title_dy=0.03, title_dx=-0.04, + rlabels=['Row 1', 'Row 2'], + clabels=['Col 1', 'Col 2', 'Col 3'], + rfontsize=16, cfontsize=16) + """ + + def mirror_matrix(x): + return x[::-1, :] + + def rotate270_matrix(x): + return mirror_matrix(x.T) + + def rotate180_matrix(x): + return x[::-1, ::-1] + + def rotate90_matrix(x): + return mirror_matrix(x).T + + def flip_matrix(x): + return mirror_matrix(rotate180_matrix(x)) + + def reorient_slice(x, axis): + if axis != 1: + x = rotate90_matrix(x) + if axis == 1: + x = rotate90_matrix(x) + x = mirror_matrix(x) + return x + + def slice_image(img, axis, idx): + if axis == 0: + return img[idx, :, :].numpy() + elif axis == 1: + return img[:, idx, :].numpy() + elif axis == 2: + return img[:, :, idx].numpy() + elif axis == -1: + return img[:, :, idx].numpy() + elif axis == -2: + return img[:, idx, :].numpy() + elif axis == -3: + return img[idx, :, :].numpy() + else: + raise ValueError("axis %i not valid" % axis) + + if isinstance(images, np.ndarray): + images = images.tolist() + if not isinstance(images, list): + raise ValueError("images argument must be of type list") + if not isinstance(images[0], list): + images = [images] + + if slices is None: + one_slice = True + if isinstance(slices, int): + one_slice = True + if isinstance(slices, np.ndarray): + slices = slices.tolist() + if isinstance(slices, list): + one_slice = False + if not isinstance(slices[0], list): + slices = [slices] + nslicerow = len(slices) + nslicecol = len(slices[0]) + + nrow = len(images) + ncol = len(images[0]) + + if rlabels is None: + rlabels = [None] * nrow + if clabels is None: + clabels = [None] * ncol + + if not one_slice: + if (nrow != nslicerow) or (ncol != nslicecol): + raise ValueError( + "`images` arg shape (%i,%i) must equal `slices` arg shape (%i,%i)!" + % (nrow, ncol, nslicerow, nslicecol) + ) + + fig = plt.figure(figsize=((ncol + 1) * 2.5 * figsize, (nrow + 1) * 2.5 * figsize)) + + if title is not None: + basex = 0.5 + basey = 0.9 if clabels[0] is None else 0.95 + fig.suptitle(title, fontsize=tfontsize, x=basex + title_dx, y=basey + title_dy) + + if (cpad > 0) and (rpad > 0): + bothgridpad = max(cpad, rpad) + cpad = 0 + rpad = 0 + else: + bothgridpad = 0.0 + + gs = gridspec.GridSpec( + nrow, + ncol, + wspace=bothgridpad, + hspace=0.0, + top=1.0 - 0.5 / (nrow + 1), + bottom=0.5 / (nrow + 1) + cpad, + left=0.5 / (ncol + 1) + rpad, + right=1 - 0.5 / (ncol + 1), + ) + + if isinstance(vmin, (int, float)): + vmins = [vmin] * nrow + elif vmin is None: + vmins = [None] * nrow + else: + vmins = vmin + + if isinstance(vmax, (int, float)): + vmaxs = [vmax] * nrow + elif vmax is None: + vmaxs = [None] * nrow + else: + vmaxs = vmax + + if isinstance(cmap, str): + cmaps = [cmap] * nrow + elif cmap is None: + cmaps = [None] * nrow + else: + cmaps = cmap + + for rowidx, rvmin, rvmax, rcmap in zip(range(nrow), vmins, vmaxs, cmaps): + for colidx in range(ncol): + ax = plt.subplot(gs[rowidx, colidx]) + + if colidx == 0: + if rlabels[rowidx] is not None: + bottom, height = 0.25, 0.5 + top = bottom + height + # add label text + ax.text( + -0.07, + 0.5 * (bottom + top), + rlabels[rowidx], + horizontalalignment="right", + verticalalignment="center", + rotation="vertical", + transform=ax.transAxes, + color=rfontcolor, + fontsize=rfontsize, + ) + + # add label background + extra = 0.3 if rowidx == 0 else 0.0 + + rect = patches.Rectangle( + (-0.3, 0), + 0.3, + 1.0 + extra, + facecolor=rfacecolor, + alpha=1.0, + transform=ax.transAxes, + clip_on=False, + ) + ax.add_patch(rect) + + if rowidx == 0: + if clabels[colidx] is not None: + bottom, height = 0.25, 0.5 + left, width = 0.25, 0.5 + right = left + width + top = bottom + height + ax.text( + 0.5 * (left + right), + 0.09 + top + bottom, + clabels[colidx], + horizontalalignment="center", + verticalalignment="center", + rotation="horizontal", + transform=ax.transAxes, + color=cfontcolor, + fontsize=cfontsize, + ) + + # add label background + rect = patches.Rectangle( + (0, 1.0), + 1.0, + 0.3, + facecolor=cfacecolor, + alpha=1.0, + transform=ax.transAxes, + clip_on=False, + ) + ax.add_patch(rect) + + tmpimg = images[rowidx][colidx] + if isinstance(axes, int): + tmpaxis = axes + else: + tmpaxis = axes[rowidx][colidx] + + if tmpimg.dimension == 2: + tmpslice = tmpimg.numpy() + tmpslice = reorient_slice(tmpslice, tmpaxis) + else: + sliceidx = slices[rowidx][colidx] if not one_slice else slices + if sliceidx is None: + sliceidx = math.ceil(tmpimg.shape[tmpaxis] / 2) + tmpslice = slice_image(tmpimg, tmpaxis, sliceidx) + tmpslice = reorient_slice(tmpslice, tmpaxis) + + im = ax.imshow(tmpslice, cmap=rcmap, aspect="auto", vmin=rvmin, vmax=rvmax) + ax.axis("off") + + # A colorbar solution with make_axes_locatable will not allow y-scaling of the colorbar. + # from mpl_toolkits.axes_grid1 import make_axes_locatable + # divider = make_axes_locatable(ax) + # cax = divider.append_axes('right', size='5%', pad=0.05) + if colorbar: + axins = inset_axes(ax, + width="5%", # width = 5% of parent_bbox width + height="90%", # height : 50% + loc='center left', + bbox_to_anchor=(1.03, 0., 1, 1), + bbox_transform=ax.transAxes, + borderpad=0, + ) + fig.colorbar(im, cax=axins, orientation='vertical') + + if filename is not None: + filename = os.path.expanduser(filename) + plt.savefig(filename, dpi=dpi, transparent=transparent, bbox_inches="tight") + plt.close(fig) + else: + plt.show() diff --git a/MindEyeV2/antspy/ants/plotting/plot_hist.py b/MindEyeV2/antspy/ants/plotting/plot_hist.py new file mode 100644 index 0000000000000000000000000000000000000000..14d50a8479dee635246d316da2d81bedaaa48a99 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/plot_hist.py @@ -0,0 +1,73 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "plot_hist" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +import numpy as np +from ants.decorators import image_method + +@image_method +def plot_hist( + image, + threshold=0.0, + fit_line=False, + normfreq=True, + ## plot label arguments + title=None, + grid=True, + xlabel=None, + ylabel=None, + ## other plot arguments + facecolor="green", + alpha=0.75, +): + """ + Plot a histogram from an ANTsImage + + Arguments + --------- + image : ANTsImage + image from which histogram will be created + """ + img_arr = image.numpy().flatten() + img_arr = img_arr[np.abs(img_arr) > threshold] + + if normfreq != False: + normfreq = 1.0 if normfreq == True else normfreq + n, bins, patches = plt.hist( + img_arr, 50, facecolor=facecolor, alpha=alpha + ) + + if fit_line: + # add a 'best fit' line + y = mlab.normpdf(bins, img_arr.mean(), img_arr.std()) + l = plt.plot(bins, y, "r--", linewidth=1) + + if xlabel is not None: + plt.xlabel(xlabel) + if ylabel is not None: + plt.ylabel(ylabel) + if title is not None: + plt.title(title) + + plt.grid(grid) + plt.show() diff --git a/MindEyeV2/antspy/ants/plotting/plot_ortho.py b/MindEyeV2/antspy/ants/plotting/plot_ortho.py new file mode 100644 index 0000000000000000000000000000000000000000..5fa35e061b5800b845341a4e6acc56603281f5b2 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/plot_ortho.py @@ -0,0 +1,612 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "plot_ortho" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +import numpy as np +import ants +from ants.decorators import image_method + +@image_method +def plot_ortho( + image, + overlay=None, + reorient=True, + blend=False, + # xyz arguments + xyz=None, + xyz_lines=True, + xyz_color="red", + xyz_alpha=0.6, + xyz_linewidth=2, + xyz_pad=5, + orient_labels=True, + # base image arguments + alpha=1, + cmap="Greys_r", + # overlay arguments + overlay_cmap="jet", + overlay_alpha=0.9, + cbar=False, + cbar_length=0.8, + cbar_dx=0.0, + cbar_vertical=True, + # background arguments + black_bg=True, + bg_thresh_quant=0.01, + bg_val_quant=0.99, + # scale/crop/domain arguments + crop=False, + scale=False, + domain_image_map=None, + # title arguments + title=None, + titlefontsize=24, + title_dx=0, + title_dy=0, + # 4th panel text arguemnts + text=None, + textfontsize=24, + textfontcolor="white", + text_dx=0, + text_dy=0, + # save & size arguments + filename=None, + dpi=500, + figsize=1.0, + flat=False, + transparent=True, + resample=False, + allow_xyz_change=True, +): + """ + Plot an orthographic view of a 3D image + + Use mask_image and/or threshold_image to preprocess images to be be + overlaid and display the overlays in a given range. See the wiki examples. + + ANTsR function: N/A + + Arguments + --------- + image : ANTsImage + image to plot + + overlay : ANTsImage + image to overlay on base image + + xyz : list or tuple of 3 integers + selects index location on which to center display + if given, solid lines will be drawn to converge at this coordinate. + This is useful for pinpointing a specific location in the image. + + flat : boolean + if true, the ortho image will be plot in one row + if false, the ortho image will be a 2x2 grid with the bottom + left corner blank + + cmap : string + colormap to use for base image. See matplotlib. + + overlay_cmap : string + colormap to use for overlay images, if applicable. See matplotlib. + + overlay_alpha : float + level of transparency for any overlays. Smaller value means + the overlay is more transparent. See matplotlib. + + cbar: boolean + if true, a colorbar will be added to the plot + + cbar_length: float + length of the colorbar relative to the image + + cbar_dx: float + horizontal shift of the colorbar relative to the image + + cbar_vertical: boolean + if true, the colorbar will be vertical, if false, it will be + horizontal underneath the image + + axis : integer + which axis to plot along if image is 3D + + black_bg : boolean + if True, the background of the image(s) will be black. + if False, the background of the image(s) will be determined by the + values `bg_thresh_quant` and `bg_val_quant`. + + bg_thresh_quant : float + if white_bg=True, the background will be determined by thresholding + the image at the `bg_thresh` quantile value and setting the background + intensity to the `bg_val` quantile value. + This value should be in [0, 1] - somewhere around 0.01 is recommended. + - equal to 1 will threshold the entire image + - equal to 0 will threshold none of the image + + bg_val_quant : float + if white_bg=True, the background will be determined by thresholding + the image at the `bg_thresh` quantile value and setting the background + intensity to the `bg_val` quantile value. + This value should be in [0, 1] + - equal to 1 is pure white + - equal to 0 is pure black + - somewhere in between is gray + + domain_image_map : ANTsImage + this input ANTsImage or list of ANTsImage types contains a reference image + `domain_image` and optional reference mapping named `domainMap`. + If supplied, the image(s) to be plotted will be mapped to the domain + image space before plotting - useful for non-standard image orientations. + + crop : boolean + if true, the image(s) will be cropped to their bounding boxes, resulting + in a potentially smaller image size. + if false, the image(s) will not be cropped + + scale : boolean or 2-tuple + if true, nothing will happen to intensities of image(s) and overlay(s) + if false, dynamic range will be maximized when visualizing overlays + if 2-tuple, the image will be dynamically scaled between these quantiles + + title : string + add a title to the plot + + filename : string + if given, the resulting image will be saved to this file + + dpi : integer + determines resolution of image if saved to file. Higher values + result in higher resolution images, but at a cost of having a + larger file size + + resample : resample image in case of unbalanced spacing + + allow_xyz_change : boolean will attempt to adjust xyz after padding + + Example + ------- + >>> import ants + >>> mni = ants.image_read(ants.get_data('mni')) + >>> ants.plot_ortho(mni, xyz=(100,100,100)) + >>> mni2 = mni.threshold_image(7000, mni.max()) + >>> ants.plot_ortho(mni, overlay=mni2) + >>> ants.plot_ortho(mni, overlay=mni2, flat=True) + >>> ants.plot_ortho(mni, overlay=mni2, xyz=(110,110,110), xyz_lines=False, + text='Lines Turned Off', textfontsize=22) + >>> ants.plot_ortho(mni, mni2, xyz=(120,100,100), + text=' Example \nOrtho Text', textfontsize=26, + title='Example Ortho Title', titlefontsize=26) + """ + + def mirror_matrix(x): + return x[::-1, :] + + def rotate270_matrix(x): + return mirror_matrix(x.T) + + def reorient_slice(x, axis): + return rotate270_matrix(x) + + # need this hack because of a weird NaN warning from matplotlib with overlays + warnings.simplefilter("ignore") + + # handle `image` argument + if isinstance(image, str): + image = ants.image_read(image) + if not ants.is_image(image): + raise ValueError("image argument must be an ANTsImage") + if image.dimension != 3: + raise ValueError("Input image must have 3 dimensions!") + + # handle `overlay` argument + if overlay is not None: + if isinstance(overlay, str): + overlay = ants.image_read(overlay) + vminol = overlay.min() + vmaxol = overlay.max() + if not ants.is_image(overlay): + raise ValueError("overlay argument must be an ANTsImage") + if overlay.components > 1: + raise ValueError("overlay cannot have more than one voxel component") + if overlay.dimension != 3: + raise ValueError("Overlay image must have 3 dimensions!") + + if not ants.image_physical_space_consistency(image, overlay): + overlay = ants.resample_image_to_target(overlay, image, interp_type="linear") + + if blend: + if alpha == 1: + alpha = 0.5 + image = image * alpha + overlay * (1 - alpha) + overlay = None + alpha = 1.0 + + if image.pixeltype not in {"float", "double"}: + scale = False # turn off scaling if image is discrete + + # reorient images + if reorient != False: + if reorient == True: + reorient = "RPI" + image = image.reorient_image2("RPI") + if overlay is not None: + overlay = overlay.reorient_image2("RPI") + + # handle `slices` argument + if xyz is None: + xyz = [int(s / 2) for s in image.shape] + for i in range(3): + if xyz[i] is None: + xyz[i] = int(image.shape[i] / 2) + + # resample image if spacing is very unbalanced + spacing = [s for i, s in enumerate(image.spacing)] + if (max(spacing) / min(spacing)) > 3.0 and resample: + new_spacing = (1, 1, 1) + image = image.resample_image(tuple(new_spacing)) + if overlay is not None: + overlay = overlay.resample_image(tuple(new_spacing)) + xyz = [ + int(sl * (sold / snew)) for sl, sold, snew in zip(xyz, spacing, new_spacing) + ] + + + # potentially crop image + if crop: + plotmask = image.get_mask(cleanup=0) + if plotmask.max() == 0: + plotmask += 1 + image = image.crop_image(plotmask) + if overlay is not None: + overlay = overlay.crop_image(plotmask) + + # pad images + if True: + image, lowpad, uppad = image.pad_image(return_padvals=True) + if allow_xyz_change: + xyz = [v + l for v, l in zip(xyz, lowpad)] + if overlay is not None: + overlay = overlay.pad_image() + + + # handle `domain_image_map` argument + if domain_image_map is not None: + if ants.is_image(domain_image_map): + tx = ants.new_ants_transform( + precision="float", + transform_type="AffineTransform", + dimension=image.dimension, + ) + image = ants.apply_ants_transform_to_image(tx, image, domain_image_map) + if overlay is not None: + overlay = ants.apply_ants_transform_to_image( + tx, overlay, domain_image_map, interpolation="linear" + ) + else: + raise Exception('The domain_image_map must be an image.') + + ## single-channel images ## + if image.components == 1: + + # potentially find dynamic range + if scale == True: + vmin, vmax = image.quantile((0.05, 0.95)) + elif isinstance(scale, (list, tuple)): + if len(scale) != 2: + raise ValueError( + "scale argument must be boolean or list/tuple with two values" + ) + vmin, vmax = image.quantile(scale) + else: + vmin = None + vmax = None + + if not flat: + nrow = 2 + ncol = 2 + else: + nrow = 1 + ncol = 3 + + fig = plt.figure(figsize=(9 * figsize, 9 * figsize)) + if title is not None: + basey = 0.88 if not flat else 0.66 + basex = 0.5 + fig.suptitle( + title, fontsize=titlefontsize, color=textfontcolor, x=basex + title_dx, y=basey + title_dy + ) + + gs = gridspec.GridSpec( + nrow, + ncol, + wspace=0.0, + hspace=0.0, + top=1.0 - 0.5 / (nrow + 1), + bottom=0.5 / (nrow + 1), + left=0.5 / (ncol + 1), + right=1 - 0.5 / (ncol + 1), + ) + + # pad image to have isotropic array dimensions + imageReturn = image.clone() + image = image.numpy() + overlayReturn = None + if overlay is not None: + overlayReturn = overlay.clone() + overlay = overlay.numpy() + if overlay.dtype not in ["uint8", "uint32"]: + overlay = np.ma.masked_where( np.abs(overlay) <= 1e-16, overlay) +# overlay[np.abs(overlay) == 0] = np.nan + + yz_slice = reorient_slice(image[xyz[0], :, :], 0) + ax = plt.subplot(gs[0, 0]) + ax.imshow(yz_slice, cmap=cmap, vmin=vmin, vmax=vmax) + if overlay is not None: + yz_overlay = reorient_slice(overlay[xyz[0], :, :], 0) + ax.imshow(yz_overlay, alpha=overlay_alpha, cmap=overlay_cmap, vmin=vminol, vmax=vmaxol ) + if xyz_lines: + # add lines + l = mlines.Line2D( + [yz_slice.shape[0] - xyz[1], yz_slice.shape[0] - xyz[1]], + [xyz_pad, yz_slice.shape[0] - xyz_pad], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + l = mlines.Line2D( + [xyz_pad, yz_slice.shape[1] - xyz_pad], + [yz_slice.shape[1] - xyz[2], yz_slice.shape[1] - xyz[2]], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + if orient_labels: + ax.text( + 0.5, + 0.98, + "S", + horizontalalignment="center", + verticalalignment="top", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.5, + 0.02, + "I", + horizontalalignment="center", + verticalalignment="bottom", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.98, + 0.5, + "A", + horizontalalignment="right", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.02, + 0.5, + "P", + horizontalalignment="left", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.axis("off") + + xz_slice = reorient_slice(image[:, xyz[1], :], 1) + ax = plt.subplot(gs[0, 1]) + ax.imshow(xz_slice, cmap=cmap, vmin=vmin, vmax=vmax) + if overlay is not None: + xz_overlay = reorient_slice(overlay[:, xyz[1], :], 1) + ax.imshow(xz_overlay, alpha=overlay_alpha, cmap=overlay_cmap, vmin=vminol, vmax=vmaxol ) + + if xyz_lines: + # add lines + l = mlines.Line2D( + [xz_slice.shape[0] - xyz[0], xz_slice.shape[0] - xyz[0]], + [xyz_pad, xz_slice.shape[0] - xyz_pad], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + l = mlines.Line2D( + [xyz_pad, xz_slice.shape[1] - xyz_pad], + [xz_slice.shape[1] - xyz[2], xz_slice.shape[1] - xyz[2]], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + if orient_labels: + ax.text( + 0.5, + 0.98, + "S", + horizontalalignment="center", + verticalalignment="top", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.5, + 0.02, + "I", + horizontalalignment="center", + verticalalignment="bottom", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.98, + 0.5, + "L", + horizontalalignment="right", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.02, + 0.5, + "R", + horizontalalignment="left", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.axis("off") + + xy_slice = reorient_slice(image[:, :, xyz[2]], 2) + if not flat: + ax = plt.subplot(gs[1, 1]) + else: + ax = plt.subplot(gs[0, 2]) + im = ax.imshow(xy_slice, cmap=cmap, vmin=vmin, vmax=vmax) + if overlay is not None: + xy_overlay = reorient_slice(overlay[:, :, xyz[2]], 2) + im = ax.imshow(xy_overlay, alpha=overlay_alpha, cmap=overlay_cmap, vmin=vminol, vmax=vmaxol) + + if xyz_lines: + # add lines + l = mlines.Line2D( + [xy_slice.shape[0] - xyz[0], xy_slice.shape[0] - xyz[0]], + [xyz_pad, xy_slice.shape[0] - xyz_pad], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + l = mlines.Line2D( + [xyz_pad, xy_slice.shape[1] - xyz_pad], + [xy_slice.shape[1] - xyz[1], xy_slice.shape[1] - xyz[1]], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + if orient_labels: + ax.text( + 0.5, + 0.98, + "A", + horizontalalignment="center", + verticalalignment="top", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.5, + 0.02, + "P", + horizontalalignment="center", + verticalalignment="bottom", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.98, + 0.5, + "L", + horizontalalignment="right", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.02, + 0.5, + "R", + horizontalalignment="left", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.axis("off") + + if not flat: + # empty corner + ax = plt.subplot(gs[1, 0]) + if text is not None: + # add text + left, width = 0.25, 0.5 + bottom, height = 0.25, 0.5 + right = left + width + top = bottom + height + ax.text( + 0.5 * (left + right) + text_dx, + 0.5 * (bottom + top) + text_dy, + text, + horizontalalignment="center", + verticalalignment="center", + fontsize=textfontsize, + color=textfontcolor, + transform=ax.transAxes, + ) + # ax.text(0.5, 0.5) + ax.imshow(np.zeros(image.shape[:-1]), cmap="Greys_r") + ax.axis("off") + + if cbar: + cbar_start = (1 - cbar_length) / 2 + if cbar_vertical: + cax = fig.add_axes([0.9 + cbar_dx, cbar_start, 0.03, cbar_length]) + cbar_orient = "vertical" + else: + cax = fig.add_axes([cbar_start, 0.08 + cbar_dx, cbar_length, 0.03]) + cbar_orient = "horizontal" + fig.colorbar(im, cax=cax, orientation=cbar_orient) + + ## multi-channel images ## + elif image.components > 1: + raise ValueError("Multi-channel images not currently supported!") + + if filename is not None: + plt.savefig(filename, dpi=dpi, transparent=transparent) + plt.close(fig) + else: + plt.show() + + # turn warnings back to default + warnings.simplefilter("default") + diff --git a/MindEyeV2/antspy/ants/plotting/plot_ortho_stack.py b/MindEyeV2/antspy/ants/plotting/plot_ortho_stack.py new file mode 100644 index 0000000000000000000000000000000000000000..f8cea10a92b148aed3fa29c736366a1501d11266 --- /dev/null +++ b/MindEyeV2/antspy/ants/plotting/plot_ortho_stack.py @@ -0,0 +1,505 @@ +""" +Functions for plotting ants images +""" + + +__all__ = [ + "plot_ortho_stack" +] + +import fnmatch +import math +import os +import warnings + +from matplotlib import gridspec +import matplotlib.pyplot as plt +import matplotlib.patheffects as path_effects +import matplotlib.lines as mlines +import matplotlib.patches as patches +import matplotlib.mlab as mlab +import matplotlib.animation as animation +from mpl_toolkits.axes_grid1.inset_locator import inset_axes + + +import numpy as np +import ants + + + + +def plot_ortho_stack( + images, + overlays=None, + reorient=True, + # xyz arguments + xyz=None, + xyz_lines=False, + xyz_color="red", + xyz_alpha=0.6, + xyz_linewidth=2, + xyz_pad=5, + # base image arguments + cmap="Greys_r", + alpha=1, + # overlay arguments + overlay_cmap="jet", + overlay_alpha=0.9, + # background arguments + black_bg=True, + bg_thresh_quant=0.01, + bg_val_quant=0.99, + # scale/crop/domain arguments + crop=False, + scale=False, + domain_image_map=None, + # title arguments + title=None, + titlefontsize=24, + title_dx=0, + title_dy=0, + # 4th panel text arguemnts + text=None, + textfontsize=24, + textfontcolor="white", + text_dx=0, + text_dy=0, + # save & size arguments + filename=None, + dpi=500, + figsize=1.0, + colpad=0, + rowpad=0, + transpose=False, + transparent=True, + orient_labels=True, +): + """ + Create a stack of orthographic plots with optional overlays. + + Use mask_image and/or threshold_image to preprocess images to be be + overlaid and display the overlays in a given range. See the wiki examples. + + Example + ------- + >>> import ants + >>> mni = ants.image_read(ants.get_data('mni')) + >>> ch2 = ants.image_read(ants.get_data('ch2')) + >>> ants.plot_ortho_stack([mni,mni,mni]) + """ + + def mirror_matrix(x): + return x[::-1, :] + + def rotate270_matrix(x): + return mirror_matrix(x.T) + + def reorient_slice(x, axis): + return rotate270_matrix(x) + + # need this hack because of a weird NaN warning from matplotlib with overlays + warnings.simplefilter("ignore") + + n_images = len(images) + + # handle `image` argument + for i in range(n_images): + if isinstance(images[i], str): + images[i] = ants.image_read(images[i]) + if not ants.is_image(images[i]): + raise ValueError("image argument must be an ANTsImage") + if images[i].dimension != 3: + raise ValueError("Input image must have 3 dimensions!") + + if overlays is None: + overlays = [None] * n_images + # handle `overlay` argument + for i in range(n_images): + if overlays[i] is not None: + if isinstance(overlays[i], str): + overlays[i] = ants.image_read(overlays[i]) + if not ants.is_image(overlays[i]): + raise ValueError("overlay argument must be an ANTsImage") + if overlays[i].components > 1: + raise ValueError("overlays[i] cannot have more than one voxel component") + if overlays[i].dimension != 3: + raise ValueError("Overlay image must have 3 dimensions!") + + if not ants.image_physical_space_consistency(images[i], overlays[i]): + overlays[i] = ants.resample_image_to_target( + overlays[i], images[i], interp_type="linear" + ) + + for i in range(1, n_images): + if not ants.image_physical_space_consistency(images[0], images[i]): + images[i] = ants.resample_image_to_target( + images[0], images[i], interp_type="linear" + ) + + # reorient images + if reorient != False: + if reorient == True: + reorient = "RPI" + + for i in range(n_images): + images[i] = images[i].reorient_image2(reorient) + + if overlays[i] is not None: + overlays[i] = overlays[i].reorient_image2(reorient) + + # handle `slices` argument + if xyz is None: + xyz = [int(s / 2) for s in images[0].shape] + for i in range(3): + if xyz[i] is None: + xyz[i] = int(images[0].shape[i] / 2) + + # resample image if spacing is very unbalanced + spacing = [s for i, s in enumerate(images[0].spacing)] + if (max(spacing) / min(spacing)) > 3.0: + new_spacing = (1, 1, 1) + for i in range(n_images): + images[i] = images[i].resample_image(tuple(new_spacing)) + if overlays[i] is not None: + overlays[i] = overlays[i].resample_image(tuple(new_spacing)) + xyz = [ + int(sl * (sold / snew)) for sl, sold, snew in zip(xyz, spacing, new_spacing) + ] + + # potentially crop image + if crop: + for i in range(n_images): + plotmask = images[i].get_mask(cleanup=0) + if plotmask.max() == 0: + plotmask += 1 + images[i] = images[i].crop_image(plotmask) + if overlays[i] is not None: + overlays[i] = overlays[i].crop_image(plotmask) + + # pad images + for i in range(n_images): + if i == 0: + images[i], lowpad, uppad = images[i].pad_image(return_padvals=True) + else: + images[i] = images[i].pad_image() + if overlays[i] is not None: + overlays[i] = overlays[i].pad_image() + xyz = [v + l for v, l in zip(xyz, lowpad)] + + # handle `domain_image_map` argument + if domain_image_map is not None: + if ants.is_image(domain_image_map): + tx = ants.new_ants_transform( + precision="float", transform_type="AffineTransform", dimension=3 + ) + for i in range(n_images): + images[i] = ants.apply_ants_transform_to_image( + tx, images[i], domain_image_map + ) + + if overlays[i] is not None: + overlays[i] = ants.apply_ants_transform_to_image( + tx, overlays[i], domain_image_map, interpolation="linear" + ) + else: + raise Exception('The domain_image_map must be an ants image.') + + # potentially find dynamic range + if scale == True: + vmins = [] + vmaxs = [] + for i in range(n_images): + vmin, vmax = images[i].quantile((0.05, 0.95)) + vmins.append(vmin) + vmaxs.append(vmax) + elif isinstance(scale, (list, tuple)): + if len(scale) != 2: + raise ValueError( + "scale argument must be boolean or list/tuple with two values" + ) + vmins = [] + vmaxs = [] + for i in range(n_images): + vmin, vmax = images[i].quantile(scale) + vmins.append(vmin) + vmaxs.append(vmax) + else: + vmin = None + vmax = None + + if not transpose: + nrow = n_images + ncol = 3 + else: + nrow = 3 + ncol = n_images + + fig = plt.figure(figsize=((ncol + 1) * 2.5 * figsize, (nrow + 1) * 2.5 * figsize)) + if title is not None: + basey = 0.93 + basex = 0.5 + fig.suptitle( + title, fontsize=titlefontsize, color=textfontcolor, x=basex + title_dx, y=basey + title_dy + ) + + if (colpad > 0) and (rowpad > 0): + bothgridpad = max(colpad, rowpad) + colpad = 0 + rowpad = 0 + else: + bothgridpad = 0.0 + + gs = gridspec.GridSpec( + nrow, + ncol, + wspace=bothgridpad, + hspace=0.0, + top=1.0 - 0.5 / (nrow + 1), + bottom=0.5 / (nrow + 1) + colpad, + left=0.5 / (ncol + 1) + rowpad, + right=1 - 0.5 / (ncol + 1), + ) + + # pad image to have isotropic array dimensions + vminols=[] + vmaxols=[] + for i in range(n_images): + images[i] = images[i].numpy() + if overlays[i] is not None: + vminols.append( overlays[i].min() ) + vmaxols.append( overlays[i].max() ) + overlays[i] = overlays[i].numpy() + if overlays[i].dtype not in ["uint8", "uint32"]: + overlays[i][np.abs(overlays[i]) == 0] = np.nan + + #################### + #################### + for i in range(n_images): + yz_slice = reorient_slice(images[i][xyz[0], :, :], 0) + if not transpose: + ax = plt.subplot(gs[i, 0]) + else: + ax = plt.subplot(gs[0, i]) + ax.imshow(yz_slice, cmap=cmap, vmin=vmin, vmax=vmax) + if overlays[i] is not None: + yz_overlay = reorient_slice(overlays[i][xyz[0], :, :], 0) + ax.imshow(yz_overlay, alpha=overlay_alpha, cmap=overlay_cmap, + vmin=vminols[i], vmax=vmaxols[i]) + if xyz_lines: + # add lines + l = mlines.Line2D( + [yz_slice.shape[0] - xyz[1], yz_slice.shape[0] - xyz[1]], + [xyz_pad, yz_slice.shape[0] - xyz_pad], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + l = mlines.Line2D( + [xyz_pad, yz_slice.shape[1] - xyz_pad], + [yz_slice.shape[1] - xyz[2], yz_slice.shape[1] - xyz[2]], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + if orient_labels: + ax.text( + 0.5, + 0.98, + "S", + horizontalalignment="center", + verticalalignment="top", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.5, + 0.02, + "I", + horizontalalignment="center", + verticalalignment="bottom", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.98, + 0.5, + "A", + horizontalalignment="right", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.02, + 0.5, + "P", + horizontalalignment="left", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.axis("off") + #################### + #################### + + xz_slice = reorient_slice(images[i][:, xyz[1], :], 1) + if not transpose: + ax = plt.subplot(gs[i, 1]) + else: + ax = plt.subplot(gs[1, i]) + ax.imshow(xz_slice, cmap=cmap, vmin=vmin, vmax=vmax) + if overlays[i] is not None: + xz_overlay = reorient_slice(overlays[i][:, xyz[1], :], 1) + ax.imshow(xz_overlay, alpha=overlay_alpha, cmap=overlay_cmap, + vmin=vminols[i], vmax=vmaxols[i]) + if xyz_lines: + # add lines + l = mlines.Line2D( + [xz_slice.shape[0] - xyz[0], xz_slice.shape[0] - xyz[0]], + [xyz_pad, xz_slice.shape[0] - xyz_pad], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + l = mlines.Line2D( + [xyz_pad, xz_slice.shape[1] - xyz_pad], + [xz_slice.shape[1] - xyz[2], xz_slice.shape[1] - xyz[2]], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + if orient_labels: + ax.text( + 0.5, + 0.98, + "I", + horizontalalignment="center", + verticalalignment="top", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.5, + 0.02, + "S", + horizontalalignment="center", + verticalalignment="bottom", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.98, + 0.5, + "L", + horizontalalignment="right", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.02, + 0.5, + "R", + horizontalalignment="left", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.axis("off") + + #################### + #################### + xy_slice = reorient_slice(images[i][:, :, xyz[2]], 2) + if not transpose: + ax = plt.subplot(gs[i, 2]) + else: + ax = plt.subplot(gs[2, i]) + ax.imshow(xy_slice, cmap=cmap, vmin=vmin, vmax=vmax) + if overlays[i] is not None: + xy_overlay = reorient_slice(overlays[i][:, :, xyz[2]], 2) + ax.imshow(xy_overlay, alpha=overlay_alpha, cmap=overlay_cmap, + vmin=vminols[i], vmax=vmaxols[i]) + if xyz_lines: + # add lines + l = mlines.Line2D( + [xy_slice.shape[0] - xyz[0], xy_slice.shape[0] - xyz[0]], + [xyz_pad, xy_slice.shape[0] - xyz_pad], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + l = mlines.Line2D( + [xyz_pad, xy_slice.shape[1] - xyz_pad], + [xy_slice.shape[1] - xyz[1], xy_slice.shape[1] - xyz[1]], + color=xyz_color, + alpha=xyz_alpha, + linewidth=xyz_linewidth, + ) + ax.add_line(l) + if orient_labels: + ax.text( + 0.5, + 0.98, + "A", + horizontalalignment="center", + verticalalignment="top", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.5, + 0.02, + "P", + horizontalalignment="center", + verticalalignment="bottom", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.98, + 0.5, + "L", + horizontalalignment="right", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.text( + 0.02, + 0.5, + "R", + horizontalalignment="left", + verticalalignment="center", + fontsize=20 * figsize, + color=textfontcolor, + transform=ax.transAxes, + ) + ax.axis("off") + + #################### + #################### + + if filename is not None: + plt.savefig(filename, dpi=dpi, transparent=transparent) + plt.close(fig) + else: + plt.show() + + # turn warnings back to default + warnings.simplefilter("default") diff --git a/MindEyeV2/antspy/ants/utils/__init__.py b/MindEyeV2/antspy/ants/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..407a09332da07fb6e30b91a9c6be2025540c325c --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/__init__.py @@ -0,0 +1,15 @@ +from .channels import merge_channels, split_channels +from .consistency import image_physical_space_consistency, allclose +from .get_ants_data import get_ants_data, get_data +from .matrix_image import (matrix_to_images, + images_from_matrix, + image_list_to_matrix, + images_to_matrix, + matrix_from_images, + timeseries_to_matrix, + matrix_to_timeseries) +from .mni2tal import mni2tal +from .ndimage_to_list import ndimage_to_list, list_to_ndimage +from .nifti_to_ants import nifti_to_ants +from .scalar_rgb_vector import rgb_to_vector, vector_to_rgb, scalar_to_rgb +from .sitk_to_ants import from_sitk, to_sitk diff --git a/MindEyeV2/antspy/ants/utils/channels.py b/MindEyeV2/antspy/ants/utils/channels.py new file mode 100644 index 0000000000000000000000000000000000000000..c4c31db8cd958f6eb1b3b3c5a66b3f7f206cf30f --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/channels.py @@ -0,0 +1,95 @@ + + + +__all__ = ['merge_channels', + 'split_channels'] + + + + +import ants +from ants.internal import get_lib_fn +from ants.decorators import image_method + + +def merge_channels(image_list, channels_first=False): + """ + Merge channels of multiple scalar ANTsImage types into one + multi-channel ANTsImage + + ANTsR function: `mergeChannels` + + Arguments + --------- + image_list : list/tuple of ANTsImage types + scalar images to merge + + Returns + ------- + ANTsImage + + Example + ------- + >>> import ants + >>> image = ants.image_read(ants.get_ants_data('r16')) + >>> image2 = ants.image_read(ants.get_ants_data('r16')) + >>> image3 = ants.merge_channels([image,image2]) + >>> image3 = ants.merge_channels([image,image2], channels_first=True) + >>> image3.numpy() + >>> image3.components == 2 + """ + inpixeltype = image_list[0].pixeltype + dimension = image_list[0].dimension + components = len(image_list) + + for image in image_list: + if not ants.is_image(image): + raise ValueError('list may only contain ANTsImage objects') + if image.pixeltype != inpixeltype: + raise ValueError('all images must have the same pixeltype') + + libfn = get_lib_fn('mergeChannels') + image_ptr = libfn([image.pointer for image in image_list]) + + image = ants.from_pointer(image_ptr) + image.channels_first = channels_first + return image + +@image_method +def split_channels(image): + """ + Split channels of a multi-channel ANTsImage into a collection + of scalar ANTsImage types + + Arguments + --------- + image : ANTsImage + multi-channel image to split + + Returns + ------- + list of ANTsImage types + + Example + ------- + >>> import ants + >>> image = ants.image_read(ants.get_ants_data('r16'), 'float') + >>> image2 = ants.image_read(ants.get_ants_data('r16'), 'float') + >>> imagemerge = ants.merge_channels([image,image2]) + >>> imagemerge.components == 2 + >>> images_unmerged = ants.split_channels(imagemerge) + >>> len(images_unmerged) == 2 + >>> images_unmerged[0].components == 1 + """ + inpixeltype = image.pixeltype + dimension = image.dimension + components = 1 + + libfn = get_lib_fn('splitChannels') + itkimages = libfn(image.pointer) + antsimages = [ants.from_pointer(itkimage) for itkimage in itkimages] + return antsimages + + + + diff --git a/MindEyeV2/antspy/ants/utils/consistency.py b/MindEyeV2/antspy/ants/utils/consistency.py new file mode 100644 index 0000000000000000000000000000000000000000..c32a9cd65c60bf216eb6c6cf9715d85a17672673 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/consistency.py @@ -0,0 +1,73 @@ +import numpy as np +import ants +from ants.decorators import image_method + +__all__ = ['image_physical_space_consistency', + 'allclose'] + +@image_method +def image_physical_space_consistency(image1, image2, tolerance=1e-2, datatype=False): + """ + Check if two or more ANTsImage objects occupy the same physical space + + ANTsR function: `antsImagePhysicalSpaceConsistency` + + Arguments + --------- + *images : ANTsImages + images to compare + + tolerance : float + tolerance when checking origin and spacing + + data_type : boolean + If true, also check that the image data types are the same + + Returns + ------- + boolean + true if images share same physical space, false otherwise + """ + images = [image1, image2] + + img1 = images[0] + for img2 in images[1:]: + if (not ants.is_image(img1)) or (not ants.is_image(img2)): + raise ValueError('Both images must be of class `AntsImage`') + + # image dimension check + if img1.dimension != img2.dimension: + return False + + # image spacing check + space_diffs = sum([abs(s1-s2)>tolerance for s1, s2 in zip(img1.spacing, img2.spacing)]) + if space_diffs > 0: + return False + + # image origin check + origin_diffs = sum([abs(s1-s2)>tolerance for s1, s2 in zip(img1.origin, img2.origin)]) + if origin_diffs > 0: + return False + + # image direction check + origin_diff = np.allclose(img1.direction, img2.direction, atol=tolerance) + if not origin_diff: + return False + + # data type + if datatype == True: + if img1.pixeltype != img2.pixeltype: + return False + + if img1.components != img2.components: + return False + + return True + + +@image_method +def allclose(image1, image2): + """ + Check if two images have the same array values + """ + return np.allclose(image1.numpy(), image2.numpy()) \ No newline at end of file diff --git a/MindEyeV2/antspy/ants/utils/get_ants_data.py b/MindEyeV2/antspy/ants/utils/get_ants_data.py new file mode 100644 index 0000000000000000000000000000000000000000..9ff6c3106ce176e99a13d7a6956ca3ae6279aa2f --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/get_ants_data.py @@ -0,0 +1,108 @@ +""" +Get local ANTsPy data +""" + +__all__ = ['get_ants_data', + 'get_data'] + +import os +import requests +import tempfile + +def get_data(file_id=None, target_file_name=None, antsx_cache_directory=None): + """ + Get ANTsPy test data file + + ANTsR function: `getANTsRData` + + Arguments + --------- + name : string + name of test image tag to retrieve + Options: + - 'r16' + - 'r27' + - 'r30' + - 'r62' + - 'r64' + - 'r85' + - 'ch2' + - 'mni' + - 'surf' + - 'pcasl' + Returns + ------- + string + filepath of test image + + Example + ------- + >>> import ants + >>> mnipath = ants.get_ants_data('mni') + """ + + def switch_data(argument): + switcher = { + "r16": "https://ndownloader.figshare.com/files/28726512", + "r27": "https://ndownloader.figshare.com/files/28726515", + "r30": "https://ndownloader.figshare.com/files/28726518", + "r62": "https://ndownloader.figshare.com/files/28726521", + "r64": "https://ndownloader.figshare.com/files/28726524", + "r85": "https://ndownloader.figshare.com/files/28726527", + "ch2": "https://ndownloader.figshare.com/files/28726494", + "mni": "https://ndownloader.figshare.com/files/28726500", + "surf": "https://ndownloader.figshare.com/files/28726530", + "pcasl": "http://files.figshare.com/1862041/101_pcasl.nii.gz", + } + return(switcher.get(argument, "Invalid argument.")) + + if antsx_cache_directory is None: + antsx_cache_directory = os.path.expanduser('~/.antspy/') + os.makedirs(antsx_cache_directory, exist_ok=True) + + if os.path.isdir(antsx_cache_directory) == False: + antsx_cache_directory = tempfile.TemporaryDirectory() + + valid_list = ("r16", + "r27", + "r30", + "r62", + "r64", + "r85", + "ch2", + "mni", + "surf", + "pcasl", + "show") + + if file_id == "show" or file_id is None: + return(valid_list) + + url = switch_data(file_id) + + if target_file_name == None: + if file_id == "pcasl": + target_file_name = antsx_cache_directory + "pcasl.nii.gz" + else: + extension = ".jpg" + if file_id == "ch2" or file_id == "mni" or file_id == "surf": + extension = ".nii.gz" + if extension == ".jpg": + target_file_name = antsx_cache_directory + file_id + "slice" + extension + else: + target_file_name = antsx_cache_directory + file_id + extension + + target_file_name_path = target_file_name + if target_file_name == None: + target_file = tempfile.NamedTemporaryFile(prefix=target_file_name, dir=antsx_cache_directory) + target_file_name_path = target_file.name + target_file.close() + + if not os.path.exists(target_file_name_path): + r = requests.get(url) + with open(target_file_name_path, 'wb') as f: + f.write(r.content) + + return(target_file_name_path) + +get_ants_data = get_data diff --git a/MindEyeV2/antspy/ants/utils/matrix_image.py b/MindEyeV2/antspy/ants/utils/matrix_image.py new file mode 100644 index 0000000000000000000000000000000000000000..cd02cb45c2deaa72eca32e59b1b013517675fde1 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/matrix_image.py @@ -0,0 +1,202 @@ + +__all__ = [ + "matrix_to_images", + "images_from_matrix", + "image_list_to_matrix", + "images_to_matrix", + "matrix_from_images", + "timeseries_to_matrix", + "matrix_to_timeseries" +] + +import os +import json +import numpy as np +import warnings + +import ants +from ants.decorators import image_method + +@image_method +def matrix_to_timeseries(image, matrix, mask=None): + """ + converts a matrix to a ND image. + + ANTsR function: `matrix2timeseries` + + Arguments + --------- + + image: reference ND image + + matrix: matrix to convert to image + + mask: mask image defining voxels of interest + + + Returns + ------- + ANTsImage + + Example + ------- + >>> import ants + >>> img = ants.make_image( (10,10,10,5 ) ) + >>> mask = ants.ndimage_to_list( img )[0] * 0 + >>> mask[ 4:8, 4:8, 4:8 ] = 1 + >>> mat = ants.timeseries_to_matrix( img, mask = mask ) + >>> img2 = ants.matrix_to_timeseries( img, mat, mask) + """ + + if mask is None: + mask = temp[0] * 0 + 1 + temp = matrix_to_images(matrix, mask) + newImage = ants.list_to_ndimage(image, temp) + ants.copy_image_info(image, newImage) + return newImage + + +def matrix_to_images(data_matrix, mask): + """ + Unmasks rows of a matrix and writes as images + + ANTsR function: `matrixToImages` + + Arguments + --------- + data_matrix : numpy.ndarray + each row corresponds to an image + array should have number of columns equal to non-zero voxels in the mask + + mask : ANTsImage + image containing a binary mask. Rows of the matrix are + unmasked and written as images. The mask defines the output image space + + Returns + ------- + list of ANTsImage types + + Example + ------- + >>> import ants + >>> img = ants.image_read(ants.get_ants_data('r16')) + >>> msk = ants.get_mask( img ) + >>> img2 = ants.image_read(ants.get_ants_data('r16')) + >>> img3 = ants.image_read(ants.get_ants_data('r16')) + >>> mat = ants.image_list_to_matrix([img,img2,img3], msk ) + >>> ilist = ants.matrix_to_images( mat, msk ) + """ + + if data_matrix.ndim > 2: + data_matrix = data_matrix.reshape(data_matrix.shape[0], -1) + + numimages = len(data_matrix) + numVoxelsInMatrix = data_matrix.shape[1] + numVoxelsInMask = (mask >= 0.5).sum() + if numVoxelsInMask != numVoxelsInMatrix: + raise ValueError( + "Num masked voxels %i must match data matrix %i" + % (numVoxelsInMask, numVoxelsInMatrix) + ) + + imagelist = [] + for i in range(numimages): + img = mask.clone() + img[mask >= 0.5] = data_matrix[i, :] + imagelist.append(img) + return imagelist + + +images_from_matrix = matrix_to_images + + +def images_to_matrix(image_list, mask=None, sigma=None, epsilon=0.5): + """ + Read images into rows of a matrix, given a mask - much faster for + large datasets as it is based on C++ implementations. + + ANTsR function: `imagesToMatrix` + + Arguments + --------- + image_list : list of ANTsImage types + images to convert to ndarray + + mask : ANTsImage (optional) + Mask image, voxels in the mask (>= epsilon) are placed in the matrix. If None, + the first image in image_list is thresholded at its mean value to create a mask. + + sigma : scaler (optional) + smoothing factor + + epsilon : scalar + threshold for mask, values >= epsilon are included in the mask. + + Returns + ------- + ndarray + array with a row for each image + shape = (N_IMAGES, N_VOXELS) + + Example + ------- + >>> import ants + >>> img = ants.image_read(ants.get_ants_data('r16')) + >>> img2 = ants.image_read(ants.get_ants_data('r16')) + >>> img3 = ants.image_read(ants.get_ants_data('r16')) + >>> mat = ants.image_list_to_matrix([img,img2,img3]) + """ + if mask is None: + mask = ants.get_mask(image_list[0]) + + num_images = len(image_list) + mask_thresh = mask.clone() >= epsilon + mask_arr = mask.numpy() >= epsilon + num_voxels = np.sum(mask_arr) + + data_matrix = np.empty((num_images, num_voxels)) + do_smooth = sigma is not None + for i, img in enumerate(image_list): + if do_smooth: + img = ants.smooth_image(img, sigma, sigma_in_physical_coordinates=True) + if np.sum(np.array(img.shape) - np.array(mask_thresh.shape)) != 0: + img = ants.resample_image_to_target(img, mask_thresh, 2) + data_matrix[i, :] = img[mask_thresh] + return data_matrix + + +image_list_to_matrix = images_to_matrix +matrix_from_images = images_to_matrix + +@image_method +def timeseries_to_matrix(image, mask=None): + """ + Convert a timeseries image into a matrix. + + ANTsR function: `timeseries2matrix` + + Arguments + --------- + image : image whose slices we convert to a matrix. E.g. a 3D image of size + x by y by z will convert to a z by x*y sized matrix + + mask : ANTsImage (optional) + image containing binary mask. voxels in the mask are placed in the matrix + + Returns + ------- + ndarray + array with a row for each image + shape = (N_IMAGES, N_VOXELS) + + Example + ------- + >>> import ants + >>> img = ants.make_image( (10,10,10,5 ) ) + >>> mat = ants.timeseries_to_matrix( img ) + """ + temp = ants.ndimage_to_list(image) + if mask is None: + mask = temp[0] * 0 + 1 + return image_list_to_matrix(temp, mask) + diff --git a/MindEyeV2/antspy/ants/utils/mni2tal.py b/MindEyeV2/antspy/ants/utils/mni2tal.py new file mode 100644 index 0000000000000000000000000000000000000000..99dbfb74509b0a8844e53a30ca932cc6f8c2d462 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/mni2tal.py @@ -0,0 +1,59 @@ + +__all__ = ['mni2tal'] + +def mni2tal(xin): + """ + mni2tal for converting from ch2/mni space to tal - very approximate. + + This is a standard approach but it's not very accurate. + + ANTsR function: `mni2tal` + + Arguments + --------- + xin : tuple + point in mni152 space. + + Returns + ------- + tuple + + Example + ------- + >>> import ants + >>> ants.mni2tal( (10,12,14) ) + + References + ---------- + http://bioimagesuite.yale.edu/mni2tal/501_95733_More\\%20Accurate\\%20Talairach\\%20Coordinates\\%20SLIDES.pdf + http://imaging.mrc-cbu.cam.ac.uk/imaging/MniTalairach + """ + if (not isinstance(xin, (tuple,list))) or (len(xin) != 3): + raise ValueError('xin must be tuple/list with 3 coordinates') + + x = list(xin) + # The input image is in RAS coordinates but we use ITK which returns LPS + # coordinates. So we need to flip the coordinates such that L => R and P => A to + # get RAS (MNI) coordinates + x[0] = x[0] * (-1) # flip X + x[1] = x[1] * (-1) # flip Y + + xout = x + + if (x[2] >= 0): + xout[0] = x[0] * 0.99 + xout[1] = x[1] * 0.9688 + 0.046 * x[2] + xout[2] = x[1] * (-0.0485) + 0.9189 * x[2] + + if (x[2] < 0): + xout[0] = x[0] * 0.99 + xout[1] = x[1] * 0.9688 + 0.042 * x[2] + xout[2] = x[1] * (-0.0485) + 0.839 * x[2] + + return(xout) + + + + + + diff --git a/MindEyeV2/antspy/ants/utils/ndimage_to_list.py b/MindEyeV2/antspy/ants/utils/ndimage_to_list.py new file mode 100644 index 0000000000000000000000000000000000000000..4b0227494db20effffa55b467a9be1b0ea870c46 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/ndimage_to_list.py @@ -0,0 +1,111 @@ +__all__ = ['ndimage_to_list', + 'list_to_ndimage'] + + +import numpy as np + +import ants +from ants.decorators import image_method + +@image_method +def list_to_ndimage( image, image_list ): + """ + Merge list of multiple scalar ANTsImage types of dimension into one + ANTsImage of dimension plus one + + ANTsR function: `mergeListToNDImage` + + Arguments + --------- + image : target image space + image_list : list/tuple of ANTsImage types + scalar images to merge into target image space + + Returns + ------- + ANTsImage + + Example + ------- + >>> import ants + >>> image = ants.image_read(ants.get_ants_data('r16')) + >>> image2 = ants.image_read(ants.get_ants_data('r16')) + >>> imageTar = ants.make_image( ( *image2.shape, 2 ) ) + >>> image3 = ants.list_to_ndimage( imageTar, [image,image2]) + >>> image3.dimension == 3 + """ + inpixeltype = image_list[0].pixeltype + dimension = image_list[0].dimension + components = len(image_list) + + for imageL in image_list: + if not ants.is_image(imageL): + raise ValueError('list may only contain ANTsImage objects') + if image.pixeltype != inpixeltype: + raise ValueError('all images must have the same pixeltype') + + dimensionout = ( *image_list[0].shape, len( image_list ) ) + newImage = ants.make_image( + dimensionout, + spacing = ants.get_spacing( image ), + origin = ants.get_origin( image ), + direction = ants.get_direction( image ), + pixeltype = inpixeltype + ) + # FIXME - should implement paste image filter from ITK + for x in range( len( image_list ) ): + if dimension == 2: + newImage[:,:,x] = image_list[x][:,:] + if dimension == 3: + newImage[:,:,:,x] = image_list[x][:,:,:] + return newImage + + +@image_method +def ndimage_to_list(image): + """ + Split a n dimensional ANTsImage into a list + of n-1 dimensional ANTsImages + + Arguments + --------- + image : ANTsImage + n-dimensional image to split + + Returns + ------- + list of ANTsImage types + + Example + ------- + >>> import ants + >>> image = ants.image_read(ants.get_ants_data('r16')) + >>> image2 = ants.image_read(ants.get_ants_data('r16')) + >>> imageTar = ants.make_image( ( *image2.shape, 2 ) ) + >>> image3 = ants.list_to_ndimage( imageTar, [image,image2]) + >>> image3.dimension == 3 + >>> images_unmerged = ants.ndimage_to_list( image3 ) + >>> len(images_unmerged) == 2 + >>> images_unmerged[0].dimension == 2 + """ + inpixeltype = image.pixeltype + dimension = image.dimension + components = 1 + imageShape = image.shape + nSections = imageShape[ dimension - 1 ] + subdimension = dimension - 1 + suborigin = ants.get_origin( image )[0:subdimension] + subspacing = ants.get_spacing( image )[0:subdimension] + subdirection = np.eye( subdimension ) + for i in range( subdimension ): + subdirection[i,:] = ants.get_direction( image )[i,0:subdimension] + subdim = image.shape[ 0:subdimension ] + imagelist = [] + for i in range( nSections ): + img = ants.slice_image( image, axis = subdimension, idx = i ) + ants.set_spacing( img, subspacing ) + ants.set_origin( img, suborigin ) + ants.set_direction( img, subdirection ) + imagelist.append( img ) + + return imagelist diff --git a/MindEyeV2/antspy/ants/utils/nifti_to_ants.py b/MindEyeV2/antspy/ants/utils/nifti_to_ants.py new file mode 100644 index 0000000000000000000000000000000000000000..0d093d29ae5eca9360336df08ba25001a8803f11 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/nifti_to_ants.py @@ -0,0 +1,39 @@ +__all__ = ["nifti_to_ants"] + +import numpy as np +import ants + +def nifti_to_ants( nib_image ): + """ + Converts a given Nifti image into an ANTsPy image + + Parameters + ---------- + img: NiftiImage + + Returns + ------- + ants_image: ANTsImage + """ + ndim = nib_image.ndim + + if ndim < 3: + print("Dimensionality is less than 3.") + return None + + q_form = nib_image.get_qform() + spacing = nib_image.header["pixdim"][1 : ndim + 1] + + origin = np.zeros((ndim)) + origin[:3] = q_form[:3, 3] + + direction = np.diag(np.ones(ndim)) + direction[:3, :3] = q_form[:3, :3] / spacing[:3] + + ants_img = ants.from_numpy( + data = nib_image.get_data().astype( np.float ), + origin = origin.tolist(), + spacing = spacing.tolist(), + direction = direction ) + + return ants_img diff --git a/MindEyeV2/antspy/ants/utils/scalar_rgb_vector.py b/MindEyeV2/antspy/ants/utils/scalar_rgb_vector.py new file mode 100644 index 0000000000000000000000000000000000000000..bd17a124e96b9e46cbe321a84e93654fc1e3bb13 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/scalar_rgb_vector.py @@ -0,0 +1,92 @@ + + +__all__ = ['rgb_to_vector', + 'vector_to_rgb', + 'scalar_to_rgb'] + +import os +from tempfile import mktemp + +import numpy as np + +import ants +from ants.internal import get_lib_fn, process_arguments +from ants.decorators import image_method + +def scalar_to_rgb(image, mask=None, filename=None, cmap='red', custom_colormap_file=None, + min_input=None, max_input=None, min_rgb_output=None, max_rgb_output=None, + vtk_lookup_table=None): + """ + Usage: ConvertScalarImageToRGB imageDimension inputImage outputImage mask colormap + [customColormapFile] [minimumInput] [maximumInput] [minimumRGBOutput=0] + [maximumRGBOutput=255] + Possible colormaps: grey, red, green, blue, copper, jet, hsv, spring, summer, autumn, winter, hot, cool, overunder, custom + + Example + ------- + >>> import ants + >>> img = ants.image_read(ants.get_data('r16')) + >>> img_color = ants.scalar_to_rgb(img, cmap='jet') + """ + raise Exception('This function is currently not supported.') + +@image_method +def rgb_to_vector(image): + """ + Convert an RGB ANTsImage to a Vector ANTsImage + + Arguments + --------- + image : ANTsImage + RGB image to be converted + + Returns + ------- + ANTsImage + + Example + ------- + >>> import ants + >>> mni = ants.image_read(ants.get_data('mni')) + >>> mni_rgb = ants.scalar_to_rgb(mni) + >>> mni_vector = mni.rgb_to_vector() + >>> mni_rgb2 = mni.vector_to_rgb() + """ + if image.pixeltype != 'unsigned char': + image = image.clone('unsigned char') + idim = image.dimension + libfn = get_lib_fn('RgbToVector%i' % idim) + new_ptr = libfn(image.pointer) + new_img = ants.from_pointer(new_ptr) + return new_img + +@image_method +def vector_to_rgb(image): + """ + Convert an Vector ANTsImage to a RGB ANTsImage + + Arguments + --------- + image : ANTsImage + RGB image to be converted + + Returns + ------- + ANTsImage + + Example + ------- + >>> import ants + >>> img = ants.image_read(ants.get_data('r16'), pixeltype='unsigned char') + >>> img_rgb = ants.scalar_to_rgb(img.clone()) + >>> img_vec = img_rgb.rgb_to_vector() + >>> img_rgb2 = img_vec.vector_to_rgb() + """ + if image.pixeltype != 'unsigned char': + image = image.clone('unsigned char') + idim = image.dimension + libfn = get_lib_fn('VectorToRgb%i' % idim) + new_ptr = libfn(image.pointer) + new_img = ants.from_pointer(new_ptr) + return new_img + diff --git a/MindEyeV2/antspy/ants/utils/sitk_to_ants.py b/MindEyeV2/antspy/ants/utils/sitk_to_ants.py new file mode 100644 index 0000000000000000000000000000000000000000..aea015a01fd414db2c0deed645b8e01ec9a6d217 --- /dev/null +++ b/MindEyeV2/antspy/ants/utils/sitk_to_ants.py @@ -0,0 +1,63 @@ +import numpy as np +import ants + + +def from_sitk(sitk_image: "SimpleITK.Image") -> ants.ANTsImage: + """ + Converts a given SimpleITK image into an ANTsPy image + + Parameters + ---------- + img: SimpleITK.Image + + Returns + ------- + ants_image: ANTsImage + """ + import SimpleITK as sitk + + ndim = sitk_image.GetDimension() + + if ndim < 3: + print("Dimensionality is less than 3.") + return None + + direction = np.asarray(sitk_image.GetDirection()).reshape((3, 3)) + spacing = list(sitk_image.GetSpacing()) + origin = list(sitk_image.GetOrigin()) + + data = sitk.GetArrayViewFromImage(sitk_image) + + ants_img: ants.ANTsImage = ants.from_numpy( + data=data.ravel(order="F").reshape(data.shape[::-1]), + origin=origin, + spacing=spacing, + direction=direction, + ) + + return ants_img + + +def to_sitk(ants_image: ants.ANTsImage) -> "SimpleITK.Image": + """ + Converts a given ANTsPy image into an SimpleITK image + + Parameters + ---------- + ants_image: ANTsImage + + Returns + ------- + img: SimpleITK.Image + """ + + import SimpleITK as sitk + + data = ants_image.view() + shape = ants_image.shape + + sitk_img = sitk.GetImageFromArray(data.ravel(order="F").reshape(shape[::-1])) + sitk_img.SetOrigin(ants_image.origin) + sitk_img.SetSpacing(ants_image.spacing) + sitk_img.SetDirection(ants_image.direction.flatten()) + return sitk_img diff --git a/MindEyeV2/antspy/docs/Makefile b/MindEyeV2/antspy/docs/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..8e5143f3c73ed4fa1283073ad14d307cb7f70b92 --- /dev/null +++ b/MindEyeV2/antspy/docs/Makefile @@ -0,0 +1,27 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = ANTsPy +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +docset: html + doc2dash --name $(SPHINXPROJ) --icon $(SOURCEDIR)/_static/img/antspy-logo-icon.png --enable-js --online-redirect-url http://ncullen.github.io/docs/ --force $(BUILDDIR)/html/ + + # Manually fix because Zeal doesn't deal well with `icon.png`-only at 2x resolution. + cp $(SPHINXPROJ).docset/icon.png $(SPHINXPROJ).docset/icon@2x.png + convert $(SPHINXPROJ).docset/icon@2x.png -resize 16x16 $(SPHINXPROJ).docset/icon.png + +.PHONY: help Makefile docset + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/MindEyeV2/antspy/docs/make.bat b/MindEyeV2/antspy/docs/make.bat new file mode 100644 index 0000000000000000000000000000000000000000..cdc3260028b388409f225ff3420f32489e0a7bad --- /dev/null +++ b/MindEyeV2/antspy/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=ANTsPy + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/MindEyeV2/antspy/docs/other/ANTsPy Tutorial.ipynb b/MindEyeV2/antspy/docs/other/ANTsPy Tutorial.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..8a7735d13623684c1912e1d2e37d3c28a65bbb36 --- /dev/null +++ b/MindEyeV2/antspy/docs/other/ANTsPy Tutorial.ipynb @@ -0,0 +1,440 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ANTsPy Tutorial\n", + "\n", + "In this tutorial, I will show of some of the core ANTsPy functionality. I will highlight the similarities with ANTsR." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Basic IO, Processing, & Plotting" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "import ants\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "img = ants.image_read( ants.get_ants_data('r16'), 'float' )\n", + "plt.imshow(img.numpy(), cmap='Greys_r')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n12+QAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mask = ants.get_mask(img)\n", + "plt.imshow(mask.numpy())\n", + "plt.show()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# N4 Bias Correction" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "data": { + "image/png": "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\nAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "img_n4 = ants.n4_bias_field_correction(img, shrink_factor=3)\n", + "\n", + "plt.imshow(img_n4.numpy(), cmap='Greys_r')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Overloaded Mathematical Operators" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "diff = img - img_n4\n", + "plt.imshow(diff.numpy())\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Atropos\n", + "\n", + "The following example has been validated with ANTsR. That is, both ANTsR and ANTsPy return the EXACT same result (images).\n", + "\n", + "R Version:\n", + "```R\n", + "img <- antsImageRead( getANTsRData(\"r16\") , 2 )\n", + "img <- resampleImage( img, c(64,64), 1, 0 )\n", + "mask <- getMask(img)\n", + "segs1 <- atropos( a = img, m = '[0.2,1x1]',\n", + " c = '[2,0]', i = 'kmeans[3]', x = mask )\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'segmentation': , 'probabilityimages': [, , ]}\n" + ] + } + ], + "source": [ + "img = ants.image_read( ants.get_ants_data(\"r16\") ).clone('float')\n", + "img = ants.resample_image( img, (64,64), 1, 0 )\n", + "mask = ants.get_mask(img)\n", + "segs1 = ants.atropos( a = img, m = '[0.2,1x1]', \n", + " c = '[2,0]', i = 'kmeans[3]', x = mask )\n", + "\n", + "print(segs1)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nrkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\ndJLMbVdONseXXue2xtpUaotXNM109ee3l7nZ7toZtnZ+ab2SuO6dk+Ouv7TYzrp3XOTk/21ctWFA\n08dcHb8X1tmufv8jKp5d/8uXH5jjVtluZdK9hacZ3SX15/nygDpuhv+mDSeZdh/8n4teXLNsutFN\nbmeLjJAIfOITEkLo+ISEEDo+ISGEY/wkMDEw3r3gphuNruf99/ty9K9uRiC+ty2Q+VZatsu0a3fb\nGqfbQybcpa0W+vLq3YeaZiMau1WDF+Xa1L36gTr4m7v/6Ms/XmXbzbjrX8b6IJkN3Crt0h/cNdpV\nszX8X97uQnZLfmpqdHcf5j6D81u5OYMXV71n2hX/1e0LmJNhC45MBsf4FcEnPiEhhI5PSAhhVz8B\nPJxvQ1nBbvo/73nC6K7qPdSX9bOlRqfdjvfly5+Z6cuX1tlg2pW2dtmAJ8/aaHRzutT25ffdTtuY\nfpVdLFRjxL8Riy2nb63wvO3aA8F8wrUlUYt0Aot2fu7ttgDrf3Qt02zdcPee/zPsAaMrKHF3GBPI\n6ltVbLvz9337S18efJgNF5KK4ROfkBBCxyckhNDxCQkhHOMngHoZtgb+NnUhtntP7WV0Iz50hTh2\nltmKRG2yP/Tl3650e9Zd1n6aaZe16SdfXrbNhqt2n5rny9nvLPDlBmNt0c+ud7m5gVFobXQZeW5f\nvdJVLnRYI6pQRvc5Q3y5xVVr7TVquzF+QV83Vj/6XXuN317xmi/3iworPr7WhekaZLrP6rQXfm/a\nHbrAhTC7Pvg6SOXwiU9ICKHjExJC2NWvIsEstu4vdTW6ob9wNeBemvuK0V2Q29mX31y/yOiK1HVn\n32g/xZdPnHeladf3pS9cu/vtiraGG1yWnC3fYcmOUSgDAB6b5UJ917ZwW3T3XjrAtGt+yRJfLova\nn2HXNJf9lz23mi8v/1c70+7bp13dwcdWP250w44535fv+sJ9pkdO2W7atX1imS8nosBIGOATn5AQ\nQscnJIRUuoVWIjmYttAKbo11aZ+rja7VqFW+/HgzW+Ric+lOXw5m+AHAc1vcMKBldbeL7D3PXGra\n1S5wr9t+uP3tPvyBwOx9cBa+LKrjH8isy2xnZ/X1azdDX7bLLRC6e81c025Eyy6IhVR3w5aNQ070\n5ayf7d9bjS3uvdReY7vw1050RTVunOs+g6OG5Zt2E7541ZevLzjP6D5a0caX2wxagIOdhG2hRQg5\n+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\nTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "for i in range(3):\n", + " plt.imshow(segs1['probabilityimages'][i].numpy())\n", + " plt.title('Class %i' % i)\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(segs1['segmentation'].numpy())\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Registration\n", + "\n", + "R Version:\n", + "```R\n", + "fi <- antsImageRead(getANTsRData(\"r16\") )\n", + "mi <- antsImageRead(getANTsRData(\"r64\") )\n", + "fi<-resampleImage(fi,c(60,60),1,0)\n", + "mi<-resampleImage(mi,c(60,60),1,0) # speed up\n", + "mytx <- antsRegistration(fixed=fi, moving=mi, typeofTransform = c('SyN') )\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'warpedmovout': , 'warpedfixout': , 'fwdtransforms': ['/var/folders/__/v7ryn14n0x749jd36ttj_s380000gp/T/tmphqe1sm5v1Warp.nii.gz', '/var/folders/__/v7ryn14n0x749jd36ttj_s380000gp/T/tmphqe1sm5v0GenericAffine.mat'], 'invtransforms': ['/var/folders/__/v7ryn14n0x749jd36ttj_s380000gp/T/tmphqe1sm5v0GenericAffine.mat', '/var/folders/__/v7ryn14n0x749jd36ttj_s380000gp/T/tmphqe1sm5v1InverseWarp.nii.gz']}\n" + ] + } + ], + "source": [ + "fi = ants.image_read( ants.get_ants_data('r16') ).clone('float')\n", + "mi = ants.image_read( ants.get_ants_data('r64')).clone('float')\n", + "fi = ants.resample_image(fi,(60,60),1,0)\n", + "mi = ants.resample_image(mi,(60,60),1,0)\n", + "mytx = ants.registration(fixed=fi, moving=mi, \n", + " type_of_transform = 'SyN' )\n", + "\n", + "print(mytx)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\nSUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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\nAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP4AAAEICAYAAAB/KknhAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnXl0XdWV5r/vDZonS5ZtyfI8gM1kgjGYuQjQhKQyEBad\ndCrLqabjdFWqOwmpFSC9OpVanapOalVnWJ1aSVEhIZABSAgF7YSA45gwBIxtzGDjEVu2JUuWZEnW\nLL1h9x/v+p27b6zBsvQkfPdvLS2dc8999+437HvOPnuffSgiMAwjXESmWgDDMHKPKb5hhBBTfMMI\nIab4hhFCTPENI4SY4htGCDHFP4cgeQPJhmkgxy6SN0zCdT9B8tmJvm4YMcUfAZL3kXw6cGz/MMc+\nllvppi8icoGIPDcJ1/2piNwy0dcNI6b4I/M8gKtIRgGAZA2AOIBLA8eWeueeESRjEyirYYwZU/yR\n2YqMoq/y6tcC2Axgb+DYOyJyDABIfofkUZJdJLeTvPbUxUh+leQvSf6EZBeAT/mOPUqym+RrJC/x\nvaaW5OMkW0keIvnffW2FJB8k2UHybQCXj/RmSArJv/ZGKN0k/xfJJST/6Mn7GMk83/mfJnmAZDvJ\np0jWese/R/KfA9d+kuTdXrme5E2+9/wYyYe8e+4iudr3uveQ3OG1/cL7HL42jPyfIvnieN4PyRkk\nN3ifY4dXrvNdaxHJ573r/I7kv5D8yUif57saEbG/Ef6QUfQveOXvAvjPAP4hcOyHvvP/AkAVgBiA\nLwJoBlDgtX0VQALAh5F56Bb6jt2BzEPmbwEc8soRANsBfAVAHoDFAA4C+A/e9b4O4AUAlQDmAdgJ\noGGE9yIAngRQBuACAIMANnnXLQfwNoB13rk3AmgD8B4A+QD+L4DnvbbrABwFQK8+A0A/gFqvXg/g\nJt97HgBwG4AogP8N4BWvLQ/AYQCf897v7QCGAHxtGPk/BeDFcb6fKgAfBVAEoBTALwD8u+9aLwP4\nZ0+mawB0AfjJVP/+Ju13PdUCTPc/74f7hFd+A8AyALcGjq0b4fUdAC7xXev501z/FV89AqAJmZHE\nFQCOBM6/D8CPvPJBALf62taPQfGv9tW3A7jHV/8/AL7tlR8A8E++thJkHlALARDAEQDXeW2fBvB7\n37lBxf+dr20lgH6vfB2ARngPEO/Yi2eo+GN6P6e51ioAHV55PoAkgCJf+0/OZcW3of7oPA/gGpKV\nAKpFZD+APyJj+1cCuBA++57k35LcTfIkyU5kep6ZvusdPc09ssdEJA2gAUAtgAUAakl2nvoD8GUA\ns73TawPXOzyG93PcV+4/Tb3Ed+3s9USkB8AJAHMloxmPAPi41/yfAPx0hHs2+8p9AAq8+Y1aAI3e\n9U5xus9nJMb0fkgWkfxXkoc9M+t5ABXeXE0tgHYR6TsLOd5VmOKPzsvIKO+nAbwEACLSBeCYd+yY\niBwCAM+e/xKAOwHMEJEKACeR6SFPcbrlkPNOFUhGANR51z8K4JCIVPj+SkXkNu/0Jv9rkem5Jopj\nyDx4TslVjMxwudE79HMAd5BcgMzI5PFx3KMJwFyS/s9n3nAnnyVfBHAegCtEpAyZ0QaQ+W6aAFSS\nLMqBHNMCU/xREJF+ANsA3I2MPX2KF71j/tn8UmSGjK0AYiS/goz9ORqXkbzd6wU/j4yt+gqAVwF0\nk7zHm8iLkryQ5KlJvMcA3OdNXNUB+G/jf6d/ws8B/CXJVSTzAfwjgC0iUg8AIrIDmTmAHwB4RkQ6\nx3GPlwGkAPwNyRjJDwFYMyHS/ymlyIwAOr2R2t+dahCRw8h8x18lmUdyLYA/nyQ5pgWm+GPjDwBm\nIaPsp3jBO+ZX/GcA/BbAPmSGyQMY25DxSQD/EZn5gE8CuF1EEiKSAvABZOzRQ3CKVu697u+9+xwC\n8CyAh8fx3k6LiPwOwP9EpidvArAEQDBW4WcAbvL+j+ceQ8hM6N0FoBOZidENyDz4JppvIzOZ2obM\nQ/W3gfZPAFiLjDnzNQCPTpIc0wJq88rINSS/CmCpiPzFVMsyHSC5BcD3ReRHUyzHowD2iMjfjXry\nuxDr8Y0pheT1JOd4Q/11AC7Gn/bGuZDjci8GIELyVgAfAvDvuZYjV1jkmDHVnIfMXEUxMu7JO0Sk\naQrkmAPgV8hMYDYA+CtvHuOcxIb6hhFCzmqoT/JWknu9sM57J0oowzAml3H3+F7gwz4ANyMzNNoK\n4OMi8vZwr8ljvhSgeFz3MwxjdAbQiyEZ5GjnnY2NvwbAARE5CAAkH0FmQmRYxS9AMa7ge8/iloZh\njMQW2TSm885mqD8X2kfd4B1TkFxPchvJbYlz1y1qGO8qJt2dJyL3i8hqEVkdR/5k384wjDFwNorf\nCB3PXAcXx20YxjTmbBR/K4BlXgKDPGTCOZ+aGLEMw5hMxj25JyJJkn+DTHx6FJlkFLsmTDLDMCaN\ns4rcE5HfAPjNBMliGEaOsFh9wwghpviGEUJM8Q0jhJjiG0YIMcU3jBBiim8YIcQU3zBCiCm+YYQQ\nU3zDCCGm+IYRQkzxDSOEmOIbRggxxTeMEGKKbxghxBTfMEKIKb5hhBBTfMMIIab4hhFCTPENI4SY\n4htGCDHFN4wQYopvGCHEFN8wQogpvmGEEFN8wwghZ7WTjvEugdR1kYk513jXYj2+YYSQURWf5A9J\ntpDc6TtWSXIjyf3e/xmTK6ZhGBPJWIb6DwL4LoCHfMfuBbBJRL5O8l6vfs/Ei2coIlFVZYTDnAhE\nKspdpSrwXA6+LuJ7/re063sU5Ku6DA25SjKpr5PQ9VRPr++FaX2umRBTyqg9vog8D6A9cPhDAH7s\nlX8M4MMTLJdhGJPIeCf3ZotIk1duBjB7uBNJrgewHgAKUDTO2xmGMZGc9eSeiAiAYcdtInK/iKwW\nkdVx5A93mmEYOWS8Pf5xkjUi0kSyBkDLRApljI3o3JpsOTm7QrW1Ly/OljuXa5t+qFLb2xJ1z+0F\nG8pU28AMPa+QLHDXivXr530kYPKX7+1ybc0nVFuqzdUlldIvNPt/0hlvj/8UgHVeeR2AJydGHMMw\ncsFY3Hk/B/AygPNINpC8C8DXAdxMcj+Am7y6YRjvEkYd6ovIx4dpeu8Ey2IYRo6wkN1pRqTY2eaR\nslLVJmUlqr7nMzPduXMGVFuy09nxkdKEaps/u0PV+xLxbLn14mp9nWJtb8dWOLs9P66N+mRaDyCb\nmpz8VdvKVVvVLucIip7sV23p/YdUXc0BmP0/IVjIrmGEEFN8wwghNtSfZnB+bbb89t3aRRcp0EPr\n9KCr58W1S6xqoQu2fN/ct1Xbgvw2VX/2xAXZ8qsXFau2VJ/+iUTFufOqi3tVW1J0P9Idd9fqWqbd\ngu3XuevGG2aqtiU/1e7H1O79rmKrBycE6/ENI4SY4htGCDHFN4wQYjZ+rgksrY3V6PVNe9ZXZct5\nZX2qraRIu+yiEWffxqPaxr9nyW+z5bmxTtXWmNRzB0NpJ5OktQ3NPB3eW1niZLq2+oBqi1PL8GCH\nc+ENFei2iE/2odna3Xj4Q9rmXzDk2lMHj6g2SCDc1xgT1uMbRggxxTeMEGJD/RwTKdY5CRpvX6jq\nUumG80tmabdbe79+7SUzG7PlS0v0EHhWtDtb3jVYq9oeblyr6kdPuKF/elCbItFC7ULsT7ifzB9P\nLFZtd819UdVvW7wrW37puD73eKPLChTr0D/D/ho9fG+9dk62PLO4QLWl39gN48yxHt8wQogpvmGE\nEFN8wwghZuNPBoGwUsbc6rfmT16k2vqu0mGv+THnPptd2K3ayvK0O++68n3Z8qr8BtVWGXF28r+2\nrFBtB/bVqDqTTt7oYCAktlP/RAYLnGstKF9FVL+XP694PVuelafP/bfOq7PlZELfM7iasO0qJ0Oy\nUGcMnr1LyyfBzL/GabEe3zBCiCm+YYQQU3zDCCFm408CfpseAJjn6n1z9TLS6xfrsNebZ2R3KlO+\neACYF+tS9UrfDjgDgZ1qyiN52fKyIp0E+dhynQ3nWJfLrNvTopflMqH7ht6Owmx5e6ROtf3lLO1/\nj8I3XxE/qdqWzmnNlmsW6/dVGtNzGZ0Jd88XEuertlnv0fMX2Oo+P1uyOzzW4xtGCDHFN4wQQsnh\ncKiMlXIFz/3kvMzXOwbt++aqbDlW1R88XRH1bW7xmZUvqLbbS3eqerlvpV8cOtS2yDfUD/LSgDYL\nvrjnzmy55VCVaiuq6VH16+a9ky1fXHxUtV1eqJNkFvhW6w2Ili8F58JrTmrT4wfHrlP1fcddAtDB\nkzpkt+oVba1W/ehVV0mHb+XeFtmELmkffjdVD+vxDSOEmOIbRggxxTeMEGLuvAmAMf0x8ny9BPW2\nNS50dUXxMdXWkdTus/yIC1e9sXiPautM6/sU+WzoOLUN3ZceGratOanDXqMR3+YbMwZV2+Iqvdnl\nDeVuGez8WLtqiwY2Tfbb8QWB7DyNSedCTIh+XzWF2vUXme2u21qmP6/2hjmqXhVwaxqnx3p8wwgh\nY9k0cx7JzSTfJrmL5Oe845UkN5Lc7/2fMdq1DMOYHoylx08C+KKIrARwJYDPklwJ4F4Am0RkGYBN\nXt0wjHcBZ+zHJ/kkgO96fzeISBPJGgDPich5I732XPXjR2dq33fHLctUveU2ZzcHs9hevKBR1avy\n3dLW/Ggg7VVKhwJ/ZtZz2fJ5cW2bH025Z3pzUm++OSuqffMzo25eIRH4OZxM63v6aU6VqfrbA3NV\n/XrfHEXQ/v9N98W+e+o5iO2d81W9a9D57hta9cAyndSf5+IHXTm6+bVhJD93mRQ/PsmFAC4FsAXA\nbBFp8pqaAcwe5mWGYUwzxqz4JEsAPA7g8yKiVlVIZthw2qEDyfUkt5HclsDg6U4xDCPHjMmdRzKO\njNL/VER+5R0+TrLGN9RvOd1rReR+APcDmaH+BMg87WCBDiPtnaOfp5ctDGwC4WNv2yxdT7h6Ykh/\nPekTOgx37nVuo4wFlS+rtq39zqX4T2/eotpWzDmu6n899/fZ8ryYdqUFh/NPtL8nW97ZrjP5dPXr\nz+GqS9xml0dTOiz3oT1rsuVYTLvggpuDFOQ5U2Rpjf6Z9Qzp8OhUvjO7orbB5rCMZVafAB4AsFtE\nvulregrAOq+8DsCTEy+eYRiTwVh6/KsBfBLAWyRPRaJ8GcDXATxG8i4AhwHcOczrDcOYZoyq+CLy\nIoDhZgnPvSl6wwgBFrI7Xnz2o3R1j3Ai0Npfki3XFOlsM6vn6KWtFXG3KeXGowHvaLHOTFOT52z8\nvoD5uqff2d+LqnXY7ZtvLlT1L7S5wdqHFr+l2v5f/YWqnn7V7brTt1hnw124UNvfzT67vjetbfGh\nfucmHBzS7rwFC1pV/f21TqaH9l+h2gb26bmDOliW3bFgIbuGEUJM8Q0jhJjiG0YIMRt/rAR8wrHZ\nzt/ecf0i1da9Qtu+0uOWkp7o1Tve5seGTw81FPDjX1jTpOoL85wt3JzS17261PnQOxO6rWSVDqR6\n7bWl2fLmx6/S8hXr991+tXvtvBq9LPeCimZVb0y48NoC6s+kvMLNZXS2lqi2ZFr3R5W+HXrW1OiY\niFfSC1S9vtK91+XPBvs1X7xAyH361uMbRggxxTeMEGJD/XHScYMb3ss67X66qEhvHtk54DaEKIzp\nIe9gSn8FzZ1uJV0kooej55fqUNsdfQuz5YHAKrqPlG/PllcUaxPhxcElqh7v8W+aqcNn+6p131A1\n07kul5S3qbZryvap+tYe9xkFV+Atq3Kf2fauQtXWeKxS1d+urs2WWwe1WbBilv5M9kVdRt5otV41\nmWr1uTUlfBl4/ViPbxghxBTfMEKIKb5hhBCz8ceJ3/Yti2i7eCgV2DVGnA2974Beyoo8/dq8IjcH\nEI/p8NOZcR0aXD8wM1vuTuglsQNl7qu9PpCtd1dPraoni9xcQu9sLXtCm9RIDrq5hIWFOhQ4Qv1e\nupLOdu9N6iXFy0tceO9bBQF5onpuY9A3f3FphQ5xfujlq1WdQ+6zbntfhWqbucHZ9akT2hUZNvee\n9fiGEUJM8Q0jhNhQf4wE97yv3eRcWSeP63SDe2/UQ945C9yQOFKkh+/ppH725ue7of55M/Vqt3lx\nPTxtGnJD2f6Idk+lfM/00oh2Ic7M18k2Cxc4E+JklV5FFyTqG+ovLTg+wplAUpwMsYB8cd8GG8mE\nNi/Sx7XZ8utel5izco7OEPT+y95Q9YY+95nsa9Vuy+oyX9LR4FA/ZFiPbxghxBTfMEKIKb5hhBCz\n8Uci4mzPSIXO9HL0NudKG6wKuIIC7qj2Lrc6r3KGDuft6dc2dV+fq3cM6lV1cSYDdWcnB8Ny/avh\n9gxVq7aiyJCq11W4TD7dRVqekrg+tzy/P1tuT2pfX3VMZxeqjLv32p7Qm11Gfa6/tYsOqrZteXpD\njf5uJ1NZgV5Z+HKzXp3X+Y4L980P7J+ZmO2+Q9YH+ryQhfBaj28YIcQU3zBCiCm+YYQQs/HHiNTO\nVPW0LwI1MUPbh9GTAb/0CWffdsS13Z6aoe32eXOdz/+qmdr2LY5o+7Y85rLYLMzTS2S7084X/ma/\ntpkbBvTGk5X57joR6vmJVCAbTmnMydAwFLhOTMcHXFTckC0fGNCxDv75ifygjz+QlajfVz7aopfs\nxuL68+Msl4l4oFLL3nHMffZVW3VmIQnMB5zrWI9vGCHEFN8wQogN9UfCN/7rr9Wuq/6a4d0/+Yv0\nKrrLao8OcybwSr1O1Nmw3yXx/ENg2L2m+B1V969aa0nqzS2Ppl32mZNJneEmHhhaD/iyAKVFD4GL\n49q8SPs2VQomxWwY0sPwkqgbdgev251ypsiRHm0y9PTokF0MOtMp3at/sgntZUW6z7VH+rXJldfj\nG88z3H1euN+9YYQUU3zDCCFj2Sa7gOSrJN8guYvk33vHF5HcQvIAyUdJ5o12LcMwpgdjsfEHAdwo\nIj0k4wBeJPk0gLsBfEtEHiH5fQB3AfjeJMo6pUQS2t8ze6lb1rmgrEO17TuhQ2S3Nriw0gvm6NDa\nj6/cpuobjlyQLR9t0bZvfZ2+bk3chdpGA9lvGpPutUVRHXZ7fLBU1YfSw/8M0qL7hkTa2c3BzLnB\nTL99Pp9ncF4h3xd+vKqyQbUNpfV1jza5DD2l9XquoHeeng9Il7n7RGbpTUa75zt3XvnCOtWWOnBI\n1c/1jDyj9viS4ZSDNu79CYAbAfzSO/5jAB+eFAkNw5hwxmTjk4ySfB1AC4CNAN4B0Ckipx7bDQDm\nDvPa9SS3kdyWwODpTjEMI8eMSfFFJCUiqwDUAVgD4Pyx3kBE7heR1SKyOo6Rs7sYhpEbzsiPLyKd\nJDcDWAuggmTM6/XrADROhoDThbwT2l481OxSPM0u0qGq65e+qOpFvlDbl7qWqbbd3XNUfe2cw9ny\nr9suVG0nkzrctzjPzS0El8ieGHL19iH9uoYenX227QWX+TdRqm3bqov0LkGzfJtoFkZ1Sq+eiA6f\nrclzabKC4byHBt18xYvHF6u2Y0f0DjgFve6e6Zv1fEoksLGov15SrL+zzhWurQH6c5/7fZ1GLN2r\nl0+fa4xlVr+aZIVXLgRwM4DdADYDuMM7bR2AJydLSMMwJpax9Pg1AH5MMorMg+IxEdlA8m0Aj5D8\nGoAdAB6YRDkNw5hARlV8EXkTwKWnOX4QGXs/FEQGtEss0uFcYkH3U3ClXEXErX6bW6WHqv495AEg\nIe4ruemaXaotuMd8Z0oP4f0U+lx4Q2ntvivL10Pg+MvOFIkMabfgkYR2IZavda/tGNKhwIOBz6E8\n5tbV1QVWFvpdf0NJ/TNcvkS7POsudm7LwYDrsbFXx+weP+nea2+fnlOKtbl7pgJRJ6wLbHSy9wDO\nZSxyzzBCiCm+YYQQU3zDCCG2LHeMSDSQVafI2cLBLDU/a71C1W+c4TatLArYusWBjLelEecCKwjs\ngOPPqgMABwfdEt79fbNUW+uAc+f1JrRBW/+OzoZTfLn7GRQ1a3derE+HyDZ1Oxu6NrCrTdC95w/p\nDYb+XlbsQmRL5+s5h7bATp2vn9DhtX6igQ1LK0vcfMpgYO6grdrZ+Hkn9WeSqNb3jOwd9pbnBNbj\nG0YIMcU3jBBiim8YIcRs/LESCzwjfabloVadcqqlUNuL75x0GXoLYjqs9bpq7S++tKg+W+4OpMy6\n7zW9ADJ/m7tPYFUu/NG9vFTb4ndf+4yql9/gwlODcw4/P65DNQ51uveaFwjR3bhfL+EQX7qtj6x4\nXbV9YsYr2XJZRNv4j7bpeyZ8cyhVhX2qrTqw829jn/Prt3Xp3XuKKlxcweBM/dOPdWoZzvWku9bj\nG0YIMcU3jBBiQ/2R8GVhSRXq7DKRUue6KijQbqyBofiw9URCuwWfHlqp6gsWu3Dfqwp1VpjkgL5u\ngW882j9Lu+EWX+4y+35hwUbVFtx8M+VztVVE9VD6S3VPq3rzHDeU/m3nxapNGrVpUu6zYp4t1mbA\ntaXOXzYrqrMSryw5Nmx9MJDl51Cf3uikZ8iF6RbmB0Kcj7lMxAt+F8iSfKAeYcJ6fMMIIab4hhFC\nTPENI4SYjT8SdO6oSELbhOk+F/J5+TK9U87JhA6t9bvAltXqJbsfnrlD1Wvjbtnu64O1qi3aosNM\nexY5mf7L9c+ptptKd2bLzUm9dPX57vNU/alDF2XL82foZcNXVemNOy8uPJItX1mqXZGvnLdA1YeO\nu0w6iTf08uNvFt2SLX972aOq7ZKCI6remnK2+RMteoX4W8f0Z5QYdD/pWHz43Y5ShbrPSw8lhjnz\n3MR6fMMIIab4hhFCbKg/Ej53XrRdR4iV7HfDzx01OrP4zfP00q71NX/IlgdEu6OCG2H4V7R9eevt\nqi1drM/96NpXs+XVRXpI7h/e7x7Q8j3ToF1rMx52EYB7r9PZeopW60i+ujy3kUhwpeHnl29S9X/c\ncWe2XPl2ILPPQmf+5C/XQ/JgJJ+f8rhuG+rW5k+8xZdlp1C7OFnp3ktPjTbHigOrLyU9vJlwLmA9\nvmGEEFN8wwghpviGEULMxh8Jnzsv1agzv856zbmqDi7R7rI/xJaqemfChbIGN6g83q9t6puqfdl6\nAhtCXLN8t6pfVlyfLT/aprP+7Gp3G0YU52k7fUG5dtkdnOfCXqNz9EYSBVEd3vt673wMx23lb6j6\n7R90G4ts6LpGtTE6/KaUcWr72h9GfPtMvcnoRWv1hpv1A+69zIrrUOAXTyzJlvt/pt2AkjR3nmEY\n5zim+IYRQkzxDSOEmI0/Ej4/vgxpO7lgv9tkseItbfe2FGqbf3OXS4eTDGzyeP68ZlVfnN+SLd+z\n4lnVtqtP++MfaHB2c3OXnivwZ78JZqK9plove31jsbN9C/O1TV/fpbMLbT3p3msspm3xkiXar39L\nmQsbfnzNKtV22Vy3x2oaOpPvO4Hde753+IZs+WS/9r939+ilwP4w3XRaXzeVcv3c8kN6niMlw885\nnItYj28YIWTMik8ySnIHyQ1efRHJLSQPkHyUZN5o1zAMY3pwJkP9zyGzPfapWNVvAPiWiDxC8vsA\n7gLwvQmWb/oQGApKv0vcmNZ7M6Kupl3VI3Sv/VjdVtXmX40HAHl+V1bgsRyPDB9G6h/aA0BJgRt2\ndw9oATc16dV56XxnCgz06+e3FPWr+lCPr71Zhx8/AZ2RZ9HK1mz5F1fcr9oKfO9zQKKBNu1aWzvT\nZSLKDyT47EjqjUP9rsAN71yo2pZ82oU1p/t0pqGwMaYen2QdgPcD+IFXJ4AbAfzSO+XHAD58+lcb\nhjHdGOtQ/9sAvgSXdbgKQKeInHr8NgCYe7oXklxPchvJbQkMnu4UwzByzKiKT/IDAFpEZPt4biAi\n94vIahFZHUf+6C8wDGPSGYuNfzWAD5K8DUABMjb+dwBUkIx5vX4dgMYRrnHOke50m1TMflXbiwcv\n1+686hkudPQbr7xPtc2p0Tb+Zxa9kC0vy9OuvsuKdNbdRXOdDX1opnaB/eboBdlyZcBO7x4MzMP6\npwea9cO5LaY3B0HC9RWJOj2CK4hqt+GOHpeRpzrWpdqWxE9gOObE9AYga0qcbf5yjw6Hfqlpsaon\nfS67aEAev10vqXN72e1ojNrji8h9IlInIgsBfAzA70XkEwA2A7jDO20dgCcnTUrDMCaUs/Hj3wPg\nbpIHkLH5H5gYkQzDmGzOKHJPRJ4D8JxXPghgzUjnG4YxPbGQ3XEiaeebj2zZqdqWd2i7c89/dUtF\nq5doH/+Scm3r+v3QqcCALC+wXPV4ws0lPLzjStW2YK7L5psX8P93ibbjWeR84zyp22ortb29eIGz\nt/3xCQDQMaTDZ+t7Xbjvzvx5qs1vx3endRju8z06NdgzjSuy5URKfybVxXoZccwXnpz+qJ6DSPnt\n+pCF6AaxkF3DCCGm+IYRQmyoP158WVglsJm6HNXZesr3uqF+a7F29fUOaNfaH/e5lXIfvEhntLmy\n5B1V92e8vf78fapte3NdthyP6qH+5XP0hhXHy13G4KMztHyleXq4XBxz9WD4bFlMuw2r4m4YfmGh\n3nTETzDz8P6eWare0ubku2qpziZcEdf3fHqv24R0aceb+kYhH977sR7fMEKIKb5hhBBTfMMIIWbj\nTwLpXh3CW/OIy5w7WKldVf1LAru9+MJMt7ToTSiP9OqNJ6+YUZ8tv6dU2+27Trgsuz0v63Dejct0\nGO583zLiwri223cdrVH1o6UV2fJNgR2Dri3T8wxFdPMB7Sl9z5+1r3WvK9Wvu3qG3ozz5aSb93hp\n1zLVhsD8ynmfdZuQSnDyxchiPb5hhBBTfMMIITbUnwwCGy6m2t0KvEUP6w0gGj5Sp+o973HuqVRa\nP5ebestU/Q8pN+z91NyXVNtH57/uzivSw+Pdh/RmEg1vObMgPl9Hwi2coyMLW3uKs+W3OvV1Li/R\nrrbqqBvq7+hfqNo2H3Uy1SzR0YFFEZ3Y9I5VbkX4L7dcrtpW3LNH1VNJbaoYp8d6fMMIIab4hhFC\nTPENI4SYjZ8LfKGiycM6dHXuQz2q3rfHua4ar5+p2grP71T1Et9mmPsGtNvNz+ISbac3Vem5gq5O\n56Ib7NXxWip2AAAG7ElEQVQhxPEqPV/xyaWvZstXFe1Xbc3JClX/9Um3iUbDgG7rPulW8m0/qTck\nKY3pMOHN2102ofMe1K7SVLfeGNMYG9bjG0YIMcU3jBBiim8YIcRs/FwTWBrq9/EDQMFGF3K6fEeV\nauu5XIfwxu92/u+nj61UbVdW12fLtfl6buDGOh0i+8Rh5xuP5Gmb/oqqelWvjbtr1Qc2t3y4UWcB\n2rvPt9VCIFtPXqv76R2p1qHIxxt0/fx/c3Z8+k0dJmxLbceH9fiGEUJM8Q0jhNhQf6oJbsbpSwiZ\natNuuMKndV0Ou80l8mpLVdvv/8q55QYT+mseOqxXyi15YiBbPnKrTpjZsFgPu0ui7tzBtM6cM5DU\n9fxKF3482KOTeBa0uV08qu7SLs3y43rjkLStsptwrMc3jBBiim8YIcQU3zBCiNn40w2fzS/BJaak\nPvUt59oq2Kdt6NmtLvQ3XaC/5lhrq6pLo9ucc+GQ3gxkny9cFgC2zb84W+5ZoG3vSELLV73DvZfy\np3TGW/9cRnLQtk/PNdbjG0YIGVOPT7IeQDeAFICkiKwmWQngUQALAdQDuFNEOoa7hmEY04cz6fH/\nTERWichqr34vgE0isgzAJq9uGMa7AMoYQh69Hn+1iLT5ju0FcIOINJGsAfCciJw30nXKWClX8L1n\nKbIxqUSiqsqIttsjJS71FtL6t5Pu00tm/Xb8n2ChtpPCFtmELmnnaOeNtccXAM+S3E5yvXdstoic\n2iuqGcDs072Q5HqS20huS8AmcQxjOjDWWf1rRKSR5CwAG0mqDIciIiRP+wgXkfsB3A9kevyzktYw\njAlhTIovIo3e/xaSTwBYA+A4yRrfUL9lEuU0ckUgQ3AwWjZ1ssvXaM/xdyujDvVJFpMsPVUGcAuA\nnQCeArDOO20dgCcnS0jDMCaWsfT4swE8wUzwSAzAz0TktyS3AniM5F0ADgO4c/LENAxjIhlV8UXk\nIIBLTnP8BACbojeMdyEWsmucGWbXnxNYyK5hhBBTfMMIIab4hhFCTPENI4SY4htGCDHFN4wQYopv\nGCHEFN8wQogpvmGEEFN8wwghpviGEUJM8Q0jhJjiG0YIMcU3jBBiim8YIcQU3zBCiCm+YYQQU3zD\nCCGm+IYRQkzxDSOEmOIbRggxxTeMEGKKbxghxBTfMEKIKb5hhBBTfMMIIab4hhFCTPENI4RQcrgJ\nIslWZLbUngmgLWc3Hh2TZ2SmmzzA9JNpusizQESqRzspp4qfvSm5TURW5/zGw2DyjMx0kweYfjJN\nN3lGw4b6hhFCTPENI4RMleLfP0X3HQ6TZ2SmmzzA9JNpuskzIlNi4xuGMbXYUN8wQogpvmGEkJwq\nPslbSe4leYDkvbm8t0+GH5JsIbnTd6yS5EaS+73/M3IozzySm0m+TXIXyc9NpUwkC0i+SvINT56/\n944vIrnF++4eJZmXC3l8ckVJ7iC5YarlIVlP8i2Sr5Pc5h2bst/QeMiZ4pOMAvgXAO8DsBLAx0mu\nzNX9fTwI4NbAsXsBbBKRZQA2efVckQTwRRFZCeBKAJ/1PpepkmkQwI0icgmAVQBuJXklgG8A+JaI\nLAXQAeCuHMlzis8B2O2rT7U8fyYiq3y++6n8DZ05IpKTPwBrATzjq98H4L5c3T8gy0IAO331vQBq\nvHINgL1TIZd3/ycB3DwdZAJQBOA1AFcgE5UWO913mQM56pBRphsBbADAKZanHsDMwLEp/77O5C+X\nQ/25AI766g3esenAbBFp8srNAGZPhRAkFwK4FMCWqZTJG1a/DqAFwEYA7wDoFJGkd0quv7tvA/gS\ngLRXr5pieQTAsyS3k1zvHZsWv6GxEptqAaYbIiIkc+7jJFkC4HEAnxeRLpJTJpOIpACsIlkB4AkA\n5+fq3kFIfgBAi4hsJ3nDVMkR4BoRaSQ5C8BGknv8jVP1GzoTctnjNwKY56vXecemA8dJ1gCA978l\nlzcnGUdG6X8qIr+aDjIBgIh0AtiMzFC6guSpjiKX393VAD5Ish7AI8gM978zhfJARBq9/y3IPBjX\nYBp8X2dCLhV/K4Bl3mxsHoCPAXgqh/cfiacArPPK65Cxs3MCM137AwB2i8g3p1omktVeTw+ShcjM\nN+xG5gFwR67lEZH7RKRORBYi85v5vYh8YqrkIVlMsvRUGcAtAHZiCn9D4yKXEwoAbgOwDxmb8X9M\nxaQGgJ8DaAKQQMY2vAsZm3ETgP0AfgegMofyXIOMzfgmgNe9v9umSiYAFwPY4cmzE8BXvOOLAbwK\n4ACAXwDIn4Lv7gYAG6ZSHu++b3h/u079jqfyNzSePwvZNYwQYpF7hhFCTPENI4SY4htGCDHFN4wQ\nYopvGCHEFN8wQogpvmGEkP8PUQBIZc1cd1IAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plt.imshow(mi.numpy())\n", + "plt.title('Original moving image')\n", + "plt.show()\n", + "\n", + "plt.imshow(fi.numpy())\n", + "plt.title('Original fixed image')\n", + "plt.show()\n", + "\n", + "plt.imshow(mytx['warpedmovout'].numpy())\n", + "plt.title('Warped moving imag')\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SparseDecom2\n", + "\n", + "Another ANTsR-validated result:\n", + "\n", + "```R\n", + "mat<-replicate(100, rnorm(20))\n", + "mat2<-replicate(100, rnorm(20))\n", + "mat<-scale(mat)\n", + "mat2<-scale(mat2)\n", + "mydecom<-sparseDecom2(inmatrix = list(mat,mat2), sparseness=c(0.1,0.3), nvecs=3, its=3, perms=0)\n", + "```\n", + "The 3 correlation values from that experiment are: [0.9762784, 0.9705170, 0.7937968]\n", + "\n", + "After saving those exact matrices, and running the ANTsPy version, we see that we get the exact same result " + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available Results: ['projections', 'projections2', 'eig1', 'eig2', 'corrs']\n", + "Correlations: [ 0.97627841 0.97051702 0.79379676]\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "mat = pd.read_csv('~/desktop/mat.csv', index_col=0).values\n", + "mat2 = pd.read_csv('~/desktop/mat2.csv', index_col=0).values\n", + "\n", + "\n", + "mydecom = ants.sparseDecom2(inmatrix=(mat,mat2), sparseness=(0.1,0.3), \n", + " nvecs=3, its=3, perms=0)\n", + "\n", + "print('Available Results: ', list(mydecom.keys()))\n", + "print('Correlations: ', mydecom['corrs'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/MindEyeV2/antspy/docs/other/ANTsR_Comparison.md b/MindEyeV2/antspy/docs/other/ANTsR_Comparison.md new file mode 100644 index 0000000000000000000000000000000000000000..d5047329c47aea8ce9d355796501088db76923d6 --- /dev/null +++ b/MindEyeV2/antspy/docs/other/ANTsR_Comparison.md @@ -0,0 +1,16 @@ + +```R +library(ANTsR) +img <- antsImageRead( getANTsRData("r16") , 2 ) +img <- resampleImage( img, c(64,64), 1, 0 ) +mask <- getMask(img) +segs1 <- atropos( a = img, m = '[0.2,1x1]', c = '[2,0]', i = 'kmeans[3]', x = mask ) +``` + +```python +import ants +img = ants.image_read(ants.get_ants_data('r16')) +img = ants.resample_image(img, (64,64), 1, 0) +mask = ants.get_mask(img) +ants.atropos(a = img, m = '[0.2,1x1]', c = '[2,0]', i = 'kmeans[3]', x = mask ) +``` \ No newline at end of file diff --git a/MindEyeV2/antspy/docs/other/All_Functions.md b/MindEyeV2/antspy/docs/other/All_Functions.md new file mode 100644 index 0000000000000000000000000000000000000000..9f49409530bd0b018cc30c78f91ff74bd0e044d6 --- /dev/null +++ b/MindEyeV2/antspy/docs/other/All_Functions.md @@ -0,0 +1,180 @@ + +ANTsR_Name ANTsPy_Name Description +abpN4 | | +affineInitializer | | a multi-start optimizer for affine registration +mean | |,antsImage-method arith.antsImage +antsImageClone | | +antsMotionCalculation | | Correct 4D time-series data for motion. +antsRegistration | | +antsImagePhysicalSpaceConsistency | | Check for physical space consistency +antsImageRead | | +antsrimpute | | +antsImageHeaderInfo | | Read file info from image header +antsMotionCorr | | Motion Correction +antsImageGetSet | | antsImageGetSet +antsImageMutualInformation | | mutual information between two images +antsImageWrite | | +antsSetPixels | | Set a pixel value at an index +aslDenoiseR | | +aslPerfusion | | +antsTransformIndexToPhysicalPoint | | Get Spatial Point from Index +as.antsImage | | +antsTransformPhysicalPointToIndex | | Get Index from Spatial Point +antsSpatialICAfMRI | | +aslOutlierRejection | | Pair-wise subtraction based outlier rejection. +atropos | | FMM Segmentation +as.antsMatrix | | as.antsMatrix +combineNuisancePredictors | | Combine and reduce dimensionality of nuisance predictors. +clusterTimeSeries | | Split time series image into k distinct images +bigLMStats | | +bayesianlm | | Simple bayesian regression function. +computeDVARS | | computeDVARS +compcor | | +basicInPaint | | Inpaints missing imaging data from boundary data +bayesianCBF | | Uses probabilistic segmentation to constrain pcasl-based cbf computation. +blockStimulus | | +bold_correlation_matrix | | bold_correlation_matrix +crossvalidatedR2 | | Cross-Validated R^2 value +createJacobianDeterminantImage | | createJacobianDeterminantImage +DesikanKillianyTourville | | DesikanKillianyTourville +eigSeg | | +cropImage | | crop a sub-image via a mask +cropIndices | | crop a sub-image by image indices +convolveImage | | convolve one image with another +corw | | +cvEigenanatomy | | +decropImage | | decrop a sub-image back into the full image +getMask | | +getfMRInuisanceVariables | | +getNeighborhoodInMask | | Get neighborhoods for voxels within mask +getPixels | | Get Pixels +icawhiten | | +image2ClusterImages | | +imageFileNames2ImageList | | +imageListToMatrix | | +iMath | | iMath +iMathOps | | iMathOps +maskImage | | +matrix2timeseries | | Simple matrix2timeseries function. +getAverageOfTimeSeries | | +getCentroids | | +hemodynamicRF | | Linear Model for FMRI Data +iBind | | iBind +initializeEigenanatomy | | +interleaveMatrixWithItself | | +labelStats | | labelStats +lappend | | +makeGraph | | +makeImage | | +partialVolumeCorrection | | +projectImageAlongAxis | | +perfusionregression | | +quantifyCBF | | +sparseDecom | | +sparseDecom2 | | +usePkg | | Use any package. If package is not installed, this will install from CRAN. +vwnrfs | | voxelwise neighborhood random forest segmentation and prediction +antsApplyTransforms | | +n4BiasFieldCorrection | | Bias Field Correction +reorientImage | | reorient image by its principal axis +renderSurfaceFunction | | +rfSegmentationPredict | | +subjectDataToGroupDataFrame | | +timeseries2matrix | | +rsfDenoise | | +taskFMRI | | +timeseriesN3 | | Run N3 on slices of timeseries. +filterfMRIforNetworkAnalysis | | +getNeighborhoodAtVoxel | | Get a hypercube neighborhood at a voxel +imagesToMatrix | | +getMultivariateTemplateCoordinates | | +lowrankRowMatrix | | Produces a low rank version of the input matrix +imageMath | | R access to the ANTs program ImageMath +labelGeometryMeasures | | labelGeometryMeasures +labelImageCentroids | | labelImageCentroids +frequencyFilterfMRI | | +antsBOLDNetworkAnalysis | | a basic framework for network analysis that produces graph metrics +antsCopyImageInfo | | Copy header info +getANTsRData | | getANTsRData +plotBasicNetwork | | +getASLNoisePredictors | | Get nuisance predictors from ASL images +is.antsImage | | is.antsImage +joinEigenanatomy | | +invariantImageSimilarity | | similarity metrics between two images as a function of geometry +jointIntensityFusion | | joint intensity fusion +aal | | aal +plot.antsImage | | Plotting an image slice or multi-slice with optional color overlay. +abpBrainExtraction | | +getTemplateCoordinates | | +extractSlice | | extract a slice from an image +%>% | | Pipe an object forward +exemplarInpainting | | Uses example images to inpaint or approximate an existing image. +quantifySNPs | | Simple quantifySNPs function. +rapidlyInspectImageData | | Simple rapidlyInspectImageData function. +whiten | | +temporalwhiten | | +thresholdImage | | Threshold Image +jointIntensityFusion3D | | jointIntensityFusion3D +antsAverageImages | | Computes average of image list +kellyKapowski | | Compute cortical thickness using the DiReCT algorithm. +mrvnrfs | | multi-res voxelwise neighborhood random forest segmentation learning +mrvnrfs.predict | | multi-res voxelwise neighborhood random forest segmentation +n3BiasFieldCorrection | | Bias Field Correction +plotPrettyGraph | | +preprocessfMRI | | Preprocess BOLD fMRI image data. +reflectImage | | reflectImage +sparseDecom2boot | | +sparseDecomboot | | +regressionNetworkViz | | +timeserieswindow2matrix | | +tracts | | tracts +save.ANTsR | | save.ANTsR +segmentShapeFromImage | | convolution-based shape identification +kmeansSegmentation | | k means image segmentation. +labelClusters | | +matrixToImages | | +networkEiganat | | +mni2tal | | +pairwiseImageDistanceMatrix | | +regressProjections | | +renderImageLabels | | +sliceTimingCorrection | | slice timing correction for fMRI. +sparseRegression | | +smoothImage | | Smooth image +spatialbayesianlm | | spatially constrained bayesian regression function. +make3ViewPNG | | +resampleImage | | resampleImage +rfSegmentation | | +splitData | | +subgradientL1Regression | | +antsPreprocessfMRI | | +as.matrix | | +as.numeric | | +perfusion-predictors | | +getROIValues | | +phantom_population_study | | +fastwhiten | | +Motion-Correction | | +sccan | | +as.antsImage | | +as.array | | +MeasureMinMaxMean | | +plotANTsImage | | +renderNetwork | | +visualizeBlob | | +inspectImageData3D | | +antsImage-class | | +CBF | | +simple_roi_analysis | | +simple_voxel_based_analysis | | +KellyKapowski | | +Atropos | | +Extract | | +SmoothImage | | +N3BiasFieldCorrection | | +ImageMath | | +Comparison | | +ThresholdImage | | +antsMatrix-class | | An S4 class to hold an antsMatrix imported from ITK types +antsRegion-class | | An S4 class to hold a region of an antsImage +%>% | | Pipe an object forward diff --git a/MindEyeV2/antspy/docs/other/_config.yml b/MindEyeV2/antspy/docs/other/_config.yml new file mode 100644 index 0000000000000000000000000000000000000000..2f7efbeab578c8042531ea7908ee8ffd7589fe46 --- /dev/null +++ b/MindEyeV2/antspy/docs/other/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-minimal \ No newline at end of file diff --git a/MindEyeV2/antspy/docs/requirements.txt b/MindEyeV2/antspy/docs/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..fb4e653ca054ad5ead6fac830d04221ec597c54c --- /dev/null +++ b/MindEyeV2/antspy/docs/requirements.txt @@ -0,0 +1,2 @@ +sphinx +-e git+https://github.com/snide/sphinx_rtd_theme.git#egg=sphinx_rtd_theme diff --git a/MindEyeV2/antspy/docs/source/ants.core.rst b/MindEyeV2/antspy/docs/source/ants.core.rst new file mode 100644 index 0000000000000000000000000000000000000000..e1668a5b9147d63a12454680661da422df3bfdad --- /dev/null +++ b/MindEyeV2/antspy/docs/source/ants.core.rst @@ -0,0 +1,62 @@ +ants.core package +================= + +Submodules +---------- + +ants.core.ants\_image module +---------------------------- + +.. automodule:: ants.core.ants_image + :members: + :undoc-members: + :show-inheritance: + +ants.core.ants\_image\_io module +-------------------------------- + +.. automodule:: ants.core.ants_image_io + :members: + :undoc-members: + :show-inheritance: + +ants.core.ants\_metric module +----------------------------- + +.. automodule:: ants.core.ants_metric + :members: + :undoc-members: + :show-inheritance: + +ants.core.ants\_metric\_io module +--------------------------------- + +.. automodule:: ants.core.ants_metric_io + :members: + :undoc-members: + :show-inheritance: + +ants.core.ants\_transform module +-------------------------------- + +.. automodule:: ants.core.ants_transform + :members: + :undoc-members: + :show-inheritance: + +ants.core.ants\_transform\_io module +------------------------------------ + +.. automodule:: ants.core.ants_transform_io + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: ants.core + :members: + :undoc-members: + :show-inheritance: diff --git a/MindEyeV2/antspy/docs/source/ants.learn.rst b/MindEyeV2/antspy/docs/source/ants.learn.rst new file mode 100644 index 0000000000000000000000000000000000000000..d81d7ca24de5e3231d9f72caccd4fef74924f6b1 --- /dev/null +++ b/MindEyeV2/antspy/docs/source/ants.learn.rst @@ -0,0 +1,22 @@ +ants.learn package +================== + +Submodules +---------- + +ants.learn.decomposition module +------------------------------- + +.. automodule:: ants.learn.decomposition + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: ants.learn + :members: + :undoc-members: + :show-inheritance: diff --git a/MindEyeV2/antspy/docs/source/ants.lib.rst b/MindEyeV2/antspy/docs/source/ants.lib.rst new file mode 100644 index 0000000000000000000000000000000000000000..d6c69751bb197b435400c183ea1805c6be1cfa2c --- /dev/null +++ b/MindEyeV2/antspy/docs/source/ants.lib.rst @@ -0,0 +1,10 @@ +ants.lib package +================ + +Module contents +--------------- + +.. automodule:: ants.lib + :members: + :undoc-members: + :show-inheritance: diff --git a/MindEyeV2/antspy/docs/source/ants.rst b/MindEyeV2/antspy/docs/source/ants.rst new file mode 100644 index 0000000000000000000000000000000000000000..cea4a63f9c2924a1123a9ce71321a1975929e66b --- /dev/null +++ b/MindEyeV2/antspy/docs/source/ants.rst @@ -0,0 +1,35 @@ +ants package +============ + +Subpackages +----------- + +.. toctree:: + + ants.core + ants.learn + ants.lib + ants.registration + ants.segmentation + ants.utils + ants.viz + +Submodules +---------- + +ants.version module +------------------- + +.. automodule:: ants.version + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: ants + :members: + :undoc-members: + :show-inheritance: diff --git a/MindEyeV2/antspy/docs/source/ants.segmentation.rst b/MindEyeV2/antspy/docs/source/ants.segmentation.rst new file mode 100644 index 0000000000000000000000000000000000000000..e91725efca0b49a67c761669fefe71dd369f9454 --- /dev/null +++ b/MindEyeV2/antspy/docs/source/ants.segmentation.rst @@ -0,0 +1,78 @@ +ants.segmentation package +========================= + +Submodules +---------- + +ants.segmentation.anti\_alias module +------------------------------------ + +.. automodule:: ants.segmentation.anti_alias + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.atropos module +-------------------------------- + +.. automodule:: ants.segmentation.atropos + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.joint\_label\_fusion module +--------------------------------------------- + +.. automodule:: ants.segmentation.joint_label_fusion + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.kelly\_kapowski module +---------------------------------------- + +.. automodule:: ants.segmentation.kelly_kapowski + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.kmeans module +------------------------------- + +.. automodule:: ants.segmentation.kmeans + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.label\_geometry\_measures module +-------------------------------------------------- + +.. automodule:: ants.segmentation.label_geometry_measures + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.otsu module +----------------------------- + +.. automodule:: ants.segmentation.otsu + :members: + :undoc-members: + :show-inheritance: + +ants.segmentation.prior\_based\_segmentation module +--------------------------------------------------- + +.. automodule:: ants.segmentation.prior_based_segmentation + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: ants.segmentation + :members: + :undoc-members: + :show-inheritance: diff --git a/MindEyeV2/antspy/docs/source/conf.py b/MindEyeV2/antspy/docs/source/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..d714c134e4b423ba1afd8d1295a5b56ce8fc695a --- /dev/null +++ b/MindEyeV2/antspy/docs/source/conf.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# ANTsPy documentation build configuration file, created by +# sphinx-quickstart on Fri Dec 23 13:31:47 2016. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# + +import os +import sys +import shutil + +on_rtd = os.environ.get('READTHEDOCS') == 'True' + + # add package to local path +sys.path.insert(0, os.path.abspath('../../')) +autodoc_mock_imports = ['_tkinter', 'matplotlib'] +if on_rtd: + # replace lib __init__ with empty init file since RTD cant handle C++ extensions + os.makedirs('../../ants/lib', exist_ok=True) + shutil.copyfile('emptyinit.py', '../../ants/lib/__init__.py') + +import ants +import numpy as np +import sphinx_rtd_theme + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', +] + +napoleon_use_ivar = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'ANTsPy' +copyright = '2017, ANTs Contributors' +author = 'ANTs Contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +# TODO: change to [:2] at v1.0 +version = 'master (0.1.3)' +# The full version, including alpha/beta/rc tags. +# TODO: verify this works as expected +release = 'master' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'alabaster' +#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +#html_theme_options = { +# 'collapse_navigation': False, +# 'display_version': True, +# 'logo_only': True, +#} + +html_logo = '_static/img/antspy-logo.png' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +#html_style_path = '_static/css/ants_theme.css' +#html_context = { +# 'css_files': [ +# 'https://fonts.googleapis.com/css?family=Lato', +# '_static/css/ants_theme.css' +# ], +#} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ANTsPydoc' + + +# -- Options for LaTeX output --------------------------------------------- + + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'ANTsPy', 'ANTsPy Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'ANTsPy', 'ANTsPy Documentation', + author, 'ANTsPy', 'One line description of project.', + 'Miscellaneous'), +] + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), + 'numpy': ('http://docs.scipy.org/doc/numpy/', None), +} + +# -- A patch that prevents Sphinx from cross-referencing ivar tags ------- +# See http://stackoverflow.com/a/41184353/3343043 + +from docutils import nodes +from sphinx.util.docfields import TypedField +from sphinx import addnodes + + +def patched_make_field(self, types, domain, items, **kw): + # `kw` catches `env=None` needed for newer sphinx while maingaining + # backwards compatibility when passed along further down! + + # type: (List, unicode, Tuple) -> nodes.field + def handle_item(fieldarg, content): + par = nodes.paragraph() + par += addnodes.literal_strong('', fieldarg) # Patch: this line added + # par.extend(self.make_xrefs(self.rolename, domain, fieldarg, + # addnodes.literal_strong)) + if fieldarg in types: + par += nodes.Text(' (') + # NOTE: using .pop() here to prevent a single type node to be + # inserted twice into the doctree, which leads to + # inconsistencies later when references are resolved + fieldtype = types.pop(fieldarg) + if len(fieldtype) == 1 and isinstance(fieldtype[0], nodes.Text): + typename = u''.join(n.astext() for n in fieldtype) + typename = typename.replace('int', 'python:int') + typename = typename.replace('long', 'python:long') + typename = typename.replace('float', 'python:float') + typename = typename.replace('type', 'python:type') + par.extend(self.make_xrefs(self.typerolename, domain, typename, + addnodes.literal_emphasis, **kw)) + else: + par += fieldtype + par += nodes.Text(')') + par += nodes.Text(' -- ') + par += content + return par + + fieldname = nodes.field_name('', self.label) + if len(items) == 1 and self.can_collapse: + fieldarg, content = items[0] + bodynode = handle_item(fieldarg, content) + else: + bodynode = self.list_type() + for fieldarg, content in items: + bodynode += nodes.list_item('', handle_item(fieldarg, content)) + fieldbody = nodes.field_body('', bodynode) + return nodes.field('', fieldname, fieldbody) + +TypedField.make_field = patched_make_field + diff --git a/MindEyeV2/antspy/docs/source/core.rst b/MindEyeV2/antspy/docs/source/core.rst new file mode 100644 index 0000000000000000000000000000000000000000..23da9f3d86c207615aaa21562e8923baa5795e69 --- /dev/null +++ b/MindEyeV2/antspy/docs/source/core.rst @@ -0,0 +1,57 @@ +Core +=================================== +.. automodule:: ants + +Images +---------------------------------- + +ANTsImage +~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: ants.core.ants_image.ANTsImage + :members: + +ANTsImage IO +~~~~~~~~~~~~~~~~~~~~~~ +.. autofunction:: image_clone +.. autofunction:: image_header_info +.. autofunction:: image_read +.. autofunction:: image_write +.. autofunction:: make_image +.. autofunction:: from_numpy +.. autofunction:: matrix_to_images +.. autofunction:: images_from_matrix +.. autofunction:: image_list_to_matrix +.. autofunction:: images_to_matrix +.. autofunction:: matrix_from_images + +Transforms +---------------------------------- + +ANTsTransform +~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: ants.core.ants_transform.ANTsTransform + :members: + +ANTsTransform IO +~~~~~~~~~~~~~~~~~~~~~~ +.. autofunction:: create_ants_transform +.. autofunction:: new_ants_transform +.. autofunction:: read_transform +.. autofunction:: write_transform +.. autofunction:: transform_from_displacement_field + +Metrics +---------------------------------- + +ANTsMetric +~~~~~~~~~~~~~~~~~~~~~~ +.. autoclass:: ants.core.ants_metric.ANTsImageToImageMetric + :members: + +ANTsMetric IO +~~~~~~~~~~~~~~~~~~~~~~ +.. autofunction:: new_ants_metric +.. autofunction:: create_ants_metric +.. autofunction:: supported_metrics + + diff --git a/MindEyeV2/antspy/docs/source/modules.rst b/MindEyeV2/antspy/docs/source/modules.rst new file mode 100644 index 0000000000000000000000000000000000000000..a889739d91c8532321f30941855ab127b19a1adc --- /dev/null +++ b/MindEyeV2/antspy/docs/source/modules.rst @@ -0,0 +1,8 @@ +ANTsPy +====== + +.. toctree:: + :maxdepth: 4 + + ants + setup diff --git a/MindEyeV2/antspy/docs/source/registration.rst b/MindEyeV2/antspy/docs/source/registration.rst new file mode 100644 index 0000000000000000000000000000000000000000..7ecef8075fe0ef7fe12c78ad8403b38e23ee254e --- /dev/null +++ b/MindEyeV2/antspy/docs/source/registration.rst @@ -0,0 +1,16 @@ +Registration +=================================== +.. automodule:: ants + +.. autofunction:: registration +.. autofunction:: affine_initializer +.. autofunction:: apply_transforms +.. autofunction:: create_jacobian_determinant_image +.. autofunction:: create_warped_grid +.. autofunction:: fsl2antstransform +.. autofunction:: image_mutual_information +.. autofunction:: reflect_image +.. autofunction:: reorient_image +.. autofunction:: get_center_of_mass +.. autofunction:: resample_image +.. autofunction:: resample_image_to_target diff --git a/MindEyeV2/antspy/docs/source/setup.rst b/MindEyeV2/antspy/docs/source/setup.rst new file mode 100644 index 0000000000000000000000000000000000000000..552eb49d6d6f0d57dfa29c548a425cb887bce52f --- /dev/null +++ b/MindEyeV2/antspy/docs/source/setup.rst @@ -0,0 +1,7 @@ +setup module +============ + +.. automodule:: setup + :members: + :undoc-members: + :show-inheritance: diff --git a/MindEyeV2/antspy/docs/source/vis.rst b/MindEyeV2/antspy/docs/source/vis.rst new file mode 100644 index 0000000000000000000000000000000000000000..3f734cddf48b62f771ecfcf1c06dad7acc5989d5 --- /dev/null +++ b/MindEyeV2/antspy/docs/source/vis.rst @@ -0,0 +1,8 @@ +Visualization +=================================== +.. automodule:: ants + +.. autofunction:: plot +.. autofunction:: surf +.. autofunction:: vol +.. autofunction:: render_surface_function diff --git a/MindEyeV2/antspy/src/WRAP_KellyKapowski.cxx b/MindEyeV2/antspy/src/WRAP_KellyKapowski.cxx new file mode 100644 index 0000000000000000000000000000000000000000..e1a7d43f52a583e1707cc16d7aa7faa64ee3e6ea --- /dev/null +++ b/MindEyeV2/antspy/src/WRAP_KellyKapowski.cxx @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "antscore/KellyKapowski.h" + +namespace nb = nanobind; +using namespace nb::literals; + +using StrVector = std::vector; + +int KellyKapowski( StrVector instring ) +{ + return ants::KellyKapowski(instring, NULL); +} + +void wrap_KellyKapowski(nb::module_ &m) { + m.def("KellyKapowski", &KellyKapowski); +} \ No newline at end of file diff --git a/MindEyeV2/antspy/src/WRAP_N4BiasFieldCorrection.cxx b/MindEyeV2/antspy/src/WRAP_N4BiasFieldCorrection.cxx new file mode 100644 index 0000000000000000000000000000000000000000..e998cada23ac62d6cd906b788e4dac276136eae8 --- /dev/null +++ b/MindEyeV2/antspy/src/WRAP_N4BiasFieldCorrection.cxx @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "antscore/N4BiasFieldCorrection.h" + +namespace nb = nanobind; +using namespace nb::literals; + +using StrVector = std::vector; + +int N4BiasFieldCorrection( StrVector instring ) +{ + return ants::N4BiasFieldCorrection(instring, NULL); +} + +void wrap_N4BiasFieldCorrection(nb::module_ &m) { + m.def("N4BiasFieldCorrection", &N4BiasFieldCorrection); +} \ No newline at end of file diff --git a/MindEyeV2/antspy/src/WRAP_ResampleImage.cxx b/MindEyeV2/antspy/src/WRAP_ResampleImage.cxx new file mode 100644 index 0000000000000000000000000000000000000000..7e103aa7c8c4cf20f8cfdb0a6c2523fbb3df1608 --- /dev/null +++ b/MindEyeV2/antspy/src/WRAP_ResampleImage.cxx @@ -0,0 +1,19 @@ +#include +#include +#include + +#include "antscore/ResampleImage.h" + +namespace nb = nanobind; +using namespace nb::literals; + +using StrVector = std::vector; + +int ResampleImage( StrVector instring ) +{ + return ants::ResampleImage(instring, NULL); +} + +void wrap_ResampleImage(nb::module_ &m) { + m.def("ResampleImage", &ResampleImage); +} \ No newline at end of file diff --git a/MindEyeV2/antspy/src/antsImage.h b/MindEyeV2/antspy/src/antsImage.h new file mode 100644 index 0000000000000000000000000000000000000000..ad55b91f98befddd83ae7bf887f10053ad63f4e0 --- /dev/null +++ b/MindEyeV2/antspy/src/antsImage.h @@ -0,0 +1,233 @@ +#ifndef __ANTSPYIMAGE_H +#define __ANTSPYIMAGE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "itkImageIOBase.h" + +#include "itkImage.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkPyBuffer.h" +#include "itkVectorImage.h" +#include "itkChangeInformationImageFilter.h" + +#include "itkMath.h" +#include "itkPyVnl.h" +#include "itkMatrix.h" +#include "vnl/vnl_matrix_fixed.hxx" +#include "vnl/vnl_transpose.h" +#include "vnl/algo/vnl_matrix_inverse.h" +#include "vnl/vnl_matrix.h" +#include "vnl/algo/vnl_determinant.h" + +namespace nb = nanobind; +using namespace nb::literals; + + +template +typename ImageType::Pointer as( void * ptr ) +{ + typename ImageType::Pointer * real = static_cast(ptr); // static_cast or reinterpret_cast ?? + return *real; +} + +template +void * wrap( const typename ImageType::Pointer &image ) +{ + typedef typename ImageType::Pointer ImagePointerType; + ImagePointerType * ptr = new ImagePointerType( image ); + return ptr; +} + +template +typename ImageType::Pointer asImage( void * ptr ) { + typename ImageType::Pointer itkImage = ImageType::New(); + itkImage = as( ptr ); + return itkImage; +} + + +template +struct AntsImage { + typename ImageType::Pointer ptr; +}; + + +template +void toFile( AntsImage & myPointer, std::string filename ) +{ + typename ImageType::Pointer image = myPointer.ptr; + + typedef itk::ImageFileWriter< ImageType > ImageWriterType ; + typename ImageWriterType::Pointer image_writer = ImageWriterType::New() ; + image_writer->SetFileName( filename.c_str() ) ; + image_writer->SetInput( image ); + image_writer->Update(); +} + + +template +std::list getShape( AntsImage & myPointer ) +{ + typename ImageType::Pointer image = myPointer.ptr; + unsigned int ndim = ImageType::GetImageDimension(); + image->UpdateOutputInformation(); + typename ImageType::SizeType shape = image->GetBufferedRegion().GetSize(); + std::list shapelist; + for (int i = 0; i < ndim; i++) + { + shapelist.push_back( shape[i] ); + } + return shapelist; +} + + +template +int getComponents( AntsImage & myPointer ) +{ + typename ImageType::Pointer image = myPointer.ptr; + return image->GetNumberOfComponentsPerPixel(); +} + +template +std::vector getOrigin( AntsImage & myPointer ) +{ + typename ImageType::Pointer image = myPointer.ptr; + typename ImageType::PointType origin = image->GetOrigin(); + unsigned int ndim = ImageType::GetImageDimension(); + + std::vector originlist; + for (int i = 0; i < ndim; i++) + { + originlist.push_back( origin[i] ); + } + + return originlist; +} + +template +void setOrigin( AntsImage & myPointer, std::vector new_origin) +{ + typename ImageType::Pointer itkImage = myPointer.ptr; + unsigned int nvals = new_origin.size(); + typename ImageType::PointType origin = itkImage->GetOrigin(); + for (int i = 0; i < nvals; i++) + { + origin[i] = new_origin[i]; + } + itkImage->SetOrigin( origin ); +} + + +template +std::vector getDirection( AntsImage & myPointer ) +{ + typename ImageType::Pointer image = myPointer.ptr; + typedef typename ImageType::DirectionType ImageDirectionType; + ImageDirectionType direction = image->GetDirection(); + + typedef typename ImageDirectionType::InternalMatrixType DirectionInternalMatrixType; + DirectionInternalMatrixType fixed_matrix = direction.GetVnlMatrix(); + + vnl_matrix vnlmat1 = fixed_matrix.as_matrix(); + + const unsigned int ndim = ImageType::SizeType::GetSizeDimension(); + + std::vector dvec; + + for (int i = 0; i < ndim; i++) + { + for (int j = 0; j < ndim; j++) + { + dvec.push_back(vnlmat1(i,j)); + } + } + return dvec; + +} + + +template +void setDirection( AntsImage & myPointer, std::vector> new_direction) +{ + + typename ImageType::Pointer itkImage = myPointer.ptr; + + typename ImageType::DirectionType new_matrix2 = itkImage->GetDirection( ); + for ( std::size_t i = 0; i < new_direction.size(); i++ ) + for ( std::size_t j = 0; j < new_direction[0].size(); j++ ) { + new_matrix2(i,j) = new_direction[i][j]; + } + itkImage->SetDirection( new_matrix2 ); +} + +template +void setSpacing( AntsImage & myPointer, std::vector new_spacing) +{ + typename ImageType::Pointer itkImage = myPointer.ptr; + unsigned int nvals = new_spacing.size(); + typename ImageType::SpacingType spacing = itkImage->GetSpacing(); + + for (int i = 0; i < nvals; i++) + { + spacing[i] = new_spacing[i]; + } + itkImage->SetSpacing( spacing ); +} + +template +std::vector getSpacing( AntsImage & myPointer ) +{ + typename ImageType::Pointer image = myPointer.ptr; + typename ImageType::SpacingType spacing = image->GetSpacing(); + unsigned int ndim = ImageType::GetImageDimension(); + + std::vector spacinglist; + for (int i = 0; i < ndim; i++) + { + spacinglist.push_back( spacing[i] ); + } + + return spacinglist; +} + +/* +This function resets the region of an image to index from zero if needed. This +keeps the voxel indices in the numpy matrix consistent with the ITK image, and +also keeps the origin of physical space of the consistent with how it will be +saved as NIFTI. +*/ +template +static void FixNonZeroIndex( typename ImageType::Pointer img ) +{ + assert(img); + + typename ImageType::RegionType r = img->GetLargestPossibleRegion(); + typename ImageType::IndexType idx = r.GetIndex(); + + for (unsigned int i = 0; i < ImageType::ImageDimension; ++i) + { + // if any index is non-zero, reset the origin and region + if ( idx[i] != 0 ) + { + typename ImageType::PointType o; + img->TransformIndexToPhysicalPoint( idx, o ); + img->SetOrigin( o ); + + idx.Fill( 0 ); + r.SetIndex( idx ); + img->SetRegions( r ); + + return; + } + } +} + +#endif diff --git a/MindEyeV2/antspy/src/antsImageClone.cxx b/MindEyeV2/antspy/src/antsImageClone.cxx new file mode 100644 index 0000000000000000000000000000000000000000..9badf2f5dc8ed13cd129d12072ccd29b8c886bf3 --- /dev/null +++ b/MindEyeV2/antspy/src/antsImageClone.cxx @@ -0,0 +1,115 @@ + +#include +#include +#include + +#include +#include +#include + +#include "itkImage.h" +#include "itkImageFileWriter.h" + +#include "antsImage.h" + +namespace nb = nanobind; +using namespace nb::literals; + +template +AntsImage antsImageClone( AntsImage & myPointer ) +{ + typename InImageType::Pointer in_image = myPointer.ptr; + + typename OutImageType::Pointer out_image = OutImageType::New() ; + out_image->SetRegions( in_image->GetLargestPossibleRegion() ) ; + out_image->SetSpacing( in_image->GetSpacing() ) ; + out_image->SetOrigin( in_image->GetOrigin() ) ; + out_image->SetDirection( in_image->GetDirection() ); + //out_image->CopyInformation( in_image ); + out_image->AllocateInitialized(); + + itk::ImageRegionConstIterator< InImageType > in_iterator( in_image , in_image->GetLargestPossibleRegion() ) ; + itk::ImageRegionIterator< OutImageType > out_iterator( out_image , out_image->GetLargestPossibleRegion() ) ; + for( in_iterator.GoToBegin() , out_iterator.GoToBegin() ; !in_iterator.IsAtEnd() ; ++in_iterator , ++out_iterator ) + { + out_iterator.Set( static_cast< typename OutImageType::PixelType >( in_iterator.Get() ) ) ; + } + AntsImage outImage = { out_image }; + return outImage; +} +void local_antsImageClone(nb::module_ &m) { + + // call the function based on the image type you are converting TO. + // the image type you are converting FROM should be automatically inferred by the template + + // dim = 2 + m.def("antsImageCloneUC2", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC2", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC2", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC2", &antsImageClone,itk::Image>); + + m.def("antsImageCloneUI2", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI2", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI2", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI2", &antsImageClone,itk::Image>); + + m.def("antsImageCloneF2", &antsImageClone,itk::Image>); + m.def("antsImageCloneF2", &antsImageClone,itk::Image>); + m.def("antsImageCloneF2", &antsImageClone,itk::Image>); + m.def("antsImageCloneF2", &antsImageClone,itk::Image>); + + m.def("antsImageCloneD2", &antsImageClone,itk::Image>); + m.def("antsImageCloneD2", &antsImageClone,itk::Image>); + m.def("antsImageCloneD2", &antsImageClone,itk::Image>); + m.def("antsImageCloneD2", &antsImageClone,itk::Image>); + + m.def("antsImageCloneRGBUC2", &antsImageClone,2>,itk::Image,2>>); + + // dim = 3 + + m.def("antsImageCloneUC3", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC3", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC3", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC3", &antsImageClone,itk::Image>); + + m.def("antsImageCloneUI3", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI3", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI3", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI3", &antsImageClone,itk::Image>); + + m.def("antsImageCloneF3", &antsImageClone,itk::Image>); + m.def("antsImageCloneF3", &antsImageClone,itk::Image>); + m.def("antsImageCloneF3", &antsImageClone,itk::Image>); + m.def("antsImageCloneF3", &antsImageClone,itk::Image>); + + m.def("antsImageCloneD3", &antsImageClone,itk::Image>); + m.def("antsImageCloneD3", &antsImageClone,itk::Image>); + m.def("antsImageCloneD3", &antsImageClone,itk::Image>); + m.def("antsImageCloneD3", &antsImageClone,itk::Image>); + + m.def("antsImageCloneRGBUC3", &antsImageClone,3>,itk::Image,3>>); + + // dim = 4 + + m.def("antsImageCloneUC4", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC4", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC4", &antsImageClone,itk::Image>); + m.def("antsImageCloneUC4", &antsImageClone,itk::Image>); + + m.def("antsImageCloneUI4", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI4", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI4", &antsImageClone,itk::Image>); + m.def("antsImageCloneUI4", &antsImageClone,itk::Image>); + + m.def("antsImageCloneF4", &antsImageClone,itk::Image>); + m.def("antsImageCloneF4", &antsImageClone,itk::Image>); + m.def("antsImageCloneF4", &antsImageClone,itk::Image>); + m.def("antsImageCloneF4", &antsImageClone,itk::Image>); + + m.def("antsImageCloneD4", &antsImageClone,itk::Image>); + m.def("antsImageCloneD4", &antsImageClone,itk::Image>); + m.def("antsImageCloneD4", &antsImageClone,itk::Image>); + m.def("antsImageCloneD4", &antsImageClone,itk::Image>); + + m.def("antsImageCloneRGBUC4", &antsImageClone,4>,itk::Image,4>>); +} \ No newline at end of file diff --git a/MindEyeV2/antspy/src/antsTransform.cxx b/MindEyeV2/antspy/src/antsTransform.cxx new file mode 100644 index 0000000000000000000000000000000000000000..3c557cae75b0fad24f4084efaafed7b5fbcf47c1 --- /dev/null +++ b/MindEyeV2/antspy/src/antsTransform.cxx @@ -0,0 +1,312 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "itkMacro.h" +#include "itkImage.h" +#include "itkVectorImage.h" +#include "itkVector.h" +#include "itkImageRegionIteratorWithIndex.h" +#include "vnl/vnl_vector_ref.h" +#include "itkTransform.h" +#include "itkAffineTransform.h" + +#include "itkAffineTransform.h" +#include "itkAffineTransform.h" +#include "itkCenteredAffineTransform.h" +#include "itkEuler2DTransform.h" +#include "itkEuler3DTransform.h" +#include "itkRigid2DTransform.h" +#include "itkRigid3DTransform.h" +#include "itkCenteredRigid2DTransform.h" +#include "itkCenteredEuler3DTransform.h" +#include "itkSimilarity2DTransform.h" +#include "itkCenteredSimilarity2DTransform.h" +#include "itkSimilarity3DTransform.h" +#include "itkQuaternionRigidTransform.h" +#include "itkTranslationTransform.h" +#include "itkResampleImageFilter.h" +#include "itkTransformFileReader.h" +#include "itkCompositeTransform.h" +#include "itkMatrixOffsetTransformBase.h" +#include "itkDisplacementFieldTransform.h" +#include "itkConstantBoundaryCondition.h" + +#include "itkBSplineInterpolateImageFunction.h" +#include "itkLinearInterpolateImageFunction.h" +#include "itkGaussianInterpolateImageFunction.h" +#include "itkInterpolateImageFunction.h" +#include "itkNearestNeighborInterpolateImageFunction.h" +#include "itkWindowedSincInterpolateImageFunction.h" +#include "itkLabelImageGaussianInterpolateImageFunction.h" +#include "itkTransformFileWriter.h" + +#include "itkMacro.h" +#include "itkImage.h" +#include "itkVectorImage.h" +#include "itkVector.h" +#include "itkImageRegionIteratorWithIndex.h" +#include "vnl/vnl_vector_ref.h" +#include "itkTransform.h" +#include "itkAffineTransform.h" + +#include "antscore/antsUtilities.h" + +#include "antsTransform.h" +#include "antsImage.h" + +namespace nb = nanobind; +using namespace nb::literals; + +template +AntsTransform antsTransformFromDisplacementField( AntsImage & field ) +{ + //typedef itk::Transform TransformType; + typedef typename TransformType::Pointer TransformPointerType; + typedef typename itk::DisplacementFieldTransform DisplacementFieldTransformType; + typedef typename DisplacementFieldTransformType::DisplacementFieldType DisplacementFieldType; + typedef typename DisplacementFieldType::PixelType VectorType; + + // Displacement field is an itk::Image with vector pixels, while in ANTsR we use the + // itk::VectorImage class for multichannel data. So we must copy the field + // and pass it to the transform + //typedef itk::VectorImage AntsrFieldType; + //typedef typename AntsrFieldType::Pointer AntsrFieldPointerType; + typedef typename VectorImageType::Pointer VectorImagePointerType; + VectorImagePointerType antsrField = field.ptr; + + typename DisplacementFieldType::Pointer itkField = DisplacementFieldType::New(); + itkField->SetRegions( antsrField->GetLargestPossibleRegion() ); + itkField->SetSpacing( antsrField->GetSpacing() ); + itkField->SetOrigin( antsrField->GetOrigin() ); + itkField->SetDirection( antsrField->GetDirection() ); + itkField->AllocateInitialized(); + + typedef itk::ImageRegionIteratorWithIndex IteratorType; + IteratorType it( itkField, itkField->GetLargestPossibleRegion() ); + while ( !it.IsAtEnd() ) + { + typename VectorImageType::PixelType vec = antsrField->GetPixel( it.GetIndex() ); + VectorType dvec; + for ( unsigned int i=0; iSetPixel(it.GetIndex(), dvec); + ++it; + } + + typename DisplacementFieldTransformType::Pointer displacementTransform = + DisplacementFieldTransformType::New(); + displacementTransform->SetDisplacementField( itkField ); + + /* + TransformPointerType transform = dynamic_cast( displacementTransform.GetPointer() ); + + Rcpp::S4 antsrTransform( "antsrTransform" ); + antsrTransform.slot("dimension") = Dimension; + antsrTransform.slot("precision") = precision; + std::string type = displacementTransform->GetNameOfClass(); + antsrTransform.slot("type") = type; + TransformPointerType * rawPointer = new TransformPointerType( transform ); + Rcpp::XPtr xptr( rawPointer, true ); + antsrTransform.slot("pointer") = xptr; + + return antsrTransform; + */ + AntsTransform outTransform = { displacementTransform.GetPointer() }; + return outTransform; +} + +template +AntsImage antsTransformToDisplacementField( AntsTransform & xfrm, + AntsImage> & ref ) +{ + //typedef itk::Transform TransformType; + using ImageType = typename itk::Image; + using ImagePointerType = typename ImageType::Pointer; + using TransformPointerType = typename TransformType::Pointer; + using DisplacementFieldTransformType = typename itk::DisplacementFieldTransform; + using DisplacementFieldTransformPointerType = typename DisplacementFieldTransformType::Pointer; + using DisplacementFieldType = typename DisplacementFieldTransformType::DisplacementFieldType; + using VectorType = typename DisplacementFieldType::PixelType; + + TransformPointerType itkTransform = xfrm.ptr; + DisplacementFieldTransformPointerType warp = dynamic_cast( itkTransform.GetPointer() ) ; + + ImagePointerType domainImage = ref.ptr; + + typedef typename VectorImageType::Pointer VectorImagePointerType; + VectorImagePointerType antsrField = VectorImageType::New(); + antsrField->CopyInformation( domainImage ); + antsrField->SetRegions( domainImage->GetLargestPossibleRegion() ); + antsrField->SetNumberOfComponentsPerPixel( Dimension ); + antsrField->AllocateInitialized(); + + typedef itk::ImageRegionIteratorWithIndex IteratorType; + IteratorType it( domainImage, domainImage->GetLargestPossibleRegion() ); + while ( !it.IsAtEnd() ) + { + VectorType vec = warp->GetDisplacementField()->GetPixel( it.GetIndex() ); + typename VectorImageType::PixelType dvec; + dvec.SetSize( Dimension ); + for( unsigned int i = 0; i < Dimension; i++ ) + { + dvec[i] = vec[i]; + } + antsrField->SetPixel( it.GetIndex(), dvec ); + ++it; + } + + AntsImage outImage = { antsrField }; + return outImage; +} + +void local_antsTransform(nb::module_ &m) { + + m.def("getTransformParameters", &getTransformParameters>); + m.def("getTransformParameters", &getTransformParameters>); + m.def("getTransformParameters", &getTransformParameters>); + m.def("getTransformParameters", &getTransformParameters>); + m.def("getTransformParameters", &getTransformParameters>); + m.def("getTransformParameters", &getTransformParameters>); + + m.def("setTransformParameters", &setTransformParameters>); + m.def("setTransformParameters", &setTransformParameters>); + m.def("setTransformParameters", &setTransformParameters>); + m.def("setTransformParameters", &setTransformParameters>); + m.def("setTransformParameters", &setTransformParameters>); + m.def("setTransformParameters", &setTransformParameters>); + + m.def("getTransformFixedParameters", &getTransformFixedParameters>); + m.def("getTransformFixedParameters", &getTransformFixedParameters>); + m.def("getTransformFixedParameters", &getTransformFixedParameters>); + m.def("getTransformFixedParameters", &getTransformFixedParameters>); + m.def("getTransformFixedParameters", &getTransformFixedParameters>); + m.def("getTransformFixedParameters", &getTransformFixedParameters>); + + m.def("setTransformFixedParameters", &setTransformFixedParameters>); + m.def("setTransformFixedParameters", &setTransformFixedParameters>); + m.def("setTransformFixedParameters", &setTransformFixedParameters>); + m.def("setTransformFixedParameters", &setTransformFixedParameters>); + m.def("setTransformFixedParameters", &setTransformFixedParameters>); + m.def("setTransformFixedParameters", &setTransformFixedParameters>); + + + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + m.def("transformPoint", &transformPoint>); + + m.def("transformVector", &transformVector>); + m.def("transformVector", &transformVector>); + m.def("transformVector", &transformVector>); + m.def("transformVector", &transformVector>); + m.def("transformVector", &transformVector>); + m.def("transformVector", &transformVector>); + + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + + // displacement field transforms + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + m.def("transformImage", &transformImage, itk::Image>); + + m.def("inverseTransform", &inverseTransform, itk::Transform>); + m.def("inverseTransform", &inverseTransform, itk::Transform>); + m.def("inverseTransform", &inverseTransform, itk::Transform>); + m.def("inverseTransform", &inverseTransform, itk::Transform>); + m.def("inverseTransform", &inverseTransform, itk::Transform>); + m.def("inverseTransform", &inverseTransform, itk::Transform>); + + m.def("composeTransformsF2", &composeTransforms, float, 2>); + m.def("composeTransformsF3", &composeTransforms, float, 3>); + m.def("composeTransformsF4", &composeTransforms, float, 4>); + m.def("composeTransformsD2", &composeTransforms, double,2> ); + m.def("composeTransformsD3", &composeTransforms, double,3> ); + m.def("composeTransformsD4", &composeTransforms, double,4> ); + + m.def("readTransformF2", &readTransform, float, 2>); + m.def("readTransformF3", &readTransform, float, 3>); + m.def("readTransformF4", &readTransform, float, 4>); + m.def("readTransformD2", &readTransform, double,2> ); + m.def("readTransformD3", &readTransform, double,3> ); + m.def("readTransformD4", &readTransform, double,4> ); + + m.def("writeTransform", &writeTransform>); + m.def("writeTransform", &writeTransform>); + m.def("writeTransform", &writeTransform>); + m.def("writeTransform", &writeTransform>); + m.def("writeTransform", &writeTransform>); + m.def("writeTransform", &writeTransform>); + + m.def("matrixOffsetF2", &matrixOffset, float, 2>); + m.def("matrixOffsetF3", &matrixOffset, float, 3>); + m.def("matrixOffsetF4", &matrixOffset, float, 4>); + m.def("matrixOffsetD2", &matrixOffset, double,2>); + m.def("matrixOffsetD3", &matrixOffset, double,3>); + m.def("matrixOffsetD4", &matrixOffset, double,4>); + + m.def("antsTransformFromDisplacementField", &antsTransformFromDisplacementField, itk::VectorImage,float,2>); + m.def("antsTransformFromDisplacementField", &antsTransformFromDisplacementField, itk::VectorImage,float,3>); + m.def("antsTransformToDisplacementField", &antsTransformToDisplacementField, itk::VectorImage,float,2>); + m.def("antsTransformToDisplacementField", &antsTransformToDisplacementField, itk::VectorImage,float,3>); + + + nb::class_>>(m, "AntsTransformDF2"); + nb::class_>>(m, "AntsTransformDF3"); + nb::class_>>(m, "AntsTransformF22"); + nb::class_>>(m, "AntsTransformF33"); + nb::class_>>(m, "AntsTransformF44"); + nb::class_>>(m, "AntsTransformD22"); + nb::class_>>(m, "AntsTransformD33"); + nb::class_>>(m, "AntsTransformD44"); + +} + + + diff --git a/MindEyeV2/antspy/src/fsl2antstransform.cxx b/MindEyeV2/antspy/src/fsl2antstransform.cxx new file mode 100644 index 0000000000000000000000000000000000000000..0870bd186dfdfc8798d6ed8655a75453b90bcb4b --- /dev/null +++ b/MindEyeV2/antspy/src/fsl2antstransform.cxx @@ -0,0 +1,172 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "itkImage.h" +#include "itkMatrixOffsetTransformBase.h" +#include "itkCastImageFilter.h" +#include "vnl/vnl_matrix_fixed.h" +#include "vnl/vnl_diag_matrix.h" +#include "vnl/vnl_vector.h" +#include "vnl/vnl_det.h" +#include "vnl/vnl_inverse.h" +#include "vnl/algo/vnl_real_eigensystem.h" +#include "vnl/algo/vnl_qr.h" + +#include "antsTransform.h" +#include "antsImage.h" + +#define RAS_TO_FSL 0 +#define FSL_TO_RAS 1 + +namespace nb = nanobind; +using namespace nb::literals; + +/** + * Get a matrix that maps points voxel coordinates to RAS coordinates + */ +template< class ImageType, class TransformMatrixType > +TransformMatrixType GetVoxelSpaceToRASPhysicalSpaceMatrix(typename ImageType::Pointer image) + { + // Generate intermediate terms + vnl_matrix_fixed m_dir, m_ras_matrix; + vnl_diag_matrix_fixed m_scale, m_lps_to_ras; + vnl_vector_fixed v_origin, v_ras_offset; + + // Compute the matrix + m_dir = image->GetDirection().GetVnlMatrix(); + m_scale.set(image->GetSpacing().GetVnlVector()); + m_lps_to_ras.set(vnl_vector(ImageType::ImageDimension, 1.0)); + m_lps_to_ras[0] = -1; + m_lps_to_ras[1] = -1; + m_ras_matrix = m_lps_to_ras * m_dir * m_scale; + + // Compute the vector + v_origin = image->GetOrigin().GetVnlVector(); + v_ras_offset = m_lps_to_ras * v_origin; + + // Create the larger matrix + TransformMatrixType mat; + vnl_vector vcol(ImageType::ImageDimension+1, 1.0); + vcol.update(v_ras_offset); + mat.SetIdentity(); + mat.GetVnlMatrix().update(m_ras_matrix); + mat.GetVnlMatrix().set_column(ImageType::ImageDimension, vcol); + + return mat; + } + + +template< class PixelType, unsigned int Dimension > +AntsTransform> fsl2antstransform( std::vector > matrix, + AntsImage> & ants_reference, + AntsImage> & ants_moving, + int flag ) +{ + typedef vnl_matrix_fixed MatrixType; + typedef itk::Image ImageType; + typedef itk::Matrix TransformMatrixType; + + typedef itk::AffineTransform AffTran; + + typedef typename ImageType::Pointer ImagePointerType; + + typedef itk::Transform TransformBaseType; + typedef typename TransformBaseType::Pointer TransformBasePointerType; + + ImagePointerType ref = ants_reference.ptr; + ImagePointerType mov = ants_moving.ptr; + + MatrixType m_fsl, m_spcref, m_spcmov, m_swpref, m_swpmov, mat, m_ref, m_mov; + + //Rcpp::NumericMatrix matrix(r_matrix); + for ( unsigned int i=0; i( ref ).GetVnlMatrix(); + m_mov = GetVoxelSpaceToRASPhysicalSpaceMatrix( mov ).GetVnlMatrix(); + + // Set the swap matrices + m_swpref.set_identity(); + if(vnl_det(m_ref) > 0) + { + m_swpref(0,0) = -1.0; + m_swpref(0,3) = (ref->GetBufferedRegion().GetSize(0) - 1) * ref->GetSpacing()[0]; + } + + m_swpmov.set_identity(); + if(vnl_det(m_mov) > 0) + { + m_swpmov(0,0) = -1.0; + m_swpmov(0,3) = (mov->GetBufferedRegion().GetSize(0) - 1) * mov->GetSpacing()[0]; + } + + // Set the spacing matrices + m_spcref.set_identity(); + m_spcmov.set_identity(); + for(size_t i = 0; i < 3; i++) + { + m_spcref(i,i) = ref->GetSpacing()[i]; + m_spcmov(i,i) = mov->GetSpacing()[i]; + } + + // Compute the output matrix + //if (flag == FSL_TO_RAS) + mat = m_mov * vnl_inverse(m_spcmov) * m_swpmov * vnl_inverse(m_fsl) * m_swpref * m_spcref * vnl_inverse(m_ref); + + // Add access to this + // NOTE: m_fsl is really m_ras here + //if (flag == RAS_TO_FSL) + // mat = + // vnl_inverse(vnl_inverse(m_swpmov) * m_spcmov* vnl_inverse(m_mov) * + // m_fsl * + // m_ref*vnl_inverse(m_spcref)*vnl_inverse(m_swpref)); + + /////////////// + + // Flip the entries that must be flipped + mat(2,0) *= -1; mat(2,1) *= -1; + mat(0,2) *= -1; mat(1,2) *= -1; + mat(0,3) *= -1; mat(1,3) *= -1; + + // Create an ITK affine transform + AffTran::Pointer atran = AffTran::New(); + + // Populate its matrix + AffTran::MatrixType amat = atran->GetMatrix(); + AffTran::OffsetType aoff = atran->GetOffset(); + + for(size_t r = 0; r < 3; r++) + { + for(size_t c = 0; c < 3; c++) + { + amat(r,c) = mat(r,c); + } + aoff[r] = mat(r,3); + } + + atran->SetMatrix(amat); + atran->SetOffset(aoff); + + TransformBasePointerType itkTransform = dynamic_cast( atran.GetPointer() ); + + AntsTransform out_ants_tx = { itkTransform }; + return out_ants_tx; +} + + +void local_fsl2antstransform(nb::module_ &m) +{ + m.def("fsl2antstransformF3", &fsl2antstransform); +} diff --git a/MindEyeV2/antspy/src/histogramMatchImages.cxx b/MindEyeV2/antspy/src/histogramMatchImages.cxx new file mode 100644 index 0000000000000000000000000000000000000000..a226f0f0960bf50ed1d889f30fd0bd22a0f5e935 --- /dev/null +++ b/MindEyeV2/antspy/src/histogramMatchImages.cxx @@ -0,0 +1,56 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "itkImage.h" +#include "itkHistogramMatchingImageFilter.h" + +#include "antsImage.h" + +namespace nb = nanobind; +using namespace nb::literals; + +template < typename ImageType > +AntsImage histogramMatchImage( AntsImage & antsSourceImage, + AntsImage & antsReferenceImage, + unsigned int numberOfHistogramBins, + unsigned int numberOfMatchPoints, + bool useThresholdAtMeanIntensity ) +{ + typedef typename ImageType::Pointer ImagePointerType; + ImagePointerType itkSourceImage = antsSourceImage.ptr; + ImagePointerType itkReferenceImage = antsReferenceImage.ptr; + + typedef itk::HistogramMatchingImageFilter FilterType; + typename FilterType::Pointer filter = FilterType::New(); + filter->SetSourceImage( itkSourceImage ); + filter->SetReferenceImage( itkReferenceImage ); + filter->ThresholdAtMeanIntensityOff(); + if( useThresholdAtMeanIntensity ) + { + filter->ThresholdAtMeanIntensityOn(); + } + filter->SetNumberOfHistogramLevels( numberOfHistogramBins ); + filter->SetNumberOfMatchPoints( numberOfMatchPoints ); + filter->Update(); + + AntsImage out_ants_image = { filter->GetOutput() }; + return out_ants_image; +} + +void local_histogramMatchImages(nb::module_ &m) +{ + m.def("histogramMatchImageF2", &histogramMatchImage>); + m.def("histogramMatchImageF3", &histogramMatchImage>); + m.def("histogramMatchImageF4", &histogramMatchImage>); +} +