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

Add coordinate for Measurement in SR #307

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions src/highdicom/sr/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Package for creation of Structured Report (SR) instances."""
from highdicom.sr.coding import CodedConcept
from highdicom.sr.content import (
CoordinatesForMeasurement,
FindingSite,
ImageRegion,
ImageRegion3D,
Expand Down Expand Up @@ -104,6 +105,7 @@
'ContentItem',
'ContentSequence',
'ContentSequence',
'CoordinatesForMeasurement',
'DateContentItem',
'DateTimeContentItem',
'DeviceObserverIdentifyingAttributes',
Expand Down
24 changes: 24 additions & 0 deletions src/highdicom/sr/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,30 @@ def from_dataset(
return cast(SourceSeriesForSegmentation, item)


class CoordinatesForMeasurement(ScoordContentItem):

"""Content item representing spatial coordinates of a measurement"""

def __init__(
self,
graphic_type: Union[GraphicTypeValues, str],
graphic_data: np.ndarray,
source_image: SourceImageForRegion,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has nothing to do with ImageRegion, but I reused SourceImageForRegion here as it needs to be an image with SELECTED FROM relationship. Not sure if there's a better option.

) -> None:
graphic_type = GraphicTypeValues(graphic_type)
super().__init__(
name=CodedConcept(
value='121112',
meaning='Source of Measurement',
scheme_designator='DCM'
),
graphic_type=graphic_type,
graphic_data=graphic_data,
relationship_type=RelationshipTypeValues.INFERRED_FROM,
)
self.ContentSequence = [source_image]


class ImageRegion(ScoordContentItem):

"""Content item representing an image region of interest in the
Expand Down
12 changes: 6 additions & 6 deletions src/highdicom/sr/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from highdicom.sr.coding import CodedConcept
from highdicom.sr.content import (
FindingSite,
CoordinatesForMeasurement, FindingSite,
LongitudinalTemporalOffsetFromEvent,
ImageRegion,
ImageRegion3D,
Expand Down Expand Up @@ -2386,7 +2386,7 @@ def __init__(
finding_sites: Optional[Sequence[FindingSite]] = None,
method: Optional[Union[CodedConcept, Code]] = None,
properties: Optional[MeasurementProperties] = None,
referenced_images: Optional[Sequence[SourceImageForMeasurement]] = None,
referenced_images: Optional[Sequence[Union[SourceImageForMeasurement, CoordinatesForMeasurement]]] = None,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created another option and used the same referenced_images parameter.

Another solution I see would be to expand SourceImageForMeasurement and implement all options from TID 320 in it.

If a SCOORD is given, use row 3 and 4. If SCOORD3D, then make it use row 6 (I have not implemented this). If no coordinate is passed, use row 1 (which is the current SourceImageForMeasurement functionality)

referenced_real_world_value_map: Optional[RealWorldValueMap] = None
):
"""
Expand Down Expand Up @@ -2429,8 +2429,8 @@ def __init__(
Measurement properties, including evaluations of its normality
and/or significance, its relationship to a reference population,
and an indication of its selection from a set of measurements
referenced_images: Union[Sequence[highdicom.sr.SourceImageForMeasurement], None], optional
Referenced images which were used as sources for the measurement
referenced_images: Union[Sequence[Union[highdicom.sr.SourceImageForMeasurement, highdicom.sr.CoordinatesForMeasurement]], None], optional
Referenced images or coordinates which were used as sources for the measurement
referenced_real_world_value_map: Union[highdicom.sr.RealWorldValueMap, None], optional
Referenced real world value map for referenced source images

Expand Down Expand Up @@ -2495,10 +2495,10 @@ def __init__(
content.extend(properties)
if referenced_images is not None:
for image in referenced_images:
if not isinstance(image, SourceImageForMeasurement):
if not isinstance(image, (SourceImageForMeasurement, CoordinatesForMeasurement)):
raise TypeError(
'Arguments "referenced_images" must have type '
'SourceImageForMeasurement.'
'SourceImageForMeasurement or CoordinatesForMeasurement.'
)
content.append(image)
if referenced_real_world_value_map is not None:
Expand Down
25 changes: 25 additions & 0 deletions tests/test_sr.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
VolumetricROIMeasurementsAndQualitativeEvaluations,
srread,
)
from highdicom.sr.content import CoordinatesForMeasurement
from highdicom.sr.utils import find_content_items
from highdicom import UID

Expand Down Expand Up @@ -2141,6 +2142,30 @@ def test_from_invalid_source_image_seg(self):
)


class TestCoordinatesForMeasurement(unittest.TestCase):

def setUp(self):
super().setUp()
self.source_image = SourceImageForRegion(
referenced_sop_class_uid='1.2.840.10008.5.1.4.1.1.2',
referenced_sop_instance_uid='1.2.3.4.5.6.7.8.9.10'
)

def test_construction(self):
item = CoordinatesForMeasurement(
graphic_type=GraphicTypeValues.MULTIPOINT,
graphic_data=np.array([[10, 20], [20, 30]]),
source_image=self.source_image
)

assert item.graphic_type == GraphicTypeValues.MULTIPOINT
assert item.GraphicData == [10, 20, 20, 30]
assert item.relationship_type == RelationshipTypeValues.INFERRED_FROM

assert len(item.ContentSequence) == 1
assert item.ContentSequence[0] == self.source_image
assert item.ContentSequence[0].relationship_type == RelationshipTypeValues.SELECTED_FROM

class TestReferencedSegment(unittest.TestCase):

def setUp(self):
Expand Down