Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.7", "3.8", "3.9", "3.10" ]
python-version: [ "3.8", "3.9", "3.10" ]

steps:
- uses: actions/checkout@v2
Expand All @@ -28,6 +28,7 @@ jobs:
python -m pip install --upgrade pip
pip install pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
pip install -e . # Ensure local package installation
- name: Test with pytest
run: |
pytest
2 changes: 1 addition & 1 deletion Paper/paper.bib
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ @article{Anderson2021-fp

@misc{Creators_The_MONAI_Consortium_undated-or,
title = {Project {MONAI}},
author = {{Creators The MONAI Consortium}},
author = {Cardoso, M Jorge and Li, Wenqi and Brown, Richard and Ma, Nic and Kerfoot, Eric and Wang, Yiheng and Murrey, Benjamin and Myronenko, Andriy and Zhao, Can and Yang, Dong and others},
doi = {10.48550/arXiv.2211.02701},
year = {2022},
url = {https://arxiv.org/abs/2211.02701}
Expand Down
2 changes: 1 addition & 1 deletion Paper/paper.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ In the pursuit of automated and precise analysis of medical images using artific

# Statement of need

The increasing adoption of AI-based methods for medical image analysis necessitates efficient tools for handling DICOM images and RT-Structures. While existing software packages provide basic functionalities for data conversion, they often lack the advanced features required for seamless integration into clinical workflows. The growing need for automated and robust analysis of medical images has driven the adoption of AI-based methods that often use DICOM images and RT structures as masks. However, the effectiveness of these AI approaches can vary due to differences in data sources and conversion techniques [@Whybra2023-en; @Yousefirizi2023-ax; @Rufenacht2023-as]. Despite the availability of tools for converting DICOM images and RT-Structures into other formats [@Anderson2021-fp; @Rufenacht2023-as], integrating auto-segmentation solutions using deep learning in clinical environments is rare due to the lack of open-source frameworks that handle DICOM RT-Structure sets effectively. Software packages like dcmrtstruct2nii, DicomRTTool [@Anderson2021-fp], and PyRaDiSe [@Rufenacht2023-as] provide necessary functionalities, while frameworks like TorchIO [@Perez-Garcia2021-jf] and MONAI [@Creators_The_MONAI_Consortium_undated-or] face limitations in processing DICOM RT-structure data. Research has shown that variations in mask-generation methods affect patient clustering and radiomic-based modeling in multi-center studies [@Whybra2023-en]. RT-utils addresses this gap by offering a specialized Python library that enhances the efficiency of manipulating RT-Structures. It is designed for researchers and clinicians who require advanced yet user-friendly tools to: i) Convert and manipulate RT-Struct data with precision. ii) Integrate AI-generated segmentation masks into clinical DICOM formats. iii) Streamline workflows by automating repetitive and complex tasks. iv) Ensure compatibility with clinical systems through meticulous DICOM header management. By providing these capabilities, RT-utils optimizes workflows in medical imaging analysis, facilitating the translation of AI models from research to clinical practice. RT-utils offers advanced techniques to convert expert-provided contours and AI tool output masks to RT-struct format, making them suitable for clinical workflows.
The increasing adoption of AI-based methods for medical image analysis necessitates efficient tools for handling DICOM images and RT-Structures. While existing software packages provide basic functionalities for data conversion, they often lack the advanced features required for seamless integration into clinical workflows. The growing need for automated and robust analysis of medical images has driven the adoption of AI-based methods that often use DICOM images and RT structures as masks. However, the effectiveness of these AI approaches can vary due to differences in data sources and conversion techniques [@Whybra2023-en; @Yousefirizi2023-ax; @Rufenacht2023-as]. Despite the availability of tools for converting DICOM images and RT-Structures into other formats [@Anderson2021-fp; @Rufenacht2023-as], integrating auto-segmentation solutions using deep learning in clinical environments is rare due to the lack of open-source frameworks that handle DICOM RT-Structure sets precisely. Software packages like dcmrtstruct2nii, DicomRTTool [@Anderson2021-fp], and PyRaDiSe [@Rufenacht2023-as] provide necessary functionalities with higher accuracy, while frameworks like TorchIO [@Perez-Garcia2021-jf] and MONAI [@Creators_The_MONAI_Consortium_undated-or] face limitations in processing DICOM RT-structure data. Research has shown that variations in mask-generation methods affect patient clustering and radiomic-based modeling in multi-center studies [@Whybra2023-en]. RT-utils is an standard Python library that enhances the efficiency of manipulating RT-Structures. It is designed for researchers and clinicians who require advanced yet user-friendly tools to: i) Convert and manipulate RT-Struct data with precision. ii) Integrate AI-generated segmentation masks into clinical DICOM formats. iii) Streamline workflows by automating repetitive and complex tasks. iv) Ensure compatibility with clinical systems through meticulous DICOM header management. By providing these capabilities, RT-utils optimizes workflows in medical imaging analysis, facilitating the translation of AI models from research to clinical practice. RT-utils offers advanced techniques to convert expert-provided contours and AI tool output masks to RT-struct format, making them suitable for clinical workflows.

# Overview of RT-utils

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ The add_roi method of our RTStruct class has a multitude of optional parameters
- nifti to rtstruct conversion has been added.
- conversion from DICOM to NIFTI for PET and corresponding RT-struct file has been added as an example usage with no issues of shift or mistmatch between the masks.

## Examples
You can find the comparison between RT-utils and other conversion techniques in this public repository: https://github.com/qurit/dicom_nifti_conversion_project

## Contributing
We welcome contributions to this project! Please review our [CONTRIBUTING guidelines](./CONTRIBUTING.md) for more details.

Expand Down
72 changes: 51 additions & 21 deletions rt_utils/rtstruct.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from typing import List, Union

import numpy as np
from pydicom.dataset import FileDataset

from rt_utils.utils import ROIData
from . import ds_helper, image_helper

Expand All @@ -17,13 +15,12 @@ def __init__(self, series_data, ds: FileDataset, ROIGenerationAlgorithm=0):
self.ds = ds
self.frame_of_reference_uid = ds.ReferencedFrameOfReferenceSequence[
-1
].FrameOfReferenceUID # Use last strucitured set ROI
].FrameOfReferenceUID # Use last structured set ROI

def set_series_description(self, description: str):
"""
Set the series description for the RTStruct dataset
"""

self.ds.SeriesDescription = description

def add_roi(
Expand All @@ -37,13 +34,49 @@ def add_roi(
roi_generation_algorithm: Union[str, int] = 0,
):
"""
Add a ROI to the rtstruct given a 3D binary mask for the ROI's at each slice
Optionally input a color or name for the ROI
If use_pin_hole is set to true, will cut a pinhole through ROI's with holes in them so that they are represented with one contour
If approximate_contours is set to False, no approximation will be done when generating contour data, leading to much larger amount of contour data
Add a Region of Interest (ROI) to the RTStruct given a 3D binary mask for each slice.

Optionally input a color or name for the ROI.
If `use_pin_hole` is set to True, attempts to handle ROIs with holes by creating a single continuous contour.
If `approximate_contours` is set to False, no approximation is done during contour generation,
potentially resulting in a large amount of contour data.

This method updates the internal DICOM structure (RTStruct) by adding:
- ROIContourSequence
- StructureSetROISequence
- RTROIObservationsSequence

Parameters
----------
mask : np.ndarray
3D boolean array indicating the ROI. Its shape must match
the underlying DICOM series in the third dimension.
color : str or list of int, optional
Color representation for the ROI (e.g., "red" or [255, 0, 0]). Defaults to None.
name : str, optional
Name/label for the ROI. Defaults to None.
description : str, optional
Longer description of the ROI. Defaults to an empty string.
use_pin_hole : bool, optional
If True, attempts to create a single continuous contour for ROIs with holes. Defaults to False.
approximate_contours : bool, optional
If False, skips approximation during contour generation, leading to larger contour data. Defaults to True.
roi_generation_algorithm : str or int, optional
Identifier for the algorithm used to generate the ROI. Defaults to 0.

Raises
------
ROIException
- If the mask is not a 3D boolean array.
- If the mask's shape does not match the loaded DICOM series dimensions.
- If the mask is empty (no voxels set to True).

Returns
-------
None
Modifies the internal RTStruct.
"""

# TODO test if name already exists
# TODO: test if name already exists
self.validate_mask(mask)
roi_number = len(self.ds.StructureSetROISequence) + 1
roi_data = ROIData(
Expand Down Expand Up @@ -71,15 +104,15 @@ def add_roi(
def validate_mask(self, mask: np.ndarray) -> bool:
if mask.dtype != bool:
raise RTStruct.ROIException(
f"Mask data type must be boolean. Got {mask.dtype}"
f"Mask data type must be boolean, but got {mask.dtype}. Please ensure the mask is a 3D boolean array."
)

if mask.ndim != 3:
raise RTStruct.ROIException(f"Mask must be 3 dimensional. Got {mask.ndim}")

if len(self.series_data) != np.shape(mask)[2]:
raise RTStruct.ROIException(
"Mask must have the save number of layers (In the 3rd dimension) as input series. "
"Mask must have the same number of layers (in the 3rd dimension) as the input series. "
+ f"Expected {len(self.series_data)}, got {np.shape(mask)[2]}"
)

Expand Down Expand Up @@ -118,25 +151,22 @@ def get_roi_mask_by_name(self, name) -> np.ndarray:

def save(self, file_path: str):
"""
Saves the RTStruct with the specified name / location
Automatically adds '.dcm' as a suffix
Saves the RTStruct with the specified name / location.
Automatically adds '.dcm' as a suffix if necessary.
"""

# Add .dcm if needed
file_path = file_path if file_path.endswith(".dcm") else file_path + ".dcm"

try:
file = 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)
file.close()
# Using 'with' to handle file opening and closing automatically
with open(file_path, "w") as file:
print("Writing file to", file_path)
self.ds.save_as(file_path)
except OSError:
raise Exception(f"Cannot write to file path '{file_path}'")

class ROIException(Exception):
"""
Exception class for invalid ROI masks
"""

pass