Skip to content

Commit 0d5729d

Browse files
bastuladarcymason
andauthored
Update to work with pydicom 2.4+, 3.X, Python 3.10+ (#384)
* Update pydicom supported to >=2.4..0 * fix DVH repr problems with 'np.int(...) repr' * collections.Callable -> collections.abc.Callable * Resolve fixing of file_meta info * Update workflows for testing pydicom 2.4, 3.X * update some of the actions script versions * Fix style and import issues * Fix syntax * Update Python versions in setup.py * make pydicom versions in build name more obvious * Fix force of old pydicom * Update README to reflect Python 3.10 compatibility and badge changes --------- Co-authored-by: Darcy Mason <darcymason@gmail.com>
1 parent 0ad6e71 commit 0d5729d

File tree

14 files changed

+91
-67
lines changed

14 files changed

+91
-67
lines changed

.github/workflows/build.yml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,22 @@ jobs:
1313
strategy:
1414
fail-fast: false
1515
matrix:
16-
python-version: ["3.7", "3.8", "3.9", "3.10"]
17-
16+
python-version: ["3.10", "3.11", "3.12", "3.13"]
17+
pydicom-version: ["pydicom-latest"]
18+
include:
19+
- python-version: "3.10"
20+
pydicom-version: "pydicom==2.4.0"
1821
steps:
1922
- name: Checkout repository
20-
uses: actions/checkout@v3
23+
uses: actions/checkout@v4
2124

2225
- name: Set up Python ${{ matrix.python-version }}
23-
uses: actions/setup-python@v4
26+
uses: actions/setup-python@v5
2427
with:
2528
python-version: ${{ matrix.python-version }}
2629

2730
- name: Cache Python dependencies
28-
uses: actions/cache@v3
31+
uses: actions/cache@v4
2932
with:
3033
path: ~/.cache/pip
3134
key: ${{ hashFiles('setup.py') }}
@@ -37,6 +40,11 @@ jobs:
3740
pip install coverage
3841
pip install coveralls
3942
pip install codecov
43+
44+
- name: Force pydicom version if needed
45+
if: startsWith(matrix.pydicom-version, 'pydicom==')
46+
run: |
47+
pip install -U "${{ matrix.pydicom-version }}"
4048
4149
- name: Run tests via coverage
4250
run: |

.github/workflows/codacy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
steps:
3737
# Checkout the repository to the GitHub Actions runner
3838
- name: Checkout code
39-
uses: actions/checkout@v3
39+
uses: actions/checkout@v4
4040

4141
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
4242
- name: Run Codacy Analysis CLI

.github/workflows/codeql.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424

2525
steps:
2626
- name: Checkout
27-
uses: actions/checkout@v3
27+
uses: actions/checkout@v4
2828

2929
- name: Initialize CodeQL
3030
uses: github/codeql-action/init@v2

.github/workflows/dependency-review.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ jobs:
1515
runs-on: ubuntu-latest
1616
steps:
1717
- name: 'Checkout Repository'
18-
uses: actions/checkout@v3
18+
uses: actions/checkout@v4
1919
- name: 'Dependency Review'
20-
uses: actions/dependency-review-action@v3
20+
uses: actions/dependency-review-action@v4

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ History
55
0.5.7 (unreleased)
66
------------------
77
- Dropped support for Python 2.
8+
- pydicom 3.X supported (requirement now >= pydicom 2.4.0)
89

910
0.5.6 (2023-05-08)
1011
------------------

README.rst

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ Other information
1616

1717
- Free software: `BSD license <https://github.com/dicompyler/dicompyler-core/blob/master/LICENSE>`__
1818
- Documentation: `Read the docs <https://dicompyler-core.readthedocs.io>`__
19-
- Tested on Python 3.7+
19+
- Tested on Python 3.10+
2020

2121
Dependencies
2222
------------
2323

2424
- `numpy <http://www.numpy.org>`__ 1.2 or higher
25-
- `pydicom <https://pydicom.github.io>`__ 0.9.9 or higher (pydicom 1.0 compatible)
25+
- `pydicom <https://pydicom.github.io>`__ 2.4.0 or higher
2626
- `matplotlib <http://matplotlib.org>`__ 1.3.0 or higher (for DVH calculation)
2727
- Optional:
2828

@@ -85,7 +85,7 @@ This package was created with
8585
:target: http://mybinder.org/repo/bastula/dicom-notebooks
8686
.. |pypi| image:: https://img.shields.io/pypi/v/dicompyler-core.svg
8787
:target: https://pypi.python.org/pypi/dicompyler-core
88-
.. |Python Version| image:: https://img.shields.io/badge/python-3.7+-blue.svg
88+
.. |Python Version| image:: https://img.shields.io/badge/python-3.10+-blue.svg
8989
:target: https://pypi.python.org/pypi/dicompyler-core
9090
.. |GH Actions| image:: https://github.com/dicompyler/dicompyler-core/actions/workflows/build.yml/badge.svg
9191
:target: https://github.com/dicompyler/dicompyler-core/actions
@@ -97,8 +97,8 @@ This package was created with
9797
:target: https://app.codacy.com/gh/dicompyler/dicompyler-core/dashboard
9898
.. |Codecov| image:: https://codecov.io/gh/dicompyler/dicompyler-core/branch/master/graph/badge.svg
9999
:target: https://codecov.io/gh/dicompyler/dicompyler-core
100-
.. |Total Lines| image:: https://img.shields.io/tokei/lines/github/dicompyler/dicompyler-core
101-
:target: https://img.shields.io/tokei/lines/github/dicompyler/dicompyler-core
100+
.. |Total Lines| image:: https://img.shields.io/endpoint?url=https://ghloc.vercel.app/api/dicompyler/dicompyler-core/badge?style=flat&logoColor=white&label=Lines%20of%20Code
101+
:target: https://ghloc.vercel.app/dicompyler/dicompyler-core?branch=master
102102
.. |Code Size| image:: https://img.shields.io/github/languages/code-size/dicompyler/dicompyler-core
103103
:target: https://img.shields.io/github/languages/code-size/dicompyler/dicompyler-core
104104
.. |Zenodo| image:: https://zenodo.org/badge/51550203.svg

dicompylercore/dicomparser.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,10 @@
1111

1212
import logging
1313
import numpy as np
14-
try:
15-
from pydicom.dicomio import read_file
16-
from pydicom.dataset import Dataset, validate_file_meta
17-
from pydicom.pixel_data_handlers.util import pixel_dtype
18-
except ImportError:
19-
from dicom import read_file
20-
from dicom.dataset import Dataset
14+
from pydicom.dicomio import dcmread
15+
from pydicom.dataset import Dataset, validate_file_meta
16+
from pydicom.pixel_data_handlers.util import pixel_dtype
17+
from pydicom.uid import ImplicitVRLittleEndian, ExplicitVRBigEndian
2118
import random
2219
from numbers import Number
2320
from io import BytesIO
@@ -33,6 +30,40 @@
3330
logger = logging.getLogger('dicompylercore.dicomparser')
3431

3532

33+
def _fix_meta_info(dataset: Dataset) -> None:
34+
"""Ensure the file meta info exists and has the correct values
35+
for transfer syntax and media storage UIDs.
36+
37+
Copied from pydicom 2.4 and edited
38+
39+
.. warning::
40+
41+
The transfer syntax for ``is_implicit_VR = False`` and
42+
``is_little_endian = True`` is ambiguous and will therefore not
43+
be set.
44+
45+
Parameters
46+
----------
47+
dataset: pydicom Dataset
48+
49+
"""
50+
dataset.ensure_file_meta()
51+
52+
if dataset.is_little_endian and dataset.is_implicit_VR:
53+
dataset.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
54+
elif not dataset.is_little_endian and not dataset.is_implicit_VR:
55+
dataset.file_meta.TransferSyntaxUID = ExplicitVRBigEndian
56+
elif not dataset.is_little_endian and dataset.is_implicit_VR:
57+
raise NotImplementedError(
58+
"Implicit VR Big Endian is not a supported Transfer Syntax."
59+
)
60+
61+
if 'SOPClassUID' in dataset:
62+
dataset.file_meta.MediaStorageSOPClassUID = dataset.SOPClassUID
63+
if 'SOPInstanceUID' in dataset:
64+
dataset.file_meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID
65+
66+
3667
class DicomParser:
3768
"""Class to parse DICOM / DICOM RT files."""
3869

@@ -59,7 +90,7 @@ def __init__(self, dataset, memmap_pixel_array=False):
5990
elif isinstance(dataset, (str, BytesIO, Path)):
6091
try:
6192
with open(dataset, "rb") as fp:
62-
self.ds = read_file(fp, defer_size=100, force=True,
93+
self.ds = dcmread(fp, defer_size=100, force=True,
6394
stop_before_pixels=memmap_pixel_array)
6495
if memmap_pixel_array:
6596
self.offset = fp.tell() + 8
@@ -78,12 +109,14 @@ def __init__(self, dataset, memmap_pixel_array=False):
78109
raise AttributeError
79110

80111
# Fix dataset file_meta if incorrect
112+
self.ds.ensure_file_meta()
81113
try:
82114
validate_file_meta(self.ds.file_meta)
83-
except ValueError:
115+
except (AttributeError, ValueError):
84116
logger.debug('Fixing invalid File Meta for ' +
85117
str(self.ds.SOPInstanceUID))
86-
self.ds.fix_meta_info()
118+
_fix_meta_info(self.ds)
119+
validate_file_meta(self.ds.file_meta)
87120

88121
# Remove the PixelData attribute if it is not set.
89122
# i.e. RTStruct does not contain PixelData and its presence can confuse

dicompylercore/dvh.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,14 @@ def from_data(cls, data, binsize=1):
108108

109109
def __repr__(self):
110110
"""String representation of the class."""
111-
return 'DVH(%s, %r bins: [%r:%r] %s, volume: %r %s, name: %r, ' \
112-
'rx_dose: %d %s%s)' % \
113-
(self.dvh_type, self.counts.size, self.bins.min(),
114-
self.bins.max(), self.dose_units,
115-
self.volume, self.volume_units,
116-
self.name,
117-
0 if not self.rx_dose else self.rx_dose,
118-
self.dose_units,
119-
', *Notes: ' + self.notes if self.notes else '')
120-
111+
return (
112+
f'DVH({self.dvh_type}, {self.counts.size} bins: '
113+
f'[{self.bins.min()}:{self.bins.max()}] {self.dose_units}, '
114+
f'volume: {self.volume} {self.volume_units}, name: {self.name}, '
115+
f'rx_dose: {0 if not self.rx_dose else self.rx_dose} '
116+
f'{self.dose_units}'
117+
f'{", *Notes: " + self.notes if self.notes else ""})'
118+
)
121119
def __eq__(self, other):
122120
"""Comparison method between two DVH objects.
123121

dicompylercore/util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def piecewise(x, condlist, funclist, *args, **kw):
132132
y = np.zeros(x.shape, x.dtype)
133133
for k in range(n):
134134
item = funclist[k]
135-
if not isinstance(item, collections.Callable):
135+
if not isinstance(item, collections.abc.Callable):
136136
y[condlist[k]] = item
137137
else:
138138
vals = x[condlist[k]]

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
'sphinx.ext.napoleon']
4545

4646
autodoc_mock_imports = [
47-
'numpy', 'dicom', 'pydicom', 'pydicom', 'dicom',
47+
'numpy', 'dicom', 'pydicom',
4848
'PIL', 'numpy.core', 'matplotlib', 'skimage', 'scipy']
4949
autodoc_member_order = 'bysource'
5050

0 commit comments

Comments
 (0)