Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REBASED ] Symmetric Diffeomorphic registration workflow #1748

Merged
merged 22 commits into from Mar 9, 2019
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
aebc7fe
Crated a new branch with clean history for the affine registration wo…
parichit Jul 31, 2018
04623cb
Pasted the stable (latest) code from the PR 1581 in the image.py file.
parichit Jul 31, 2018
c47a31c
Pasted the stable (latest) code from PR 1581 into the test_align.py f…
parichit Jul 31, 2018
f600f64
Pasted the clean and stable code in align.py from PR1598.
parichit Jul 31, 2018
17adbf1
Pasted the clean and stable code in test_align.py from PR1598.
parichit Jul 31, 2018
d30a61a
Fixed the PEP8 warning in the test_align.py for the test_apply_transf…
parichit Aug 7, 2018
5fb386c
Modifed the align.py workflow to include sYn_registration class.
parichit Jul 31, 2018
4722b0f
Created the command line wrapper for syn registration workflow.
parichit Aug 2, 2018
d2ce48d
1) Added the optional parameters to the Syn_registration workflow to …
parichit Aug 2, 2018
1d268d4
1) Added the command line wrapper for the diffeomorphic registration …
parichit Aug 13, 2018
252020d
Pasted the clean and stable code in dipy_apply_affine wrapper from PR…
parichit Jul 31, 2018
fe7f1b5
1) Fixed the PEP8 formatting issue in the dipy_align_affine and dipy_…
parichit Aug 2, 2018
bb1fc3e
1) Renamed the ApplyTransformFlow to ApplyAffineFlow to distinguish i…
parichit Aug 2, 2018
5951b14
Add metric management
skoudoro Mar 4, 2019
3cdf3ab
import cleaning
skoudoro Mar 4, 2019
f48f072
remove dipy_syn_register due to duplication
skoudoro Mar 4, 2019
8ce3862
update optimize parameters
skoudoro Mar 5, 2019
3427933
add tests
skoudoro Mar 5, 2019
63a1935
adress Ariel comment
skoudoro Mar 5, 2019
4d443de
fix tests for python27
skoudoro Mar 7, 2019
9b484af
convert ApplyAffineFlow to ApplyTransformFlow
skoudoro Mar 7, 2019
4d73efa
update tests
skoudoro Mar 7, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions bin/dipy_align_syn
@@ -0,0 +1,7 @@
#!python

from dipy.workflows.flow_runner import run_flow
from dipy.workflows.align import SynRegistrationFlow

if __name__ == "__main__":
run_flow(SynRegistrationFlow())
6 changes: 6 additions & 0 deletions bin/dipy_syn_register
@@ -0,0 +1,6 @@
#!python
from dipy.workflows.flow_runner import run_flow
from dipy.workflows.align import SynRegistrationFlow

if __name__ == "__main__":
run_flow(SynRegistrationFlow())
1 change: 0 additions & 1 deletion dipy/io/image.py
Expand Up @@ -40,7 +40,6 @@ def save_qa_metric(fname, xopt, fopt):
image registration.
fopt: int
The distance between the registered images.

"""
np.savetxt(fname, xopt, header="Optimal Parameter metric")
with open(fname, 'a') as f:
Expand Down
188 changes: 187 additions & 1 deletion dipy/workflows/align.py
Expand Up @@ -6,6 +6,8 @@

from dipy.align.imaffine import AffineMap, transform_centers_of_mass, \
MutualInformationMetric, AffineRegistration
from dipy.align.imwarp import SymmetricDiffeomorphicRegistration
from dipy.align.metrics import CCMetric, SSDMetric, EMMetric
from dipy.align.reslice import reslice
from dipy.align.transforms import (TranslationTransform3D, RigidTransform3D,
AffineTransform3D)
Expand Down Expand Up @@ -676,7 +678,6 @@ def run(self, static_image_file, moving_image_files, affine_matrix_file,

# Loading the image data from the input files into object.
static_image, static_grid2world = load_nifti(static_image_file)

moving_image, moving_grid2world = load_nifti(moving_image_file)

# Doing a sanity check for validating the dimensions of the input
Expand All @@ -698,3 +699,188 @@ def run(self, static_image_file, moving_image_files, affine_matrix_file,
transformed = img_transformation.transform(moving_image)

save_nifti(out_file, transformed, affine=static_grid2world)


class SynRegistrationFlow(Workflow):

def run(self, static_image_file, moving_image_file, affine_matrix_file,
inv_static=False, level_iters=[10, 10, 5], metric="cc",
mopt_sigma_diff=2.0, mopt_radius=4, mopt_smooth=0.0,
mopt_inner_iter=0.0, mopt_q_levels=256, mopt_double_gradient=True,
mopt_step_type='', step_length=0.25,
ss_sigma_factor=0.2, opt_tol=1e-5, inv_iter=20,
inv_tol=1e-3, out_dir='', out_warped='warped_moved.nii.gz',
out_inv_static='inc_static.nii.gz',
out_field='displacefield.txt'):

"""
Parameters
----------
static_image_file : string
Path of the static image file.

