diff --git a/pyproject.toml b/pyproject.toml index dcabe36..9237700 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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'", ] diff --git a/src/dicomweb_client/file.py b/src/dicomweb_client/file.py index b1a421d..854d703 100644 --- a/src/dicomweb_client/file.py +++ b/src/dicomweb_client/file.py @@ -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 @@ -350,7 +350,7 @@ def _read_bot(fp: DicomFileLike) -> np.ndarray: 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) diff --git a/src/dicomweb_client/web.py b/src/dicomweb_client/web.py index f8e0d0e..8605276 100644 --- a/src/dicomweb_client/web.py +++ b/src/dicomweb_client/web.py @@ -29,6 +29,7 @@ ) import pydicom +from pydicom.valuerep import INT_VR, FLOAT_VR, VR import requests import retrying @@ -60,7 +61,7 @@ def _load_xml_dataset(dataset: Element) -> pydicom.dataset.Dataset: 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) @@ -74,6 +75,26 @@ def _load_xml_dataset(dataset: Element) -> pydicom.dataset.Dataset: 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 diff --git a/tests/test_web.py b/tests/test_web.py index ca91e63..f058950 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -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' @@ -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, @@ -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, @@ -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='', @@ -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='', @@ -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',