{"id":144,"date":"2020-08-18T14:32:37","date_gmt":"2020-08-18T14:32:37","guid":{"rendered":"http:\/\/half4.xyz\/?p=144"},"modified":"2024-01-25T10:36:44","modified_gmt":"2024-01-25T10:36:44","slug":"part-1-practical-what-makes-skin-color","status":"publish","type":"post","link":"https:\/\/half4.xyz\/index.php\/2020\/08\/18\/part-1-practical-what-makes-skin-color\/","title":{"rendered":"Part 1 (Practical): What Makes Skin Color?"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Getting Going<\/h2>\n\n\n\n<p>First things, open Python IDLE 3.8 (64-bit)\u2026<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img fetchpriority=\"high\" decoding=\"async\" width=\"973\" height=\"801\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-8.png\" alt=\"\" class=\"wp-image-211\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-8.png 973w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-8-300x247.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-8-768x632.png 768w\" sizes=\"(max-width: 973px) 100vw, 973px\" \/><\/figure>\n\n\n\n<p>and create a new file\u2026<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"831\" height=\"890\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-9.png\" alt=\"\" class=\"wp-image-212\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-9.png 831w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-9-280x300.png 280w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-9-768x823.png 768w\" sizes=\"(max-width: 831px) 100vw, 831px\" \/><\/figure>\n\n\n\n<p>Save this file as <em>Skin_Reflectance_Generation.py<\/em>, or similar.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" width=\"1024\" height=\"797\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-10-1024x797.png\" alt=\"\" class=\"wp-image-213\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-10-1024x797.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-10-300x233.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-10-768x598.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-10.png 1312w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p><em>Skin_Reflectance_Generation.py<\/em><br><br>If you have a preferred IDE for authoring Python, feel free to use that. I wanted to keep this tutorial minimal, hence I\u2019m using IDLE.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chromophores and Power Curves<\/h2>\n\n\n\n<p>We now need to convert all those formulae on the previous chapter into Python code.<\/p>\n\n\n\n<p>Let us begin by first creating a class and instancing that class. This is just the boiler-plate code to get us going:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class SkinAbsorption:\n    def __init__(self):\n        print(\"init SkinAbsorption\")\n\nskinAbsorption = SkinAbsorption()<\/code><\/pre>\n\n\n\n<p><code>class SkinAbsorption:<br>\n    def __init__(self):<br>\n        print(\"init SkinAbsorption\")<\/code><\/p>\n\n\n\n<p>Running this code should result in:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"810\" height=\"61\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-11.png\" alt=\"\" class=\"wp-image-215\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-11.png 810w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-11-300x23.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-11-768x58.png 768w\" sizes=\"(max-width: 810px) 100vw, 810px\" \/><\/figure>\n\n\n\n<p>Now we need to generate the absorption coefficients. In order to go through each wavelength, we\u2019ll need a for loop. We\u2019ll put this into a new function, in our class like so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class SkinAbsorption:\n    def __init__(self):\n        print(\"init SkinAbsorption\")\n        \n    def GetReflectanceValues(self):\n        wavelengths = range(380,790,10)\n        for nm in wavelengths:\n            print(f\"{nm}\")\n\nskinAbsorption = SkinAbsorption()\nskinAbsorption.GetReflectanceValues()<\/code><\/pre>\n\n\n\n<p>We don\u2019t need a value for every nm of light wavelength, 10nm intervals will suffice. We get enough precision without the overhead of hundreds of values. Running this code will output every wavelength we\u2019re going to calculate values for.<\/p>\n\n\n\n<p>In order to calculate values, we need to pass in values for our melanin fraction (C<sub>m<\/sub>), hemoglobin fraction (C<sub>h<\/sub>) and melanin blend (\u03b2<sub>m<\/sub>). Jimenez et al [2010] helpfully provide us with suitable ranges for us:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"426\" height=\"145\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-12.png\" alt=\"\" class=\"wp-image-217\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-12.png 426w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-12-300x102.png 300w\" sizes=\"(max-width: 426px) 100vw, 426px\" \/><\/figure>\n\n\n\n<p>Its also worth noting that Jimenez et al [2010] also state we should sample cubicly to maximize texture space usage, so our sampling values should be:<br>C<sub>m<\/sub> = [0.002, 0.0135, 0.0425, 0.1, 0.185, 0.32, 0.5]<br>C<sub>h<\/sub> = [0.003, 0.02, 0.07, 0.16, 0.32]<br>Our blend value does not need to be cubic, we\u2019ll do 3 samples to keep it fast and interpolate the values in our shader later.<br>\u03b2<sub>m<\/sub> = [0.01, 0.5, 0.99]<\/p>\n\n\n\n<p>We\u2019ll extract some more for loops in functions from these, like so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class SkinAbsorption:\n    def __init__(self):\n        print(\"init SkinAbsorption\")\n\n    def Generate(self):\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                    groupByHemoglobin.append(self.GetReflectanceValues( Cm, Ch, Bm ))\n                groupByMelanin.append(groupByHemoglobin)\n            groupByBlend.append(groupByMelanin)\n        return groupByBlend\n    \n    def GetReflectanceValues(self, Cm, Ch, Bm):\n        ReflectanceValues = &#91;]\n        wavelengths = range(380,790,10)\n        for nm in wavelengths:\n            print(f\"{nm} &gt;&gt; {Cm}, {Ch}, {Bm}\")\n        return ReflectanceValues\n\nskinAbsorption = SkinAbsorption()\nreflectanceValues = skinAbsorption.Generate()<\/code><\/pre>\n\n\n\n<p>At the moment, running our code will just dump a list of wavelengths and our fractional and blend values, like so:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"836\" height=\"819\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-13.png\" alt=\"\" class=\"wp-image-219\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-13.png 836w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-13-300x294.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-13-768x752.png 768w\" sizes=\"(max-width: 836px) 100vw, 836px\" \/><\/figure>\n\n\n\n<p>Now for the math!<br>We\u2019ll replace our print line (on Line 21) with the following in order to generate our coefficients for epidermal absorption and scattering:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class SkinAbsorption:\n    def __init__(self):\n        print(\"init SkinAbsorption\")\n\n    def Generate(self):\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                    groupByHemoglobin.append(self.GetReflectanceValues( Cm, Ch, Bm ))\n                groupByMelanin.append(groupByHemoglobin)\n            groupByBlend.append(groupByMelanin)\n        return groupByBlend\n    \n    def GetReflectanceValues(self, Cm, Ch, Bm):\n        ReflectanceValues = &#91;]\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 = hemoglobin_fraction * 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 = melanin_fraction * ((melanin_type_blend * SAV_eumelanin_L) +((1 -  melanin_type_blend ) * SAV_pheomelanin_L)) + ((1 - epidermal_hemoglobin_fraction) * 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            # Index of refraction - we assume a constant value of 1.4 as it changes very little through the skin\n\n            print(f\"{SAV_eumelanin_L}, {SAV_pheomelanin_L}, {baselineSkinAbsorption_L},  {epidermis}, {scattering_epidermis}, {scattering_dermis}\")\n\n        return ReflectanceValues\n\nskinAbsorption = SkinAbsorption()\nreflectanceValues = skinAbsorption.Generate()\n    \n<\/code><\/pre>\n\n\n\n<p>Running those code should produce a lot of values! We don\u2019t have to make sense of these yet, we\u2019ll debug these later.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"662\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-14-1024x662.png\" alt=\"\" class=\"wp-image-221\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-14-1024x662.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-14-300x194.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-14-768x497.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-14.png 1308w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Extracting Graph Data to CSV<\/h2>\n\n\n\n<p>The dermis is a bit more complicated as we can\u2019t easily reconstruct the data. What we need to do is to get actual data that maps wavelengths to coefficients. <br>There is a file format, known as CSV, which stands for \u201ccomma separated values\u201d. It is simply a list of rows and column values, with each column separated by a comma and each row in on a new line. Its structure looks identical to the values we just printed in the image above, i.e.:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>columnName1,columnName2,columnName3\n234,456,789\n136,234,567\n...<\/code><code><\/code><\/pre>\n\n\n\n<p>You can upload an image, define the graph boundaries and axis, and get out CSV values as a file. It is perfect for this sort of thing.<\/p>\n\n\n\n<p>Let\u2019s graph a graph that has the data we want to sample, and load it into graphreader:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/upload.wikimedia.org\/wikipedia\/commons\/a\/a1\/Hemoglobin_extinction.png\" alt=\"\"\/><\/figure>\n\n\n\n<p>Make the blue box surround the values from 300-800 (we can remove rows we don\u2019t want after). Then start double clicking add to add points along one of the lines. Be sure to set the axis appropriately:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"821\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-15-1024x821.png\" alt=\"\" class=\"wp-image-222\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-15-1024x821.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-15-300x241.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-15-768x616.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-15.png 1168w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Then, \u201cRedraw fix-points\u201d, then \u201cGenerate curve\u201d, you will get the following values output:<\/p>\n\n\n\n<p>These values are measured in [cm<sup>-1<\/sup>M<sup>-1<\/sup>], so we need to divide these down by 100.<br>My CSV files for oxygenated and deoxygenated hemoglobin are here. Feel free to download them if you have issues with graph reader.<br>Save them into the same location as your python file as O2hb.csv and hb.csv respectively:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"279\" height=\"184\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-17.png\" alt=\"\" class=\"wp-image-226\"\/><figcaption class=\"wp-element-caption\">You should have it like this<\/figcaption><\/figure>\n\n\n\n<p>Now we can update our dermis absorption section, add this to our code between epidermis and the scattering calculation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Second layer absorption - Dermis\ngammaOxy = self.O2Hb&#91;int(nm)]\ngammaDeoxy = self.Hb&#91;int(nm)]\nA = 0.75 * gammaOxy + (1 - 0.75) * gammaDeoxy      \ndermis = Ch * (A + (( 1 - Ch) * baselineSkinAbsorption_L))<\/code><\/pre>\n\n\n\n<p>This won\u2019t work yet! We need to load our CSV data. Above the __init__ function add the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>class SkinAbsorption:\n\n    O2Hb = {}\n    Hb = {}\n    \n    def __init__(self):\n    #...<\/code><code><\/code><\/pre>\n\n\n\n<p>Then, in the __init__ function, do the following:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    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()<\/code><\/pre>\n\n\n\n<p>This will open and read the CSV files and write them to a python dictionary, where each key-value pair is:<br>{ key : value} = { wavelength : absorption coefficient }<br>In other words:<br>{ 380 : 97.58 , 390 : 100.024, \u2026}<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"661\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-18-1024x661.png\" alt=\"\" class=\"wp-image-227\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-18-1024x661.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-18-300x194.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-18-768x496.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-18-1536x992.png 1536w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-18.png 1735w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Running it, you should get this sort of garbage in your console window!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Debugging Values to CSV<\/h2>\n\n\n\n<p>The final thing to do is to check all our values are as we expect. As you can see, we\u2019re dumping a load of values to the console that look remarkably like CSV values already \u2013 this is no coincidence. <br>Instead of printing the values, we shall compress them into a string and write this out to a CSV ourselves, so we can create the debug graphs we want in Excel (or other Spreadsheet software).<\/p>\n\n\n\n<p>First, lets remove the printing and convert it to an appended string. We\u2019ll set the first line to be our column headings, and make sure we add the wavelength of light for easier reading later. Like so:<\/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\\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            # Index of refraction - we assume a constant value of 1.4 as it changes very little through the skin\n            NR = 1.4\n            CSV_Out = CSV_Out + f\"{nm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis}\\n\"\n        return CSV_Out<\/code><\/pre>\n\n\n\n<p>This will now output a CSV in our folder:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"212\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-1024x212.png\" alt=\"\" class=\"wp-image-228\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-1024x212.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-300x62.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-768x159.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19.png 1370w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"212\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-1024x212.png\" alt=\"\" class=\"wp-image-228\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-1024x212.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-300x62.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19-768x159.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-19.png 1370w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>For completeness, here is the Python code so far:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>class 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[int(splitLine[0])] = float(splitLine[1].rstrip(\"\\n\"))\n\n        O2HbLines = O2HbFile.readlines()\n        for line in O2HbLines:\n            splitLine = line.split(\",\")\n            self.O2Hb[int(splitLine[0])] = float(splitLine[1].rstrip(\"\\n\"))\n            \n        HbFile.close()\n        O2HbFile.close()\n\n    def Generate(self):\n        CSV_Out = \"Wavelength,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis\\n\"\n        groupByBlend = []\n        for Bm in [0.01,0.5,0.99]:\n            groupByMelanin = []\n            for Cm in [0.002,0.0135,0.0425,0.1,0.185,0.32,0.5]:\n                groupByHemoglobin = []\n                for Ch in [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[int(nm)]\n            gammaDeoxy = self.Hb[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            # Index of refraction - we assume a constant value of 1.4 as it changes very little through the skin\n            NR = 1.4\n            CSV_Out = CSV_Out + f\"{nm},{SAV_eumelanin_L},{SAV_pheomelanin_L},{baselineSkinAbsorption_L},{epidermis},{scattering_epidermis},{scattering_dermis},{dermis}\\n\"\n        return CSV_Out\n\nskinAbsorption = SkinAbsorption()\nreflectanceValues = skinAbsorption.Generate()<\/code><code><\/code><\/pre>\n\n\n\n<p>Now we just need to bring this into a program that can graph. For this, we\u2019ll use Excel. Open it up, and click the \u201cData\u201d tab, then \u201cImport from Text\/CSV\u201d<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"487\" height=\"151\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-20.png\" alt=\"\" class=\"wp-image-230\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-20.png 487w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-20-300x93.png 300w\" sizes=\"(max-width: 487px) 100vw, 487px\" \/><\/figure>\n\n\n\n<p>Find our CSV and bring the data in<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"761\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-21-1024x761.png\" alt=\"\" class=\"wp-image-231\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-21-1024x761.png 1024w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-21-300x223.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-21-768x571.png 768w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-21.png 1114w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"994\" height=\"876\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-22.png\" alt=\"\" class=\"wp-image-232\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-22.png 994w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-22-300x264.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-22-768x677.png 768w\" sizes=\"(max-width: 994px) 100vw, 994px\" \/><\/figure>\n\n\n\n<p>Firstly this is the graph we want to check our absorption coefficient values against (you may recognize it\u2026):<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"500\" height=\"412\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-23.png\" alt=\"\" class=\"wp-image-233\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-23.png 500w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-23-300x247.png 300w\" sizes=\"(max-width: 500px) 100vw, 500px\" \/><\/figure>\n\n\n\n<p>We\u2019ll grab these columns and plot them:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"966\" height=\"187\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-24.png\" alt=\"\" class=\"wp-image-234\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-24.png 966w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-24-300x58.png 300w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-24-768x149.png 768w\" sizes=\"(max-width: 966px) 100vw, 966px\" \/><\/figure>\n\n\n\n<p>Select the three columns, plot to a line chart. You can adjust the x-axis names if you want, as have I. You\u2019ll want to edit the Y-Axis to be logarthmic, like so:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"297\" height=\"483\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-28.png\" alt=\"\" class=\"wp-image-238\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-28.png 297w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-28-184x300.png 184w\" sizes=\"(max-width: 297px) 100vw, 297px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"310\" height=\"757\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-27.png\" alt=\"\" class=\"wp-image-237\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-27.png 310w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-27-123x300.png 123w\" sizes=\"(max-width: 310px) 100vw, 310px\" \/><\/figure>\n\n\n\n<p>Finally, format the X-axis and set it to be:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"310\" height=\"789\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-29.png\" alt=\"\" class=\"wp-image-239\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-29.png 310w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-29-118x300.png 118w\" sizes=\"(max-width: 310px) 100vw, 310px\" \/><\/figure>\n\n\n\n<p>You should now have a chart that looks like so:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"584\" height=\"306\" src=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-30.png\" alt=\"\" class=\"wp-image-240\" srcset=\"https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-30.png 584w, https:\/\/half4.xyz\/wp-content\/uploads\/2020\/07\/image-30-300x157.png 300w\" sizes=\"(max-width: 584px) 100vw, 584px\" \/><\/figure>\n\n\n\n<p>Great! So your absorption coefficients for melanin and the baseline are correct! It becomes trickier to do the epidermis and dermis values because these vary based on all the fractional and blend values \u2013 but the graph setup remains the same if you wish to debug values.<\/p>\n\n\n\n<p>In the next chapter we\u2019re going to learn how to start simulating photons through these mediums we\u2019ve now defined, using the Monte Carlo method. Buckle in!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Getting Going First things, open Python IDLE 3.8 (64-bit)\u2026 and create a new file\u2026 Save this file as Skin_Reflectance_Generation.py, or similar. Skin_Reflectance_Generation.py If you have a preferred IDE for authoring Python, feel free to use that. I wanted to keep this tutorial minimal, hence I\u2019m using IDLE. Chromophores and Power Curves We now need to convert all those formulae on the previous chapter into Python code. Let us begin by first creating a class and instancing that class. This is just the boiler-plate code to get us going: class SkinAbsorption: def __init__(self): print(&#8220;init SkinAbsorption&#8221;) Running this code should result in: Now we need to generate the absorption coefficients. In order to go through each wavelength, we\u2019ll need a for loop. We\u2019ll put this into a new function, in our class like so: We don\u2019t need a value for every nm of light wavelength, 10nm intervals will suffice. We get enough precision without the overhead of hundreds of values. Running this code will output every wavelength we\u2019re going to calculate values for. In order to calculate values, we need to pass in values for our melanin fraction (Cm), hemoglobin fraction (Ch) and melanin blend (\u03b2m). Jimenez et al [2010] helpfully provide us with suitable ranges for us: Its also worth noting that Jimenez et al [2010] also state we should sample cubicly to maximize texture space usage, so our sampling values should be:Cm = [0.002, 0.0135, 0.0425, 0.1, 0.185, 0.32, 0.5]Ch = [0.003, 0.02, 0.07, 0.16, 0.32]Our blend value does not need to be cubic, we\u2019ll do 3 samples to keep it fast and interpolate the values in our shader later.\u03b2m = [0.01, 0.5, 0.99] We\u2019ll extract some more for loops in functions from these, like so: At the moment, running our code will just dump a list of wavelengths and our fractional and blend values, like so: Now for the math!We\u2019ll replace our print line (on Line 21) with the following in order to generate our coefficients for epidermal absorption and scattering: Running those code should produce a lot of values! We don\u2019t have to make sense of these yet, we\u2019ll debug these later. Extracting Graph Data to CSV The dermis is a bit more complicated as we can\u2019t easily reconstruct the data. What we need to do is to get actual data that maps wavelengths to coefficients. There is a file format, known as CSV, which stands for \u201ccomma separated values\u201d. It is simply a list of rows and column values, with each column separated by a comma and each row in on a new line. Its structure looks identical to the values we just printed in the image above, i.e.: columnName1,columnName2,columnName3 234,456,789 136,234,567 &#8230; You can upload an image, define the graph boundaries and axis, and get out CSV values as a file. It is perfect for this sort of thing. Let\u2019s graph a graph that has the data we want to sample, and load it into graphreader: Make the blue box surround the values from 300-800 (we can remove rows we don\u2019t want after). Then start double clicking add to add points along one of the lines. Be sure to set the axis appropriately: Then, \u201cRedraw fix-points\u201d, then \u201cGenerate curve\u201d, you will get the following values output: These values are measured in [cm-1M-1], so we need to divide these down by 100.My CSV files for oxygenated and deoxygenated hemoglobin are here. Feel free to download them if you have issues with graph reader.Save them into the same location as your python file as O2hb.csv and hb.csv respectively: Now we can update our dermis absorption section, add this to our code between epidermis and the scattering calculation: This won\u2019t work yet! We need to load our CSV data. Above the __init__ function add the following: class SkinAbsorption: O2Hb = {} Hb = {} def __init__(self): #&#8230; Then, in the __init__ function, do the following: This will open and read the CSV files and write them to a python dictionary, where each key-value pair is:{ key : value} = { wavelength : absorption coefficient }In other words:{ 380 : 97.58 , 390 : 100.024, \u2026} Running it, you should get this sort of garbage in your console window! Debugging Values to CSV The final thing to do is to check all our values are as we expect. As you can see, we\u2019re dumping a load of values to the console that look remarkably like CSV values already \u2013 this is no coincidence. Instead of printing the values, we shall compress them into a string and write this out to a CSV ourselves, so we can create the debug graphs we want in Excel (or other Spreadsheet software). First, lets remove the printing and convert it to an appended string. We\u2019ll set the first line to be our column headings, and make sure we add the wavelength of light for easier reading later. Like so: This will now output a CSV in our folder: For completeness, here is the Python code so far: class SkinAbsorption: O2Hb = {} Hb = {} def __init__(self): print(&#8220;init SkinAbsorption&#8221;) HbFile = open(&#8216;hb.csv&#8217;, &#8220;r&#8221;) O2HbFile = open(&#8216;O2Hb.csv&#8217;, &#8220;r&#8221;) HbLines = HbFile.readlines() for line in HbLines: splitLine = line.split(&#8220;,&#8221;) self.Hb[int(splitLine[0])] = float(splitLine[1].rstrip(&#8220;\\n&#8221;)) O2HbLines = O2HbFile.readlines() for line in O2HbLines: splitLine = line.split(&#8220;,&#8221;) self.O2Hb[int(splitLine[0])] = float(splitLine[1].rstrip(&#8220;\\n&#8221;)) HbFile.close() O2HbFile.close() def Generate(self): CSV_Out = &#8220;Wavelength,Eumelanin,Pheomelanin,Baseline,Epidermis,ScatteringEpi,ScatteringDerm,Dermis\\n&#8221; groupByBlend = [] for Bm in [0.01,0.5,0.99]: groupByMelanin = [] for Cm in [0.002,0.0135,0.0425,0.1,0.185,0.32,0.5]: groupByHemoglobin = [] for Ch in [0.003,0.02,0.07,0.16,0.32]: values = self.GetReflectanceValues( Cm, Ch, Bm ) CSV_Out = CSV_Out + values groupByHemoglobin.append(values) groupByMelanin.append(groupByHemoglobin) groupByBlend.append(groupByMelanin) CSV_Out_File = open(&#8220;AbsorptionValues.csv&#8221;,&#8221;w+&#8221;) CSV_Out_File.write(CSV_Out) CSV_Out_File.close() return groupByBlend def GetReflectanceValues(self, Cm, Ch, Bm): CSV_Out = &#8220;&#8221; wavelengths = range(380,790,10) for nm in wavelengths: # First layer absorption &#8211; Epidermis SAV_eumelanin_L = 6.6 * pow(10,10) * pow(nm,-3.33) SAV_pheomelanin_L = 2.9 * pow(10,14) * pow(nm,-4.75) epidermal_hemoglobin_fraction = Ch * 0.25 # baseline &#8211; used in both layers baselineSkinAbsorption_L = 0.0244 + 8.54 * pow(10,-(nm-220)\/123) # Epidermis Absorption Coefficient: epidermis = Cm * ((Bm * SAV_eumelanin_L) +((1 &#8211; Bm ) *<\/p>\n","protected":false},"author":1,"featured_media":240,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[8,7],"class_list":["post-144","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\/144"}],"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=144"}],"version-history":[{"count":14,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/144\/revisions"}],"predecessor-version":[{"id":1281,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/posts\/144\/revisions\/1281"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media\/240"}],"wp:attachment":[{"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/media?parent=144"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/categories?post=144"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/half4.xyz\/index.php\/wp-json\/wp\/v2\/tags?post=144"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}