Skip to content

Commit

Permalink
Fix merge with master, make mag optional in unwrap phase and prepare …
Browse files Browse the repository at this point in the history
…fieldmap and fix comments
  • Loading branch information
po09i committed Nov 25, 2020
1 parent 1f98ef5 commit ddde911
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 61 deletions.
28 changes: 14 additions & 14 deletions shimmingtoolbox/prepare_fieldmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,30 @@
from shimmingtoolbox.unwrap.unwrap_phase import unwrap_phase


def prepare_fieldmap(phase, echo_times, affine, mag=None, unwrapper='prelude', mask=None, threshold=None):
def prepare_fieldmap(phase, echo_times, affine, unwrapper='prelude', mag=None, mask=None, threshold=None):
""" Creates fieldmap (in Hz) from phase images. This function accommodates multiple echoes (2 or more) and phase
difference. This function also accommodates 4D phase inputs, where the 4th dimension represents the time, in case
multiple field maps are acquired across time for the purpose of real-time shimming experiments.
Args:
phase (list): List of phase values in a numpy.ndarray. The numpy array can be [x, y], [x, y, z] or [x, y, z, t].
The values must range from [-pi to pi]
The values must range from [-pi to pi].
echo_times (list): List of echo times in seconds for each echo. The number of echotimes must match the number of
echoes. It input is a phasediff (1 phase), input 2 echotimes
affine (numpy.ndarray): 4x4 affine matrix
mag (numpy.ndarray): Array containing magnitude data relevant for ``phase`` input. Shape must match phase[echo]
unwrapper (str): Unwrapper to use for phase unwrapping. Supported: prelude
mask (numpy.ndarray): Mask for masking output fieldmap. Must match shape of phase[echo]
echoes. It input is a phasediff (1 phase), input 2 echotimes.
affine (numpy.ndarray): 4x4 affine matrix.
unwrapper (str): Unwrapper to use for phase unwrapping. Supported: prelude.
mag (numpy.ndarray): Array containing magnitude data relevant for ``phase`` input. Shape must match phase[echo].
mask (numpy.ndarray): Mask for masking output fieldmap. Must match shape of phase[echo].
threshold: Prelude parameter used for masking.
Returns
numpy.ndarray: Unwrapped fieldmap in Hz
numpy.ndarray: Unwrapped fieldmap in Hz.
"""
# Check inputs
for i_echo in range(len(phase)):
# Check that the output phase is in radian (Note: the test below is not 100% bullet proof)
if (phase[i_echo].max() > math.pi) or (phase[i_echo].min() < -math.pi):
raise RuntimeError("read_nii must range from -pi to pi")
raise RuntimeError("read_nii must range from -pi to pi.")

