The TID1500 Measurement Report Template

The TID1500 “Measurement Report” template is a general-purpose template for communicating measurements and qualitative qualitative evaluations derived from one or more images or regions of images. It is recommended to read the previous page on Structured Report (SR) Overview before this page.

Highdicom represents the various sub-templates of the TID1500 template as Python classes. Using these classes will guide you through the process of creating TID 1500 SRs in a modular and structured way, and will perform various checks on the inputs you provide.

Overview of TID1500 Content

A diagram of the structure of TID1500 content is shown here:

TID1500 diagram

Simplified diagram of the structure of the TID1500 template and major subtemplates. Note that this is intended to give a quick overview, please refer to the standard itself for full details.

At the top level, the Measurement Report template (highdicom.sr.MeasurementReport) represents a report containing various measurements and various metadata about the process through which they were created.

A measurement report contains one or more “Measurement Groups”, where each group contains measurements and/or qualitative evaluations about a particular image or image region. There are three types of Measurement Group, each of which refer to different types of region:

A single Measurement Report may contain a mixture of Measurement Groups of these different types in any combination (as long as there is at least one group).

Each Measurement Group contains a number of Measurements (TID300) - numerical values derived from an image, such as a length or volume - and/or Qualitative Evaluations - categorical values derived from an image, such as classification of a tumor morphology.

When constructing the content, it is necessary to start at the bottom of the content tree with the Measurements and Evaluations and work up, by adding them into Measurement Groups, adding these groups to a Measurement Report, and then creating the document that contains the report. However, here we will describe the structure from the top down as it makes the big picture clearer.

Measurement Report (TID1500)

Every TID1500 Structured Report contains exactly one Measurement Report at the root of its content tree. This is represented by the class highdicom.sr.MeasurementReport.

The first ingredient in the Measurement Report is the “Observation Context”, which contains metadata describing the way the observations that led to the report were made. This includes information such as the person or device that made the observations, and the subject about which the observations were made:

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

observer_person_context = hd.sr.ObserverContext(
    observer_type=codes.DCM.Person,
    observer_identifying_attributes=hd.sr.PersonObserverIdentifyingAttributes(
        name='Doe^John'
    )
)
observer_device_context = hd.sr.ObserverContext(
    observer_type=codes.DCM.Device,
    observer_identifying_attributes=hd.sr.DeviceObserverIdentifyingAttributes(
        uid=hd.UID()
    )
)
observation_context = hd.sr.ObservationContext(
    observer_person_context=observer_person_context,
    observer_device_context=observer_device_context,
)

The second required ingredient is a procedure code describing the procedure that was performed to result in the observations. Finally, we have the image measurement groups that the report contains (described below). There are some further optional parameters, such as a title for the report. Combining these we can construct the Measurement Report, and use it to construct the SR document:

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

measurement_report = hd.sr.MeasurementReport(
    observation_context=observation_context,  # from above
    procedure_reported=codes.LN.CTUnspecifiedBodyRegion,
    imaging_measurements=[...],  # list of measurement groups, see below
    title=codes.DCM.ImagingMeasurementReport,
)

# Create the Structured Report instance
sr_dataset = hd.sr.Comprehensive3DSR(
    evidence=[...],  # all datasets referenced in the report
    content=measurement_report,
    series_number=1,
    series_instance_uid=hd.UID(),
    sop_instance_uid=hd.UID(),
    instance_number=1,
    manufacturer='Manufacturer'
)

Measurement Groups

A Measurement Report contains one or more Measurement Groups. There are three types of Measurement Groups, corresponding to entire images, 2D regions of interest, and 3D regions of interest. The three types may be mixed and matched within a single Measurement Report in any combination.

Measurements And Qualitative Evaluations Group (TID1501)

The first, and simplest, type of Measurement Group applies to one or more entire images (or alternatively one or more entire frames in the case of multiframe source images). This is implemented using highdicom.sr.MeasurementsAndQualitativeEvaluations.

This class also accepts a parameter source_images, which is a sequence of highdicom.sr.SourceImageForMeasurementGroup items specifying the images (or frames) to which the measurement group applies. If this is omitted, the measurement group is assumed to include all images referenced in the SR document (as passed in the evidence parameter of the relevant Structured Report object’s __init__ method).

