Git Product home page Git Product logo

rt-utils's Introduction

A minimal Python library for RT Struct manipulation

Python version PyPI version PyPI - License


RT-Utils is motivated to allow physicians and other users to view the results of segmentation performed on a series of DICOM images. RT-Utils allows you to create or load RT Structs, extract 3d masks from RT Struct ROIs, easily add one or more regions of interest, and save the resulting RT Struct in just a few lines! You can also use the RT-Utils for merging two existing RT Structs to one file.

How it works

RT-Utils provides a builder class to faciliate the creation and loading of an RT Struct. From there, you can add ROIs through binary masks and optionally input the colour of the region along with the region name.

The format for the ROI mask is an nd numpy array of type bool. It is an array of 2d binary masks, one plane for each slice location within the DICOM series. The slices should be sorted in ascending order within the mask. Through these masks, we extract the contours of the regions of interest and place them within the RT Struct file. Note that there is currently only support for the use of one frame of reference UID and structered set ROI sequence. Also note that holes within the ROI may be handled poorly.

Installation

pip install rt_utils

Installation in editable mode

git clone https://github.com/qurit/rt-utils.git
cd rt-utils
pip install -e .

Creating new RT Structs

from rt_utils import RTStructBuilder

# Create new RT Struct. Requires the DICOM series path for the RT Struct.
rtstruct = RTStructBuilder.create_new(dicom_series_path="./testlocation")

# ...
# Create mask through means such as ML
# ...

# Add the 3D mask as an ROI.
# The colour, description, and name will be auto generated
rtstruct.add_roi(mask=MASK_FROM_ML_MODEL)

# Add another ROI, this time setting the color, description, and name
rtstruct.add_roi(
  mask=MASK_FROM_ML_MODEL, 
  color=[255, 0, 255], 
  name="RT-Utils ROI!"
)

rtstruct.save('new-rt-struct')

Adding to existing RT Structs

from rt_utils import RTStructBuilder
import matplotlib.pyplot as plt

# Load existing RT Struct. Requires the series path and existing RT Struct path
rtstruct = RTStructBuilder.create_from(
  dicom_series_path="./testlocation", 
  rt_struct_path="./testlocation/rt-struct.dcm"
)

# Add ROI. This is the same as the above example.
rtstruct.add_roi(
  mask=MASK_FROM_ML_MODEL, 
  color=[255, 0, 255], 
  name="RT-Utils ROI!"
)

rtstruct.save('new-rt-struct')

Creation Results

The results of a generated ROI with a dummy mask, as viewed in Slicer.

The results of a generated ROI with a liver segmentation model, as viewed in Slicer. (Note the underlying patient data has been hidden)

Loading an existing RT Struct contour as a mask

from rt_utils import RTStructBuilder
import matplotlib.pyplot as plt

# Load existing RT Struct. Requires the series path and existing RT Struct path
rtstruct = RTStructBuilder.create_from(
  dicom_series_path="./testlocation", 
  rt_struct_path="./testlocation/rt-struct.dcm"
)

# View all of the ROI names from within the image
print(rtstruct.get_roi_names())

# Loading the 3D Mask from within the RT Struct
mask_3d = rtstruct.get_roi_mask_by_name("ROI NAME")

# Display one slice of the region
first_mask_slice = mask_3d[:, :, 0]
plt.imshow(first_mask_slice)
plt.show()

Loading Results

The results of a loading an exisiting ROI as a mask, as viewed in Python.

Merging two existing RT Structs

To be able to merge two RT Structs it is important that both RT Structs have to belong to the same image series, e.g. if there is one set for the organs at risk and one set for the target volume(s).

from rt_utils import RTStructMerger

# Load existing RT Structs and corresponding image series and merge them into one RTStruct
merged_rt_struct = RTStructMerger.merge_rtstructs(
  dicom_series_path="./testlocation",
  rt_struct_path1="./testlocation/rt-struct1.dcm",
  rt_struct_path2="./testlocation/rt-struct2.dcm"
  )
merged_rt_struct.save('merged-rt-struct')

Additional Parameters