# Check that the input echotimes are the appropriate size by looking at phase
is_phasediff = (len(phase) == 1 and len(echo_times) == 2)
Expand All @@ -39,15 +39,15 @@ def prepare_fieldmap(phase, echo_times, affine, mag=None, unwrapper='prelude', m
raise RuntimeError("Phasediff must have 2 echotime points. Otherwise the number of echoes must match the"
" number of echo times.")

# Make sure mag is the reight shape
# Make sure mag is the right shape
if mag is not None:
if mag.shape != phase[0].shape:
raise RuntimeError("mag and phase must have the same dimensions")
raise RuntimeError("mag and phase must have the same dimensions.")

# Make sure mask has the right shape
if mask is not None:
if mask.shape != phase[0].shape:
raise RuntimeError("Shape of mask and phase must match")
raise RuntimeError("Shape of mask and phase must match.")

# Get the time between echoes and calculate phase difference depending on number of echoes
if len(phase) == 1:
Expand All @@ -70,10 +70,10 @@ def prepare_fieldmap(phase, echo_times, affine, mag=None, unwrapper='prelude', m
else:
# TODO: More echoes
# TODO: Add method once multiple methods are implemented
raise NotImplementedError(f"This number of phase input is not supported: {len(phase)}")
raise NotImplementedError(f"This number of phase input is not supported: {len(phase)}.")

# Run the unwrapper
phasediff_unwrapped = unwrap_phase(phasediff, mag, affine, unwrapper=unwrapper, mask=mask, threshold=threshold)
phasediff_unwrapped = unwrap_phase(phasediff, affine, unwrapper=unwrapper, mag=mag, mask=mask, threshold=threshold)

# TODO: correct for potential wraps between time points

Expand Down
26 changes: 13 additions & 13 deletions shimmingtoolbox/unwrap/unwrap_phase.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
""" Wrapper to different unwrapping algorithms """
""" Wrapper to different unwrapping algorithms. """

import numpy as np
from shimmingtoolbox.unwrap.prelude import prelude


def unwrap_phase(phase, mag, affine, unwrapper='prelude', mask=None, threshold=None):
def unwrap_phase(phase, affine, unwrapper='prelude', mag=None, mask=None, threshold=None):
""" Calls different unwrapping algorithms according to the specified `unwrapper` parameter. The function also
allows to call the different unwrappers with more flexibility regarding input shape.
Args:
phase (numpy.ndarray): 2D, 3D or 4D radian values [-pi to pi] to perform phase unwrapping.
Supported shapes: [x, y], [x, y, z] or [x, y, z, t]
mag (numpy.ndarray): 2D, 3D or 4D magnitude data corresponding to phase data. Shape must be the same as
``phase``
Supported shapes: [x, y], [x, y, z] or [x, y, z, t].
affine (numpy.ndarray): 2D array (4x4) containing the transformation coefficients. Can be acquired by :
nii = nib.load("nii_path")
affine = nii.affine
unwrapper (str, optional): Unwrapper algorithm name. Possible values: ``prelude``
unwrapper (str, optional): Unwrapper algorithm name. Possible values: ``prelude``.
mag (numpy.ndarray): 2D, 3D or 4D magnitude data corresponding to phase data. Shape must be the same as
``phase``.
mask (numpy.ndarray): numpy array of booleans with shape of ``phase`` to mask during phase unwrapping.
threshold (float): Prelude parameter, see prelude for more detail
threshold (float): Prelude parameter, see prelude for more detail.
Returns:
numpy.ndarray: Unwrapped phase image
numpy.ndarray: Unwrapped phase image.
"""

if unwrapper == 'prelude':
Expand All @@ -39,12 +39,12 @@ def unwrap_phase(phase, mag, affine, unwrapper='prelude', mask=None, threshold=N
mag = mag4d
mask = mask4d

phase3d_unwrapped = prelude(phase4d, mag, affine, mask=mask, threshold=threshold)
phase3d_unwrapped = prelude(phase4d, affine, mag=mag, mask=mask, threshold=threshold)

phase_unwrapped = phase3d_unwrapped[..., 0]

elif phase.ndim == 3:
phase_unwrapped = prelude(phase, mag, affine, mask=mask, threshold=threshold)
phase_unwrapped = prelude(phase, affine, mag=mag, mask=mask, threshold=threshold)

elif phase.ndim == 4:
phase_unwrapped = np.zeros_like(phase)
Expand All @@ -61,12 +61,12 @@ def unwrap_phase(phase, mag, affine, unwrapper='prelude', mask=None, threshold=N
mask_input = mask3d
mag_input = mag3d

phase_unwrapped[..., i_t] = prelude(phase3d, mag_input, affine, mask=mask_input, threshold=threshold)
phase_unwrapped[..., i_t] = prelude(phase3d, affine, mag=mag_input, mask=mask_input, threshold=threshold)

else:
raise RuntimeError("Shape of input phase is not supported")
raise RuntimeError("Shape of input phase is not supported.")

else:
raise NotImplementedError(f'The unwrap function {unwrapper} is not implemented')
raise NotImplementedError(f'The unwrap function {unwrapper} is not implemented.')

return phase_unwrapped
30 changes: 15 additions & 15 deletions test/test_prepare_fieldmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def setup(self):
self.echo_times = [0.00246, 0.00492]

def test_prepare_fieldmap_1_echo(self):
"""Test default works"""
"""Test default works."""
fieldmap = prepare_fieldmap([self.phase], self.echo_times, self.affine)

assert fieldmap.shape == self.phase.shape
Expand All @@ -35,13 +35,13 @@ def test_prepare_fieldmap_1_echo(self):
np.array([18.51355514, 13.84794053, 9.48013154, 5.11232207, 0.64524454])))

def test_prepare_fieldmap_with_mag(self):
"""Test mag works"""
"""Test mag works."""
fieldmap = prepare_fieldmap([self.phase], self.echo_times, self.affine, mag=self.mag)

assert fieldmap.shape == self.phase.shape

def test_prepare_fieldmap_2_echoes(self):
"""Test 2 echoes works"""
"""Test 2 echoes works."""

# Import 2 echoes and rescale
fname_phase1 = os.path.join(__dir_testing__, 'sub-fieldmap', 'fmap', 'sub-fieldmap_phase1.nii.gz')
Expand All @@ -63,7 +63,7 @@ def test_prepare_fieldmap_2_echoes(self):

# Tests that should throw errors
def test_prepare_fieldmap_wrong_range(self):
"""Test error when range is not between -pi and pi"""
"""Test error when range is not between -pi and pi."""

# This should return an error
try:
Expand All @@ -73,11 +73,11 @@ def test_prepare_fieldmap_wrong_range(self):
return 0

# If there isn't an error, then there is a problem
print('\nRange is not between -pi and pi but does not throw an error')
print("\nRange is not between -pi and pi but does not throw an error.")
assert False

def test_prepare_fieldmap_wrong_echo_times(self):
"""Wrong number of echo times"""
"""Wrong number of echo times."""

echo_times = [0.001, 0.002, 0.003]

Expand All @@ -89,11 +89,11 @@ def test_prepare_fieldmap_wrong_echo_times(self):
return 0

# If there isn't an error, then there is a problem
print('\nEcho_times has too many elements but does not throw an error')
print("\nEcho_times has too many elements but does not throw an error.")
assert False

def test_prepare_fieldmap_mag_wrong_shape(self):
"""Mag has the wrong shape"""
"""Mag has the wrong shape."""

# This should return an error
try:
Expand All @@ -103,11 +103,11 @@ def test_prepare_fieldmap_mag_wrong_shape(self):
return 0

# If there isn't an error, then there is a problem
print('\nMag has the wrong shape but does not throw an error')
print("\nMag has the wrong shape but does not throw an error.")
assert False

def test_prepare_fieldmap_mask_wrong_shape(self):
"""Mask has the wrong shape"""
"""Mask has the wrong shape."""

# This should return an error
try:
Expand All @@ -117,11 +117,11 @@ def test_prepare_fieldmap_mask_wrong_shape(self):
return 0

# If there isn't an error, then there is a problem
print('\nMask has the wrong shape but does not throw an error')
print("\nMask has the wrong shape but does not throw an error.")
assert False

def test_prepare_fieldmap_phasediff_1_echotime(self):
"""EchoTime of length one for phasediff should fail"""
"""EchoTime of length one for phasediff should fail."""

# This should return an error
try:
Expand All @@ -131,11 +131,11 @@ def test_prepare_fieldmap_phasediff_1_echotime(self):
return 0

# If there isn't an error, then there is a problem
print('\necho_time has the wrong shape but does not throw an error')
print("\necho_time has the wrong shape but does not throw an error.")
assert False

def test_prepare_fieldmap_3_echoes(self):
"""3 echoes are not implemented so the test should fail"""
"""3 echoes are not implemented so the test should fail."""

echo_times = [0.001, 0.002, 0.003]

Expand All @@ -147,5 +147,5 @@ def test_prepare_fieldmap_3_echoes(self):
return 0

# If there isn't an error, then there is a problem
print('\n3 echoes are not implemented')
print("\n3 echoes are not implemented.")
assert False
39 changes: 20 additions & 19 deletions test/test_unwrap_phase.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,65 +25,66 @@ def setup(self):
self.mag = nii_mag.get_fdata()

def test_unwrap_phase_prelude_4d(self):
"""Test prelude with 4d input data"""
unwrapped = unwrap_phase(self.phase, self.mag, self.affine, unwrapper='prelude')
"""Test prelude with 4d input data."""
unwrapped = unwrap_phase(self.phase, self.affine, unwrapper='prelude', mag=self.mag)
assert unwrapped.shape == self.phase.shape

def test_unwrap_phase_prelude_3d(self):
"""Test prelude with 3d input data"""
"""Test prelude with 3d input data."""
phase = self.phase[..., 0]
mag = self.mag[..., 0]
unwrapped = unwrap_phase(phase, mag, self.affine, unwrapper='prelude')
unwrapped = unwrap_phase(phase, self.affine, unwrapper='prelude', mag=mag)
assert unwrapped.shape == phase.shape

def test_unwrap_phase_prelude_2d(self):
"""Test prelude with 2d input data"""
"""Test prelude with 2d input data."""
phase = self.phase[..., 0, 0]
mag = self.mag[..., 0, 0]
unwrapped = unwrap_phase(phase, mag, self.affine, unwrapper='prelude')
unwrapped = unwrap_phase(phase, self.affine, unwrapper='prelude', mag=mag)
assert unwrapped.shape == phase.shape

def test_unwrap_phase_prelude_threshold(self):
"""Test prelude with threshold parameter"""
unwrapped = unwrap_phase(self.phase, self.mag, self.affine, unwrapper='prelude', threshold=0.1)
"""Test prelude with threshold parameter."""
unwrapped = unwrap_phase(self.phase, self.affine, unwrapper='prelude', mag=self.mag, threshold=0.1)
assert unwrapped.shape == self.phase.shape

def test_unwrap_phase_prelude_4d_mask(self):
"""Test prelude with mask parameter"""
unwrapped = unwrap_phase(self.phase, self.mag, self.affine, unwrapper='prelude', mask=np.ones_like(self.phase))
"""Test prelude with mask parameter."""
unwrapped = unwrap_phase(self.phase, self.affine, unwrapper='prelude', mag=self.mag,
mask=np.ones_like(self.phase))
assert unwrapped.shape == self.phase.shape

def test_unwrap_phase_prelude_2d_mask(self):
"""Test prelude with 2d mask parameter"""
"""Test prelude with 2d mask parameter."""
phase = self.phase[..., 0, 0]
mag = self.mag[..., 0, 0]
unwrapped = unwrap_phase(phase, mag, self.affine, unwrapper='prelude', mask=np.ones_like(phase))
unwrapped = unwrap_phase(phase, self.affine, unwrapper='prelude', mag=mag, mask=np.ones_like(phase))
assert unwrapped.shape == phase.shape

def test_unwrap_phase_wrong_unwrapper(self):
"""Input wrong unwrapper"""
"""Input wrong unwrapper."""

# This should return an error
try:
unwrap_phase(self.phase, self.mag, self.affine, unwrapper='Not yet implemented')
unwrap_phase(self.phase, self.affine, mag=self.mag, unwrapper='Not yet implemented.')
except NotImplementedError:
# If an exception occurs, this is the desired behaviour
return 0

# If there isn't an error, then there is a problem
print('\nNot supported unwrapper but does not throw an error')
print('\nNot supported unwrapper but does not throw an error.')
assert False

def test_unwrap_phase_wrong_shape(self):
"""Input wrong shape"""
"""Input wrong shape."""

# This should return an error
try:
unwrap_phase(np.expand_dims(self.phase, -1), self.mag, self.affine)
unwrap_phase(np.expand_dims(self.phase, -1), self.affine, mag=self.mag)
except RuntimeError:
# If an exception occurs, this is the desired behaviour
return 0

# If there isn't an error, then there is a problem
print('\nWrong dimensions but does not throw an error')
assert False
print('\nWrong dimensions but does not throw an error.')
assert False

0 comments on commit ddde911

Please sign in to comment.