コード例 #1
0
ファイル: roiClass.py プロジェクト: oncoray/mirp
    def has_metadata(self, tag):

        if self.metadata is None:
            return None

        else:
            return get_pydicom_meta_tag(dcm_seq=self.metadata, tag=tag, test_tag=True)
コード例 #2
0
    def rename(self, new):

        if self.metadata is not None:
            # Obtain the old name
            old = self.name

            if not self.has_metadata(tag=(0x3006, 0x0020)):
                raise ValueError(
                    f"The DICOM metaheader does not contain a Structure Set ROI sequence."
                )

            # Iterate over roi elements in the roi sequence
            for ii, roi_element in enumerate(self.metadata[0x3006, 0x0020]):

                # Find ROI name that matches the old name
                if get_pydicom_meta_tag(dcm_seq=roi_element,
                                        tag=(0x3006, 0x0026),
                                        tag_type="str") == old:
                    set_pydicom_meta_tag(dcm_seq=roi_element,
                                         tag=(0x3006, 0x0026),
                                         value=new)

            # Assign a new name
            self.name = new
        else:
            # Assign a new name
            self.name = new
コード例 #3
0
def get_all_dicom_headers(image_folder,
                          modality=None,
                          series_uid=None,
                          sop_instance_uid=None):

    # Parse to list
    if sop_instance_uid is not None:
        if isinstance(sop_instance_uid, str):
            sop_instance_uid = [sop_instance_uid]

    # Obtain a list with image files
    file_list = _find_dicom_image_series(image_folder=image_folder,
                                         allowed_modalities=["CT", "PT", "MR"],
                                         modality=modality,
                                         series_uid=series_uid)

    # Obtain dicom metadata for each file
    slice_dcm = [
        pydicom.dcmread(os.path.join(image_folder, file_name),
                        stop_before_pixels=True,
                        force=True) for file_name in file_list
    ]

    # Filter slices according to sop instance UIDs that are in the provided list.
    if sop_instance_uid is not None:
        slice_dcm = [
            slice_dcm_metadata for slice_sop_instance_uid in sop_instance_uid
            for slice_dcm_metadata in slice_dcm
            if get_pydicom_meta_tag(dcm_seq=slice_dcm_metadata,
                                    tag=(0x0008, 0x0018),
                                    tag_type="str") == slice_sop_instance_uid
        ]

    return slice_dcm
コード例 #4
0
    def get_metadata(self, tag, tag_type, default=None):
        # Do not attempt to read the metadata if no metadata is present.
        if self.metadata is None:
            return

        return get_pydicom_meta_tag(dcm_seq=self.metadata,
                                    tag=tag,
                                    tag_type=tag_type,
                                    default=default)
コード例 #5
0
    def update_dicom_header(self, dcm):

        # Update unit of pixel values
        voxel_unit = "CM2ML" if self.suv_type == "BSA" else "GML"
        set_pydicom_meta_tag(dcm_seq=dcm,
                             tag=(0x0054, 0x1001),
                             value=voxel_unit)

        # Update the SUV type
        set_pydicom_meta_tag(dcm_seq=dcm,
                             tag=(0x0054, 0x1006),
                             value=self.suv_type)

        # Decay correction
        set_pydicom_meta_tag(dcm_seq=dcm,
                             tag=(0x0054, 0x1102),
                             value=self.decay_correction)

        # Add DECY to the image corrections, if this was not done previously.
        image_corrections = get_pydicom_meta_tag(dcm_seq=dcm,
                                                 tag=(0x0028, 0x0051),
                                                 tag_type="mult_str",
                                                 default=[])
        if "DECY" not in image_corrections:
            image_corrections += ["DECY"]
        set_pydicom_meta_tag(dcm_seq=dcm,
                             tag=(0x0028, 0x0051),
                             value=image_corrections)

        # Update the image type
        image_type = get_pydicom_meta_tag(dcm_seq=dcm,
                                          tag=(0x0008, 0x0008),
                                          tag_type="mult_str",
                                          default=[])
        if len(image_type) > 2:
            image_type[0] = "DERIVED"
            image_type[1] = "SECONDARY"
        else:
            image_type = ["DERIVED", "SECONDARY"]
        set_pydicom_meta_tag(dcm_seq=dcm,
                             tag=(0x0008, 0x0008),
                             value=image_type)

        return dcm
コード例 #6
0
ファイル: dicomImport.py プロジェクト: liuwenhaha/mirp
def _get_frame_of_reference_uid(dcm):

    # Try to obtain a frame of reference UID
    if has_pydicom_meta_tag(dcm_seq=dcm, tag=(0x0020, 0x0052)):
        return get_pydicom_meta_tag(dcm_seq=dcm,
                                    tag=(0x0020, 0x0052),
                                    tag_type="str")

    # For RT structure sets, the FOR UID may be tucked away in the Structure Set ROI Sequence
    if has_pydicom_meta_tag(dcm_seq=dcm, tag=(0x3006, 0x0020)):
        structure_set_roi_sequence = dcm[(0x3006, 0x0020)]

        for structure_set_roi_element in structure_set_roi_sequence:
            if has_pydicom_meta_tag(dcm_seq=structure_set_roi_element,
                                    tag=(0x3006, 0x0024)):
                return get_pydicom_meta_tag(dcm_seq=structure_set_roi_element,
                                            tag=(0x3006, 0x0024),
                                            tag_type="str")

    return None
コード例 #7
0
def _find_dicom_roi_names(dcm, with_roi_number=False):

    # Placeholder roi_names list
    roi_names = []
    roi_sequence_numbers = []

    # Check if a Structure Set ROI Sequence exists (3006, 0020)
    if not get_pydicom_meta_tag(
            dcm_seq=dcm, tag=(0x3006, 0x0020), test_tag=True):
        warnings.warn(
            "The RT-structure set did not contain any ROI sequences.")

        if with_roi_number:
            return roi_names, roi_sequence_numbers
        else:
            return roi_names

    # Iterate over roi elements in the roi sequence
    for roi_element in dcm[0x3006, 0x0020]:

        # Check if the roi element contains a name (3006, 0026)
        if get_pydicom_meta_tag(dcm_seq=roi_element,
                                tag=(0x3006, 0x0026),
                                test_tag=True):
            roi_names += [
                get_pydicom_meta_tag(dcm_seq=roi_element,
                                     tag=(0x3006, 0x0026),
                                     tag_type="str")
            ]
            roi_sequence_numbers += [
                get_pydicom_meta_tag(dcm_seq=roi_element,
                                     tag=(0x3006, 0x0022),
                                     tag_type="str")
            ]

    if with_roi_number:
        return roi_names, roi_sequence_numbers
    else:
        return roi_names
コード例 #8
0
    def __init__(self, dcm):

        # Start of image acquisition for the current position
        acquisition_start_date = get_pydicom_meta_tag(dcm_seq=dcm,
                                                      tag=(0x0008, 0x0022),
                                                      tag_type="str")
        acquisition_start_time = get_pydicom_meta_tag(dcm_seq=dcm,
                                                      tag=(0x0008, 0x0032),
                                                      tag_type="str")
        self.acquisition_ref_time = convert_dicom_time(
            date_str=acquisition_start_date, time_str=acquisition_start_time)
        self.start_ref_time = deepcopy(self.acquisition_ref_time)

        # Frame reference time frame (ms)
        self.frame_duration = get_pydicom_meta_tag(dcm_seq=dcm,
                                                   tag=(0x0018, 0x1242),
                                                   tag_type="float")

        # Radionuclide administration
        if get_pydicom_meta_tag(dcm_seq=dcm,
                                tag=(0x0054, 0x0016),
                                test_tag=True):
            radio_admin_start_time = get_pydicom_meta_tag(
                dcm_seq=dcm[0x0054, 0x0016][0],
                tag=(0x0018, 0x1078),
                tag_type="str")
            self.radio_admin_ref_time = convert_dicom_time(
                datetime_str=radio_admin_start_time)

            if self.radio_admin_ref_time is None:
                # If unsuccessful, attempt determining administration time from (0x0018, 0x1072)
                radio_admin_start_time = get_pydicom_meta_tag(
                    dcm_seq=dcm[0x0054, 0x0016][0],
                    tag=(0x0018, 0x1072),
                    tag_type="str")
                self.radio_admin_ref_time = convert_dicom_time(
                    date_str=acquisition_start_date,
                    time_str=radio_admin_start_time)
        else:
            self.radio_admin_ref_time = None

        if self.radio_admin_ref_time is None:
            # If neither (0x0018, 0x1078) or (0x0018, 0x1072) are present, attempt to read private tags

            # GE tags
            ge_acquistion_ref_time = get_pydicom_meta_tag(dcm_seq=dcm,
                                                          tag=(0x0009, 0x100d),
                                                          tag_type="str")
            ge_radio_admin_ref_time = get_pydicom_meta_tag(dcm_seq=dcm,
                                                           tag=(0x0009,
                                                                0x103b),
                                                           tag_type="str")

            if ge_acquistion_ref_time is not None and ge_radio_admin_ref_time is not None:
                self.acquisition_ref_time = convert_dicom_time(
                    datetime_str=ge_acquistion_ref_time)
                self.radio_admin_ref_time = convert_dicom_time(
                    datetime_str=ge_radio_admin_ref_time)

        if self.radio_admin_ref_time is not None and self.acquisition_ref_time is not None:

            day_diff = abs(self.radio_admin_ref_time -
                           self.acquisition_ref_time).days
            if day_diff > 1:
                # Correct for de-identification mistakes (i.e. administration time was de-identified correctly, but acquisition time not)
                # We do not expect that the difference between the two is more than a day, or even more than a few hours at most.
                if self.radio_admin_ref_time > self.acquisition_ref_time:
                    self.radio_admin_ref_time -= datetime.timedelta(
                        days=day_diff)
                else:
                    self.radio_admin_ref_time += datetime.timedelta(
                        days=day_diff)

            if self.radio_admin_ref_time > self.acquisition_ref_time:
                # Correct for overnight
                self.radio_admin_ref_time -= datetime.timedelta(days=1)

        # Radionuclide total dose and radionuclide half-life
        if get_pydicom_meta_tag(dcm_seq=dcm,
                                tag=(0x0054, 0x0016),
                                test_tag=True):
            self.total_dose = get_pydicom_meta_tag(dcm_seq=dcm[0x0054,
                                                               0x0016][0],
                                                   tag=(0x0018, 0x1074),
                                                   tag_type="float")
            self.half_life = get_pydicom_meta_tag(dcm_seq=dcm[0x0054,
                                                              0x0016][0],
                                                  tag=(0x0018, 0x1075),
                                                  tag_type="float")
        else:
            self.total_dose = None
            self.half_life = None

        # Type of intensity in a voxel
        self.voxel_unit = get_pydicom_meta_tag(dcm_seq=dcm,
                                               tag=(0x0054, 0x1001),
                                               tag_type="str")

        # Type of decay correction that is used
        self.decay_correction = get_pydicom_meta_tag(dcm_seq=dcm,
                                                     tag=(0x0054, 0x1102),
                                                     tag_type="str",
                                                     default="NONE")

        # Decay factor for the image
        self.decay_factor = get_pydicom_meta_tag(dcm_seq=dcm,
                                                 tag=(0x0054, 0x1321),
                                                 tag_type="float",
                                                 default=1.0)

        # TODO: Determine if any decay correction took place from (0018,9758), which has either YES or NO, if present. If YES, (0018,9701) should be present as well.
        # TODO: Use Decay Correction DateTime (0018,9701) as alternative for determining the correction time.

        # Type of SUV
        self.suv_type = get_pydicom_meta_tag(dcm_seq=dcm,
                                             tag=(0x0054, 0x1006),
                                             tag_type="str",
                                             default="BW")

        # Patient data
        self.patient_gender = get_pydicom_meta_tag(dcm_seq=dcm,
                                                   tag=(0x0010, 0x0040),
                                                   tag_type="str")

        self.patient_height = get_pydicom_meta_tag(dcm_seq=dcm,
                                                   tag=(0x0010, 0x1020),
                                                   tag_type="float")
        if self.patient_height is not None:
            if self.patient_height > 3.0:
                # Interpret patient height as cm and convert to meter
                self.patient_height = self.patient_height / 100.0

        self.patient_weight = get_pydicom_meta_tag(dcm_seq=dcm,
                                                   tag=(0x0010, 0x1030),
                                                   tag_type="float")
        if self.patient_weight is None:
            logging.warning(
                "Patient weight was not found in the DICOM header. SUV normalisation cannot take place."
            )

        # Private scale factors
        self.philips_suv_scale = get_pydicom_meta_tag(dcm_seq=dcm,
                                                      tag=(0x7053, 0x1000),
                                                      tag_type="float")
        self.philips_count_scale = get_pydicom_meta_tag(dcm_seq=dcm,
                                                        tag=(0x7053, 0x1009),
                                                        tag_type="float")
