From 34e00ba8114aa38540f6485346cbb54077aa76a7 Mon Sep 17 00:00:00 2001 From: dPys Date: Tue, 11 Feb 2020 16:51:03 -0600 Subject: [PATCH 1/5] [ENH] Add affine registration tools for EMC --- dmriprep/utils/register.py | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 dmriprep/utils/register.py diff --git a/dmriprep/utils/register.py b/dmriprep/utils/register.py new file mode 100644 index 00000000..56b47084 --- /dev/null +++ b/dmriprep/utils/register.py @@ -0,0 +1,170 @@ +""" +Linear affine registration tools for motion correction. +""" +import numpy as np +import nibabel as nb +from dipy.align.metrics import CCMetric, EMMetric, SSDMetric +from dipy.align.imaffine import ( + transform_centers_of_mass, + AffineMap, + MutualInformationMetric, + AffineRegistration, +) +from dipy.align.transforms import ( + TranslationTransform3D, + RigidTransform3D, + AffineTransform3D, +) +from nipype.utils.filemanip import fname_presuffix + +syn_metric_dict = {"CC": CCMetric, "EM": EMMetric, "SSD": SSDMetric} + +__all__ = [ + "c_of_mass", + "translation", + "rigid", + "affine", + "affine_registration", +] + + +def apply_affine(moving, static, transform_affine, invert=False): + """Apply an affine to transform an image from one space to another. + + Parameters + ---------- + moving : array + The image to be resampled + + static : array + + Returns + ------- + warped_img : the moving array warped into the static array's space. + + """ + affine_map = AffineMap( + transform_affine, static.shape, static.affine, moving.shape, moving.affine + ) + if invert is True: + warped_arr = affine_map.transform_inverse(np.asarray(moving.dataobj)) + else: + warped_arr = affine_map.transform(np.asarray(moving.dataobj)) + + return nb.Nifti1Image(warped_arr, static.affine) + + +def average_affines(transforms): + affine_list = [np.load(aff) for aff in transforms] + average_affine_file = fname_presuffix( + transforms[0], use_ext=False, suffix="_average.npy" + ) + np.save(average_affine_file, np.mean(affine_list, axis=0)) + return average_affine_file + + +# Affine registration pipeline: +affine_metric_dict = {"MI": MutualInformationMetric, "CC": CCMetric} + + +def c_of_mass( + moving, static, static_affine, moving_affine, reg, starting_affine, params0=None +): + transform = transform_centers_of_mass(static, static_affine, moving, moving_affine) + transformed = transform.transform(moving) + return transformed, transform.affine + + +def translation( + moving, static, static_affine, moving_affine, reg, starting_affine, params0=None +): + transform = TranslationTransform3D() + translation = reg.optimize( + static, + moving, + transform, + params0, + static_affine, + moving_affine, + starting_affine=starting_affine, + ) + + return translation.transform(moving), translation.affine + + +def rigid( + moving, static, static_affine, moving_affine, reg, starting_affine, params0=None +): + transform = RigidTransform3D() + rigid = reg.optimize( + static, + moving, + transform, + params0, + static_affine, + moving_affine, + starting_affine=starting_affine, + ) + return rigid.transform(moving), rigid.affine + + +def affine( + moving, static, static_affine, moving_affine, reg, starting_affine, params0=None +): + transform = AffineTransform3D() + affine = reg.optimize( + static, + moving, + transform, + params0, + static_affine, + moving_affine, + starting_affine=starting_affine, + ) + + return affine.transform(moving), affine.affine + + +def affine_registration( + moving, + static, + nbins, + sampling_prop, + metric, + pipeline, + level_iters, + sigmas, + factors, + params0, +): + + """ + Find the affine transformation between two 3D images. + + Parameters + ---------- + + """ + # Define the Affine registration object we'll use with the chosen metric: + use_metric = affine_metric_dict[metric](nbins, sampling_prop) + affreg = AffineRegistration( + metric=use_metric, level_iters=level_iters, sigmas=sigmas, factors=factors + ) + + if not params0: + starting_affine = np.eye(4) + else: + starting_affine = params0 + + # Go through the selected transformation: + for func in pipeline: + transformed, starting_affine = func( + np.asarray(moving.dataobj), + np.asarray(static.dataobj), + static.affine, + moving.affine, + affreg, + starting_affine, + params0, + ) + return nb.Nifti1Image(np.array(transformed), static.affine), starting_affine From d72a60bc58df2a747f15cb756a682e067efa7b82 Mon Sep 17 00:00:00 2001 From: dPys Date: Tue, 11 Feb 2020 16:56:39 -0600 Subject: [PATCH 2/5] [ENH] Add coarse/precise affine/rigid templates --- dmriprep/config/emc_coarse_Affine.json | 8 ++++++++ dmriprep/config/emc_coarse_Rigid.json | 8 ++++++++ dmriprep/config/emc_precise_Affine.json | 8 ++++++++ dmriprep/config/emc_precise_Rigid.json | 8 ++++++++ 4 files changed, 32 insertions(+) create mode 100644 dmriprep/config/emc_coarse_Affine.json create mode 100644 dmriprep/config/emc_coarse_Rigid.json create mode 100644 dmriprep/config/emc_precise_Affine.json create mode 100644 dmriprep/config/emc_precise_Rigid.json diff --git a/dmriprep/config/emc_coarse_Affine.json b/dmriprep/config/emc_coarse_Affine.json new file mode 100644 index 00000000..f7663871 --- /dev/null +++ b/dmriprep/config/emc_coarse_Affine.json @@ -0,0 +1,8 @@ +{ + "level_iters": [1000, 100], + "metric": "MI", + "sigmas": [8.0, 2.0], + "factors": [2, 1], + "sampling_prop": 0.15, + "nbins": 48 +} \ No newline at end of file diff --git a/dmriprep/config/emc_coarse_Rigid.json b/dmriprep/config/emc_coarse_Rigid.json new file mode 100644 index 00000000..5d23fba6 --- /dev/null +++ b/dmriprep/config/emc_coarse_Rigid.json @@ -0,0 +1,8 @@ +{ + "level_iters": [100, 100], + "metric": "MI", + "sigmas": [8.0, 2.0], + "factors": [2, 1], + "sampling_prop": 0.15, + "nbins": 48 +} diff --git a/dmriprep/config/emc_precise_Affine.json b/dmriprep/config/emc_precise_Affine.json new file mode 100644 index 00000000..99fbb1e2 --- /dev/null +++ b/dmriprep/config/emc_precise_Affine.json @@ -0,0 +1,8 @@ +{ + "level_iters": [1000, 1000], + "metric": "MI", + "sigmas": [8.0, 2.0], + "factors": [2, 1], + "sampling_prop": 0.15, + "nbins": 48 +} \ No newline at end of file diff --git a/dmriprep/config/emc_precise_Rigid.json b/dmriprep/config/emc_precise_Rigid.json new file mode 100644 index 00000000..e62cd12a --- /dev/null +++ b/dmriprep/config/emc_precise_Rigid.json @@ -0,0 +1,8 @@ +{ + "level_iters": [1000, 1000], + "metric": "MI", + "sigmas": [8.0, 2.0], + "factors": [2, 1], + "sampling_prop": 0.15, + "nbins": 48 +} From a0dd650a3cd712c1ff0a5103665115cc8115c11b Mon Sep 17 00:00:00 2001 From: Ariel Rokem Date: Mon, 13 Apr 2020 11:07:34 -0700 Subject: [PATCH 3/5] Removes config files. To be reintroduced in the relevant context. --- dmriprep/config/emc_coarse_Affine.json | 8 -------- dmriprep/config/emc_coarse_Rigid.json | 8 -------- dmriprep/config/emc_precise_Affine.json | 8 -------- dmriprep/config/emc_precise_Rigid.json | 8 -------- 4 files changed, 32 deletions(-) delete mode 100644 dmriprep/config/emc_coarse_Affine.json delete mode 100644 dmriprep/config/emc_coarse_Rigid.json delete mode 100644 dmriprep/config/emc_precise_Affine.json delete mode 100644 dmriprep/config/emc_precise_Rigid.json diff --git a/dmriprep/config/emc_coarse_Affine.json b/dmriprep/config/emc_coarse_Affine.json deleted file mode 100644 index f7663871..00000000 --- a/dmriprep/config/emc_coarse_Affine.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "level_iters": [1000, 100], - "metric": "MI", - "sigmas": [8.0, 2.0], - "factors": [2, 1], - "sampling_prop": 0.15, - "nbins": 48 -} \ No newline at end of file diff --git a/dmriprep/config/emc_coarse_Rigid.json b/dmriprep/config/emc_coarse_Rigid.json deleted file mode 100644 index 5d23fba6..00000000 --- a/dmriprep/config/emc_coarse_Rigid.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "level_iters": [100, 100], - "metric": "MI", - "sigmas": [8.0, 2.0], - "factors": [2, 1], - "sampling_prop": 0.15, - "nbins": 48 -} diff --git a/dmriprep/config/emc_precise_Affine.json b/dmriprep/config/emc_precise_Affine.json deleted file mode 100644 index 99fbb1e2..00000000 --- a/dmriprep/config/emc_precise_Affine.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "level_iters": [1000, 1000], - "metric": "MI", - "sigmas": [8.0, 2.0], - "factors": [2, 1], - "sampling_prop": 0.15, - "nbins": 48 -} \ No newline at end of file diff --git a/dmriprep/config/emc_precise_Rigid.json b/dmriprep/config/emc_precise_Rigid.json deleted file mode 100644 index e62cd12a..00000000 --- a/dmriprep/config/emc_precise_Rigid.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "level_iters": [1000, 1000], - "metric": "MI", - "sigmas": [8.0, 2.0], - "factors": [2, 1], - "sampling_prop": 0.15, - "nbins": 48 -} From 04a843e5ba164c589ff0d6d5dd54ce1e5cbf16cf Mon Sep 17 00:00:00 2001 From: Ariel Rokem Date: Mon, 13 Apr 2020 11:53:49 -0700 Subject: [PATCH 4/5] Object-oriented interface, with fit and apply methods. --- dmriprep/utils/register.py | 120 +++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 52 deletions(-) diff --git a/dmriprep/utils/register.py b/dmriprep/utils/register.py index 56b47084..816d5125 100644 --- a/dmriprep/utils/register.py +++ b/dmriprep/utils/register.py @@ -1,6 +1,8 @@ """ Linear affine registration tools for motion correction. """ +import attr + import numpy as np import nibabel as nb from dipy.align.metrics import CCMetric, EMMetric, SSDMetric @@ -72,7 +74,7 @@ def c_of_mass( ): transform = transform_centers_of_mass(static, static_affine, moving, moving_affine) transformed = transform.transform(moving) - return transformed, transform.affine + return transform def translation( @@ -89,7 +91,7 @@ def translation( starting_affine=starting_affine, ) - return translation.transform(moving), translation.affine + return translation def rigid( @@ -105,12 +107,13 @@ def rigid( moving_affine, starting_affine=starting_affine, ) - return rigid.transform(moving), rigid.affine + return rigid -def affine( - moving, static, static_affine, moving_affine, reg, starting_affine, params0=None -): +def affine(moving, static, static_affine, moving_affine, reg, starting_affine, + params0=None): + """ + """ transform = AffineTransform3D() affine = reg.optimize( static, @@ -122,49 +125,62 @@ def affine( starting_affine=starting_affine, ) - return affine.transform(moving), affine.affine - - -def affine_registration( - moving, - static, - nbins, - sampling_prop, - metric, - pipeline, - level_iters, - sigmas, - factors, - params0, -): - - """ - Find the affine transformation between two 3D images. - - Parameters - ---------- - - """ - # Define the Affine registration object we'll use with the chosen metric: - use_metric = affine_metric_dict[metric](nbins, sampling_prop) - affreg = AffineRegistration( - metric=use_metric, level_iters=level_iters, sigmas=sigmas, factors=factors - ) - - if not params0: - starting_affine = np.eye(4) - else: - starting_affine = params0 - - # Go through the selected transformation: - for func in pipeline: - transformed, starting_affine = func( - np.asarray(moving.dataobj), - np.asarray(static.dataobj), - static.affine, - moving.affine, - affreg, - starting_affine, - params0, - ) - return nb.Nifti1Image(np.array(transformed), static.affine), starting_affine + return affine + + +@attr.s(slots=True, frozen=True) +class AffineRegistration(): + def __init__(self): + nbins = attr.ib(default=32) + sampling_prop = attr.ib(default=1.0) + metric = attr.ib(default="MI") + level_iters = attr.ib(default=[10000, 1000, 100]) + sigmas = attr.ib(defaults=[3, 1, 0.0]) + factors = attr.ib(defaults=[4, 2, 1]) + pipeline = attr.ib(defaults=[c_of_mass, translation, rigid, affine]) + + def fit(self, static, moving, params0=None): + """ + static, moving : nib.Nifti1Image class images + """ + if params0 is None: + starting_affine = np.eye(4) + else: + starting_affine = params0 + + use_metric = affine_metric_dict[self.metric](self.nbins, + self.sampling_prop) + affreg = AffineRegistration( + metric=use_metric, + level_iters=self.level_iters, + sigmas=self.sigmas, + factors=self.factors) + + # Go through the selected transformation: + for func in self.pipeline: + transform = func( + np.asarray(moving.dataobj), + np.asarray(static.dataobj), + static.affine, + moving.affine, + affreg, + starting_affine, + params0, + ) + starting_affine = transform.affine + + self.static_affine_ = static.affine + self.moving_affine_ = moving.affine + self.affine_ = starting_affine + self.reg_ = AffineMap(starting_affine, + static.shape, static.affine, + moving.shape, moving.affine) + + def apply(self, moving): + """ + + """ + data = moving.get_fdata() + assert np.all(moving.affine, self.moving_affine_) + return nb.Nifti1Image(np.array(self.reg_.transform(data)), + self.static_affine_) From fed7e788da78ee91bf240bfd5d20997b54315275 Mon Sep 17 00:00:00 2001 From: Ariel Rokem Date: Tue, 29 Sep 2020 08:30:13 -0700 Subject: [PATCH 5/5] Add nitransforms as a dependency. --- dmriprep/utils/register.py | 5 ++++- setup.cfg | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dmriprep/utils/register.py b/dmriprep/utils/register.py index 816d5125..7e6ecd3b 100644 --- a/dmriprep/utils/register.py +++ b/dmriprep/utils/register.py @@ -6,12 +6,14 @@ import numpy as np import nibabel as nb from dipy.align.metrics import CCMetric, EMMetric, SSDMetric + from dipy.align.imaffine import ( transform_centers_of_mass, AffineMap, MutualInformationMetric, AffineRegistration, ) + from dipy.align.transforms import ( TranslationTransform3D, RigidTransform3D, @@ -21,12 +23,13 @@ syn_metric_dict = {"CC": CCMetric, "EM": EMMetric, "SSD": SSDMetric} + __all__ = [ "c_of_mass", "translation", "rigid", "affine", - "affine_registration", + "AffineRegistration", ] diff --git a/setup.cfg b/setup.cfg index 61b04423..a9be9d6f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,7 @@ install_requires = nibabel ~= 3.0 nipype ~= 1.4 niworkflows @ git+https://github.com/nipreps/niworkflows.git@master + nitransforms numpy pybids >=0.10.2 pyyaml