The add_roi method of our RTStruct class has a multitude of optional parameters available. Below is a comprehensive list of all these parameters and what they do.

  • color: This parameter can either be a colour string such as '#ffffff' or a RGB value as a list such as '[255, 255, 255]'. This parameter will dictate the colour of your ROI when viewed in a viewing program. If no colour is provided, RT Utils will pick from our internal colour palette based on the ROI Number of the ROI.
  • name: A str value that defaults to none. Used to set the name of the ROI within the RT Struct. If the name is none, RT Utils will set a name of ROI-{ROI Number}.
  • description: A str value that sets the description of the ROI within the RT Struct. If no value is provided, the description is just left blank.
  • use_pin_hole: A boolean value that defaults to false. If set to true, lines will be erased through your mask such that each separate region within your image can be encapsulated via a single contour instead of contours nested within one another. Use this if your RT Struct viewer of choice does not support nested contours / contours with holes.
  • approximate_contours: A boolean value that defaults to True which defines whether or not approximations are made when extracting contours from the input mask. Setting this to false will lead to much larger contour data within your RT Struct so only use this if as much precision as possible is required.
  • roi_generation_algorithm: An enum value that defaults to 0 which defines what ROI generation algorithm will be used. 0='AUTOMATIC', 1='SEMIAUTOMATIC', or 2='MANUAL'.

#To be added nifti to rtstruct

How to Cite

If you are incorporating RT-Utils into your projects, kindly include the following citation:

Shrestha A, Watkins A, Yousefirizi F, Rahmim A, Uribe CF (2024). RT-utils: A Minimal Python Library for RT-struct Manipulation. arXiv preprint arXiv:2405.06184.

rt-utils's People

Contributors

asim-shrestha avatar awtkns avatar carluri avatar igorhlx avatar joarroba avatar maxencelarose avatar pchlap avatar plesqui avatar qurit-frizi avatar rheg49 avatar smichi23 avatar thomasbudd avatar tomaroberts avatar w0nsh avatar zhack47 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rt-utils's Issues

RTSTRUCT save as

Two small things here:

  • We are opening but never closing the file.
  • Why are we using open? Why not Path.exsists() && Path.is_file()

def save(self, file_path):
try:
open(file_path, 'w')
# Opening worked, we should have a valid file_path
print("Writing file to", file_path)
self.ds.save_as(file_path)
except OSError:
raise Exception(f"Cannot write to file path '{file_path}'")

image(s) that are not contained in input series

Hello, thank you very much for the great package. When running it, I got this error when loading an RT struct:

At line 400: <class 'Exception'>: Loaded RTStruct references image(s) that are not contained in input series data. Problematic image has SOP Instance Id: 1.2.752.37.5.626934496.20190830.24838.6.2067

I understand this is from a check, but what exactly is being checked?

the possibility of changing one of ROIs volumes.

Hi guys.
I am a medical researcher and working on DICOM files in my current project.
I wonder to know is it possible to be able to uniformly extend or shorten one of ROIs in rt-struct DICOM file?
Uniformly extending or shortening PTV(Planning Target Volume) in rt-struct DICOM file and obtaining the overlap volume between this PTV and surrounding Organ at risks (OAR) lead us to calculate OVH (Overlap Volume Histogram) which is a useful feature feeding machine learning models.

Make Certain Dataset Fields Optional

Opened from a comment on blog post.

PatientAge is optional information. It seems the utils requires PatientAge.

I have a CT dataset that has no PatientAge. The code would be crashed.

Traceback (most recent call last):
File โ€œconvert_nifti_segments_2_dicom.pyโ€, line 6, in
rtstruct = RTStructBuilder.create_new(dicom_series_path=โ€./chest_CTโ€)
File โ€œ/home/gchen/anaconda3/lib/python3.8/site-packages/rt_utils/rtstruct_builder.pyโ€, line 20, in create_new
ds = ds_helper.create_rtstruct_dataset(series_data)
File โ€œ/home/gchen/anaconda3/lib/python3.8/site-packages/rt_utils/ds_helper.pyโ€, line 16, in create_rtstruct_dataset
add_patient_information(ds, series_data)
File โ€œ/home/gchen/anaconda3/lib/python3.8/site-packages/rt_utils/ds_helper.pyโ€, line 84, in add_patient_information
ds.PatientAge = reference_ds.PatientAge
File โ€œ/home/gchen/anaconda3/lib/python3.8/site-packages/pydicom/dataset.pyโ€, line 835, in __getattr__
return object.__getattribute__(self, name)
AttributeError: โ€˜FileDatasetโ€™ object has no attribute โ€˜PatientAgeโ€™

Is it possible to make PatientAge as an optional?

Delete ROI

Hi,

I know there is a function to add a roi to the RT struct (add_roi), but is there also something similar for deleting a roi?

Thanks!

AUTOMATIC ROIGenerationAlgorithm