コード例 #9
0
def _convert_rtstruct_to_segmentation(dcm: FileDataset, roi: str,
                                      image_object: ImageClass):

    # Deparse roi
    deparsed_roi = parse_roi_name(roi=roi)

    # Keep only structure data corresponding to the current ROIs.
    dcm = _filter_rt_structure_set(dcm=dcm, roi_names=deparsed_roi)

    # Check if the roi is found.
    if dcm is None:
        return None

    # Initialise a data list
    contour_data_list = []

    # Obtain segmentation
    for roi_contour_sequence in dcm[(0x3006, 0x0039)]:
        for contour_sequence in roi_contour_sequence[(0x3006, 0x0040)]:

            # Check if the geometric type exists (3006, 0042)
            if not get_pydicom_meta_tag(dcm_seq=contour_sequence,
                                        tag=(0x3006, 0x0042),
                                        test_tag=True):
                continue

            # Check if the geometric type equals "CLOSED_PLANAR"
            if get_pydicom_meta_tag(dcm_seq=contour_sequence,
                                    tag=(0x3006, 0x0042),
                                    tag_type="str") != "CLOSED_PLANAR":
                continue

            # Check if contour data exists (3006, 0050)
            if not get_pydicom_meta_tag(dcm_seq=contour_sequence,
                                        tag=(0x3006, 0x0050),
                                        test_tag=True):
                continue

            contour_data = np.array(get_pydicom_meta_tag(
                dcm_seq=contour_sequence,
                tag=(0x3006, 0x0050),
                tag_type="mult_float"),
                                    dtype=np.float64)
            contour_data = contour_data.reshape((-1, 3))

            # Determine if there is an offset (3006, 0045)
            contour_offset = np.array(get_pydicom_meta_tag(
                dcm_seq=contour_sequence,
                tag=(0x3006, 0x0045),
                tag_type="mult_float",
                default=[0.0, 0.0, 0.0]),
                                      dtype=np.float64)

            # Remove the offset from the data
            contour_data -= contour_offset

            # Obtain the sop instance uid.
            if get_pydicom_meta_tag(dcm_seq=contour_sequence,
                                    tag=(0x3006, 0x0016),
                                    test_tag=True):
                sop_instance_uid = get_pydicom_meta_tag(
                    dcm_seq=contour_sequence[(0x3006, 0x0016)][0],
                    tag=(0x0008, 0x1155),
                    tag_type="str",
                    default=None)
            else:
                sop_instance_uid = None

            # Store as contour.
            contour = ContourClass(contour=contour_data,
                                   sop_instance_uid=sop_instance_uid)

            # Add contour data to the contour list
            contour_data_list += [contour]

    if len(contour_data_list) > 0:
        # Create a new ROI object.
        roi_obj = RoiClass(name="+".join(deparsed_roi),
                           contour=contour_data_list,
                           metadata=dcm)

        # Convert contour into segmentation object
        roi_obj.create_mask_from_contours(img_obj=image_object,
                                          disconnected_segments="keep_as_is")

        return roi_obj

    else:
        return None
コード例 #10
0
def _filter_rt_structure_set(dcm, roi_numbers=None, roi_names=None):

    from pydicom.sequence import Sequence

    # We need to update a few sequences in the RT structure set:
    # * The Structure Set ROI sequence (3006, 0020)
    # * The ROI Contour sequence (3006, 0039)
    # * The RT ROI Observations Sequence (3006, 0080)

    # Initialise new sequences
    new_structure_set_roi_sequence = Sequence()
    new_roi_contour_sequence = Sequence()
    new_rt_roi_observations_sequence = Sequence() if get_pydicom_meta_tag(
        dcm_seq=dcm, tag=(0x3006, 0x0080), test_tag=True) else None

    if not get_pydicom_meta_tag(
            dcm_seq=dcm, tag=(0x3006, 0x0020), test_tag=True):
        return None

    if not get_pydicom_meta_tag(
            dcm_seq=dcm, tag=(0x3006, 0x0039), test_tag=True):
        return None

    # Check that either roi_numbers is provided, or roi_names is provided.
    if roi_numbers is None and roi_names is None:
        raise ValueError(
            "Either the ROI Reference numbers or the ROI names should be provided."
        )

    elif roi_numbers is None and roi_names is not None:
        roi_names_available, roi_numbers_available = _find_dicom_roi_names(
            dcm=dcm, with_roi_number=True)
        roi_numbers = [
            roi_number for ii, roi_number in enumerate(roi_numbers_available)
            if roi_names_available[ii] in roi_names
        ]

    if len(roi_numbers) == 0:
        return None

    for ii, current_roi_number in enumerate(roi_numbers):

        # Look through the existing Structure Set ROI sequence for matching roi_number
        for structure_set_elem in dcm[(0x3006, 0x0020)]:
            if not get_pydicom_meta_tag(dcm_seq=structure_set_elem,
                                        tag=(0x3006, 0x0022),
                                        test_tag=True):
                # No ROI number present.
                continue

            elif get_pydicom_meta_tag(dcm_seq=structure_set_elem,
                                      tag=(0x3006, 0x0022),
                                      tag_type="str") == current_roi_number:
                # ROI number matches
                new_structure_set_roi_sequence.append(structure_set_elem)

            else:
                continue

        # Look through existing ROI contour sequence for matching roi_number
        for roi_contour_elem in dcm[(0x3006, 0x0039)]:
            if not get_pydicom_meta_tag(dcm_seq=roi_contour_elem,
                                        tag=(0x3006, 0x0084),
                                        test_tag=True):
                # No ROI number present
                continue

            elif get_pydicom_meta_tag(dcm_seq=roi_contour_elem,
                                      tag=(0x3006, 0x0084),
                                      tag_type="str") == current_roi_number:
                # ROI number matches
                new_roi_contour_sequence.append(roi_contour_elem)

            else:
                continue

        if get_pydicom_meta_tag(dcm_seq=dcm,
                                tag=(0x3006, 0x0080),
                                test_tag=True):
            for roi_observation_elem in dcm[(0x3006, 0x0080)]:

                if not get_pydicom_meta_tag(dcm_seq=roi_observation_elem,
                                            tag=(0x3006, 0x0084),
                                            tag_type="str"):
                    # No ROI number present
                    continue

                elif get_pydicom_meta_tag(
                        dcm_seq=roi_observation_elem,
                        tag=(0x3006, 0x0084),
                        tag_type="str") == current_roi_number:
                    # ROI number matches
                    new_rt_roi_observations_sequence.append(
                        roi_observation_elem)

                else:
                    continue

    # Local copy of dcm
    dcm = deepcopy(dcm)

    # Add as data element
    if new_structure_set_roi_sequence is not None:
        dcm[(0x3006, 0x0020)].value = new_structure_set_roi_sequence

    if new_roi_contour_sequence is not None:
        dcm[(0x3006, 0x0039)].value = new_roi_contour_sequence

    if new_rt_roi_observations_sequence is not None:
        dcm[(0x3006, 0x0080)].value = new_rt_roi_observations_sequence

    return dcm
