def add_dimension_organization( self, dim_organization: DimensionOrganizationSequence) -> None: """Adds a dimension organization sequence to the dataset. This methods registers the (0x0020, 0x9164) DimensionOrganizationUID and appends all items from the sequence to (0x0020, 0x9222) DimensionIndexSequence. Args: dim_organization: A `DimensionOrganizationSequence` with one or more dimension items configured. """ if 'DimensionOrganizationSequence' not in self: self.DimensionOrganizationSequence = pydicom.Sequence() self.DimensionIndexSequence = pydicom.Sequence() for item in self.DimensionOrganizationSequence: if item.DimensionOrganizationUID == dim_organization[ 0].DimensionOrganizationUID: raise ValueError( 'Dimension organization with UID ' f'{item.DimensionOrganizationUID} already exists') item = pydicom.Dataset() item.DimensionOrganizationUID = dim_organization[ 0].DimensionOrganizationUID self.DimensionOrganizationSequence.append(item) self.DimensionIndexSequence.extend(dim_organization)
def _create_empty_pixel_measures_sequence(self) -> pydicom.Dataset: ds = pydicom.Dataset() ds.SharedFunctionalGroupsSequence = pydicom.Sequence([pydicom.Dataset()]) ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence = pydicom.Sequence( [pydicom.Dataset()] ) return ds
def get_local_dicom_sequence(dir_entry_folder, rgx_dicom=re.compile(r'^i\d+\.MRDC\.\d+$'), presort=True): """Get the DICOM Sequence in a given DirEntry folder whose DICOM Datasets that match the provided Regex :param dir_entry_folder: A DirEntry folder holding DICOM Datasets that will be bundled as a DICOM Sequence :type dir_entry_folder: DirEntry :param rgx_dicom: A Regex matching the DICOM filenames :type rgx_dicom: Regex :param presort: A boolean flag for sorting the DICOM Datasets within the DICOM Sequence before returning it :type presort: boolean :return: pydicom Sequence of DICOM Datasets (where a DICOM "dataset" is a DICOM file) :rtype: pydicom Sequence """ subitems = get_local_subitems(dir_entry_folder) dicom_subfiles = get_local_subfiles(subitems, rgx_dicom) if presort: sorted_dicom_subfiles = sorted( dicom_subfiles, key=lambda f: int(f.name.split(".")[-1])) dicom_datasets = map( lambda dicom_subfile: get_local_dicom_dataset(dicom_subfile), sorted_dicom_subfiles) else: dicom_datasets = map( lambda dicom_subfile: get_local_dicom_dataset(dicom_subfile), dicom_subfiles) return pydicom.Sequence(dicom_datasets)
def from_dcmqi_metainfo(metainfo: Union[dict, str]) -> pydicom.Dataset: """Converts a `metainfo.json` file from the dcmqi project to a `pydicom.Dataset` with the matching DICOM data elements set from JSON. Those JSON files can be easilly created using the segmentation editor tool from QIICR/dcmqi: http://qiicr.org/dcmqi/#/seg When converting the JSON to a DICOM dataset, the validity of the provided JSON document is ensured using the official JSON schema files from the dcmqi project. Args: metainfo: Either a `str` for a file path to read from or a `dict` with the JSON already imported or constructed in source code. Returns: A `pydicom.Dataset` containg all values from the JSON document and some defaults if the elements were not available. """ # Add convienence loader of JSON dictionary if isinstance(metainfo, str): with open(metainfo) as ifile: metainfo = json.load(ifile) assert isinstance(metainfo, dict) # Validate dictionary against dcmqi JSON schemas validator = _create_validator() if not validator.is_valid(metainfo): raise NotImplementedError() # Create dataset from provided JSON dataset = pydicom.Dataset() tags_with_defaults = [ ("BodyPartExamined", ""), ("ClinicalTrialCoordinatingCenterName", ""), ("ClinicalTrialSeriesID", "Session1"), ("ClinicalTrialTimePointID", "1"), ("ContentCreatorName", "Reader1"), ("ContentDescription", "Image segmentation"), ("ContentLabel", "SEGMENTATION"), ("InstanceNumber", "1"), ("SeriesDescription", "Segmentation"), ("SeriesNumber", "300"), ] for tag_name, default_value in tags_with_defaults: dataset.__setattr__(tag_name, metainfo.get(tag_name, default_value)) if len(metainfo["segmentAttributes"]) > 1: raise ValueError( "Only metainfo.json files written for single-file input are supported" ) dataset.SegmentSequence = pydicom.Sequence( [_create_segment_dataset(x) for x in metainfo["segmentAttributes"][0]]) return dataset
def _create_code_sequence(data: dict) -> pydicom.Sequence: """Helper function for creating a DICOM sequence from JSON attributes. Returns: A `pydicom.Sequence` with a single `pydicom.Dataset` item containing all attributes from the JSON document. """ dataset = pydicom.Dataset() for key in data: dataset.__setattr__(key, data[key]) return pydicom.Sequence([dataset])
def add_instance_reference(self, dataset: pydicom.Dataset) -> bool: """Adds an instance to the (0x0008,0x1115) ReferencedSeriesSequence. Args: dataset: A `pydicom.Dataset` DICOM image which should be added to the segmentation dataset. Returns: Returns `True`, if `dataset` was added as a reference. """ if 'ReferencedSeriesSequence' not in self: self.ReferencedSeriesSequence = pydicom.Sequence() for series_item in self.ReferencedSeriesSequence: if series_item.SeriesInstanceUID != dataset.SeriesInstanceUID: continue for instance_item in series_item.ReferencedInstanceSequence: if instance_item.ReferencedSOPInstanceUID == dataset.SOPInstanceUID: return False # Series found, but instance is missing break else: # Series not yet referenced, create a new series item series_item = pydicom.Dataset() series_item.SeriesInstanceUID = dataset.SeriesInstanceUID series_item.ReferencedInstanceSequence = pydicom.Sequence([]) self.ReferencedSeriesSequence.append(series_item) # Instance not yet referenced, create a new instance item instance_item = pydicom.Dataset() instance_item.ReferencedSOPClassUID = dataset.SOPClassUID instance_item.ReferencedSOPInstanceUID = dataset.SOPInstanceUID series_item.ReferencedInstanceSequence.append(instance_item) return True
def set_shared_functional_groups_sequence( target: pydicom.Dataset, segmentation: sitk.Image ) -> None: spacing = segmentation.GetSpacing() dataset = pydicom.Dataset() dataset.PixelMeasuresSequence = [pydicom.Dataset()] dataset.PixelMeasuresSequence[0].PixelSpacing = [f"{x:e}" for x in spacing[:2]] dataset.PixelMeasuresSequence[0].SliceThickness = f"{spacing[2]:e}" dataset.PixelMeasuresSequence[0].SpacingBetweenSlices = f"{spacing[2]:e}" dataset.PlaneOrientationSequence = [pydicom.Dataset()] dataset.PlaneOrientationSequence[ 0 ].ImageOrientationPatient = sitk_to_dcm_orientation(segmentation) target.SharedFunctionalGroupsSequence = pydicom.Sequence([dataset])
def set_shared_functional_groups_sequence(target: pydicom.Dataset, segmentation: sitk.Image): spacing = segmentation.GetSpacing() dataset = pydicom.Dataset() dataset.PixelMeasuresSequence = [pydicom.Dataset()] dataset.PixelMeasuresSequence[0].PixelSpacing = [ f'{x:e}' for x in spacing[:2] ] dataset.PixelMeasuresSequence[0].SliceThickness = f'{spacing[2]:e}' dataset.PixelMeasuresSequence[0].SpacingBetweenSlices = f'{spacing[2]:e}' dataset.PlaneOrientationSequence = [pydicom.Dataset()] dataset.PlaneOrientationSequence[0].ImageOrientationPatient = [ f'{x:e}' for x in np.ravel(segmentation.GetDirection())[:6] ] target.SharedFunctionalGroupsSequence = pydicom.Sequence([dataset])
def sqlist_to_sequence(slist): temp = [] for item in slist: ds = pydicom.dataset.Dataset() for key in item.keys(): tag = key_to_Tag(key) VR = item[key][1] val = item[key][2] if VR == 'SQ': val = sqlist_to_sequence(val) elif (VR == 'UI'): val = uidname_to_val(val) else: val = eval(val) ds.add_new(tag, VR, val) temp.append(ds) return pydicom.Sequence(temp)
def ConvertDataset(ds: pydicom.Dataset) -> pydicom.Dataset: msg = 'tag VR length value value_tell is_implicit_VR is_little_endian used_in_verification' MyRawDataElement = namedtuple('RawDataElement', msg) if type(ds) == pydicom.dataset.FileDataset: ds.file_meta = ConvertDataset(ds.file_meta) already_coverted = False for key, val in ds.items(): try: elem = ds[key] already_coverted = True except KeyError: elem = val already_coverted = False if type(elem.value) == pydicom.Sequence: new_seq_elem = DataElementX(elem.tag, elem.VR, pydicom.Sequence()) for item in elem.value: new_seq_elem.value.append(ConvertDataset(item)) ds[key] = new_seq_elem elif type(elem.value) == pydicom.Dataset: ds_elem = DataElementX(elem.tag, elem.VR, pydicom.Dataset()) ds_elem = ConvertDataset(elem.value) ds[key] = ds_elem else: if type(elem) == pydicom.dataelem.RawDataElement: # new_elem = pydicom.dataelem.RawDataElement(elem.tag, elem.VR, elem.length, elem.value, # elem.value_tell, elem.is_implicit_VR, elem.is_little_endian, False) # ds[key] = elem new_elem = DataElementX(elem.tag, 'UN', elem.value, already_converted=already_coverted) elif type(elem) == pydicom.DataElement: new_elem = DataElementX(elem.tag, elem.VR, elem.value, already_converted=already_coverted) ds[key] = new_elem return ds
def add_frame( self, data: np.ndarray, referenced_segment: int, referenced_images: List[pydicom.Dataset] = None ) -> pydicom.Dataset: """Adds a frame to the dataset. Adds the frame data to the PixelData element after encoding the data into the correct format for the configured segmentation type. Each referenced image is also registered in (0x0008,0x1115) ReferencedSeriesSequence. Args: data: A `np.ndarray` with integer data type for binary or floating point data type for fractional segmentations. referenced_segment: An integer number for the segment to which this frame belongs. The segment must exist in (0x0062, 0x0002) SegmentSequence. referenced_images: A list of `pydicom.Dataset` source images, which map to the frame. Returns: A `pydicom.Dataset` with pre-initialized values for an item in (0x5200,0x9230) PerFrameFunctionalGroupsSequence, which needs further configuration based on writer dependent strategy. """ referenced_images = referenced_images or [] if len(data.shape) != 2: raise ValueError('Invalid frame data shape, expecting 2D images') if data.shape[0] != self.Rows or data.shape[1] != self.Columns: raise ValueError( f'Invalid frame data shape, expecting {self.Rows}x{self.Columns} images' ) # TODO Optimize packing/unpacking/concatenation by using a io.BytesIO # TODO stream and track free bits in the last byte if self.SegmentationType == SegmentationType.BINARY.value: if not np.issubdtype(data.dtype, np.integer): raise ValueError( 'Binary segmentation data requires an integer data type') data = np.greater(data, 0, dtype=np.uint8) self._frames.append(data) self.PixelData = np.packbits(np.ravel(self._frames), bitorder='little').tobytes() else: if not np.issubdtype(data.dtype, np.floating): raise ValueError( 'Fractional segmentation data requires a floating point data type' ) data = np.clip(data, 0.0, 1.0) data *= self.MaximumFractionalValue data = data.astype(np.uint8) self._frames.append(data.ravel()) self.PixelData = np.concatenate(self._frames).tobytes() # A frame was added to the dataset self.NumberOfFrames += 1 # Update (0x5200,0x9230) PerFunctionalGroupsSequence frame_fg_item = pydicom.Dataset() if referenced_segment not in [ x.SegmentNumber for x in self.SegmentSequence ]: raise IndexError('Segment not found in SegmentSequence') frame_fg_item.SegmentIdentificationSequence = pydicom.Sequence( [pydicom.Dataset()]) frame_fg_item.SegmentIdentificationSequence[ 0].ReferencedSegmentNumber = referenced_segment # Each frame requires references to the original DICOM files derivation_image = pydicom.Dataset() derivation_image.SourceImageSequence = pydicom.Sequence() for referenced_image in referenced_images: # Update (0x0008,0x1115) ReferencedSeriesSequence for each referenced image self.add_instance_reference(referenced_image) # Add referenced image to SourceImageSequence ref = pydicom.Dataset() ref.ReferencedSOPClassUID = referenced_image.SOPClassUID ref.ReferencedSOPInstanceUID = referenced_image.SOPInstanceUID ref.PurposeOfReferenceCodeSequence = CodeSequence( '121322', 'DCM', 'Source image for image processing operation') derivation_image.SourceImageSequence.append(ref) derivation_image.DerivationCodeSequence = CodeSequence( '113076', 'DCM', 'Segmentation') frame_fg_item.DerivationImageSequence = pydicom.Sequence( [derivation_image]) self.PerFrameFunctionalGroupsSequence.append(frame_fg_item) return frame_fg_item
def labelvol_to_rtstruct(roi_vol, aff, refdcm_file, filename, ordered_slices, uid_base='1.2.826.0.1.3680043.9.7147.', seriesDescription='test rois', structureSetLabel='RTstruct', structureSetName='my rois', connect_holes=True, roinames=None, roidescriptions=None, roigenerationalgs=None, roi_colors=[['255', '0', '0'], ['0', '0', '255'], ['0', '255', '0'], ['255', '0', '255'], ['255', '255', '0'], ['0', '255', '255']], tags_to_copy=[ 'PatientName', 'PatientID', 'AccessionNumber', 'StudyID', 'StudyDescription', 'StudyDate', 'StudyTime', 'SeriesDate', 'SeriesTime' ], tags_to_add=None): """Convert a 3D array with integer ROI label to RTstruct Parameters --------- roi_vol : 3d numpy integer array in LPS orientation containing the ROI labels 0 is considered background 1 ... ROI-1 n ... ROI-n aff : 2d 4x4 numpy array affine matrix that maps from voxel to (RAS) world coordinates refdcm_file : string or list A single reference dicom file or a list of reference files (multiple CT slices) From this file several dicom tags are copied (e.g. the FrameOfReferenceUID). In case a list of files is given, the dicom tags are copied from the first file, however all SOPInstanceUIDs are added to the ContourImageSequence (needed for some RT systems) filename : string name of the output rtstruct file uid_base : string, optional uid base used to generate some dicom UID seriesDescription : string, optional dicom series description for the rtstruct series structureSetLabel, structureSetName : string, optional Label and Name of the structSet connect_holes : bool, optional whether to connect inner holes to their outer parents contour - default: True this connection is needed to show holes correctly in MIM roinames, roidescriptions, roigenerationalgs : lists, optional containing strings for ROIName, ROIDescription and ROIGenerationAlgorithm roi_colors: list of lists containing 3 integer strings (0 - 255), optional used as ROI display colors tags_to_copy: list of strings, list optional extra dicom tags to copy from the refereced dicom file tags_to_add: dictionary with valid dicom tags to add in the header """ roinumbers = np.unique(roi_vol) roinumbers = roinumbers[roinumbers > 0] nrois = len(roinumbers) if isinstance(refdcm_file, list): refdcm = pydicom.read_file(refdcm_file[0]) else: refdcm = pydicom.read_file(refdcm_file) file_meta = pydicom.Dataset() file_meta.ImplementationClassUID = uid_base + '1.1.1' file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid( '1.2.840.10008.5.1.4.1.1.481.3.') ds = pydicom.FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) ds.Modality = 'RTSTRUCT' ds.SeriesDescription = seriesDescription #--- copy dicom tags from reference dicom file for tag in tags_to_copy: if tag in refdcm: setattr(ds, tag, refdcm.data_element(tag).value) else: warnings.warn( tag + ' not in reference dicom file -> will not be written') ds.StudyInstanceUID = refdcm.StudyInstanceUID ds.SeriesInstanceUID = pydicom.uid.generate_uid(uid_base) ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.481.3' ds.SOPInstanceUID = pydicom.uid.generate_uid(uid_base) ds.StructureSetLabel = structureSetLabel ds.StructureSetName = structureSetName dfr = pydicom.Dataset() dfr.FrameOfReferenceUID = refdcm.FrameOfReferenceUID ds.ReferencedFrameOfReferenceSequence = pydicom.Sequence([dfr]) if tags_to_add is not None: for tag, value in tags_to_add.items(): setattr(ds, tag, value) ####################################################################### ####################################################################### # write the ReferencedFrameOfReferenceSequence contourImageSeq = pydicom.Sequence() if isinstance(refdcm_file, list): # in case we got all reference dicom files we add all SOPInstanceUIDs # otherwise some RT planning systems refuse to read the RTstructs for fname in refdcm_file: with pydicom.read_file(fname) as tmpdcm: tmp = pydicom.Dataset() tmp.ReferencedSOPClassUID = tmpdcm.SOPClassUID tmp.ReferencedSOPInstanceUID = tmpdcm.SOPInstanceUID contourImageSeq.append(tmp) else: tmp = pydicom.Dataset() tmp.ReferencedSOPClassUID = refdcm.SOPClassUID tmp.ReferencedSOPInstanceUID = refdcm.SOPInstanceUID contourImageSeq.append(tmp) tmp2 = pydicom.Dataset() tmp2.SeriesInstanceUID = refdcm.SeriesInstanceUID tmp2.ContourImageSequence = contourImageSeq tmp3 = pydicom.Dataset() tmp3.ReferencedSOPClassUID = '1.2.840.10008.3.1.2.3.1' # TODO SOP just copied from MIM rtstructs tmp3.ReferencedSOPInstanceUID = refdcm.StudyInstanceUID tmp3.RTReferencedSeriesSequence = pydicom.Sequence([tmp2]) tmp4 = pydicom.Dataset() tmp4.FrameOfReferenceUID = refdcm.FrameOfReferenceUID tmp4.RTReferencedStudySequence = pydicom.Sequence([tmp3]) ds.ReferencedFrameOfReferenceSequence = pydicom.Sequence([tmp4]) ####################################################################### ####################################################################### ds.StructureSetROISequence = pydicom.Sequence() ds.ROIContourSequence = pydicom.Sequence() if roinames is None: roinames = ['ROI-' + str(x) for x in roinumbers] if roidescriptions is None: roidescriptions = ['ROI-' + str(x) for x in roinumbers] if roigenerationalgs is None: roigenerationalgs = len(roinumbers) * ['MANUAL'] # loop over the ROIs for iroi, roinumber in enumerate(roinumbers): dssr = pydicom.Dataset() dssr.ROINumber = roinumber dssr.ROIName = roinames[iroi] dssr.ROIDescription = roidescriptions[iroi] dssr.ROIGenerationAlgorithm = roigenerationalgs[iroi] dssr.ReferencedFrameOfReferenceUID = dfr.FrameOfReferenceUID ds.StructureSetROISequence.append(dssr) ####################################################################### ####################################################################### # write ROIContourSequence containing the actual 2D polygon points of the ROI ds_contour = pydicom.Dataset() ds_contour.ReferencedSOPClassUID = refdcm.SOPClassUID ### # generate binary volume for the current ROI bin_vol = (roi_vol == dssr.ROINumber).astype(int) # find the bounding box in the last direction ob_sls = find_objects(bin_vol) z_start = min([x[2].start for x in ob_sls]) z_end = max([x[2].stop for x in ob_sls]) ds_roi_contour = pydicom.Dataset() ds_roi_contour.ROIDisplayColor = roi_colors[iroi % len(roi_colors)] ds_roi_contour.ReferencedROINumber = dssr.ROINumber ds_roi_contour.ContourSequence = pydicom.Sequence() # loop over the slices in the 2 direction to create 2D polygons for sl in np.arange(z_start, z_end): bin_slice = bin_vol[:, :, sl] if bin_slice.max() > 0: contours = binary_2d_image_to_contours( bin_slice, connect_holes=connect_holes) for ic in range(len(contours)): npoints = contours[ic].shape[0] contour = np.zeros((npoints, 3)) for ipoint in range(npoints): contour[ipoint, :] = (aff @ np.concatenate( (contours[ic][ipoint, :], [sl, 1])))[:-1] dsci = pydicom.Dataset() dsci.ReferencedSOPInstanceUID = ordered_slices[sl][0] dsci.ContourGeometricType = 'CLOSED_PLANAR' dsci.NumberOfContourPoints = contour.shape[0] dsci.ContourImageSequence = pydicom.Sequence([ds_contour]) dsci.ContourData = contour.flatten().tolist() # ContourImageSequence contains 1 element per 2D contour ds_roi_contour.ContourSequence.append(dsci) # has to contain one element per ROI ds.ROIContourSequence.append(ds_roi_contour) ####################################################################### ####################################################################### pydicom.filewriter.write_file(os.path.join('.', filename), ds, write_like_original=False) return z_start, z_end
def __init__(self, fname, spotspecs=None, uid=None, verbose=False): """ Create a fake treatment plan DICOM file for unit testing. The spot specs are a list of dictionaries, one dictionary per beam. Each beam dictionary contains optional entries for gantry angle, patient support angle and isocenter position and an obligatory list of "controlpoints". A file with filename `fname` should not yet exist. A DICOM file with name `fname` will be written by _test_plan_creator, and also be deleted when the creator goes out of scope. """ assert (sys.version_info.major == 3) self.verbose = verbose assert (not os.path.exists(fname)) self.dcm_filename = fname classUID = '1.2.840.10008.5.1.4.1.1.481.8' if uid is None else uid instanceUID = pydicom.uid.generate_uid() # File meta info data elements file_meta = pydicom.Dataset() file_meta.MediaStorageSOPClassUID = str(classUID) file_meta.MediaStorageSOPInstanceUID = str(instanceUID) file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian #FIXME: we probably need to apply for an official UID here file_meta.ImplementationClassUID = '1.2.826.0.1.3680043.1.2.100.6.40.0.76' file_meta.ImplementationVersionName = 'DicomObjects.NET' self.ds = pydicom.FileDataset(self.dcm_filename, {}, file_meta=file_meta, preamble=b"\0" * 128) self.ds.SOPClassUID = classUID self.ds.SOPInstanceUID = instanceUID self.ds.PatientName = "unit test" self.ds.PatientID = "42" #print("trying to fix 'is_little_endian'") if spotspecs: self.ds.IonBeamSequence = pydicom.Sequence() self.ds.NumberOfBeams = len(spotspecs) for beamdata in spotspecs: beam = pydicom.Dataset() beam.BeamName = beamdata.get('Nm', 'noname') beam.BeamNumber = beamdata.get('Nr', -1) beam.NumberOfIonControlPoints = len(beamdata["controlpoints"]) beam.IonControlPointSequence = pydicom.Sequence() for i, icpdata in enumerate(beamdata["controlpoints"]): icp = pydicom.Dataset() if i == 0: if 'G' in beamdata: icp.GantryAngle = beamdata['G'] if 'P' in beamdata: icp.PatientSupportAngle = beamdata['P'] if "I" in beamdata: icp.IsocenterPosition = list( [str(v) for v in beamdata.get('I')]) else: icp.IsocenterPosition = "" icp.NominalBeamEnergy = icpdata.get('E', 100.) if 'T' in icpdata: icp.ScanSpotTuneID = icpdata['T'] #unfortunately, this does not work icp.ScanSpotMetersetWeights = [ float(v) for v in icpdata['SSW'] ] icp.ScanSpotPositionMap = [ float(v) for v in np.array(icpdata['SSP']).flatten() ] icp.NumberOfScanSpotPositions = int( icpdata.get('NSSP', len(icp.ScanSpotPositionMap) / 2)) beam.IonControlPointSequence.append(icp) self.ds.IonBeamSequence.append(beam) else: self.ds.NumberOfBeams = 0 if verbose: logger.info("number of beams is {}".format(self.ds.NumberOfBeams)) dt = datetime.datetime.now() self.ds.ContentDate = dt.strftime('%Y%m%d') timeStr = dt.strftime('%H%M%S.%f') # long format with micro seconds self.ds.ContentTime = timeStr self.ds.is_little_endian = True self.ds.is_implicit_VR = True self.ds.fix_meta_info() self.ds.save_as(self.dcm_filename) if self.verbose: logger.info("wrote dicom file {}".format(self.dcm_filename))
def __init__(self, *, rows: int, columns: int, segmentation_type: SegmentationType, segmentation_fractional_type: SegmentationFractionalType = SegmentationFractionalType.PROBABILITY, max_fractional_value: int = 255): """Initializes the segmentation dataset. Args: rows: Number of rows in a frame/image (y-axis) columns: Number of columns in a frame/image (x-axis) segmentation_type: Either `SegmentationType.BINARY` or `SegmentationType.FRACTIONAL` depending on the data to encode. segmentation_fractional_type: If `segmentation_type == SegmentationType.FRACTIONAL`, then the fractional type indicates the semantic meaning. Can be either `PROBABILITY` or `OCCUPANCY`. max_fractional_value: Fractional data is expected to be within `[0.0, 1.0]` and will be rescaled to `[0, max_fractional_value]`. """ super().__init__() self._frames: List[np.ndarray] = [] self.SpecificCharacterSet = 'ISO_IR 100' self.SOPClassUID = SegmentationStorage self.SOPInstanceUID = pydicom.uid.generate_uid() self._set_file_meta() # General Series module self.Modality = 'SEG' self.SeriesInstanceUID = pydicom.uid.generate_uid() self.SeriesNumber = 1 # Generate SOP and Series and General Image timestamps timestamp = datetime.now() self.InstanceCreationDate = timestamp.strftime('%Y%m%d') self.InstanceCreationTime = timestamp.strftime('%H%M%S.%f') self.SeriesDate = self.InstanceCreationDate self.SeriesTime = self.InstanceCreationTime self.ContentDate = self.InstanceCreationDate self.ContentTime = self.InstanceCreationTime # Frame of Reference module self.FrameOfReferenceUID = pydicom.uid.generate_uid() # Enhanced General Equipment module # http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.5.2.html#table_C.7-8b self.Manufacturer = 'pydicom-seg' self.ManufacturerModelName = '[email protected]/razorx89/pydicom-seg.git' self.DeviceSerialNumber = '0' self.SoftwareVersions = __version__ # Image Pixel module if rows <= 0 or columns <= 0: raise ValueError('Rows and columns must be larger than zero') self.Rows = rows self.Columns = columns self.PixelData = b'' # Segmentation Image module # http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.20.2.html#table_C.8.20-2 self.ImageType = ['DERIVED', 'PRIMARY'] self.InstanceNumber = '1' self.ContentLabel = 'SEGMENTATION' self.ContentDescription = '' self.ContentCreatorName = '' self.SamplesPerPixel = 1 self.PhotometricInterpretation = 'MONOCHROME2' self.PixelRepresentation = 0 self.LossyImageCompression = '00' self.SegmentSequence = pydicom.Sequence() self.SegmentationType = segmentation_type.value if segmentation_type == SegmentationType.BINARY: # Binary segmentations are always bit-packed self.BitsAllocated = 1 self.BitsStored = 1 self.HighBit = 0 else: # Fractional segmentations are always 8-bit unsigned self.SegmentationFractionalType = segmentation_fractional_type.value if max_fractional_value < 1 or max_fractional_value > 255: raise ValueError('Invalid maximum fractional value for 8-bit unsigned int data') self.MaximumFractionalValue = max_fractional_value self.BitsAllocated = 8 self.BitsStored = 8 self.HighBit = 7 # Multi-frame Functional Groups module self.SharedFunctionalGroupsSequence = pydicom.Sequence([pydicom.Dataset()]) self.PerFrameFunctionalGroupsSequence = pydicom.Sequence() self.NumberOfFrames = 0
def deidentifier(src_path, newptID): ds = pydicom.dcmread(src_path) ## defer_size could improve performance ds.remove_private_tags( ) # this will get rid of all private tags, this will cause some loss of information but this is inevitable for good de-identification if (0x40, 0x275) in ds: del ds[(0x40, 0x275)] # ugly way to get arround key error of (0x40,0x275) for row in ds.iterall(): # if tag of data_element is in X_list it will be deleted if row.tag in Tag_dict["X_list"]: delattr(ds, row.keyword) # if tag of data_elem is in Z_list it shall be set to an empty string elif row.tag in Tag_dict["Z_list"]: row.value = "" # if tag in in D_list it shall be set to a dummy value elif row.tag in Tag_dict["D_list"]: # sequences needed some extra touch, since they need to be changed to an empty sequence if row.VR == "SQ": row.value = pydicom.Sequence( [pydicom.Dataset()]) # Might cause issues at some point # gets the dummie values from VR_dummies by VR else: row.value = VR_dummies[row.VR] # if tag of data_elem is in U_list a new UID needs to be created, keeping the internal consistency elif row.tag in Tag_dict["U_list"]: if row.value not in known_UIDS.keys(): new_UID = "2.25." + str(uuid.uuid4().int).lstrip( "0") # or use pydicom.uid.generate(prefix = None) ''' In accordance with the NEMA standard of creating UID by using Universeally Unique Identifier (UUID) http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_B.2.html ''' known_UIDS[row.value] = new_UID row.value = new_UID else: new_UID = known_UIDS[row.value] row.value = new_UID # two hardcoded group removals for the 50xx (Curve Data) and (60xx,3000)[Overlay Data] and (60xx,4000)[Overlay Comments] groups of the nema standards: group = row.tag.group if group >= 0x5000 and group < 0x5100: delattr(ds, row.keyword) if group >= 0x6000 and group < 0x6100 and ( row.tag.element == 0x3000 or row.tag.element == 0x4000): delattr(ds, row.keyword) # sets PatientID and PatientName to a newptID given when calling this function ds.PatientID = newptID ds.PatientName = newptID # removes all dates mentioned in the seriesDescription in the fromat of "digitdigit nonwhitespacecharacter digitdigit nonwhitespacecharacter digitdigitdigitdigit" if (0x008, 0x103E) in ds: ds.SeriesDescription = re.sub(r"\d{2}\S\d{2}\S\d{4}", "", ds.SeriesDescription) return ds
def __init__( self, *, rows: int, columns: int, segmentation_type: SegmentationType, segmentation_fractional_type: SegmentationFractionalType = SegmentationFractionalType.PROBABILITY, reference_dicom: Optional[pydicom.Dataset] = None, max_fractional_value: int = 255): super().__init__() self._frames: List[np.ndarray] = [] if reference_dicom: writer_utils.import_hierarchy(target=self, reference=reference_dicom, import_frame_of_reference=True, import_series=False) else: logger.warning('No source images provided, cannot import patient '\ 'and study level information.') self.SpecificCharacterSet = 'ISO_IR 100' self.SOPClassUID = SegmentationStorage self.SOPInstanceUID = pydicom.uid.generate_uid() self._set_file_meta() # General Series module self.Modality = 'SEG' self.SeriesInstanceUID = pydicom.uid.generate_uid() self.SeriesNumber = 1 # Generate SOP and Series and General Image timestamps timestamp = datetime.now() self.InstanceCreationDate = timestamp.strftime('%Y%m%d') self.InstanceCreationTime = timestamp.strftime('%H%M%S.%f') self.SeriesDate = self.InstanceCreationDate self.SeriesTime = self.InstanceCreationTime self.ContentDate = self.InstanceCreationDate self.ContentTime = self.InstanceCreationTime # Frame of Reference module self.FrameOfReferenceUID = pydicom.uid.generate_uid() # Enhanced General Equipment module # http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.5.2.html#table_C.7-8b self.Manufacturer = 'pydicom-seg' self.ManufacturerModelName = 'https://github.com/razorx89/pydicom-seg' self.DeviceSerialNumber = '0' self.SoftwareVersions = __version__ # Image Pixel module if rows <= 0 or columns <= 0: raise ValueError('Rows and columns must be larger than zero') self.Rows = rows self.Columns = columns self.PixelData = b'' # Segmentation Image module # http://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.8.20.2.html#table_C.8.20-2 self.ImageType = ['DERIVED', 'PRIMARY'] self.InstanceNumber = '1' self.ContentLabel = 'SEGMENTATION' self.ContentDescription = '' self.ContentCreatorName = '' self.SamplesPerPixel = 1 self.PhotometricInterpretation = 'MONOCHROME2' self.PixelRepresentation = 0 self.LossyImageCompression = '00' self.SegmentSequence = pydicom.Sequence() self.SegmentationType = segmentation_type.value if segmentation_type == SegmentationType.BINARY: # Binary segmentations are always bit-packed self.BitsAllocated = 1 self.BitsStored = 1 self.HighBit = 0 else: # Fractional segmentations are always 8-bit unsigned self.SegmentationFractionalType = segmentation_fractional_type.value if max_fractional_value < 1 or max_fractional_value > 255: raise ValueError( 'Invalid maximum fractional value for 8-bit unsigned int data' ) self.MaximumFractionalValue = max_fractional_value self.BitsAllocated = 8 self.BitsStored = 8 self.HighBit = 7 # Multi-frame Functional Groups module self.SharedFunctionalGroupsSequence = pydicom.Sequence( [pydicom.Dataset()]) self.PerFrameFunctionalGroupsSequence = pydicom.Sequence() self.NumberOfFrames = 0
import pydicom as pdcm import PIL.Image as IMG import numpy as np import cv2 import os pdcm.Sequence() file_path = r"C:\Users\MERT\Desktop\Biokido\Bacak" file_path_list = os.listdir(file_path) cnt_volume_array = np.array([]) closed_cnt = [] pixel_counter = 0 def PolygonArea(corners): n = len(corners) area = 0.0 for i in range(n): j = (i + 1) % n area += corners[i][0] * corners[j][1] area -= corners[j][0] * corners[i][1] area = abs(area) / 2.0 return area for i in range(len(file_path_list)): file_name = file_path_list[i] im = cv2.imread(file_path + "/" + file_name) im = cv2.fastNlMeansDenoisingColored(im, None, 10, 10, 7, 21) roi = cv2.selectROI("contour", im) cv2.waitKey(1) cv2.destroyAllWindows()
else: ref.add_new(0x300a0018, 'DS', [0, 0, 0]) ref.add_new(0x300a0020, 'CS', 'ORGAN_AT_RISK') ref.add_new( 0x300a0023, 'DS', beamset.Prescription.PrimaryDosePrescription.DoseValue / 100) ref.add_new( 0x300a002c, 'DS', beamset.Prescription.PrimaryDosePrescription.DoseValue / 100) if 'DoseReferenceSequence' not in ds: ds.add_new(0x300a0010, 'SQ', pydicom.Sequence([ref])) expected.add(ds[0x300a0010]) else: if 'DoseReferenceStructureType' not in ds.DoseReferenceSequence[0] or \ ds.DoseReferenceSequence[0].DoseReferenceStructureType != \ ref.DoseReferenceStructureType: expected.add(ref[0x300a0014]) if 'DoseReferenceDescription' not in ds.DoseReferenceSequence[0] or \ ds.DoseReferenceSequence[0].DoseReferenceDescription != ref.DoseReferenceDescription: expected.add(ref[0x300a0016]) if 'DoseReferencePointCoordinates' not in ds.DoseReferenceSequence[0] or \ ds.DoseReferenceSequence[0].DoseReferencePointCoordinates != \ ref.DoseReferencePointCoordinates: