|
| 1 | +# This is an example of python code using VTS to provide inverse solution |
| 2 | +# for R(fx) to find chromophore concentrations [Hb HbO2] and power law |
| 3 | +# coefficients [A b] using fixed fx=[0 0.2]/mm and wavelengths=[650:50:1000]nm. |
| 4 | +# Scaled Monte Carlo with Nurbs forward solver provides the simulated |
| 5 | +# measured data and PointSourceSDA provides the model used during the inversion. |
| 6 | +# The optimization is performed by a python library scipy. |
| 7 | +# |
| 8 | +# Import the Operating System so we can access the files for the VTS library |
| 9 | +from pythonnet import load |
| 10 | +load('coreclr') |
| 11 | +import clr |
| 12 | +import os |
| 13 | +file = '../libraries/Vts.dll' |
| 14 | +clr.AddReference(os.path.abspath(file)) |
| 15 | +import numpy as np |
| 16 | +import plotly.graph_objects as go |
| 17 | +from Vts import * |
| 18 | +from Vts.Common import * |
| 19 | +from Vts.Extensions import * |
| 20 | +from Vts.Modeling.Optimizers import * |
| 21 | +from Vts.Modeling.ForwardSolvers import * |
| 22 | +from Vts.SpectralMapping import * |
| 23 | +from Vts.Factories import * |
| 24 | +from Vts.MonteCarlo import * |
| 25 | +from Vts.MonteCarlo.Sources import * |
| 26 | +from Vts.MonteCarlo.Tissues import * |
| 27 | +from Vts.MonteCarlo.Detectors import * |
| 28 | +from Vts.MonteCarlo.Factories import * |
| 29 | +from Vts.MonteCarlo.PhotonData import * |
| 30 | +from Vts.MonteCarlo.PostProcessing import * |
| 31 | +from System import Array, Object |
| 32 | +# Setup wavelengths in visible and NIR spectral regimes |
| 33 | +wavelengths = Array.CreateInstance(float, 8) |
| 34 | +for i in range(0, len(wavelengths)): |
| 35 | + wavelengths[i] = 650.0 + 50 * i |
| 36 | +# Setup fxs |
| 37 | +fxs = [0.0, 0.2] |
| 38 | +# Define parameters to fit [Hb,HbO2,A,b] |
| 39 | +measuredData = [28.4, 22.4, 1.2, 1.42] |
| 40 | +# Construct a scatter |
| 41 | +scattererMeasuredData = PowerLawScatterer(measuredData[2], measuredData[3]) |
| 42 | +# Setup the values for the measured data |
| 43 | +chromophoresMeasuredData = Array.CreateInstance(IChromophoreAbsorber, 2) |
| 44 | +chromophoresMeasuredData[0] = ChromophoreAbsorber(ChromophoreType.HbO2, measuredData[0]) |
| 45 | +chromophoresMeasuredData[1] = ChromophoreAbsorber(ChromophoreType.Hb, measuredData[1]) |
| 46 | +# len(opsMeasured)=8 per number of wavelengths |
| 47 | +opsMeasured = Tissue(chromophoresMeasuredData, scattererMeasuredData, "", n=1.4).GetOpticalProperties(wavelengths) |
| 48 | +# Create measurements using Nurbs-based white Monte Carlo forward solver |
| 49 | +measurementForwardSolver = NurbsForwardSolver() |
| 50 | +# len(measuredROfFx)=(#fxs)x(#wavelengths) flattened |
| 51 | +rOfFxMeasured=np.concatenate( |
| 52 | + [np.array(measurementForwardSolver.ROfFx(opsMeasured, fxs[0]), dtype=float), |
| 53 | + np.array(measurementForwardSolver.ROfFx(opsMeasured, fxs[1]), dtype=float)]) |
| 54 | +# Create a forward solver as a model function for inversion |
| 55 | +forwardSolverForInversion = PointSourceSDAForwardSolver() |
| 56 | +#forwardSolverForInversion = NurbsForwardSolver() # results improved but inverse crime! |
| 57 | + |
| 58 | +# Declare local forward reflectance function that computes reflectance |
| 59 | +# from chromophores and scatterer values |
| 60 | +# valuesSought = [Hb, HbO2, A, b] |
| 61 | +def CalculateReflectanceVsWavelengthFromChromophoreConcAndScatterer( |
| 62 | + valuesSought, wavelengths, fxs, forwardSolver): |
| 63 | +# Create a forward solve model function to solve inverse |
| 64 | + forwardSolverForInversion = PointSourceSDAForwardSolver() |
| 65 | + # Create an array of chromophore absorbers based on values |
| 66 | + chromophoresLocal = Array.CreateInstance(IChromophoreAbsorber, 2) |
| 67 | + chromophoresLocal[0] = ChromophoreAbsorber(ChromophoreType.HbO2, valuesSought[0]) |
| 68 | + chromophoresLocal[1] = ChromophoreAbsorber(ChromophoreType.Hb, valuesSought[1]) |
| 69 | + # Create a scatterr based on values |
| 70 | + scattererLocal = PowerLawScatterer(valuesSought[2], valuesSought[3]) |
| 71 | + # Compose local tissue to obtain optical properties |
| 72 | + opsLocal = Tissue(chromophoresLocal, scattererLocal, "", n=1.4).GetOpticalProperties(wavelengths) |
| 73 | + print("iter:[Hb HbO2 A b]=[%5.3f %5.3f %5.3f %5.3f]" % ( |
| 74 | + valuesSought[0], valuesSought[1], valuesSought[2],valuesSought[3])) |
| 75 | + # Compute reflectance for local absorbers |
| 76 | + modelDataLocal=np.concatenate( |
| 77 | + [np.array(forwardSolver.ROfFx(opsLocal, fxs[0]), dtype=float), |
| 78 | + np.array(forwardSolver.ROfFx(opsLocal, fxs[1]), dtype=float)]) |
| 79 | + return modelDataLocal |
| 80 | + |
| 81 | +# func for residual |
| 82 | +def residual(valuesSought, wavelengths, fxs, measuredROfFx, forwardSolver): |
| 83 | + predictedROfFx= CalculateReflectanceVsWavelengthFromChromophoreConcAndScatterer( |
| 84 | + valuesSought, wavelengths, fxs, forwardSolver) |
| 85 | + difference = Array.CreateInstance(float,len(wavelengths)*len(fxs)) |
| 86 | + for i in range(0, len(fxs)*len(wavelengths)-1): |
| 87 | + difference[i] = predictedROfFx[i] - measuredROfFx[i] |
| 88 | + return difference |
| 89 | + |
| 90 | +# Run the inversion: set up initial guess |
| 91 | +initialGuess = [18.0, 30.0, 0.8, 1.6] |
| 92 | +chromophoresInitialGuess = Array.CreateInstance(IChromophoreAbsorber, 2) |
| 93 | +chromophoresInitialGuess[0] = ChromophoreAbsorber(ChromophoreType.HbO2, initialGuess[0]) |
| 94 | +chromophoresInitialGuess[1] = ChromophoreAbsorber(ChromophoreType.Hb, initialGuess[1]) |
| 95 | +scattererInitialGuess = PowerLawScatterer(initialGuess[2], initialGuess[3]) |
| 96 | +# Compose tissue for initial guess data to obtain OPs |
| 97 | +opsInitialGuess = Tissue(chromophoresInitialGuess, scattererInitialGuess, "", n=1.4).GetOpticalProperties(wavelengths) |
| 98 | +rOfFxInitialGuess=np.concatenate( |
| 99 | + [np.array(forwardSolverForInversion.ROfFx(opsInitialGuess, fxs[0]), dtype=float), |
| 100 | + np.array(forwardSolverForInversion.ROfFx(opsInitialGuess, fxs[1]), dtype=float)]) |
| 101 | +# Run the levenberg-marquardt inversion |
| 102 | +from scipy.optimize import least_squares |
| 103 | +fit = least_squares( |
| 104 | + residual, |
| 105 | + initialGuess, |
| 106 | + ftol=1e-9, xtol=1e-9, max_nfev=10000, # max_nfev needs to be integer |
| 107 | + args=(wavelengths, fxs, rOfFxMeasured, forwardSolverForInversion), |
| 108 | + method='lm') |
| 109 | +# Calculate final reflectance from model at fit values |
| 110 | +chromophoresFit = Array.CreateInstance(IChromophoreAbsorber, 2) |
| 111 | +chromophoresFit[0] = ChromophoreAbsorber(ChromophoreType.HbO2, fit.x[0]) |
| 112 | +chromophoresFit[1] = ChromophoreAbsorber(ChromophoreType.Hb, fit.x[1]) |
| 113 | +scattererFit = PowerLawScatterer(fit.x[2], fit.x[3]) |
| 114 | +# Compose tissue for fit data to obtain OPs |
| 115 | +opsFit= Tissue(chromophoresFit, scattererFit, "", n=1.4).GetOpticalProperties(wavelengths) |
| 116 | +rOfFxFit=np.concatenate( |
| 117 | + [np.array(forwardSolverForInversion.ROfFx(opsFit, fxs[0]), dtype=float), |
| 118 | + np.array(forwardSolverForInversion.ROfFx(opsFit, fxs[1]), dtype=float)]) |
| 119 | +# plot Reflectance: flattened so have to separate |
| 120 | +chart1 = go.Figure() |
| 121 | +xLabel = "wavelength [nm]" |
| 122 | +yLabel = "R(wavelength)" |
| 123 | +wvs = [w for w in wavelengths] |
| 124 | +# plot measured data first fx first |
| 125 | +measR= [m for m in rOfFxMeasured] |
| 126 | +midpoint=len(measR) // 2 |
| 127 | +chart1.add_trace(go.Scatter(x=wvs, y=measR[:midpoint], mode='markers', name='measured data: fx1')) |
| 128 | +chart1.add_trace(go.Scatter(x=wvs, y=measR[midpoint:], mode='markers', name='measured data: fx2')) |
| 129 | +# plot initial guess data |
| 130 | +igR = [i for i in rOfFxInitialGuess] |
| 131 | +chart1.add_trace(go.Scatter(x=wvs, y=igR[:midpoint], mode='markers', name='initial guess: fx1')) |
| 132 | +chart1.add_trace(go.Scatter(x=wvs, y=igR[midpoint:], mode='markers', name='initial guess: fx2')) |
| 133 | +# plot fit: need to organize by fx |
| 134 | +convR = [f for f in rOfFxFit] |
| 135 | +chart1.add_trace(go.Scatter(x=wvs, y=convR[:midpoint], mode='lines', name='converged: fx1')) |
| 136 | +chart1.add_trace(go.Scatter(x=wvs, y=convR[midpoint:], mode='lines', name='converged: fx2')) |
| 137 | +chart1.update_layout( title="ROfFx (inverse solution for chromophore concentrations, multiple wavelengths, multiple fx)", xaxis_title=xLabel, yaxis_title=yLabel) |
| 138 | +chart1.show(renderer="browser") |
| 139 | +# plot Mus': flattened so have to separate |
| 140 | +chart2 = go.Figure() |
| 141 | +xLabel = "wavelength [nm]" |
| 142 | +yLabel = "us'(wavelength)" |
| 143 | +wvs = [w for w in wavelengths] |
| 144 | +# plot measured data |
| 145 | +scattererMeasuredDataMusp= np.zeros(len(wavelengths),dtype=float) |
| 146 | +for i in range(0, len(wavelengths)): |
| 147 | + scattererMeasuredDataMusp[i]=opsMeasured[i].Musp |
| 148 | +measMusp = [m for m in scattererMeasuredDataMusp] |
| 149 | +chart2.add_trace(go.Scatter(x=wvs, y=measMusp, mode='markers', name='measured data')) |
| 150 | +# plot initial guess data |
| 151 | +scattererInitialGuessMusp= np.zeros(len(wavelengths),dtype=float) |
| 152 | +for i in range(0, len(wavelengths)): |
| 153 | + scattererInitialGuessMusp[i]=opsInitialGuess[i].Musp |
| 154 | +igMusp = [i for i in scattererInitialGuessMusp] |
| 155 | +chart2.add_trace(go.Scatter(x=wvs, y=igMusp, mode='markers', name='initial guess')) |
| 156 | +# plot fit |
| 157 | +scattererFitMusp= np.zeros(len(wavelengths),dtype=float) |
| 158 | +for i in range(0, len(wavelengths)): |
| 159 | + scattererFitMusp[i]=opsFit[i].Musp |
| 160 | +convMusp = [f for f in scattererFitMusp] |
| 161 | +chart2.add_trace(go.Scatter(x=wvs, y=convMusp, mode='lines', name='converged')) |
| 162 | +chart2.update_layout( title="ROfFx (inverse solution for chromophore concentrations, multiple wavelengths, multiple fx)", xaxis_title=xLabel, yaxis_title=yLabel) |
| 163 | +chart2.show(renderer="browser") |
| 164 | +# output results |
| 165 | +print("Meas = [%5.3f %5.3f %5.3f %5.3f]" % ( |
| 166 | + measuredData[0], measuredData[1], measuredData[2], measuredData[3])) |
| 167 | +print("IG = [%5.3f %5.3f %5.3f %5.3f] Chi2=%5.3e" % ( |
| 168 | + initialGuess[0], initialGuess[1], initialGuess[2], initialGuess[3], |
| 169 | + np.dot(rOfFxMeasured-rOfFxInitialGuess,rOfFxMeasured-rOfFxInitialGuess))) |
| 170 | +print("Conv = [%5.3f %5.3f %5.3f %5.3f] Chi2=%5.3e" % (fit.x[0], fit.x[1], fit.x[2], fit.x[3], |
| 171 | + np.dot(rOfFxMeasured-rOfFxFit,rOfFxMeasured-rOfFxFit))) |
| 172 | +print("error = [%3.2f %3.2f %3.2f %3.2f]%%" % ( |
| 173 | + 100*abs((measuredData[0]-fit.x[0])/measuredData[0]), |
| 174 | + 100*abs((measuredData[1]-fit.x[1])/measuredData[1]), |
| 175 | + 100*abs((measuredData[2]-fit.x[2])/measuredData[2]), |
| 176 | + 100*abs((measuredData[3]-fit.x[3])/measuredData[3]))) |
0 commit comments