Exemple #1
0
    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))
Exemple #2
0
 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)
Exemple #3
0
    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)
Exemple #4
0
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)
Exemple #5
0
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")
Exemple #6
0
    def __init__(self, data, name, number, color=None):
        VolumeData.__init__(self, data)

        self.name = name
        self.number = number
        self.color = color
Exemple #7
0
    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]
Exemple #8
0
    def __init__(self, data, port=None):
        VolumeData.__init__(self, data)

        self.spacing = data.GetSpacing()
        self.dimensions = data.GetDimensions()
        self.origin = data.GetOrigin()