|
4 | 4 | from concurrent.futures import Executor, Future, ProcessPoolExecutor |
5 | 5 | from contextlib import contextmanager |
6 | 6 | from copy import deepcopy |
| 7 | +from itertools import chain |
7 | 8 | from os import PathLike |
8 | 9 | import sqlite3 |
9 | 10 | from typing import ( |
|
44 | 45 | from pydicom.sr.coding import Code |
45 | 46 | from pydicom.filereader import dcmread |
46 | 47 |
|
47 | | -from highdicom._module_utils import ModuleUsageValues, get_module_usage |
| 48 | +from highdicom._module_utils import ( |
| 49 | + ModuleUsageValues, |
| 50 | + get_module_usage, |
| 51 | + does_iod_have_pixel_data, |
| 52 | +) |
48 | 53 | from highdicom.base import SOPClass, _check_little_endian |
49 | 54 | from highdicom.content import ( |
50 | 55 | ContentCreatorIdentificationCodeSequence, |
@@ -1191,6 +1196,7 @@ def __init__( |
1191 | 1196 | tile_size: Union[Sequence[int], None] = None, |
1192 | 1197 | pyramid_uid: Optional[str] = None, |
1193 | 1198 | pyramid_label: Optional[str] = None, |
| 1199 | + further_source_images: Optional[Sequence[Dataset]] = None, |
1194 | 1200 | **kwargs: Any |
1195 | 1201 | ) -> None: |
1196 | 1202 | """ |
@@ -1400,6 +1406,13 @@ def __init__( |
1400 | 1406 | Human readable label for the pyramid containing this segmentation. |
1401 | 1407 | Should only be used if this segmentation is part of a |
1402 | 1408 | multi-resolution pyramid. |
| 1409 | + further_source_images: Optional[Sequence[pydicom.Dataset]], optional |
| 1410 | + Additional images to record as source images in the segmentation. |
| 1411 | + Unlike the main ``source_images`` parameter, these images will |
| 1412 | + *not* be used to infer the position and orientation of the |
| 1413 | + ``pixel_array`` in the case that no plane positions are supplied. |
| 1414 | + Images from multiple series may be passed, however they must all |
| 1415 | + belong to the same study. |
1403 | 1416 | **kwargs: Any, optional |
1404 | 1417 | Additional keyword arguments that will be passed to the constructor |
1405 | 1418 | of `highdicom.base.SOPClass` |
@@ -1570,12 +1583,35 @@ def __init__( |
1570 | 1583 |
|
1571 | 1584 | # General Reference |
1572 | 1585 |
|
| 1586 | + if further_source_images is not None: |
| 1587 | + # We make no requirement here that images should be from the same |
| 1588 | + # series etc, but they should belong to the same study and be image |
| 1589 | + # objects |
| 1590 | + for s_img in further_source_images: |
| 1591 | + if not isinstance(s_img, Dataset): |
| 1592 | + raise TypeError( |
| 1593 | + "All items in 'further_source_images' should be " |
| 1594 | + "of type 'pydicom.Dataset'." |
| 1595 | + ) |
| 1596 | + if s_img.StudyInstanceUID != self.StudyInstanceUID: |
| 1597 | + raise ValueError( |
| 1598 | + "All items in 'further_source_images' should belong " |
| 1599 | + "to the same study as 'source_images'." |
| 1600 | + ) |
| 1601 | + if not does_iod_have_pixel_data(s_img.SOPClassUID): |
| 1602 | + raise ValueError( |
| 1603 | + "All items in 'further_source_images' should be " |
| 1604 | + "image objects." |
| 1605 | + ) |
| 1606 | + else: |
| 1607 | + further_source_images = [] |
| 1608 | + |
1573 | 1609 | # Note that appending directly to the SourceImageSequence is typically |
1574 | 1610 | # slow so it's more efficient to build as a Python list then convert |
1575 | 1611 | # later. We save conversion for after the main loop |
1576 | 1612 | source_image_seq: List[Dataset] = [] |
1577 | 1613 | referenced_series: Dict[str, List[Dataset]] = defaultdict(list) |
1578 | | - for s_img in source_images: |
| 1614 | + for s_img in chain(source_images, further_source_images): |
1579 | 1615 | ref = Dataset() |
1580 | 1616 | ref.ReferencedSOPClassUID = s_img.SOPClassUID |
1581 | 1617 | ref.ReferencedSOPInstanceUID = s_img.SOPInstanceUID |
|
0 commit comments