Skip to content

Commit 88fb5f7

Browse files
committed
Merge branch 'v0.27.0dev' into feature/pm_rebase
2 parents 298ccc3 + 5a13726 commit 88fb5f7

16 files changed

Lines changed: 720 additions & 78 deletions

docs/ann.rst

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ contains. The required metadata elements include:
3535
* A ``uid`` (``str`` or :class:`highdicom.UID`) uniquely identifying the group.
3636
Usually, you will want to generate a UID for this.
3737
* An ``annotated_property_category`` and ``annotated_property_type``
38-
(:class:`highdicom.sr.CodedConcept`) coded values (see :ref:`coding`)
38+
(:class:`highdicom.sr.CodedConcept`), coded values (see :ref:`coding`)
3939
describing the category and specific structure that has been annotated.
4040
* A ``graphic_type`` (:class:`highdicom.ann.GraphicTypeValues`) indicating the
4141
"form" of the annotations. Permissible values are ``"ELLIPSE"``, ``"POINT"``,
@@ -45,8 +45,23 @@ contains. The required metadata elements include:
4545
algorithm used to generate the annotations (``"MANUAL"``,
4646
``"SEMIAUTOMATIC"``, or ``"AUTOMATIC"``).
4747

48-
Further optional metadata may optionally be provided, see the API documentation
49-
for more information.
48+
Further optional metadata may optionally be provided, including:
49+
50+
* An ``algorithm_identification``
51+
(:class:`highdicom.AlgorithmIdentificationSequence`), specifying information
52+
about an algorithm that generated the annotations.
53+
* A list of ``anatomic_regions`` (a sequence of
54+
:class:`highdicom.sr.CodedConcept` objects), giving coded values (see
55+
:ref:`coding`) describing regions containing the annotations.
56+
* A list of ``primary_anatomic_structures`` (a sequence of
57+
:class:`highdicom.sr.CodedConcept` objects) giving coded values (see
58+
:ref:`coding`) describing anatomic structures of interest.
59+
* A free-text ``description`` (``str``) of the annotation group.
60+
* A ``display_color`` (:class:`highdicom.color.CIELabColor`) giving a
61+
recommended value for viewers to use to render these annotations. This is in
62+
CIE-Lab color space, but alternative constructors of the
63+
:class:`highdicom.color.CIELabColor` class allow conversion from RGB values
64+
or well-known color names.
5065

5166
The actual annotation data is passed to the group as a list of
5267
``numpy.ndarray`` objects, each of shape (*N* x *D*). *N* is the number of
@@ -88,6 +103,7 @@ Here is a simple example of constructing an annotation group:
88103
algorithm_type=hd.ann.AnnotationGroupGenerationTypeValues.MANUAL,
89104
graphic_type=hd.ann.GraphicTypeValues.POINT,
90105
graphic_data=graphic_data,
106+
display_color=hd.color.CIELabColor.from_string('turquoise'),
91107
)
92108
93109
Note that including two nuclei would be very unusual in practice: annotations
@@ -141,6 +157,7 @@ Here is the above example with an area measurement included:
141157
graphic_type=hd.ann.GraphicTypeValues.POINT,
142158
graphic_data=graphic_data,
143159
measurements=[area_measurement],
160+
display_color=hd.color.CIELabColor.from_string('lawngreen'),
144161
)
145162
146163
Constructing MicroscopyBulkSimpleAnnotation Objects
@@ -256,7 +273,7 @@ of matching groups, since the filters may match multiple groups.
256273
257274
# If there are no matches, an empty list is returned
258275
groups = ann.get_annotation_groups(
259-
annotated_property_type=Code('53982002', "SCT", "Cell membrane"),
276+
annotated_property_type=Code('53982002', 'SCT', 'Cell membrane'),
260277
)
261278
assert len(groups) == 0
262279

docs/highdicom_and_pydicom.rst

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,19 @@ There is a wide variety of DICOM objects defined in the standard, covering many
2626
types of images (X-Ray, CT, MRI, Microscopy, Ophthalmic images) as well as
2727
various types of image-derived information, such as Structured Reports,
2828
Annotations, Presentation States, Segmentations, and Parametric Maps. Formally,
29-
these "types" are known as Information Object Definitions (IODs). The standard
30-
requires different combinations of attributes are required for different IODs
31-
(e.g. the "Echo Time" attribute exists with the *MRImage* IOD but not within
32-
the *CTImage* IOD) ``pydicom`` represents all of these objects using a general
33-
``Dataset`` class, which implements behavior that is common to all of these
34-
objects. However, it does not attempt to specialize its representation to
35-
implement specific behaviors of these various IODs, leaving it up to the user
36-
to interpret the individual attributes in the file in each case.
29+
these "types" are known as Information Object Definitions (IODs). Each IOD in
30+
the standard requires different combinations of attributes. For example, the
31+
"Echo Time" attribute exists with the *MRImage* IOD but not within the
32+
*CTImage* IOD. ``pydicom`` represents all of these objects using the same
33+
general ``Dataset`` class, which implements behavior that is common to all
34+
DICOM objects However, it does not attempt to specialize its representation to
35+
implement IOD-specific behavior, leaving this up to the user.
3736

