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

Issue with Multilayer NIfTI to DICOM SEG Conversion - Double (Incorrect) Number of Frames in Output DCM SEG #510

Open
zain18jan2000 opened this issue Dec 1, 2024 · 1 comment

Comments

@zain18jan2000
Copy link

zain18jan2000 commented Dec 1, 2024

I am trying to convert a multilayer NIFTI file to a DICOM SEG file using the itkimage2segimage tool from dcmqi. After conversion the output DICOM SEG file contains two times the actual number of frames.

For example:

If input NIFTI has 203 frames and 2 labels segmentations (e.g., Liver, Spleen).
My multilayer output Dicom SEG file contains 2 × 203 = 406 frames, with each set of 203 frames corresponding to a single label. however the expected multilayer output DICOM SEG file should contains 203 frames only, where each frame contains the combined segmentation for both labels.

Here is the Python code I used to perform the conversion:

import nibabel as nib  # To validate NIfTI labels
import subprocess
import os
import pydicom
import json


def get_unique_labels_from_nifti(nifti_file):
    """
    Extracts unique labels from the NIfTI file.
    Args:
        nifti_file (str): Path to the NIfTI file.
    Returns:
        list: Sorted unique labels (intensity values) in the NIfTI file.
    """
    nifti_data = nib.load(nifti_file).get_fdata()
    unique_labels = sorted(set(nifti_data.flatten()))
    return [int(label) for label in unique_labels if label > 0]  # Exclude background (0)


def convert_nifti_to_dcm_seg(nifti_file, dicom_dir, output_seg_file):
    """
    Converts a NIfTI segmentation file to DICOM SEG using itkimage2segimage.

    Args:
        nifti_file (str): Path to the input NIfTI file.
        dicom_dir (str): Directory containing the DICOM files.
        output_seg_file (str): Path to save the output DICOM SEG file.
    """
    # Ensure output directory exists
    os.makedirs(os.path.dirname(output_seg_file), exist_ok=True)

    # Extract the StudyInstanceUID from one of the DICOM files in the directory
    dicom_file = os.path.join(dicom_dir, os.listdir(dicom_dir)[0])
    study_instance_uid = pydicom.dcmread(dicom_file).StudyInstanceUID

    # Validate the labels in the NIfTI file
    labels_in_nifti = get_unique_labels_from_nifti(nifti_file)
    print(f"Unique labels in NIfTI file: {labels_in_nifti}")

    # Generate segment attributes for each class
    segment_attributes = []

    # Example descriptions for each label
    descriptions = {
        labels_in_nifti[0]: "Liver",
        labels_in_nifti[1]: "Spleen"
    }

    for i, label in enumerate(labels_in_nifti):
        segment_attributes.append({
            "SegmentNumber": i+1,
            "labelID": label,
            "SegmentDescription": descriptions.get(label, "Unknown"),
            "SegmentAlgorithmType": "AUTOMATIC",
            "SegmentAlgorithmName": "TotalSegmentator",
            "SegmentedPropertyCategoryCodeSequence": {
                "CodeValue": "T-D0050" if label == labels_in_nifti[0] else "T-62000",
                "CodingSchemeDesignator": "SCT",
                "CodeMeaning": "Anatomical Structure"
            },
            "SegmentedPropertyTypeCodeSequence": {
                "CodeValue": "10200004" if label == labels_in_nifti[0] else "10200005",
                "CodingSchemeDesignator": "SCT",
                "CodeMeaning": descriptions.get(label, "Unknown")
            }
        })

    # Construct metadata
    metadata = {
        "ContentCreatorName": "Reader1",
        "ClinicalTrialSeriesID": "Session1",
        "ClinicalTrialTimePointID": "1",
        "SeriesDescription": "Segmentation",
        "SeriesNumber": "300",
        "InstanceNumber": "1",
        "BodyPartExamined": "Liver and Spleen",
        "segmentAttributes": [segment_attributes],
        "ContentLabel": "SEGMENTATION",
        "ContentDescription": "Image segmentation",
        "ClinicalTrialCoordinatingCenterName": "dcmqi"
    }

    # Save metadata to a JSON file
    metadata_file = os.path.join(os.path.dirname(output_seg_file), "metadata.json")
    with open(metadata_file, "w") as f:
        json.dump(metadata, f, indent=4)

    # Construct the command to convert the NIfTI to DICOM SEG
    cmd = [
        r'/media/zain/New Volume/PycharmProjects/XRAD/.venv/lib/python3.10/site-packages/dcmqi/bin/itkimage2segimage',
        "--inputImageList", nifti_file,
        "--inputMetadata", metadata_file,
        "--outputDICOM", output_seg_file,
        "--inputDICOMDirectory", dicom_dir,
        "--skip", "0"
    ]

    try:
        # Execute the command
        result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        print("Conversion successful!")
        print("Output:", result.stdout)
    except subprocess.CalledProcessError as e:
        # Handle errors
        print("Error during conversion:")
        print("Output:", e.stdout)
        print("Error:", e.stderr)


# Example usage
nifti_file = "niftis/2.25.1937671288494678696440169598763456789/2.25.1937671288494678696440169598763456789_seg.nii.gz"  # Path to your NIfTI file
dicom_dir = "dicoms/2.25.1937671288494678696440169598763456789/"  # Directory containing the DICOM files
output_seg_file = "./output/multi_class_seg.dcm"  # Path to save the DICOM SEG file

convert_nifti_to_dcm_seg(nifti_file, dicom_dir, output_seg_file)

Conversion command used in above python script

itkimage2segimage --inputImageList <nifti_file> \
                  --inputMetadata <metadata.json> \
                  --outputDICOM <output_seg_file> \
                  --inputDICOMDirectory <dicom_dir> \
                  --skip 0

To help debug this issue, I am sharing the files - link

@fedorov
Copy link
Member

fedorov commented Jan 8, 2025

I am sorry for the delayed response.

dcmqi currently does not support multilayered segmentations. You would need to store your segmentation as a single-channel labelmap (if labels do not overlap), or as separate files (if they do overlap).

Pull request implementing improvements to support multilayer input would definitely be welcomed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants