Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to handle pydicom 3.X #381

Closed
wants to merge 6 commits into from
Closed
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
18 changes: 13 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10"]

python-version: ["3.10", "3.11", "3.12", "3.13"]
pydicom-version: ["pydicom-latest"]
include:
- python-version: "3.10"
pydicom-version: "pydicom~=2.4.4"
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Cache Python dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ hashFiles('setup.py') }}
Expand All @@ -37,6 +40,11 @@ jobs:
pip install coverage
pip install coveralls
pip install codecov

- name: Force pydicom version if needed
if: startsWith(matrix.pydicom-version, '2')
run: |
pip install -U "${{ matrix.pydicom-version }}"

- name: Run tests via coverage
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codacy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
steps:
# Checkout the repository to the GitHub Actions runner
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4

# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
- name: Run Codacy Analysis CLI
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v2
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3
uses: actions/dependency-review-action@v4
1 change: 1 addition & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ History
0.5.7 (unreleased)
------------------
- Dropped support for Python 2.
- pydicom 3.X supported (requirement now >= pydicom 2.4.0)

0.5.6 (2023-05-08)
------------------
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Dependencies
------------

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

Expand Down
53 changes: 43 additions & 10 deletions dicompylercore/dicomparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@

import logging
import numpy as np
try:
from pydicom.dicomio import read_file
from pydicom.dataset import Dataset, validate_file_meta
from pydicom.pixel_data_handlers.util import pixel_dtype
except ImportError:
from dicom import read_file
from dicom.dataset import Dataset
from pydicom.dicomio import dcmread
from pydicom.dataset import Dataset, validate_file_meta
from pydicom.pixel_data_handlers.util import pixel_dtype
from pydicom.uid import ImplicitVRLittleEndian, ExplicitVRBigEndian
import random
from numbers import Number
from io import BytesIO
Expand All @@ -33,6 +30,40 @@
logger = logging.getLogger('dicompylercore.dicomparser')


def _fix_meta_info(dataset: Dataset) -> None:
"""Ensure the file meta info exists and has the correct values
for transfer syntax and media storage UIDs.

Copied from pydicom 2.4 and edited

.. warning::

The transfer syntax for ``is_implicit_VR = False`` and
``is_little_endian = True`` is ambiguous and will therefore not
be set.

Parameters
----------
dataset: pydicom Dataset

"""
dataset.ensure_file_meta()

if dataset.is_little_endian and dataset.is_implicit_VR:
dataset.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
elif not dataset.is_little_endian and not dataset.is_implicit_VR:
dataset.file_meta.TransferSyntaxUID = ExplicitVRBigEndian
elif not dataset.is_little_endian and dataset.is_implicit_VR:
raise NotImplementedError(
"Implicit VR Big Endian is not a supported Transfer Syntax."
)

if 'SOPClassUID' in dataset:
dataset.file_meta.MediaStorageSOPClassUID = dataset.SOPClassUID
if 'SOPInstanceUID' in dataset:
dataset.file_meta.MediaStorageSOPInstanceUID = dataset.SOPInstanceUID


class DicomParser:
"""Class to parse DICOM / DICOM RT files."""

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

# Fix dataset file_meta if incorrect
self.ds.ensure_file_meta()
try:
validate_file_meta(self.ds.file_meta)
except ValueError:
except (AttributeError, ValueError):
logger.debug('Fixing invalid File Meta for ' +
str(self.ds.SOPInstanceUID))
self.ds.fix_meta_info()
_fix_meta_info(self.ds)
validate_file_meta(self.ds.file_meta)

# Remove the PixelData attribute if it is not set.
# i.e. RTStruct does not contain PixelData and its presence can confuse
Expand Down
18 changes: 8 additions & 10 deletions dicompylercore/dvh.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,14 @@ def from_data(cls, data, binsize=1):

def __repr__(self):
"""String representation of the class."""
return 'DVH(%s, %r bins: [%r:%r] %s, volume: %r %s, name: %r, ' \
'rx_dose: %d %s%s)' % \
(self.dvh_type, self.counts.size, self.bins.min(),
self.bins.max(), self.dose_units,
self.volume, self.volume_units,
self.name,
0 if not self.rx_dose else self.rx_dose,
self.dose_units,
', *Notes: ' + self.notes if self.notes else '')