The following is a simple example:

import highdicom as hd
from pydicom import dcmread

im = dcmread('/path/to/file.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Image0001',
   uid=hd.UID(),
)

# An object describing the source image for the measurements
source_image = hd.sr.SourceImageForMeasurementGroup.from_source_image(im)

# Construct the measurement group
group = hd.sr.MeasurementsAndQualitativeEvaluations(
   source_images=[source_image],
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

Planar ROI Image Measurements Group (TID1410)

This type of Measurement Group applies to a specific planar sub-region of the source image or images. This is implemented in the class highdicom.sr.PlanarROIMeasurementsAndQualitativeEvaluations.

This class takes a parameter specifying the region. There are two distinct options here:

  • referenced_region: The image region is specified directly in the SR using a highdicom.sr.ImageRegion or highdicom.sr.ImageRegion3D passed as the referenced_region parameter. In this case, the coordinates defining the region are stored within the measurement group itself. The choice between highdicom.sr.ImageRegion and highdicom.sr.ImageRegion3D determines whether the image region is defined in 2D image coordinates or 3D frame-of-reference coordinates. Either way, the region must be planar.

  • referenced_segment: The region is specified indirectly as a reference to a single slice of a single segment stored in a separate DICOM Segmentation Image object, specified by passing a highdicom.sr.ReferencedSegmentationFrame to the referenced_segment parameter, which contains UIDs to identify the Segmentation Image along with the segment number of the specific segment and the frames within which it is stored.

Note that either referenced_region or referenced_segment should be passed, and not both (or neither).

The following example uses an highdicom.sr.ImageRegion as the referenced_region:

import highdicom as hd
import numpy as np
from pydicom import dcmread

im = dcmread('/path/to/file.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Region0001',
   uid=hd.UID(),
)

# Define the image region (a circle) using image coordinates
region = hd.sr.ImageRegion(
   graphic_type=hd.sr.GraphicTypeValues.CIRCLE,
   graphic_data=np.array([[45.0, 55.0], [45.0, 65.0]]),
   source_image=hd.sr.SourceImageForRegion.from_source_image(im),
)

# Construct the measurement group
group = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
   referenced_region=region,
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

This example uses an highdicom.sr.ImageRegion3D as the referenced_region:

import highdicom as hd
import numpy as np
from pydicom import dcmread

im = dcmread('/path/to/file.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Region3D0001',
   uid=hd.UID(),
)

# Define the image region (a point) using frame-of-reference coordinates
region = hd.sr.ImageRegion3D(
   graphic_type=hd.sr.GraphicTypeValues3D.POINT,
   graphic_data=np.array([[123.5, 234.1, -23.7]]),
   frame_of_reference_uid=im.FrameOfReferenceUID,
)

# Construct the measurement group
group = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
   referenced_region=region,
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

The final example uses an highdicom.sr.ReferencedSegmentationFrame as the referenced_segment:

import highdicom as hd
import numpy as np
from pydicom import dcmread

# The image dataset referenced
im = dcmread('/path/to/file.dcm')

# A segmentation dataset, assumed to contain a segmentation of the source
# image above
seg = dcmread('/path/to/seg.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Region3D0001',
   uid=hd.UID(),
)

# Define the image region using a specific segment from the segmentation
ref_segment = hd.sr.ReferencedSegmentationFrame.from_segmentation(
   segmentation=seg,
   segment_number=1,
)

# Construct the measurement group
group = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
   referenced_segment=ref_segment,
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

Volumetric ROI Image Measurements Group (TID1411)

This type of Measurement Group applies to a specific volumetric sub-region of the source image or images. This is implemented in the class highdicom.sr.VolumetricROIMeasurementsAndQualitativeEvaluations.

Like the similar Planar ROI class, this class takes a parameter specifying the region. In this case there are three options:

  • referenced_regions: The image region is specified directly in the SR in image coordinates using one or more objects of type highdicom.sr.ImageRegion passed as the referenced_regions parameter, representing the volumetric region as a set of 2D regions across multiple images or frames.

  • referenced_volume_surface: The region is specified directly in the SR as a single volumetric region defined in frame of reference coordinates using a single highdicom.sr.VolumeSurface object passed to the referenced_volume_surface parameter.

  • referenced_segment: The region is specified indirectly as a reference to an entire segment (which may spread across multiple images or frames) of a Segmentation Image object, specified by passing a highdicom.sr.ReferencedSegment to the referenced_segment parameter, which contains UIDs to identify the Segmentation Image along with the segment number of the specific segment within it.

Note that exactly one of referenced_regions, referenced_volume_surface, or referenced_segment should be passed.

The following example uses a list of highdicom.sr.ImageRegion objects as the referenced_regions:

import highdicom as hd
import numpy as np
from pydicom import dcmread

im1 = dcmread('/path/to/file1.dcm')
im2 = dcmread('/path/to/file2.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Region0001',
   uid=hd.UID(),
)

# Define the image regions (a circle in two images) using image coordinates
region1 = hd.sr.ImageRegion(
   graphic_type=hd.sr.GraphicTypeValues.CIRCLE,
   graphic_data=np.array([[45.0, 55.0], [45.0, 65.0]]),
   source_image=hd.sr.SourceImageForRegion.from_source_image(im1),
)
region2 = hd.sr.ImageRegion(
   graphic_type=hd.sr.GraphicTypeValues.CIRCLE,
   graphic_data=np.array([[40.0, 50.0], [40.0, 60.0]]),
   source_image=hd.sr.SourceImageForRegion.from_source_image(im2),
)

# Construct the measurement group
group = hd.sr.VolumetricROIMeasurementsAndQualitativeEvaluations(
   referenced_regions=[region1, region2],
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

This example uses a highdicom.sr.VolumeSurface object as the referenced_volume_surface:

import highdicom as hd
import numpy as np
from pydicom import dcmread

im = dcmread('/path/to/file.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Region0001',
   uid=hd.UID(),
)

# Define the image region (a point) using frame-of-reference coordinates
volume_surface = hd.sr.VolumeSurface(
    graphic_type=hd.sr.GraphicTypeValues.POINT,
    graphic_data=np.array([[123.5, 234.1, -23.7]]),
    source_images=[hd.sr.SourceImageForSegmentation.from_source_image(im)],
    frame_of_reference_uid=im.FrameOfReferenceUID,
)

# Construct the measurement group
group = hd.sr.VolumetricROIMeasurementsAndQualitativeEvaluations(
   referenced_volume_surface=volume_surface,
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

The final example uses an highdicom.sr.ReferencedSegment as the referenced_segment:

import highdicom as hd
import numpy as np
from pydicom import dcmread

# The image dataset referenced
im = dcmread('/path/to/file.dcm')

# A segmentation dataset, assumed to contain a segmentation of the source
# image above
seg = dcmread('/path/to/seg.dcm')

# A tracking identifier for this measurement group
tracking_id = hd.sr.TrackingIdentifier(
   identifier='Region3D0001',
   uid=hd.UID(),
)

# Define the image region using a specific segment from the segmentation
ref_segment = hd.sr.ReferencedSegment.from_segmentation(
   segmentation=seg,
   segment_number=1,
)

# Construct the measurement group
group = hd.sr.VolumetricROIMeasurementsAndQualitativeEvaluations(
   referenced_segment=ref_segment,
   tracking_identifier=tracking_id,
   measurements=[...],
   qualitative_evaluations=[...],
)

Further Parameters for Measurement Groups

The three types of measurement group are more alike than different. The following parameters may be used for all Measurement Groups, regardless of type (some have been omitted for brevity):

Measurements and Qualitative Evaluations

Finally, we get down to the bottom of the content tree, and the measurements and qualitative evaluations themselves. Information derived from the images or image regions represented by the measurement group may be stored as either measurements, qualitative evaluations, or a mixture or the two. These two concepts play a similar role in the SR, but measurements have numerical values and qualitative evaluations have categorical values.

Qualitative Evaluations

A Qualitative Evaluation is essentially a categorical value inferred from an image. For example, this could represent a diagnosis derived from the referenced region or a severity grading. These are represented in highdicom using the class highdicom.sr.QualitativeEvalution, which is essentially a single highdicom.sr.CodeContentItem within a special template.

To create a Qualitative Evaluation, just pass the name and value parameters as coded values:

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

# An evaluation of disease severity as "mild"
severity_item = hd.sr.QualitativeEvalution(
   name=codes.SCT.Severity,
   value=codes.SCT.Mild,
)

# An evaluation of tumor morphology as adenocarcinoma
morphology_item = hd.sr.QualitativeEvalution(
   name=codes.SCT.AssociatedMorphology,
   value=codes.SCT.Anenocarcinoma,
)

Measurements (TID300)

A Measurement is essentially a numerical (decimal) value derived from the image or image region. In highdicom, a measurement is represented by the class highdicom.sr.Measurement. It is a small template that contains at its core a highdicom.sr.NumContentItem containing the value, a highdicom.sr.CodeContentItem specifying the unit of the measurement, and optionally several more content items describing further context or qualifications for the measurement.

Here is a basic example:

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

# A volume measurement
measurement = hd.sr.Measurement(
   name=codes.SCT.Volume,
   value=1983.123,
   unit=codes.UCUM.CubicMillimeter,
)

In addition, the following optional parameters are available (see the API reference for more information):

  • Qualifier: Qualification of the measurement.

  • Tracking Identifier: Identifier for uniquely identifying and tracking measurements.

  • Algorithm: Identification of algorithm used for making measurements.

  • Derivation: How the value was computed.

  • Finding Sites: Coded description of one or more anatomic locations corresponding to the image region from which measurement was taken.

  • Method: Measurement method.

  • Properties: Measurement properties, including qualitative 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: Referenced images which were used as sources for the measurement.

  • Referenced Real World Value Map: Referenced real world value map for referenced source images used to generate the measurement.

Putting It All Together

The snippet below is a full example of creating an SR document using the TID1500 template. You can find the file created by this snippet in the highdicom test data within the highdicom repository at data/test_files/sr_document_with_multiple_groups.dcm.

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

im = pydicom.dcmread("data/test_files/ct_image.dcm")

# Information about the observer
observer_person_context = hd.sr.ObserverContext(
    observer_type=codes.DCM.Person,
    observer_identifying_attributes=hd.sr.PersonObserverIdentifyingAttributes(
        name='Doe^John'
    )
)
observer_device_context = hd.sr.ObserverContext(
    observer_type=codes.DCM.Device,
    observer_identifying_attributes=hd.sr.DeviceObserverIdentifyingAttributes(
        uid=hd.UID()
    )
)
observation_context = hd.sr.ObservationContext(
    observer_person_context=observer_person_context,
    observer_device_context=observer_device_context,
)

# An object describing the source image for the measurements
source_image = hd.sr.SourceImageForMeasurementGroup.from_source_image(im)

# First, we define an image measurement group for the CT image describing
# the intensity histogram at a certain vertebral level

# A tracking identifier for this measurement group
im_tracking_id = hd.sr.TrackingIdentifier(
   identifier='Image0001',
   uid=hd.UID(),
)

# A measurement using an IBSI code (not in pydicom)
histogram_intensity_code = hd.sr.CodedConcept(
    value="X6K6",
    meaning="Intensity Histogram Mean",
    scheme_designator="IBSI",
)
hist_measurement = hd.sr.Measurement(
    name=histogram_intensity_code,
    value=-119.0738525390625,
    unit=codes.UCUM.HounsfieldUnit,
)
im_evaluation = hd.sr.QualitativeEvaluation(
    name=codes.SCT.AnatomicalPosition,
    value=codes.SCT.LevelOfT4T5IntervertebralDisc,
)

# Construct the measurement group
im_group = hd.sr.MeasurementsAndQualitativeEvaluations(
   source_images=[source_image],
   tracking_identifier=im_tracking_id,
   measurements=[hist_measurement],
   qualitative_evaluations=[im_evaluation],
)

# Next, we define a planar ROI measurement group describing a lung nodule

# A tracking identifier for this measurement group
lung_nodule_roi_tracking_id = hd.sr.TrackingIdentifier(
   identifier='LungNodule0001',
   uid=hd.UID(),
)

# Define the image region (a circle) using image coordinates
region = hd.sr.ImageRegion(
   graphic_type=hd.sr.GraphicTypeValues.CIRCLE,
   graphic_data=np.array([[45.0, 55.0], [45.0, 65.0]]),
   source_image=hd.sr.SourceImageForRegion.from_source_image(im),
)
nodule_measurement = hd.sr.Measurement(
    name=codes.SCT.Diameter,
    value=10.0,
    unit=codes.UCUM.mm,
)
nodule_evaluation = hd.sr.QualitativeEvaluation(
    name=codes.DCM.LevelOfSignificance,
    value=codes.SCT.NotSignificant,
)

# Construct the measurement group
planar_group_1 = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
   referenced_region=region,
   tracking_identifier=lung_nodule_roi_tracking_id,
   finding_type=codes.SCT.Nodule,
   finding_category=codes.SCT.MorphologicallyAbnormalStructure,
   finding_sites=[hd.sr.FindingSite(codes.SCT.Lung)],
   measurements=[nodule_measurement],
   qualitative_evaluations=[nodule_evaluation],
)

# Next, we define a second planar ROI measurement group describing the
# aorta

# A tracking identifier for this measurement group
aorta_roi_tracking_id = hd.sr.TrackingIdentifier(
   identifier='Aorta0001',
   uid=hd.UID(),
)

# Define the image region (a circle) using image coordinates
region = hd.sr.ImageRegion(
   graphic_type=hd.sr.GraphicTypeValues.POLYLINE,
   graphic_data=np.array([[25.0, 45.0], [45.0, 45.0], [45.0, 65.0], [25.0, 65.0]]),
   source_image=hd.sr.SourceImageForRegion.from_source_image(im),
)
aorta_measurement = hd.sr.Measurement(
    name=codes.SCT.Diameter,
    value=20.0,
    unit=codes.UCUM.mm,
)

# Construct the measurement group
planar_group_2 = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
   referenced_region=region,
   tracking_identifier=aorta_roi_tracking_id,
   finding_type=codes.SCT.Aorta,
   finding_category=structure_code,
   measurements=[aorta_measurement],
)

# Finally, we define a volumetric ROI measurement group describing a
# vertebral body

# A tracking identifier for this measurement group
volumetric_roi_tracking_id = hd.sr.TrackingIdentifier(
   identifier='Vertebra0001',
   uid=hd.UID(),
)

# Define the region (a point) using frame of reference coordinates
volume_surface = hd.sr.VolumeSurface(
    graphic_type=hd.sr.GraphicTypeValues3D.POINT,
    graphic_data=np.array([[123.5, 234.1, -23.7]]),
    source_images=[hd.sr.SourceImageForSegmentation.from_source_image(im)],
    frame_of_reference_uid=im.FrameOfReferenceUID,
)
vol_measurement = hd.sr.Measurement(
    name=codes.SCT.Volume,
    value=200.0,
    unit=codes.UCUM.CubicMillimeter,
)

# Construct the measurement group
vol_group = hd.sr.VolumetricROIMeasurementsAndQualitativeEvaluations(
   referenced_volume_surface=volume_surface,
   tracking_identifier=volumetric_roi_tracking_id,
   finding_category=structure_code,
   finding_type=codes.SCT.Vertebra,
   measurements=[vol_measurement],
)

measurement_report = hd.sr.MeasurementReport(
    observation_context=observation_context,  # from above
    procedure_reported=codes.LN.CTUnspecifiedBodyRegion,
    imaging_measurements=[im_group, planar_group_1, planar_group_2, vol_group],
    title=codes.DCM.ImagingMeasurementReport,
)

# Create the Structured Report instance
sr_dataset = hd.sr.Comprehensive3DSR(
    evidence=[im],  # all datasets referenced in the report
    content=measurement_report,
    series_number=1,
    series_instance_uid=hd.UID(),
    sop_instance_uid=hd.UID(),
    instance_number=1,
    manufacturer='Manufacturer'
)
sr_dataset.save_as("sr_document_with_multiple_groups.dcm")