moving_image_file : string
Path to the moving image file.

affine_matrix_file : string
The text file containing pre alignment information or the
affine matrix.

inv_static : boolean, optional
Apply the inverse mapping to the static image (default 'False').

level_iters : variable int, optional
The number of iterations at each level of the gaussian pyramid.
By default, a 3-level scale space with iterations
sequence equal to [10, 10, 5] will be used. The 0-th
level corresponds to the finest resolution.

metric : string, optional
The metric to be used (Default cc, 'Cross Correlation metric').
metric available: cc (Cross Correlation), ssd (Sum Squared
Difference), em (Expectation-Maximization).

mopt_sigma_diff : float, optional
Metric option applied on Cross correlation (CC).
The standard deviation of the Gaussian smoothing kernel to be
applied to the update field at each iteration (default 2.0)

mopt_radius : int, optional
Metric option applied on Cross correlation (CC).
the radius of the squared (cubic) neighborhood at each voxel to
be considered to compute the cross correlation. (default 4)

mopt_smooth : float, optional
Metric option applied on Sum Squared Difference (SSD) and
Expectation Maximization (EM). Smoothness parameter, the
larger the value the smoother the deformation field.
(default 1.0 for EM, 4.0 for SSD)

mopt_inner_iter : int, optional
Metric option applied on Sum Squared Difference (SSD) and
Expectation Maximization (EM). This is number of iterations to be
performed at each level of the multi-resolution Gauss-Seidel
optimization algorithm (this is not the number of steps per
Gaussian Pyramid level, that parameter must be set for the
optimizer, not the metric). Default 5 for EM, 10 for SSD.

mopt_q_levels : int, optional
Metric option applied on Expectation Maximization (EM).
Number of quantization levels (Default: 256 for EM)

mopt_double_gradient : bool, optional
Metric option applied on Expectation Maximization (EM).
if True, the gradient of the expected static image under the moving
modality will be added to the gradient of the moving image,
similarly, the gradient of the expected moving image under the
static modality will be added to the gradient of the static image.

mopt_step_type : string, optional
Metric option applied on Sum Squared Difference (SSD) and
Expectation Maximization (EM). The optimization schedule to be
used in the multi-resolution Gauss-Seidel optimization algorithm
(not used if Demons Step is selected). Possible value:
('gauss_newton', 'demons'). default: 'gauss_newton' for EM,
'demons' for SSD.

step_length : float, optional
the length of the maximum displacement vector of the update
displacement field at each iteration.

ss_sigma_factor : float, optional
parameter of the scale-space smoothing kernel. For example, the
std. dev. of the kernel will be factor*(2^i) in the isotropic case
where i = 0, 1, ..., n_scales is the scale.

opt_tol : float, optional
the optimization will stop when the estimated derivative of the
energy profile w.r.t. time falls below this threshold.

inv_iter : int, optional
the number of iterations to be performed by the displacement field
inversion algorithm.

inv_tol : float, optional
the displacement field inversion algorithm will stop iterating
when the inversion error falls below this threshold.

out_dir : string, optional
Directory to save the transformed files (default '').

