-
Notifications
You must be signed in to change notification settings - Fork 24
[ENH] Add emc-related helper functions - images #77
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
3bee02d
88a6e80
bb4cc79
1e9c015
e4ea798
e982d6f
3588947
1544a55
b6e917e
a888d73
d872094
eb43043
abf5186
25ecfc0
02f82ce
bb6c9a5
56213a0
5e64dfa
85a285a
b13d57b
7767498
947a5ca
7403652
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import numpy as np | ||
import nibabel as nb | ||
from nipype.utils.filemanip import fname_presuffix | ||
|
||
|
||
def extract_b0(in_file, b0_ixs, newpath=None): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Extract the *b0* volumes from a DWI dataset.""" | ||
out_file = fname_presuffix(in_file, suffix="_b0", newpath=newpath) | ||
|
||
img = nb.load(in_file) | ||
data = img.get_fdata(dtype="float32") | ||
|
||
b0 = data[..., b0_ixs] | ||
|
||
hdr = img.header.copy() | ||
hdr.set_data_shape(b0.shape) | ||
hdr.set_xyzt_units("mm") | ||
hdr.set_data_dtype(np.float32) | ||
nb.Nifti1Image(b0, img.affine, hdr).to_filename(out_file) | ||
return out_file | ||
|
||
|
||
def rescale_b0(in_file, mask_file, newpath=None): | ||
"""Rescale the input volumes using the median signal intensity.""" | ||
out_file = fname_presuffix(in_file, suffix="_rescaled_b0", newpath=newpath) | ||
|
||
img = nb.load(in_file) | ||
if img.dataobj.ndim == 3: | ||
return in_file | ||
|
||
data = img.get_fdata(dtype="float32") | ||
mask_img = nb.load(mask_file) | ||
mask_data = mask_img.get_fdata(dtype="float32") | ||
|
||
median_signal = np.median(data[mask_data > 0, ...], axis=0) | ||
rescaled_data = 1000 * data / median_signal | ||
hdr = img.header.copy() | ||
nb.Nifti1Image(rescaled_data, img.affine, hdr).to_filename(out_file) | ||
return out_file | ||
|
||
|
||
def median(in_file, newpath=None): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Average a 4D dataset across the last dimension using median.""" | ||
out_file = fname_presuffix(in_file, suffix="_b0ref", newpath=newpath) | ||
|
||
img = nb.load(in_file) | ||
if img.dataobj.ndim == 3: | ||
return in_file | ||
if img.shape[-1] == 1: | ||
nb.squeeze_image(img).to_filename(out_file) | ||
return out_file | ||
|
||
median_data = np.median(img.get_fdata(dtype="float32"), axis=-1) | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
hdr = img.header.copy() | ||
hdr.set_xyzt_units("mm") | ||
hdr.set_data_dtype(np.float32) | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
nb.Nifti1Image(median_data, img.affine, hdr).to_filename(out_file) | ||
return out_file | ||
|
||
|
||
def average_images(images, out_path=None): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Average a 4D dataset across the last dimension using mean.""" | ||
from nilearn.image import mean_img | ||
|
||
average_img = mean_img([nb.load(img) for img in images]) | ||
if out_path is None: | ||
out_path = fname_presuffix( | ||
images[0], use_ext=False, suffix="_mean.nii.gz" | ||
) | ||
average_img.to_filename(out_path) | ||
return out_path | ||
|
||
|
||
def quick_load_images(image_list, dtype=np.float32): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Load 3D volumes from a list of file paths into a 4D array.""" | ||
example_img = nb.load(image_list[0]) | ||
num_images = len(image_list) | ||
output_matrix = np.zeros(tuple(example_img.shape) + (num_images,), dtype=dtype) | ||
for image_num, image_path in enumerate(image_list): | ||
output_matrix[..., image_num] = nb.load(image_path).get_fdata(dtype=dtype) | ||
return output_matrix | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
def match_transforms(dwi_files, transforms, b0_indices): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Arranges the order of a list of affine transforms to correspond with that of | ||
each individual dwi volume file, accounting for the indices of B0s. A helper | ||
function for EMC.""" | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
num_dwis = len(dwi_files) | ||
num_transforms = len(transforms) | ||
|
||
if num_dwis == num_transforms: | ||
return transforms | ||
|
||
# Do sanity checks | ||
if not len(transforms) == len(b0_indices): | ||
raise Exception("number of transforms does not match number of b0 images") | ||
|
||
# Create a list of which emc affines go with each of the split images | ||
nearest_affines = [] | ||
for index in range(num_dwis): | ||
nearest_b0_num = np.argmin(np.abs(index - np.array(b0_indices))) | ||
this_transform = transforms[nearest_b0_num] | ||
nearest_affines.append(this_transform) | ||
|
||
return nearest_affines | ||
|
||
|
||
def save_4d_to_3d(in_file): | ||
"""Split a 4D dataset along the last dimension into multiple 3D volumes.""" | ||
files_3d = nb.four_to_three(nb.load(in_file)) | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
out_files = [] | ||
for i, file_3d in enumerate(files_3d): | ||
out_file = fname_presuffix(in_file, suffix="_tmp_{}".format(i)) | ||
file_3d.to_filename(out_file) | ||
out_files.append(out_file) | ||
del files_3d | ||
return out_files | ||
|
||
|
||
def prune_b0s_from_dwis(in_files, b0_ixs): | ||
"""Remove *b0* volume files from a complete list of DWI volume files.""" | ||
if in_files[0].endswith("_warped.nii.gz"): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
out_files = [ | ||
i | ||
for j, i in enumerate( | ||
sorted( | ||
in_files, key=lambda x: int(x.split("_")[-2].split(".nii.gz")[0]) | ||
) | ||
) | ||
if j not in b0_ixs | ||
] | ||
else: | ||
out_files = [ | ||
i | ||
for j, i in enumerate( | ||
sorted( | ||
in_files, key=lambda x: int(x.split("_")[-1].split(".nii.gz")[0]) | ||
) | ||
) | ||
if j not in b0_ixs | ||
] | ||
return out_files | ||
|
||
|
||
def save_3d_to_4d(in_files): | ||
"""Concatenate a list of 3D volumes into a 4D output.""" | ||
img_4d = nb.funcs.concat_images([nb.load(img_3d) for img_3d in in_files]) | ||
out_file = fname_presuffix(in_files[0], suffix="_merged") | ||
img_4d.to_filename(out_file) | ||
del img_4d | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return out_file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should contribute this to Nipype. Why don't we start a module of nibabel interfaces with these functions and also upstream https://github.com/poldracklab/niworkflows/blob/master/niworkflows/interfaces/nibabel.py ? WDYT @effigies ? I think the more interfaces with nibabel handling we have screened by Chris, the better. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure why the function exists. It doesn't seem to be being called. And written without unnecessary boilerplate, it's quite short: def save_3d_to_4d(in_files):
out_file = fname_presuffix(in_files[0], suffix="_merged")
nb.concat_images(in_files).to_filename(out_file)
return out_file If something this small seems worth upstreaming, sure, why not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, this is a nice, general purpose function that could be used in a lot of scenarios. It is basically a pure python equivalent to @effigies -- this function is referenced here. We are in the process of incorporating a much larger WIP piecemeal, for which this is a critical helper function. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're using it in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's included in a general import list, along with numpy and a few others, for all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yep, I think having nibabel interfaces handy with NiPype would be super-useful for everyone and with the big plus of your eyeballs for the time you keep tabs on nibabel and nipype (hopefully long from my egoistic pov). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've kept these in this PR for now until nipreps/niworkflows#489 gets merged. Also, the doctests here are specific to dmriprep's fixtures so they will need to be tweaked for tests in niworkflows. @oesteban @effigies |
||
|
||
|
||
def get_params(A): | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Takes an transformation affine matrix A and determines | ||
rotations and translations.""" | ||
dPys marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def rang(b): | ||
a = min(max(b, -1), 1) | ||
return a | ||
|
||
Ry = np.arcsin(A[0, 2]) | ||
# Rx = np.arcsin(A[1, 2] / np.cos(Ry)) | ||
# Rz = np.arccos(A[0, 1] / np.sin(Ry)) | ||
|
||
if (abs(Ry) - np.pi / 2) ** 2 < 1e-9: | ||
Rx = 0 | ||
Rz = np.arctan2(-rang(A[1, 0]), rang(-A[2, 0] / A[0, 2])) | ||
else: | ||
c = np.cos(Ry) | ||
Rx = np.arctan2(rang(A[1, 2] / c), rang(A[2, 2] / c)) | ||
Rz = np.arctan2(rang(A[0, 1] / c), rang(A[0, 0] / c)) | ||
|
||
rotations = [Rx, Ry, Rz] | ||
translations = [A[0, 3], A[1, 3], A[2, 3]] | ||
|
||
return rotations, translations |
Uh oh!
There was an error while loading. Please reload this page.