def mk_dcm(dcm_path, slice_data, meta): file_meta = pydicom.Dataset() file_meta.TransferSyntaxUID = '1.2.840.10008.1.2' file_meta.MediaStorageSOPClassUID = 'Secondary Capture Image Storage' file_meta.MediaStorageSOPInstanceUID = '1.3.6.1.4.1.9590.100.1.1.111165684411017669021768385720736873780' file_meta.ImplementationClassUID = '1.3.6.1.4.1.9590.100.1.0.100.4.0' ds = pydicom.FileDataset(dcm_path, {}, file_meta=file_meta, preamble=b'\0' * 128) ds.Modality = 'WSD' ds.ContentDate = str(datetime.date.today()).replace('-', '') ds.ContentTime = str(time.time()) #milliseconds since the epoch ds.StudyInstanceUID = '1.3.6.1.4.1.9590.100.1.1.124313977412360175234271287472804872093' ds.SeriesInstanceUID = '1.3.6.1.4.1.9590.100.1.1.369231118011061003403421859172643143649' ds.SOPInstanceUID = '1.3.6.1.4.1.9590.100.1.1.111165684411017669021768385720736873780' ds.SOPClassUID = 'Secondary Capture Image Storage' ds.SecondaryCaptureDeviceManufctur = 'Python 3.6' # TAGS NECESSARY TO CONTAIN IMAGE DATA: ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.PixelRepresentation = 1 ds.HighBit = 15 ds.BitsStored = 16 ds.BitsAllocated = 16 ds.RescaleIntercept = 0 ds.RescaleSlope = 1 ds.WindowCenter = 80 ds.WindowWidth = 600 ds.Columns = slice_data.shape[0] ds.Rows = slice_data.shape[1] ds.PixelData = slice_data.tobytes() [ds.add_new(k, 'LO', v) for k, v in meta.items()] ds.save_as(dcm_path)
def gen_file(self, fn=None): # print("Setting file meta information...") # Populate required values for file meta information file_meta = pydicom.Dataset() file_meta.FileMetaInformationGroupLength = 60 # Will be rewritten but must exist file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT file_meta.MediaStorageSOPInstanceUID = "1.2.3" file_meta.ImplementationClassUID = "1.2.3.4" # Wants all of these to be legit: # * (0002,0000) FileMetaInformationGroupLength, UL, 4 # * (0002,0001) FileMetaInformationVersion, OB, 2 # X * (0002,0002) MediaStorageSOPClassUID, UI, N # X * (0002,0003) MediaStorageSOPInstanceUID, UI, N # * (0002,0010) TransferSyntaxUID, UI, N # X * (0002,0012) ImplementationClassUID, UI, N ds = pydicom.FileDataset(fn, self.as_pydicom_ds(), file_meta=file_meta, preamble=b"\0" * 128) # print(ds) with NamedTemporaryFile() as f: ds.save_as(filename=f.name, write_like_original=True) self.file = f.read()
def save(self) -> None: """ Save input data into DICOM file. :return: None """ if not self.parseInput(): return else: filename = QtWidgets.QFileDialog.getSaveFileName(self, "Save DICOM", "../../..", "*.dcm")[0] if filename == '': return else: fm = pd.Dataset() # CT Image Storage fm.MediaStorageSOPClassUID = "1.2.840.10008.5.1.4.1.1.2" fm.MediaStorageSOPInstanceUID = pd.uid.generate_uid() fm.TransferSyntaxUID = pd.uid.ExplicitVRLittleEndian fm.ImplementationClassUID = pd.uid.generate_uid() ds = pd.FileDataset(None, {}) ds.file_meta = fm # CT Image Storage ds.SOPClassUID = "1.2.840.10008.5.1.4.1.1.2" ds.SOPInstanceUID = pd.uid.generate_uid() ds.ContentDate = f"{self.date_time.year:04}{self.date_time.month:02}{self.date_time.day:02}" ds.ContentTime = f"{self.date_time.hour:02}{self.date_time.minute:02}{self.date_time.second:02}" ds.StudyInstanceID = pd.uid.generate_uid() ds.SeriesInstanceID = pd.uid.generate_uid() ds.Modality = "CT" ds.ConversionType = 'WSD' # workstation ds.ImageType = r"ORIGINAL\PRIMARY\AXIAL" ds.PatientName = self.input_data["PatientName"] ds.PatientID = self.input_data["PatientID"] ds.PatientSex = self.input_data["PatientSex"] ds.ImageComments = self.input_data["ImageComments"] ds.PixelData = self.img.astype(np.uint8).tobytes() ds.Rows, ds.Columns = self.img.shape ds.SamplesPerPixel = 1 ds.PixelRepresentation = 0 ds.PhotometricInterpretation = "MONOCHROME2" ds.BitsAllocated, ds.BitsStored = 8, 8 ds.HighBit = 7 ds.is_little_endian = True ds.is_implicit_VR = False ds.save_as(f"{filename}.dcm", write_like_original=False) self.close()
def write_dicom(output_name, data, image_number=0): # Write the data to a dicom file. # Dicom files are difficult to set up correctly, this file will likely # crash when trying to open it using dcm2nii. However, if it is loaded in # python (e.g., dicom.dcmread) then pixel_array contains the relevant # voxel data # Convert data from float to in dataInts = data.astype(np.int16) # Populate required values for file meta information file_meta = dicom.Dataset() file_meta.MediaStorageSOPClassUID = '1.2' # '1.2.840.10008.5.1.4.1.1.2' file_meta.MediaStorageSOPInstanceUID = "1.2.3" file_meta.ImplementationClassUID = "1.2.3.4" file_meta.TransferSyntaxUID = '1.2.840.10008.1.2' # Create the FileDataset ds = dicom.FileDataset(output_name, {}, file_meta=file_meta, preamble=b"\0" * 128) # Set image dimensions frames, rows, cols = dataInts.shape ds.Rows = rows ds.Columns = cols ds.NumberOfFrames = frames ds.SamplesPerPixel = 1 ds.BitsAllocated = 16 ds.BitsStored = 16 ds.PixelRepresentation = 0 ds.InstanceNumber = image_number ds.ImagePositionPatient = [0, 0, 0] ds.ImageOrientationPatient = [.01, 0, 0, 0, 0, 0] # Add the data elements -- not trying to set all required here. Check DICOM # standard ds.PatientName = "sim" ds.PatientID = "sim" # Set the transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Set creation date/time image_datetime = script_datetime + datetime.timedelta(seconds=image_number) timeStr = image_datetime.strftime('%H%M%S') ds.ContentDate = image_datetime.strftime('%Y%m%d') ds.ContentTime = timeStr # Add the data ds.PixelData = dataInts.tobytes() ds.save_as(output_name)
def validate_transfer_syntax_uid(data_set): meta = pydicom.Dataset() meta.ImplementationClassUID = pydicom.uid.generate_uid() meta.TransferSyntaxUID = ImplicitVRLittleEndian new_data_set = pydicom.FileDataset(filename_or_obj=None, dataset=data_set, is_little_endian=True, file_meta=meta) new_data_set.is_little_endian = True new_data_set.is_implicit_VR = True return new_data_set
def build(kwargs: dict = None) -> pydicom.FileDataset: if kwargs is None: kwargs = {} validate_kwargs(kwargs) file_meta = pydicom.Dataset() file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' file_meta.MediaStorageSOPInstanceUID = '1.2.3' file_meta.ImplementationClassUID = '1.2.3.4' ds = pydicom.FileDataset('', {}, file_meta=file_meta, preamble=b"\0" * 128) # Add the data elements ds.PatientName = 'Fake Name' ds.PatientID = '123456' # Set transfer syntax ds.is_little_endian = True ds.is_implicit_VR = True # Set creation time dt = datetime.datetime.now() ds.ContentDate = dt.strftime('%Y%m%d') ds.ContentTime = dt.strftime('%H%M%S.%f') # Set transfer syntax ds.file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRBigEndian ds.BitsAllocated = 8 ds.Rows = kwargs.get('Rows', 64) ds.Columns = kwargs.get('Columns', 64) ds.PixelRepresentation = 0 ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = 'MONOCHROME1' ds.SeriesDescription = kwargs.get('SeriesDescription', '') ds.ViewPosition = '' np.random.seed(kwargs.get('seed', np.random.randint(0, 2**32 - 1))) ds.PixelData = np.random.randint(0, 256, size=(ds.Rows, ds.Columns), dtype=np.uint8).reshape(-1) return ds
def save_dicom(orgpath, npy, config, idx): """ Original code can be found in : https://github.com/jryoungw/DicomWrite/blob/master/write_dicom.py Supported by Mingyu Kim """ dcm = pydicom.read_file(orgpath) savepath = config.result_path + '/' + '/'.join(orgpath.split('/')[:-1]) os.makedirs(config.result_path + '/' + '/'.join(orgpath.split('/')[:-1]), exist_ok=True) ds = pydicom.FileDataset(config.result_path + '/' + '/'.join(orgpath.split('/')[:-1])+\ '/'+'{:05d}.dcm'.format(idx), {}, file_meta=pydicom.Dataset(), \ preamble=("\0"*128).encode()) ds.ImageType = dcm.ImageType ds.SOPClassUID = dcm.SOPClassUID ds.SOPInstanceUID = dcm.SOPInstanceUID methods = [i for i in dcm.__dir__() if '_' not in i] for m in methods: try: exec('ds.' + m + '=dcm.' + m) except: print('[*] ' + m + ' is not implemented') try: ds.RescaleIntercept = '-1024' except: pass try: ds.RescaleSlope = '1' except: pass try: ds.WindowCenterWidthExplanation = dcm.WindowCenterWidthExplanation except: pass ds.is_little_endian = True ##################### If pixels are not written correctly, change this value to True ##################### ds.is_implicit_VR = False ########################################################################################################## savepath = os.path.join(savepath, '{:05d}.dcm'.format(idx + 1)) ds.PixelData = npy.tostring() ds.save_as(savepath)
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))