Skip to content

Commit d5995db

Browse files
authored
Merge pull request #1387 from moloney/dw-fixes
BF+TST: Fix 'frame_order' for single frame files
2 parents 59733ff + b1eb9b0 commit d5995db

File tree

2 files changed

+52
-24
lines changed

2 files changed

+52
-24
lines changed

nibabel/nicom/dicomwrappers.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,8 @@ def b_vector(self):
532532
class FrameFilter:
533533
"""Base class for defining how to filter out (ignore) frames from a multiframe file
534534
535-
It is guaranteed that the `applies` method will on a dataset before the `keep` method
536-
is called on any of the frames inside.
535+
It is guaranteed that the `applies` method will called on a dataset before the `keep`
536+
method is called on any of the frames inside.
537537
"""
538538

539539
def applies(self, dcm_wrp) -> bool:
@@ -549,7 +549,7 @@ class FilterMultiStack(FrameFilter):
549549
"""Filter out all but one `StackID`"""
550550

551551
def __init__(self, keep_id=None):
552-
self._keep_id = keep_id
552+
self._keep_id = str(keep_id) if keep_id is not None else None
553553

554554
def applies(self, dcm_wrp) -> bool:
555555
first_fcs = dcm_wrp.frames[0].get('FrameContentSequence', (None,))[0]
@@ -562,10 +562,16 @@ def applies(self, dcm_wrp) -> bool:
562562
self._selected = self._keep_id
563563
if len(stack_ids) > 1:
564564
if self._keep_id is None:
565+
try:
566+
sids = [int(x) for x in stack_ids]
567+
except:
568+
self._selected = dcm_wrp.frames[0].FrameContentSequence[0].StackID
569+
else:
570+
self._selected = str(min(sids))
565571
warnings.warn(
566-
'A multi-stack file was passed without an explicit filter, just using lowest StackID'
572+
'A multi-stack file was passed without an explicit filter, '
573+
f'using StackID = {self._selected}'
567574
)
568-
self._selected = min(stack_ids)
569575
return True
570576
return False
571577

@@ -707,6 +713,7 @@ def vendor(self):
707713

708714
@cached_property
709715
def frame_order(self):
716+
"""The ordering of frames to make nD array"""
710717
if self._frame_indices is None:
711718
_ = self.image_shape
712719
return np.lexsort(self._frame_indices.T)
@@ -742,14 +749,20 @@ def image_shape(self):
742749
rows, cols = self.get('Rows'), self.get('Columns')
743750
if None in (rows, cols):
744751
raise WrapperError('Rows and/or Columns are empty.')
745-
# Check number of frames, initialize array of frame indices
752+
# Check number of frames and handle single frame files
746753
n_frames = len(self.frames)
754+
if n_frames == 1:
755+
self._frame_indices = np.array([[0]], dtype=np.int64)
756+
return (rows, cols)
757+
# Initialize array of frame indices
747758
try:
748759
frame_indices = np.array(
749760
[frame.FrameContentSequence[0].DimensionIndexValues for frame in self.frames]
750761
)
751762
except AttributeError:
752763
raise WrapperError("Can't find frame 'DimensionIndexValues'")
764+
if len(frame_indices.shape) == 1:
765+
frame_indices = frame_indices.reshape(frame_indices.shape + (1,))
753766
# Determine the shape and which indices to use
754767
shape = [rows, cols]
755768
curr_parts = n_frames

