def __init__(self, data, port=None, thickness=None): VolumeData.__init__(self, data, thickness) self.port = port self.spacing = data.GetSpacing() self.dimensions = data.GetDimensions() self.origin = data.GetOrigin() log.info("CT Spacing %s, Dimensions %s, Origin %s" % (self.spacing, self.dimensions, self.origin))
def read(self, name): volume = VolumeData() number, color = self.find(name) contours = self.findContours(number) grid = volume.createGrid(self.spacing, self.dimensions, self.origin) # sprawdzamy, jak sa ponumerowane wezly - wychodzi, ze najpierw x, potem y, na koncu z #for i in range(0,self.dimensions[0]*self.dimensions[1]*5,self.dimensions[0]*self.dimensions[1]): # print i # print grid.GetPoint(i) array = volume.createArray(grid) array = self.fillArray(array, contours) grid.GetPointData().SetScalars(array) return RTStructureVolumeData(grid, name, number, color)
def __init__(self, spacing=None, dimensions=None, origin=None, mark=None): volume = VolumeData() grid = volume.createGrid(spacing, dimensions, origin) array = volume.createIntegerArray(grid) ox, oy, oz = origin sx, sy, sz = spacing dx, dy, dz = dimensions index = 0 for i in range(dz): for j in range(dy): for k in range(dx): array.SetValue(index, mark[i][j][k]) index += 1 grid.GetPointData().SetScalars(array) self.roiView = RoiView(grid)
def saveToVTI(filename, beamlet_doses, spacing, n, orig): # Zapisywanie danych do formatu vti volume = VolumeData() grid = VolumeData.createGrid(spacing, n, orig) ar = VolumeData.createFloatArray(grid) ar.SetVoidArray(beamlet_doses, n[0] * n[1] * n[2], 1) grid.GetPointData().SetScalars(ar) volume = VolumeData(grid) volume.save(filename)
def do_run(args): ctgriddata = None if hasattr(args,"rass_data"): rass_data = args.rass_data else: rass_data = RASSData(root_folder=args.root_folder) ################################################################ # Wczytuję opcje z folderu "input" ################################################################ options = default_options() cfname = rass_data.input("config.json") if os.path.isfile(cfname): log.info("Reading options from file: %s" % cfname) with open(cfname) as options_file: options.update(json.load(options_file)) ################################################################ # Przesłaniam opcje za pomocą pliku przekazanego za pomocą argumentów linii komend ################################################################ for i in range(len(argv)): if "options" == argv[i]: fname = "%s" % (argv[i + 1]) log.info("Reading options from file: %s" % fname) with open(fname) as options_file: options.update(json.load(options_file)) dicomutils.DEBUG_LEVEL = options["debug_level"] ################################################################ # Szukam plików DICOM w podkatalogu "input"/dicom ################################################################ rtss, plan, ctlist, doseslist = dicomutils.find_ct_rs_rp_dicom(rass_data.input("dicom")) if rtss is None or plan is None: raise Exception(f"No RS.* or rtss.* file in {rass_data.input('dicom')}") ################################################################ # Wczytuję pliki DICOM z informacjami o strukturach (ROIach) # oraz plan ################################################################ rtss = dicom.read_file(rtss) plan = dicom.read_file(plan) treatment_name = '-'.join(plan.PatientID.split('^')) log.info('Name: ' + treatment_name) ################################################################ # Wczytuję dane CT używając VTK ################################################################ from ct import CTVolumeDataReader reader = CTVolumeDataReader(rass_data.input("dicom"), ctfiles=ctlist) ctVolumeData = reader.read() ctData = ctVolumeData.getCTDataAsNumpyArray() if len(ctlist) > 0: ct = dicom.read_file(ctlist[0]) ctgriddata = list(map(float, ( ct.ImagePositionPatient[0], ct.ImagePositionPatient[1], ct.PixelSpacing[0], ct.PixelSpacing[1], ct.Columns, ct.Rows))) else: ctgriddata = None ################################################################ # reading doses information for beams from DICOM ################################################################ beams = [dicom.read_file(f) for f in doseslist] ################################################################## # Wczytuję dawki z poszczególnych wiązek (beams) ################################################################## beamDoses = {} totalDoses = None totalDosesFile = None doseScaling = None singleBeam = False for beam in beams: doseScaling = float(beam.DoseGridScaling) try: bn = int(beam.ReferencedRTPlanSequence[0].ReferencedFractionGroupSequence[0].ReferencedBeamSequence[0].ReferencedBeamNumber) except: print("Semething wrong went...") if totalDoses is None: singleBeam = True totalDoses = beam.pixel_array.copy() totalDosesFile = beam.filename continue beamDoses[bn] = beam.pixel_array if doseScaling is not None and float(beam.DoseGridScaling) != doseScaling: log.warning('Strange data: DoseGridScaling is not same all beams!') log.info(f"Got doses data for beam number {bn}") ################################################################## # Sumuję dawki z poszczególnych wiązek (beams) do dawki całkowitej ################################################################## if not singleBeam: print(beamDoses) bns = list(beamDoses.keys()) totalDoses = beamDoses[bns[0]].copy() for i in range(1, len(bns)): log.info(f"Adding doses from beam {i}") totalDoses += beamDoses[bns[i]] totalDoses = np.array(totalDoses, dtype=np.float32) log.info("Read doses for %d beams" % len(beamDoses)) minDose = np.min(totalDoses) averageDose = np.average(totalDoses) maxDose = np.max(totalDoses) if totalDosesFile is None: log.info('Total doses calculated as sum of beam doses (min dose=%f, average dose=%f, max dose=%f, doseScaling=%f)' % ( minDose, averageDose, maxDose, doseScaling)) else: log.info('Got total doses from file %s (min dose=%f, average dose=%f, max dose = %f, doseScaling=%f)' % ( totalDosesFile, minDose, averageDose, maxDose, doseScaling)) # To są informacje o siatce planowania wyciete z pierwszej wiązki tBeam = beams[0] kmax = tBeam.Columns # x? jmax = tBeam.Rows # y? imax = len(tBeam.GridFrameOffsetVector) # z xbase = float(tBeam.ImagePositionPatient[0]) * SCALE ybase = float(tBeam.ImagePositionPatient[1]) * SCALE zbase = float(tBeam.ImagePositionPatient[2]) * SCALE dx = float(tBeam.PixelSpacing[0]) * SCALE dy = float(tBeam.PixelSpacing[1]) * SCALE zoffsets = list(map(float, tBeam.GridFrameOffsetVector)) for i in range(len(zoffsets)): zoffsets[i] *= SCALE dz = zoffsets[1] - zoffsets[0] dv = dx * dy * dz log.info('Planning grid: %d x %d x %d in [%g:%g]x[%g:%g]x[%g:%g] dx,dy,dz=%g,%g,%g -> dv=%g' % ( kmax, jmax, imax, xbase, xbase + kmax * dx, ybase, ybase + jmax * dy, zbase + zoffsets[0], zbase + zoffsets[-1], dx, dy, dz, dv)) planGridInfo = {'ixmax': kmax, 'iymax': jmax, 'izmax': imax, 'xorig': xbase, 'yorig': ybase, 'zorig': zbase, 'dx': dx, 'dy': dy, 'dz': dz, 'minDose': minDose, 'avgDose': averageDose, 'maxDose': maxDose, 'doseScaling': doseScaling } #################################################### # Analiza ROIów #################################################### myROIs = [] idxROIBody = -1 for i in range(0, len(rtss.StructureSetROISequence)): roiName = rtss.StructureSetROISequence[i].ROIName log.info(f"Reading contours for {roiName} from DICOM") contours = dicomutils.findContours(rtss, rtss.StructureSetROISequence[i].ROINumber) if len(contours) > 1: r = MyRoi(contours, roiName, float(tBeam.PixelSpacing[0]) / 1000.0) myROIs.append(r) if ("body" in roiName.lower() or "skin" in roiName.lower() or "outline" in roiName.lower()) and (idxROIBody == -1): idxROIBody = i log.info("Found ROI body (or skin): idx = %d" % idxROIBody) if idxROIBody == -1: raise Exception("The structure file does not contain any structure with 'body', 'outline' or 'skin' in the name.") ########################################################################## # Mark ROIs or read from cache (cache is a file in a working # directory, separate file for each ROI, # the filename pattern is: "%s_%s.markscache" % (treatment_name, ROIName) ########################################################################## roi_marks = np.zeros((imax, jmax, kmax), dtype=np.int64) for r in range(0, len(myROIs)): fcache = rass_data.processing("%s_%s.markscache" % (treatment_name, myROIs[r].name)) if myROIs[r].read_marks(fcache, roi_marks): log.info("Read marking voxels for %s from cache" % myROIs[r].name) myROIs[r].countVoxels(roi_marks, 2 ** r) else: log.info("Marking voxels for %s" % myROIs[r].name) log.debug("CTGRID DATA %s" % list(ctgriddata)) myROIs[r].mark(xbase / SCALE, ybase / SCALE, dx / SCALE, dy / SCALE, kmax, jmax, imax, np.linspace(zbase, zbase + (imax - 1) * dz, imax) / SCALE, roi_marks, 2 ** r, ctgriddata=ctgriddata) myROIs[r].save_marks(fcache, roi_marks, 2 ** r) for r in range(len(myROIs)): log.info("Statistics for %20s: ID=%8d, %7d voxels, vol=%8.1f discrete vol=%8.1f [cm3]" % ( myROIs[r].name, 2 ** r, myROIs[r].count, myROIs[r].volume / 1000., myROIs[r].count * dv / SCALE / SCALE / SCALE / 1000.0)) # mam wczytane CT - ctData # mam wczytane Dawki - totalDoses (wspolrzedne siatki planowania) # mam informacje o rojach - roi_marks (współrzędne siatki planowania) # Teraz trzeba przeskalować CT i pozapisywać dane i będzie z głowy... plan_origin = (xbase, ybase, zbase) plan_dimensions = (kmax, jmax, imax) plan_spacing = (dx, dy, dz) ctOnPlanningGrid = ctVolumeData.approximateCTOnPlanGrid( plan_origin, plan_spacing, plan_dimensions ) ## zapisuję do plików VTI npar = ctOnPlanningGrid if not skip_vti: VolumeData.saveVolumeGridToFile(plan_spacing, plan_dimensions, plan_origin, npar, rass_data.output("approximated_ct")) VolumeData.saveVolumeGridToFileAsLong(plan_spacing, plan_dimensions, plan_origin, roi_marks, rass_data.output("roi_marks")) for r in range(0, len(myROIs)): d = np.array(np.bitwise_and(roi_marks, (2 ** r)) / (2 ** r), dtype=np.float32) log.debug(f"ROI: {myROIs[r].name}[{2 ** r}].size() = {np.sum(d)}") log.info(f"Saving roi marks for {myROIs[r].name} to {rass_data.output(f'roi_marks_{myROIs[r].name}')}.vti file ...") VolumeData.saveVolumeGridToFile(plan_spacing, plan_dimensions, plan_origin, d, rass_data.output(f"roi_marks_{myROIs[r].name}")) VolumeData.saveVolumeGridToFile(plan_spacing, plan_dimensions, plan_origin, totalDoses, rass_data.output("total_doses")) ## zapisuję do plików ndarray from bdfileutils import save_ndarray, read_ndarray ctOnPlanningGrid = np.reshape(ctOnPlanningGrid, (imax, jmax, kmax)) save_ndarray(rass_data.output("approximated_ct.nparray"),ctOnPlanningGrid) roi_marks = np.reshape(roi_marks, (imax, jmax, kmax)) save_ndarray(rass_data.output("roi_marks.nparray"),roi_marks) for r in range(0, len(myROIs)): d = np.array(np.bitwise_and(roi_marks, (2 ** r)) / (2 ** r), dtype=np.int32) d = np.reshape(d, (imax, jmax, kmax)) save_ndarray(rass_data.output(f"roi_marks_{myROIs[r].name}.nparray"), d) totalDoses = np.reshape(totalDoses, (imax, jmax, kmax)) save_ndarray(rass_data.output("total_doses.nparray"),totalDoses) with open(rass_data.output("roi_mapping.txt"),"w") as f: for i in range(len(myROIs)): f.write(f"{myROIs[i].name}:{2 ** i}\n")
def __init__(self, data, name, number, color=None): VolumeData.__init__(self, data) self.name = name self.number = number self.color = color
def prepare_ct_file(self, v2Drow): log.info("Reading CT Dicom data from folder: %s" % self.dicom_folder) # wczytuję oryginalny CT z DICOMa reader = CTVolumeDataReader(self.dicom_folder, ctfiles=self.ctfiles) ctVolumeData = reader.read() ctVolumeData.save(self.rass_data.output( "phantom_ct")) # zapisuję do vti do debugingu bDimFromCT = True if self.plan_n is None else False self.n = self.plan_n if self.plan_n is not None else ctVolumeData.dimensions self.orig = self.plan_origin if self.plan_origin is not None else ctVolumeData.origin self.spacing = self.plan_spacing if self.plan_spacing is not None else ctVolumeData.spacing bnx = struct.pack("i", self.n[0]) bny = struct.pack("i", self.n[1]) bnz = struct.pack("i", self.n[2]) fout = open("%s" % (self.ct_file), "wb") fout.write(bnx) fout.write(bny) fout.write(bnz) dcorr = [-0.5, -0.5, -0.5] for d in range(3): #print "orig: %f" % self.orig[d] for i in range(0, self.n[d] + 1): b = struct.pack("f", (self.orig[d] + self.spacing[d] * i + dcorr[d] * self.spacing[d])) #print "[%d,%d]=%f" % (d, i, self.orig[d] + self.spacing[d] * i + dcorr[d] * self.spacing[d]) fout.write(b) if bDimFromCT: npar = ctVolumeData.getCTDataAsNumpyArray() else: npar = self.approximateCTOnPlanGrid(ctVolumeData) # Zapisywanie danych do formatu vti log.info( "Zapisuję Przed skalowaniem dane gęstości masy z CT do pliku: %s" % self.rass_data.output("approximated_ct_before_scale")) grid = VolumeData.createGrid( (self.spacing[0], self.spacing[1], self.spacing[2]), (self.n[0], self.n[1], self.n[2]), (self.orig[0], self.orig[1], self.orig[2])) array = VolumeData.createFloatArray(grid) array.SetVoidArray(npar, numpy.prod(npar.shape), 1) grid.GetPointData().SetScalars(array) volume = VolumeData(grid) volume.save(self.rass_data.output("approximated_ct_before_scale")) log.info( "Zapisałem przeskalowane dane gęstości masy z CT do pliku: %s" % self.rass_data.output("approximated_ct_before_scale")) ################################################################################### # Skalowanie wartości Hounsfielda (-1000-6000) do zakresu (0-4) - używanego przez VMC # Skalowanie dwuetapowe: # 1. Najpierw z HU do Rel.Electron Ddensity - na podstawie danych Eclipse # 2. Z Rel.Electron Ddensity do gęstości masy - na podstawie artykułu: # Kanematsu, N., Inaniwa, T., & Koba, Y. (2012). Relationship between electron density # and effective densities of body tissues for stopping, scattering, and nuclear # interactions of proton and ion beams. Medical Physics, 39(2), 1016–20. http://doi.org/10.1118/1.3679339 ################################################################################### # Pierwszy etap #CT_Calibration Curve: Def_CTScanner Electron Density # #HU Value [HU] Rel.Density # #-1050.000 0.000 #-1000.000 0.000 a = 0.001, b = 1 # 100.000 1.100 a = 4.800e-4, b = 1.0520 # 1000.000 1.532 a = 4.7760e-04, b = 1.0544 # 6000.000 3.920 npar -= 1000 npar[npar < -1000] = 0 cond1 = (npar > -1000) & (npar <= 100) cond2 = (npar > 100) & (npar <= 1000) cond3 = npar > 1000 npar[cond1] *= 0.001 npar[cond1] += 1 npar[cond2] *= 4.800e-4 npar[cond2] += 1.0520 npar[cond3] *= 4.7760e-04 npar[cond3] += 1.0544 npar[npar > 3.92] = 3.920 # Drugi etap: # Relative Electron Density Mass density # 0 0 a=0.98901, b=0 # 0.91 0.9 a=1.1538, b=-0.15000 # 2.73 3 cond1 = npar < 0.9 cond2 = npar >= 0.9 npar[cond1] *= 0.98901 npar[cond2] *= 1.1538 npar[cond2] += -0.15000 npar[npar < 0] = 0 if (self.water_phantom): log.info( "Warning! Applying WATER PHANTOM transformation to CT data. All voxel will have scaled Hounsfield number equal to 1." ) npar[:] = 1 if (v2Drow is not None): log.info( "Zeruję gęstość masy dla wszystkich 'nienteresujących voxeli' dla %d voxeli" % numpy.sum(v2Drow < 1)) npar[v2Drow < 1] = 0 # zapisuję cały wektor do pliku binarnie npar.tofile(fout) fout.close() # Zapisywanie danych do formatu vti log.info( "Zapisuję przeskalowane dane gęstości masy z CT do pliku: %s" % self.rass_data.output("approximated_ct")) grid = VolumeData.createGrid( (self.spacing[0], self.spacing[1], self.spacing[2]), (self.n[0], self.n[1], self.n[2]), (self.orig[0], self.orig[1], self.orig[2])) array = VolumeData.createFloatArray(grid) array.SetVoidArray(npar, numpy.prod(npar.shape), 1) grid.GetPointData().SetScalars(array) volume = VolumeData(grid) volume.save(self.rass_data.output("approximated_ct")) log.info( "Zapisałem przeskalowane dane gęstości masy z CT do pliku: %s" % self.rass_data.output("approximated_ct")) return npar[v2Drow > 0]
def __init__(self, data, port=None): VolumeData.__init__(self, data) self.spacing = data.GetSpacing() self.dimensions = data.GetDimensions() self.origin = data.GetOrigin()