コード例 #11
0
def _find_dicom_image_series(image_folder,
                             allowed_modalities,
                             modality=None,
                             series_uid=None,
                             frame_of_ref_uid=None):

    # Check folder contents, keep only files that are recognised as DICOM images.
    file_list = os.listdir(image_folder)
    file_list = [
        file_name for file_name in file_list
        if not os.path.isdir(os.path.join(image_folder, file_name))
    ]
    file_list = [
        file_name for file_name in file_list
        if file_name.lower().endswith(".dcm")
    ]

    if len(file_list) == 0:
        raise FileNotFoundError(
            f"The image folder does not contain any DICOM files.")

    # Modality and series UID
    series_modality = []
    series_series_uid = []
    series_FOR_uid = []

    # Identify modality of the files
    for file_name in file_list:
        # Read DICOM header using pydicom
        dcm = pydicom.dcmread(os.path.join(image_folder, file_name),
                              stop_before_pixels=True,
                              force=True)

        # Read modality
        series_modality += [
            get_pydicom_meta_tag(dcm_seq=dcm,
                                 tag=(0x0008, 0x0060),
                                 tag_type="str")
        ]

        # Read series UID
        series_series_uid += [
            get_pydicom_meta_tag(dcm_seq=dcm,
                                 tag=(0x0020, 0x000e),
                                 tag_type="str")
        ]

        # Frame of reference UID
        series_FOR_uid += [_get_frame_of_reference_uid(dcm=dcm)]

    # User-provided modality
    if modality is not None:

        if modality.lower() in ["ct"]:
            requested_modality = ["CT"]
        elif modality.lower() in ["pet", "pt"]:
            requested_modality = ["PT"]
        elif modality.lower() in ["mri", "mr"]:
            requested_modality = ["MR"]
        elif modality.lower() in ["rtstruct", "structure_set"]:
            requested_modality = ["RTSTRUCT"]
        else:
            raise ValueError(
                f"Unknown modality requested. Available choices are CT, PT, MR, RTSTRUCT. Found: {modality}"
            )

        if not any([
                tmp_modality in allowed_modalities
                for tmp_modality in requested_modality
        ]):
            raise ValueError(
                f"The selected modality ({modality}) cannot be used within the current context. This error can occur when attempting to image files instead of"
                "segmentations when segmentations are intended.")

    else:
        # Any supported modality
        requested_modality = allowed_modalities

    # Filter file list
    file_list = [
        file_list[ii] for ii, file_modality in enumerate(series_modality)
        if file_modality in requested_modality
    ]
    series_series_uid = [
        series_series_uid[ii]
        for ii, file_modality in enumerate(series_modality)
        if file_modality in requested_modality
    ]
    series_FOR_uid = [
        series_FOR_uid[ii] for ii, file_modality in enumerate(series_modality)
        if file_modality in requested_modality
    ]

    # Check if the requested modality was found.
    if len(file_list) == 0:
        raise ValueError(
            f"The DICOM folder does not contain any DICOM images with a currently supported modality ({allowed_modalities})."
        )

    # Check uniqueness of series UID
    if len(list(set(series_series_uid))
           ) > 1 and series_uid is None and frame_of_ref_uid is None:
        raise ValueError(
            f"Multiple series UID were found in the DICOM folder. Please select one using the series_uid argument. Found: {list(set(series_series_uid))}"
        )

    elif len(list(set(series_series_uid))
             ) > 1 and series_uid is None and frame_of_ref_uid is not None:
        series_uid = [
            series_series_uid[ii]
            for ii, file_FOR_uid in enumerate(series_FOR_uid)
            if file_FOR_uid == frame_of_ref_uid
        ]

        if len(list(set(series_uid))) > 1:
            raise ValueError(
                f"Multiple series UID that share a frame of reference UID were found in the DICOM folder. Please select one using the series_uid argument."
                f"Found: {list(set(series_uid))}")
        else:
            series_uid = series_uid[0]

    elif series_uid is not None:

        # Check if the series_uid exists
        if series_uid not in series_series_uid:
            raise ValueError(
                f"The requested series UID ({series_uid}) was not found in the DICOM folder. Found: {list(set(series_series_uid))}"
            )

    else:
        series_uid = series_series_uid[0]

    # Check uniqueness of FOR_uid
    if len(list(set(series_FOR_uid))
           ) > 1 and frame_of_ref_uid is None and series_uid is None:
        raise ValueError(
            f"Multiple series with different frame of reference UIDs were found in the DICOM folder."
        )

    elif len(list(set(series_FOR_uid))
             ) > 1 and frame_of_ref_uid is None and series_uid is not None:
        frame_of_ref_uid = [
            series_FOR_uid[ii]
            for ii, file_series_uid in enumerate(series_series_uid)
            if file_series_uid == series_uid
        ]

        if len(list(set(frame_of_ref_uid))) > 1:
            raise ValueError(
                f"Multiple frame of reference UIDs where found for the same DICOM series. This may indicate corruption of the DICOM files."
            )
        else:
            frame_of_ref_uid = frame_of_ref_uid[0]

    elif frame_of_ref_uid is not None:

        if frame_of_ref_uid not in series_FOR_uid:
            raise ValueError(
                f"The requested frame of reference UID ({frame_of_ref_uid}) was not found in the DICOM folder {image_folder}. Found: {list(set(series_FOR_uid))}"
            )

    else:
        frame_of_ref_uid = series_FOR_uid[0]

    # Filter series with the particular frame of reference uid and series uid
    return [
        file_list[ii] for ii in range(len(file_list))
        if series_series_uid[ii] == series_uid
        and series_FOR_uid[ii] == frame_of_ref_uid
    ]
コード例 #12
0
def read_dicom_image_series(image_folder, modality=None, series_uid=None):

    # Obtain a list with image files
    file_list = _find_dicom_image_series(image_folder=image_folder,
                                         allowed_modalities=["CT", "PT", "MR"],
                                         modality=modality,
                                         series_uid=series_uid)

    # Obtain slice positions for each file
    file_table = pd.DataFrame({
        "file_name": file_list,
        "position_z": 0.0,
        "position_y": 0.0,
        "position_x": 0.0,
        "sop_instance_uid": ""
    })

    image_position_x = []
    image_position_y = []
    image_position_z = []
    sop_instance_uid = []

    for file_name in file_list:
        # Load the dicom header
        dcm = pydicom.dcmread(os.path.join(image_folder, file_name),
                              stop_before_pixels=True,
                              force=True)

        # Find the origin of each slice.
        slice_origin = get_pydicom_meta_tag(dcm_seq=dcm,
                                            tag=(0x0020, 0x0032),
                                            tag_type="mult_float",
                                            default=np.array([0.0, 0.0,
                                                              0.0]))[::-1]

        # Update with slice positions
        image_position_x += [slice_origin[2]]
        image_position_y += [slice_origin[1]]
        image_position_z += [slice_origin[0]]

        # Find the sop instance UID of each slice.
        slice_sop_instance_uid = get_pydicom_meta_tag(dcm_seq=dcm,
                                                      tag=(0x0008, 0x0018),
                                                      tag_type="str")

        # Update with the slice SOP instance UID.
        sop_instance_uid += [slice_sop_instance_uid]

    # Order ascending position (DICOM: z increases from feet to head)
    file_table = pd.DataFrame({
        "file_name": file_list,
        "position_z": image_position_z,
        "position_y": image_position_y,
        "position_x": image_position_x,
        "sop_instance_uid": sop_instance_uid
    }).sort_values(by=["position_z", "position_y", "position_x"])

    # Obtain DICOM metadata from the bottom slice. This will be used to fill most of the different details.
    dcm = pydicom.dcmread(os.path.join(image_folder,
                                       file_table.file_name.values[0]),
                          stop_before_pixels=True,
                          force=True)

    # Find the number of rows (y) and columns (x) in the data set.
    n_x = get_pydicom_meta_tag(dcm_seq=dcm,
                               tag=(0x0028, 0x011),
                               tag_type="int")
    n_y = get_pydicom_meta_tag(dcm_seq=dcm,
                               tag=(0x0028, 0x010),
                               tag_type="int")

    # Create an empty voxel grid. Use z, y, x ordering for consistency within MIRP.
    voxel_grid = np.zeros((len(file_table), n_y, n_x), dtype=np.float32)

    # Read all dicom slices in order.
    slice_dcm_list = [
        pydicom.dcmread(os.path.join(image_folder, file_name),
                        stop_before_pixels=False,
                        force=True)
        for file_name in file_table.file_name.values
    ]

    # Iterate over the different slices to fill out the voxel_grid.
    for ii, file_name in enumerate(file_table.file_name.values):

        # Read the dicom file and extract the slice grid
        slice_dcm = slice_dcm_list[ii]
        slice_grid = slice_dcm.pixel_array.astype(np.float32)

        # Update with scale and intercept. These may change per slice.
        rescale_intercept = get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                                 tag=(0x0028, 0x1052),
                                                 tag_type="float",
                                                 default=0.0)
        rescale_slope = get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                             tag=(0x0028, 0x1053),
                                             tag_type="float",
                                             default=1.0)
        slice_grid = slice_grid * rescale_slope + rescale_intercept

        # Convert all images to SUV at admin
        if get_pydicom_meta_tag(dcm_seq=dcm,
                                tag=(0x0008, 0x0060),
                                tag_type="str") == "PT":
            suv_conversion_object = SUVscalingObj(dcm=slice_dcm)
            scale_factor = suv_conversion_object.get_scale_factor(
                suv_normalisation="bw")

            # Convert to SUV
            slice_grid *= scale_factor

            # Update the DICOM header
            slice_dcm = suv_conversion_object.update_dicom_header(
                dcm=slice_dcm)

        # Store in voxel grid
        voxel_grid[ii, :, :] = slice_grid

    # Obtain the image origin from the dicom header (note: z, y, x order)
    image_origin = get_pydicom_meta_tag(dcm_seq=dcm,
                                        tag=(0x0020, 0x0032),
                                        tag_type="mult_float",
                                        default=np.array([0.0, 0.0,
                                                          0.0]))[::-1]

    # Obtain the image spacing from the dicom header and slice positions.
    image_pixel_spacing = get_pydicom_meta_tag(dcm_seq=dcm,
                                               tag=(0x0028, 0x0030),
                                               tag_type="mult_float")
    image_slice_thickness = get_pydicom_meta_tag(dcm_seq=dcm,
                                                 tag=(0x0018, 0x0050),
                                                 tag_type="float",
                                                 default=None)

    if len(file_table) > 1:
        # Compute the distance between the origins of the slices. This is the slice spacing.
        image_slice_spacing = np.median(
            np.sqrt(
                np.sum(np.array([
                    np.power(np.diff(file_table.position_z.values), 2.0),
                    np.power(np.diff(file_table.position_y.values), 2.0),
                    np.power(np.diff(file_table.position_x.values), 2.0)
                ]),
                       axis=0)))

        if image_slice_thickness is None:
            # TODO: Update slice thickness tag in dcm
            pass
        else:
            # Warn the user if there is a mismatch between slice thickness and the actual slice spacing.
            if not np.around(image_slice_thickness - image_slice_spacing,
                             decimals=3) == 0.0:
                warnings.warn(
                    f"Mismatch between slice thickness ({image_slice_thickness}) and actual slice spacing ({image_slice_spacing}). The actual slice spacing will be "
                    f"used.", UserWarning)

    elif image_slice_thickness is not None:
        # There is only one slice, and we use the slice thickness as parameter.
        image_slice_spacing = image_slice_thickness

    else:
        # There is only one slice and the slice thickness is unknown. In this situation, we use the pixel spacing
        image_slice_spacing = np.max(image_pixel_spacing)

    # Combine pixel spacing and slice spacing into the voxel spacing, using z, y, x order.
    image_spacing = np.array(
        [image_slice_spacing, image_pixel_spacing[1], image_pixel_spacing[0]])

    # Obtain image orientation (Xx, Xy, Xz, Yx, Yy, Yz; see DICOM C.7.6.2 Image Plane Module)
    image_orientation = get_pydicom_meta_tag(dcm_seq=dcm,
                                             tag=(0x0020, 0x0037),
                                             tag_type="mult_float")

    # Add (Zx, Zy, Zz)
    if len(file_table) > 1:

        # Compute distance between subsequent origins.
        slice_origin_distance = np.sqrt(
            np.power(np.diff(file_table.position_x.values), 2.0) +
            np.power(np.diff(file_table.position_y.values), 2.0) +
            np.power(np.diff(file_table.position_z.values), 2.0))

        # Find unique distance values.
        slice_origin_distance = np.unique(np.around(slice_origin_distance, 3))

        # If there is more than one value, this means that there is an unexpected shift in origins.
        if len(slice_origin_distance) > 1:
            raise ValueError(
                f"Inconsistent distance between slice origins of subsequent slices: "
                f"{slice_origin_distance}. Slices cannot be aligned correctly. This is likely due to "
                f"missing slices.")

        z_orientation = np.array([
            np.median(np.diff(file_table.position_x.values)),
            np.median(np.diff(file_table.position_y.values)),
            np.median(np.diff(file_table.position_z.values))
        ]) / image_slice_spacing

        # Append orientation.
        image_orientation += z_orientation.tolist()
    else:
        image_orientation += [0.0, 0.0, 1.0]

    # Revert to z, y, x order
    image_orientation = image_orientation[::-1]

    # Create an ImageClass object and store dicom meta-data
    img_obj = ImageClass(voxel_grid=voxel_grid,
                         origin=image_origin,
                         spacing=image_spacing,
                         orientation=image_orientation,
                         modality=get_pydicom_meta_tag(dcm_seq=dcm,
                                                       tag=(0x0008, 0x0060),
                                                       tag_type="str"),
                         spat_transform="base",
                         no_image=False,
                         metadata=slice_dcm_list[0],
                         slice_table=file_table)

    return img_obj
