{"id":148,"date":"2020-08-18T14:37:17","date_gmt":"2020-08-18T14:37:17","guid":{"rendered":"http:\/\/half4.xyz\/?p=148"},"modified":"2024-01-25T11:12:08","modified_gmt":"2024-01-25T11:12:08","slug":"part-2-practical-the-monte-carlo-simulation","status":"publish","type":"post","link":"https:\/\/half4.xyz\/index.php\/2020\/08\/18\/part-2-practical-the-monte-carlo-simulation\/","title":{"rendered":"Part 2 (Practical): The Monte Carlo Simulation"},"content":{"rendered":"\n<p>We\u2019ll take what we learned in the previous part and start fleshing out our Monte Carlo (\u201cMC\u201d) simulation.<\/p>\n\n\n\n<p>First, let\u2019s import the random and math classes \u2013 we\u2019ll need them.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import random\nimport math\nclass SkinAbsorption:\n    ....<\/code><\/pre>\n\n\n\n<p>In our Python script, we\u2019ll add a new function to our class. We\u2019ll call this MonteCarlo. As its inputs, we need these parameters from our spectral work: \u03c3<sup>epidermis<\/sup><sub>a<\/sub>, \u03c3<sup>epidermis<\/sup><sub>s<\/sub>, \u03c3<sup>dermis<\/sup><sub>a<\/sub>, \u03c3<sup>dermis<\/sup><sub>s<\/sub> and \u03bb<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def MonteCarlo (self, epi_mua, epi_mus, derm_mua, derm_mus, nm):<\/code><\/pre>\n\n\n\n<p>We\u2019ll need two more utility functions. One is the Fresnel function, the second is a quick sign(x) function. Add these to the class, too:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def RFresnel(self, n1, n2, cosT1):\n    r = 0.0\n    cosT2 = 0.0\n    COSZERO = 1.0 - 1.0e-12\n    COS90D = 1 * pow(10,-6)\n    if n1 == n2: #matched boundary\n        r = 0.0\n        cosT2 = cosT1\n    elif cosT1 &gt; COSZERO:     # normal incident\n        cosT2 = ca1\n        r = (n2-n1)\/(n2+n1)\n        r *= r\n    elif cosT1 &lt; COS90D:      # very slant\n        cosT2 = 0.0\n        r = 1.0\n    else: #general\n        sinT1 = math.sqrt(1 - cosT1*cosT1)\n        sinT2 = n1 * sinT1\/n2\n        \n        if sinT2 &gt;= 1.0:\n            r = 1.0\n            cosT2 = 0.0\n        else:\n            cosT2 = math.sqrt(1 - sinT2 * sinT2)\n            cosAP = cosT1*cosT2 - sinT1*sinT2\n            cosAM = cosT1*cosT2 + sinT1*sinT2\n            sinAP = sinT1*cosT2 + cosT1*sinT2\n            sinAM = sinT1*cosT2 - cosT1*sinT2\n            r = 0.5 * sinAM * sinAM*(cosAM*cosAM+cosAP*cosAP)\/(sinAP*sinAP*cosAM*cosAM)\n    return r<\/code><\/pre>\n\n\n\n<p>The next step is to add the following values to our Monte Carlo function. These are values we need for our light transport:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># These are our Monte Carlo Light Transport Variables that don't change\nNbins = 1000\nNbinsp1 = 1001\nPI = 3.1415926\nLIGHTSPEED = exp(2.997925,10)\nALIVE = 1\nDEAD = 0\nTHRESHOLD = 0.01\nCHANCE = 0.1\nCOS90D = exp(1,-6)\nONE_MINUS_COSZERO = exp(1,-12)\nCOSZERO = 1.0 - 1.0e-12     # cosine of about 1e-6 rad\ng = 0.9\nnt = 1.4 # Index of refraction\nepidermis_thickness = 0.25<\/code><\/pre>\n\n\n\n<p>Lets now create all the variables we need to change, such as position, trajectory, bins, weight, etc.:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>x = 0.0\ny = 0.0\nz = 0.0 # photon position\n\nux = 0.0\nuy = 0.0\nuz = 0.0 # photon trajectory as cosines\n\nuxx = 0.0\nuyy = 0.0\nuzz = 0.0 # temporary values used during SPIN\n\ns = 0.0 # step sizes. s = -log(RND)\/mus &#91;cm] \ncostheta = 0.0 # cos(theta) \nsintheta = 0.0 # sin(theta) \ncospsi = 0.0 # cos(psi) \nsinpsi = 0.0 # sin(psi) \npsi = 0.0 # azimuthal angle \ni_photon = 0.0 # current photon\nW = 0.0 # photon weight \nabsorb = 0.0 # weighted deposited in a step due to absorption \nphoton_status = 0.0 # flag = ALIVE=1 or DEAD=0 \nReflBin = &#91;None] * Nbinsp1 #bin to store weights of escaped photos for reflectivity\nepi_albedo = epi_mus\/(epi_mus + epi_mua) # albedo of tissue\nderm_albedo = derm_mus\/(derm_mus + derm_mua) # albedo of tissue\nNphotons = 1000 # number of photons in simulation \nNR = Nbins # number of radial positions \nradial_size = 2.5 # maximum radial size \nr = 0.0 # radial position \ndr = radial_size\/NR; # cm, radial bin size \nir = 0 # index to radial position \nshellvolume = 0.0 # volume of shell at radial position r \nCNT = 0.0 # total count of photon weight summed over all bins \nrnd = 0.0 # assigned random value 0-1 \nu = 0.0\ntemp = 0.0 # dummy variables<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<p>And the last thing before we begin looping; some initialization:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Inits\nrandom.seed(0)\nRandomNum = random.random()\nfor i in range(NR+1):\n    ReflBin&#91;i] = 0<\/code><\/pre>\n\n\n\n<p>Now we can begin our iteration code. We\u2019ll start with a simple while loop for number of photons:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while True:\n    i_photon = i_photon + 1\n    if i_photon &gt;= Nphotons:\n        break<\/code><\/pre>\n\n\n\n<p><em>Within <\/em>this loop, we need to do some further initialization for this loop:<\/p>\n\n\n\n<p>W = 1.0<br>photon_status = ALIVE<br><br>x= 0<br>y = 0<br>z = 0<br><br>#Randomly set photon trajectory to yield an isotropic source.<br>costheta = 2.0 * random.random() &#8211; 1.0<br><br>sintheta = math.sqrt(1.0 &#8211; costheta*costheta)<br>psi = 2.0 * PI * random.random()<br>ux = sintheta * math.cos(psi)<br>uy = sintheta * math.sin(psi)<br>uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so > 0<br><br># Propagate one photon until it dies as determined by ROULETTE.<br># or if it reaches the surface again<br>it = 0<br>max_iterations = 100000 # to help avoid infinite loops in case we do something wrong<br><br># we&#8217;ll hit epidermis first, so set mua\/mus to those scattering\/absorption values<br>mua = epi_mua<br>mus = epi_mus<br>albedo = epi_albedo<br><br><\/p>\n\n\n\n<p><code>W = 1.0<br>\nphoton_status = ALIVE<\/code><code>\n<\/code><\/p>\n\n\n\n<p><code>x= 0<br>\ny = 0<br>\nz = 0<\/code><\/p>\n\n\n\n<code>\n<p>#Randomly set photon trajectory to yield an isotropic source.<br>\ncostheta = 2.0 * random.random() - 1.0<\/p>\n<p>sintheta = math.sqrt(1.0 - costheta*costheta)<br>\npsi = 2.0 * PI * random.random()<br>\nux = sintheta * math.cos(psi)<br>\nuy = sintheta * math.sin(psi)<br>\nuz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0<\/p>\n<p># Propagate one photon until it dies as determined by ROULETTE.<br>\n# or if it reaches the surface again<br>\nit = 0<br>\nmax_iterations = 100000 # to help avoid infinite loops in case we do something wrong<\/p>\n<p># we'll hit epidermis first, so set mua\/mus to those scattering\/absorption values<br>\nmua = epi_mua<br>\nmus = epi_mus<br>\nalbedo = epi_albedo<\/p>\n<\/code>\n\n\n\n<p><\/p>\n\n\n\n<p><code>W = 1.0<br>\nphoton_status = ALIVE<\/code><\/p>\n\n\n\n<p><code>W = 1.0<br>\nphoton_status = ALIVE<\/code><br>\n<\/p>\n\n\n\n<p>x= 0<br>\ny = 0<br>\nz = 0<\/p>\n\n\n\n<p>#Randomly set photon trajectory to yield an isotropic source.<br>\ncostheta = 2.0 * random.random() &#8211; 1.0<\/p>\n\n\n\n<p>sintheta = math.sqrt(1.0 &#8211; costheta*costheta)<br>\npsi = 2.0 * PI * random.random()<br>\nux = sintheta * math.cos(psi)<br>\nuy = sintheta * math.sin(psi)<br>\nuz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0<\/p>\n\n\n\n<p># Propagate one photon until it dies as determined by ROULETTE.<br>\n# or if it reaches the surface again<br>\nit = 0<br>\nmax_iterations = 100000 # to help avoid infinite loops in case we do something wrong<\/p>\n\n\n\n<p># we&#8217;ll hit epidermis first, so set mua\/mus to those scattering\/absorption values<br>\nmua = epi_mua<br>\nmus = epi_mus<br>\nalbedo = epi_albedo<\/p>\n\n\n\n<p>This will so far spawn a photon and do nothing. We now need to implement that flow chart from the Theory part; launch, hop, drop, spin, terminate.<\/p>\n\n\n\n<p>Let\u2019s make a new loop within the existing one. The first thing we have to do is add a break condition \u2013 for now, we\u2019ll put in a max iteration, but we\u2019ll also add in our terminate functionality.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>while True:            \n    it = it + 1\n    # Check Roulette\n    if (W &lt; THRESHOLD):\n        if (random.random() &lt;= CHANCE):\n            W = W \/ CHANCE\n        else:\n            photon_status = DEAD\n    if photon_status is DEAD:\n        break\n    if it &gt; max_iterations:\n        break<\/code><\/pre>\n\n\n\n<p><code>while True:<br>\n    it = it + 1<br>\n    # Check Roulette<br>\n    if (W &lt; THRESHOLD):<br>\n        if (random.random() &lt;= CHANCE):<br>\n            W = W \/ CHANCE<br>\n        else:<br>\n            photon_status = DEAD<br>\n    if photon_status is DEAD:<br>\n        break<br>\n    if it &gt; max_iterations:<br>\n        break<\/code><\/p>\n\n\n\n<p><code>while True:<br>\n    it = it + 1<br>\n    # Check Roulette<br>\n    if (W &lt; THRESHOLD):<br>\n        if (random.random() &lt;= CHANCE):<br>\n            W = W \/ CHANCE<br>\n        else:<br>\n            photon_status = DEAD<br>\n    if photon_status is DEAD:<br>\n        break<br>\n    if it &gt; max_iterations:<br>\n        break<\/code><\/p>\n\n\n\n<p><code>while True:<br>\n    it = it + 1<br>\n    # Check Roulette<br>\n    if (W &lt; THRESHOLD):<br>\n        if (random.random() &lt;= CHANCE):<br>\n            W = W \/ CHANCE<br>\n        else:<br>\n            photon_status = DEAD<br>\n    if photon_status is DEAD:<br>\n        break<br>\n    if it &gt; max_iterations:<br>\n        break<\/code><\/p>\n\n\n\n<p>Above the \u2018<em># Check Roulette<\/em>\u2018 line, we\u2019ll flesh out our hop code:<\/p>\n\n\n\n<p><em># Check Roulette<\/em><\/p>\n\n\n\n<p><em># Check Roulette<\/em><\/p>\n\n\n\n<p><em># Check Roulette<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>rnd = random.random()\nwhile rnd &lt;= 0.0: # make sure it is &gt; 0.0\n    rnd = random.random()\ns = -math.log(rnd)\/(mua + mus)\nx = x + (s * ux)\ny = y + (s * uy)\nz = z + (s * uz)<\/code><\/pre>\n\n\n\n<p><code>rnd = random.random()<br>\nwhile rnd &lt;= 0.0: # make sure it is &gt; 0.0<br>\n    rnd = random.random()<br>\ns = -math.log(rnd)\/(mua + mus)<br>\nx = x + (s * ux)<br>\ny = y + (s * uy)<br>\nz = z + (s * uz)<\/code><\/p>\n\n\n\n<p><code>rnd = random.random()<br>\nwhile rnd &lt;= 0.0: # make sure it is &gt; 0.0<br>\n    rnd = random.random()<br>\ns = -math.log(rnd)\/(mua + mus)<br>\nx = x + (s * ux)<br>\ny = y + (s * uy)<br>\nz = z + (s * uz)<\/code><\/p>\n\n\n\n<p><code>rnd = random.random()<br>\nwhile rnd &lt;= 0.0: # make sure it is &gt; 0.0<br>\n    rnd = random.random()<br>\ns = -math.log(rnd)\/(mua + mus)<br>\nx = x + (s * ux)<br>\ny = y + (s * uy)<br>\nz = z + (s * uz)<\/code><\/p>\n\n\n\n<p>On our first \u2018hop\u2019, the code is the \u2018launch\u2019 step. Immediately after this, we do a boundary check to see if the photon is escaping the skin, moving back to the boundary as needed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>if uz &lt; 0:\n    # calculate partial step to reach boundary surface\n    s1 = abs(z\/uz)\n    # move back\n    x = x - (s * ux)\n    y = y - (s * uy)\n    z = z - (s * uz)\n    # take partial step\n    x = x + (s1 * ux)\n    y = y + (s1 * uy)\n    z = z + (s1 * uz)<\/code><\/pre>\n\n\n\n<p><code>if uz &lt; 0:<br>\n    # calculate partial step to reach boundary surface<br>\n    s1 = abs(z\/uz)<br>\n    # move back<br>\n    x = x - (s * ux)<br>\n    y = y - (s * uy)<br>\n    z = z - (s * uz)<br>\n    # take partial step<br>\n    x = x + (s1 * ux)<br>\n    y = y + (s1 * uy)<br>\n    z = z + (s1 * uz)<\/code><\/p>\n\n\n\n<p><code>if uz &lt; 0:<br>\n    # calculate partial step to reach boundary surface<br>\n    s1 = abs(z\/uz)<br>\n    # move back<br>\n    x = x - (s * ux)<br>\n    y = y - (s * uy)<br>\n    z = z - (s * uz)<br>\n    # take partial step<br>\n    x = x + (s1 * ux)<br>\n    y = y + (s1 * uy)<br>\n    z = z + (s1 * uz)<\/code><\/p>\n\n\n\n<p><code>if uz &lt; 0:<br>\n    # calculate partial step to reach boundary surface<br>\n    s1 = abs(z\/uz)<br>\n    # move back<br>\n    x = x - (s * ux)<br>\n    y = y - (s * uy)<br>\n    z = z - (s * uz)<br>\n    # take partial step<br>\n    x = x + (s1 * ux)<br>\n    y = y + (s1 * uy)<br>\n    z = z + (s1 * uz)<\/code><\/p>\n\n\n\n<p>To finish of this if-statement, we need to find out how much of our photon energy escaped and how much was reflected internally. To do this, we call our Fresnel function, and bounce the photon back:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># photon is now at the surface boundary, figure out how much escaped and how much was reflected\ninternal_reflectance = self.RFresnel(1.0,nt, -uz )\n\n#Add weighted reflectance of escpaed photon to reflectance bin\nexternal_reflectance = 1 - internal_reflectance\nr = math.sqrt(x*x + y*y)\nir = (r\/dr)\nif ir &gt;= NR:\n   ir = NR  \nReflBin&#91;int(ir)] = ReflBin&#91;int(ir)] + (W * external_reflectance)\n\t\t\t\t\t\t\n# Bounce the photon back into the skin\nW = internal_reflectance * W\nuz = -uz\nx = (s-s1) * ux\ny = (s-s1) * uy\nz = (s-s1) * uz<\/code><\/pre>\n\n\n\n<p><code># photon is now at the surface boundary, figure out how much escaped and how much was reflected<br>\ninternal_reflectance = self.RFresnel(1.0,nt, -uz )<\/code><code>\n<\/code><\/p>\n\n\n\n<p><code>#Add weighted reflectance of escpaed photon to reflectance bin<br>\nexternal_reflectance = 1 - internal_reflectance<br>\nr = math.sqrt(x*x + y*y)<br>\nir = (r\/dr)<br>\nif ir &gt;= NR:<br>\n   ir = NR<br>\nReflBin[int(ir)] = ReflBin[int(ir)] + (W * external_reflectance)<\/code><\/p>\n\n\n\n<code>\n<\/code>\n\n\n\n<p><\/p>\n\n\n\n<p><code># Bounce the photon back into the skin<br>\nW = internal_reflectance * W<br>\nuz = -uz<br>\nx = (s-s1) * ux<br>\ny = (s-s1) * uy<br>\nz = (s-s1) * uz<\/code><\/p>\n\n\n\n<p><code># photon is now at the surface boundary, figure out how much escaped and how much was reflected<br>\ninternal_reflectance = self.RFresnel(1.0,nt, -uz )<\/code><\/p>\n\n\n\n<p><code># photon is now at the surface boundary, figure out how much escaped and how much was reflected<br>\ninternal_reflectance = self.RFresnel(1.0,nt, -uz )<\/code><br>\n<\/p>\n\n\n\n<p>#Add weighted reflectance of escpaed photon to reflectance bin<br>\nexternal_reflectance = 1 &#8211; internal_reflectance<br>\nr = math.sqrt(x*x + y*y)<br>\nir = (r\/dr)<br>\nif ir &gt;= NR:<br>\n   ir = NR<br>\nReflBin[int(ir)] = ReflBin[int(ir)] + (W * external_reflectance)<\/p>\n\n\n\n<p># Bounce the photon back into the skin<br>\nW = internal_reflectance * W<br>\nuz = -uz<br>\nx = (s-s1) * ux<br>\ny = (s-s1) * uy<br>\nz = (s-s1) * uz<\/p>\n\n\n\n<p>Close of the if-statement. Rather than do a full boundary check between the skin layers, we\u2019re simply going to adjust our albedo and scattering parameters. It makes the code a bit easier to maintain, but if you want more of a challenge, you can also put reflectance between the skin layers.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># check if we have passed into the second layer, or the first\nif z &lt;= epidermis_thickness:\n   mua = epi_mua\n   mus = epi_mus\n   albedo = epi_albedo\nelse:\n   mua = derm_mua\n   mus = derm_mus\n   albedo = derm_albedo<\/code><\/pre>\n\n\n\n<p><code># check if we have passed into the second layer, or the first<br>\nif z &lt;= epidermis_thickness:<br>\n   mua = epi_mua<br>\n   mus = epi_mus<br>\n   albedo = epi_albedo<br>\nelse:<br>\n   mua = derm_mua<br>\n   mus = derm_mus<br>\n   albedo = derm_albedo<\/code><\/p>\n\n\n\n<p><code># check if we have passed into the second layer, or the first<br>\nif z &lt;= epidermis_thickness:<br>\n   mua = epi_mua<br>\n   mus = epi_mus<br>\n   albedo = epi_albedo<br>\nelse:<br>\n   mua = derm_mua<br>\n   mus = derm_mus<br>\n   albedo = derm_albedo<\/code><\/p>\n\n\n\n<p><code># check if we have passed into the second layer, or the first<br>\nif z &lt;= epidermis_thickness:<br>\n   mua = epi_mua<br>\n   mus = epi_mus<br>\n   albedo = epi_albedo<br>\nelse:<br>\n   mua = derm_mua<br>\n   mus = derm_mus<br>\n   albedo = derm_albedo<\/code><\/p>\n\n\n\n<p>Drop stage now \u2013 fairly simple. We just reduce the photon weight by the albedo.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>absorb = W*(1 - albedo)\nW = W - absorb<\/code><\/pre>\n\n\n\n<p><code>absorb = W*(1 - albedo)<br>\nW = W - absorb<\/code><\/p>\n\n\n\n<p><code>absorb = W*(1 - albedo)<br>\nW = W - absorb<\/code><\/p>\n\n\n\n<p><code>absorb = W*(1 - albedo)<br>\nW = W - absorb<\/code><br>\n<\/p>\n\n\n\n<p>Straight into the Spin\u2026 :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>''' SPIN \n   Scatter photon into new trajectory defined by theta and psi.\n   Theta is specified by cos(theta), which is determined \n   based on the Henyey-Greenstein scattering function.\n   Convert theta and psi into cosines ux, uy, uz. \n'''\n# Sample for costheta \nrnd = random.random()\nif (g == 0.0):\n   costheta = 2.0*rnd - 1.0\nelse:\n   temp = (1.0 - g*g)\/(1.0 - g + 2*g*rnd)\n   costheta = (1.0 + g*g - temp*temp)\/(2.0*g)\nsintheta = math.sqrt(1.0 - costheta*costheta) \n\n# Sample psi. \npsi = 2.0*PI*random.random()\ncospsi = math.cos(psi)\nif (psi &lt; PI):\n   sinpsi = math.sqrt(1.0 - cospsi*cospsi)\nelse:\n   sinpsi = -math.sqrt(1.0 - cospsi*cospsi)\n\n# New trajectory. \nif (1 - abs(uz) &lt;= ONE_MINUS_COSZERO) :      # close to perpendicular. \n   uxx = sintheta * cospsi\n   uyy = sintheta * sinpsi\n   uzz = costheta * self.SIGN(uz)   # SIGN() is faster than division. \nelse: # usually use this option \n   temp = math.sqrt(1.0 - uz * uz)\n   uxx = sintheta * (ux * uz * cospsi - uy * sinpsi) \/ temp + ux * costheta\n   uyy = sintheta * (uy * uz * cospsi + ux * sinpsi) \/ temp + uy * costheta\n   uzz = -sintheta * cospsi * temp + uz * costheta\n\n# Update trajectory \nux = uxx\nuy = uyy\nuz = uzz<\/code><\/pre>\n\n\n\n<p><code>''' SPIN<br>\n   Scatter photon into new trajectory defined by theta and psi.<br>\n   Theta is specified by cos(theta), which is determined<br>\n   based on the Henyey-Greenstein scattering function.<br>\n   Convert theta and psi into cosines ux, uy, uz.<br>\n'''<br>\n# Sample for costheta<br>\nrnd = random.random()<br>\nif (g == 0.0):<br>\n   costheta = 2.0*rnd - 1.0<br>\nelse:<br>\n   temp = (1.0 - g*g)\/(1.0 - g + 2*g*rnd)<br>\n   costheta = (1.0 + g*g - temp*temp)\/(2.0*g)<br>\nsintheta = math.sqrt(1.0 - costheta*costheta) <\/code><code>\n<\/code><\/p>\n\n\n\n<p><code># Sample psi.<br>\npsi = 2.0*PI*random.random()<br>\ncospsi = math.cos(psi)<br>\nif (psi &lt; PI):<br>\n   sinpsi = math.sqrt(1.0 - cospsi*cospsi)<br>\nelse:<br>\n   sinpsi = -math.sqrt(1.0 - cospsi*cospsi)<\/code><\/p>\n\n\n\n<code>\n<p># New trajectory.<br>\nif (1 - abs(uz) &lt;= ONE_MINUS_COSZERO) :      # close to perpendicular.<br>\n   uxx = sintheta * cospsi<br>\n   uyy = sintheta * sinpsi<br>\n   uzz = costheta * self.SIGN(uz)   # SIGN() is faster than division.<br>\nelse: # usually use this option<br>\n   temp = math.sqrt(1.0 - uz * uz)<br>\n   uxx = sintheta * (ux * uz * cospsi - uy * sinpsi) \/ temp + ux * costheta<br>\n   uyy = sintheta * (uy * uz * cospsi + ux * sinpsi) \/ temp + uy * costheta<br>\n   uzz = -sintheta * cospsi * temp + uz * costheta<\/p>\n<\/code>\n\n\n\n<p><\/p>\n\n\n\n<p><code># Update trajectory<br>\nux = uxx<br>\nuy = uyy<br>\nuz = uzz<\/code><\/p>\n\n\n\n<p><code>''' SPIN<br>\n   Scatter photon into new trajectory defined by theta and psi.<br>\n   Theta is specified by cos(theta), which is determined<br>\n   based on the Henyey-Greenstein scattering function.<br>\n   Convert theta and psi into cosines ux, uy, uz.<br>\n'''<br>\n# Sample for costheta<br>\nrnd = random.random()<br>\nif (g == 0.0):<br>\n   costheta = 2.0*rnd - 1.0<br>\nelse:<br>\n   temp = (1.0 - g*g)\/(1.0 - g + 2*g*rnd)<br>\n   costheta = (1.0 + g*g - temp*temp)\/(2.0*g)<br>\nsintheta = math.sqrt(1.0 - costheta*costheta) <\/code><\/p>\n\n\n\n<p><code>''' SPIN<br>\n   Scatter photon into new trajectory defined by theta and psi.<br>\n   Theta is specified by cos(theta), which is determined<br>\n   based on the Henyey-Greenstein scattering function.<br>\n   Convert theta and psi into cosines ux, uy, uz.<br>\n'''<br>\n# Sample for costheta<br>\nrnd = random.random()<br>\nif (g == 0.0):<br>\n   costheta = 2.0*rnd - 1.0<br>\nelse:<br>\n   temp = (1.0 - g*g)\/(1.0 - g + 2*g*rnd)<br>\n   costheta = (1.0 + g*g - temp*temp)\/(2.0*g)<br>\nsintheta = math.sqrt(1.0 - costheta*costheta) <\/code><\/p>\n\n\n\n<p># Sample psi.<br>\npsi = 2.0*PI*random.random()<br>\ncospsi = math.cos(psi)<br>\nif (psi &lt; PI):<br>\n   sinpsi = math.sqrt(1.0 &#8211; cospsi*cospsi)<br>\nelse:<br>\n   sinpsi = -math.sqrt(1.0 &#8211; cospsi*cospsi)<\/p>\n\n\n\n<p># New trajectory.<br>\nif (1 &#8211; abs(uz) &lt;= ONE_MINUS_COSZERO) :      # close to perpendicular.<br>\n   uxx = sintheta * cospsi<br>\n   uyy = sintheta * sinpsi<br>\n   uzz = costheta * self.SIGN(uz)   # SIGN() is faster than division.<br>\nelse: # usually use this option<br>\n   temp = math.sqrt(1.0 &#8211; uz * uz)<br>\n   uxx = sintheta * (ux * uz * cospsi &#8211; uy * sinpsi) \/ temp + ux * costheta<br>\n   uyy = sintheta * (uy * uz * cospsi + ux * sinpsi) \/ temp + uy * costheta<br>\n   uzz = -sintheta * cospsi * temp + uz * costheta<\/p>\n\n\n\n<p># Update trajectory<br>\nux = uxx<br>\nuy = uyy<br>\nuz = uzz<\/p>\n\n\n\n<p>The terminate code we wrote towards the beginning should sit below all of the above, being the final stage.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Calculating Reflectance<\/h2>\n\n\n\n<p>The code should run, but nothing will happen yet. We need to sum up the reflections, as described in the previous chapter. We will add this beneath our nested while loops at the end of our Monte Carlo function:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>total_reflection = 0.0\n   for each in range(NR+1):\n      total_reflection = total_reflection + ReflBin&#91;each]\/Nphotons\n   return total_reflection<\/code><\/pre>\n\n\n\n<p><code>total_reflection = 0.0<br>\n   for each in range(NR+1):<br>\n      total_reflection = total_reflection + ReflBin[each]\/Nphotons<br>\n   return total_reflection<\/code><\/p>\n\n\n\n<p><code>total_reflection = 0.0<br>\n   for each in range(NR+1):<br>\n      total_reflection = total_reflection + ReflBin[each]\/Nphotons<br>\n   return total_reflection<\/code><\/p>\n\n\n\n<p><code>total_reflection = 0.0<br>\n   for each in range(NR+1):<br>\n      total_reflection = total_reflection + ReflBin[each]\/Nphotons<br>\n   return total_reflection<\/code><\/p>\n\n\n\n<p>Now we need up update our <em>GetReflectanceValues<\/em>() function so that it can manage the data returned. For debugging purposes, we\u2019ll put the reflectance values on the end of our CSV debug code, like so:<\/p>\n\n\n\n<p><em>GetReflectanceValues<\/em><br>\n<br>\n<em>GetReflectanceValues<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;....]\n# Scattering coefficients\nscattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)\nscattering_dermis = scattering_epidermis * 0.5\n            \nreflectance = self.MonteCarlo(epidermis, scattering_epidermis, dermis, scattering_dermis, nm)\n            \nCSV_Out = CSV_Out + f\"{nm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis},{reflectance}\\n\"\n return CSV_Out\n&#91;...]<\/code><\/pre>\n\n\n\n<p><code>[....]<br>\n# Scattering coefficients<br>\nscattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)<br>\nscattering_dermis = scattering_epidermis * 0.5<\/code><code>\n<\/code><\/p>\n\n\n\n<p><code>reflectance = self.MonteCarlo(epidermis, scattering_epidermis, dermis, scattering_dermis, nm)<\/code><\/p>\n\n\n\n<code>\n<\/code>\n\n\n\n<p><\/p>\n\n\n\n<p><code>CSV_Out = CSV_Out + f\"{nm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis},{reflectance}\\n\"<br>\n return CSV_Out<br>\n[...]<\/code><\/p>\n\n\n\n<p><code>[....]<br>\n# Scattering coefficients<br>\nscattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)<br>\nscattering_dermis = scattering_epidermis * 0.5<\/code><\/p>\n\n\n\n<p><code>[....]<br>\n# Scattering coefficients<br>\nscattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)<br>\nscattering_dermis = scattering_epidermis * 0.5<\/code><\/p>\n\n\n\n<p>reflectance = self.MonteCarlo(epidermis, scattering_epidermis, dermis, scattering_dermis, nm)<\/p>\n\n\n\n<p>CSV_Out = CSV_Out + f&#8221;{nm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis},{reflectance}\\n&#8221;<br>\n return CSV_Out<br>\n[&#8230;]<\/p>\n\n\n\n<p>Also don\u2019t forget to update the headers in <em>Generate()<\/em>\u2026<\/p>\n\n\n\n<p><em>Generate()<\/em><\/p>\n\n\n\n<p><em>Generate()<\/em><\/p>\n\n\n\n<p><em>Generate()<\/em><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def Generate(self):\n    CSV_Out = \"Wavelength,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n\"\n<\/code><\/pre>\n\n\n\n<p><code>def Generate(self):<br>\n    CSV_Out = \"Wavelength,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n\"<br>\n<\/code><\/p>\n\n\n\n<p><code>def Generate(self):<br>\n    CSV_Out = \"Wavelength,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n\"<br>\n<\/code><\/p>\n\n\n\n<p><code>def Generate(self):<br>\n    CSV_Out = \"Wavelength,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n\"<br>\n<\/code><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging<\/h2>\n\n\n\n<p>Give the code a run and import your CSV into Excel again.<strong><span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><\/strong>, it takes a good 15 mins on a Core i9-9900k. I haven\u2019t implemented multi-threading for this tutorial as it complicates what is already a heavy topic. If you\u2019re confident modifying this to be multi-threaded, feel free to do so. <\/p>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><\/strong><br>\n<span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><br>\n<em>This may take some time<\/em><\/p>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><\/strong><br>\n<span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><br>\n<em>This may take some time<\/em><\/p>\n\n\n\n<p><strong><span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><\/strong><br>\n<span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><br>\n<em>This may take some time<\/em><br>\n<br>\n<span style=\"text-decoration: underline;\"><em>This may take some time<\/em><\/span><br>\n<em>This may take some time<\/em><br>\n<br>\n<em>This may take some time<\/em><\/p>\n\n\n\n<p>The final code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import random\nimport math\n\nclass SkinAbsorption:\n\n    O2Hb = {}\n    Hb = {}\n    \n    def __init__(self):\n        print(\"init SkinAbsorption\")\n        HbFile = open('hb.csv', \"r\")\n        O2HbFile = open('O2Hb.csv', \"r\")\n\n        HbLines = HbFile.readlines()\n        for line in HbLines:\n            splitLine = line.split(\",\")\n            self.Hb&#91;int(splitLine&#91;0])] = float(splitLine&#91;1].rstrip(\"\\n\"))\n\n        O2HbLines = O2HbFile.readlines()\n        for line in O2HbLines:\n            splitLine = line.split(\",\")\n            self.O2Hb&#91;int(splitLine&#91;0])] = float(splitLine&#91;1].rstrip(\"\\n\"))\n            \n        HbFile.close()\n        O2HbFile.close()\n\n    def Generate(self):\n        CSV_Out = \"Wavelength,Cm, Ch, Bm,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n\"\n        groupByBlend = &#91;]\n        for Bm in &#91;0.01,0.5,0.99]:\n            groupByMelanin = &#91;]\n            for Cm in &#91;0.002,0.0135,0.0425,0.1,0.185,0.32,0.5]:\n                groupByHemoglobin = &#91;]\n                for Ch in &#91;0.003,0.02,0.07,0.16,0.32]:\n                    values = self.GetReflectanceValues( Cm, Ch, Bm )\n                    CSV_Out = CSV_Out + values\n                    groupByHemoglobin.append(values)\n                groupByMelanin.append(groupByHemoglobin)\n            groupByBlend.append(groupByMelanin)\n        CSV_Out_File = open(\"AbsorptionValues.csv\",\"w+\")\n        CSV_Out_File.write(CSV_Out)\n        CSV_Out_File.close() \n        return groupByBlend\n    \n    def GetReflectanceValues(self, Cm, Ch, Bm):\n        CSV_Out = \"\"\n        wavelengths = range(380,790,10)\n        for nm in wavelengths:\n            \n            # First layer absorption - Epidermis\n            SAV_eumelanin_L = 6.6 * pow(10,10) * pow(nm,-3.33)\n            SAV_pheomelanin_L = 2.9 * pow(10,14) * pow(nm,-4.75)\n            epidermal_hemoglobin_fraction = Ch * 0.25\n            \n            # baseline - used in both layers\n            baselineSkinAbsorption_L = 0.0244 + 8.54 * pow(10,-(nm-220)\/123)\n\n            # Epidermis Absorption Coefficient:          \n            epidermis = Cm * ((Bm * SAV_eumelanin_L) +((1 -  Bm ) * SAV_pheomelanin_L)) + ((1 - epidermal_hemoglobin_fraction) * baselineSkinAbsorption_L)\n            \n            # Second layer absorption - Dermis\n            gammaOxy = self.O2Hb&#91;int(nm)]\n            gammaDeoxy = self.Hb&#91;int(nm)]\n            A = 0.75 * gammaOxy + (1 - 0.75) * gammaDeoxy      \n            dermis = Ch * (A + (( 1 - Ch) * baselineSkinAbsorption_L))\n            \n            # Scattering coefficients\n            scattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)\n            scattering_dermis = scattering_epidermis * 0.5\n            \n            reflectance = self.MonteCarlo(epidermis, scattering_epidermis, dermis, scattering_dermis, nm)\n            print(f\"Done on {nm} &gt;&gt; ({Cm}, {Ch}, {Bm}) = {reflectance}\")\n            \n            CSV_Out = CSV_Out + f\"{nm},{Cm},{Ch},{Bm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis},{reflectance}\\n\"\n        return CSV_Out\n\n    def MonteCarlo (self, epi_mua, epi_mus, derm_mua, derm_mus, nm):\n        # These are our Monte Carlo Light Transport Variables that don't change\n        Nbins = 1000\n        Nbinsp1 = 1001\n        PI = 3.1415926\n        LIGHTSPEED = 2.997925 * pow(10,10)\n        ALIVE = 1\n        DEAD = 0\n        THRESHOLD = 0.01\n        CHANCE = 0.1\n        COS90D = 1 * pow(10,-6)\n        ONE_MINUS_COSZERO = 1 * pow(10,-12)\n        COSZERO = 1.0 - 1.0e-12     # cosine of about 1e-6 rad\n        g = 0.9\n        nt = 1.33 # Index of refraction\n        epidermis_thickness = 0.25\n\n        x = 0.0\n        y = 0.0\n        z = 0.0 # photon position\n\n        ux = 0.0\n        uy = 0.0\n        uz = 0.0 # photon trajectory as cosines\n\n        uxx = 0.0\n        uyy = 0.0\n        uzz = 0.0 # temporary values used during SPIN\n\n        s = 0.0 # step sizes. s = -log(RND)\/mus &#91;cm] \n        costheta = 0.0 # cos(theta) \n        sintheta = 0.0 # sin(theta) \n        cospsi = 0.0 # cos(psi) \n        sinpsi = 0.0 # sin(psi) \n        psi = 0.0 # azimuthal angle \n        i_photon = 0.0 # current photon\n        W = 0.0 # photon weight \n        absorb = 0.0 # weighted deposited in a step due to absorption \n        photon_status = 0.0 # flag = ALIVE=1 or DEAD=0 \n        ReflBin = &#91;None] * Nbinsp1 #bin to store weights of escaped photos for reflectivity\n        epi_albedo = epi_mus\/(epi_mus + epi_mua) # albedo of tissue\n        derm_albedo = derm_mus\/(derm_mus + derm_mua) # albedo of tissue\n        Nphotons = 1000 # number of photons in simulation \n        NR = Nbins # number of radial positions \n        radial_size = 2.5 # maximum radial size \n        r = 0.0 # radial position \n        dr = radial_size\/NR; # cm, radial bin size \n        ir = 0 # index to radial position \n        shellvolume = 0.0 # volume of shell at radial position r \n        CNT = 0.0 # total count of photon weight summed over all bins \n        rnd = 0.0 # assigned random value 0-1 \n        u = 0.0\n        temp = 0.0 # dummy variables\n\n        # Inits\n        random.seed(0)\n        RandomNum = random.random()\n        for i in range(NR+1):\n            ReflBin&#91;i] = 0\n\n        while True:\n            i_photon = i_photon + 1\n\n            W = 1.0\n            photon_status = ALIVE\n\n            x= 0\n            y = 0\n            z = 0\n\n            #Randomly set photon trajectory to yield an isotropic source.\n            costheta = 2.0 * random.random() - 1.0\n\n            sintheta = math.sqrt(1.0 - costheta*costheta)\n            psi = 2.0 * PI * random.random()\n            ux = sintheta * math.cos(psi)\n            uy = sintheta * math.sin(psi)\n            uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0\n\n            # Propagate one photon until it dies as determined by ROULETTE.\n            # or if it reaches the surface again\n            it = 0\n            max_iterations = 100000 # to help avoid infinite loops in case we do something wrong\n\n            # we'll hit epidermis first, so set mua\/mus to those scattering\/absorption values\n            mua = epi_mua\n            mus = epi_mus\n            albedo = epi_albedo\n            while True:            \n                it = it + 1\n                rnd = random.random()\n                while rnd &lt;= 0.0: # make sure it is &gt; 0.0\n                    rnd = random.random()\n                s = -math.log(rnd)\/(mua + mus)\n                x = x + (s * ux)\n                y = y + (s * uy)\n                z = z + (s * uz)\n\n                if uz &lt; 0:\n                    # calculate partial step to reach boundary surface\n                    s1 = abs(z\/uz)\n                    # move back\n                    x = x - (s * ux)\n                    y = y - (s * uy)\n                    z = z - (s * uz)\n                    # take partial step\n                    x = x + (s1 * ux)\n                    y = y + (s1 * uy)\n                    z = z + (s1 * uz)\n\n                    # photon is now at the surface boundary, figure out how much escaped and how much was reflected\n                    internal_reflectance = self.RFresnel(1.0,nt, -uz )\n\n                    #Add weighted reflectance of escpaed photon to reflectance bin\n                    external_reflectance = 1 - internal_reflectance\n                    r = math.sqrt(x*x + y*y)\n                    ir = (r\/dr)\n                    if ir &gt;= NR:\n                       ir = NR  \n                    ReflBin&#91;int(ir)] = ReflBin&#91;int(ir)] + (W * external_reflectance)\n                                                                    \n                    # Bounce the photon back into the skin\n                    W = internal_reflectance * W\n                    uz = -uz\n                    x = (s-s1) * ux\n                    y = (s-s1) * uy\n                    z = (s-s1) * uz\n\n                # check if we have passed into the second layer, or the first\n                if z &lt;= epidermis_thickness:\n                   mua = epi_mua\n                   mus = epi_mus\n                   albedo = epi_albedo\n                else:\n                   mua = derm_mua\n                   mus = derm_mus\n                   albedo = derm_albedo\n\n                ''' DROP '''\n                absorb = W*(1 - albedo)\n                W = W - absorb\n\n                ''' SPIN '''\n                # Sample for costheta \n                rnd = random.random()\n                if (g == 0.0):\n                   costheta = 2.0*rnd - 1.0\n                else:\n                   temp = (1.0 - g*g)\/(1.0 - g + 2*g*rnd)\n                   costheta = (1.0 + g*g - temp*temp)\/(2.0*g)\n                sintheta = math.sqrt(1.0 - costheta*costheta) \n\n                # Sample psi. \n                psi = 2.0*PI*random.random()\n                cospsi = math.cos(psi)\n                if (psi &lt; PI):\n                   sinpsi = math.sqrt(1.0 - cospsi*cospsi)\n                else:\n                   sinpsi = -math.sqrt(1.0 - cospsi*cospsi)\n\n                # New trajectory. \n                if (1 - abs(uz) &lt;= ONE_MINUS_COSZERO) :      # close to perpendicular. \n                   uxx = sintheta * cospsi\n                   uyy = sintheta * sinpsi\n                   uzz = costheta * self.SIGN(uz)   # SIGN() is faster than division. \n                else: # usually use this option \n                   temp = math.sqrt(1.0 - uz * uz)\n                   uxx = sintheta * (ux * uz * cospsi - uy * sinpsi) \/ temp + ux * costheta\n                   uyy = sintheta * (uy * uz * cospsi + ux * sinpsi) \/ temp + uy * costheta\n                   uzz = -sintheta * cospsi * temp + uz * costheta\n\n                # Update trajectory \n                ux = uxx\n                uy = uyy\n                uz = uzz\n\n            \n                # Check Roulette\n                if (W &lt; THRESHOLD):\n                    if (random.random() &lt;= CHANCE):\n                        W = W \/ CHANCE\n                    else:\n                        photon_status = DEAD\n                if photon_status is DEAD:\n                    break\n                if it &gt; max_iterations:\n                    break\n            \n            if i_photon &gt;= Nphotons:\n                break\n        total_reflection = 0.0\n        for each in range(NR+1):\n            total_reflection = total_reflection + ReflBin&#91;each]\/Nphotons\n        return total_reflection\n\n    def RFresnel(self, n1, n2, cosT1):\n        r = 0.0\n        cosT2 = 0.0\n        COSZERO = 1.0 - 1.0e-12\n        COS90D = 1 * pow(10,-6)\n        if n1 == n2: #matched boundary\n            r = 0.0\n            cosT2 = cosT1\n        elif cosT1 &gt; COSZERO:     # normal incident\n            cosT2 = ca1\n            r = (n2-n1)\/(n2+n1)\n            r *= r\n        elif cosT1 &lt; COS90D:      # very slant\n            cosT2 = 0.0\n            r = 1.0\n        else: #general\n            sinT1 = math.sqrt(1 - cosT1*cosT1)\n            sinT2 = n1 * sinT1\/n2\n            \n            if sinT2 &gt;= 1.0:\n                r = 1.0\n                cosT2 = 0.0\n            else:\n                cosT2 = math.sqrt(1 - sinT2 * sinT2)\n                cosAP = cosT1*cosT2 - sinT1*sinT2\n                cosAM = cosT1*cosT2 + sinT1*sinT2\n                sinAP = sinT1*cosT2 + cosT1*sinT2\n                sinAM = sinT1*cosT2 - cosT1*sinT2\n                r = 0.5 * sinAM * sinAM*(cosAM*cosAM+cosAP*cosAP)\/(sinAP*sinAP*cosAM*cosAM)\n        return r\n\n    def SIGN(self, x):\n        if x &gt;=0:\n            return 1\n        else:\n            return 0 \n\nskinAbsorption = SkinAbsorption()\nreflectanceValues = skinAbsorption.Generate()<\/code><\/pre>\n\n\n\n<p><code>import random<br>\nimport math<\/code><code>\n<\/code><\/p>\n\n\n\n<p><code>class SkinAbsorption:<\/code><\/p>\n\n\n\n<code>\n<p>    O2Hb = {}<br>\n    Hb = {}<\/p>\n<p>    def __init__(self):<br>\n        print(\"init SkinAbsorption\")<br>\n        HbFile = open('hb.csv', \"r\")<br>\n        O2HbFile = open('O2Hb.csv', \"r\")<\/p>\n<p>        HbLines = HbFile.readlines()<br>\n        for line in HbLines:<br>\n            splitLine = line.split(\",\")<br>\n            self.Hb[int(splitLine[0])] = float(splitLine[1].rstrip(\"\\n\"))<\/p>\n<p>        O2HbLines = O2HbFile.readlines()<br>\n        for line in O2HbLines:<br>\n            splitLine = line.split(\",\")<br>\n            self.O2Hb[int(splitLine[0])] = float(splitLine[1].rstrip(\"\\n\"))<\/p>\n<p>        HbFile.close()<br>\n        O2HbFile.close()<\/p>\n<p>    def Generate(self):<br>\n        CSV_Out = \"Wavelength,Cm, Ch, Bm,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n\"<br>\n        groupByBlend = []<br>\n        for Bm in [0.01,0.5,0.99]:<br>\n            groupByMelanin = []<br>\n            for Cm in [0.002,0.0135,0.0425,0.1,0.185,0.32,0.5]:<br>\n                groupByHemoglobin = []<br>\n                for Ch in [0.003,0.02,0.07,0.16,0.32]:<br>\n                    values = self.GetReflectanceValues( Cm, Ch, Bm )<br>\n                    CSV_Out = CSV_Out + values<br>\n                    groupByHemoglobin.append(values)<br>\n                groupByMelanin.append(groupByHemoglobin)<br>\n            groupByBlend.append(groupByMelanin)<br>\n        CSV_Out_File = open(\"AbsorptionValues.csv\",\"w+\")<br>\n        CSV_Out_File.write(CSV_Out)<br>\n        CSV_Out_File.close()<br>\n        return groupByBlend<\/p>\n<p>    def GetReflectanceValues(self, Cm, Ch, Bm):<br>\n        CSV_Out = \"\"<br>\n        wavelengths = range(380,790,10)<br>\n        for nm in wavelengths:<\/p>\n<p>            # First layer absorption - Epidermis<br>\n            SAV_eumelanin_L = 6.6 * pow(10,10) * pow(nm,-3.33)<br>\n            SAV_pheomelanin_L = 2.9 * pow(10,14) * pow(nm,-4.75)<br>\n            epidermal_hemoglobin_fraction = Ch * 0.25<\/p>\n<p>            # baseline - used in both layers<br>\n            baselineSkinAbsorption_L = 0.0244 + 8.54 * pow(10,-(nm-220)\/123)<\/p>\n<p>            # Epidermis Absorption Coefficient:<br>\n            epidermis = Cm * ((Bm * SAV_eumelanin_L) +((1 -  Bm ) * SAV_pheomelanin_L)) + ((1 - epidermal_hemoglobin_fraction) * baselineSkinAbsorption_L)<\/p>\n<p>            # Second layer absorption - Dermis<br>\n            gammaOxy = self.O2Hb[int(nm)]<br>\n            gammaDeoxy = self.Hb[int(nm)]<br>\n            A = 0.75 * gammaOxy + (1 - 0.75) * gammaDeoxy<br>\n            dermis = Ch * (A + (( 1 - Ch) * baselineSkinAbsorption_L))<\/p>\n<p>            # Scattering coefficients<br>\n            scattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)<br>\n            scattering_dermis = scattering_epidermis * 0.5<\/p>\n<p>            reflectance = self.MonteCarlo(epidermis, scattering_epidermis, dermis, scattering_dermis, nm)<br>\n            print(f\"Done on {nm} &gt;&gt; ({Cm}, {Ch}, {Bm}) = {reflectance}\")<\/p>\n<p>            CSV_Out = CSV_Out + f\"{nm},{Cm},{Ch},{Bm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis},{reflectance}\\n\"<br>\n        return CSV_Out<\/p>\n<p>    def MonteCarlo (self, epi_mua, epi_mus, derm_mua, derm_mus, nm):<br>\n        # These are our Monte Carlo Light Transport Variables that don't change<br>\n        Nbins = 1000<br>\n        Nbinsp1 = 1001<br>\n        PI = 3.1415926<br>\n        LIGHTSPEED = 2.997925 * pow(10,10)<br>\n        ALIVE = 1<br>\n        DEAD = 0<br>\n        THRESHOLD = 0.01<br>\n        CHANCE = 0.1<br>\n        COS90D = 1 * pow(10,-6)<br>\n        ONE_MINUS_COSZERO = 1 * pow(10,-12)<br>\n        COSZERO = 1.0 - 1.0e-12     # cosine of about 1e-6 rad<br>\n        g = 0.9<br>\n        nt = 1.33 # Index of refraction<br>\n        epidermis_thickness = 0.25<\/p>\n<p>        x = 0.0<br>\n        y = 0.0<br>\n        z = 0.0 # photon position<\/p>\n<p>        ux = 0.0<br>\n        uy = 0.0<br>\n        uz = 0.0 # photon trajectory as cosines<\/p>\n<p>        uxx = 0.0<br>\n        uyy = 0.0<br>\n        uzz = 0.0 # temporary values used during SPIN<\/p>\n<p>        s = 0.0 # step sizes. s = -log(RND)\/mus [cm]<br>\n        costheta = 0.0 # cos(theta)<br>\n        sintheta = 0.0 # sin(theta)<br>\n        cospsi = 0.0 # cos(psi)<br>\n        sinpsi = 0.0 # sin(psi)<br>\n        psi = 0.0 # azimuthal angle<br>\n        i_photon = 0.0 # current photon<br>\n        W = 0.0 # photon weight<br>\n        absorb = 0.0 # weighted deposited in a step due to absorption<br>\n        photon_status = 0.0 # flag = ALIVE=1 or DEAD=0<br>\n        ReflBin = [None] * Nbinsp1 #bin to store weights of escaped photos for reflectivity<br>\n        epi_albedo = epi_mus\/(epi_mus + epi_mua) # albedo of tissue<br>\n        derm_albedo = derm_mus\/(derm_mus + derm_mua) # albedo of tissue<br>\n        Nphotons = 1000 # number of photons in simulation<br>\n        NR = Nbins # number of radial positions<br>\n        radial_size = 2.5 # maximum radial size<br>\n        r = 0.0 # radial position<br>\n        dr = radial_size\/NR; # cm, radial bin size<br>\n        ir = 0 # index to radial position<br>\n        shellvolume = 0.0 # volume of shell at radial position r<br>\n        CNT = 0.0 # total count of photon weight summed over all bins<br>\n        rnd = 0.0 # assigned random value 0-1<br>\n        u = 0.0<br>\n        temp = 0.0 # dummy variables<\/p>\n<p>        # Inits<br>\n        random.seed(0)<br>\n        RandomNum = random.random()<br>\n        for i in range(NR+1):<br>\n            ReflBin[i] = 0<\/p>\n<p>        while True:<br>\n            i_photon = i_photon + 1<\/p>\n<p>            W = 1.0<br>\n            photon_status = ALIVE<\/p>\n<p>            x= 0<br>\n            y = 0<br>\n            z = 0<\/p>\n<p>            #Randomly set photon trajectory to yield an isotropic source.<br>\n            costheta = 2.0 * random.random() - 1.0<\/p>\n<p>            sintheta = math.sqrt(1.0 - costheta*costheta)<br>\n            psi = 2.0 * PI * random.random()<br>\n            ux = sintheta * math.cos(psi)<br>\n            uy = sintheta * math.sin(psi)<br>\n            uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0<\/p>\n<p>            # Propagate one photon until it dies as determined by ROULETTE.<br>\n            # or if it reaches the surface again<br>\n            it = 0<br>\n            max_iterations = 100000 # to help avoid infinite loops in case we do something wrong<\/p>\n<p>            # we'll hit epidermis first, so set mua\/mus to those scattering\/absorption values<br>\n            mua = epi_mua<br>\n            mus = epi_mus<br>\n            albedo = epi_albedo<br>\n            while True:<br>\n                it = it + 1<br>\n                rnd = random.random()<br>\n                while rnd &lt;= 0.0: # make sure it is &gt; 0.0<br>\n                    rnd = random.random()<br>\n                s = -math.log(rnd)\/(mua + mus)<br>\n                x = x + (s * ux)<br>\n                y = y + (s * uy)<br>\n                z = z + (s * uz)<\/p>\n<p>                if uz &lt; 0:<br>\n                    # calculate partial step to reach boundary surface<br>\n                    s1 = abs(z\/uz)<br>\n                    # move back<br>\n                    x = x - (s * ux)<br>\n                    y = y - (s * uy)<br>\n                    z = z - (s * uz)<br>\n                    # take partial step<br>\n                    x = x + (s1 * ux)<br>\n                    y = y + (s1 * uy)<br>\n                    z = z + (s1 * uz)<\/p>\n<p>                    # photon is now at the surface boundary, figure out how much escaped and how much was reflected<br>\n                    internal_reflectance = self.RFresnel(1.0,nt, -uz )<\/p>\n<p>                    #Add weighted reflectance of escpaed photon to reflectance bin<br>\n                    external_reflectance = 1 - internal_reflectance<br>\n                    r = math.sqrt(x*x + y*y)<br>\n                    ir = (r\/dr)<br>\n                    if ir &gt;= NR:<br>\n                       ir = NR<br>\n                    ReflBin[int(ir)] = ReflBin[int(ir)] + (W * external_reflectance)<\/p>\n<p>                    # Bounce the photon back into the skin<br>\n                    W = internal_reflectance * W<br>\n                    uz = -uz<br>\n                    x = (s-s1) * ux<br>\n                    y = (s-s1) * uy<br>\n                    z = (s-s1) * uz<\/p>\n<p>                # check if we have passed into the second layer, or the first<br>\n                if z &lt;= epidermis_thickness:<br>\n                   mua = epi_mua<br>\n                   mus = epi_mus<br>\n                   albedo = epi_albedo<br>\n                else:<br>\n                   mua = derm_mua<br>\n                   mus = derm_mus<br>\n                   albedo = derm_albedo<\/p>\n<p>                ''' DROP '''<br>\n                absorb = W*(1 - albedo)<br>\n                W = W - absorb<\/p>\n<p>                ''' SPIN '''<br>\n                # Sample for costheta<br>\n                rnd = random.random()<br>\n                if (g == 0.0):<br>\n                   costheta = 2.0*rnd - 1.0<br>\n                else:<br>\n                   temp = (1.0 - g*g)\/(1.0 - g + 2*g*rnd)<br>\n                   costheta = (1.0 + g*g - temp*temp)\/(2.0*g)<br>\n                sintheta = math.sqrt(1.0 - costheta*costheta) <\/p>\n<p>                # Sample psi.<br>\n                psi = 2.0*PI*random.random()<br>\n                cospsi = math.cos(psi)<br>\n                if (psi &lt; PI):<br>\n                   sinpsi = math.sqrt(1.0 - cospsi*cospsi)<br>\n                else:<br>\n                   sinpsi = -math.sqrt(1.0 - cospsi*cospsi)<\/p>\n<p>                # New trajectory.<br>\n                if (1 - abs(uz) &lt;= ONE_MINUS_COSZERO) :      # close to perpendicular.<br>\n                   uxx = sintheta * cospsi<br>\n                   uyy = sintheta * sinpsi<br>\n                   uzz = costheta * self.SIGN(uz)   # SIGN() is faster than division.<br>\n                else: # usually use this option<br>\n                   temp = math.sqrt(1.0 - uz * uz)<br>\n                   uxx = sintheta * (ux * uz * cospsi - uy * sinpsi) \/ temp + ux * costheta<br>\n                   uyy = sintheta * (uy * uz * cospsi + ux * sinpsi) \/ temp + uy * costheta<br>\n                   uzz = -sintheta * cospsi * temp + uz * costheta<\/p>\n<p>                # Update trajectory<br>\n                ux = uxx<br>\n                uy = uyy<br>\n                uz = uzz<\/p>\n<p>                # Check Roulette<br>\n                if (W &lt; THRESHOLD):<br>\n                    if (random.random() &lt;= CHANCE):<br>\n                        W = W \/ CHANCE<br>\n                    else:<br>\n                        photon_status = DEAD<br>\n                if photon_status is DEAD:<br>\n                    break<br>\n                if it &gt; max_iterations:<br>\n                    break<\/p>\n<p>            if i_photon &gt;= Nphotons:<br>\n                break<br>\n        total_reflection = 0.0<br>\n        for each in range(NR+1):<br>\n            total_reflection = total_reflection + ReflBin[each]\/Nphotons<br>\n        return total_reflection<\/p>\n<p>    def RFresnel(self, n1, n2, cosT1):<br>\n        r = 0.0<br>\n        cosT2 = 0.0<br>\n        COSZERO = 1.0 - 1.0e-12<br>\n        COS90D = 1 * pow(10,-6)<br>\n        if n1 == n2: #matched boundary<br>\n            r = 0.0<br>\n            cosT2 = cosT1<br>\n        elif cosT1 &gt; COSZERO:     # normal incident<br>\n            cosT2 = ca1<br>\n            r = (n2-n1)\/(n2+n1)<br>\n            r *= r<br>\n        elif cosT1 &lt; COS90D:      # very slant<br>\n            cosT2 = 0.0<br>\n            r = 1.0<br>\n        else: #general<br>\n            sinT1 = math.sqrt(1 - cosT1*cosT1)<br>\n            sinT2 = n1 * sinT1\/n2<\/p>\n<p>            if sinT2 &gt;= 1.0:<br>\n                r = 1.0<br>\n                cosT2 = 0.0<br>\n            else:<br>\n                cosT2 = math.sqrt(1 - sinT2 * sinT2)<br>\n                cosAP = cosT1*cosT2 - sinT1*sinT2<br>\n                cosAM = cosT1*cosT2 + sinT1*sinT2<br>\n                sinAP = sinT1*cosT2 + cosT1*sinT2<br>\n                sinAM = sinT1*cosT2 - cosT1*sinT2<br>\n                r = 0.5 * sinAM * sinAM*(cosAM*cosAM+cosAP*cosAP)\/(sinAP*sinAP*cosAM*cosAM)<br>\n        return r<\/p>\n<p>    def SIGN(self, x):<br>\n        if x &gt;=0:<br>\n            return 1<br>\n        else:<br>\n            return 0 <\/p>\n<\/code>\n\n\n\n<p><\/p>\n\n\n\n<p><code>skinAbsorption = SkinAbsorption()<br>\nreflectanceValues = skinAbsorption.Generate()<\/code><\/p>\n\n\n\n<p><code>import random<br>\nimport math<\/code><\/p>\n\n\n\n<p><code>import random<br>\nimport math<\/code><br>\n<\/p>\n\n\n\n<p>class SkinAbsorption:<\/p>\n\n\n\n<p>    O2Hb = {}<br>\n    Hb = {}<\/p>\n\n\n\n<p>    def __init__(self):<br>\n        print(&#8220;init SkinAbsorption&#8221;)<br>\n        HbFile = open(&#8216;hb.csv&#8217;, &#8220;r&#8221;)<br>\n        O2HbFile = open(&#8216;O2Hb.csv&#8217;, &#8220;r&#8221;)<\/p>\n\n\n\n<p>        HbLines = HbFile.readlines()<br>\n        for line in HbLines:<br>\n            splitLine = line.split(&#8220;,&#8221;)<br>\n            self.Hb[int(splitLine[0])] = float(splitLine[1].rstrip(&#8220;\\n&#8221;))<\/p>\n\n\n\n<p>        O2HbLines = O2HbFile.readlines()<br>\n        for line in O2HbLines:<br>\n            splitLine = line.split(&#8220;,&#8221;)<br>\n            self.O2Hb[int(splitLine[0])] = float(splitLine[1].rstrip(&#8220;\\n&#8221;))<\/p>\n\n\n\n<p>        HbFile.close()<br>\n        O2HbFile.close()<\/p>\n\n\n\n<p>    def Generate(self):<br>\n        CSV_Out = &#8220;Wavelength,Cm, Ch, Bm,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis,Reflectance\\n&#8221;<br>\n        groupByBlend = []<br>\n        for Bm in [0.01,0.5,0.99]:<br>\n            groupByMelanin = []<br>\n            for Cm in [0.002,0.0135,0.0425,0.1,0.185,0.32,0.5]:<br>\n                groupByHemoglobin = []<br>\n                for Ch in [0.003,0.02,0.07,0.16,0.32]:<br>\n                    values = self.GetReflectanceValues( Cm, Ch, Bm )<br>\n                    CSV_Out = CSV_Out + values<br>\n                    groupByHemoglobin.append(values)<br>\n                groupByMelanin.append(groupByHemoglobin)<br>\n            groupByBlend.append(groupByMelanin)<br>\n        CSV_Out_File = open(&#8220;AbsorptionValues.csv&#8221;,&#8221;w+&#8221;)<br>\n        CSV_Out_File.write(CSV_Out)<br>\n        CSV_Out_File.close()<br>\n        return groupByBlend<\/p>\n\n\n\n<p>    def GetReflectanceValues(self, Cm, Ch, Bm):<br>\n        CSV_Out = &#8220;&#8221;<br>\n        wavelengths = range(380,790,10)<br>\n        for nm in wavelengths:<\/p>\n\n\n\n<p>            # First layer absorption &#8211; Epidermis<br>\n            SAV_eumelanin_L = 6.6 * pow(10,10) * pow(nm,-3.33)<br>\n            SAV_pheomelanin_L = 2.9 * pow(10,14) * pow(nm,-4.75)<br>\n            epidermal_hemoglobin_fraction = Ch * 0.25<\/p>\n\n\n\n<p>            # baseline &#8211; used in both layers<br>\n            baselineSkinAbsorption_L = 0.0244 + 8.54 * pow(10,-(nm-220)\/123)<\/p>\n\n\n\n<p>            # Epidermis Absorption Coefficient:<br>\n            epidermis = Cm * ((Bm * SAV_eumelanin_L) +((1 &#8211;  Bm ) * SAV_pheomelanin_L)) + ((1 &#8211; epidermal_hemoglobin_fraction) * baselineSkinAbsorption_L)<\/p>\n\n\n\n<p>            # Second layer absorption &#8211; Dermis<br>\n            gammaOxy = self.O2Hb[int(nm)]<br>\n            gammaDeoxy = self.Hb[int(nm)]<br>\n            A = 0.75 * gammaOxy + (1 &#8211; 0.75) * gammaDeoxy<br>\n            dermis = Ch * (A + (( 1 &#8211; Ch) * baselineSkinAbsorption_L))<\/p>\n\n\n\n<p>            # Scattering coefficients<br>\n            scattering_epidermis = pow(14.74 * nm, -0.22) + 2.2 * pow(10,11) * pow(nm, -4)<br>\n            scattering_dermis = scattering_epidermis * 0.5<\/p>\n\n\n\n<p>            reflectance = self.MonteCarlo(epidermis, scattering_epidermis, dermis, scattering_dermis, nm)<br>\n            print(f&#8221;Done on {nm} &gt;&gt; ({Cm}, {Ch}, {Bm}) = {reflectance}&#8221;)<\/p>\n\n\n\n<p>            CSV_Out = CSV_Out + f&#8221;{nm},{Cm},{Ch},{Bm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis},{reflectance}\\n&#8221;<br>\n        return CSV_Out<\/p>\n\n\n\n<p>    def MonteCarlo (self, epi_mua, epi_mus, derm_mua, derm_mus, nm):<br>\n        # These are our Monte Carlo Light Transport Variables that don&#8217;t change<br>\n        Nbins = 1000<br>\n        Nbinsp1 = 1001<br>\n        PI = 3.1415926<br>\n        LIGHTSPEED = 2.997925 * pow(10,10)<br>\n        ALIVE = 1<br>\n        DEAD = 0<br>\n        THRESHOLD = 0.01<br>\n        CHANCE = 0.1<br>\n        COS90D = 1 * pow(10,-6)<br>\n        ONE_MINUS_COSZERO = 1 * pow(10,-12)<br>\n        COSZERO = 1.0 &#8211; 1.0e-12     # cosine of about 1e-6 rad<br>\n        g = 0.9<br>\n        nt = 1.33 # Index of refraction<br>\n        epidermis_thickness = 0.25<\/p>\n\n\n\n<p>        x = 0.0<br>\n        y = 0.0<br>\n        z = 0.0 # photon position<\/p>\n\n\n\n<p>        ux = 0.0<br>\n        uy = 0.0<br>\n        uz = 0.0 # photon trajectory as cosines<\/p>\n\n\n\n<p>        uxx = 0.0<br>\n        uyy = 0.0<br>\n        uzz = 0.0 # temporary values used during SPIN<\/p>\n\n\n\n<p>        s = 0.0 # step sizes. s = -log(RND)\/mus [cm]<br>\n        costheta = 0.0 # cos(theta)<br>\n        sintheta = 0.0 # sin(theta)<br>\n        cospsi = 0.0 # cos(psi)<br>\n        sinpsi = 0.0 # sin(psi)<br>\n        psi = 0.0 # azimuthal angle<br>\n        i_photon = 0.0 # current photon<br>\n        W = 0.0 # photon weight<br>\n        absorb = 0.0 # weighted deposited in a step due to absorption<br>\n        photon_status = 0.0 # flag = ALIVE=1 or DEAD=0<br>\n        ReflBin = [None] * Nbinsp1 #bin to store weights of escaped photos for reflectivity<br>\n        epi_albedo = epi_mus\/(epi_mus + epi_mua) # albedo of tissue<br>\n        derm_albedo = derm_mus\/(derm_mus + derm_mua) # albedo of tissue<br>\n        Nphotons = 1000 # number of photons in simulation<br>\n        NR = Nbins # number of radial positions<br>\n        radial_size = 2.5 # maximum radial size<br>\n        r = 0.0 # radial position<br>\n        dr = radial_size\/NR; # cm, radial bin size<br>\n        ir = 0 # index to radial position<br>\n        shellvolume = 0.0 # volume of shell at radial position r<br>\n        CNT = 0.0 # total count of photon weight summed over all bins<br>\n        rnd = 0.0 # assigned random value 0-1<br>\n        u = 0.0<br>\n        temp = 0.0 # dummy variables<\/p>\n\n\n\n<p>        # Inits<br>\n        random.seed(0)<br>\n        RandomNum = random.random()<br>\n        for i in range(NR+1):<br>\n            ReflBin[i] = 0<\/p>\n\n\n\n<p>        while True:<br>\n            i_photon = i_photon + 1<\/p>\n\n\n\n<p>            W = 1.0<br>\n            photon_status = ALIVE<\/p>\n\n\n\n<p>            x= 0<br>\n            y = 0<br>\n            z = 0<\/p>\n\n\n\n<p>            #Randomly set photon trajectory to yield an isotropic source.<br>\n            costheta = 2.0 * random.random() &#8211; 1.0<\/p>\n\n\n\n<p>            sintheta = math.sqrt(1.0 &#8211; costheta*costheta)<br>\n            psi = 2.0 * PI * random.random()<br>\n            ux = sintheta * math.cos(psi)<br>\n            uy = sintheta * math.sin(psi)<br>\n            uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0<\/p>\n\n\n\n<p>            # Propagate one photon until it dies as determined by ROULETTE.<br>\n            # or if it reaches the surface again<br>\n            it = 0<br>\n            max_iterations = 100000 # to help avoid infinite loops in case we do something wrong<\/p>\n\n\n\n<p>            # we&#8217;ll hit epidermis first, so set mua\/mus to those scattering\/absorption values<br>\n            mua = epi_mua<br>\n            mus = epi_mus<br>\n            albedo = epi_albedo<br>\n            while True:<br>\n                it = it + 1<br>\n                rnd = random.random()<br>\n                while rnd &lt;= 0.0: # make sure it is &gt; 0.0<br>\n                    rnd = random.random()<br>\n                s = -math.log(rnd)\/(mua + mus)<br>\n                x = x + (s * ux)<br>\n                y = y + (s * uy)<br>\n                z = z + (s * uz)<\/p>\n\n\n\n<p>                if uz &lt; 0:<br>\n                    # calculate partial step to reach boundary surface<br>\n                    s1 = abs(z\/uz)<br>\n                    # move back<br>\n                    x = x &#8211; (s * ux)<br>\n                    y = y &#8211; (s * uy)<br>\n                    z = z &#8211; (s * uz)<br>\n                    # take partial step<br>\n                    x = x + (s1 * ux)<br>\n                    y = y + (s1 * uy)<br>\n                    z = z + (s1 * uz)<\/p>\n\n\n\n<p>                    # photon is now at the surface boundary, figure out how much escaped and how much was reflected<br>\n                    internal_reflectance = self.RFresnel(1.0,nt, -uz )<\/p>\n\n\n\n<p>                    #Add weighted reflectance of escpaed photon to reflectance bin<br>\n                    external_reflectance = 1 &#8211; internal_reflectance<br>\n                    r = math.sqrt(x*x + y*y)<br>\n                    ir = (r\/dr)<br>\n                    if ir &gt;= NR:<br>\n                       ir = NR<br>\n                    ReflBin[int(ir)] = ReflBin[int(ir)] + (W * external_reflectance)<\/p>\n\n\n\n<p>                    # Bounce the photon back into the skin<br>\n                    W = internal_reflectance * W<br>\n                    uz = -uz<br>\n                    x = (s-s1) * ux<br>\n                    y = (s-s1) * uy<br>\n                    z = (s-s1) * uz<\/p>\n\n\n\n<p>                # check if we have passed into the second layer, or the first<br>\n                if z &lt;= epidermis_thickness:<br>\n                   mua = epi_mua<br>\n                   mus = epi_mus<br>\n                   albedo = epi_albedo<br>\n                else:<br>\n                   mua = derm_mua<br>\n                   mus = derm_mus<br>\n                   albedo = derm_albedo<\/p>\n\n\n\n<p>                &#8221;&#8217; DROP &#8221;&#8217;<br>\n                absorb = W*(1 &#8211; albedo)<br>\n                W = W &#8211; absorb<\/p>\n\n\n\n<p>                &#8221;&#8217; SPIN &#8221;&#8217;<br>\n                # Sample for costheta<br>\n                rnd = random.random()<br>\n                if (g == 0.0):<br>\n                   costheta = 2.0*rnd &#8211; 1.0<br>\n                else:<br>\n                   temp = (1.0 &#8211; g*g)\/(1.0 &#8211; g + 2*g*rnd)<br>\n                   costheta = (1.0 + g*g &#8211; temp*temp)\/(2.0*g)<br>\n                sintheta = math.sqrt(1.0 &#8211; costheta*costheta) <\/p>\n\n\n\n<p>                # Sample psi.<br>\n                psi = 2.0*PI*random.random()<br>\n                cospsi = math.cos(psi)<br>\n                if (psi &lt; PI):<br>\n                   sinpsi = math.sqrt(1.0 &#8211; cospsi*cospsi)<br>\n                else:<br>\n                   sinpsi = -math.sqrt(1.0 &#8211; cospsi*cospsi)<\/p>\n\n\n\n<p>                # New trajectory.<br>\n                if (1 &#8211; abs(uz) &lt;= ONE_MINUS_COSZERO) :      # close to perpendicular.<br>\n                   uxx = sintheta * cospsi<br>\n                   uyy = sintheta * sinpsi<br>\n                   uzz = costheta * self.SIGN(uz)   # SIGN() is faster than division.<br>\n                else: # usually use this option<br>\n                   temp = math.sqrt(1.0 &#8211; uz * uz)<br>\n                   uxx = sintheta * (ux * uz * cospsi &#8211; uy * sinpsi) \/ temp + ux * costheta<br>\n                   uyy = sintheta * (uy * uz * cospsi + ux * sinpsi) \/ temp + uy * costheta<br>\n                   uzz = -sintheta * cospsi * temp + uz * costheta<\/p>\n\n\n\n<p>                # Update trajectory<br>\n                ux = uxx<br>\n                uy = uyy<br>\n                uz = uzz<\/p>\n\n\n\n<p>                # Check Roulette<br>\n                if (W &lt; THRESHOLD):<br>\n                    if (random.random() &lt;= CHANCE):<br>\n                        W = W \/ CHANCE<br>\n                    else:<br>\n                        photon_status = DEAD<br>\n                if photon_status is DEAD:<br>\n                    break<br>\n                if it &gt; max_iterations:<br>\n                    break<\/p>\n\n\n\n<p>            if i_photon &gt;= Nphotons:<br>\n                break<br>\n        total_reflection = 0.0<br>\n        for each in range(NR+1):<br>\n            total_reflection = total_reflection + ReflBin[each]\/Nphotons<br>\n        return total_reflection<\/p>\n\n\n\n<p>    def RFresnel(self, n1, n2, cosT1):<br>\n        r = 0.0<br>\n        cosT2 = 0.0<br>\n        COSZERO = 1.0 &#8211; 1.0e-12<br>\n        COS90D = 1 * pow(10,-6)<br>\n        if n1 == n2: #matched boundary<br>\n            r = 0.0<br>\n            cosT2 = cosT1<br>\n        elif cosT1 &gt; COSZERO:     # normal incident<br>\n            cosT2 = ca1<br>\n            r = (n2-n1)\/(n2+n1)<br>\n            r *= r<br>\n        elif cosT1 &lt; COS90D:      # very slant<br>\n            cosT2 = 0.0<br>\n            r = 1.0<br>\n        else: #general<br>\n            sinT1 = math.sqrt(1 &#8211; cosT1*cosT1)<br>\n            sinT2 = n1 * sinT1\/n2<\/p>\n\n\n\n<p><a href=\"https:\/\/www.icesculpturesltd.com\/\">click to read more<\/a><\/p>\n\n\n\n<p>            if sinT2 &gt;= 1.0:<br>\n                r = 1.0<br>\n                cosT2 = 0.0<br>\n            else:<br>\n                cosT2 = math.sqrt(1 &#8211; sinT2 * sinT2)<br>\n                cosAP = cosT1*cosT2 &#8211; sinT1*sinT2<br>\n                cosAM = cosT1*cosT2 + sinT1*sinT2<br>\n                sinAP = sinT1*cosT2 + cosT1*sinT2<br>\n                sinAM = sinT1*cosT2 &#8211; cosT1*sinT2<br>\n                r = 0.5 * sinAM * sinAM*(cosAM*cosAM+cosAP*cosAP)\/(sinAP*sinAP*cosAM*cosAM)<br>\n        return r<\/p>\n\n\n\n<p>    def SIGN(self, x):<br>\n        if x &gt;=0:<br>\n            return 1<br>\n        else:<br>\n            return 0 <\/p>\n\n\n\n<p>skinAbsorption = SkinAbsorption()<br>\nreflectanceValues = skinAbsorption.Generate()<\/p>\n\n\n\n<p>Once its done, create a few charts that span the reflectrance values for 380nm-790nm at varying melanin blend and fractional values. You should see lines that follow these sorts of paths and values:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img fetchpriority=\"high\" decoding=\"async\" width=\"567\" height=\"1024\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png\" alt=\"\" class=\"wp-image-290\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png 567w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-166x300.png 166w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36.png 594w\" sizes=\"(max-width: 567px) 100vw, 567px\" \/><figcaption class=\"wp-element-caption\">Reflectance % for a variety of fractional and blend values<\/figcaption><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img fetchpriority=\"high\" decoding=\"async\" width=\"567\" height=\"1024\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png\" alt=\"\" class=\"wp-image-290\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png 567w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-166x300.png 166w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36.png 594w\" sizes=\"(max-width: 567px) 100vw, 567px\" \/><\/figure>\n\n\n\n<figcaption>Reflectance % for a variety of fractional and blend values<\/figcaption>\n\n\n\n<figure class=\"wp-block-image\"><img fetchpriority=\"high\" decoding=\"async\" width=\"567\" height=\"1024\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png\" alt=\"\" class=\"wp-image-290\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png 567w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-166x300.png 166w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36.png 594w\" sizes=\"(max-width: 567px) 100vw, 567px\" \/><\/figure>\n\n\n\n<figcaption>Reflectance % for a variety of fractional and blend values<\/figcaption>\n\n\n\n<p>And that completes our Monte Carlo simulation work. We now need to get this into a usable color.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img fetchpriority=\"high\" decoding=\"async\" width=\"567\" height=\"1024\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png\" alt=\"\" class=\"wp-image-290\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-567x1024.png 567w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36-166x300.png 166w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-36.png 594w\" sizes=\"(max-width: 567px) 100vw, 567px\" \/><\/figure>\n\n\n\n<figcaption>Reflectance % for a variety of fractional and blend values<\/figcaption>\n\n\n\n<p>And that completes our Monte Carlo simulation work. We now need to get this into a usable color.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We\u2019ll take what we learned in the previous part and start fleshing out our Monte Carlo (\u201cMC\u201d) simulation. First, let\u2019s import the random and math classes \u2013 we\u2019ll need them. In our Python script, we\u2019ll add a new function to our class. We\u2019ll call this MonteCarlo. As its inputs, we need these parameters from our spectral work: \u03c3epidermisa, \u03c3epidermiss, \u03c3dermisa, \u03c3dermiss and \u03bb We\u2019ll need two more utility functions. One is the Fresnel function, the second is a quick sign(x) function. Add these to the class, too: The next step is to add the following values to our Monte Carlo function. These are values we need for our light transport: Lets now create all the variables we need to change, such as position, trajectory, bins, weight, etc.: And the last thing before we begin looping; some initialization: Now we can begin our iteration code. We\u2019ll start with a simple while loop for number of photons: Within this loop, we need to do some further initialization for this loop: W = 1.0photon_status = ALIVE x= 0y = 0z = 0 #Randomly set photon trajectory to yield an isotropic source.costheta = 2.0 * random.random() &#8211; 1.0 sintheta = math.sqrt(1.0 &#8211; costheta*costheta)psi = 2.0 * PI * random.random()ux = sintheta * math.cos(psi)uy = sintheta * math.sin(psi)uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so > 0 # Propagate one photon until it dies as determined by ROULETTE.# or if it reaches the surface againit = 0max_iterations = 100000 # to help avoid infinite loops in case we do something wrong # we&#8217;ll hit epidermis first, so set mua\/mus to those scattering\/absorption valuesmua = epi_muamus = epi_musalbedo = epi_albedo W = 1.0 photon_status = ALIVE x= 0 y = 0 z = 0 #Randomly set photon trajectory to yield an isotropic source. costheta = 2.0 * random.random() &#8211; 1.0 sintheta = math.sqrt(1.0 &#8211; costheta*costheta) psi = 2.0 * PI * random.random() ux = sintheta * math.cos(psi) uy = sintheta * math.sin(psi) uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0 # Propagate one photon until it dies as determined by ROULETTE. # or if it reaches the surface again it = 0 max_iterations = 100000 # to help avoid infinite loops in case we do something wrong # we&#8217;ll hit epidermis first, so set mua\/mus to those scattering\/absorption values mua = epi_mua mus = epi_mus albedo = epi_albedo W = 1.0 photon_status = ALIVE W = 1.0 photon_status = ALIVE x= 0 y = 0 z = 0 #Randomly set photon trajectory to yield an isotropic source. costheta = 2.0 * random.random() &#8211; 1.0 sintheta = math.sqrt(1.0 &#8211; costheta*costheta) psi = 2.0 * PI * random.random() ux = sintheta * math.cos(psi) uy = sintheta * math.sin(psi) uz = (abs(costheta)) # on the first step we want to head down, into the tissue, so &gt; 0 # Propagate one photon until it dies as determined by ROULETTE. # or if it reaches the surface again it = 0 max_iterations = 100000 # to help avoid infinite loops in case we do something wrong # we&#8217;ll hit epidermis first, so set mua\/mus to those scattering\/absorption values mua = epi_mua mus = epi_mus albedo = epi_albedo This will so far spawn a photon and do nothing. We now need to implement that flow chart from the Theory part; launch, hop, drop, spin, terminate. Let\u2019s make a new loop within the existing one. The first thing we have to do is add a break condition \u2013 for now, we\u2019ll put in a max iteration, but we\u2019ll also add in our terminate functionality. while True: it = it + 1 # Check Roulette if (W &lt; THRESHOLD): if (random.random() &lt;= CHANCE): W = W \/ CHANCE else: photon_status = DEAD if photon_status is DEAD: break if it &gt; max_iterations: break while True: it = it + 1 # Check Roulette if (W &lt; THRESHOLD): if (random.random() &lt;= CHANCE): W = W \/ CHANCE else: photon_status = DEAD if photon_status is DEAD: break if it &gt; max_iterations: break while True: it = it + 1 # Check Roulette if (W &lt; THRESHOLD): if (random.random() &lt;= CHANCE): W = W \/ CHANCE else: photon_status = DEAD if photon_status is DEAD: break if it &gt; max_iterations: break Above the \u2018# Check Roulette\u2018 line, we\u2019ll flesh out our hop code: # Check Roulette # Check Roulette # Check Roulette rnd = random.random() while rnd &lt;= 0.0: # make sure it is &gt; 0.0 rnd = random.random() s = -math.log(rnd)\/(mua + mus) x = x + (s * ux) y = y + (s * uy) z = z + (s * uz) rnd = random.random() while rnd &lt;= 0.0: # make sure it is &gt; 0.0 rnd = random.random() s = -math.log(rnd)\/(mua + mus) x = x + (s * ux) y = y + (s * uy) z = z + (s * uz) rnd = random.random() while rnd &lt;= 0.0: # make sure it is &gt; 0.0 rnd = random.random() s = -math.log(rnd)\/(mua + mus) x = x + (s * ux) y = y + (s * uy) z = z + (s * uz) On our first \u2018hop\u2019, the code is the \u2018launch\u2019 step. Immediately after this, we do a boundary check to see if the photon is escaping the skin, moving back to the boundary as needed: if uz &lt; 0: # calculate partial step to reach boundary surface s1 = abs(z\/uz) # move back x = x &#8211; (s * ux) y = y &#8211; (s * uy) z = z &#8211; (s * uz) # take partial step x = x + (s1 * ux) y = y + (s1 * uy) z = z + (s1 * uz) if uz &lt; 0: # calculate partial step to reach boundary surface s1 = abs(z\/uz) # move back x = x &#8211; (s * ux) y = y<\/p>\n","protected":false},"author":1,"featured_media":290,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[8,7],"class_list":["post-148","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-lut","tag-skin"],"_links":{"self":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/148"}],"collection":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/comments?post=148"}],"version-history":[{"count":20,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/148\/revisions"}],"predecessor-version":[{"id":1286,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/148\/revisions\/1286"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media\/290"}],"wp:attachment":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}