def add_to_roi(rtss, roi_name, roi_coordinates, data_set): """ Add new contour image sequence ROI to rtss :param rtss: dataset of RTSS :param roi_name: ROIName :param roi_coordinates: Coordinates of pixels for new ROI :param data_set: Data Set of selected DICOM image file :return: rtss, with added ROI """ # Creating a new ROIContourSequence, ContourSequence, ContourImageSequence contour_sequence = Sequence([Dataset()]) contour_image_sequence = Sequence([Dataset()]) number_of_contour_points = len(roi_coordinates) / 3 referenced_sop_class_uid = data_set.SOPClassUID referenced_sop_instance_uid = data_set.SOPInstanceUID existing_roi_number = None for item in rtss["StructureSetROISequence"]: if item.ROIName == roi_name: existing_roi_number = item.ROINumber position = None # Get the index of the ROI for index, contour in enumerate(rtss.ROIContourSequence): if contour.ReferencedROINumber == existing_roi_number: position = index new_contour_number = len(rtss.ROIContourSequence[position].ContourSequence) + 1 # ROI Sequence for contour in contour_sequence: # if data_set.get("ReferencedImageSequence"): contour.add_new(Tag("ContourImageSequence"), "SQ", contour_image_sequence) # Contour Sequence for contour_image in contour_image_sequence: contour_image.add_new(Tag("ReferencedSOPClassUID"), "UI", referenced_sop_class_uid) # CT Image Storage contour_image.add_new(Tag("ReferencedSOPInstanceUID"), "UI", referenced_sop_instance_uid) contour.add_new(Tag("ContourNumber"), "IS", new_contour_number) if not _is_closed_contour(roi_coordinates): contour.add_new(Tag("ContourGeometricType"), "CS", "OPEN_PLANAR") contour.add_new(Tag("NumberOfContourPoints"), "IS", number_of_contour_points) contour.add_new(Tag("ContourData"), "DS", roi_coordinates) else: contour.add_new(Tag("ContourGeometricType"), "CS", "CLOSED_PLANAR") contour.add_new(Tag("NumberOfContourPoints"), "IS", number_of_contour_points-1) contour.add_new(Tag("ContourData"), "DS", roi_coordinates[0:-3]) rtss.ROIContourSequence[position].ContourSequence.extend(contour_sequence) return rtss
def datasetFromJSON(data: dict): #return Dataset.from_json(data) ds = Dataset() for key in data.keys(): tag = Tag(key) try: if 'Value' in data[key].keys(): if data[key]['vr'] == 'SQ': tempds = [] for subdata in data[key]['Value']: tempds.append(datasetFromJSON(subdata)) seq = Sequence(tempds) ds[key] = DataElement(tag, data[key]['vr'], seq) elif type( data[key]['Value'][0] ) == dict and 'Alphabetic' in data[key]['Value'][0].keys(): ds[key] = DataElement(tag, data[key]['vr'], data[key]['Value'][0]['Alphabetic']) else: if len(data[key]['Value']) > 1: ds[key] = DataElement(tag, data[key]['vr'], data[key]['Value']) else: ds[key] = DataElement(tag, data[key]['vr'], data[key]['Value'][0]) else: ds[key] = DataElement(tag, data[key]['vr'], '') except: from IPython import embed embed() return ds
def __init__(self, suid, split_num=0): # Init dataset list and the callback self._datasets = Sequence() self._filenames = [] # Init props self._suid = suid self._info = None self._shape = None self._sampling = None self._split_num = split_num
def generate_CRPES_sequence(dcm_files): """Helper function to generate a Current Requested Procedure Evidence Sequence with required DICOM attributes from a list of DICOM objects Parameters ---------- dcm_files : List of DICOM objects that are to be referenced in the Current Requested Procedure Evidence Sequence """ sequence_content = dict() for dcm_file in dcm_files: ds = read_file(dcm_file) if ds.StudyInstanceUID not in sequence_content: sequence_content[ds.StudyInstanceUID] = dict() if ds.SeriesInstanceUID not in sequence_content[ds.StudyInstanceUID]: sequence_content[ds.StudyInstanceUID][ ds.SeriesInstanceUID] = list() sequence_content[ds.StudyInstanceUID][ds.SeriesInstanceUID].append( (ds.SOPClassUID, ds.SOPInstanceUID)) sequence_data = list() for study_instance_uid in sequence_content: study_dict = dict() study_dict["StudyInstanceUID"] = study_instance_uid study_dict["ReferencedSeriesSequence"] = list() for series_instance_uid in sequence_content[study_instance_uid]: series_dict = dict() series_dict["SeriesInstanceUID"] = series_instance_uid series_dict["ReferencedSOPSequence"] = list() for class_instance_uid_tuple in sequence_content[ study_instance_uid][series_instance_uid]: instance_dict = dict() instance_dict[ "ReferencedSOPClassUID"] = class_instance_uid_tuple[0] instance_dict[ "ReferencedSOPInstanceUID"] = class_instance_uid_tuple[1] series_dict["ReferencedSOPSequence"].append(instance_dict) study_dict["ReferencedSeriesSequence"].append(series_dict) sequence_data.append(study_dict) sequence = Sequence() for study_item in sequence_data: study_ds = Dataset() study_ds.StudyInstanceUID = study_instance_uid study_ds.ReferencedSeriesSequence = generate_sequence( "ReferencedSeriesSequence", study_item["ReferencedSeriesSequence"]) sequence.append(study_ds) return sequence
def __init__(self): self.sequence = Sequence()
def generate_dicom_sr(file_path, img_ds, data, series_description): """ Generates DICOM Structured Report files for the given file path. :param file_path: the file name and directory to save the DICOM SR file in. :param img_ds: A CT or MR image from the dataset used to pull general information for the DICOM SR. :param data: Text data to be written to the DICOM SR file. :param series_description: Description of text data written to SR. :return: dicom_sr, a dataset for the new DICOM SR file. """ if img_ds is None: raise ValueError("No CT data to initialize RT SS") # Create file meta file_meta = FileMetaDataset() file_meta.FileMetaInformationGroupLength = 238 file_meta.FileMetaInformationVersion = b'\x00\x01' file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.88.33' file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() file_meta.TransferSyntaxUID = ImplicitVRLittleEndian validate_file_meta(file_meta) # Create dataset dicom_sr = pydicom.dataset.FileDataset(file_path, {}, preamble=b"\0" * 128, file_meta=file_meta) dicom_sr.fix_meta_info() # Get current date and time now = datetime.datetime.now() dicom_date = now.strftime("%Y%m%d") dicom_time = now.strftime("%H%M") # List of tags to copy from CT/MR image top_level_tags_to_copy: list = [ Tag("PatientName"), Tag("PatientID"), Tag("PatientBirthDate"), Tag("PatientSex"), Tag("StudyDate"), Tag("StudyTime"), Tag("ReferringPhysicianName"), Tag("StudyDescription"), Tag("StudyInstanceUID"), Tag("StudyID"), Tag("RequestingService"), Tag("PatientAge"), Tag("PatientSize"), Tag("PatientWeight"), Tag("MedicalAlerts"), Tag("Allergies"), Tag("PregnancyStatus"), Tag("InstitutionName"), Tag("InstitutionAddress") ] # Copy tags from CT/MR image for tag in top_level_tags_to_copy: if tag in img_ds: dicom_sr[tag] = deepcopy(img_ds[tag]) dicom_sr.AccessionNumber = "" # == SR Document Series Module dicom_sr.SeriesDate = dicom_date dicom_sr.SeriesTime = dicom_time dicom_sr.Modality = "SR" dicom_sr.SeriesDescription = series_description # Can be empty referenced_performed_procedure_step_sequence = Sequence() dicom_sr.ReferencedPerformedProcedureStepSequence = \ referenced_performed_procedure_step_sequence dicom_sr.SeriesInstanceUID = pydicom.uid.generate_uid() dicom_sr.SeriesNumber = 1 # == General Equipment Module dicom_sr.Manufacturer = "OnkoDICOM" dicom_sr.ManufacturersModelName = "OnkoDICOM" # TODO: Pull this off build information in some way dicom_sr.SoftwareVersions = "2021" # == SR Document General Module dicom_sr.ContentDate = dicom_date dicom_sr.ContentTime = dicom_time dicom_sr.InstanceNumber = 1 # Empty if unknown performed_procedure_code_sequence = Sequence() dicom_sr.PerformedProcedureCodeSequence = performed_procedure_code_sequence # Do not want to mark as complete in case it isn't! dicom_sr.CompletionFlag = "PARTIAL" dicom_sr.VerificationFlag = "UNVERIFIED" # == SR Document Content Module referenced_sop_sequence = Sequence([Dataset()]) referenced_sop_sequence[0].ReferencedSOPClassUID = '' referenced_sop_sequence[0].ReferencedSOPInstanceUID = '' dicom_sr.ReferencedSOPSequence = referenced_sop_sequence dicom_sr.ValueType = "CONTAINER" dicom_sr.ContinuityOfContent = "CONTINUOUS" dicom_sr.TemporalRangeTime = "" dicom_sr.ReferencedTimeOffsets = "" dicom_sr.ReferencedDateTime = "" dicom_sr.MeasuredValueSequence = Sequence() og_frame_of_reference_UID = \ deepcopy(img_ds[Tag("FrameOfReferenceUID")].value) dicom_sr.ReferencedFrameOfReferenceUID = og_frame_of_reference_UID # == Content Sequence content_sequence = Sequence([Dataset()]) content_sequence[0].RelationshipType = 'CONTAINS' content_sequence[0].ValueType = 'TEXT' concept_name_code_sequence = Sequence([Dataset()]) concept_name_code_sequence[0].CodeValue = '' concept_name_code_sequence[0].CodingSchemeDesignator = '' concept_name_code_sequence[0].CodeMeaning = '' content_sequence[0].ConceptNameCodeSequence = concept_name_code_sequence content_sequence[0].TextValue = data dicom_sr.ContentSequence = content_sequence # == SOP Common Module dicom_sr.SOPClassUID = '1.2.840.10008.5.1.4.1.1.88.33' dicom_sr.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID dicom_sr.is_little_endian = True dicom_sr.is_implicit_VR = True return dicom_sr
def createStructDS(self, planData, ImageInfoUIDs, setupPosition, roiShiftVector): print("Creating Data structure") # create RS SOPInstanceUID structSOPInstanceUID = pydicom.uid.generate_uid() structSeriesInstanceUID = pydicom.uid.generate_uid() # get image header info from ImageSet_0.ImageInfo structFrameUID = '' structStudyInstanceUID = '' structSeriesUID = '' structClassUID = '1.2.840.10008.5.1.4.1.1.481.3' if "ImageInfoList" in ImageInfoUIDs: structFrameUID = ImageInfoUIDs.ImageInfoList[0].FrameUID structStudyInstanceUID = ImageInfoUIDs.ImageInfoList[ 0].StudyInstanceUID structSeriesUID = ImageInfoUIDs.ImageInfoList[0].SeriesUID # structClassUID = ImageInfoUIDs.ImageInfoList[0].ClassUID # Populate required values for file meta information file_meta = Dataset() # RT Structure Set Storage file_meta.MediaStorageSOPClassUID = structClassUID file_meta.MediaStorageSOPInstanceUID = structSOPInstanceUID structfilename = "RS." + structSOPInstanceUID + ".dcm" # this value remains static since implementation for creating file is the same file_meta.ImplementationClassUID = '1.2.826.0.1.3680043.8.498.75006884747854523615841001' # Create the FileDataset instance (initially no data elements, but file_meta supplied) ds = FileDataset(structfilename, {}, file_meta=file_meta, preamble=b'\x00' * 128) # print(file_meta.preamble) # add info_data,basic patientinfo # [0008,0005] - [0008,0018] ds.SpecificCharacterSet = 'ISO_IR 100' ds.InstanceCreationDate = time.strftime("%Y%m%d") ds.InstanceCreationTime = time.strftime("%H%M%S") ds.SOPClassUID = structClassUID ds.SOPInstanceUID = structSOPInstanceUID ds.Modality = 'RTSTRUCT' ds.AccessionNumber = "" ds.Manufacturer = 'Pinnalce3' # from sample dicom file, maybe should change? # not sure where to get information for this element can find this and read in from ds.StationName = "adacp3u7" # ds.ManufacturersModelName = 'Pinnacle3' ds = self.modifyPatientInfo(ds, planData) # [0008,1110] ds.ReferencedStudySequence = Sequence() ReferencedStudy1 = Dataset() ds.ReferencedStudySequence.append(ReferencedStudy1) # Study Component Management SOP Class (chosen from template) ds.ReferencedStudySequence[ 0].ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.2' ds.ReferencedStudySequence[ 0].ReferencedSOPInstanceUID = structStudyInstanceUID # ds.StudyInstanceUID = StudyInstanceUID print("Setting structure file study instance: " + str(structStudyInstanceUID)) # [0020,000d] ds.StudyInstanceUID = structStudyInstanceUID # [0020,000e] ds.SeriesInstanceUID = structSeriesInstanceUID # [3006,0010] ds.ReferencedFrameOfReferenceSequence = Sequence() ReferencedFrameofReference1 = Dataset() ds.ReferencedFrameOfReferenceSequence.append( ReferencedFrameofReference1) ds.ReferencedFrameOfReferenceSequence[ 0].FrameofReferenceUID = structFrameUID # [3006,0012] ds.ReferencedFrameOfReferenceSequence[ 0].RTReferencedStudySequence = Sequence() RTReferencedStudy1 = Dataset() ds.ReferencedFrameOfReferenceSequence[ 0].RTReferencedStudySequence.append(RTReferencedStudy1) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.2' ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].ReferencedSOPInstanceUID = structStudyInstanceUID # ds.StudyInstanceUID = StudyInstanceUID # [3006,0014] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence = Sequence() RTReferencedSeries1 = Dataset() ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence.append(RTReferencedSeries1) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[ 0].SeriesInstanceUID = structSeriesUID # [3006,0016] ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence = Sequence() # [fffe,e000] for i, value in enumerate(ImageInfoUIDs.ImageInfoList, 1): exec("ContourImage%d = Dataset()" % i) exec( "ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[0].RTReferencedSeriesSequence[0].ContourImageSequence.append(ContourImage%d)" % i) ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence[ i - 1].ReferencedSOPClassUID = value.ClassUID ds.ReferencedFrameOfReferenceSequence[0].RTReferencedStudySequence[ 0].RTReferencedSeriesSequence[0].ContourImageSequence[ i - 1].ReferencedSOPInstanceUID = value.InstanceUID # exec("del ContourImage%d" % i) # [3006,0020] roiListData = planData.planROIsRawData.roiList ds.StructureSetROISequence = Sequence() for i, value in enumerate(roiListData, 1): exec("ROISet%d = Dataset()" % i) exec("ds.StructureSetROISequence.append(ROISet%d)" % i) ds.StructureSetROISequence[i - 1].ROIName = value.name ds.StructureSetROISequence[i - 1].ROINumber = i ds.StructureSetROISequence[ i - 1].ReferencedFrameOfReferenceUID = structFrameUID if 'volume' in value: ds.StructureSetROISequence[i - 1].ROIVolume = value.volume ds.StructureSetROISequence[ i - 1].ROIGenerationAlgorithm = value.roiinterpretedtype # [3006,0039]get each ROI ds.ROIContourSequence = Sequence() for i, value in enumerate(roiListData, 1): exec("ContourSequence%d = Dataset()" % i) exec("ds.ROIContourSequence.append(ContourSequence%d)" % i) ds.ROIContourSequence[i - 1].ROIDisplayColor = [0, 255, 0] ds.ROIContourSequence[i - 1].ReferencedROINumber = i # get all curves in current ROI ds.ROIContourSequence[i - 1].ContourSequence = Sequence() planROIsCurvesList = value.num_curve # get each ROI_Curvers for j, data in enumerate(planROIsCurvesList, 1): exec("CurvesPoint%d = Dataset()" % j) exec( "ds.ROIContourSequence[i - 1].ContourSequence.append(CurvesPoint%d)" % j) # [3006,0040] ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].ContourImageSequence = Sequence() coutourImage1 = Dataset() ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].ContourImageSequence.append(coutourImage1) ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].ContourImageSequence[ 0].ReferencedSOPClassUID = structClassUID ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].ContourImageSequence[ 0].ReferencedSOPInstanceUID = self.getCTInstanceUID( data.Points[0], setupPosition, ImageInfoUIDs) # [3006,0042] ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].ContourGeometricType = "CLOSED_PLANAR" ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].NumberOfContourPoints = data.num_points # get each ROI_Curves_Points, using data.Points ds.ROIContourSequence[i - 1].ContourSequence[ j - 1].ContourData = self.getContourCurvePoints( data.Points, setupPosition, roiShiftVector) # [3006,0080] ds.RTROIObservationsSequence = Sequence() for i, current_roi in enumerate(ds.StructureSetROISequence, 1): exec("Observation%d = Dataset()" % i) exec("ds.RTROIObservationsSequence.append(Observation%d)" % i) ds.RTROIObservationsSequence[ i - 1].ObservationNumber = current_roi.ROINumber ds.RTROIObservationsSequence[ i - 1].ReferencedROINumber = current_roi.ROINumber ds.RTROIObservationsSequence[i - 1].RTROIInterpretedType = 'ORGAN' ds.RTROIObservationsSequence[i - 1].ROIInterpreter = "" # find out where to get if its been approved or not ds.ApprovalStatus = 'UNAPPROVED' # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # # Create the FileDataset instance (initially no data elements, but file_meta supplied) # structds = FileDataset(structfilename, {}, # file_meta=ds, preamble=b'\x00' * 128) # structfilepath=outputfolder + patientfolder + "/" + structfilename # structds.save_as("structfilepath") # print("Structure file being saved\n") ds.save_as(os.getenv('HOME') + '/PinnWork/' + structfilename) # # dcmds = pydicom.dcmread(ds) # print(dcmds) return ds
def create_roi(rtss, roi_name, roi_coordinates, data_set, rt_roi_interpreted_type="ORGAN"): """ Create new ROI to rtss :param rtss: dataset of RTSS :param roi_name: ROIName :param roi_coordinates: Coordinates of pixels for new ROI :param data_set: Data Set of selected DICOM image file :return: rtss, with added ROI """ patient_dict_container = PatientDictContainer() existing_rois = patient_dict_container.get("rois") roi_exists = False # This is for adding a new slice to an already existing ROI. For Future Development. # Check to see if the ROI already exists for key, value in existing_rois.items(): if value["name"] == roi_name: roi_exists = True if not roi_exists: number_of_contour_points = len(roi_coordinates) / 3 referenced_sop_class_uid = data_set.SOPClassUID referenced_sop_instance_uid = data_set.SOPInstanceUID referenced_frame_of_reference_uid = rtss["StructureSetROISequence"].value[0].ReferencedFrameOfReferenceUID roi_number = rtss["StructureSetROISequence"].value[-1].ROINumber + 1 # Colour TBC red = random.randint(0, 255) green = random.randint(0, 255) blue = random.randint(0, 255) rgb = [red, green, blue] # Saving a new StructureSetROISequence structure_set_sequence = Sequence([Dataset()]) original_structure_set = rtss.StructureSetROISequence for structure_set in structure_set_sequence: structure_set.add_new(Tag("ROINumber"), 'IS', roi_number) structure_set.add_new(Tag("ReferencedFrameOfReferenceUID"), 'UI', referenced_frame_of_reference_uid) structure_set.add_new(Tag("ROIName"), 'LO', roi_name) structure_set.add_new(Tag("ROIGenerationAlgorithm"), 'CS', "") # Combine old and new structure set original_structure_set.extend(structure_set_sequence) rtss.add_new(Tag("StructureSetROISequence"), "SQ", original_structure_set) # Saving a new ROIContourSequence, ContourSequence, ContourImageSequence roi_contour_sequence = Sequence([Dataset()]) contour_sequence = Sequence([Dataset()]) contour_image_sequence = Sequence([Dataset()]) # Original File original_ROI_contour = rtss.ROIContourSequence # ROI Contour Sequence for roi_contour in roi_contour_sequence: roi_contour.add_new(Tag("ROIDisplayColor"), "IS", rgb) roi_contour.add_new(Tag("ContourSequence"), "SQ", contour_sequence) # ROI Sequence for contour in contour_sequence: # if data_set.get("ReferencedImageSequence"): contour.add_new(Tag("ContourImageSequence"), "SQ", contour_image_sequence) # Contour Sequence for contour_image in contour_image_sequence: contour_image.add_new(Tag("ReferencedSOPClassUID"), "UI", referenced_sop_class_uid) # CT Image Storage contour_image.add_new(Tag("ReferencedSOPInstanceUID"), "UI", referenced_sop_instance_uid) contour.add_new(Tag("ContourNumber"), "IS", 1) if not _is_closed_contour(roi_coordinates): contour.add_new(Tag("ContourGeometricType"), "CS", "OPEN_PLANAR") contour.add_new(Tag("NumberOfContourPoints"), "IS", number_of_contour_points) contour.add_new(Tag("ContourData"), "DS", roi_coordinates) else: contour.add_new(Tag("ContourGeometricType"), "CS", "CLOSED_PLANAR") contour.add_new(Tag("NumberOfContourPoints"), "IS", number_of_contour_points-1) contour.add_new(Tag("ContourData"), "DS", roi_coordinates[0:-3]) roi_contour.add_new(Tag("ReferencedROINumber"), "IS", roi_number) # Combine original ROIContourSequence with new original_ROI_contour.extend(roi_contour_sequence) rtss.add_new(Tag("ROIContourSequence"), "SQ", original_ROI_contour) # Saving a new RTROIObservationsSequence RT_ROI_observations_sequence = Sequence([Dataset()]) original_ROI_observation_sequence = rtss.RTROIObservationsSequence for ROI_observations in RT_ROI_observations_sequence: # TODO: Check to make sure that there aren't multiple observations per ROI, e.g. increment from existing Observation Numbers? ROI_observations.add_new(Tag("ObservationNumber"), 'IS', roi_number) ROI_observations.add_new(Tag("ReferencedROINumber"), 'IS', roi_number) ROI_observations.add_new(Tag("RTROIInterpretedType"), 'CS', rt_roi_interpreted_type) original_ROI_observation_sequence.extend(RT_ROI_observations_sequence) rtss.add_new(Tag("RTROIObservationsSequence"), "SQ", original_ROI_observation_sequence) else: # Add contour image data to existing ROI rtss = add_to_roi(rtss, roi_name, roi_coordinates, data_set) return rtss
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]))
def add_new_roi(rtss, roi_name, roi_coordinates, data_set, rt_roi_interpreted_type): """ Add the information of a new ROI to the rtss :param rtss: dataset of RTSS :param roi_name: ROIName :param roi_coordinates: Coordinates of pixels for new ROI :param data_set: data set of selected DICOM image file :param rt_roi_interpreted_type: the interpreted type of the new ROI :return: rtss, with added ROI """ number_of_contour_points = len(roi_coordinates) / 3 referenced_sop_class_uid = data_set.SOPClassUID referenced_sop_instance_uid = data_set.SOPInstanceUID # Check if there is any ROIs in rtss if not len(rtss["StructureSetROISequence"].value): referenced_frame_of_reference_uid = data_set.FrameOfReferenceUID roi_number = 1 else: first_roi_sequence = rtss["StructureSetROISequence"].value[0] referenced_frame_of_reference_uid = \ first_roi_sequence.ReferencedFrameOfReferenceUID roi_number = \ rtss["StructureSetROISequence"].value[-1].ROINumber + 1 # Colour TBC red = random.randint(0, 255) green = random.randint(0, 255) blue = random.randint(0, 255) rgb = [red, green, blue] # Saving a new StructureSetROISequence structure_set_sequence = Sequence([Dataset()]) original_structure_set = rtss.StructureSetROISequence for structure_set in structure_set_sequence: structure_set.add_new(Tag("ROINumber"), 'IS', roi_number) structure_set.add_new(Tag("ReferencedFrameOfReferenceUID"), 'UI', referenced_frame_of_reference_uid) structure_set.add_new(Tag("ROIName"), 'LO', roi_name) structure_set.add_new(Tag("ROIGenerationAlgorithm"), 'CS', "") # Combine old and new structure set original_structure_set.extend(structure_set_sequence) rtss.add_new(Tag("StructureSetROISequence"), "SQ", original_structure_set) # Saving a new ROIContourSequence, ContourSequence, # ContourImageSequence roi_contour_sequence = Sequence([Dataset()]) contour_sequence = Sequence([Dataset()]) contour_image_sequence = Sequence([Dataset()]) # Original File original_roi_contour = rtss.ROIContourSequence # ROI Contour Sequence for roi_contour in roi_contour_sequence: roi_contour.add_new(Tag("ROIDisplayColor"), "IS", rgb) roi_contour.add_new(Tag("ContourSequence"), "SQ", contour_sequence) # ROI Sequence for contour in contour_sequence: # if data_set.get("ReferencedImageSequence"): contour.add_new(Tag("ContourImageSequence"), "SQ", contour_image_sequence) # Contour Sequence for contour_image in contour_image_sequence: contour_image.add_new( Tag("ReferencedSOPClassUID"), "UI", referenced_sop_class_uid) # CT Image Storage contour_image.add_new(Tag("ReferencedSOPInstanceUID"), "UI", referenced_sop_instance_uid) contour.add_new(Tag("ContourNumber"), "IS", 1) if not _is_closed_contour(roi_coordinates): contour.add_new(Tag("ContourGeometricType"), "CS", "OPEN_PLANAR") contour.add_new(Tag("NumberOfContourPoints"), "IS", number_of_contour_points) contour.add_new(Tag("ContourData"), "DS", roi_coordinates) else: contour.add_new(Tag("ContourGeometricType"), "CS", "CLOSED_PLANAR") contour.add_new(Tag("NumberOfContourPoints"), "IS", number_of_contour_points - 1) contour.add_new(Tag("ContourData"), "DS", roi_coordinates[0:-3]) roi_contour.add_new(Tag("ReferencedROINumber"), "IS", roi_number) # Combine original ROIContourSequence with new original_roi_contour.extend(roi_contour_sequence) rtss.add_new(Tag("ROIContourSequence"), "SQ", original_roi_contour) # Saving a new RTROIObservationsSequence rt_roi_observations_sequence = Sequence([Dataset()]) original_roi_observation_sequence = rtss.RTROIObservationsSequence for ROI_observations in rt_roi_observations_sequence: # TODO: Check to make sure that there aren't multiple # observations per ROI, e.g. increment from existing # Observation Numbers? ROI_observations.add_new(Tag("ObservationNumber"), 'IS', roi_number) ROI_observations.add_new(Tag("ReferencedROINumber"), 'IS', roi_number) ROI_observations.add_new(Tag("RTROIInterpretedType"), 'CS', rt_roi_interpreted_type) ROI_observations.add_new(Tag("ROIInterpreter"), 'CS', "") original_roi_observation_sequence.extend(rt_roi_observations_sequence) rtss.add_new(Tag("RTROIObservationsSequence"), "SQ", original_roi_observation_sequence) return rtss
def rtDicomFromPreviousFolder(input_folder, ctr_folder_names, ctr_names, base_img, masks_np, output_file_name, prefix_name=''): ''' :param input_folder: Input folder where the ORIGINAL DICOMS are stored :param ctr_folder_names: Name of folders where to search for contours :param ctr_names: Original contour names (this should have the same order as the contours in masks_np) :param base_img: Itk base image to use for obtaining the positions :param masks_np: This is the numpy 4D binary array with the masks for each contour (ctr, slice, w, h) :param prefix_name: Additional prefix string to use in the new names of the contours :return: ''' print('Getting pydicom DataSequence from previous data...') # Gets the proper rt_folder to look for contours cont_file = getLatestContourFolder(ctr_folder_names, input_folder) lstFilesContour = io_com.get_dicom_files_in_folder( join(input_folder, cont_file)) # ===================== Contours ================== ds = pydicom.read_file(lstFilesContour[0]) # Reads original dataset final_ROIContourSequence_values = [ ] # This should be an array of datasets and will be replaced from the original FileDataset # Iterate over the ROIContourSequences (ak the contours) Type: Sequence for new_idx_ctr, c_ctr_name in enumerate(ctr_names): old_idx_ctr = [ i for i, x in enumerate(ds.StructureSetROISequence) if x.ROIName == c_ctr_name ] if len(old_idx_ctr) == 0: # If the ROI was not found print( F'ERROR the contour {c_ctr_name} was not found on previous RTStructure' ) else: # If the ROI is found, the we use it as a template and just modify the positions of the contours old_idx_ctr = old_idx_ctr[0] # Get just the index ds.StructureSetROISequence[ new_idx_ctr].ROIName == c_ctr_name # Use the new name for this index ds.StructureSetROISequence[ new_idx_ctr].ROINumber == new_idx_ctr + 1 # Use the new name for this index ds.Manufacturer = 'Deep Learning Biomarkers Group' ds.SeriesDate = str(datetime.date.today().strftime('%Y%m%d')) ds.SeriesDescription = 'NN Prediction' ds.StructureSetName = 'NN Prediction' print(F'\t Found {c_ctr_name} with idx {old_idx_ctr}!!!') # Copy the ROIContourSequence of the OLD contour (VERY IMPORTANT STEP, we initalize with everything that was there before) old_ROIContour_ds = ds.ROIContourSequence[old_idx_ctr] ds.StructureSetROISequence[ old_idx_ctr].ROIName = F'{prefix_name}{c_ctr_name}' cur_mask = masks_np[ new_idx_ctr, :, :, :] # Get the proper mask data slices_w_data = np.unique(np.where(cur_mask > 0)[0]) cur_ContourSequence_values = [] # Use the first contour sequence as template (IMPORTANT) old_ContourSequence_ds = old_ROIContour_ds.ContourSequence[0] # Iterate slices with some contour inside. Transform the mask to contour and add it to the sequence for idx_slice, slice in enumerate(slices_w_data): # for idx_slice,slice in enumerate([82]): # Get a contour from the 2D mask using OpenCV ctr_pts = getContourMinPosFrom2DMask(cur_mask[slice, :, :]) # --- Use to visualize the contoured points for each slice and contour ----------- # import matplotlib.pyplot as plt # plt.imshow(cur_mask[slice,:,:]) # plt.title(slice) # for c_ctr in ctr_pts: # plt.scatter(c_ctr[:,0,0],c_ctr[:,0,1],s=1) # plt.show() tot_ctrs = len(ctr_pts) # Iterate over EACH contour and create a dataset for each of them for idx_ctr in range(tot_ctrs): cur_contourdata_ds = Dataset( ) # This is the Contour data for this ContourSequence # Copy the desired values from the OLD ContourSequence cur_contourdata_ds.ContourGeometricType = old_ContourSequence_ds.ContourGeometricType contourdata_values = [] tot_pts = ctr_pts[idx_ctr].shape[0] this_slice_indx_pos = [] # Add the 'desired' format of (x,y,z) for each contour point cur_ctr_pts = ctr_pts[idx_ctr] for cur_pt in cur_ctr_pts: this_slice_indx_pos.append( [int(cur_pt[0][0]), int(cur_pt[0][1]), int(slice)]) # We add again the first point of each contour at the end (trying to 'close' the contour) this_slice_indx_pos.append([ int(cur_ctr_pts[0][0][0]), int(cur_ctr_pts[0][0][1]), int(slice) ]) # Transform each point into a physical position for c_pos in this_slice_indx_pos: phys_pos = base_img.TransformIndexToPhysicalPoint( c_pos) contourdata_values.append(phys_pos[0]) contourdata_values.append(phys_pos[1]) contourdata_values.append(phys_pos[2]) # Copy the list of physical positions in the variable ContourData of the dataset cur_contourdata_ds.ContourData = MultiValue( pydicom.valuerep.DSfloat, contourdata_values) cur_contourdata_ds.NumberOfContourPoints = tot_pts # Append this Dataset into the list of slices with date cur_ContourSequence_values.append(cur_contourdata_ds) old_ROIContour_ds.ContourSequence = Sequence( cur_ContourSequence_values ) # Replace the desired sequence with the new values final_ROIContourSequence_values.append( old_ROIContour_ds) # Append it to the ROIContourSquence values # Replace the original Sequence of ROIContours with the new ones. These were initialized with the previous values # and only the Contour Data was replaced new_ROIContourSequence = Sequence(final_ROIContourSequence_values) ds.ROIContourSequence = new_ROIContourSequence # Replace old ROIContourSequence with new one pydicom.dcmwrite(output_file_name, ds)