Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ dependencies = [
"requests>=2.18",
"retrying>=1.3.3",
"Pillow>=8.3",
"pydicom>=2.2",
"pydicom>=3.0.0",
"typing-extensions>=4.0; python_version < '3.8.0'",
]

Expand Down
47 changes: 28 additions & 19 deletions src/dicomweb_client/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
from pydicom.datadict import dictionary_VR, keyword_for_tag, tag_for_keyword
from pydicom.dataelem import DataElement
from pydicom.dataset import Dataset, FileMetaDataset
from pydicom.encaps import encapsulate, get_frame_offsets
from pydicom.encaps import encapsulate, parse_basic_offsets
from pydicom.errors import InvalidDicomError
from pydicom.filebase import DicomFileLike
from pydicom.filereader import data_element_offset_to_value, dcmread
Expand All @@ -45,7 +45,7 @@
Tag,
TupleTag,
)
from pydicom.uid import UID, RLELossless
from pydicom.uid import UID
from pydicom.valuerep import DA, DT, TM

from dicomweb_client.uri import build_query_string, parse_query_parameters
Expand Down Expand Up @@ -350,14 +350,14 @@
fp.is_implicit_VR, 'OB'
)
fp.seek(pixel_data_element_value_offset - 4, 1)
is_empty, offsets = get_frame_offsets(fp)
offsets = parse_basic_offsets(fp)
return np.array(offsets, dtype=np.uint32)


def _build_bot(
fp: DicomFileLike,
number_of_frames: int,
transfer_syntax_uid: str

Check warning on line 360 in src/dicomweb_client/file.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unused function parameter "transfer_syntax_uid".

See more on https://sonarcloud.io/project/issues?id=ImagingDataCommons_dicomweb-client&issues=AZ0cSdrsrOcQDbT7xTUv&open=AZ0cSdrsrOcQDbT7xTUv&pullRequest=120
) -> np.ndarray:
"""Build a Basic Offset Table (BOT) for an encapsulated Pixel Data element.

Expand Down Expand Up @@ -393,8 +393,13 @@

"""
initial_position = fp.tell()
offset_values = []
current_offset = 0

# We will keep two lists, one of all fragment boundaries (regardless of
# whether or not they are frame boundaries) and the other of just those
# fragment boundaries that are known to be frame boundaries (as identified
# by JPEG start markers).
frame_offset_values = []
fragment_offset_values = []
i = 0
while True:
frame_position = fp.tell()
Expand All @@ -421,31 +426,35 @@
f'Length of Frame item #{i} is zero.'
)

current_offset = frame_position - initial_position
fragment_offset_values.append(current_offset)

first_two_bytes = fp.read(2)
if not fp.is_little_endian:
first_two_bytes = first_two_bytes[::-1]

current_offset = frame_position - initial_position
if transfer_syntax_uid == RLELossless:
offset_values.append(current_offset)
else:
# In case of fragmentation, we only want to get the offsets to the
# first fragment of a given frame. We can identify those based on
# the JPEG and JPEG 2000 markers that should be found at the
# beginning and end of the compressed byte stream.
if first_two_bytes in _START_MARKERS:
offset_values.append(current_offset)
# In case of fragmentation, we only want to get the offsets to the
# first fragment of a given frame. We can identify those based on
# the JPEG and JPEG 2000 markers that should be found at the
# beginning and end of the compressed byte stream.
if first_two_bytes in _START_MARKERS:
frame_offset_values.append(current_offset)

i += 1
fp.seek(length - 2, 1) # minus the first two bytes

if len(offset_values) != number_of_frames:
if len(frame_offset_values) == number_of_frames:
basic_offset_table = frame_offset_values
elif len(fragment_offset_values) == number_of_frames:
# This covers RLE and others that have no frame markers but have a
# single fragment per frame
basic_offset_table = fragment_offset_values
else:
raise ValueError(
'Number of found frame items does not match specified number '
f'of frames: {len(offset_values)} instead of {number_of_frames}.'
f'of frames: {len(frame_offset_values)} instead of '
f'{number_of_frames}.'
)
else:
basic_offset_table = offset_values

fp.seek(initial_position, 0)
return np.array(basic_offset_table, dtype=np.uint32)
Expand Down
23 changes: 22 additions & 1 deletion src/dicomweb_client/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)

import pydicom
from pydicom.valuerep import INT_VR, FLOAT_VR, VR
import requests
import retrying

Expand All @@ -42,7 +43,7 @@
logger = logging.getLogger(__name__)


