def _convert_value(self, val): """Convert `val` to an appropriate type and return the result. Uses the element's VR in order to determine the conversion method and resulting type. """ if self.VR == 'SQ': # a sequence - leave it alone from pydicom.sequence import Sequence if isinstance(val, Sequence): return val else: return Sequence(val) # if the value is a list, convert each element try: val.append except AttributeError: # not a list return self._convert(val) else: return MultiValue(lambda x: self._convert(x), val)
def MultiString(val, valtype=str): """Split a bytestring by delimiters if there are any val -- DICOM bytestring to split up valtype -- default str, but can be e.g. UID to overwrite to a specific type """ # Remove trailing blank used to pad to even length # 2005.05.25: also check for trailing 0, error made # in PET files we are converting if val and (val.endswith(' ') or val.endswith('\x00')): val = val[:-1] splitup = val.split("\\") if len(splitup) == 1: val = splitup[0] return valtype(val) if val else val else: return MultiValue(valtype, splitup)
def convert_PN(byte_string, encodings=None): """Read and return string(s) as PersonName instance(s)""" def get_valtype(x): if not in_py2: return PersonName(x, encodings).decode() return PersonName(x, encodings) # XXX - We have to replicate MultiString functionality # here because we can't decode easily here since that # is performed in PersonNameUnicode if byte_string.endswith((b' ', b'\x00')): byte_string = byte_string[:-1] splitup = byte_string.split(b"\\") if len(splitup) == 1: return get_valtype(splitup[0]) else: return MultiValue(get_valtype, splitup)
def copy_contours(self): ''' copy contours to rs :param self: :return: ''' ROIContourSequence = Sequence() #ContourSequence = Sequence() StructureSetROISequence = Sequence() for i in range( len( self.contours)): StructureSetROI = Dataset() StructureSetROI.ROINumber = self.contours[i]['number'] StructureSetROI.ROIName = self.contours[i]['name'] StructureSetROI.ROIGenerationAlgorithm = 'MANUAL' StructureSetROI.ReferencedFrameOfReferenceUID = '' StructureSetROISequence.append(StructureSetROI) ContourSequence = Sequence() for k in range( len( self.contours[i]['contour'])): contour_slice = Dataset() contour_slice.ContourData = MultiValue( dicom.valuerep.DSfloat, self.contours[i]['contour'][k]) contour_slice.ContourGeometricType = 'CLOSED_PLANAR' contour_slice.NumberOfContourPoints = len( contour_slice.ContourData) /3 ########wrong contour_slice.ContourImageSequence = Sequence() ContourSequence.append( contour_slice) ROIContour = Dataset() ROIContour.ContourSequence = ContourSequence ROIContour.ROIDisplayColor = self.rs_generic.structure.ROIContourSequence[i].ROIDisplayColor ROIContour.ReferencedROINumber = self.rs_generic.structure.ROIContourSequence[i].ReferencedROINumber ROIContourSequence.append( ROIContour) self.rs.structure.ROIContourSequence = ROIContourSequence self.rs.structure.StructureSetROISequence = StructureSetROISequence return 0
def convert_ATvalue( byte_string: bytes, is_little_endian: bool, struct_format: Optional[str] = None ) -> Union[BaseTag, MutableSequence[BaseTag]]: """Return a decoded 'AT' value. Parameters ---------- byte_string : bytes The encoded 'AT' element value. is_little_endian : bool ``True`` if the value is encoded as little endian, ``False`` otherwise. struct_format : str, optional Not used. Returns ------- BaseTag or MultiValue of BaseTag The decoded value(s). """ length = len(byte_string) if length == 4: return convert_tag(byte_string, is_little_endian) # length > 4 if length % 4 != 0: logger.warn( "Expected length to be multiple of 4 for VR 'AT', " f"got length {length}" ) return MultiValue( Tag, [ convert_tag(byte_string, is_little_endian, offset=x) for x in range(0, length, 4) ] )
def _convert_value(self, val: Any) -> Any: """Convert `val` to an appropriate type and return the result. Uses the element's VR in order to determine the conversion method and resulting type. """ if self.VR == VR_.SQ: # a sequence - leave it alone from pydicom.sequence import Sequence if isinstance(val, Sequence): return val return Sequence(val) # if the value is a list, convert each element try: val.append except AttributeError: # not a list return self._convert(val) if len(val) == 1: return self._convert(val[0]) return MultiValue(self._convert, val, validation_mode=self.validation_mode)
def convert_text(byte_string, encodings=None): """Return a decoded text VR value, ignoring backslashes. Text VRs are 'SH', 'LO' and 'UC'. Parameters ---------- byte_string : bytes or str The encoded text VR element value. encodings : list of str, optional A list of the character encoding schemes used to encode the value. Returns ------- str or list of str The decoded value(s). """ values = byte_string.split(b'\\') values = [convert_single_string(value, encodings) for value in values] if len(values) == 1: return values[0] else: return MultiValue(str, values)
def testSetIndex(self): """MultiValue: Setting list item converts it to required type""" multival = MultiValue(IS, [1, 5, 10]) multival[1] = '7' assert isinstance(multival[1], IS) assert 7 == multival[1]
def testAppend(self): """MultiValue: Append of item converts it to required type...""" multival = MultiValue(IS, [1, 5, 10]) multival.append('5') assert isinstance(multival[-1], IS) assert 5 == multival[-1]
def testLimits(self, enforce_valid_values): """MultiValue: Raise error if any item outside DICOM limits....""" with pytest.raises(OverflowError): MultiValue(IS, [1, -2**31 - 1])
def testMultiDS(self): """MultiValue: Multi-valued data elements can be created........""" multival = MultiValue(DS, ['11.1', '22.2', '33.3']) for val in multival: assert isinstance(val, (DSfloat, DSdecimal))
def testLimits(self): """MultiValue: Raise error if any item outside DICOM limits....""" with pytest.raises(OverflowError): MultiValue(IS, [1, -2**31 - 1], validation_mode=config.RAISE)
def dump_dicom(data, folder, spacing=(1, 1, 1), origin=(0, 0, 0), intercept=0, slope=1): """ Dump 3D scan in dicom format. Parameters ---------- data : ndarray 3D numpy array containing ct scan's data. folder : str folder where dicom files will be dumped. spacing : ArrayLike ndarray of shape (3,) that contains spacing along z, y, x axes. origin : ArrayLike ndarray of shape (3,) that contains origin for z, y, x axes. interception : float interception value. Default is 0. slope : float slope value. Default is 1. """ spacing = np.array(spacing).reshape(-1) origin = np.array(origin).reshape(-1) if not os.path.exists(folder): os.makedirs(folder) num_slices = data.shape[0] scan_id = np.random.randint(2 ** 16) for i in range(num_slices): slice_name = ( hex(scan_id + i) .replace('x', '') .upper() .zfill(8) ) filename = os.path.join(folder, slice_name) pixel_array = (data[i, ...] - intercept) / slope locZ, locY, locX = (float(origin[0] + spacing[0] * i), float(origin[1]), float(origin[2])) file_meta = Dataset() file_meta.MediaStorageSOPClassUID = 'Secondary Capture Image Storage' file_meta.MediaStorageSOPInstanceUID = ( hex(scan_id) .replace('x', '') .upper() .zfill(8) ) file_meta.ImplementationClassUID = slice_name dataset = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0"*128) dataset.PixelData = pixel_array.astype(np.uint16).tostring() dataset.RescaleSlope = slope dataset.RescaleIntercept = intercept dataset.ImagePositionPatient = MultiValue(type_constructor=float, iterable=[locZ, locY, locX]) dataset.PixelSpacing = MultiValue(type_constructor=float, iterable=[float(spacing[1]), float(spacing[2])]) dataset.SliceThickness = float(spacing[0]) dataset.Modality = 'WSD' dataset.Columns = pixel_array.shape[0] dataset.Rows = pixel_array.shape[1] dataset.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian dataset.PixelRepresentation = 1 dataset.BitsAllocated = 16 dataset.BitsStored = 16 dataset.SamplesPerPixel = 1 write_file(filename, dataset)
def testSetIndex(self): """MultiValue: Setting list item converts it to required type""" multival = MultiValue(IS, [1, 5, 10]) multival[1] = '7' self.assertTrue(isinstance(multival[1], IS)) self.assertEqual(multival[1], 7, "Item set by index is not correct value")
def testAppend(self): """MultiValue: Append of item converts it to required type...""" multival = MultiValue(IS, [1, 5, 10]) multival.append('5') self.assertTrue(isinstance(multival[-1], IS)) self.assertEqual(multival[-1], 5, "Item set by append is not correct value")
def copy_from_rs(self): ''' Just change the contour in the structure, not create a new structure :return: ''' K = 20 # for i in range(1, 5): # self.rs.structure.ROIContourSequence[i].ContourSequence = self.rs.structure.ROIContourSequence[ # i].ContourSequence[K:K] #points = [[8, 9, -238],[8,9, -241],[8,9,-244]] #points = [[8, 9, -238], [8, 9, -241], [8, 9, -244]] # points = [[8.057, -194.784, -238], [8.545, -194.784, -238], [14.648, -195.272, -238]] #points = [[8, -194, -238], [8, -194, -241], [14, -195, -244]] #import numpy as np #points = 10 * np.random.random([K, 3] ) # for i in [0,1,2,3,4]: # for k in range( K): # contour = MultiValue( dicom.valuerep.DSfloat, np.array(self.contours[i]['contour'][k][:30])*1) # #contour = MultiValue( dicom.valuerep.DSfloat, points[k]) # print contour # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourData = contour # self.rs.structure.ROIContourSequence[i].ContourSequence = self.rs.structure.ROIContourSequence[i].ContourSequence[:K] # for i in [0,1,2,3,4]: # for k in range( K): # contour = MultiValue( dicom.valuerep.DSfloat, np.array(self.contours[i]['contour'][k])*10) # # contour = MultiValue( dicom.valuerep.DSfloat, points[k]) # print contour # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourData = contour # # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourData = self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourData # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourImageSequence = self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourImageSequence # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourGeometricType = self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourGeometricType # self.rs.structure.ROIContourSequence[i].ContourSequence[k].NumberOfContourPoints = self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].NumberOfContourPoints # self.rs.structure.ROIContourSequence[i].ContourSequence = self.rs.structure.ROIContourSequence[i].ContourSequence[:K] # self.rs.structure.ROIContourSequence[i] = self.rs_test.structure.ROIContourSequence[i] # for i in range(len( self.contours)): # for k in range(K): #range( len( self.contours[i]['contour'])): # contour = MultiValue( dicom.valuerep.DSfloat, np.array(self.contours[i]['contour'][k])) # # contour = MultiValue( dicom.valuerep.DSfloat, points[k]) # #print contour # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourData = contour # # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourData = self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourData # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourImageSequence = Sequence() #self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourImageSequence # self.rs.structure.ROIContourSequence[i].ContourSequence[k].ContourGeometricType = 'CLOSED_PLANAR'#self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourGeometricType # self.rs.structure.ROIContourSequence[i].ContourSequence[k].NumberOfContourPoints = len(contour)/3 #self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].NumberOfContourPoints # self.rs.structure.ROIContourSequence[i].ContourSequence = self.rs.structure.ROIContourSequence[i].ContourSequence[:K] for i in range(len(self.contours)): self.rs.structure.ROIContourSequence[i].ContourSequence = Sequence( ) for k in range(len(self.contours[i]['contour'])): contour = MultiValue(dicom.valuerep.DSfloat, np.array(self.contours[i]['contour'][k])) # contour = MultiValue( dicom.valuerep.DSfloat, points[k]) #print contour ContourSequence = Dataset() ContourSequence.ContourData = contour ContourSequence.ContourImageSequence = Sequence( ) #self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourImageSequence ContourSequence.ContourGeometricType = 'CLOSED_PLANAR' #self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].ContourGeometricType ContourSequence.NumberOfContourPoints = len( contour ) / 3 #self.rs_test.structure.ROIContourSequence[i].ContourSequence[k].NumberOfContourPoints self.rs.structure.ROIContourSequence[i].ContourSequence.append( ContourSequence)
def testMultiDS(self): """MultiValue: Multi-valued data elements can be created........""" multival = MultiValue(DS, ['11.1', '22.2', '33.3']) for val in multival: self.assertTrue(isinstance(val, (DSfloat, DSdecimal)), "Multi-value DS item not converted to DS")
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)