out_warped : string, optional
Name of the warped file. If no name is given then a
suffix 'transformed' will be appended to the name of the
original input file (default 'warped_moved.nii.gz').
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing: it says that if no name is given then the output will be name_of_original_file_transformed.nii.gz. but then also says that it will be warped_moved.nii.gz. That seems to be a contradiction. Please clarify.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's probably just the documentation that needs clarification here?


out_inv_static : string, optional
Name of the file to save the static image after applying the
inverse mapping (default 'inv_static.nii.gz').

out_field : string, optional
Name of the file to save the diffeomorphic field.

"""
io_it = self.get_io_iterator()
metric = metric.lower()
if metric not in ['ssd', 'cc', 'em']:
raise ValueError("Invalid similarity metric: Please"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to say what metrics are valid.

" provide a valid metric.")

# Init parameter if they are not setup
init_param = {'ssd': {'mopt_smooth': 4.0,
'mopt_inner_iter': 10,
'mopt_step_type': 'demons'
},
'em': {'mopt_smooth': 1.0,
'mopt_inner_iter': 5,
'mopt_step_type': 'gauss_newton'
}
}
mopt_smooth = mopt_smooth or init_param[metric]['mopt_smooth']
mopt_inner_iter = mopt_inner_iter or \
init_param[metric]['mopt_inner_iter']
mopt_step_type = mopt_step_type or \
init_param[metric]['mopt_step_type']

for static_file, moving_file, in_affine, \
warped_file, inv_static_file, displ_file in io_it:

# Loading the image data from the input files into object.
static_image, static_grid2world = load_nifti(static_file)
moving_image, moving_grid2world = load_nifti(moving_file)

# Sanity check for the input image dimensions.
ImageRegistrationFlow.check_dimensions(static_image, moving_image)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make the check_dimensions a module-wide utility function, rather than a method of the ImageRegistrationFlow?


# Loading the affine matrix.
affine_matrix = np.loadtxt(in_affine)

l_metric = {"ssd": SSDMetric(static_image.ndim,
smooth=mopt_smooth,
inner_iter=mopt_inner_iter,
step_type=mopt_step_type
),
"cc": CCMetric(static_image.ndim,
sigma_diff=mopt_sigma_diff,
radius=mopt_radius),
"em": EMMetric(static_image.ndim,
smooth=mopt_smooth,
inner_iter=mopt_inner_iter,
step_type=mopt_step_type,
q_levels=mopt_q_levels,
double_gradient=mopt_double_gradient)
}

current_metric = l_metric.get(metric.lower())

sdr = SymmetricDiffeomorphicRegistration(current_metric,
level_iters)

mapping = sdr.optimize(static_image, moving_image,
static_grid2world, moving_grid2world,
affine_matrix)

warped_moving = mapping.transform(moving_image)

# Saving the warped moving file and the alignment matrix.
save_nifti(warped_file, warped_moving, static_grid2world)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also save out the transformation? That is, the X by Y by Z by 3 values of the diffeomorphism? We could then apply this transformation to other files, which may be useful.

We have an implementation of something like that here:

https://github.com/yeatmanlab/pyAFQ/blob/master/AFQ/registration.py#L138
https://github.com/yeatmanlab/pyAFQ/blob/master/AFQ/registration.py#L153

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, will do

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! We'll want to eventually follow up with an API to apply these transforms to input files. But that's probably for another release cycle...

Copy link
Member Author

@skoudoro skoudoro Mar 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am thinking of changing ApplyAffineFlow to make it more generic. I want to call it ApplyTransformFlow with an option --affine or --diffeormorphic.

I will see if I have time to do that today. Otherwise, next release....

3 changes: 1 addition & 2 deletions dipy/workflows/tests/test_align.py
@@ -1,7 +1,6 @@
import numpy.testing as npt
import numpy as np

import numpy as np
import nibabel as nib
from nibabel.tmpdirs import TemporaryDirectory
from dipy.tracking.streamline import Streamlines
Expand All @@ -14,7 +13,7 @@
from dipy.align.tests.test_parzenhist import setup_random_transform
from dipy.align.transforms import regtransforms
from dipy.io.image import save_nifti
from dipy.workflows.align import ImageRegistrationFlow
from dipy.workflows.align import ImageRegistrationFlow, SynRegistrationFlow
from dipy.workflows.align import ApplyAffineFlow


Expand Down