Add another optional parameter to add_roi() in order to set the ROIGenerationAlgorithm. Update the comments and documentation for add_roi() to reflect this change.

Setting value of "Contour Geometric Type".

Very nice RT toolbox you are making! Due to a lack of resources such as this a year ago, i actually ended up writing some RT tools for my hospital department, for transforming mask outputs from DL models, which are also based on cv2 and pydicom. I never found the time to turn them into a well made toolbox for github, so i would love to help you guys expand your toolbox a little instead.

Contour Geometric Type
contour.ContourGeometricType = 'CLOSED_PLANAR' # TODO figure out how to get this value
contour.NumberOfContourPoints = len(contour_data) / 3 # Each point has an x, y, and z value

Setting the geometric value as CLOSED_PLANAR will nearly always work, but i recently had to expand this part myself, as it resulted in a few errors when loading some RTstructs into our image software.
If part of the mask has a lone voxel in a slice, the voxel will be deleted when importing the RTstruct unless the geometric type is set to "POINT".
If only two voxels are connected, they must be set to "OPEN_PLANAR", as the CLOSED_PLANAR value only works for 3+ connected contour points. OPEN_PLANAR is not always supported by image software, so i have had to define cases with two connected voxels as two separate POINT contours.

Error accessing one ROI name

Code and error message are shown below. Other ROI name is fine so far. Any help? Thanks.

dicom_dir = f"./dicom/Renamed/Pediatric-CT-SEG-028C9198/"
rt_file = f"{dicom_dir}/Mask/1-1.dcm"
rtstruct = RTStructBuilder.create_from(
    dicom_series_path=dicom_dir+"Image/", 
    rt_struct_path=rt_file
    )
