Skip to content

Commit 2f14618

Browse files
authored
Merge pull request #415 from ImagingDataCommons/v0.28.0dev
v0.28.0
2 parents 8325dcb + 3e552d7 commit 2f14618

74 files changed

Lines changed: 955883 additions & 936561 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.codespellrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[codespell]
22
skip = .git,*.pdf,*.svg,*.ipynb
33
# te,fo - either abbreviations of variables
4-
ignore-words-list = te,fo,socio-economic,laf
4+
ignore-words-list = te,fo,socio-economic,laf,re-use

.github/workflows/run_unit_tests.yml

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,35 @@ on:
1111

1212
jobs:
1313
build:
14+
runs-on: ${{ matrix.os }}
1415

15-
runs-on: ubuntu-latest
1616
strategy:
17+
fail-fast: false
18+
1719
matrix:
1820
# NB no 3.12 because an issue with coverage makes it extremely slow
1921
# and the likelihood of 3.12-specific bugs is considered low at this
2022
# stage
23+
os: [ubuntu-latest]
2124
python-version: ["3.10", "3.11", "3.13", "3.14"]
22-
dependencies: [".", "'.[libjpeg]'"]
25+
dependencies: [".", ".[libjpeg,itk,simpleitk,nibabel]"]
26+
27+
include:
28+
- os: windows-latest
29+
python-version: "3.14"
30+
dependencies: "."
31+
32+
- os: windows-latest
33+
python-version: "3.14"
34+
dependencies: ".[libjpeg,itk,simpleitk,nibabel]"
35+
36+
- os: macos-latest
37+
python-version: "3.14"
38+
dependencies: "."
39+
40+
- os: macos-latest
41+
python-version: "3.14"
42+
dependencies: ".[libjpeg,itk,simpleitk,nibabel]"
2343

2444
env:
2545
# Set this otherwise coverage on python 3.12 is absurdly slow
@@ -38,8 +58,7 @@ jobs:
3858
pip install ${{ matrix.dependencies }}
3959
- name: Lint with flake8
4060
run: |
41-
flake8 --exclude='bin,build,.eggs,src/highdicom/_*'
61+
flake8 --exclude='bin,build,.eggs'
4262
- name: Test with pytest
4363
run: |
4464
pytest --cov=highdicom --cov-fail-under=80
45-

bin/create_iods_modules.py

Lines changed: 17 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@
1212
logger = logging.getLogger(__name__)
1313

1414

