Skip to content

Commit 1563b7a

Browse files
committed
Fix storage of image instances via file client
1 parent 2b396f2 commit 1563b7a

File tree

2 files changed

+73
-6
lines changed

2 files changed

+73
-6
lines changed

src/dicomweb_client/file.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1997,16 +1997,27 @@ def insert_instances(
19971997
transfer_syntax_uid=transfer_syntax_uid
19981998
)
19991999
dcmwrite(b, ds, write_like_original=False)
2000+
# The data set needs to be read back (at least partially)
2001+
# to determine the offset of the Pixel Data element. This
2002+
# is required to either read or build the Basic Offset Table
2003+
# for image instances to allow for fast retrieval of frames.
2004+
fp.seek(0)
2005+
# One needs to specify at least one tag to satisfy mypy.
2006+
tag = tag_for_keyword('PatientID')
2007+
dcmread(
2008+
fp,
2009+
specific_tags=[tag], # type: ignore
2010+
stop_before_pixels=True
2011+
)
2012+
pixel_data_offset = fp.tell()
2013+
20002014
pixel_data_element: Union[DataElement, None] = None
20012015
for pixel_data_tag in _PIXEL_DATA_TAGS:
20022016
try:
20032017
pixel_data_element = ds[pixel_data_tag]
20042018
except KeyError:
20052019
continue
20062020
if pixel_data_element is not None:
2007-
pixel_data_offset = pixel_data_element.file_tell
2008-
if pixel_data_offset is None:
2009-
continue
20102021
fp.seek(pixel_data_offset, 0)
20112022
first_frame_offset, bot = _get_frame_offsets(
20122023
fp,
@@ -2017,7 +2028,8 @@ def insert_instances(
20172028
np.product([
20182029
ds.Rows,
20192030
ds.Columns,
2020-
ds.SamplesPerPixel])
2031+
ds.SamplesPerPixel
2032+
])
20212033
),
20222034
transfer_syntax_uid=ds.file_meta.TransferSyntaxUID,
20232035
bits_allocated=ds.BitsAllocated
@@ -2028,14 +2040,14 @@ def insert_instances(
20282040

20292041
fp.seek(0)
20302042
file_content = fp.read()
2043+
20312044
instances[sop_instance_uid] = (
20322045
*instance_metadata,
20332046
str(ds.file_meta.TransferSyntaxUID),
20342047
str(rel_file_path),
20352048
first_frame_offset,
20362049
bot,
20372050
)
2038-
20392051
file_path = self.base_dir.joinpath(rel_file_path)
20402052
successes.append((ds, file_path, file_content))
20412053
except Exception as error:
@@ -2899,6 +2911,7 @@ def retrieve_instance_rendered(
28992911
frame_index=frame_index,
29002912
transfer_syntax_uid=transfer_syntax_uid
29012913
)
2914+
image_file_pointer.close()
29022915

29032916
# TODO: ICC Profile
29042917
codec_name, codec_kwargs = self._get_image_codec_parameters(
@@ -3187,6 +3200,8 @@ def iter_instance_frames(
31873200
else:
31883201
yield frame
31893202

3203+
image_file_pointer.close()
3204+
31903205
def retrieve_instance_frames(
31913206
self,
31923207
study_instance_uid: str,
@@ -3313,6 +3328,7 @@ def retrieve_instance_frames(
33133328
else:
33143329
retrieved_frames.append(frame)
33153330

3331+
image_file_pointer.close()
33163332
return retrieved_frames
33173333

33183334
def retrieve_instance_frames_rendered(
@@ -3383,6 +3399,7 @@ def retrieve_instance_frames_rendered(
33833399
frame_index=frame_index,
33843400
transfer_syntax_uid=transfer_syntax_uid
33853401
)
3402+
image_file_pointer.close()
33863403

33873404
# TODO: ICC Profile
33883405
codec_name, codec_kwargs = self._get_image_codec_parameters(
@@ -3573,6 +3590,9 @@ def store_instances(
35733590
response = Dataset()
35743591
response.RetrieveURL = None
35753592

3593+
if len(successes) == 0 and len(failures) == 0:
3594+
raise RuntimeError('Failed to store instances.')
3595+
35763596
if len(successes) > 0:
35773597
response.ReferencedSOPSequence = []
35783598
for ds, file_path, file_content in successes:

tests/test_file.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
from pydicom.dataset import Dataset
1+
import numpy as np
22
import pytest
3+
from pydicom.dataset import Dataset, FileMetaDataset
4+
from pydicom.uid import (
5+
ExplicitVRLittleEndian,
6+
generate_uid,
7+
VLWholeSlideMicroscopyImageStorage,
8+
)
39

410

511
STUDY_ATTRIBUTES = {
@@ -274,6 +280,7 @@ def test_retrieve_instance_frames(file_client):
274280

275281
for test_frame in frames:
276282
assert isinstance(test_frame, bytes)
283+
assert len(test_frame) > 0
277284

278285

279286
def test_retrieve_instance_frames_rendered(file_client):
@@ -285,3 +292,43 @@ def test_retrieve_instance_frames_rendered(file_client):
285292
media_types=('image/png', )
286293
)
287294
assert isinstance(frame, bytes)
295+
assert len(frame) > 0
296+
297+
298+
def test_store_instances(file_client):
299+
dataset = Dataset()
300+
dataset.PatientID = None
301+
dataset.PatientSex = None
302+
dataset.PatientBirthDate = None
303+
dataset.StudyInstanceUID = generate_uid()
304+
dataset.StudyID = None
305+
dataset.StudyDate = None
306+
dataset.StudyTime = None
307+
dataset.ReferringPhysicianName = ''
308+
dataset.SeriesInstanceUID = generate_uid()
309+
dataset.SeriesNumber = 1
310+
dataset.Modality = 'SM'
311+
dataset.AccessionNumber = None
312+
dataset.SOPInstanceUID = generate_uid()
313+
dataset.SOPClassUID = VLWholeSlideMicroscopyImageStorage
314+
dataset.InstanceNumber = 1
315+
dataset.Rows = 10
316+
dataset.Columns = 10
317+
dataset.SamplesPerPixel = 3
318+
dataset.BitsAllocated = 8
319+
dataset.BitsStored = 8
320+
dataset.HighBit = 7
321+
dataset.PixelData = np.zeros(
322+
(dataset.Rows, dataset.Columns, dataset.SamplesPerPixel),
323+
dtype=np.dtype(f'uint{dataset.BitsAllocated}')
324+
).tobytes()
325+
dataset.file_meta = FileMetaDataset()
326+
dataset.file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
327+
328+
response = file_client.store_instances([dataset])
329+
assert hasattr(response, 'ReferencedSOPSequence')
330+
assert not hasattr(response, 'FailedSOPSequence')
331+
assert len(response.ReferencedSOPSequence) == 1
332+
ref_item = response.ReferencedSOPSequence[0]
333+
assert ref_item.ReferencedSOPInstanceUID == dataset.SOPInstanceUID
334+
assert ref_item.ReferencedSOPClassUID == dataset.SOPClassUID

0 commit comments

Comments
 (0)