Skip to content
Merged
21 changes: 16 additions & 5 deletions nibabel/nicom/dicomwrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from .dwiparams import B2q, nearest_pos_semi_def, q2bg
from ..openers import ImageOpener
from ..onetime import setattr_on_read as one_time
from ..pydicom_compat import tag_for_keyword
from ..pydicom_compat import tag_for_keyword, Sequence


class WrapperError(Exception):
Expand Down Expand Up @@ -461,10 +461,19 @@ def __init__(self, dcm_data):
Wrapper.__init__(self, dcm_data)
self.dcm_data = dcm_data
self.frames = dcm_data.get('PerFrameFunctionalGroupsSequence')
self._nframes = self.get('NumberOfFrames')
try:
self.frames[0]
except TypeError:
raise WrapperError("PerFrameFunctionalGroupsSequence is empty.")
# DWI image where derived isotropic, ADC or trace volume was appended to the series
if self.frames[0].get([0x18, 0x9117]):
self.frames = Sequence(
frame for frame in self.frames if
frame.get([0x18, 0x9117])[0].get([0x18, 0x9075]).value
!= 'ISOTROPIC'
)
self._nframes = len(self.frames)
try:
self.shared = dcm_data.get('SharedFunctionalGroupsSequence')[0]
except TypeError:
Expand Down Expand Up @@ -503,8 +512,7 @@ def image_shape(self):
if None in (rows, cols):
raise WrapperError("Rows and/or Columns are empty.")
# Check number of frames
n_frames = self.get('NumberOfFrames')
assert len(self.frames) == n_frames
assert len(self.frames) == self._nframes
frame_indices = np.array(
[frame.FrameContentSequence[0].DimensionIndexValues
for frame in self.frames])
Expand All @@ -528,12 +536,15 @@ def image_shape(self):
# Store frame indices
self._frame_indices = frame_indices
if n_dim < 4: # 3D volume
return rows, cols, n_frames
return rows, cols, self._nframes
# More than 3 dimensions
ns_unique = [len(np.unique(row)) for row in self._frame_indices.T]
if len(ns_unique) == 3:
# derived volume is included
ns_unique.pop(1)
Copy link
Member

Choose a reason for hiding this comment

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

I don't really follow this guard. Can you elaborate?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure this addresses the problem properly / gracefully, but I'm unsure how to proceed. I've come across a derived image included in the diffusion DICOM, which is currently not properly handled. I thought this meant 5D data wasn't yet handled, but in fact there is a test specifically for this introduced in 3f04e3c (maybe @matthew-brett can share some wisdom)

# 5D!
dim_idxs = [
[1, 4, 2, 1],
[1, 2, 2, 1],
[1, 3, 2, 1],
[1, 1, 2, 1],
[1, 4, 2, 2],
[1, 2, 2, 2],
[1, 3, 2, 2],
[1, 1, 2, 2],
[1, 4, 1, 1],
[1, 2, 1, 1],
[1, 3, 1, 1],
[1, 1, 1, 1],
[1, 4, 1, 2],
[1, 2, 1, 2],
[1, 3, 1, 2],
[1, 1, 1, 2]]
fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0))
shape = (2, 3, 4, 2, 2)
data = np.arange(np.prod(shape)).reshape(shape)
sorted_data = data.reshape(shape[:2] + (-1,), order='F')
order = [11, 9, 10, 8, 3, 1, 2, 0,
15, 13, 14, 12, 7, 5, 6, 4]
sorted_data = sorted_data[..., np.argsort(order)]
fake_mf['pixel_array'] = np.rollaxis(sorted_data, 2)
assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1)

shape = (rows, cols) + tuple(ns_unique)
n_vols = np.prod(shape[3:])
if n_frames != n_vols * shape[2]:
if self._nframes != n_vols * shape[2]:
raise WrapperError("Calculated shape does not match number of "
"frames.")
return tuple(shape)
Expand Down
Binary file not shown.
8 changes: 8 additions & 0 deletions nibabel/nicom/tests/test_dicomwrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
DATA_FILE_SLC_NORM = pjoin(IO_DATA_PATH, 'csa_slice_norm.dcm')
DATA_FILE_DEC_RSCL = pjoin(IO_DATA_PATH, 'decimal_rescale.dcm')
DATA_FILE_4D = pjoin(IO_DATA_PATH, '4d_multiframe_test.dcm')
DATA_FILE_4D_DERIVED = pjoin(IO_DATA_PATH, '4d_multiframe_with_derived.dcm')

# This affine from our converted image was shown to match our image spatially
# with an image from SPM DICOM conversion. We checked the matching with SPM
Expand Down Expand Up @@ -616,6 +617,13 @@ def test_data_real(self):
assert_equal(sha1(dat_str).hexdigest(),
'149323269b0af92baa7508e19ca315240f77fa8c')

@dicom_test
def test_data_derived_shape(self):
# Test 4D diffusion data with an additional trace volume included
# Excludes the trace volume and generates the correct shape
dw = didw.wrapper_from_file(DATA_FILE_4D_DERIVED)
assert_equal(dw.image_shape, (96, 96, 60, 33))

@dicom_test
def test_data_fake(self):
# Test algorithm for get_data
Expand Down
1 change: 1 addition & 0 deletions nibabel/pydicom_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
read_file = pydicom.read_file

if have_dicom:
from pydicom.sequence import Sequence
try:
# Versions >= 1.0
tag_for_keyword = pydicom.datadict.tag_for_keyword
Expand Down