3837
The purpose of ``highdicom`` is to build upon ``pydicom`` to implement specific
39-
behaviors for various IODs, to make it easier to correctly create and work with
38+
behaviors for various IODs to make it easier to correctly create and work with
4039
**specific** types of DICOM object. ``highdicom`` defines sub-classes of
41-
``pydicom.Dataset`` that implement particular IODs, for example:
40+
``pydicom.Dataset`` that implement particular IODs, with a specific focus on
41+
IODs that store information derived from other images. For example:
4242

4343
- :class:`highdicom.Image` (this actually covers many IODs)
4444
- :class:`highdicom.seg.Segmentation`
@@ -63,11 +63,11 @@ they also have:
6363
ensuring correctness. The constructors guide you through which attributes are
6464
required and enforce inter-relationships between them required by the
6565
standard.
66-
- Several of these IODs also have further methods that allow you to search,
67-
filter, and access the information within them more easily.
66+
- Further methods that allow you to search, filter, and access the information
67+
within them more easily.
6868

69-
However, not all classes within ``highdicom`` are DICOM objects, and such
70-
objects are not sub-classes of ``pydicom.Dataset``. Notable examples include
69+
However, some classes within ``highdicom`` are not DICOM objects and as such
70+
are not sub-classes of ``pydicom.Dataset``. Notable examples include
7171
:class:`highdicom.Volume`,
7272
:class:`highdicom.spatial.ImageToReferenceTransformer` (and other similar
7373
objects), :class:`highdicom.io.ImageFileReader`.

docs/quickstart.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ For more information see :doc:`tid1500parsing`.
585585
assert measurement.value == 10.0
586586
587587
# Access the measurement's unit
588-
assert measurement.unit == codes.UCUM.mm
588+
assert measurement.unit == codes.UCUM.Millimeter
589589
590590
# Get the diameter measurement in this group
591591
evaluation = group.get_qualitative_evaluations(

docs/remote.rst

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ spatial patch from a large whole slide image from the IDC.
5656
)
5757
5858
# Read directly from the blob object using lazy frame retrieval
59-
with blob.open(mode="rb") as reader:
59+
with blob.open(mode="rb", chunk_size=500_000) as reader:
6060
im = hd.imread(reader, lazy_frame_retrieval=True)
6161
6262
# Grab an arbitrary region of tile full pixel matrix
@@ -80,6 +80,13 @@ spatial patch from a large whole slide image from the IDC.
8080
Figure produced by the above code snippet showing an arbitrary spatial
8181
region of a slide loaded directly from a Google Cloud bucket
8282

83+
It is important to set the `chunk_size` parameter carefully. This value is the
84+
number of bytes that are retrieved in a single request (set to around 500kB in
85+
the above example). Ideally this should be just large enough to retrieve a
86+
single frame of the image in one request, but any larger leads to unnecessary
87+
data being retrieved. The default value is 40MiB, which is orders of magnitude
88+
larger than the size of most image frames and therefore will be very inefficient.
89+
8390
As a further example, we use lazy frame retrieval to load only a specific set
8491
of segments from a large multi-organ segmentation of a CT image in the IDC
8592
stored in binary format (meaning each segment is stored using a separate set of

docs/seg.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ description includes the following information:
8585
a human readable ID and unique ID to a specific segment. This can be used,
8686
for example, to uniquely identify particular lesions over multiple imaging
8787
studies. These are passed as strings.
88+
- **Display Color**: (Optional) You can provide a recommended color as a
89+
:class:`highdicom.color.CIELabColor` to use when displaying this segment.
90+
Some viewers will use this information to decide what color to render the
91+
segment by default. This color should be provided in CIE-Lab color space, but
92+
alternative constructors of the :class:`highdicom.color.CIELabColor` class
93+
allow conversion from RGB values or well-known color names.
94+
8895

8996
Notice that the segment description makes use of coded concepts to ensure that
9097
the way a particular anatomical structure is described is standardized and
@@ -108,6 +115,7 @@ representing a liver that has been manually segmented.
108115
segmented_property_category=codes.SCT.Organ,
109116
segmented_property_type=codes.SCT.Liver,
110117
algorithm_type=hd.seg.SegmentAlgorithmTypeValues.MANUAL,
118+
display_color=hd.color.CIELabColor.from_string('red'),
111119
)
112120
113121
In this second example, we describe a segment representing a tumor that has
@@ -134,6 +142,7 @@ we must first provide more information about the algorithm used in an
134142
algorithm_type=hd.seg.SegmentAlgorithmTypeValues.AUTOMATIC,
135143
algorithm_identification=algorithm_identification,
136144
anatomic_regions=[codes.SCT.Kidney]
145+
display_color=hd.color.CIELabColor.from_rgb(0, 0, 255),
137146
)
138147
139148
For a description of how to access segment metadata in existing segmentations,

