diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 39aea77..a939d29 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -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 @@ -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 diff --git a/Paper/paper.bib b/Paper/paper.bib index c1c7340..53e4c9a 100644 --- a/Paper/paper.bib +++ b/Paper/paper.bib @@ -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} diff --git a/Paper/paper.md b/Paper/paper.md index 6eadcc1..e2896e4 100644 --- a/Paper/paper.md +++ b/Paper/paper.md @@ -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 diff --git a/README.md b/README.md index 2b620c9..462fb93 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/rt_utils/rtstruct.py b/rt_utils/rtstruct.py index dfe82be..c52d816 100644 --- a/rt_utils/rtstruct.py +++ b/rt_utils/rtstruct.py @@ -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 @@ -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( @@ -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( @@ -71,7 +104,7 @@ 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: @@ -79,7 +112,7 @@ def validate_mask(self, mask: np.ndarray) -> bool: 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]}" ) @@ -118,19 +151,17 @@ 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}'") @@ -138,5 +169,4 @@ class ROIException(Exception): """ Exception class for invalid ROI masks """ - pass