Parsing Measurement Reports

In addition to the ability to create TID 1500 Structured Reports, highdicom also includes functionality to help you find and extract information from existing SR documents in this format.

First you must get the SR dataset into the format of a highdicom class. You can do this using the highdicom.sr.srread() function:

import highdicom as hd

# This example is in the highdicom test data files in the repository
sr = hd.sr.srread("data/test_files/sr_document.dcm")

Alternatively, if you already have a pydicom.Dataset in memory, you can use the relevant from_dataset method like this:

import pydicom
import highdicom as hd

sr_dataset = pydicom.dcmread("data/test_files/sr_document.dcm")

# Use the appropriate class depending on the specific IOD, here it is a
# Comprehensive3DSR
sr = hd.sr.Comprehensive3DSR.from_dataset(sr_dataset)

If the Structured Report conforms to the TID 1500 measurement report template, when you access the content property, a highdicom.sr.MeasurementReport object will be returned. Otherwise, a general highdicom.sr.ContentSequence object is returned.

The resulting highdicom.sr.MeasurementReport object has methods that allow you to find and access the content of the report conveniently.

Searching For Measurement Groups

To search for measurement groups, the highdicom.sr.MeasurementReport class has highdicom.sr.MeasurementReport.get_image_measurement_groups(), highdicom.sr.MeasurementReport.get_planar_roi_measurement_groups(), and highdicom.sr.MeasurementReport.get_volumetric_roi_measurement_groups() methods, each of which returns a list of the measurement groups of the three different types from the structured SR. You can additionally provide filters to return only those measurement groups that meet certain criteria.

The available search criteria include: tracking UID, finding type, finding site, referenced SOP instance UID, and referenced SOP class UID. If you provide multiple criteria, the methods return those groups that meet all the specified criteria.

The returned objects are of type highdicom.sr.MeasurementsAndQualitativeEvaluations, highdicom.sr.PlanarROIMeasurementsAndQualitativeEvaluations, or highdicom.sr.VolumetricROIMeasurementsAndQualitativeEvaluations, respectively, representing the entire sub-template in the SR content tree.

Here are just some examples of using these methods to find measurement groups of interest within a measurement report. As an example SR document, we use the SR document created on the previous page (see Putting It All Together for the relevant snippet).

import highdicom as hd
from pydicom.sr.codedict import codes

# This example is in the highdicom test data files in the repository
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Get a list of all image measurement groups referencing an image with a
# particular SOP Instance UID
groups = sr.content.get_image_measurement_groups(
    referenced_sop_instance_uid="1.3.6.1.4.1.5962.1.1.1.1.1.20040119072730.12322",
)
assert len(groups) == 1

# Get a list of all image measurement groups with a particular tracking UID
groups = sr.content.get_image_measurement_groups(
    tracking_uid="1.2.826.0.1.3680043.10.511.3.77718622501224431322963356892468048",
)
assert len(groups) == 1

# Get a list of all planar ROI measurement groups with finding type "Nodule"
# AND finding site "Lung"
groups = sr.content.get_planar_roi_measurement_groups(
    finding_type=codes.SCT.Nodule,
    finding_site=codes.SCT.Lung,
)
assert len(groups) == 1

# Get a list of all volumetric ROI measurement groups (with no filters)
groups = sr.content.get_volumetric_roi_measurement_groups()
assert len(groups) == 1

Additionally for highdicom.sr.MeasurementReport.get_planar_roi_measurement_groups(), and highdicom.sr.MeasurementReport.get_volumetric_roi_measurement_groups() it is possible to filter by graphic type and reference type (how the ROI is specified in the measurement group).

To search by graphic type, pass an instance of either the highdicom.sr.GraphicTypeValues or highdicom.sr.GraphicTypeValues3D enums:

import highdicom as hd
from pydicom.sr.codedict import codes

# This example is in the highdicom test data files in the repository
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Get a list of all planar ROI measurement groups with graphic type CIRCLE
groups = sr.content.get_planar_roi_measurement_groups(
    graphic_type=hd.sr.GraphicTypeValues.CIRCLE,
)
assert len(groups) == 1

For reference type, you should provide one of the following values (which reflect how the SR document stores the information internally):

  • CodedConcept(value="111030", meaning="Image Region", scheme_designator="DCM") aka pydicom.sr.codedict.codes.DCM.ImageRegion for ROIs defined in the SR as image regions (vector coordinates for planar regions defined within the SR document).

  • CodedConcept(value="121231", meaning="Volume Surface", scheme_designator="DCM") aka pydicom.sr.codedict.codes.DCM.VolumeSurface for ROIs defined in the SR as a volume surface (vector coordinates for a volumetric region defined within the SR document).

  • CodedConcept(value="121191", meaning="Referenced Segment", scheme_designator="DCM") aka pydicom.sr.codedict.codes.DCM.ReferencedSegment for ROIs defined in the SR indirectly by referencing a segment stored in a DICOM Segmentation Image.

  • CodedConcept(value="121191", meaning="Region In Space", scheme_designator="DCM") For ROIs defined in the SR indirectly by referencing a region stored in a DICOM RT Struct object (this is not currently supported by the highdicom constructor, but is an option in the standard). Unfortunately this code is not including in pydicom.sr.codedict.codes at this time.

import highdicom as hd
from pydicom.sr.codedict import codes

# This example is in the highdicom test data files in the repository
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Get a list of all planar ROI measurement groups stored as regions
groups = sr.content.get_planar_roi_measurement_groups(
    reference_type=codes.DCM.ImageRegion,
)
assert len(groups) == 2

# Get a list of all volumetric ROI measurement groups stored as volume
# surfaces
groups = sr.content.get_volumetric_roi_measurement_groups(
    reference_type=codes.DCM.VolumeSurface,
)
assert len(groups) == 1

Accessing Data in Measurement Groups

Once you have found measurement groups, there are various properties on the returned object that allow you to access the information that you may need. These may be in the form of basic Python data types extracted from the measurement group’s content items, or highdicom classes representing full sub-templates that in turn have methods and properties defined on them. These classes are the same classes that you use to construct the objects.

The following example demonstrates some examples, see the API documentation of the relevant class for a full list.

import highdicom as hd
import numpy as np
from pydicom.sr.codedict import codes

# This example is in the highdicom test data files in the repository
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Use the first (only) image measurement group as an example
group = sr.content.get_image_measurement_groups()[0]

# tracking_identifier returns a Python str
assert group.tracking_identifier == "Image0001"

# tracking_uid returns a hd.UID, a subclass of str
assert group.tracking_uid == "1.2.826.0.1.3680043.10.511.3.77718622501224431322963356892468048"

# source_images returns a list of hd.sr.SourceImageForMeasurementGroup, which
# in turn have some properties to access data
assert isinstance(group.source_images[0], hd.sr.SourceImageForMeasurementGroup)
assert group.source_images[0].referenced_sop_instance_uid == "1.3.6.1.4.1.5962.1.1.1.1.1.20040119072730.12322"

# for the various optional pieces of information in a measurement, accessing
# the relevant property returns None if the information is not present
assert group.finding_type is None

# Now use the first planar ROI group as a second example
group = sr.content.get_planar_roi_measurement_groups()[0]

# finding_type returns a CodedConcept
assert group.finding_type == codes.SCT.Nodule

# finding_sites returns a list of hd.sr.FindingSite objects (a sub-template)
assert isinstance(group.finding_sites[0], hd.sr.FindingSite)
# the value of a finding site is a CodedConcept
assert group.finding_sites[0].value == codes.SCT.Lung

# reference_type returns a CodedConcept (the same values used above for
# filtering)
assert group.reference_type == codes.DCM.ImageRegion

# since this has reference type ImageRegion, we can access the referenced roi
# using 'roi', which will return an hd.sr.ImageRegion object
assert isinstance(group.roi, hd.sr.ImageRegion)

# the graphic type and actual ROI coordinates (as a numpy array) can be
# accessed with the graphic_type and value properties of the roi
assert group.roi.graphic_type == hd.sr.GraphicTypeValues.CIRCLE
assert isinstance(group.roi.value, np.ndarray)
assert group.roi.value.shape == (2, 2)

A volumetric group returns a highdicom.sr.VolumeSurface or list of highdicom.sr.ImageRegion objects, depending on the reference type. If instead, a planar/volumetric measurement group uses the ReferencedSegment reference type, the referenced segment can be accessed by the group.referenced_segmention_frame property (for planar groups) or group.referenced_segment property (for volumetric groups), which return objects of type highdicom.sr.ReferencedSegmentationFrame and highdicom.sr.ReferencedSegment respectively.

Searching for Measurements

Each measurement group may optionally contain any number of “measurements”, represented by the TID300 “Measurement” template and the highdicom.sr.Measurement class that implements it in highdicom. A measurement contains a numerical measurement derived from the image, along with the physical unit of the measurement and various other optional descriptive metadata

You can search for measurements within a measurements group using the get_measurements() method on the relevant measurement group class. You can optionally provide a name parameter, which should be a coded value that allows you to find measurements with a particular name.

import highdicom as hd
from pydicom.sr.codedict import codes

# Use the same example file in the highdicom test data
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Use the first planar measurement group as an example
group = sr.content.get_planar_roi_measurement_groups()[0]

# Get a list of all measurements
measurements = group.get_measurements()

# Get a list of measurements for diameter
measurements = group.get_measurements(name=codes.SCT.Diameter)

Note that although there will usually be only a single measurement with a given name within a measurement group, multiple measurements with the same name are not disallowed by the standard. Consequently, the get_measurements() method returns a list containing 0 or more measurements.

Accessing Data in Measurements

You can access the name of a measurement with the name property (returns a highdicom.sr.CodedConcept), its numerical value with the value property (returns a float), and the unit with the unit property.

import highdicom as hd
from pydicom.sr.codedict import codes

# Use the same example file in the highdicom test data
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Use the first planar measurement group as an example
group = sr.content.get_planar_roi_measurement_groups()[0]

# Get the diameter measurement in this group
measurement = group.get_measurements(name=codes.SCT.Diameter)[0]

# Access the measurement's name
assert measurement.name == codes.SCT.Diameter

# Access the measurement's value
assert measurement.value == 10.0

# Access the measurement's unit
assert measurement.unit == codes.UCUM.mm

Additionally, the properties method, finding_sites, qualifier, referenced_images, and derivation allow you to access further optional metadata that may be present in the stored measurement.

Searching for Evaluations

In addition to numerical measurements, measurement groups may also contain “Qualitative Evaluations”. These contain an evaluation of the image represented using a coded concept.

Similar to measurements, you can search for evaluations with the get_qualitative_evaluations() method. You can optionally filter by name with the name parameter. You can access the name and value of the returned evaluations with the name and value properties.

import highdicom as hd
from pydicom.sr.codedict import codes

# Use the same example file in the highdicom test data
sr = hd.sr.srread("data/test_files/sr_document_with_multiple_groups.dcm")

# Use the first planar measurement group as an example
group = sr.content.get_planar_roi_measurement_groups()[0]

# Get the level of significance evaluation in this group
evaluation = group.get_qualitative_evaluations(
    name=codes.DCM.LevelOfSignificance
)[0]

# Access the evaluation's name
assert evaluation.name == codes.DCM.LevelOfSignificance

# Access the evaluation's value
assert evaluation.value == codes.SCT.NotSignificant