Skip to content

FIX: Correctly handle Philips DICOMs w/ derived volume #795

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

Merged
merged 13 commits into from
Aug 21, 2019
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "nibabel-data/nitest-cifti2"]
path = nibabel-data/nitest-cifti2
url = https://github.com/demianw/nibabel-nitest-cifti2.git
[submodule "nibabel-data/nitest-dicom"]
path = nibabel-data/nitest-dicom
url = https://github.com/effigies/nitest-dicom
1 change: 1 addition & 0 deletions nibabel-data/nitest-dicom
Submodule nitest-dicom added at ff6844
36 changes: 35 additions & 1 deletion nibabel/nicom/dicomwrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
from __future__ import division

import operator
import warnings

import numpy as np

from . import csareader as csar
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 @@ -502,8 +503,32 @@ def image_shape(self):
rows, cols = self.get('Rows'), self.get('Columns')
if None in (rows, cols):
raise WrapperError("Rows and/or Columns are empty.")

# Check number of frames
first_frame = self.frames[0]
n_frames = self.get('NumberOfFrames')
# some Philips may have derived images appended
has_derived = False
if hasattr(first_frame, 'get') and first_frame.get([0x18, 0x9117]):
# DWI image may include derived isotropic, ADC or trace volume
try:
self.frames = Sequence(
frame for frame in self.frames if
frame.MRDiffusionSequence[0].DiffusionDirectionality
!= 'ISOTROPIC'
)
except IndexError:
# Sequence tag is found but missing items!
raise WrapperError("Diffusion file missing information")
except AttributeError:
# DiffusionDirectionality tag is not required
pass
else:
if n_frames != len(self.frames):
warnings.warn("Derived images found and removed")
n_frames = len(self.frames)
has_derived = True

assert len(self.frames) == n_frames
frame_indices = np.array(
[frame.FrameContentSequence[0].DimensionIndexValues
Expand All @@ -522,6 +547,15 @@ def image_shape(self):
if stackid_tag in dim_seq:
stackid_dim_idx = dim_seq.index(stackid_tag)
frame_indices = np.delete(frame_indices, stackid_dim_idx, axis=1)
dim_seq.pop(stackid_dim_idx)
if has_derived:
# derived volume is included
derived_tag = tag_for_keyword("DiffusionBValue")
if derived_tag not in dim_seq:
raise WrapperError("Missing information, cannot remove indices "
"with confidence.")
derived_dim_idx = dim_seq.index(derived_tag)
frame_indices = np.delete(frame_indices, derived_dim_idx, axis=1)
# account for the 2 additional dimensions (row and column) not included
# in the indices
n_dim = frame_indices.shape[1] + 2
Expand Down
11 changes: 11 additions & 0 deletions nibabel/nicom/tests/test_dicomwrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
assert_not_equal, assert_raises)

from numpy.testing import assert_array_equal, assert_array_almost_equal
from ...tests.nibabel_data import get_nibabel_data, needs_nibabel_data

IO_DATA_PATH = pjoin(dirname(__file__), 'data')
DATA_FILE = pjoin(IO_DATA_PATH, 'siemens_dwi_1000.dcm.gz')
Expand All @@ -36,6 +37,8 @@
DATA_FILE_DEC_RSCL = pjoin(IO_DATA_PATH, 'decimal_rescale.dcm')
DATA_FILE_4D = pjoin(IO_DATA_PATH, '4d_multiframe_test.dcm')
DATA_FILE_EMPTY_ST = pjoin(IO_DATA_PATH, 'slicethickness_empty_string.dcm')
DATA_FILE_4D_DERIVED = pjoin(get_nibabel_data(), 'nitest-dicom',
'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 @@ -622,6 +625,14 @@ def test_slicethickness_fallback(self):
dw = didw.wrapper_from_file(DATA_FILE_EMPTY_ST)
assert_equal(dw.voxel_sizes[2], 1.0)

@dicom_test
@needs_nibabel_data('nitest-dicom')
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
8 changes: 5 additions & 3 deletions nibabel/pydicom_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,24 @@
import numpy as np

have_dicom = True
pydicom = read_file = tag_for_keyword = None
pydicom = read_file = tag_for_keyword = Sequence = None

try:
import dicom as pydicom
# Values not imported by default
import dicom.values
except ImportError:
try:
import pydicom
except ImportError:
have_dicom = False
else: # pydicom module available
from pydicom.dicomio import read_file
from pydicom.sequence import Sequence
# Values not imported by default
import pydicom.values
else: # dicom module available
# Values not imported by default
import dicom.values
from dicom.sequence import Sequence
read_file = pydicom.read_file

if have_dicom:
Expand Down