From 4fb633f9b2829c9139311748a80c7abda013ed9c Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 21 Aug 2019 09:56:54 -0400 Subject: [PATCH 01/13] DATA: Add nibabel-data/nitest-dicom submodule --- .gitmodules | 3 +++ nibabel-data/nitest-dicom | 1 + 2 files changed, 4 insertions(+) create mode 160000 nibabel-data/nitest-dicom diff --git a/.gitmodules b/.gitmodules index db0afa268e..a0dc77c8ec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/nibabel-data/nitest-dicom b/nibabel-data/nitest-dicom new file mode 160000 index 0000000000..ff6844f3a5 --- /dev/null +++ b/nibabel-data/nitest-dicom @@ -0,0 +1 @@ +Subproject commit ff6844f3a5ef79974c5809a79314c98fd81693cf From 2fa98728d634c7d5781f8d1cd11520570c993482 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 20 Feb 2019 16:13:23 -0500 Subject: [PATCH 02/13] enh+wip: support for philips dcm w/ derived volume * if DICOM has MR Diffusion Sequence (0018, 9117), discard any derived frames * out correct final shape * TODO: fix get_data() repr --- nibabel/nicom/dicomwrappers.py | 21 ++++++++++++++++----- nibabel/nicom/tests/test_dicomwrappers.py | 8 ++++++++ nibabel/pydicom_compat.py | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 194227c6cf..09dbbfdb03 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -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): @@ -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: @@ -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]) @@ -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) 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) diff --git a/nibabel/nicom/tests/test_dicomwrappers.py b/nibabel/nicom/tests/test_dicomwrappers.py index bea88936d3..abc21f52e5 100755 --- a/nibabel/nicom/tests/test_dicomwrappers.py +++ b/nibabel/nicom/tests/test_dicomwrappers.py @@ -36,6 +36,7 @@ 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(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 @@ -622,6 +623,13 @@ def test_slicethickness_fallback(self): dw = didw.wrapper_from_file(DATA_FILE_EMPTY_ST) assert_equal(dw.voxel_sizes[2], 1.0) + @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 diff --git a/nibabel/pydicom_compat.py b/nibabel/pydicom_compat.py index 7a8658cf47..590d8edf94 100644 --- a/nibabel/pydicom_compat.py +++ b/nibabel/pydicom_compat.py @@ -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 From 7550262405a8715e827e6c93ce74299dc24bc1ea Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 20 Feb 2019 16:47:36 -0500 Subject: [PATCH 03/13] fix: (py)dicom import --- nibabel/pydicom_compat.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nibabel/pydicom_compat.py b/nibabel/pydicom_compat.py index 590d8edf94..4ecc1c121b 100644 --- a/nibabel/pydicom_compat.py +++ b/nibabel/pydicom_compat.py @@ -27,6 +27,7 @@ import dicom as pydicom # Values not imported by default import dicom.values + from dicom.sequence import Sequence except ImportError: try: import pydicom @@ -34,13 +35,13 @@ 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 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 From 3046347ae3cc58949eff88ecc23f5e57c496d432 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 20 Feb 2019 17:24:56 -0500 Subject: [PATCH 04/13] fix: set missing import to None, ensure first frame exists before fetching --- nibabel/nicom/dicomwrappers.py | 2 +- nibabel/pydicom_compat.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 09dbbfdb03..820620196a 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -467,7 +467,7 @@ def __init__(self, dcm_data): 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]): + if self.frames[0] and self.frames[0].get([0x18, 0x9117], None): self.frames = Sequence( frame for frame in self.frames if frame.get([0x18, 0x9117])[0].get([0x18, 0x9075]).value diff --git a/nibabel/pydicom_compat.py b/nibabel/pydicom_compat.py index 4ecc1c121b..e0c390692e 100644 --- a/nibabel/pydicom_compat.py +++ b/nibabel/pydicom_compat.py @@ -21,7 +21,7 @@ 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 From 5608f5cff534ac6a60872627b83aa42723097bd6 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 20 Feb 2019 17:41:55 -0500 Subject: [PATCH 05/13] fix: skip if frame does not have get attribute --- nibabel/nicom/dicomwrappers.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 820620196a..f7b46c3ec0 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -466,14 +466,18 @@ def __init__(self, dcm_data): 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] and self.frames[0].get([0x18, 0x9117], None): - 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: + # DWI image where derived isotropic, ADC or trace volume + # was appended to the series + if self.frames[0].get([0x18, 0x9117], None): + 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) + except AttributeError: + pass try: self.shared = dcm_data.get('SharedFunctionalGroupsSequence')[0] except TypeError: From d1dce02f45606907cac9d5e09e81742d5cadd4ca Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 27 Feb 2019 15:10:01 -0500 Subject: [PATCH 06/13] sty: import specifics separately --- nibabel/pydicom_compat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nibabel/pydicom_compat.py b/nibabel/pydicom_compat.py index e0c390692e..beb787f315 100644 --- a/nibabel/pydicom_compat.py +++ b/nibabel/pydicom_compat.py @@ -25,9 +25,6 @@ try: import dicom as pydicom - # Values not imported by default - import dicom.values - from dicom.sequence import Sequence except ImportError: try: import pydicom @@ -39,6 +36,9 @@ # 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: From ca5cd312a35b3edcd7577f0ea1cc5598d20462a3 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Wed, 27 Feb 2019 15:11:03 -0500 Subject: [PATCH 07/13] fix: allow non-diffusion 5D --- nibabel/nicom/dicomwrappers.py | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index f7b46c3ec0..1cb054e7c7 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -461,23 +461,10 @@ 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.") - try: - # DWI image where derived isotropic, ADC or trace volume - # was appended to the series - if self.frames[0].get([0x18, 0x9117], None): - 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) - except AttributeError: - pass try: self.shared = dcm_data.get('SharedFunctionalGroupsSequence')[0] except TypeError: @@ -515,8 +502,23 @@ 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 - assert len(self.frames) == self._nframes + 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 + # check and remove + self.frames = Sequence( + frame for frame in self.frames if + frame.get([0x18, 0x9117])[0].get([0x18, 0x9075]).value + != 'ISOTROPIC' + ) + n_frames = len(self.frames) + has_derived = True + assert len(self.frames) == n_frames frame_indices = np.array( [frame.FrameContentSequence[0].DimensionIndexValues for frame in self.frames]) @@ -540,15 +542,15 @@ def image_shape(self): # Store frame indices self._frame_indices = frame_indices if n_dim < 4: # 3D volume - return rows, cols, self._nframes + return rows, cols, n_frames # More than 3 dimensions ns_unique = [len(np.unique(row)) for row in self._frame_indices.T] - if len(ns_unique) == 3: + if len(ns_unique) == 3 and has_derived: # derived volume is included ns_unique.pop(1) shape = (rows, cols) + tuple(ns_unique) n_vols = np.prod(shape[3:]) - if self._nframes != n_vols * shape[2]: + if n_frames == self.get('NumberOfFrames') and n_frames != n_vols * shape[2]: raise WrapperError("Calculated shape does not match number of " "frames.") return tuple(shape) From b8f32bf12cb03ac48b32c30717c9d15dfa06a358 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Fri, 1 Mar 2019 17:27:49 -0500 Subject: [PATCH 08/13] fix: remove unnecessary check --- nibabel/nicom/dicomwrappers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 1cb054e7c7..edc57b3c1a 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -550,7 +550,7 @@ def image_shape(self): ns_unique.pop(1) shape = (rows, cols) + tuple(ns_unique) n_vols = np.prod(shape[3:]) - if n_frames == self.get('NumberOfFrames') and n_frames != n_vols * shape[2]: + if n_frames != n_vols * shape[2]: raise WrapperError("Calculated shape does not match number of " "frames.") return tuple(shape) From 21af99d05c37a492f46a4df37d114afcecb162c1 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 14 Mar 2019 12:33:50 -0400 Subject: [PATCH 09/13] rf: better error handling, reflect change on frame_indices --- nibabel/nicom/dicomwrappers.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index edc57b3c1a..75a997808f 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -14,6 +14,7 @@ from __future__ import division import operator +import warnings import numpy as np @@ -511,13 +512,20 @@ def image_shape(self): if hasattr(first_frame, 'get') and first_frame.get([0x18, 0x9117]): # DWI image may include derived isotropic, ADC or trace volume # check and remove - self.frames = Sequence( - frame for frame in self.frames if - frame.get([0x18, 0x9117])[0].get([0x18, 0x9075]).value - != 'ISOTROPIC' - ) - n_frames = len(self.frames) - has_derived = True + try: + self.frames = Sequence( + frame for frame in self.frames if + frame.MRDiffusionSequence[0].DiffusionDirectionality + != 'ISOTROPIC' + ) + n_frames = len(self.frames) + has_derived = True + except IndexError: + # Sequence tag is found but missing items! + raise WrapperError("Diffusion file missing information") + except AttributeError: + # DiffusionDirectionality tag is not required + pass assert len(self.frames) == n_frames frame_indices = np.array( [frame.FrameContentSequence[0].DimensionIndexValues @@ -536,6 +544,9 @@ 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) + if has_derived: + # derived volume is included + frame_indices = np.delete(frame_indices, 1, axis=1) # account for the 2 additional dimensions (row and column) not included # in the indices n_dim = frame_indices.shape[1] + 2 @@ -545,9 +556,6 @@ def image_shape(self): return rows, cols, n_frames # More than 3 dimensions ns_unique = [len(np.unique(row)) for row in self._frame_indices.T] - if len(ns_unique) == 3 and has_derived: - # derived volume is included - ns_unique.pop(1) shape = (rows, cols) + tuple(ns_unique) n_vols = np.prod(shape[3:]) if n_frames != n_vols * shape[2]: From 1040c519963bd13591dd33ad4ba7dddad8d8aee7 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Thu, 14 Mar 2019 12:46:52 -0400 Subject: [PATCH 10/13] add: warn if frames were altered --- nibabel/nicom/dicomwrappers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 75a997808f..ebc1402d28 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -511,21 +511,23 @@ def image_shape(self): has_derived = False if hasattr(first_frame, 'get') and first_frame.get([0x18, 0x9117]): # DWI image may include derived isotropic, ADC or trace volume - # check and remove try: self.frames = Sequence( frame for frame in self.frames if frame.MRDiffusionSequence[0].DiffusionDirectionality != 'ISOTROPIC' ) - n_frames = len(self.frames) - has_derived = True + if n_frames != len(self.frames): + warnings.warn("Derived images found and removed") + n_frames = len(self.frames) + has_derived = True except IndexError: # Sequence tag is found but missing items! raise WrapperError("Diffusion file missing information") except AttributeError: # DiffusionDirectionality tag is not required pass + assert len(self.frames) == n_frames frame_indices = np.array( [frame.FrameContentSequence[0].DimensionIndexValues From 8e2bd0857a9f1db65bb5d03f0db26325531ef896 Mon Sep 17 00:00:00 2001 From: mathiasg Date: Mon, 18 Mar 2019 11:10:48 -0400 Subject: [PATCH 11/13] rf: condense frame checking try block --- nibabel/nicom/dicomwrappers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index ebc1402d28..115254d45d 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -517,16 +517,17 @@ def image_shape(self): frame.MRDiffusionSequence[0].DiffusionDirectionality != 'ISOTROPIC' ) - if n_frames != len(self.frames): - warnings.warn("Derived images found and removed") - n_frames = len(self.frames) - has_derived = True 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( From caa6df963e745c48d679d4d7a702420d59906eec Mon Sep 17 00:00:00 2001 From: mathiasg Date: Mon, 18 Mar 2019 11:57:14 -0400 Subject: [PATCH 12/13] fix: ensure derived index is removed --- nibabel/nicom/dicomwrappers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nibabel/nicom/dicomwrappers.py b/nibabel/nicom/dicomwrappers.py index 115254d45d..14a92e1c5f 100755 --- a/nibabel/nicom/dicomwrappers.py +++ b/nibabel/nicom/dicomwrappers.py @@ -547,9 +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 - frame_indices = np.delete(frame_indices, 1, axis=1) + 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 From 265511e58541d58d8319c8ca2de8270e75660021 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Wed, 21 Aug 2019 10:19:44 -0400 Subject: [PATCH 13/13] TEST: Update test DICOM file location --- nibabel/nicom/tests/test_dicomwrappers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nibabel/nicom/tests/test_dicomwrappers.py b/nibabel/nicom/tests/test_dicomwrappers.py index abc21f52e5..1ebf464d0e 100755 --- a/nibabel/nicom/tests/test_dicomwrappers.py +++ b/nibabel/nicom/tests/test_dicomwrappers.py @@ -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') @@ -36,7 +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(IO_DATA_PATH, '4d_multiframe_with_derived.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 @@ -624,6 +626,7 @@ def test_slicethickness_fallback(self): 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