nibabel/nicom/tests/test_dicomwrappers.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -427,13 +427,6 @@ def fake_shape_dependents(
427427
generate ipp values so slice location is negatively correlated with slice index
428428
"""
429429

430-
class PrintBase:
431-
def __repr__(self):
432-
attr_strs = [
433-
f'{attr}={getattr(self, attr)}' for attr in dir(self) if attr[0].isupper()
434-
]
435-
return f"{self.__class__.__name__}({', '.join(attr_strs)})"
436-
437430
class DimIdxSeqElem(pydicom.Dataset):
438431
def __init__(self, dip=(0, 0), fgp=None):
439432
super().__init__()
@@ -444,8 +437,8 @@ def __init__(self, dip=(0, 0), fgp=None):
444437
class FrmContSeqElem(pydicom.Dataset):
445438
def __init__(self, div, sid):
446439
super().__init__()
447-
self.DimensionIndexValues = div
448-
self.StackID = sid
440+
self.DimensionIndexValues = list(div)
441+
self.StackID = str(sid)
449442

450443
class PlnPosSeqElem(pydicom.Dataset):
451444
def __init__(self, ipp):
@@ -545,17 +538,28 @@ def test_shape(self):
545538
with pytest.raises(didw.WrapperError):
546539
dw.image_shape
547540
fake_mf.Rows = 32
548-
# No frame data raises WrapperError
541+
# Single frame doesn't need dimension index values
542+
assert dw.image_shape == (32, 64)
543+
assert len(dw.frame_order) == 1
544+
assert dw.frame_order[0] == 0
545+
# Multiple frames do require dimension index values
546+
fake_mf.PerFrameFunctionalGroupsSequence = [pydicom.Dataset(), pydicom.Dataset()]
549547
with pytest.raises(didw.WrapperError):
550-
dw.image_shape
548+
MFW(fake_mf).image_shape
551549
# check 2D shape with StackID index is 0
552550
div_seq = ((1, 1),)
553551
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0))
554-
assert MFW(fake_mf).image_shape == (32, 64)
552+
dw = MFW(fake_mf)
553+
assert dw.image_shape == (32, 64)
554+
assert len(dw.frame_order) == 1
555+
assert dw.frame_order[0] == 0
555556
# Check 2D shape with extraneous extra indices
556557
div_seq = ((1, 1, 2),)
557558
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0))
558-
assert MFW(fake_mf).image_shape == (32, 64)
559+
dw = MFW(fake_mf)
560+
assert dw.image_shape == (32, 64)
561+
assert len(dw.frame_order) == 1
562+
assert dw.frame_order[0] == 0
559563
# Check 2D plus time
560564
div_seq = ((1, 1, 1), (1, 1, 2), (1, 1, 3))
561565
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0))
@@ -569,7 +573,7 @@ def test_shape(self):
569573
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0))
570574
with pytest.warns(
571575
UserWarning,
572-
match='A multi-stack file was passed without an explicit filter, just using lowest StackID',
576+
match='A multi-stack file was passed without an explicit filter,',
573577
):
574578
assert MFW(fake_mf).image_shape == (32, 64, 3)
575579
# No warning if we expclitly select that StackID to keep
@@ -581,7 +585,7 @@ def test_shape(self):
581585
fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq))
582586
with pytest.warns(
583587
UserWarning,
584-
match='A multi-stack file was passed without an explicit filter, just using lowest StackID',
588+
match='A multi-stack file was passed without an explicit filter,',
585589
):
586590
assert MFW(fake_mf).image_shape == (32, 64, 3)
587591
# No warning if we expclitly select that StackID to keep
@@ -590,6 +594,17 @@ def test_shape(self):
590594
# Check for error when explicitly requested StackID is missing
591595
with pytest.raises(didw.WrapperError):
592596
MFW(fake_mf, frame_filters=(didw.FilterMultiStack(3),))
597+
# StackID can be a string
598+
div_seq = ((1,), (2,), (3,), (4,))
599+
sid_seq = ('a', 'a', 'a', 'b')
600+
fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq))
601+
with pytest.warns(
602+
UserWarning,
603+
match='A multi-stack file was passed without an explicit filter,',
604+
):
605+
assert MFW(fake_mf).image_shape == (32, 64, 3)
606+
assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack('a'),)).image_shape == (32, 64, 3)
607+
assert MFW(fake_mf, frame_filters=(didw.FilterMultiStack('b'),)).image_shape == (32, 64)
593608
# Make some fake frame data for 4D when StackID index is 0
594609
div_seq = ((1, 1, 1), (1, 2, 1), (1, 1, 2), (1, 2, 2), (1, 1, 3), (1, 2, 3))
595610
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0))
@@ -599,7 +614,7 @@ def test_shape(self):
599614
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=0))
600615
with pytest.warns(
601616
UserWarning,
602-
match='A multi-stack file was passed without an explicit filter, just using lowest StackID',
617+
match='A multi-stack file was passed without an explicit filter,',
603618
):
604619
with pytest.raises(didw.WrapperError):
605620
MFW(fake_mf).image_shape
@@ -638,7 +653,7 @@ def test_shape(self):
638653
fake_mf.update(fake_shape_dependents(div_seq, sid_seq=sid_seq))
639654
with pytest.warns(
640655
UserWarning,
641-
match='A multi-stack file was passed without an explicit filter, just using lowest StackID',
656+
match='A multi-stack file was passed without an explicit filter,',
642657
):
643658
with pytest.raises(didw.WrapperError):
644659
MFW(fake_mf).image_shape
@@ -651,7 +666,7 @@ def test_shape(self):
651666
fake_mf.update(fake_shape_dependents(div_seq, sid_dim=1))
652667
with pytest.warns(
653668
UserWarning,
654-
match='A multi-stack file was passed without an explicit filter, just using lowest StackID',
669+
match='A multi-stack file was passed without an explicit filter,',
655670
):
656671
assert MFW(fake_mf).image_shape == (32, 64, 3)
657672
# Make some fake frame data for 4D when StackID index is 1

0 commit comments

Comments
 (0)