Skip to content

Commit

Permalink
Improvements to refrenced instance storage
Browse files Browse the repository at this point in the history
  • Loading branch information
CPBridge committed Jan 17, 2025
1 parent aab02ab commit e323f87
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 44 deletions.
102 changes: 69 additions & 33 deletions src/highdicom/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1624,8 +1624,15 @@ def _build_luts_single_frame(self) -> None:
self._is_tiled_full = False
self._dim_ind_pointers = []
self._dim_ind_col_names = {}
self._single_source_frame_per_frame = True
self._single_source_frame_per_frame = False
self._locations_preserved = None
self._missing_reference_instances = []
referenced_uids = self._get_ref_instance_uids()
all_referenced_sops = {uids[2] for uids in referenced_uids}

col_defs = []
col_defs.append('FrameNumber INTEGER PRIMARY KEY')
col_data = [[1]]

if 'SourceImageSequence' in self:
self._single_source_frame_per_frame = (
Expand All @@ -1647,10 +1654,21 @@ def _build_luts_single_frame(self) -> None:
self._locations_preserved = (
SpatialLocationsPreservedValues.NO
)

col_defs = []
col_defs.append('FrameNumber INTEGER PRIMARY KEY')
col_data = [[1]]
if self._single_source_frame_per_frame:
ref_frame = self.SourceImageSequence[0].get('ReferencedFrameNumber')
ref_uid = self.SourceImageSequence[0].ReferencedSOPInstanceUID
if ref_uid not in all_referenced_sops:
self._missing_reference_instances.append(ref_uid)
col_defs.append('ReferencedFrameNumber INTEGER')
col_defs.append('ReferencedSOPInstanceUID VARCHAR NOT NULL')
col_defs.append(
'FOREIGN KEY(ReferencedSOPInstanceUID) '
'REFERENCES InstanceUIDs(SOPInstanceUID)'
)
col_data += [
[ref_frame],
[ref_uid],
]

if (
self._coordinate_system is not None and
Expand Down Expand Up @@ -1791,6 +1809,7 @@ def _build_luts_multiframe(self) -> None:
)

self._single_source_frame_per_frame = True
self._missing_reference_instances = []

if self._is_tiled_full:
# With TILED_FULL, there is no PerFrameFunctionalGroupsSequence,
Expand Down Expand Up @@ -1964,13 +1983,8 @@ def _build_luts_multiframe(self) -> None:
else:
ref_instance_uid = frame_source_instances[0]
if ref_instance_uid not in all_referenced_sops:
raise AttributeError(
f'SOP instance {ref_instance_uid} referenced in '
'the source image sequence is not included in the '
'Referenced Series Sequence or Studies Containing '
'Other Referenced Instances Sequence. This is an '
'error with the integrity of the '
'object.'
self._missing_reference_instances.append(
ref_instance_uid
)
referenced_instances.append(ref_instance_uid)
referenced_frames.append(frame_source_frames[0])
Expand Down Expand Up @@ -2247,28 +2261,41 @@ def _get_ref_instance_uids(self) -> List[Tuple[str, str, str]]:
"""
instance_data = []
if hasattr(self, 'ReferencedSeriesSequence'):
for ref_series in self.ReferencedSeriesSequence:
for ref_ins in ref_series.ReferencedInstanceSequence:
instance_data.append(
(
self.StudyInstanceUID,
ref_series.SeriesInstanceUID,
ref_ins.ReferencedSOPInstanceUID
)
)
other_studies_kw = 'StudiesContainingOtherReferencedInstancesSequence'
if hasattr(self, other_studies_kw):
for ref_study in getattr(self, other_studies_kw):
for ref_series in ref_study.ReferencedSeriesSequence:
for ref_ins in ref_series.ReferencedInstanceSequence:
instance_data.append(
(
ref_study.StudyInstanceUID,
ref_series.SeriesInstanceUID,
ref_ins.ReferencedSOPInstanceUID,

def _include_sequence(seq):
for ds in seq:
if hasattr(ds, 'ReferencedSeriesSequence'):
for ref_series in ds.ReferencedSeriesSequence:

# Two different sequences are used here, depending on
# which particular top sequence level sequence we are
# in
if 'ReferencedSOPSequence' in ref_series:
instance_sequence = (
ref_series.ReferencedSOPSequence
)
else:
instance_sequence = (
ref_series.ReferencedInstanceSequence
)
)

for ref_ins in instance_sequence:
instance_data.append(
(
ds.StudyInstanceUID,
ref_series.SeriesInstanceUID,
ref_ins.ReferencedSOPInstanceUID
)
)

# Include the "main" referenced series sequence
_include_sequence([self])
for kw in [
'StudiesContainingOtherReferencedInstancesSequence',
'SourceImageEvidenceSequence'
]:
if hasattr(self, kw):
_include_sequence(getattr(self, kw))

# There shouldn't be duplicates here, but there's no explicit rule
# preventing it.
Expand Down Expand Up @@ -2459,6 +2486,15 @@ def get_source_image_uids(self) -> List[Tuple[UID, UID, UID]]:
for every image instance referenced in the image.
"""
for ref_instance_uid in self._missing_reference_instances:
logger.warning(
f'SOP instances {ref_instance_uid} referenced in the source '
'image sequence is not included in the Referenced Series '
'Sequence, Source Image Evidence Sequence, or Studies Containing '
'Other Referenced Instances Sequence. This is an error with the '
'integrity of the object. This instance will be omitted from '
'the returned list. '
)
cur = self._db_con.cursor()
res = cur.execute(
'SELECT StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID '
Expand Down
6 changes: 3 additions & 3 deletions src/highdicom/spatial.py
Original file line number Diff line number Diff line change
Expand Up @@ -1193,15 +1193,15 @@ def create_affine_matrix_from_attributes(
coordinate system.
""" # noqa: E501
if not isinstance(image_position, Sequence):
if not isinstance(image_position, (Sequence, np.ndarray)):
raise TypeError('Argument "image_position" must be a sequence.')
if len(image_position) != 3:
raise ValueError('Argument "image_position" must have length 3.')
if not isinstance(image_orientation, Sequence):
if not isinstance(image_orientation, (Sequence, np.ndarray)):
raise TypeError('Argument "image_orientation" must be a sequence.')
if len(image_orientation) != 6:
raise ValueError('Argument "image_orientation" must have length 6.')
if not isinstance(pixel_spacing, Sequence):
if not isinstance(pixel_spacing, (Sequence, np.ndarray)):
raise TypeError('Argument "pixel_spacing" must be a sequence.')
if len(pixel_spacing) != 2:
raise ValueError('Argument "pixel_spacing" must have length 2.')
Expand Down
27 changes: 19 additions & 8 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,25 @@ def find_readable_images() -> list[str]:

# Various files are not expected to work and should be excluded
exclusions = [
"badVR.dcm", # cannot be read due to bad VFR
"MR_truncated.dcm", # pixel data is truncated
"liver_1frame.dcm", # missing number of frames
"JPEG2000-embedded-sequence-delimiter.dcm", # pydicom cannot decode pixels
"image_dfl.dcm", # deflated transfer syntax cannot be read lazily
"JPEG-lossy.dcm", # pydicom cannot decode pixels
"TINY_ALPHA", # no pixels
"SC_rgb_jpeg.dcm", # messed up transder syntax
# cannot be read due to bad VFR
"badVR.dcm",
# pixel data is truncated
"MR_truncated.dcm",
# missing number of frames
"liver_1frame.dcm",
# pydicom cannot decode pixels
"JPEG2000-embedded-sequence-delimiter.dcm",
# deflated transfer syntax cannot be read lazily
"image_dfl.dcm",
# pydicom cannot decode pixels
"JPEG-lossy.dcm",
# no pixels
"TINY_ALPHA",
# messed up transfer syntax
"SC_rgb_jpeg.dcm",
# Incorrect source image sequence. This can hopefully be added back
# after https://github.com/pydicom/pydicom/pull/2204
"SC_rgb_small_odd.dcm",
]

files_to_use = []
Expand Down

0 comments on commit e323f87

Please # to comment.