names = rtstruct.get_roi_names()
print(names)
mask_3d = rtstruct.get_roi_mask_by_name("Breast Left")
first_mask_slice = mask_3d[:, :, 1]
plt.imshow(first_mask_slice) # View one slice within the mask
plt.show()
['Adrenal Left', 'Adrenal Right', 'Bladder', 'Breast Left', 'Breast Right', 'Duodenum', 'Esophagus', 'Femoral Head Lef', 'Femoral Head Rig', 'Gall Bladder', 'Gonads', 'Heart', 'Kidney Left', 'Kidney Right', 'Large Intestine', 'Liver', 'Pancreas', 'Prostate', 'Rectum', 'Small Intestine', 'Spinal Canal', 'Spleen', 'Stomach', 'Thymus', 'UteroCervix', 'Lung_L', 'Lung_R', 'Skin', 'Bones']
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
d:\PythonProjects\AI_Segmention_Pediatric_Organ\step0_clean_names.ipynb Cell 9 in <cell line: 9>()
      [7](vscode-notebook-cell:/d%3A/PythonProjects/AI_Segmention_Pediatric_Organ/step0_clean_names.ipynb#X16sZmlsZQ%3D%3D?line=6) names = rtstruct.get_roi_names()
      [8](vscode-notebook-cell:/d%3A/PythonProjects/AI_Segmention_Pediatric_Organ/step0_clean_names.ipynb#X16sZmlsZQ%3D%3D?line=7) print(names)
----> [9](vscode-notebook-cell:/d%3A/PythonProjects/AI_Segmention_Pediatric_Organ/step0_clean_names.ipynb#X16sZmlsZQ%3D%3D?line=8) mask_3d = rtstruct.get_roi_mask_by_name("Breast Left")
     [10](vscode-notebook-cell:/d%3A/PythonProjects/AI_Segmention_Pediatric_Organ/step0_clean_names.ipynb#X16sZmlsZQ%3D%3D?line=9) first_mask_slice = mask_3d[:, :, 1]
     [11](vscode-notebook-cell:/d%3A/PythonProjects/AI_Segmention_Pediatric_Organ/step0_clean_names.ipynb#X16sZmlsZQ%3D%3D?line=10) plt.imshow(first_mask_slice) # View one slice within the mask

File d:\python38\lib\site-packages\rt_utils\rtstruct.py:110, in RTStruct.get_roi_mask_by_name(self, name)
    108 for structure_roi in self.ds.StructureSetROISequence:
    109     if structure_roi.ROIName == name:
--> 110         contour_sequence = ds_helper.get_contour_sequence_by_roi_number(
    111             self.ds, structure_roi.ROINumber
    112         )
    113         return image_helper.create_series_mask_from_contour_sequence(
    114             self.series_data, contour_sequence
    115         )
    117 raise RTStruct.ROIException(f"ROI of name `{name}` does not exist in RTStruct")

File d:\python38\lib\site-packages\rt_utils\ds_helper.py:221, in get_contour_sequence_by_roi_number(ds, roi_number)
    217 for roi_contour in ds.ROIContourSequence:
    218 
    219     # Ensure same type
    220     if str(roi_contour.ReferencedROINumber) == str(roi_number):
--> 221         return roi_contour.ContourSequence
    223 raise Exception(f"Referenced ROI number '{roi_number}' not found")

File d:\python38\lib\site-packages\pydicom\dataset.py:834, in Dataset.__getattr__(self, name)
    832     return {}
    833 # Try the base class attribute getter (fix for issue 332)
--> 834 return object.__getattribute__(self, name)

AttributeError: 'Dataset' object has no attribute 'ContourSequence'

Add ROIs from polygons

Is there a way of adding ROI using polygons rather than masks?

I'm currently converting our ROI annotations to dicomrt. Our annotations are polygon coordinates.

I think it would be straightforward to have a API that accepts polygon as input, although I can convert my polygons into mask and use the existing add_roi API.

I can raise a PR to do this if you are feeling comfortable with it.

suggestion to avoid issue when multiple types of files are in the same folder

Hi rt_Utils authors,

Thanks for a great tool !

I've experienced some problems, when I am running the add_roi function.
I got the following error-message: Mask must have the same number of layers (In the 3rd dimension) as input series. Expected 130, got 129.

The problem arises, because my folder contains both RT structs, CT scans, RT dose and RT plan. The RT dose file do also have a 'pixel_array' attribute.

I changed a line in the function: load_dcm_images_from_path

from
if hasattr(ds, "pixel_array"):
series_data.append(ds)

to
if ds.Modality == 'CT':
series_data.append(ds)

It solved my problem. :)

path_length

Hi,
I was wondering if there is a potential limit to the length of the path directory used by the RTStrcuctBuilder.create_from() ?

I have created a script which was working well but as I lauched it on a new computer (and adapted the path) it did not work...I had no space or special caractere in my path.

The only solution I have found was to change the path by reducing it a little bit ?

Do you have an explanation ?

Best

Fabien

Switch to using a logger

Rt-utils currently prints a few debug statements. We should instead log these via a logger so that we only need to display them if desired by the user.

Not support old version DICOM

No DICOM magic number found, but the file appears to be DICOM without a preamble.
Proceeding without caution.No DICOM magic number found, but the file appears to be DICOM without a preamble.
Proceeding without caution.

solution:
use dcmread force=True, and
`def load_dcm_images_from_path(dicom_series_path: str) -> List[Dataset]:
series_data = []
for root, _, files in os.walk(dicom_series_path):
for file in files:
try:
ds = dcmread(os.path.join(root, file), force=True)
if hasattr(ds, 'PixelData'):
series_data.append(ds)

        except Exception:
            # Not a valid DICOM file
            continue

return series_data`

and delete the validation function
` def create_from(dicom_series_path: str, rt_struct_path: str) -> RTStruct:
"""
Method to load an existing rt struct, given related DICOM series and existing rt struct
"""

    series_data = image_helper.load_sorted_image_series(dicom_series_path)
    ds = dcmread(rt_struct_path)
    # RTStructBuilder.validate_rtstruct(ds)
    # RTStructBuilder.validate_rtstruct_series_references(ds, series_data)

    # TODO create new frame of reference? Right now we assume the last frame of reference created is suitable 
    return RTStruct(series_data, ds)`

Contour Sequence should be optional.

Hi,

Thank you for this awesome tool!

Contour Sequence is Type 3 (Optional), there's no guarantee it'll be present in a given item in the ROI Contour Sequence. I recently stumbled on AttributeError: 'Dataset' object has no attribute 'ContourSequence' while loading an existing RT Struct contour as a mask.

I think this error should not occur. I managed to fix it and will make a pull request to review my correction.

Best,

Maxence

pydicom 2.1.2 requirement

I've been running rt-utils with the latest pydicom for a few months now. I've had no issues. I've also run your tests with pydicom 2.3; no issues either. Maybe the requirement can be updated to allow later versions of pydicom?

Apply limit to decimal places of ContourData DICOM item

Hi there,

Really nice package โ€“ great work.

I've been using rt-utils recently to create some RT Struct DICOM files which we would like to open in the Eclipse software platform. The RT Struct writer works and I get the DICOM files, however, when we import them into Eclipse, the logs state that the byte limit on the ContourData DICOM item has been exceeded.

According to the DICOM standard, there is a 16 byte maximum size for DS items (see here for screenshot from NEMA website).

Would it be possible to amend rt-utils so that it caps the number of decimal places on the item?

Edit โ€“ realised I was being silly: PR here.

Many thanks.

RTStruct FrameOfReferenceUID is not matching with the reference CT

Hi my code is simple, like below. It runs fine, but I see the FrameOfRefernceUID in the output file (RS.new.dcm) is difference from the FrameOfRefceUID of the reference CT dicom series. So, I cannot import the RS file to my application. Is this a bug?... any suggestion would be greatly appreciated. Thanks! Jay.

from rt_utils import RTStructBuilder
import numpy as np
import SimpleITK as sitk
from os.path import join

ct_path = 'U:\\temp\\New folder\\ct_dicom'
mask_mhd = '.\\data1\\00001500\Rectum.mhd'
out_rs_path = 'U:\\temp\\New folder\\RS.new.dcm'

# read segmentation image
mask_img = sitk.ReadImage(mask_mhd)

# convert to np array
mask_np = sitk.GetArrayFromImage(mask_img)

# reorder from z,y,x->y,x,z (donno why... but this worked ok for my application)
new_shape = (mask_np.shape[2], mask_np.shape[1], mask_np.shape[0])
mask_np_trans = np.empty(new_shape, dtype=bool)
for k in range(mask_np.shape[0]):
    for j in range(mask_np.shape[1]):
        for i in range(mask_np.shape[2]):
            mask_np_trans[i,j,k] = bool(mask_np[k,j,i])

# create new RT Struct. 
rtstruct = RTStructBuilder.create_new(dicom_series_path=ct_path)

# add mask
rtstruct.add_roi(
  mask=mask_np_trans, 
  color=[255, 0, 255], 
  name="Rectum"
)

#save
rtstruct.save(out_rs_path)
print('done')

'FileDataset' object has no attribute 'SliceLocation'

I have an rt-struct.dcm file by itself, I want to extract a 3D volumetric mask from this. From loading the Rt-struct using dicominfo on MATLAB I can see that it has no SliceLocation attribute. However, it does have ROI contour attributes. All I am trying to do is extract these ROI contours (which are currently stored in Cartesian coordinates) as a mask. Is it possible to do this with rt-utils?

Allow adding empty ROIs

Dear rt-utils devs,
Does the dicom format allow adding empty ROIs? This would be extremely useful to be able to:
When a template rtstruct is made, our deep learning segmentation model does not always predict something for all labels. Thus, we just need to remember to add them manually later. With the possibility to add empty ROIs this problem is solved and making rt-utils even more useful.

Mathis

Add DICOM attribute `RTROIInterpretedType` as optional parameter to `add_roi` and `ROIData`

RTROIInterpretedType stores information about the type of the contoured volume. Possible values can be found here: https://dicom.innolitics.com/ciods/rt-structure-set/rt-roi-observations/30060080/300600a4
One should be able to pass this parameter to add_roi so that the desired type of ROI can be passed to ds_helper.create_rtroi_observation(roi_data) within add_roi.

(follow-up issue to #70: after that, the RTStructMerger maybe could be changed to the usage of add_roi instead of just appending ROIContourSequence, StructureSetROISequence, RTROIObservationsSequence from one RTStructure to the other one)

Not compatible with open-cv < 4.0.0

Hello there!

Great tool. Thank you for making it public :) . I noticed that the tool won't work with open-cv < 4.0.0. In particular, this line: https://github.com/qurit/rt-utils/blob/main/rt_utils/image_helper.py#L90 will return an error because cv.findContours returns a tuple of three elements in open-cv < 4.0.0 (e.g., see -> https://docs.opencv.org/3.4.17/d3/dc0/group__imgproc__shape.html#ga95f5b48d01abc7c2e0732db24689837b for 3.4.17). That behaviour changed in 4.0.0.

I am not sure if there are other backward compatibility issues.

I would suggests updating the requirements file to enforce a version of open-cv >= 4.0.0 (or whichever version the tests were run on) so that if the existing python environment already contains an old version of open-cv, this gets also updated.

My best regards,

P

AttributeError: 'Dataset' object has no attribute 'ContourImageSequence'

Hi,

Thanks for sharing the great package.

I'm using the following code to load RT annotations.

rtstruct = RTStructBuilder.create_from(
  dicom_series_path=r"path to \Export_Case\CT", 
  rt_struct_path=r"path to\lung.dcm"
)
print(rtstruct.get_roi_names())
for name in rtstruct.get_roi_names():
    mask_3d = rtstruct.get_roi_mask_by_name(name).astype(np.uint8)

It can output the roi_names, but encounter the following error

['Lungs 1000 intensity threshold', 'Heart']
Traceback (most recent call last):

  File ".\utils.py", line 40, in <module>
    mask_3d = rtstruct.get_roi_mask_by_name(name).astype(np.uint8)

  File "D:\ProgramData\Anaconda3\lib\site-packages\rt_utils\rtstruct.py", line 113, in get_roi_mask_by_name
    return image_helper.create_series_mask_from_contour_sequence(

  File "D:\ProgramData\Anaconda3\lib\site-packages\rt_utils\image_helper.py", line 246, in create_series_mask_from_contour_sequence
    slice_contour_data = get_slice_contour_data(series_slice, contour_sequence)

  File "D:\ProgramData\Anaconda3\lib\site-packages\rt_utils\image_helper.py", line 259, in get_slice_contour_data
    for contour_image in contour.ContourImageSequence:

  File "D:\ProgramData\Anaconda3\lib\site-packages\pydicom\dataset.py", line 835, in __getattr__
    return object.__getattribute__(self, name)

AttributeError: 'Dataset' object has no attribute 'ContourImageSequence'

How can I fix this problem?
Any comments would be highly appreciated:)

Best regards,
Jun

Sign error in z coordinates of ContourData

Dear Authors,
thanks a lot for making this great library public, it is highly appreciated!
We have encountered a small problem with it: we're using 3d CT images and due to the left-handed coordinate system the z coordinates in the image are negative. However the ROIs created by your library are missing the minus sign. By manually hacking I managed to get it working now, but it would be nice if this could be solved in your code.
Also it would be nice if one could change the ROIGenerationAlgorithm to AUTOMATIC possibly just as an additional argument to add_roi.
Many thanks!
Thomas

Rtstruct Referencing Multiple CT Image Series

Hi,

Thank you for this tool, it really helped me with my work.

Currently I have a single rtstruct file which references to multiple 3D CT Images of different Series.
I am using,
rtstruct.get_roi_mask_by_name(roiname)

By removing the validation check, it works for me, but in most of the sequence I am getting a 3d mask -> 0s.
Do you have any advice for me?

add ability to retrieve ROI names in a case insensitive way

HI,
i'm experiencing problem with low quality ROI labels (written like this: Heart, heart, HEART),
maybe an option to extract case insensitive ROIs could be useful to other also

I have a quick_fix in rtstruct.py:98
if structure_roi.ROIName.casefold() == name.casefold():

maybe a more general option while using ".get_roi__mask_by_name" could be better

'FileDataset' object has no attribute 'SeriesDate'

Hi,

We have come across this error on a few datasets we are using. Including the LCTSC dataset from TCIA: http://doi.org/10.7937/K9/TCIA.2017.3r3fvz08

/usr/local/lib/python3.6/dist-packages/rt_utils/rtstruct_builder.py:22: in create_new
    ds = ds_helper.create_rtstruct_dataset(series_data)
/usr/local/lib/python3.6/dist-packages/rt_utils/ds_helper.py:15: in create_rtstruct_dataset
    add_study_and_series_information(ds, series_data)
/usr/local/lib/python3.6/dist-packages/rt_utils/ds_helper.py:68: in add_study_and_series_information
    ds.SeriesDate = reference_ds.SeriesDate

AttributeError: 'FileDataset' object has no attribute 'SeriesDate'

The problem is our DICOMs don't have the SeriesDate attribute. Is this something any one else has come across? Could we consider adjusting the code only copy over that attribute if it is available? I'd be happy to look at submitting a pull request if this is something you are interested in.

Thanks!
Phil

RTReferencedStudySequence not present in RTSTRUCT dicom file?

Hi,

Because the attribute RTReferencedStudySequence is of type 3 (optional) it can be the case this is omitted in the RTSTRUCT dicom file. Because of this, an error message in validate_rtstruct_series_references.py occurs, when I try to convert the RTSTRUCT contour into a 3D mask in python.

Is this attribute really necessary and can you work around this issue?

Thank you!

get_contour_fill_mask: truncating contour data

Hi there,

It seems perhaps that you're rounding the contour data too early in your code. The contour_data is changed to type 'int' here, before being converted to the pixel coordinates. I think you can just remove .astype(int) as it's converted to 'int32' further on.

This change can make a difference to the resulting mask, please see attached example where I forked the code and applied the change to the fork:

https://colab.research.google.com/drive/1anBcwIGgrGcPDbsyIDrNqgb0H9UZxJhC?usp=sharing

Ability to load SPECT image masks

For modalities like PET, its image information spans across multiple files with a 2D image "slice" present in each file. With SPECT images on the other hand, only one file is given containing the entire 3D volume. This poses an issue when loading a 3D SPECT file mask as RT-Utils assumes that the series data is split as a file per slice.

Notes:

  • We can distinguish a SPECT image via its 'NM' modality
  • Instead of each slice containing its own image patient orientation, SPECT images will only have one orientation for the 3D volume. Some interpolation from this original value may be required as we iterate across each slice

@carluri Feel free to add anything you feel is missing.

File type for ML masks

What file type/ format are the masks expected to be in to create the RTStruct files? We're currently trialling a ML auto segmentation solution which outputs anatomical models as separate nii.gz (zipped nifti) files. Do you know if files of this type can be converted into the required format for them to be packaged into an RTStruct file? Thanks.

ImplementationClassUID invalid

The ImplementationClassUID (0002, 0012) tag is set to an invalid value.

In this line it is set to PYDICOM_ROOT_UID value defined in pydicom.
This value is just the root of the pydicom's implementation UID and not the full UID("1.2.826.0.1.3680043.8.498."). The full implementation class UID of pydicom is defined in PYDICOM_IMPLEMENTATION_UID ( "1.2.826.0.1.3680043.8.498.1")

Found out that it is invalid while running the DICOM validation tool dciovfy.

issue with saggital MR scans

I have a 3D MR series with 160 dicom files. x, y, z dims for the series are (160, 256, 256). If I've followed the code properly, i think it assumes that image series files are acquired in z direction, is that a correct?

Support for Hex Colors

We should support hex and rgb colors. We should also maybe have a default list containing mims colors?

@carluri Do you have a list of colors you would like?

tuple issue

Hi rt_Utils authors

Thanks for a great tool !

I had it working fine but made some updates and now I get som format issues ( tuples). I am adding the error message here - hope you can help :)

Skipping empty mask layer
Skipping empty mask layer
Skipping empty mask layer
Skipping empty mask layer
Skipping empty mask layer
Skipping empty mask layer
Traceback (most recent call last):

File "", line 1, in
rtstruct.add_roi(mask=mask_3d_bool, color=json.loads(strct_color1[strnum-1]), name=strct_names1[strnum-1])

File "C:\Users\jeskal\Anaconda3\lib\site-packages\rt_utils\rtstruct.py", line 59, in add_roi
self.ds.ROIContourSequence.append(ds_helper.create_roi_contour(roi_data, self.series_data))

File "C:\Users\jeskal\Anaconda3\lib\site-packages\rt_utils\ds_helper.py", line 144, in create_roi_contour
roi_contour.ContourSequence = create_contour_sequence(roi_data, series_data)

File "C:\Users\jeskal\Anaconda3\lib\site-packages\rt_utils\ds_helper.py", line 157, in create_contour_sequence
contours_coords = get_contours_coords(roi_data, series_data)

File "C:\Users\jeskal\Anaconda3\lib\site-packages\rt_utils\image_helper.py", line 64, in get_contours_coords
contours, _ = find_mask_contours(mask_slice, roi_data.approximate_contours)

File "C:\Users\jeskal\Anaconda3\lib\site-packages\rt_utils\image_helper.py", line 87, in find_mask_contours
contours[i] = [[pos[0][0], pos[0][1]] for pos in contour]

TypeError: 'tuple' object does not support item assignment

add roi to existing structure set

Hi all,

I have two CTs and two structure sets (generated in TPS). I am trying to copy some rois from structutSet1 to structurSet2. The patient is the same, and most of the setting (same CT type, place where CT was taken, ...) just the acquisition date and time differs.

It seems that the append worked, so no error appear and a roi with "name" is added to structurSet2. But when I display the newly added roi, it is just a point and not the structure it should show. It does not matter, which roi I copy, it is always the same point at the same coordinates.

RoiInRTStruct1and2

When I copy the rois from structutSet1 to a new structure set, everything works fine.

Do you have any idea, why this is not working? I also tried to copy a roi from structurSet2 to structurSet2 with a new name. The name is added to structurSet2, but the roi is again a point at the same coordinates, as shown in the image above.

Thanks a lot for your help! Its a great tool by the way

Cheers,
Nadja

Change from Exception to warning when the rt's ReferencedSOPInstanceUID is different from the CT's SOPInstanceUID

Problem:

Sometimes the CT used by the radiologist for the manual segmentation of ROIs might not be the one put in the final database.
To give a concrete example, one might have an injection CT and one without injection, do the rtstruct on the injected one and put the non-injected one in the database.

Consequence

In this case the CT's SOPInstanceUID is different from thee RT's ReferencedSOPInstanceUID, even though the space is the same.

At lines 78-82, the check made raises an error if these are not identical.

Proposition

It would be great if the user could chose whether this should be a warning or an Exception, through a parameter passed to the RTStructBuilder (like strict_check_SOPInstanceUID=False)

If you think it's a good idea, I will submit a pull request for it.

Creating contour sequences with SOPInstanceUID instead of using the MediaStorageSOPInstanceUID

Hi,

First of all, you did an impressive work on this package!

I simply wanted to ask if there was a specific reason behind building the contour sequence around the MediaStorageSOPInstanceUID.

If not, adding an optional argument in order to switch between MediaStorageSOPInstanceUID and SOPInstanceUID could maybe be useful for users.

To give you additional context, here is small change I had to do in order to be able to add a new contour to an existing RTSTRUCT file with respect to the original structure.

In ds_helper.py

def create_contour_image_sequence(series_data) -> Sequence:
contour_image_sequence = Sequence()

# Add each referenced image
for series in series_data:
    contour_image = Dataset()
    contour_image.ReferencedSOPClassUID = series.file_meta.MediaStorageSOPClassUID
    contour_image.ReferencedSOPInstanceUID = series.file_meta.MediaStorageSOPInstanceUID
    contour_image_sequence.append(contour_image)
return contour_image_sequence

became

def create_contour_image_sequence(series_data) -> Sequence:
contour_image_sequence = Sequence()

# Add each referenced image
for series in series_data:
    contour_image = Dataset()
    contour_image.ReferencedSOPClassUID = series.SOPClassUID
    contour_image.ReferencedSOPInstanceUID = series.SOPInstanceUID
    contour_image_sequence.append(contour_image)
return contour_image_sequence

Thank you again for this amazing work!

RTSTRUCT slice interpolation

Dear RT-UTILS authors,

Firstly, thank you for this wonderful and helpful library. It is amazing to use.

I would like to make a feature suggestion. I am not aware of any existing tool for resampling the RTSTRUCT pointsets into a different 3D geometry, which is a nontrivial problem. Existing tools are capable of handling different pixel spacing using workarounds, but not different slice spacing. For example, I could prepare a dummy CT series with different pixel spacing, and pass it to RTStructBuilder.create_from. Similar hacking is possible in Plastimatch using a dummy reference series or image file e.g. Nifti format. However, to get to a different slice spacing, nearest-neighbor interpolation is still required. I believe that a more intelligent way to handle this, starting from the pointsets and not the voxel masks, would be a choice addition to RT-UTILS.

Best regards,
Dylan Hsu, Ph.D
Memorial Sloan-Kettering Cancer Center

ROI_number

Hi,
thank you for this tool.

I have some difficulties when adding a new ROI into an existing RTstructure.

In fact, I looked at the code and when adding a new ROI, the ROI_number is set to ds.StructureSetROISequence +1. However, in the RT file I have downloaded from my PACS, ROI_number are not consecutive. For example they are foing from 2 to 17, then 19 and 63. So I have for example 17 initial ROIs, and if I add 2 more ROIs, numbers 18 and 19 are automatically attributed, and this generates a error message when I reimport the RT structure in my PACS because there are two "numbers 19".

I will try to get around the problem but one of my suggestion would be to propose to the user to add (or not) the ROI_number when calling the add_ROI function and at least to specify this problem in your readme file as it was very difficult to understand where was the problem.

Best

Fabien

How to create mask for tandem and ovoid?

We have tandem and ovoid in the brachytherapy. I have problem in creating a mask for tandem and ovoid using the rtutils. This package successfully load and create an existing RT structure contour as mask for OARs like Bladder, rectum, sigmoid and hrctv. But it gives and error for creating mask for tandem and ovoid. Could anyone help me in figuring out?

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.