return (
f'DVH({self.dvh_type}, {self.counts.size} bins: '
f'[{self.bins.min()}:{self.bins.max()}] {self.dose_units}, '
f'volume: {self.volume} {self.volume_units}, name: {self.name}, '
f'rx_dose: {0 if not self.rx_dose else self.rx_dose} '
f'{self.dose_units}'
f'{", *Notes: " + self.notes if self.notes else ""})'
)
def __eq__(self, other):
"""Comparison method between two DVH objects.

Expand Down
2 changes: 1 addition & 1 deletion dicompylercore/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def piecewise(x, condlist, funclist, *args, **kw):
y = np.zeros(x.shape, x.dtype)
for k in range(n):
item = funclist[k]
if not isinstance(item, collections.Callable):
if not isinstance(item, collections.abc.Callable):
y[condlist[k]] = item
else:
vals = x[condlist[k]]
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
'sphinx.ext.napoleon']

autodoc_mock_imports = [
'numpy', 'dicom', 'pydicom', 'pydicom', 'dicom',
'numpy', 'dicom', 'pydicom',
'PIL', 'numpy.core', 'matplotlib', 'skimage', 'scipy']
autodoc_member_order = 'bysource'

Expand Down
9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
include_package_data=True,
install_requires=[
"numpy>=1.2",
"pydicom>=0.9.9",
"pydicom>=2.4.0,<4",
"matplotlib>=1.3.0"
],
extras_require={
Expand All @@ -56,11 +56,10 @@
'Intended Audience :: Science/Research',
'License :: OSI Approved :: BSD License',
'Natural Language :: English',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'Topic :: Scientific/Engineering :: Medical Science Apps.',
'Topic :: Scientific/Engineering :: Physics'
],
Expand Down
12 changes: 4 additions & 8 deletions tests/test_dicomparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@

import unittest
import os
from dicompylercore import dicomparser
from dicompylercore.config import pil_available, shapely_available
try:
from pydicom.multival import MultiValue as mv
from pydicom.valuerep import DSfloat
except ImportError:
from dicom.multival import MultiValue as mv
from dicom.valuerep import DSfloat
from pydicom.multival import MultiValue as mv
Fixed Show fixed Hide fixed
from pydicom.valuerep import DSfloat
Fixed Show fixed Hide fixed
from numpy import array, arange
from numpy.testing import assert_array_equal, assert_array_almost_equal
from dicompylercore import dicomparser
from dicompylercore.config import pil_available, shapely_available

basedata_dir = "tests/testdata"
example_data = os.path.join(basedata_dir, "example_data")
Expand Down
11 changes: 2 additions & 9 deletions tests/test_dose.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,8 @@

import unittest
import os
from dicompylercore import dicomparser, dose

try:
from pydicom.dataset import Dataset
from pydicom.sequence import Sequence
from pydicom import read_file as read_dicom
except ImportError:
from dicom.dataset import Dataset
from dicom.sequence import Sequence
from dicom import read_file as read_dicom
from pydicom import dcmread as read_dicom
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
Fixed Show fixed Hide fixed
from numpy import arange, zeros
from numpy.testing import (
assert_array_almost_equal,
Expand All @@ -26,6 +18,7 @@
)
import warnings
from dicompylercore.config import mpl_available, scipy_available
from dicompylercore import dicomparser, dose

basedata_dir = "tests/testdata"
example_data = os.path.join(basedata_dir, "example_data")
Expand Down
14 changes: 5 additions & 9 deletions tests/test_dvhcalc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@

import unittest
import os
from pydicom.dataset import Dataset
Fixed Show fixed Hide fixed
from pydicom.sequence import Sequence
Fixed Show fixed Hide fixed
from numpy import arange
from numpy.testing import assert_allclose
from .util import fake_rtdose, fake_ss
from dicompylercore import dicomparser, dvhcalc
from dicompylercore.config import skimage_available
from dicompylercore.dvh import DVH
from dicompylercore.dvhcalc import get_dvh
try:
from pydicom.dataset import Dataset
from pydicom.sequence import Sequence
except ImportError:
from dicom.dataset import Dataset
from dicom.sequence import Sequence
from numpy import arange
from numpy.testing import assert_allclose
from .util import fake_rtdose, fake_ss


basedata_dir = "tests/testdata"
Expand Down
Loading