From 9e75ea840efa7e30322cf3f8b0627b9bcf4471e3 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Wed, 16 Sep 2026 10:13:39 +0100 Subject: [PATCH 1/2] Update pydicom, deid and dicom-anonymizer for pydicom 3 We also need to update to: deid 0.4.12 dicom-anonymizer 2.0.0 Update uv.lock --- pixl_dcmd/pyproject.toml | 6 +++--- pixl_imaging/pyproject.toml | 2 +- pytest-pixl/pyproject.toml | 2 +- uv.lock | 28 ++++++++++++++-------------- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pixl_dcmd/pyproject.toml b/pixl_dcmd/pyproject.toml index 2dfe64860..17f8cf8ef 100644 --- a/pixl_dcmd/pyproject.toml +++ b/pixl_dcmd/pyproject.toml @@ -9,11 +9,11 @@ classifiers = ["Programming Language :: Python :: 3"] dependencies = [ "core==0.2.0rc0", "arrow==1.4.0", - "deid==0.3.25", - "dicom-anonymizer==1.0.13.post1", + "deid==0.4.12", + "dicom-anonymizer==2.0.0", "dicom-validator==0.7.3", "logger==1.4", - "pydicom==2.4.5", + "pydicom==3.0.2", "pydicom-data", "python-decouple==3.8", "requests==2.33.0", diff --git a/pixl_imaging/pyproject.toml b/pixl_imaging/pyproject.toml index 5123e334c..3956912da 100644 --- a/pixl_imaging/pyproject.toml +++ b/pixl_imaging/pyproject.toml @@ -10,7 +10,7 @@ dependencies = [ "core==0.2.0rc0", "aiohttp==3.13.4", "alembic==1.18.4", - "pydicom==2.4.5", + "pydicom==3.0.2", "uvicorn==0.44.0", ] diff --git a/pytest-pixl/pyproject.toml b/pytest-pixl/pyproject.toml index b0955cb7e..a71c9fe9e 100644 --- a/pytest-pixl/pyproject.toml +++ b/pytest-pixl/pyproject.toml @@ -9,7 +9,7 @@ classifiers = [ "Programming Language :: Python :: 3" ] dependencies = [ - "pydicom==2.4.5", + "pydicom==3.0.2", "pyftpdlib==2.2.0", "pyOpenSSL==26.0.0", "python-decouple==3.8", diff --git a/uv.lock b/uv.lock index 5c472cbe8..cdaba1933 100644 --- a/uv.lock +++ b/uv.lock @@ -707,7 +707,7 @@ wheels = [ [[package]] name = "deid" -version = "0.3.25" +version = "0.4.12" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "matplotlib" }, @@ -715,22 +715,22 @@ dependencies = [ { name = "pydicom" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/9d/3726d4463e50d038a19c79f3c61b2d70fb23507b9e1a583d9e636cabb0c3/deid-0.3.25.tar.gz", hash = "sha256:92a8a3807c999026a88fae1d741752d38243ecb7dbc257a8d7158b5022a386c3", size = 50667, upload-time = "2024-10-02T16:44:25.066Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/e8/d81233da51fcbebfdfdb5396a700af98fc8ac819d7129b928f0aa8f8b05b/deid-0.4.12.tar.gz", hash = "sha256:76973e54dbf1d177de668c0badb72a8fee37121df1f47983d49a7af14f07978a", size = 55892, upload-time = "2026-01-12T16:52:54.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/c2/71eaa892531f58ebcda2a852d2139db3f6f316080b21ce44862eb821531e/deid-0.3.25-py3-none-any.whl", hash = "sha256:dddb20dd4a67120020a072fc55784efb87c3d5ff5beeaadc35c815f71189e418", size = 94388, upload-time = "2024-10-02T16:44:23.562Z" }, + { url = "https://files.pythonhosted.org/packages/5a/b8/3782cf106dae65884c0a727d099cd3eb3d60d01ec82e83a6843f2b9a780d/deid-0.4.12-py3-none-any.whl", hash = "sha256:2272d4bd8d9f9ba6ecb453eda8455acf7e4baa3c9f4533abf06aff6b5a11a632", size = 105369, upload-time = "2026-01-12T16:52:52.546Z" }, ] [[package]] name = "dicom-anonymizer" -version = "1.0.13.post1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydicom" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c2/93/e84cf78f2317851d9d3443b8fcb4ba95c217355250fd211f4248a05ad888/dicom_anonymizer-1.0.13.post1.tar.gz", hash = "sha256:fc5763235c0115f47475acf1a84902c407ad781f5f9425ed65cd1322fb0d76f4", size = 30928, upload-time = "2024-09-17T11:24:19.569Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/34/f99cddacbf74ab1272cb140f17b73451d5cf9430b78b007dd0f2a2725dbf/dicom_anonymizer-2.0.0.tar.gz", hash = "sha256:2cf2e6bf5c412163a2ab74e77ae917a1c7cffb9ab6dc730690f1862182547216", size = 35028, upload-time = "2026-07-10T09:36:22.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/5e/c1f75bb8fd467269abc7f78abf0392ec2b7f7b303579bb1a8ea23419cdde/dicom_anonymizer-1.0.13.post1-py3-none-any.whl", hash = "sha256:cd4ddb656f96ebb67a987d2245a9de7d871f9e6942ab807989586a3131862392", size = 29962, upload-time = "2024-09-17T11:24:18.128Z" }, + { url = "https://files.pythonhosted.org/packages/69/28/ddcdc5d1e41f1bab2dd642c4a4098a23330226c476a81b7c9d9d4b3050a8/dicom_anonymizer-2.0.0-py3-none-any.whl", hash = "sha256:bf1302e6ab423bdc08d8aedb836c57507722206ad1862efe5f9320b13a24b5c0", size = 34823, upload-time = "2026-07-10T09:36:21.014Z" }, ] [[package]] @@ -2148,11 +2148,11 @@ requires-dist = [ { name = "core", editable = "pixl_core" }, { name = "core", extras = ["dev"], marker = "extra == 'dev'", editable = "pixl_core" }, { name = "core", extras = ["test"], marker = "extra == 'test'", editable = "pixl_core" }, - { name = "deid", specifier = "==0.3.25" }, - { name = "dicom-anonymizer", specifier = "==1.0.13.post1" }, + { name = "deid", specifier = "==0.4.12" }, + { name = "dicom-anonymizer", specifier = "==2.0.0" }, { name = "dicom-validator", specifier = "==0.7.3" }, { name = "logger", specifier = "==1.4" }, - { name = "pydicom", specifier = "==2.4.5" }, + { name = "pydicom", specifier = "==3.0.2" }, { name = "pydicom-data" }, { name = "pylibjpeg", specifier = ">=2.0" }, { name = "pylibjpeg-libjpeg", specifier = ">=2.3" }, @@ -2219,7 +2219,7 @@ requires-dist = [ { name = "core", editable = "pixl_core" }, { name = "core", extras = ["dev"], marker = "extra == 'dev'", editable = "pixl_core" }, { name = "core", extras = ["test"], marker = "extra == 'test'", editable = "pixl_core" }, - { name = "pydicom", specifier = "==2.4.5" }, + { name = "pydicom", specifier = "==3.0.2" }, { name = "uvicorn", specifier = "==0.44.0" }, ] provides-extras = ["test", "dev"] @@ -2513,11 +2513,11 @@ wheels = [ [[package]] name = "pydicom" -version = "2.4.5" +version = "3.0.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8b/6fca5f1f0cc74b2ad9ea0271d374bee2cb13ed3b2793fd93254678de1aba/pydicom-2.4.5.tar.gz", hash = "sha256:ba2077bd76cb49e05fe51c3e1e0b2d77218f50c6c231120b443d29b38ea6b7a8", size = 2018349, upload-time = "2026-03-20T14:39:26.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/de/52aaf905f1f0ae7aba85996e2592ea2c1fe49157f3cfbcd1871965bdb51d/pydicom-3.0.2.tar.gz", hash = "sha256:5942bfc2d72c6fa4b3b5b62c527f54b7f2355f21d6f5d296df6bb30188df6a4f", size = 2886792, upload-time = "2026-03-19T21:46:20.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/5b/4e7d67d880b3344afff01708725a39717694a79391a32711fd40a8958bed/pydicom-2.4.5-py3-none-any.whl", hash = "sha256:35a594cc8ebae8dd40b1f632cd7c9f08ecaa147cb9305a9f9fbca2c03f5ccc93", size = 1771963, upload-time = "2026-03-20T14:39:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/e0/60466c6d712dad2cf807df315e39863e91609ffd1064ecb835994460bbda/pydicom-3.0.2-py3-none-any.whl", hash = "sha256:abf971a5440f84dbaf42c4b6758e30e62480902584f8b270b9a5d146e278a07b", size = 2376822, upload-time = "2026-03-19T21:46:19.042Z" }, ] [[package]] @@ -2744,7 +2744,7 @@ test = [ requires-dist = [ { name = "core", marker = "extra == 'test'", editable = "pixl_core" }, { name = "core", extras = ["dev"], marker = "extra == 'dev'", editable = "pixl_core" }, - { name = "pydicom", specifier = "==2.4.5" }, + { name = "pydicom", specifier = "==3.0.2" }, { name = "pyftpdlib", specifier = "==2.2.0" }, { name = "pyopenssl", specifier = "==26.0.0" }, { name = "pytest-loguru", specifier = "==0.4.0" }, From 8c4fc614594072e5e9d95e5de983c9dfb63a97d2 Mon Sep 17 00:00:00 2001 From: Paul Smith Date: Wed, 16 Sep 2026 10:13:43 +0100 Subject: [PATCH 2/2] Update test fixtures to work with pydicom 3 write_like_original=False to ds.save_as is deprecated use enforce_file_format=True instead don't set ds.is_implicit_VR and ds.is_little_endian attributes they are not dicom tags, but pydicom dataset attributes, and are no longer used in pydicom 3 instead, pass implicit_vr=True, little_endian=True to ds.save_as --- pixl_dcmd/tests/test_tagrecording.py | 2 +- pytest-pixl/src/pytest_pixl/dicom.py | 19 +++++++------------ test/conftest.py | 2 +- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/pixl_dcmd/tests/test_tagrecording.py b/pixl_dcmd/tests/test_tagrecording.py index 470847ee8..a2bd275c6 100644 --- a/pixl_dcmd/tests/test_tagrecording.py +++ b/pixl_dcmd/tests/test_tagrecording.py @@ -21,7 +21,7 @@ def test_record_dicom_headers(tmpdir, mock_header_record_path): ds = generate_dicom_dataset() - ds.save_as(str(tmpdir / "test.dcm"), write_like_original=False) + ds.save_as(str(tmpdir / "test.dcm"), enforce_file_format=True) with open(str(tmpdir / "test.dcm"), "rb") as f: ds_bytes = f.read() record_dicom_headers(ds_bytes) diff --git a/pytest-pixl/src/pytest_pixl/dicom.py b/pytest-pixl/src/pytest_pixl/dicom.py index f6aca7fb9..62e0ddf75 100644 --- a/pytest-pixl/src/pytest_pixl/dicom.py +++ b/pytest-pixl/src/pytest_pixl/dicom.py @@ -46,10 +46,10 @@ def write_volume(filename_pattern: str) -> None: variables = json.loads(dicom_variables_path.open("r").read()) rng = np.random.default_rng(0) for i, slice_info in enumerate(variables): - slice_info["pixel_data"] = rng.random(size=(256, 256)) + slice_info["pixel_data"] = (rng.random(size=(256, 256)) * 65535).astype(np.uint16) ds = generate_dicom_dataset(slice_info) file_name = filename_pattern.format(slice=i) - ds.save_as(file_name, write_like_original=False) + ds.save_as(file_name, enforce_file_format=True) TAGS_DICT = { @@ -101,7 +101,7 @@ def generate_dicom_dataset(tag_values: dict = TAGS_DICT, **kwargs: Any) -> Datas pixel_data = tag_values["pixel_data"] if pixel_data is None: - pixel_data = np.zeros((256, 256)) + pixel_data = np.zeros((256, 256), dtype=np.uint16) ds = _generate_default_dicom_dataset() ds.InstanceCreationTime = instance_creation_time @@ -131,7 +131,8 @@ def generate_dicom_dataset(tag_values: dict = TAGS_DICT, **kwargs: Any) -> Datas # Convert Dataset to FileDataset # Note: required for image operations using deid which requires FileDataset with tempfile.NamedTemporaryFile(suffix=".dcm") as tmp: - ds.save_as(tmp.name, write_like_original=False) + # a from_json dataset has no transfer syntax, so state the encoding here + ds.save_as(tmp.name, enforce_file_format=True, implicit_vr=True, little_endian=True) return dcmread(tmp.name) @@ -144,11 +145,7 @@ def _generate_default_dicom_dataset() -> Dataset: "data/default_dicom_tags.json" ) variables = json.loads(default_variables_path.open("r").read()) - ds = Dataset.from_json(variables) - # Not sure why these weren't carried over to the JSON - ds.is_implicit_VR = True - ds.is_little_endian = True - return ds + return Dataset.from_json(variables) def _create_default_json(json_file: Path) -> None: # noqa: PLR0915 (too many statements) @@ -159,7 +156,7 @@ def _create_default_json(json_file: Path) -> None: # noqa: PLR0915 (too many st :param json_file: The path to save the JSON file to. """ - pixel_data = np.zeros((256, 256)) + pixel_data = np.zeros((256, 256), dtype=np.uint16) # File meta info data elements file_meta = FileMetaDataset() @@ -408,8 +405,6 @@ def _create_default_json(json_file: Path) -> None: # noqa: PLR0915 (too many st ds.PixelData = pixel_data.tobytes() ds.file_meta = file_meta - ds.is_implicit_VR = True - ds.is_little_endian = True # Export as JSON dictionary with Path(json_file, encoding="utf-8").open("w") as f: diff --git a/test/conftest.py b/test/conftest.py index b238a0018..c0ac1593f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -142,8 +142,8 @@ def _upload_dicom_instance(dicom_dir: Path, **kwargs: Any) -> None: dicom_dir / f"{kwargs['PatientID']}_{kwargs['AccessionNumber']}_{kwargs['SeriesDescription']}.dcm" ) - ds.save_as(str(test_dcm_file), write_like_original=False) # I think we can skip writing to disk! + ds.save_as(str(test_dcm_file), enforce_file_format=True) _upload_to_vna(test_dcm_file)