def get_dvh(structure, dose, roi, limit=None, calculate_full_volume=True, thickness=None, callback=None): """Calculate a cumulative DVH in Gy from a DICOM RT Structure Set & Dose. Parameters ---------- structure : pydicom Dataset DICOM RT Structure Set used to determine the structure data. dose : pydicom Dataset DICOM RT Dose used to determine the dose grid. roi : int The ROI number used to uniquely identify the structure in the structure set. limit : int, optional Dose limit in cGy as a maximum bin for the histogram. calculate_full_volume : bool, optional Calculate the full structure volume including contours outside of the dose grid. thickness : float, optional Structure thickness used to calculate volume of a voxel. callback : function, optional A function that will be called at every iteration of the calculation. """ from dicompylercore import dicomparser rtss = dicomparser.DicomParser(structure) rtdose = dicomparser.DicomParser(dose) structures = rtss.GetStructures() s = structures[roi] s['planes'] = rtss.GetStructureCoordinates(roi) s['thickness'] = thickness if thickness else rtss.CalculatePlaneThickness( s['planes']) calcdvh = calculate_dvh(s, rtdose, limit, calculate_full_volume, callback) return dvh.DVH(counts=calcdvh.histogram, bins=(np.arange(0, 2) if (calcdvh.histogram.size == 1) else np.arange(0, calcdvh.histogram.size + 1) / 100), dvh_type='differential', dose_units='Gy', notes=calcdvh.notes, name=s['name']).cumulative
def get_pixels_hu(im): ds = dicomparser.DicomParser(im) #intercept, slope = ds.GetRescaleInterceptSlope intercept = im.RescaleIntercept slope = im.RescaleSlope rescale = im.pixel_array * slope + intercept window, level = ds.GetDefaultImageWindowLevel() pixel = ds.GetLUTValue(rescale, window, level) return pixel
def pluginMenu(self, evt): """Import DICOM data quickly.""" dlg = wx.FileDialog( self.parent, defaultDir=self.path, wildcard="All Files (*.*)|*.*|DICOM File (*.dcm)|*.dcm", message="Choose a DICOM File", ) patient = {} if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetPath() # Try to parse the file if is a DICOM file try: logger.debug("Reading: %s", filename) dp = dicomparser.DicomParser(filename) # Otherwise show an error dialog except (AttributeError, EOFError, IOError, KeyError): logger.info("%s is not a valid DICOM file.", filename) dlg = wx.MessageDialog( self.parent, filename + " is not a valid DICOM file.", "Invalid DICOM File", wx.OK | wx.ICON_ERROR, ) dlg.ShowModal() # If this is really a DICOM file, place it in the appropriate bin else: if ("ImageOrientationPatient" in dp.ds) and not (dp.ds.Modality in ["RTDOSE"]): patient["images"] = [] patient["images"].append(dp.ds) elif dp.ds.Modality in ["RTSTRUCT"]: patient["rtss"] = dp.ds elif dp.ds.Modality in ["RTPLAN"]: patient["rtplan"] = dp.ds elif dp.ds.Modality in ["RTDOSE"]: patient["rtdose"] = dp.ds else: patient[dp.ds.Modality] = dp.ds # Since we have decided to use this location to import from, # update the location in the preferences for the next session # if the 'import_location_setting' is "Remember Last Used" if self.import_location_setting == "Remember Last Used": pub.sendMessage( "preferences.updated.value", msg={ "general.dicom.import_location": dlg.GetDirectory() }, ) pub.sendMessage("preferences.requested.values", msg="general.dicom") pub.sendMessage("patient.updated.raw_data", msg=patient) dlg.Destroy() return
def show(self, z=None): """Show the dose grid using Matplotlib if present. Parameters ---------- z : float, optional slice position to display initially, by default None """ if not mpl_available: raise ImportError( "Matplotlib could not be loaded. Install and try again.") return self import matplotlib.pyplot as plt from matplotlib.widgets import Slider # Extract the list of planes (z) from the dose grid planes = (np.array(self.ds.GridFrameOffsetVector) * self.ds.ImageOrientationPatient[0] * self.ds.ImageOrientationPatient[4] ) + self.ds.ImagePositionPatient[2] # Set up the plot fig = plt.figure() ax = fig.add_subplot(111) rtdose = dicomparser.DicomParser(self.ds) # Get the middle slice if the z is not provided z = planes[planes.size // 2] if z is None else z zplane = rtdose.GetDoseGrid(z) * self.ds.DoseGridScaling # Flag to invert slider min/max if GFOV is decreasing (i.e. FFS) reverse = planes[0] > planes[-1] im = ax.imshow( zplane, cmap="jet", ) # Create a slider to change the (z) axslice = fig.add_axes([0.34, 0.01, 0.50, 0.02]) slider = Slider( ax=axslice, label="Slice Position (mm):", valmin=planes[-1] if reverse else planes[0], valmax=planes[0] if reverse else planes[-1], valinit=z, valstep=np.diff(planes)[0], ) def updateslice(z): """Update the data to show on the plot.""" im.set_data(rtdose.GetDoseGrid(z) * self.ds.DoseGridScaling) plt.draw() slider.on_changed(updateslice) plt.show() return self
def get_roi_names(self, mrn): structure_file = self.structure[self.mrn.index(mrn)] rt_st = dicomparser.DicomParser(structure_file) rt_structures = rt_st.GetStructures() roi = {} for key in list(rt_structures): if rt_structures[key]['type'].upper() not in {'MARKER', 'REGISTRATION', 'ISOCENTER'}: roi[key] = rt_structures[key]['name'] return roi
def calDVHmetrics(rtssfile, rtdosefile, outputfile=None): RTss = dicomparser.DicomParser(rtssfile) #RTdose = dicomparser.DicomParser("testdata/rtdose.dcm") RTstructures = RTss.GetStructures() patient = rtssfile[rtssfile.rfind('RS.') + 3:rtssfile.rfind('.dcm')] calcdvhs = {} OARs = ['Liver', 'Spleen', 'Kidney R', 'Kidney L', 'Spinal cord T10 S1'] strDVH = "" for key, structure in RTstructures.iteritems(): OARname = structure['name'].replace("_", " ") if OARname in OARs: calcdvhs[key] = dvhcalc.get_dvh(rtssfile, rtdosefile, key) #print(structure['name']) diff_dvh = calcdvhs[key].differential mean_dose = np.sum( diff_dvh.counts * diff_dvh.bins[1:] ) / diff_dvh.volume #integra of the differential dvh, and divide the volume if len(calcdvhs[key].counts) > 1000: V10 = calcdvhs[key].counts[1000] / calcdvhs[key].volume * 100 V5 = calcdvhs[key].counts[500] / calcdvhs[key].volume * 100 elif len(calcdvhs[key].counts) > 500: V10 = 0 V5 = calcdvhs[key].counts[500] / calcdvhs[key].volume * 100 else: V10 = 0 V5 = 0 if calcdvhs[key].volume > 0: v2cc = 2 / calcdvhs[ key].volume * 100 # relative volume of 2cc to the OAR binsabove2cc = calcdvhs[key].bins[np.where( calcdvhs[key].counts > v2cc)] D2cc = calcdvhs[key].bins[np.where( calcdvhs[key].counts > v2cc)][-1] else: D2cc = 0 strDVH = strDVH + patient + ', ' + structure['name'] + ', ' + str( np.round(calcdvhs[key].volume, 2)) + ', ' + str( np.round(mean_dose, 2)) + ', ' + str(np.round( V5, 2)) + ', ' + str(np.round(V10, 2)) + ', ' + str( np.round(D2cc, 2)) + '\n' if outputfile is None: print "patient, OAR, volumn(cc), Mean dose(Gy), V5(%), V10(%), D2cc(Gy)\n", strDVH return strDVH else: fl = open(outputfile, "a") if os.stat(outputfile).st_size == 0: fl.write( "patient, OAR, volumn(cc), Mean dose(Gy), V5(%), V10(%), D2cc(Gy)\n" ) fl.write(strDVH) fl.close() return None
def get_dicom_dose(file): """Will return the prescribed dose (in Gy) from an rtplan file. This is needed for DVH analysis. An error will be shown to the user if a non RTPlan file is supplied""" if dicom_type(file) != 'rtplan': print('Prescription cannot be obtained from non RTPlan DICOM file. Returned None.') dicom_dose = None else: if is_oncentra(file) == True: ## oncentra seems to not store the prescribed dose in the same way, so deal with seperately print('Oncentra File - Attempting to determine prescription...') dicom_dose = get_oncentra_dose(file) ## get oncentra dose... else: dicom_dose = dicomparser.DicomParser(file).GetPlan()['rxdose']/100 ## convert to Gy for use return dicom_dose
def get_image(slice): """ Retorna um array de pixels com valores LUT :param slice: dataset dicom :return pixels_slice: array numpy com os pixels transformados """ ds = dicomparser.DicomParser(slice) intercept, slope = ds.GetRescaleInterceptSlope() rescaled_image = slice.pixel_array * slope + intercept window, level = ds.GetDefaultImageWindowLevel() pixels_slice = ds.GetLUTValue(rescaled_image, window, level) return pixels_slice
def update_plan_structures(self): self.structures = dicomparser.DicomParser( self.current_struct_file).GetStructures() self.roi_keys = [ key for key in self.structures if self.structures[key]['type'].upper() != 'MARKER' ] self.roi_names = [ str(self.structures[key]['name']) for key in self.roi_keys ] self.roi_key_map = { name: self.roi_keys[i] for i, name in enumerate(self.roi_names) } self.select_roi.options = [''] + self.roi_names self.update_roi_select()
def buildPlan(self, planRawDataDict): planRawData = planRawDataDict.patientBaseDict if 'planListRawData' in planRawData: for plan in planRawData.planListRawData: logging.info('planName:%s', plan.PlanName) refImageID = plan.PrimaryCTImageSetID refImageHder = None refImageInfo = None refImageData = None for image in planRawData.imageSetListRawData: if image.ImageSetID == refImageID: refImageHder = image.CTHeader refImageInfo = image.CTInfo refImageData = image.CTData break setupPosition = None roiShiftVector = None if refImageHder: setupPosition = refImageHder.patient_position roiShiftVector = self.getStructShift(refImageHder) # if 'planPointsRawData' in plan.planData: # pointsDict = self.getPoints( # plan.planData.planPointsRawData, setupPosition, roiShiftVector) if 'planROIsRawData' in plan.planData: # contourDict = self.getContours( # plan.planData.planROIsRawData, setupPosition, roiShiftVector) # createStructDS(patientInfoDict, ImageInfoUIDs, planROIsRawData,setupPosition, roiShiftVector) RS_ds = self.createStructDS(plan.planData, refImageInfo, setupPosition, roiShiftVector) rsObject = dicomparser.DicomParser(RS_ds) structs = rsObject.GetStructures() for (key, Roi) in structs.items(): # print('============================') planes = rsObject.GetStructureCoordinates(key) thickness = rsObject.CalculatePlaneThickness(planes) volume = rsObject.CalculateStructureVolume( planes, thickness) logging.info('key=%d,name=%s,volume=%s', key, Roi['name'], str(volume)) print('end')
def getImages(datasets): """ Retorna um array de pixels com valores LUT para cada dataset :param datasets: lista de datasets dicom :return pixels_slice: lista de arrays numpy com os pixels transformados para cada dataset """ pixels_slice = list() for slice in datasets: ds = dicomparser.DicomParser(slice) intercept, slope = ds.GetRescaleInterceptSlope() rescaled_image = slice.pixel_array * slope + intercept window, level = ds.GetDefaultImageWindowLevel() pixels_slice.append(ds.GetLUTValue(rescaled_image, window, level)) return pixels_slice
def load_rtstruct(path): """ Carrega o documento de marcação RT STRUCT do volume no referente path :param path: str indicando o diretório do conjunto de slices DICOM, incluindo o RT STRUCT :return rt_struct: uma instância do dicompyler com as marcações """ rt_struct = None for root, dirs, files in os.walk(path): for file in files: ds = pydicom.dcmread(root + '/'+ file) if ds.Modality != 'CT': rt_struct = dicomparser.DicomParser(ds) return rt_struct
def rebuild_tree_ctrl_rois(self, plan_uid): """ Delete all nodes of current tree_ctrl_rois and build for the specified plan :param plan_uid: pydicom ds.SOPInstanceUID for the RT Plan of interest """ self.tree_ctrl_rois.DeleteChildren(self.root_rois) if self.dicom_file_paths[plan_uid]['rtstruct'][0]: self.tree_ctrl_rois.SetItemBackgroundColour(self.root_rois, None) dicom_rt_struct = dicomparser.DicomParser(self.dicom_file_paths[plan_uid]['rtstruct'][0]) structures = dicom_rt_struct.GetStructures() self.roi_name_map = {structures[key]['name']: {'key': key, 'type': structures[key]['type']} for key in list(structures) if structures[key]['type'] != 'MARKER'} self.roi_nodes = {} rois = list(self.roi_name_map) rois.sort() for roi in rois: self.roi_nodes[roi] = self.tree_ctrl_rois.AppendItem(self.root_rois, roi, ct_type=0) else: self.tree_ctrl_rois.SetItemBackgroundColour(self.root_rois, wx.Colour(255, 0, 0))
def get_mark(dataset, position, spacing, roi): """ Retorna a marcação de uma região de interesse (ROI) dado um dataset DICOM na modalidade RTSTRUCT :param dataset: Dataset da modalidade RTSTRUCT :param position: tuple() com coordenadas x,y,z do canto superior esquerdo da imagem :param spacing: tuple() com a distância física no paciente entre o centro de cada pixel, especificado por um par numérico - espaçamento de linhas adjacentes (delimitador) espaçamento de colunas adjacentes em mm. :param roi: str() representando o nome da região de interesse que se deseja obter a marcação :return coordinates: list() com indices das marcação na imagem. """ marking = dicomparser.DicomParser(dataset) structures = marking.GetStructures() roi_numbers = list() for i in structures: if roi in structures[i]['name']: roi_numbers.append(structures[i]['id']) if roi_numbers == None: raise NameError(roi + " não está entre as estruturas marcadas") coordinates = list() for roi_number in roi_numbers: try: for mark in marking.GetStructureCoordinates(roi_number)[ str(round(position[2], 2)) + '0']: contours = np.array(mark['data']) lista = list() for c in contours: lista.append(((c[0] - position[0]) / spacing[0], (c[1] - position[1]) / spacing[1])) #rows = ((contour[:, 1] - position[1])/spacing[1]).astype(int) #columns = ((contour[:, 0] - position[0])/spacing[0]).astype(int) coordinates.append(lista) except: continue return coordinates
def getStandardTemplate(standard_path): ###########Suponha que o volume tenha sido selecionado aleatoriamente #Gerando o template padrão #- Seleciona aleatoriamente um volume dentre todos os outros do banco de dados. volume = choice_volume(standard_path) quant_slices = len(volume) slice = choice(volume[1]) #- Encontrar a marcação do especialista, encontrar o centro da massa dessa marcação marking = dicomparser.DicomParser(volume[0]) structures = marking.GetStructures() number_roi = None for i in structures: if structures[i]['name'] == 'SpinalCord': number_roi = structures[i]['id'] while True: #Sorteia um novo slice caso o slice sorteado não contenha a medula try: contour = np.array(marking.GetStructureCoordinates(number_roi)['{0:.2f}'.format(slice.ImagePositionPatient[2])][0]['data']) break except KeyError: slice = choice(volume[1]) rows = ((contour[:, 1] - slice.ImagePositionPatient[1])/slice.PixelSpacing[1]).astype(int) columns = ((contour[:, 0] - slice.ImagePositionPatient[0])/slice.PixelSpacing[0]).astype(int) #Conseguindo LUT Values pixels_slice = getImage(slice) diameter_x = rows.max() - rows.min() diameter_y = columns.max() - columns.min() center_x = int(diameter_x//2 + rows.min()) center_y = int(diameter_y//2 + columns.min()) #- Recortar duas vezes o tamanho da região correspondente de todos os lados. #print("[{0}:{1}, {2}:{3}]".format(rows.min() - (2*diameter_y), rows.max() + (2*diameter_y), columns.min() - (2*diameter_x), columns.max() + (2*diameter_x))) standard_template = pixels_slice[rows.min() - (2*diameter_y): rows.max() + (2*diameter_y), columns.min() - (2*diameter_x):columns.max() + (2*diameter_x)] cv2.imwrite("standard_template.png", standard_template) return standard_template
def dicom_type(file): """ will return 'rtss', 'rtdose', 'rtplan','ct' dependant on type of dicom file""" the_type = dicomparser.DicomParser(file).GetSOPClassUID() return the_type
def setUp(self): """Setup files for Image modality testing.""" ct_0_dcm = os.path.join(example_data, "ct.0.dcm") self.dp = dicomparser.DicomParser(ct_0_dcm)
def test_dataset_import(self): """Test if a pydicom dataset file can be parsed.""" dp1 = dicomparser.DicomParser(self.dp.ds) self.assertEqual(self.dp.ds, dp1.ds)
def setUp(self): """Setup the files for RT Dose modality testing.""" rtdose_dcm = os.path.join(example_data, "rtdose.dcm") self.dp = dicomparser.DicomParser(rtdose_dcm)
def setUp(self): """Setup the files for RT Structure Set modality testing.""" rtss_dcm = os.path.join(example_data, "rtss.dcm") self.dp = dicomparser.DicomParser(rtss_dcm)
def DirectorySearchThread(path): """Search the directory.""" patients = {} patients['path'] = path patients['series'] = {} # Check if the path is valid if os.path.isdir(path): files = [] # 遍历整个目录 for root, dirs, filenames in os.walk(path): files += map(lambda f: os.path.join(root, f), filenames) # 获得所有的文件 patients['filearray'] = filenames # print("files:{}".format(files)) for n in range(len(files)): if (os.path.isfile(files[n])): try: logger.debug("Reading: %s", files[n]) dp = dicomparser.DicomParser(files[n]) except (AttributeError, EOFError, IOError, KeyError): pass logger.info("%s is not a valid DICOM file.", files[n]) else: patient = dp.GetDemographics() patients['demographics'] = patient # Create each Series of images if (('ImageOrientationPatient' in dp.ds) and \ not (dp.GetSOPClassUID() == 'rtdose')): seinfo = dp.GetSeriesInfo() seinfo['numimages'] = 0 seinfo['modality'] = dp.ds.SOPClassUID.name if not seinfo['id'] in patients['series']: patients['series'][seinfo['id']] = seinfo if not 'images' in patients: patients['images'] = {} image = {} image['id'] = dp.GetSOPInstanceUID() image['filename'] = files[n] image['series'] = seinfo['id'] image['referenceframe'] = dp.GetFrameOfReferenceUID() patients['series'][seinfo['id']]['numimages'] = \ patients['series'][seinfo['id']]['numimages'] + 1 patients['images'][image['id']] = image # Create each RT Structure Set elif dp.ds.Modality in ['RTSTRUCT']: if not 'structures' in patients: patients['structures'] = {} structure = dp.GetStructureInfo() structure['id'] = dp.GetSOPInstanceUID() structure['filename'] = files[n] structure['series'] = dp.GetReferencedSeries() structure[ 'referenceframe'] = dp.GetFrameOfReferenceUID() patients['structures'][structure['id']] = structure # Otherwise it is a currently unsupported file else: logger.info("%s is a %s file and is not " + \ "currently supported.", files[n], dp.ds.SOPClassUID.name) return patients # resultFunc(patients) # if the path is not valid, display an error message else: print('No dcm!')
def GetPatientData(patients): """Get the data of the selected patient from the DICOM importer dialog.""" filearray = patients['filearray'] # print(filearray) path = patients['path'] # print(path) for n in range(0, len(filearray)): dcmfile = str(os.path.join(path, filearray[n])) dp = dicomparser.DicomParser(dcmfile) # 解析dicom文件 if (n == 0): patient = {} if (('ImageOrientationPatient' in dp.ds) and \ not (dp.GetSOPClassUID() == 'rtdose')): if not 'images' in patient: patient['images'] = [] try: dp.ds.file_meta.TransferSyntaxUID except: dp.ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian patient['images'].append(dp.ds) # print(dp.ds.pixel_array.shape) # patient['images'][n-1].pixel_array = np.array([bytearray(patient['images'][n-1].PixelData)])\ #.view(dtype = np.uint16).reshape(patient['images'][n-1].Rows, patient['images'][n-1].Columns) elif (dp.ds.Modality in ['RTSTRUCT']): patient['rtss'] = dp.ds # Sort the images based on a sort descriptor: # (ImagePositionPatient, InstanceNumber or AcquisitionNumber) if 'images' in patient: sortedimages = [] unsortednums = [] sortednums = [] images = patient['images'] sort = 'IPP' # Determine if all images in the series are parallel # by testing for differences in ImageOrientationPatient parallel = True for i, item in enumerate(images): if (i > 0): iop0 = np.array(item.ImageOrientationPatient) iop1 = np.array(images[i - 1].ImageOrientationPatient) if (np.any(np.array(np.round(iop0 - iop1), dtype=np.int32))): parallel = False break # Also test ImagePositionPatient, as some series # use the same patient position for every slice ipp0 = np.array(item.ImagePositionPatient) ipp1 = np.array(images[i - 1].ImagePositionPatient) if not (np.any(np.array(np.round(ipp0 - ipp1), dtype=np.int32))): parallel = False break # If the images are parallel, sort by ImagePositionPatient if parallel: sort = 'IPP' else: # Otherwise sort by Instance Number if not (images[0].InstanceNumber == \ images[1].InstanceNumber): sort = 'InstanceNumber' # Otherwise sort by Acquisition Number elif not (images[0].AcquisitionNumber == \ images[1].AcquisitionNumber): sort = 'AcquisitionNumber' # Add the sort descriptor to a list to be sorted for i, image in enumerate(images): if (sort == 'IPP'): unsortednums.append(image.ImagePositionPatient[2]) else: unsortednums.append(image.data_element(sort).value) # Sort image numbers in descending order for head first patients if ('hf' in image.PatientPosition.lower()) and (sort == 'IPP'): sortednums = sorted(unsortednums, reverse=True) # Otherwise sort image numbers in ascending order else: sortednums = sorted(unsortednums) # Add the images to the array based on the sorted order for s, slice in enumerate(sortednums): for i, image in enumerate(images): if (sort == 'IPP'): if (slice == image.ImagePositionPatient[2]): sortedimages.append(image) elif (slice == image.data_element(sort).value): sortedimages.append(image) # Save the images back to the patient dictionary patient['images'] = sortedimages return patient
def get_patient_data(self, rxdose): """Get the data of the selected patient from the DICOM importer dialog.""" for n in range(0, len(self.filearray)): dcmfile = str(os.path.join(self.path, self.filearray[n])) dp = dicomparser.DicomParser(dcmfile) if (n == 0): self.patient = {} self.patient['rxdose'] = rxdose if (('ImageOrientationPatient' in dp.ds) and not (dp.GetSOPClassUID() == 'rtdose')): if not 'images' in self.patient: self.patient['images'] = [] self.patient['images'].append(dp.ds) elif (dp.ds.Modality in ['RTSTRUCT']): self.patient['rtss'] = dp.ds elif (dp.ds.Modality in ['RTPLAN']): self.patient['rtplan'] = dp.ds elif (dp.ds.Modality in ['RTDOSE']): self.patient['rtdose'] = dp.ds # Sort the images based on a sort descriptor: # (ImagePositionPatient, InstanceNumber or AcquisitionNumber) if 'images' in self.patient: sortedimages = [] unsortednums = [] sortednums = [] images = self.patient['images'] sort = 'IPP' # Determine if all images in the series are parallel # by testing for differences in ImageOrientationPatient parallel = True for i, item in enumerate(images): if (i > 0): iop0 = np.array(item.ImageOrientationPatient) iop1 = np.array(images[i - 1].ImageOrientationPatient) if (np.any(np.array(np.round(iop0 - iop1), dtype=np.int32))): parallel = False break # Also test ImagePositionPatient, as some series # use the same patient position for every slice ipp0 = np.array(item.ImagePositionPatient) ipp1 = np.array(images[i - 1].ImagePositionPatient) if not (np.any( np.array(np.round(ipp0 - ipp1), dtype=np.int32))): parallel = False break # If the images are parallel, sort by ImagePositionPatient if parallel: sort = 'IPP' else: # Otherwise sort by Instance Number if not (images[0].InstanceNumber == images[1].InstanceNumber): sort = 'InstanceNumber' # Otherwise sort by Acquisition Number elif not (images[0].AcquisitionNumber == images[1].AcquisitionNumber): sort = 'AcquisitionNumber' # Add the sort descriptor to a list to be sorted for i, image in enumerate(images): if (sort == 'IPP'): unsortednums.append(image.ImagePositionPatient[2]) else: unsortednums.append(image.data_element(sort).value) # Sort image numbers in descending order for head first patients if ('hf' in image.PatientPosition.lower()) and (sort == 'IPP'): sortednums = sorted(unsortednums, reverse=True) # Otherwise sort image numbers in ascending order else: sortednums = sorted(unsortednums) # Add the images to the array based on the sorted order for s, slice in enumerate(sortednums): for i, image in enumerate(images): if (sort == 'IPP'): if (slice == image.ImagePositionPatient[2]): sortedimages.append(image) elif (slice == image.data_element(sort).value): sortedimages.append(image) # Save the images back to the patient dictionary self.patient['images'] = sortedimages
def parse_patient_data(self): """Thread to load the patient data.""" ptdata = self.patient patient = {} pbar = tqdm(total=100) if not 'images' in ptdata: # Look for DICOM data in the ptdata dictionary for rtdatatype in ptdata.keys(): if isinstance(ptdata[rtdatatype], pydicom.dataset.FileDataset): patient.update( dicomparser.DicomParser( ptdata[rtdatatype]).GetDemographics()) break if 'rtss' in ptdata: # pbar.update(20) pbar.n = 20 pbar.set_description('Processing RT Structure Set...') pbar.refresh() d = dicomparser.DicomParser(ptdata['rtss']) s = d.GetStructures() for k in s.keys(): s[k]['planes'] = d.GetStructureCoordinates(k) s[k]['thickness'] = d.CalculatePlaneThickness(s[k]['planes']) patient['structures'] = s if 'rtplan' in ptdata: pbar.n = 40 pbar.refresh() pbar.set_description('Processing RT Plan...') patient['plan'] = dicomparser.DicomParser( ptdata['rtplan']).GetPlan() if 'rtdose' in ptdata: pbar.n = 60 pbar.set_description('Processing RT Dose...') pbar.refresh() patient['dvhs'] = dicomparser.DicomParser( ptdata['rtdose']).GetDVHs() patient['dose'] = dicomparser.DicomParser(ptdata['rtdose']) if 'images' in ptdata: pbar.n = 80 pbar.set_description('Processing Images...') pbar.refresh() if not 'id' in patient: patient.update( dicomparser.DicomParser( ptdata['images'][0]).GetDemographics()) patient['images'] = [] for image in ptdata['images']: patient['images'].append(dicomparser.DicomParser(image)) if 'rxdose' in ptdata: if not 'plan' in patient: patient['plan'] = {} patient['plan']['rxdose'] = ptdata['rxdose'] # if the min/max/mean dose was not present, calculate it and save it for each structure pbar.n = 90 pbar.set_description('Processing DVH data...') pbar.refresh() if ('dvhs' in patient) and ('structures' in patient): # If the DVHs are not present, calculate them i = 0 for key, structure in patient['structures'].items(): # Only calculate DVHs if they are not present for the structure # or recalc all DVHs if the preference is set if ((not (key in patient['dvhs'].keys()))): # Only calculate DVHs for structures, not applicators # and only if the dose grid is present if ((structure['name'].startswith('Applicator')) or (not "PixelData" in patient['dose'].ds)): continue pbar.n = int(np.round( 10 * i / len(patient['structures']))) + 90 pbar.set_description('Calculating DVH for ' + structure['name'] + '...') pbar.refresh() # Limit DVH bins to 500 Gy due to high doses in brachy dvh = dvhcalc.get_dvh(ptdata['rtss'], patient['dose'].ds, key, 50000) if len(dvh.counts): patient['dvhs'][key] = dvh i += 1 for key, dvh in patient['dvhs'].items(): dvh.rx_dose = patient['plan']['rxdose'] / 100 pbar.n = 100 pbar.set_description('Done') pbar.close() self.parse_patient = patient
def get_pt_id(file): """Get patient ID from dicom file""" pt_info = dicomparser.DicomParser(file).GetDemographics() return pt_info['id']
def plot(self): #retrieve plan data & design DVH window plan1files = float(self.box5.get()) plan2files = float(self.box6.get()) self.newWindow = Toplevel( self.window) #initiate new window for summed DVH self.newWindow.title("Recalculated DVH") #self.newWindow.iconbitmap("C:/Users/Owner/Documents/Rlogo2.ico") rtssfile1 = self.filename5 #structure file RTss1 = dicomparser.DicomParser( rtssfile1) #read through structure file RTstructures1 = RTss1.GetStructures( ) #get each individual structure information #at least one dose file required for plan 1 and plan 2 rtdosefile1 = self.filename1 rtdosefile5 = self.filename6 #conditional statements for multiple dose file input if plan1files >= 2: rtdosefile2 = self.filename2 if plan1files >= 3: rtdosefile3 = self.filename3 if plan1files >= 4: rtdosefile4 = self.filename4 if plan2files >= 2: rtdosefile6 = self.filename7 if plan2files >= 3: rtdosefile7 = self.filename8 if plan2files >= 4: rtdosefile8 = self.filename9 #structure to be analysed enteredtext = str(self.box4.get()) #EQD2 parameters dpf1 = float(self.box1.get()) #plan 1 dose per fraction dpf2 = float(self.box2.get()) #plan 2 dose per fraction abratio = float(self.box3.get()) # tissue-specific alpha/beta ratio #EQD2 equation x1 = np.array((dpf1 + abratio) / (float(2.0) + abratio)) x2 = np.array((dpf2 + abratio) / (float(2.0) + abratio)) # Generation of the calculated DVHs #initiation of empty arrays to fill with correct structure data calcdvhs1 = {} calcdvhs2 = {} calcdvhs3 = {} calcdvhs4 = {} calcdvhs5 = {} calcdvhs6 = {} calcdvhs7 = {} calcdvhs8 = {} print("RED is calculating your EQD2 DVH....") print("Please wait, this could take a few moments") #iterate through dose file 1 to find correct stucture data for key, structure in RTstructures1.items(): calcdvhs1[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile1, key) if (key in calcdvhs1) and (structure['name'] == enteredtext) and ( len(calcdvhs1[key].counts) and calcdvhs1[key].counts[0] != 0): print('1st Plan 1 DVH found for ' + structure['name']) data1 = np.array(calcdvhs1[key].bins) * x1 lastdata1 = data1[-1] vola = calcdvhs1[key].counts * 100 / calcdvhs1[key].counts[0] # iterate through dose file 2 to find correct stucture data if plan1files >= 2: for key, structure in RTstructures1.items(): calcdvhs2[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile2, key) if (key in calcdvhs2) and (structure['name'] == enteredtext) and ( len(calcdvhs2[key].counts) and calcdvhs2[key].counts[0] != 0): print('2nd Plan 1 DVH found for ' + structure['name']) data2 = np.array(calcdvhs2[key].bins) * x1 lastdata2 = data2[-1] volb = calcdvhs2[key].counts * 100 / calcdvhs2[key].counts[ 0] # iterate through dose file 3 to find correct stucture data if plan1files >= 3: for key, structure in RTstructures1.items(): calcdvhs3[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile3, key) if (key in calcdvhs3) and (structure['name'] == enteredtext) and ( len(calcdvhs3[key].counts) and calcdvhs3[key].counts[0] != 0): print('3rd Plan 1 DVH found for ' + structure['name']) data3 = np.array(calcdvhs3[key].bins) * x1 lastdata3 = data3[-1] volc = calcdvhs3[key].counts * 100 / calcdvhs3[key].counts[ 0] # iterate through dose file 4 to find correct stucture data if plan1files >= 4: for key, structure in RTstructures1.items(): calcdvhs4[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile4, key) if (key in calcdvhs4) and (structure['name'] == enteredtext) and ( len(calcdvhs4[key].counts) and calcdvhs4[key].counts[0] != 0): print('4th Plan 1 DVH found for ' + structure['name']) data4 = np.array(calcdvhs4[key].bins) * x1 lastdata4 = data4[-1] vold = calcdvhs4[key].counts * 100 / calcdvhs4[key].counts[ 0] # iterate through dose file 5 to find correct stucture data for key, structure in RTstructures1.items(): calcdvhs5[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile5, key) if (key in calcdvhs5) and (structure['name'] == enteredtext) and ( len(calcdvhs5[key].counts) and calcdvhs5[key].counts[0] != 0): print('1st Plan 2 DVH found for ' + structure['name']) data5 = np.array(calcdvhs5[key].bins) * x2 lastdata5 = data5[-1] vole = calcdvhs5[key].counts * 100 / calcdvhs5[key].counts[0] # iterate through dose file 6 to find correct stucture data if plan2files >= 2: for key, structure in RTstructures1.items(): calcdvhs6[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile6, key) if (key in calcdvhs6) and (structure['name'] == enteredtext) and ( len(calcdvhs6[key].counts) and calcdvhs6[key].counts[0] != 0): print('2nd Plan 2 DVH found for ' + structure['name']) data6 = np.array(calcdvhs6[key].bins) * x2 lastdata6 = data6[-1] volf = calcdvhs6[key].counts * 100 / calcdvhs6[key].counts[ 0] # iterate through dose file 7 to find correct stucture data if plan2files >= 3: for key, structure in RTstructures1.items(): calcdvhs7[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile7, key) if (key in calcdvhs7) and (structure['name'] == enteredtext) and ( len(calcdvhs7[key].counts) and calcdvhs7[key].counts[0] != 0): print('3rd Plan 2 DVH found for ' + structure['name']) data7 = np.array(calcdvhs7[key].bins) * x2 lastdata7 = data7[-1] volg = calcdvhs7[key].counts * 100 / calcdvhs7[key].counts[ 0] # iterate through dose file 8 to find correct stucture data if plan2files >= 4: for key, structure in RTstructures1.items(): calcdvhs8[key] = dvhcalc.get_dvh(rtssfile1, rtdosefile8, key) if (key in calcdvhs8) and (structure['name'] == enteredtext) and ( len(calcdvhs8[key].counts) and calcdvhs8[key].counts[0] != 0): print('4th Plan 2 DVH found for ' + structure['name']) data8 = np.array(calcdvhs8[key].bins) * x2 lastdata8 = data8[-1] volh = calcdvhs8[key].counts * 100 / calcdvhs8[key].counts[ 0] #initiation of DVH window fig = Figure() ax = fig.add_subplot(111) #addition of plan 1 dose data if plan1files == 1: totaldose1 = lastdata1 if plan1files == 2: totaldose1 = lastdata1 + lastdata2 if plan1files == 3: totaldose1 = lastdata1 + lastdata2 + lastdata3 if plan1files == 4: totaldose1 = lastdata1 + lastdata2 + lastdata3 + lastdata4 #addition of plan 2 dose data if plan2files == 1: totaldose2 = lastdata5 if plan2files == 2: totaldose2 = lastdata5 + lastdata6 if plan2files == 3: totaldose2 = lastdata5 + lastdata6 + lastdata7 if plan2files == 4: totaldose2 = lastdata5 + lastdata6 + lastdata7 + lastdata8 #conditional statements for plan 1 based on number of dose files #definition of volume and dose data if plan1files == 1: y = vola #volume data y1len = len(y) x = np.linspace(0, totaldose1, num=y1len) #dose data elif plan1files == 2: interyvaluesplan1b = np.concatenate((vola, volb), axis=0) #summed volume data sorty21 = np.sort(interyvaluesplan1b ) #sorted volume data array in ascending order vol21 = sorty21[:: -1] #volume data array reversed to descending order y = vol21 #volume data y1len = len(y) x = np.linspace(0, totaldose1, num=y1len) #dose data elif plan1files == 3: interyvalues1plan1a = np.concatenate((vola, volb), axis=0) interyvaluesplan1c = np.concatenate((interyvalues1plan1a, volc), axis=0) #sumed volume data sorty31 = np.sort(interyvaluesplan1c ) #sorted volume data array in ascending order vol31 = sorty31[:: -1] #volume data array reversed to descending order y = vol31 #volume data y1len = len(y) x = np.linspace(0, totaldose1, num=y1len) #dose data elif plan1files == 4: interyvalues1plan1b = np.concatenate((vola, volb), axis=0) interyvalues2plan1a = np.concatenate((volc, vold), axis=0) interyvaluesplan1d = np.concatenate( (interyvalues1plan1b, interyvalues2plan1a), axis=0) #summed volume data sorty41 = np.sort(interyvaluesplan1d ) #sorted volume data array in ascending order vol41 = sorty41[:: -1] #volume data array reversed to descending order y = vol41 y1len = len(y) x = np.linspace(0, totaldose1, num=y1len) #dose data else: print("please check code starting at line 367") if plan2files == 1: b = vole #volume data blen = len(b) a = np.linspace(0, totaldose2, num=blen) #dose data elif plan2files == 2: interyvaluesplan2b = np.concatenate((vole, volf), axis=0) #summed volume data sorty22 = np.sort(interyvaluesplan2b ) # sorted volume data array in ascending order vol22 = sorty22[:: -1] #volume data array reversed to descending order b = vol22 blen = len(b) a = np.linspace(0, totaldose2, num=blen) #dose data elif plan2files == 3: interyvalues1plan2a = np.concatenate((vole, volf), axis=0) interyvaluesplan2c = np.concatenate((interyvalues1plan2a, volg), axis=0) #summed volume data sorty32 = np.sort(interyvaluesplan2c ) #sorted volume data array in ascending order vol32 = sorty32[:: -1] #volume data array reversed to descending order b = vol32 blen = len(b) a = np.linspace(0, totaldose2, num=blen) #dose data elif plan2files == 4: interyvalues1plan2b = np.concatenate((vole, volf), axis=0) interyvalues2plan2a = np.concatenate((volg, volh), axis=0) interyvaluesplan2d = np.concatenate( (interyvalues1plan2b, interyvalues2plan2a), axis=0) #summed volume data sorty42 = np.sort(interyvaluesplan2d ) #sorted volume data array in ascending order vol42 = sorty42[:: -1] # volume data array reversed to descending order b = vol42 blen = len(b) a = np.linspace(0, totaldose2, num=blen) #dose data else: print("please check code starting at line 398") #plot plan 1 and plan 2 re-calculated DVH ax.plot(x, y, c='b') ax.plot(a, b, c='g') array = np.linspace(0, 100, 9000, endpoint=False) # get values from plan1 graph line1 = ax.lines[0] line1.get_xydata() xdat1 = line1.get_xdata() #get x data fp1 = xdat1[:: -1] # reverses x values to match reversed y values in array# ydat1 = line1.get_ydata() #get y data xp1 = ydat1[:: -1] # reverses y values from decreasng to increasing so interpolation function can work # get values from plan2 graph line2 = ax.lines[1] line2.get_xydata() xdat2 = line2.get_xdata() #get xdata fp2 = xdat2[:: -1] # reverses x values to match reversed y values in array ydat2 = line2.get_ydata() #get y data xp2 = ydat2[:: -1] # reverses y values from decreasng to increasing so interpolation function can work # set volume array to use for dose interpolation inter1 = np.interp([array], xp1, fp1) # interpolation of plan1 dose reshapeinter1 = np.reshape(inter1, (9000, 1)) inter2 = np.interp([array], xp2, fp2) # interpolation of plan2 dose reshapeinter2 = np.reshape(inter2, (9000, 1)) xvalues = reshapeinter1 + reshapeinter2 # adding plan1 and plan2 dose reshapearray = np.reshape( array, (9000, 1)) # array of specified %volume intervals #define strings dpf12 = str(dpf1) dpf21 = str(dpf2) abdisplay = str(abratio) #plot summed re-calculated DVH in seperate window plt.plot(xvalues, reshapearray, c='r') plt.title("Recalculated Summed DVH") # plt.iconbitmap("C:/Users/Owner/Documents/Rlogo2.ico") plt.title("Summed EQD2 DVH for " + enteredtext + " a/b=" + abdisplay + " dpf Plan 1=" + dpf12 + " dpf Plan 2=" + dpf21) ax.legend(["Plan 1", "Plan 2"]) ax.set_title("EQD2 DVH for " + enteredtext + " a/b=" + abdisplay + " dpf Plan 1=" + dpf12 + " dpf Plan 2=" + dpf21) ax.set_ylabel("Volume %", fontsize=14) ax.set_xlabel("EQD2 Gy", fontsize=14) plt.legend(["Plan 1 + 2"]) plt.ylabel("Volume %", fontsize=14) plt.xlabel("EQD2 Gy", fontsize=14) ax.set_axisbelow(True) #design of summed dvh plt.grid(color='gray', linestyle='dashed') datacursor() ax.yaxis.grid(color='gray', linestyle='dashed') ax.xaxis.grid(color='gray', linestyle='dashed') #draw sumed DVH canvas = FigureCanvasTkAgg(fig, master=self.newWindow) canvas.get_tk_widget().grid(row=10) canvas.draw() plt.show() #initiate toolbar for analysis toolbarFrame = Frame(master=self.newWindow) toolbarFrame.grid(row=20) toolbar = NavigationToolbar2Tk(canvas, toolbarFrame) toolbar.draw()
def GetPatientData(self, path, filearray, RxDose, terminate, progressFunc): """Get the data of the selected patient from the DICOM importer dialog.""" wx.CallAfter(progressFunc, -1, 100, "Importing patient. Please wait...") for n in range(0, len(filearray)): if terminate(): wx.CallAfter(progressFunc, 98, 100, "Importing patient cancelled.") return dcmfile = str(os.path.join(self.path, filearray[n])) dp = dicomparser.DicomParser(dcmfile) if n == 0: self.patient = {} self.patient["rxdose"] = RxDose if ("ImageOrientationPatient" in dp.ds) and not ( dp.GetSOPClassUID() == "rtdose" ): if not "images" in self.patient: self.patient["images"] = [] self.patient["images"].append(dp.ds) elif dp.ds.Modality in ["RTSTRUCT"]: self.patient["rtss"] = dp.ds elif dp.ds.Modality in ["RTPLAN"]: self.patient["rtplan"] = dp.ds elif dp.ds.Modality in ["RTDOSE"]: self.patient["rtdose"] = dp.ds wx.CallAfter( progressFunc, n, len(filearray), "Importing patient. Please wait..." ) # Sort the images based on a sort descriptor: # (ImagePositionPatient, InstanceNumber or AcquisitionNumber) if "images" in self.patient: sortedimages = [] unsortednums = [] sortednums = [] images = self.patient["images"] sort = "IPP" # Determine if all images in the series are parallel # by testing for differences in ImageOrientationPatient parallel = True for i, item in enumerate(images): if i > 0: iop0 = np.array(item.ImageOrientationPatient) iop1 = np.array(images[i - 1].ImageOrientationPatient) if np.any(np.array(np.round(iop0 - iop1), dtype=np.int32)): parallel = False break # Also test ImagePositionPatient, as some series # use the same patient position for every slice ipp0 = np.array(item.ImagePositionPatient) ipp1 = np.array(images[i - 1].ImagePositionPatient) if not (np.any(np.array(np.round(ipp0 - ipp1), dtype=np.int32))): parallel = False break # If the images are parallel, sort by ImagePositionPatient if parallel: sort = "IPP" else: # Otherwise sort by Instance Number if not (images[0].InstanceNumber == images[1].InstanceNumber): sort = "InstanceNumber" # Otherwise sort by Acquisition Number elif not (images[0].AcquisitionNumber == images[1].AcquisitionNumber): sort = "AcquisitionNumber" logger.debug(f"parallel: {parallel}, sort: {sort}") # Add the sort descriptor to a list to be sorted for i, image in enumerate(images): if sort == "IPP": unsortednums.append(image.ImagePositionPatient[2]) else: unsortednums.append(image.data_element(sort).value) # Sort image numbers in descending order for head first patients if ("hf" in image.PatientPosition.lower()) and (sort == "IPP"): sortednums = sorted(unsortednums, reverse=True) # Otherwise sort image numbers in ascending order else: sortednums = sorted(unsortednums) # Add the images to the array based on the sorted order for s, slice in enumerate(sortednums): for i, image in enumerate(images): if sort == "IPP": if slice == image.ImagePositionPatient[2]: sortedimages.append(image) elif slice == image.data_element(sort).value: sortedimages.append(image) # Save the images back to the patient dictionary self.patient["images"] = sortedimages wx.CallAfter(progressFunc, 98, 100, "Importing patient complete.")
def DirectorySearchThread( self, parent, path, subfolders, terminate, progressFunc, foundFunc, resultFunc ): """Thread to start the directory search.""" # Call the progress function to update the gui wx.CallAfter(progressFunc, 0, 0, "Searching for patients...") patients = {} # Check if the path is valid if os.path.isdir(path): files = [] for root, dirs, filenames in os.walk(path): files += map(lambda f: os.path.join(root, f), filenames) if self.import_search_subfolders == False: break for n in range(len(files)): # terminate the thread if the value has changed # during the loop duration if terminate(): wx.CallAfter(progressFunc, 0, 0, "Search terminated.") return if os.path.isfile(files[n]): try: logger.debug("Reading: %s", files[n]) dp = dicomparser.DicomParser(files[n]) except (AttributeError, EOFError, IOError, KeyError): pass logger.info("%s is not a valid DICOM file.", files[n]) else: patient = dp.GetDemographics() h = hashlib.sha1(patient["id"].encode("utf-8")).hexdigest() if not h in patients: patients[h] = {} patients[h]["demographics"] = patient if not "studies" in patients[h]: patients[h]["studies"] = {} patients[h]["series"] = {} wx.CallAfter(foundFunc, patient) # Create each Study but don't create one for RT Dose # since some vendors use incorrect StudyInstanceUIDs if not (dp.GetSOPClassUID() == "rtdose"): stinfo = dp.GetStudyInfo() if not stinfo["id"] in patients[h]["studies"]: patients[h]["studies"][stinfo["id"]] = stinfo # Create each Series of images if ("ImageOrientationPatient" in dp.ds) and not ( dp.GetSOPClassUID() == "rtdose" ): seinfo = dp.GetSeriesInfo() seinfo["numimages"] = 0 seinfo["modality"] = dp.ds.SOPClassUID.name if not seinfo["id"] in patients[h]["series"]: patients[h]["series"][seinfo["id"]] = seinfo if not "images" in patients[h]: patients[h]["images"] = {} image = {} image["id"] = dp.GetSOPInstanceUID() image["filename"] = files[n] image["series"] = seinfo["id"] image["referenceframe"] = dp.GetFrameOfReferenceUID() patients[h]["series"][seinfo["id"]]["numimages"] = ( patients[h]["series"][seinfo["id"]]["numimages"] + 1 ) patients[h]["images"][image["id"]] = image # Create each RT Structure Set elif dp.ds.Modality in ["RTSTRUCT"]: if not "structures" in patients[h]: patients[h]["structures"] = {} structure = dp.GetStructureInfo() structure["id"] = dp.GetSOPInstanceUID() structure["filename"] = files[n] structure["series"] = dp.GetReferencedSeries() structure["referenceframe"] = dp.GetFrameOfReferenceUID() patients[h]["structures"][structure["id"]] = structure # Create each RT Plan elif dp.ds.Modality in ["RTPLAN"]: if not "plans" in patients[h]: patients[h]["plans"] = {} plan = dp.GetPlan() plan["id"] = dp.GetSOPInstanceUID() plan["filename"] = files[n] plan["series"] = dp.ds.SeriesInstanceUID plan["referenceframe"] = dp.GetFrameOfReferenceUID() plan["beams"] = dp.GetReferencedBeamsInFraction() plan["rtss"] = dp.GetReferencedStructureSet() patients[h]["plans"][plan["id"]] = plan # Create each RT Dose elif dp.ds.Modality in ["RTDOSE"]: if not "doses" in patients[h]: patients[h]["doses"] = {} dose = {} dose["id"] = dp.GetSOPInstanceUID() dose["filename"] = files[n] dose["referenceframe"] = dp.GetFrameOfReferenceUID() dose["hasdvh"] = dp.HasDVHs() dose["hasgrid"] = "PixelData" in dp.ds dose["summationtype"] = dp.ds.DoseSummationType dose["beam"] = dp.GetReferencedBeamNumber() dose["rtss"] = dp.GetReferencedStructureSet() dose["rtplan"] = dp.GetReferencedRTPlan() patients[h]["doses"][dose["id"]] = dose # Otherwise it is a currently unsupported file else: logger.info( "%s is a %s file and is not " + "currently supported.", files[n], dp.ds.SOPClassUID.name, ) # Call the progress function to update the gui wx.CallAfter(progressFunc, n, len(files), "Searching for patients...") if len(patients) == 0: progressStr = "Found 0 patients." elif len(patients) == 1: progressStr = "Found 1 patient. Reading DICOM data..." elif len(patients) > 1: progressStr = ( "Found " + str(len(patients)) + " patients. Reading DICOM data..." ) wx.CallAfter(progressFunc, 0, 1, progressStr) wx.CallAfter(resultFunc, patients) # if the path is not valid, display an error message else: wx.CallAfter(progressFunc, 0, 0, "Select a valid location.") dlg = wx.MessageDialog( parent, "The DICOM import location does not exist. Please select a valid location.", "Invalid DICOM Import Location", wx.OK | wx.ICON_ERROR, ) dlg.ShowModal()
def get_dvh(structure, dose, roi, limit=None, calculate_full_volume=True, use_structure_extents=False, interpolation_resolution=None, interpolation_segments_between_planes=0, thickness=None, memmap_rtdose=False, callback=None): """Calculate a cumulative DVH in Gy from a DICOM RT Structure Set & Dose. Parameters ---------- structure : pydicom Dataset or filename DICOM RT Structure Set used to determine the structure data. dose : pydicom Dataset or filename DICOM RT Dose used to determine the dose grid. roi : int The ROI number used to uniquely identify the structure in the structure set. limit : int, optional Dose limit in cGy as a maximum bin for the histogram. calculate_full_volume : bool, optional Calculate the full structure volume including contours outside of the dose grid. use_structure_extents : bool, optional Limit the DVH calculation to the in-plane structure boundaries. interpolation_resolution : tuple or float, optional Resolution in mm (row, col) to interpolate structure and dose data to. If float is provided, original dose grid pixel spacing must be square. interpolation_segments_between_planes : integer, optional Number of segments to interpolate between structure slices. thickness : float, optional Structure thickness used to calculate volume of a voxel. memmap_rtdose : bool, optional Use memory mapping to access the pixel array of the DICOM RT Dose. This reduces memory usage at the expense of increased calculation time. callback : function, optional A function that will be called at every iteration of the calculation. Returns ------- dvh.DVH An instance of dvh.DVH in cumulative dose. This can be converted to different formats using the attributes and properties of the DVH class. """ from dicompylercore import dicomparser rtss = dicomparser.DicomParser(structure) rtdose = dicomparser.DicomParser(dose, memmap_pixel_array=memmap_rtdose) structures = rtss.GetStructures() s = structures[roi] s['planes'] = rtss.GetStructureCoordinates(roi) s['thickness'] = thickness if thickness else rtss.CalculatePlaneThickness( s['planes']) calcdvh = _calculate_dvh(s, rtdose, limit, calculate_full_volume, use_structure_extents, interpolation_resolution, interpolation_segments_between_planes, callback) return dvh.DVH(counts=calcdvh.histogram, bins=(np.arange(0, 2) if (calcdvh.histogram.size == 1) else np.arange(0, calcdvh.histogram.size + 1) / 100), dvh_type='differential', dose_units='Gy', notes=calcdvh.notes, name=s['name']).cumulative
def calculate_review_dvh(self): global x, y patches = { 'x': [(0, [])], 'y': [(0, [])], 'roi_name': [(0, '')], 'volume': [(0, 1)], 'min_dose': [(0, '')], 'mean_dose': [(0, '')], 'max_dose': [(0, '')], 'mrn': [(0, '')], 'rx_dose': [(0, 1)] } try: if not self.sources.dvhs.data['x']: self.query.update_data() else: file_index = self.temp_dvh_info.mrn.index( self.select_reviewed_mrn.value) roi_index = self.dvh_review_rois.index( self.select_reviewed_dvh.value) structure_file = self.temp_dvh_info.structure[file_index] plan_file = self.temp_dvh_info.plan[file_index] dose_file = self.temp_dvh_info.dose[file_index] key = list( self.temp_dvh_info.get_roi_names( self.select_reviewed_mrn.value))[roi_index] rt_st = dicomparser.DicomParser(structure_file) rt_structures = rt_st.GetStructures() review_dvh = dvhcalc.get_dvh(structure_file, dose_file, key) dicompyler_plan = dicomparser.DicomParser(plan_file).GetPlan() roi_name = rt_structures[key]['name'] volume = review_dvh.volume min_dose = review_dvh.min mean_dose = review_dvh.mean max_dose = review_dvh.max if not self.review_rx.value: rx_dose = float(dicompyler_plan['rxdose']) / 100. self.review_rx.value = str(round(rx_dose, 2)) else: rx_dose = round(float(self.review_rx.value), 2) x = review_dvh.bincenters if max(review_dvh.counts): y = np.divide(review_dvh.counts, max(review_dvh.counts)) else: y = review_dvh.counts if self.radio_group_dose.active == 1: f = 5000 bin_count = len(x) new_bin_count = int(bin_count * f / (rx_dose * 100.)) x1 = np.linspace(0, bin_count, bin_count) x2 = np.multiply( np.linspace(0, new_bin_count, new_bin_count), rx_dose * 100. / f) y = np.interp(x2, x1, review_dvh.counts) y = np.divide(y, np.max(y)) x = np.divide(np.linspace(0, new_bin_count, new_bin_count), f) if self.radio_group_volume.active == 0: y = np.multiply(y, volume) patches = { 'x': [(0, x)], 'y': [(0, y)], 'roi_name': [(0, roi_name)], 'volume': [(0, volume)], 'min_dose': [(0, min_dose)], 'mean_dose': [(0, mean_dose)], 'max_dose': [(0, max_dose)], 'mrn': [(0, self.select_reviewed_mrn.value)], 'rx_dose': [(0, rx_dose)] } except: pass self.sources.dvhs.patch(patches) self.update_source_endpoint_calcs()