コード例 #13
0
ファイル: dicomImport.py プロジェクト: liuwenhaha/mirp
def read_dicom_image_series(image_folder, modality=None, series_uid=None):

    # Obtain a list with image files
    file_list = _find_dicom_image_series(image_folder=image_folder,
                                         allowed_modalities=["CT", "PT", "MR"],
                                         modality=modality,
                                         series_uid=series_uid)

    # Obtain slice positions for each file
    image_position_z = []
    for file_name in file_list:

        # Read DICOM header
        dcm = pydicom.dcmread(os.path.join(image_folder, file_name),
                              stop_before_pixels=True,
                              force=True,
                              specific_tags=[Tag(0x0020, 0x0032)])

        # Obtain the z position
        image_position_z += [
            get_pydicom_meta_tag(dcm_seq=dcm,
                                 tag=(0x0020, 0x0032),
                                 tag_type="mult_float")[2]
        ]

    # Order ascending position (DICOM: z increases from feet to head)
    file_table = pd.DataFrame({
        "file_name": file_list,
        "position_z": image_position_z
    }).sort_values(by="position_z")

    # Obtain DICOM metadata from the bottom slice. This will be used to fill out all the different details.
    dcm = pydicom.dcmread(os.path.join(image_folder,
                                       file_table.file_name.values[0]),
                          stop_before_pixels=True,
                          force=True)

    # Find the number of rows (y) and columns (x) in the data set.
    n_x = get_pydicom_meta_tag(dcm_seq=dcm,
                               tag=(0x0028, 0x011),
                               tag_type="int")
    n_y = get_pydicom_meta_tag(dcm_seq=dcm,
                               tag=(0x0028, 0x010),
                               tag_type="int")

    # Create an empty voxel grid. Use z, y, x ordering for consistency within MIRP.
    voxel_grid = np.zeros((len(file_table), n_y, n_x), dtype=np.float32)

    # Read all dicom slices in order.
    slice_dcm_list = [
        pydicom.dcmread(os.path.join(image_folder, file_name),
                        stop_before_pixels=False,
                        force=True)
        for file_name in file_table.file_name.values
    ]

    # Iterate over the different slices to fill out the voxel_grid.
    for ii, file_name in enumerate(file_table.file_name.values):

        # Read the dicom file and extract the slice grid
        slice_dcm = slice_dcm_list[ii]
        slice_grid = slice_dcm.pixel_array.astype(np.float32)

        # Update with scale and intercept. These may change per slice.
        rescale_intercept = get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                                 tag=(0x0028, 0x1052),
                                                 tag_type="float",
                                                 default=0.0)
        rescale_slope = get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                             tag=(0x0028, 0x1053),
                                             tag_type="float",
                                             default=1.0)
        slice_grid = slice_grid * rescale_slope + rescale_intercept

        # Convert all images to SUV at admin
        if get_pydicom_meta_tag(dcm_seq=dcm,
                                tag=(0x0008, 0x0060),
                                tag_type="str") == "PT":
            suv_conversion_object = SUVscalingObj(dcm=slice_dcm)
            scale_factor = suv_conversion_object.get_scale_factor(
                suv_normalisation="bw")

            # Convert to SUV
            slice_grid *= scale_factor

            # Update the DICOM header
            slice_dcm = suv_conversion_object.update_dicom_header(
                dcm=slice_dcm)

        # Store in voxel grid
        voxel_grid[ii, :, :] = slice_grid

    # Obtain the image origin from the dicom header (note: z, y, x order)
    image_origin = get_pydicom_meta_tag(dcm_seq=dcm,
                                        tag=(0x0020, 0x0032),
                                        tag_type="mult_float",
                                        default=np.array([0.0, 0.0,
                                                          0.0]))[::-1]

    # Obtain the image spacing from the dicom header and slice positions.
    image_pixel_spacing = get_pydicom_meta_tag(dcm_seq=dcm,
                                               tag=(0x0028, 0x0030),
                                               tag_type="mult_float")
    image_slice_thickness = get_pydicom_meta_tag(dcm_seq=dcm,
                                                 tag=(0x0018, 0x0050),
                                                 tag_type="float",
                                                 default=None)

    if len(file_table) > 1:
        # Slice spacing can be determined from the slice positions
        image_slice_spacing = np.median(
            np.abs(np.diff(file_table.position_z.values)))

        if image_slice_thickness is None:
            # TODO: Update slice thickness tag in dcm
            pass
        else:
            # Warn the user if there is a mismatch between slice thickness and the actual slice spacing.
            if not np.around(image_slice_thickness - image_slice_spacing,
                             decimals=5) == 0.0:
                warnings.warn(
                    f"Mismatch between slice thickness ({image_slice_thickness}) and actual slice spacing ({image_slice_spacing}). The actual slice spacing will be "
                    f"used.", UserWarning)

    elif image_slice_thickness is not None:
        # There is only one slice, and we use the slice thickness as parameter.
        image_slice_spacing = image_slice_thickness

    else:
        # There is only one slice and the slice thickness is unknown. In this situation, we use the pixel spacing
        image_slice_spacing = np.max(image_pixel_spacing)

    # Combine pixel spacing and slice spacing into the voxel spacing, using z, y, x order.
    image_spacing = np.array(
        [image_slice_spacing, image_pixel_spacing[1], image_pixel_spacing[0]])

    # Obtain image orientation and add the 3rd dimension
    image_orientation = get_pydicom_meta_tag(dcm_seq=dcm,
                                             tag=(0x0020, 0x0037),
                                             tag_type="mult_float")
    image_orientation += [0.0, 0.0, 1.0]

    # Revert to z, y, x order
    image_orientation = image_orientation[::-1]

    # Create an ImageClass object and store dicom meta-data
    img_obj = ImageClass(voxel_grid=voxel_grid,
                         origin=image_origin,
                         spacing=image_spacing,
                         slice_z_pos=file_table.position_z.values,
                         orientation=image_orientation,
                         modality=get_pydicom_meta_tag(dcm_seq=dcm,
                                                       tag=(0x0008, 0x0060),
                                                       tag_type="str"),
                         spat_transform="base",
                         no_image=False,
                         metadata=slice_dcm_list[0],
                         metadata_sop_instances=[
                             get_pydicom_meta_tag(dcm_seq=slice_dcm,
                                                  tag=(0x0008, 0x0018),
                                                  tag_type="str")
                             for slice_dcm in slice_dcm_list
                         ])

    return img_obj
