From ee0d89941fb454be05b792d5ad9ca2f66cbb43ae Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Fri, 8 Apr 2016 18:04:29 -0700 Subject: [PATCH 1/4] DOC: add more links on DICOM multi-frame More links to sections and diagrams on multi-frame / enhanced MR image IOP. --- nibabel/nicom/dicomwrappers.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index fb01e86651..72d0452f93 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -412,7 +412,18 @@ def b_vector(self): class MultiframeWrapper(Wrapper): """Wrapper for Enhanced MR Storage SOP Class - tested with Philips' Enhanced DICOM implementation + Tested with Philips' Enhanced DICOM implementation. + + The specification for the Enhanced MR image IOP / SOP began life as `DICOM + supplement 49 `_, + but as of 2016 it is part of the standard. In particular see: + + * `A.36 Enhanced MR Information Object Definitions + `_; + * `C.7.6.16 Multi-Frame Functional Groups Module + `_; + * `C.7.6.17 Multi-Frame Dimension Module + `_. Attributes ---------- @@ -480,9 +491,12 @@ def image_shape(self): References ---------- - - * C.7.6.16 Multi-Frame Functional Groups Module: http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#sect_C.7.6.16 - * C.7.6.17 Multi-Frame Dimension Module: http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#sect_C.7.6.17 + * C.7.6.16 Multi-Frame Functional Groups Module: + http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#sect_C.7.6.16 + * C.7.6.17 Multi-Frame Dimension Module: + http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#sect_C.7.6.17 + * Diagram of DimensionIndexSequence and DimensionIndexValues: + http://dicom.nema.org/medical/dicom/current/output/pdf/part03.pdf#figure_C.7.6.17-1 """ rows, cols = self.get('Rows'), self.get('Columns') if None in (rows, cols): From 78c002db9fc1f94f774fe68f9667c408335a3b0b Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Fri, 8 Apr 2016 18:31:36 -0700 Subject: [PATCH 2/4] RF: add extra blank line for PEP8 PEP8 fussy about exact number of blank lines. --- nibabel/nicom/dicomwrappers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 72d0452f93..e43a8ed627 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -23,6 +23,7 @@ from ..onetime import setattr_on_read as one_time from ..pydicom_compat import pydicom + class WrapperError(Exception): pass From 4cafaf5c0371714c6dffa440ac655bb5cee43233 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Fri, 8 Apr 2016 18:32:31 -0700 Subject: [PATCH 3/4] BF: note multiframe shape test as DICOM test Require DICOM for multiframe image shape tests. --- nibabel/nicom/tests/test_dicomwrappers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nibabel/nicom/tests/test_dicomwrappers.py b/nibabel/nicom/tests/test_dicomwrappers.py index 109025f1c6..870b771b69 100755 --- a/nibabel/nicom/tests/test_dicomwrappers.py +++ b/nibabel/nicom/tests/test_dicomwrappers.py @@ -435,6 +435,7 @@ class TestMultiFrameWrapper(TestCase): 'SharedFunctionalGroupsSequence': [None]} WRAPCLASS = didw.MultiframeWrapper + @dicom_test def test_shape(self): # Check the shape algorithm fake_mf = copy(self.MINIMAL_MF) @@ -614,6 +615,7 @@ def test_data_real(self): assert_equal(sha1(dat_str).hexdigest(), '149323269b0af92baa7508e19ca315240f77fa8c') + @dicom_test def test_data_fake(self): # Test algorithm for get_data fake_mf = copy(self.MINIMAL_MF) From bf0b018c94a3d5a7c2d7db137d02b60d67a20592 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Fri, 8 Apr 2016 18:33:05 -0700 Subject: [PATCH 4/4] RF: clear characters in test file Whitespace changes only. --- nibabel/nicom/tests/test_dicomwrappers.py | 156 +++++++++++----------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/nibabel/nicom/tests/test_dicomwrappers.py b/nibabel/nicom/tests/test_dicomwrappers.py index 870b771b69..a992a71046 100755 --- a/nibabel/nicom/tests/test_dicomwrappers.py +++ b/nibabel/nicom/tests/test_dicomwrappers.py @@ -5,7 +5,7 @@ import gzip from hashlib import sha1 from decimal import Decimal -from copy import copy +from copy import copy import numpy as np @@ -379,54 +379,54 @@ class Fake(object): setattr(fake_frame, seq_name, [fake_element]) frames.append(fake_frame) return frames - - -def fake_shape_dependents(div_seq, sid_seq=None, sid_dim=None): - """ Make a fake dictionary of data that ``image_shape`` is dependent on. - - Parameters - ---------- - div_seq : list of tuples - list of values to use for the `DimensionIndexValues` of each frame. - sid_seq : list of int - list of values to use for the `StackID` of each frame. - sid_dim : int - the index of the column in 'div_seq' to use as 'sid_seq' - """ - class DimIdxSeqElem(object): - def __init__(self, dip=(0, 0), fgp=None): - self.DimensionIndexPointer = dip - if fgp is not None: - self.FunctionalGroupPointer = fgp - class FrmContSeqElem(object): - def __init__(self, div, sid): - self.DimensionIndexValues = div - self.StackID = sid - class PerFrmFuncGrpSeqElem(object): - def __init__(self, div, sid): - self.FrameContentSequence = [FrmContSeqElem(div, sid)] - # if no StackID values passed in then use the values at index 'sid_dim' in - # the value for DimensionIndexValues for it - if sid_seq is None: - if sid_dim is None: - sid_dim = 0 - sid_seq = [div[sid_dim] for div in div_seq] - # create the DimensionIndexSequence - num_of_frames = len(div_seq) - dim_idx_seq = [DimIdxSeqElem()] * num_of_frames - # add an entry for StackID into the DimensionIndexSequence - if sid_dim is not None: - sid_tag = pydicom.datadict.tag_for_name('StackID') - fcs_tag = pydicom.datadict.tag_for_name('FrameContentSequence') - dim_idx_seq[sid_dim] = DimIdxSeqElem(sid_tag, fcs_tag) - # create the PerFrameFunctionalGroupsSequence - frames = [PerFrmFuncGrpSeqElem(div, sid) - for div, sid in zip(div_seq, sid_seq)] - return {'NumberOfFrames' : num_of_frames, - 'DimensionIndexSequence' : dim_idx_seq, - 'PerFrameFunctionalGroupsSequence' : frames} - - + + +def fake_shape_dependents(div_seq, sid_seq=None, sid_dim=None): + """ Make a fake dictionary of data that ``image_shape`` is dependent on. + + Parameters + ---------- + div_seq : list of tuples + list of values to use for the `DimensionIndexValues` of each frame. + sid_seq : list of int + list of values to use for the `StackID` of each frame. + sid_dim : int + the index of the column in 'div_seq' to use as 'sid_seq' + """ + class DimIdxSeqElem(object): + def __init__(self, dip=(0, 0), fgp=None): + self.DimensionIndexPointer = dip + if fgp is not None: + self.FunctionalGroupPointer = fgp + class FrmContSeqElem(object): + def __init__(self, div, sid): + self.DimensionIndexValues = div + self.StackID = sid + class PerFrmFuncGrpSeqElem(object): + def __init__(self, div, sid): + self.FrameContentSequence = [FrmContSeqElem(div, sid)] + # if no StackID values passed in then use the values at index 'sid_dim' in + # the value for DimensionIndexValues for it + if sid_seq is None: + if sid_dim is None: + sid_dim = 0 + sid_seq = [div[sid_dim] for div in div_seq] + # create the DimensionIndexSequence + num_of_frames = len(div_seq) + dim_idx_seq = [DimIdxSeqElem()] * num_of_frames + # add an entry for StackID into the DimensionIndexSequence + if sid_dim is not None: + sid_tag = pydicom.datadict.tag_for_name('StackID') + fcs_tag = pydicom.datadict.tag_for_name('FrameContentSequence') + dim_idx_seq[sid_dim] = DimIdxSeqElem(sid_tag, fcs_tag) + # create the PerFrameFunctionalGroupsSequence + frames = [PerFrmFuncGrpSeqElem(div, sid) + for div, sid in zip(div_seq, sid_seq)] + return {'NumberOfFrames' : num_of_frames, + 'DimensionIndexSequence' : dim_idx_seq, + 'PerFrameFunctionalGroupsSequence' : frames} + + class TestMultiFrameWrapper(TestCase): # Test MultiframeWrapper MINIMAL_MF = { @@ -453,65 +453,65 @@ def test_shape(self): assert_raises(AssertionError, getattr, dw, 'image_shape') fake_mf['NumberOfFrames'] = 4 # PerFrameFunctionalGroupsSequence does not match NumberOfFrames - assert_raises(AssertionError, getattr, dw, 'image_shape') - # check 3D shape when StackID index is 0 - div_seq = ((1, 1), (1, 2), (1, 3), (1, 4)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) + assert_raises(AssertionError, getattr, dw, 'image_shape') + # check 3D shape when StackID index is 0 + div_seq = ((1, 1), (1, 2), (1, 3), (1, 4)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 4)) # Check stack number matching when StackID index is 0 div_seq = ((1, 1), (1, 2), (1, 3), (2, 4)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) assert_raises(didw.WrapperError, getattr, MFW(fake_mf), 'image_shape') - # Make some fake frame data for 4D when StackID index is 0 + # Make some fake frame data for 4D when StackID index is 0 div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 2, 3)) # Check stack number matching for 4D when StackID index is 0 div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (2, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) assert_raises(didw.WrapperError, getattr, MFW(fake_mf), 'image_shape') # Check indices can be non-contiguous when StackID index is 0 div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 2, 2)) # Check indices can include zero when StackID index is 0 div_seq = ((1, 1, 0), (1, 2, 0), (1, 1, 3), (1, 2, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 2, 2)) - # check 3D shape when there is no StackID index + # check 3D shape when there is no StackID index div_seq = ((1,), (2,), (3,), (4,)) sid_seq = (1, 1, 1, 1) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) + fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 4)) # check 3D stack number matching when there is no StackID index div_seq = ((1,), (2,), (3,), (4,)) sid_seq = (1, 1, 1, 2) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) + fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) assert_raises(didw.WrapperError, getattr, MFW(fake_mf), 'image_shape') - # check 4D shape when there is no StackID index + # check 4D shape when there is no StackID index div_seq = ((1, 1), (2, 1), (1, 2), (2, 2), (1, 3), (2, 3)) sid_seq = (1, 1, 1, 1, 1, 1) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) + fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 2, 3)) # check 4D stack number matching when there is no StackID index div_seq = ((1, 1), (2, 1), (1, 2), (2, 2), (1, 3), (2, 3)) sid_seq = (1, 1, 1, 1, 1, 2) - fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) + fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq)) assert_raises(didw.WrapperError, getattr, MFW(fake_mf), 'image_shape') - # check 3D shape when StackID index is 1 + # check 3D shape when StackID index is 1 div_seq = ((1, 1), (2, 1), (3, 1), (4, 1)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 4)) # Check stack number matching when StackID index is 1 - div_seq = ((1, 1), (2, 1), (3, 2), (4, 1)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) + div_seq = ((1, 1), (2, 1), (3, 2), (4, 1)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) assert_raises(didw.WrapperError, getattr, MFW(fake_mf), 'image_shape') - # Make some fake frame data for 4D when StackID index is 1 + # Make some fake frame data for 4D when StackID index is 1 div_seq = ((1, 1, 1), (2, 1, 1), (1, 1, 2), (2, 1, 2), (1, 1, 3), (2, 1, 3)) - fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) + fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1)) assert_equal(MFW(fake_mf).image_shape, (32, 64, 2, 3)) def test_iop(self): @@ -629,9 +629,9 @@ def test_data_fake(self): assert_raises(didw.WrapperError, dw.get_data) # Make shape and indices fake_mf['Rows'] = 2 - fake_mf['Columns'] = 3 - dim_idxs = ((1, 1), (1, 2), (1, 3), (1, 4)) - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) + fake_mf['Columns'] = 3 + dim_idxs = ((1, 1), (1, 2), (1, 3), (1, 4)) + fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) assert_equal(MFW(fake_mf).image_shape, (2, 3, 4)) # Still fails - no data assert_raises(didw.WrapperError, dw.get_data) @@ -646,9 +646,9 @@ def test_data_fake(self): fake_mf['RescaleSlope'] = 2.0 fake_mf['RescaleIntercept'] = -1 assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1) - # Check slice sorting - dim_idxs = ((1, 4), (1, 2), (1, 3), (1, 1)) - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) + # Check slice sorting + dim_idxs = ((1, 4), (1, 2), (1, 3), (1, 1)) + fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) sorted_data = data[..., [3, 1, 2, 0]] fake_mf['pixel_array'] = np.rollaxis(sorted_data, 2) assert_array_equal(MFW(fake_mf).get_data(), data * 2.0 - 1) @@ -670,7 +670,7 @@ def test_data_fake(self): [1, 2, 1, 2], [1, 3, 1, 2], [1, 1, 1, 2]] - fake_mf.update(fake_shape_dependents(dim_idxs, sid_dim=0)) + 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')