Skip to content

Commit 924ab16

Browse files
author
Chris Bridge
committed
Implement SegmentDescription and Code
Signed-off-by: Chris Bridge <cbridge@partners.org>
1 parent 40cf301 commit 924ab16

File tree

1 file changed

+72
-4
lines changed

1 file changed

+72
-4
lines changed

monai/deploy/operators/dicom_seg_writer_operator.py

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@
1212
import os
1313
from pathlib import Path
1414
from random import randint
15-
from typing import TYPE_CHECKING, List, Union
15+
from typing import List, NamedTuple, Optional, Sequence, Union
1616

1717
import numpy as np
18+
from typeguard import typechecked
1819

1920
from monai.deploy.utils.importutil import optional_import
2021
from monai.deploy.utils.version import get_sdk_semver
@@ -24,18 +25,85 @@
2425
ImplicitVRLittleEndian, _ = optional_import("pydicom.uid", name="ImplicitVRLittleEndian")
2526
Dataset, _ = optional_import("pydicom.dataset", name="Dataset")
2627
FileDataset, _ = optional_import("pydicom.dataset", name="FileDataset")
27-
Sequence, _ = optional_import("pydicom.sequence", name="Sequence")
2828
hd, _ = optional_import("highdicom")
2929
sitk, _ = optional_import("SimpleITK")
3030
codes, _ = optional_import("pydicom.sr.codedict", name="codes")
31-
SegmentDescription, _ = optional_import("highdicom.seg", name="SegmentDescription")
31+
pydicom_Code, _ = optional_import("pydicom.sr.coding", name="Codes")
3232

3333
import monai.deploy.core as md
3434
from monai.deploy.core import DataPath, ExecutionContext, Image, InputContext, IOType, Operator, OutputContext
3535
from monai.deploy.core.domain.dicom_series import DICOMSeries
3636
from monai.deploy.core.domain.dicom_series_selection import StudySelectedSeries
3737

3838

39+
class Code(NamedTuple):
40+
"""Namedtuple for representation of a coded concept consisting of the
41+
actual code *value*, the coding *scheme designator*, the code *meaning*
42+
(and optionally the coding *scheme version*).
43+
"""
44+
value: str
45+
scheme_designator: str
46+
meaning: str
47+
scheme_version: Optional[str] = None
48+
49+
50+
class SegmentDescription:
51+
52+
@typechecked
53+
def __init__(
54+
self,
55+
segment_number: int,
56+
segment_label: str,
57+
segmented_property_category: Code,
58+
segmented_property_type: Code,
59+
algorithm_name: str,
60+
algorithm_version: str,
61+
algorithm_family: Code = codes.DCM.ArtificialIntelligence,
62+
tracking_uid: Optional[str] = None,
63+
tracking_id: Optional[str] = None,
64+
anatomic_regions: Optional[Sequence[Code]] = None,
65+
primary_anatomic_structures: Optional[Sequence[Code]] = None
66+
):
67+
self._segment_number = segment_number
68+
self._segment_label = segment_label
69+
self._segmented_property_category = pydicom_Code(*segmented_property_category)
70+
self._segmented_property_type = pydicom_Code(*segmented_property_type)
71+
self._tracking_id = tracking_id
72+
if anatomic_regions is not None:
73+
self._anatomic_regions = [pydicom_Code(*ar) for ar in anatomic_regions]
74+
else:
75+
self._anatomic_regions = None
76+
if primary_anatomic_structures is not None:
77+
self._primary_anatomic_structures = [pydicom_Code(*pas) for pas in primary_anatomic_structures]
78+
else:
79+
self._primary_anatomic_structures = None
80+
81+
# Generate a UID if one was not provided
82+
if tracking_id is not None and tracking_uid is None:
83+
tracking_uid = hd.UID()
84+
self._tracking_uid = tracking_uid
85+
86+
self._algorithm_identification = hd.AlgorithmIdentificationSequence(
87+
name=algorithm_name,
88+
family=algorithm_family,
89+
version=algorithm_version,
90+
)
91+
92+
def to_segment_description(self) -> hd.seg.SegmentDescription:
93+
return hd.seg.SegmentDescription(
94+
segment_number=self._segment_number,
95+
segment_label=self._segment_label,
96+
segmented_property_category=self._segment_label,
97+
segmented_property_type=self._segmented_property_type,
98+
algorithm_identification=self._algorithm_identification,
99+
algorithm_type="AUTOMATIC",
100+
tracking_uid=self._tracking_uid,
101+
tracking_id=self._tracking_id,
102+
anatomic_regions=self._anatomic_regions,
103+
primary_anatomic_structures=self._primary_anatomic_structures,
104+
)
105+
106+
39107
@md.input("seg_image", Image, IOType.IN_MEMORY)
40108
@md.input("study_selected_series_list", List[StudySelectedSeries], IOType.IN_MEMORY)
41109
@md.output("dicom_seg_instance", DataPath, IOType.DISK)
@@ -72,7 +140,7 @@ def __init__(self, segment_descriptions: List[SegmentDescription], *args, **kwar
72140
segmentation.
73141
"""
74142

75-
self._seg_descs = segment_descriptions
143+
self._seg_descs = [sd.to_segment_description() for sd in segment_descriptions]
76144

77145
def compute(self, op_input: InputContext, op_output: OutputContext, context: ExecutionContext):
78146
"""Performs computation for this operator and handles I/O.

0 commit comments

Comments
 (0)