コード例 #14
0
    def _convert_to_parametric_map_iod(self):

        if self.metadata is None:
            return None

        # Create a copy of the metadata.
        old_dcm: FileDataset = copy.deepcopy(self.metadata)

        # Update the SOP class to that of a parametric map image
        self.set_metadata(tag=(0x0008, 0x0016), value="1.2.840.10008.5.1.4.1.1.30")

        # Update the image type attribute
        image_type = self.get_metadata(tag=(0x0008, 0x0008), tag_type="mult_str", default=[])
        image_type = [image_type[ii] if ii < len(image_type) else "" for ii in range(4)]
        image_type[0] = "DERIVED"
        image_type[1] = "PRIMARY"
        image_type[2] = image_type[2] if not image_type[2] == "" else "STATIC"
        image_type[3] = "MIXED" if self.spat_transform == "base" else "FILTERED"

        self.set_metadata(tag=(0x0008, 0x0008), value=image_type)

        # Parametric Map Image module attributes that may be missing.
        self.cond_set_metadata(tag=(0x2050, 0x0020), value="IDENTITY")  # Presentation LUT shape
        self.cond_set_metadata(tag=(0x0018, 0x9004), value="RESEARCH")  # Content qualification
        self.cond_set_metadata(tag=(0x0028, 0x0301), value="NO")  # Burned-in Annotation
        self.cond_set_metadata(tag=(0x0028, 0x0302), value="YES")  # Recognisable facial features
        self.cond_set_metadata(tag=(0x0070, 0x0080), value=self.get_export_descriptor().upper().strip()[:15])  # Content label
        self.cond_set_metadata(tag=(0x0070, 0x0081), value=self.get_export_descriptor()[:63])  # Content description
        self.cond_set_metadata(tag=(0x0070, 0x0084), value="Doe^John")

        # Set the source instance sequence
        source_instance_list = []
        for reference_instance_sop_uid in self.slice_table.sop_instance_uid:
            ref_inst = Dataset()
            set_pydicom_meta_tag(dcm_seq=ref_inst, tag=(0x0008, 0x1150), value=get_pydicom_meta_tag(dcm_seq=old_dcm, tag=(0x0008, 0x0016), tag_type="str"))
            set_pydicom_meta_tag(dcm_seq=ref_inst, tag=(0x0008, 0x1155), value=reference_instance_sop_uid)

            source_instance_list += [ref_inst]

        self.set_metadata(tag=(0x0008, 0x2112), value=Sequence(source_instance_list))

        # Attributes from the enhanced general equipment module may be missing.
        self.cond_set_metadata(tag=(0x0008, 0x0070), value="unknown")  # Manufacturer
        self.cond_set_metadata(tag=(0x0008, 0x1090), value="unknown")  # Model name
        self.cond_set_metadata(tag=(0x0018, 0x1000), value="unknown")  # Device Serial Number
        self.set_metadata(tag=(0x0018, 0x1020), value="MIRP " + get_version())

        # Items from multi-frame function groups may be missing. We currently only use a single frame.
        self.set_metadata(tag=(0x5200, 0x9229), value=Sequence())  # Shared functional groups sequence
        self.set_metadata(tag=(0x5200, 0x9230), value=Sequence())  # Per-frame functional groups sequence

        # Multi-frame Dimension module

        # Dimension organisation sequence. We copy the frame of reference as UID.
        dim_org_seq_elem = Dataset()
        set_pydicom_meta_tag(dim_org_seq_elem, tag=(0x0020, 0x9164), value=self.get_metadata(tag=(0x0020, 0x0052), tag_type="str"))  # Dimension organisation UID
        self.set_metadata(tag=(0x0020, 0x9221), value=Sequence([dim_org_seq_elem]))

        # Dimension Index sequence. We point to the instance number.
        dim_index_seq_elem = Dataset()
        set_pydicom_meta_tag(dim_index_seq_elem, tag=(0x0020, 0x9165), value=(0x0020, 0x0013))  # Dimension index pointer
        set_pydicom_meta_tag(dim_index_seq_elem, tag=(0x0020, 0x9164), value=self.get_metadata(tag=(0x00200052), tag_type="str"))  # Dimension organisation UID
        self.set_metadata(tag=(0x0020, 0x9222), value=Sequence([dim_index_seq_elem]))