docs/tid1500.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ highdicom test data within the highdicom repository at
641641
nodule_measurement = hd.sr.Measurement(
642642
name=codes.SCT.Diameter,
643643
value=10.0,
644-
unit=codes.UCUM.mm,
644+
unit=codes.UCUM.Millimeter,
645645
)
646646
nodule_evaluation = hd.sr.QualitativeEvaluation(
647647
name=codes.DCM.LevelOfSignificance,
@@ -677,15 +677,15 @@ highdicom test data within the highdicom repository at
677677
aorta_measurement = hd.sr.Measurement(
678678
name=codes.SCT.Diameter,
679679
value=20.0,
680-
unit=codes.UCUM.mm,
680+
unit=codes.UCUM.Millimeter,
681681
)
682682
683683
# Construct the measurement group
684684
planar_group_2 = hd.sr.PlanarROIMeasurementsAndQualitativeEvaluations(
685685
referenced_region=region,
686686
tracking_identifier=aorta_roi_tracking_id,
687687
finding_type=codes.SCT.Aorta,
688-
finding_category=structure_code,
688+
finding_category=codes.SCT.AnatomicalStructure,
689689
measurements=[aorta_measurement],
690690
)
691691
@@ -715,7 +715,7 @@ highdicom test data within the highdicom repository at
715715
vol_group = hd.sr.VolumetricROIMeasurementsAndQualitativeEvaluations(
716716
referenced_volume_surface=volume_surface,
717717
tracking_identifier=volumetric_roi_tracking_id,
718-
finding_category=structure_code,
718+
finding_category=codes.SCT.AnatomicalStructure,
719719
finding_type=codes.SCT.Vertebra,
720720
measurements=[vol_measurement],
721721
)

docs/tid1500parsing.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ property (returns a ``float``), and the unit with the ``unit`` property.
304304
assert measurement.value == 10.0
305305
306306
# Access the measurement's unit
307-
assert measurement.unit == codes.UCUM.mm
307+
assert measurement.unit == codes.UCUM.Millimeter
308308
309309
Additionally, the properties ``method``, ``finding_sites``, ``qualifier``,
310310
``referenced_images``, and ``derivation`` allow you to access further optional

src/highdicom/ann/content.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
AnnotationGroupGenerationTypeValues,
1414
GraphicTypeValues,
1515
)
16+
from highdicom.color import CIELabColor
1617
from highdicom.content import AlgorithmIdentificationSequence
1718
from highdicom.sr.coding import CodedConcept
1819
from highdicom.uid import UID
@@ -194,7 +195,8 @@ def __init__(
194195
) = None,
195196
primary_anatomic_structures: None | (
196197
Sequence[Code | CodedConcept]
197-
) = None
198+
) = None,
199+
display_color: CIELabColor | None = None,
198200
):
199201
"""
200202
Parameters
@@ -238,6 +240,8 @@ def __init__(
238240
primary_anatomic_structures: Union[Sequence[Union[highdicom.sr.Code, highdicom.sr.CodedConcept]], None], optional
239241
Anatomic structure(s) the annotations represent
240242
(see CIDs for domain-specific primary anatomic structures)
243+
display_color: Union[highdicom.color.CIELabColor, None], optional
244+
A recommended color to render this annotation group.
241245
242246
""" # noqa: E501
243247
super().__init__()
@@ -293,6 +297,16 @@ def __init__(
293297
graphic_type = GraphicTypeValues(graphic_type)
294298
self.GraphicType = graphic_type.value
295299

300+
if display_color is not None:
301+
if not isinstance(display_color, CIELabColor):
302+
raise TypeError(
303+
'"display_color" must be of type '
304+
'highdicom.color.CIELabColor.'
305+
)
306+
self.RecommendedDisplayCIELabValue = list(
307+
display_color.value
308+
)
309+
296310
for i in range(len(graphic_data)):
297311
num_coords = graphic_data[i].shape[0]
298312
if graphic_type == GraphicTypeValues.POINT:

0 commit comments

Comments
 (0)