15-
PGK_PATH = os.path.join(
15+
PKG_JSON_PATH = os.path.join(
1616
os.path.dirname(__file__),
1717
'..',
1818
'src',
19-
'highdicom'
19+
'highdicom',
20+
'_standard',
2021
)
2122

2223

@@ -25,10 +26,6 @@ def _load_json_from_file(filename):
2526
return json.load(f)
2627

2728

28-
def _dump_json(data):
29-
return json.dumps(data, indent=4, sort_keys=True)
30-
31-
3229
def _create_sop_to_iods(directory):
3330
filename = os.path.join(directory, 'sops.json')
3431
sops = _load_json_from_file(filename)
@@ -97,14 +94,16 @@ def _create_modules(directory):
9794
return modules
9895

9996

100-
if __name__ == '__main__':
101-
97+
if __name__ == "__main__":
10298
logging.basicConfig()
10399
logger.setLevel(logging.DEBUG)
104100

105101
# Positional argument is path to directory containing JSON files generated
106102
# using the dicom-standard Python package, see
107103
# https://github.com/innolitics/dicom-standard/tree/master/standard
104+
# If generating yourself using the instructions in that repo, be sure to
105+
# use "make all" instead of just "make", otherwise some of the required
106+
# files are skipped
108107
try:
109108
directory = sys.argv[1]
110109
except IndexError as e:
@@ -119,37 +118,16 @@ def _create_modules(directory):
119118
current_time = datetime.datetime.time(now).strftime('%H:%M:%S')
120119

121120
iods = _create_iods(directory)
122-
iods_docstr = '\n'.join([
123-
'"""DICOM Information Object Definitions (IODs)',
124-
f'auto-generated on {current_date} at {current_time}.',
125-
'"""',
126-
'from typing import Dict, List'
127-
])
121+
iods_filename = os.path.join(PKG_JSON_PATH, 'iods.json')
122+
with open(iods_filename, 'w') as jf:
123+
json.dump(iods, jf, indent=2)
124+
128125
sop_to_iods = _create_sop_to_iods(directory)
129-
iods_filename = os.path.join(PGK_PATH, '_iods.py')
130-
with open(iods_filename, 'w') as fp:
131-
fp.write(iods_docstr)
132-
fp.write('\n\n')
133-
iods_formatted = _dump_json(iods).replace('null', 'None')
134-
fp.write(
135-
f'IOD_MODULE_MAP: Dict[str, List[Dict[str, str]]] = {iods_formatted}'
136-
)
137-
fp.write('\n\n')
138-
sop_to_iods_formatted = _dump_json(sop_to_iods).replace('null', 'None')
139-
fp.write(f'SOP_CLASS_UID_IOD_KEY_MAP = {sop_to_iods_formatted}')
126+
sop_to_iods_filename = os.path.join(PKG_JSON_PATH, 'sop_class_to_iod.json')
127+
with open(sop_to_iods_filename, 'w') as jf:
128+
json.dump(sop_to_iods, jf, indent=2)
140129

141130
modules = _create_modules(directory)
142-
modules_docstr = '\n'.join([
143-
'"""DICOM modules'
144-
f'auto-generated on {current_date} at {current_time}.'
145-
'"""',
146-
'from typing import Dict, List, Sequence, Union'
147-
])
148-
modules_filename = os.path.join(PGK_PATH, '_modules.py')
149-
with open(modules_filename, 'w') as fp:
150-
fp.write(modules_docstr)
151-
fp.write('\n\n')
152-
modules_formatted = _dump_json(modules).replace('null', 'None')
153-
fp.write(
154-
f'MODULE_ATTRIBUTE_MAP: Dict[str, List[Dict[str, Union[str, Sequence[str]]]]] = {modules_formatted}' # noqa: E501
155-
)
131+
modules_filename = os.path.join(PKG_JSON_PATH, 'modules.json')
132+
with open(modules_filename, 'w') as jf:
133+
json.dump(modules, jf, indent=2)

docs/coding.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,16 @@ documentation for the coding scheme itself. This
182182
`webpage <https://fedorov.github.io/dcmterms/terminology.html>`_ is a nice
183183
interactive tool for searching the terms and Context Groups defined within
184184
DICOM.
185+
186+
UCUM
187+
----
188+
189+
UCUM works a little differently to the other coding schemes discussed here.
190+
Rather than a finite set of codes, it defines codes for a set of units and a
191+
syntax for combining them to give derived units (e.g. `"mm/s"` for millimeters
192+
per second), meaning there are potentially infinite codes. Pydicom defines
193+
a list of the most common units, but this will not cover every use case.
194+
195+
We recommend this `page
196+
<https://ucum.nlm.nih.gov/ucum-lhc/demo.html>`_, a useful validator for
197+
checking UCUM syntax.

docs/general.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ parts of the library.
1616
volume
1717
coding
1818
remote
19+
other_libraries

docs/image.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,26 @@ matrix. In these cases, the first spatial dimension will always have shape 1.
243243

244244
See :doc:`volume` for an overview of the :class:`highdicom.Volume` class.
245245

246+
Accessing Frames By Derivation Source
247+
-------------------------------------
248+
249+
Some DICOM images are derived from other DICOM images. This pattern is common
250+
in IODs designed specifically for derived images, such as Parametric Maps, but
251+
can also occur in many other types of image. In this case, the image *may*
252+
contain frame-level references that communicate which frames/instance each
253+
frame was derived from. If such information is present in a file, highdicom
254+
provides methods to access derived frames by specifying information about
255+
the source frame.
256+
257+
The two methods implementing this behavior are:
258+
259+
* :meth:`highdicom.Image.get_pixels_by_source_instance` is used when each
260+
frame of the image is derived from a different instance of a series of
261+
single-frame images, identified by its SOP Instance UID.
262+
* :meth:`highdicom.Image.get_pixels_by_source_frame` is used when each frame of
263+
the image is derived from a different frame of a multi-frame source image,
264+
identified by the source frame number.
265+
246266
.. _lazy:
247267

248268
Lazy Frame Retrieval

docs/itk_lib.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
.. _itk_lib:
2+
3+
ITK
4+
===
5+
6+
`ITK`_ is a widely-used library for volumetric image processing. In order to
7+
use ITK with highdicom, the ``itk`` python package must be installed separately.
8+
Version 5.4.0 or later is required.
9+
10+
.. _itk_vol:
11+
12+
Volume Conversions
13+
------------------
14+
15+
Highdicom supports conversions with the ``itk.Image`` class through the
16+
:meth:`highdicom.Volume.to_itk` and :meth:`highdicom.Volume.from_itk` methods.
17+
Like highdicom, ITK uses the "LPS" convention. However, when converting to and
18+
from NumPy arrays, ITK reverses the order of dimensions. This permutation is
19+
handled automatically by highdicom and requires no intervention by the user.
20+
21+
Creating an ITK Image from a Volume:
22+
23+
.. code-block:: python
24+
25+
import highdicom as hd
26+
27+
28+
vol = hd.Volume(...)
29+
30+
itk_image = vol.to_itk()
31+
32+
Creating a volume from an ITK Image:
33+
34+
.. code-block:: python
35+
36+
import itk
37+
import highdicom as hd
38+
39+
40+
itk_image = itk.Image(...)
41+
42+
vol = hd.Volume.from_itk(
43+
itk_image=itk_image,
44+
coordinate_system='PATIENT',
45+
frame_of_reference_uid=None
46+
)
47+
48+
49+
.. _`ITK`: https://itk.org/

0 commit comments

Comments
 (0)