def _load_xml_dataset(dataset: Element) -> pydicom.dataset.Dataset:

Check failure on line 46 in src/dicomweb_client/web.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 33 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=ImagingDataCommons_dicomweb-client&issues=AZqPxmEQdXvA_UAOmo4i&open=AZqPxmEQdXvA_UAOmo4i&pullRequest=120
"""Load DICOM Data Set in DICOM XML format.

Parameters
Expand All @@ -60,7 +61,7 @@
for element in dataset:
keyword = element.attrib['keyword']
vr = element.attrib['vr']
value: Optional[Union[List[Any], str]]
value: Optional[Union[List[Any], str, int, float]]
if vr == 'SQ':
value = [
_load_xml_dataset(item)
Expand All @@ -74,6 +75,26 @@
value = [v.text.strip() for v in value]
else:
value = None

# Convert string values to appropriate Python types for
# numeric VRs to satisfy pydicom 3.0+ stricter type validation
if value is not None:
try:
vr_enum = VR(vr)
if vr_enum in INT_VR:
if isinstance(value, list):
value = [int(v) for v in value]
else:
value = int(value)
elif vr_enum in FLOAT_VR:
if isinstance(value, list):
value = [float(v) for v in value]
else:
value = float(value)
except ValueError:
# VR not recognized, leave value as-is
pass

setattr(ds, keyword, value)
return ds

Expand Down
32 changes: 21 additions & 11 deletions tests/test_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -776,10 +776,20 @@ def test_retrieve_instance_singlepart(httpserver, client, cache_dir):
cache_filename = str(cache_dir.joinpath('file.dcm'))
with open(cache_filename, 'rb') as f:
data = f.read()
media_type = 'application/dicom'
boundary = 'boundary'
headers = {
'content-type': 'application/dicom'
'content-type': (
'multipart/related; '
f'type="{media_type}"; '
f'boundary="{boundary}"'
),
}
httpserver.serve_content(content=data, code=200, headers=headers)
message = DICOMwebClient._encode_multipart_message(
content=[data],
content_type=headers['content-type']
)
httpserver.serve_content(content=message, code=200, headers=headers)
study_instance_uid = '1.2.3'
series_instance_uid = '1.2.4'
sop_instance_uid = '1.2.5'
Expand Down Expand Up @@ -1285,8 +1295,8 @@ def test_retrieve_instance_frames_rendered_png(httpserver, client, cache_dir):

def test_store_instance_error_with_retries(httpserver, client, cache_dir):
dataset = pydicom.Dataset.from_json({})
dataset.is_little_endian = True
dataset.is_implicit_VR = True
dataset.file_meta = pydicom.dataset.FileMetaDataset()
dataset.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
max_attempts = 2
client.set_http_retry_params(
retry=True,
Expand All @@ -1311,8 +1321,8 @@ def test_store_instance_error_with_retries_and_additional_params(
httpserver, client, cache_dir
):
dataset = pydicom.Dataset.from_json({})
dataset.is_little_endian = True
dataset.is_implicit_VR = True
dataset.file_meta = pydicom.dataset.FileMetaDataset()
dataset.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
max_attempts = 2
client.set_http_retry_params(
retry=True,
Expand All @@ -1339,8 +1349,8 @@ def test_store_instance_error_with_retries_and_additional_params(

def test_store_instance_error_with_no_retries(httpserver, client, cache_dir):
dataset = pydicom.Dataset.from_json({})
dataset.is_little_endian = True
dataset.is_implicit_VR = True
dataset.file_meta = pydicom.dataset.FileMetaDataset()
dataset.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
client.set_http_retry_params(retry=False)
httpserver.serve_content(
content='',
Expand All @@ -1360,8 +1370,8 @@ def test_store_instance_error_with_no_retries_and_additional_params(
httpserver, client, cache_dir
):
dataset = pydicom.Dataset.from_json({})
dataset.is_little_endian = True
dataset.is_implicit_VR = True
dataset.file_meta = pydicom.dataset.FileMetaDataset()
dataset.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian
client.set_http_retry_params(retry=False)
httpserver.serve_content(
content='',
Expand Down Expand Up @@ -1527,7 +1537,7 @@ def test_delete_instance_error_with_additional_params(


def test_load_json_dataset_da(httpserver, client, cache_dir):
value = ['2018-11-21']
value = ['20181121'] # DA format must be YYYYMMDD
dicom_json = {
'00080020': {
'vr': 'DA',
Expand Down
Loading