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 segments to highdicom Segmentation object after its construction #299

Open
ramyayyagari opened this issue Sep 9, 2024 · 3 comments
Open
Assignees
Labels
question Further information is requested

Comments

@ramyayyagari
Copy link

From my understanding, the current version of highdicom doesn't allow for updating Segmentation objects after they are constructed. A previous version of highdicom (< 0.8.0) included the add_segments feature which allowed for adding segments to existing Segmentation objects, but this has since been deprecated.

I would like to update my Segmentation objects after they have been constructed. Since these objects are immutable, how do I work around this?

@CPBridge
Copy link
Collaborator

Hi @ramyayyagari thanks for the question.

Unfortunately adding segments to an existing seg gets quite tricky because of the way they are structured internally. If at all possible, therefore, I would restructure your process to enable creating a segmentation only once with multiple segments as in this section of the documentation.

However if for whatever reason that simply isn't possible, your best option is to simply create a new segmentation object using the normal constructor and copy everything from the existing object. Assuming that you have the original source images (i.e. the image files to which the segmentation applies), this will be a little verbose but straightforward.

Assuming you are working with a series of source instances as in the typical radiology case:

import pydicom
import highdicom as hd

# Read in the existing segmentation
old_seg = hd.seg.segread("old_segmentation.dcm")

# Read in the source images
source_image_paths = ["im1.dcm", "im2.dcm", "im3.dcm"]
source_images = [pydicom.dcmread(im) for im in source_image_paths]

# Get the segmentation mask from the original segmentation (note that if your source images are multiframe instances you would use the get_pixels_by_source_frame method instead)
source_sop_uids = [im.SOPInstanceUID for im in source_images]
old_mask = old_seg.get_pixels_by_source_instance(source_sop_uids)

# Concatenate the new mask to the old mask
new_mask = np.zeros(...) # shape slices x rows x columns x segments
combined_mask = np.concatenate([old_mask, new_mask], axis=3)

# Create a segment description for the new segment(s)
old_segment_descriptions = old_seg.SegmentSequence
new_segment_descriptions = [
    hd.seg.SegmentDescription(
        segment_number=len(old_segment_descriptions) + 1,
        segment_label='liver',
        segmented_property_category=codes.SCT.Organ,
        segmented_property_type=codes.SCT.Liver,
        algorithm_type=hd.seg.SegmentAlgorithmTypeValues.MANUAL,
    ),
   ...
]
combined_descriptions = old_segment_descriptions + new_segment_descriptions

# Create the new segmentation object combining old and new
combined_seg = hd.seg.Segmentation(
    source_images=source_images,
    pixel_array=combined_mask,
    segmentation_type=old_seg.segmentation_type,
    segment_descriptions=combined_descriptions,
    series_instance_uid=hd.UID(),
    series_number=old_seg.SeriesNumber,
    sop_instance_uid=hd.UID(),
    instance_number=old_seg.InstanceNumber,
    manufacturer=old_seg.Manufacturer,
    manufacturer_model_name=old_seg.ManufacturerModelName,
    software_versions=old_seg.SoftwareVersions,
    device_serial_number=old_seg.DeviceSerialNumber,
    ...  # there may be other metadata you need to copy
)

If you no longer have access to the source images, this gets a bit more fiddly since you need to deduce the information about them from the existing segmentation. This is possible but quite inconvenient at the moment. I actually plan to add some utility methods to do this in the near future. Further, if your segmentation frames are not spatially aligned 1:1 with your source frames, you will also need to copy over the geometry from the source image. I omitted this for the sake of clarity but can help if needed.

Note that I would generally recommend against copying the SOPInstanceUID and SeriesInstanceUID from the original image, since according to the standard these UIDs should refer only to a single immutable object. Therefore the example above generates new UIDs for the new seg. But in certain situations you may want to copy these across so that it really appears like you have added segments to an existing segmentation.

It probably would be good for us to provide an easier way to do this in the future...

@CPBridge CPBridge self-assigned this Sep 10, 2024
@CPBridge CPBridge added the question Further information is requested label Sep 10, 2024
@ramyayyagari
Copy link
Author

Thanks for clarifying! Looking forward to the utility methods for accessing source images from previous segmentations. Currently I do not have access to the source images, so I am loading these images every time I create a new Segmentation, which is taking up time. Thanks again!

@CPBridge
Copy link
Collaborator

I'll leave this issue open as a reminder to implement the utility method

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants