skinoptics 0.0.1b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,942 @@
1
+ '''
2
+ | SkinOptics
3
+ | Copyright (C) 2024 Victor Lima
4
+
5
+ | This program is free software: you can redistribute it and/or modify
6
+ | it under the terms of the GNU General Public License as published by
7
+ | the Free Software Foundation, either version 3 of the License, or
8
+ | (at your option) any later version.
9
+
10
+ | This program is distributed in the hope that it will be useful,
11
+ | but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ | GNU General Public License for more details.
14
+
15
+ | You should have received a copy of the GNU General Public License
16
+ | along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ | Victor Lima
19
+ | victorporto\@ifsc.usp.br
20
+ | victor.lima\@ufscar.br
21
+
22
+ | Release Date:
23
+ | October 2024
24
+ | Last Modification:
25
+ | October 2024
26
+
27
+ | References:
28
+
29
+ | [HG41] Henyey & Greenstein 1941.
30
+ | Diffuse radiation in the Galaxy.
31
+ | https://doi.org/10.1086/144246
32
+
33
+ | [RM80] Reynolds & McCormick 1980.
34
+ | Approximate two-parameter phase function for light scattering.
35
+ | https://doi.org/10.1364/JOSA.70.001206
36
+
37
+ | [Bv84] Bruls & van der Leun 1984.
38
+ | Forward scattering properties of human epidermal layers.
39
+ | https://doi.org/10.1111/j.1751-1097.1984.tb04581.x
40
+
41
+ | [JAP87] Jacques, Alter & Prahl 1987.
42
+ | Angular Dependence of HeNe Laser Light Scattering by Human Dermis.
43
+ | https://omlc.org/~prahl/pubs/pdfx/jacques87b.pdf
44
+
45
+ | [Y*87] Yoon, Welch, Motamedi & van Gemert 1987.
46
+ | Development and Application of Three-Dimensional Light Distribution Model for Laser Irradiated Tissue.
47
+ | https://doi.org/10.1109/JQE.1987.1073224
48
+
49
+ | [v*89] van Gemert, Jacques, Sterenborg & Star 1989.
50
+ | Skin Optics.
51
+ | https://doi.org/10.1109/10.42108
52
+
53
+ | [CS92] Cornette & Shanks 1992.
54
+ | Physically reasonable analytic expression for the single-scattering phase function
55
+ | https://doi.org/10.1364/AO.31.003152
56
+
57
+ | [WJ92] Wang & Jacques 1992.
58
+ | Monte Carlo Modeling of Light Transport in Multi-layered Tissues in Standard C.
59
+ | https://omlc.org/software/mc/mcml/MCman.pdf
60
+
61
+ | [D03] Draine 2003.
62
+ | Scattering by Interstellar Dust Grains. I. Optical and Ultraviolet.
63
+ | https://doi.org/10.48550/arXiv.astro-ph/0304060
64
+
65
+ | [F11] Frisvad 2011.
66
+ | Importance sampling the Rayleigh phase function.
67
+ | https://doi.org/10.1364/JOSAA.28.002436
68
+
69
+ | [B*14] Bosschaart, Edelman, Aalders, van Leeuwen & Faber 2014.
70
+ | A literature review and novel theoretical approach on the optical properties of whole blood.
71
+ | https://doi.org/10.1007/s10103-013-1446-7
72
+
73
+ | [BCK22] Baes, Camps & Kapoor 2022.
74
+ | A new analytical scattering phase function for interstellar dust.
75
+ | https://doi.org/10.1051/0004-6361/202142437
76
+
77
+ | [JM23] Jacques & McCormick 2023.
78
+ | Two-term scattering phase function for photon transport to model subdiffuse reflectance
79
+ | in superficial tissues.
80
+ | https://doi.org/10.1364/BOE.476461
81
+ '''
82
+
83
+ import numpy as np
84
+ from scipy.interpolate import interp1d
85
+
86
+ from skinoptics.utils import *
87
+ from skinoptics.dataframes import *
88
+
89
+ def ptheta_R(theta):
90
+ r'''
91
+ | The Rayleigh scattering phase function.
92
+ | For details please check Frisvad 2011 [F11].
93
+
94
+ :math:`p_{R}(\theta) = \frac{3}{8}(1 + \cos^2\theta)`
95
+
96
+ :param theta: scattering angle [degrees]
97
+ :type theta: float or np.ndarray
98
+
99
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
100
+ '''
101
+
102
+ return (3/8)*(1 + np.cos(theta*np.pi/180)**2)
103
+
104
+ def ptheta_HG(theta, g):
105
+ r'''
106
+ | The Henyey-Greenstein scattering phase function.
107
+ | For details please check Henyey & Greenstein 1941 [HG41].
108
+
109
+ :math:`p_{HG}(\theta, g) = \frac{1}{2}\frac{1 - g^2}{(1 + g^2 - 2g \cos \theta )^{3/2}}`
110
+
111
+ In this particular model :math:`g` is the anisotropy factor.
112
+
113
+ :param theta: scattering angle [degrees]
114
+ :type theta: float or np.ndarray
115
+
116
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
117
+ :type g: float
118
+
119
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
120
+ '''
121
+
122
+ if g < -1 or g > 1:
123
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
124
+ raise Exception(msg)
125
+
126
+ return (1/2)*(1 - g**2)/(1 + g**2 - 2*g*np.cos(theta*np.pi/180))**(3/2)
127
+
128
+ def ptheta_HGIT(theta, g, gamma):
129
+ r'''
130
+ | The Henyey-Greenstein scattering phase function with an isotropic term.
131
+ | For details please check Jacques, Alter & Prahl 1987 [JAP87] and Yoon et al. [Y*87].
132
+
133
+ :math:`p_{HGIT}(\theta, g, \gamma) = \frac{1}{2}\gamma + (1-\gamma) \mbox{ } p_{HG}(\theta, g)`
134
+
135
+ In this model :math:`g` is NOT the anisotropy factor.
136
+
137
+ :param theta: scattering angle [degrees]
138
+ :type theta: float or np.ndarray
139
+
140
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
141
+ :type g: float
142
+
143
+ :param gamma: relative weight of the isotropic term component [-] (must be in the range [0, 1])
144
+ :type gamma: float
145
+
146
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
147
+ '''
148
+
149
+ if g < -1 or g > 1:
150
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
151
+ raise Exception(msg)
152
+ if gamma < 0 or gamma > 1:
153
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
154
+ raise Exception(msg)
155
+
156
+ return (1/2)*gamma + (1 - gamma)*ptheta_HG(theta = theta, g = g)
157
+
158
+ def ptheta_TTHG(theta, g1, g2, gamma):
159
+ r'''
160
+ | The two-term Henyey-Greenstein scattering phase function.
161
+ | For details please check Baes, Camps & Kapoor 2022 [BCK22].
162
+
163
+ :math:`p_{TTHG}(\theta, g_1, g_2, \gamma) = \gamma \mbox{ } p_{HG}(\theta, g_1) + (1 - \gamma) \mbox{ } p_{HG}(\theta, g_2)`
164
+
165
+ :math:`g_1` characterises the shape and the strength of the forward scattering peak
166
+
167
+ :math:`g_2` characterises the shape and the strength of the backward scattering peak
168
+
169
+ :param theta: scattering angle [degrees]
170
+ :type theta: float or np.ndarray
171
+
172
+ :param g1: parameter :math:`g_1` [-] (must be in the range [0, 1])
173
+ :type g1: float
174
+
175
+ :param g2: parameter :math:`g_2` [-] (must be in the range [-1, 0])
176
+ :type g2: float
177
+
178
+ :param gamma: relative weight of the forward scattering component [-] (must be in the range [0, 1])
179
+ :type gamma: float
180
+
181
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
182
+ '''
183
+
184
+ if g1 < 0 or g1 > 1:
185
+ msg = 'The input g1 = {} is out of the range [0, 1].'.format(g1)
186
+ raise Exception(msg)
187
+ if g2 < -1 or g2 > 0:
188
+ msg = 'The input g2 = {} is out of the range [-1, 0].'.format(g2)
189
+ raise Exception(msg)
190
+ if gamma < 0 or gamma > 1:
191
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
192
+ raise Exception(msg)
193
+
194
+ return gamma*ptheta_HG(theta = theta, g = g1) + (1 - gamma)*ptheta_HG(theta = theta, g = g2)
195
+
196
+ def ptheta_RM(theta, g, alpha):
197
+ r'''
198
+ | The Reynolds-McCormick scattering phase function.
199
+ | For details please check Reynolds & McCormick 1980 [RM80] and Jacques & McCormick 2023 [JM23].
200
+
201
+ :math:`p_{RM}(\theta, g, \alpha) = 2 \frac{\alpha g}{(1 + g)^{2\alpha} - (1 - g)^{2\alpha}}\frac{(1 - g^2)^{2\alpha}}{(1 + g^2 - 2g\cos\theta)^{\alpha + 1}}`
202
+
203
+
204
+ | For :math:`\alpha = 1/2` it reduces to the Henyey-Greenstein scattering phase function.
205
+ | For :math:`\alpha = 1` it reduces to the Ultraspherical-2 scattering phase function.
206
+
207
+ | In this model :math:`g` is the anisotropy factor only when :math:`\alpha = 1/2`.
208
+
209
+ :param theta: scattering angle [degrees]
210
+ :type theta: float or np.ndarray
211
+
212
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
213
+ :type g: float
214
+
215
+ :param alpha: parameter :math:`\alpha` [-] (must be greater than -0.5)
216
+ :type alpha: float
217
+
218
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
219
+ '''
220
+
221
+ if g < -1 or g > 1:
222
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
223
+ raise Exception(msg)
224
+ if alpha <= -1/2:
225
+ msg = 'The input alpha = {} is not greater than -1/2.'.format(alpha)
226
+ raise Exception(msg)
227
+
228
+ return 2*(alpha*g)/((1 + g)**(2*alpha) - (1 - g)**(2*alpha))*((1 - g**2)**(2*alpha))/((1 + g**2 - 2*g*np.cos(theta*np.pi/180))**(alpha + 1))
229
+
230
+ def ptheta_TTRM(theta, g1, g2, alpha1, alpha2, gamma):
231
+ r'''
232
+ | The two-term Reynolds-McCormick scattering phase function.
233
+ | For details please check Reynolds & McCormick 1980 [RM80] and Jacques & McCormick 2023 [JM23].
234
+
235
+ :math:`p_{TTRM}(\theta, g_1, g_2, \alpha_1, \alpha_2, \gamma) = \gamma \mbox{ } p_{RM}(\theta, g_1, \alpha_1) + (1 - \gamma) \mbox{ } p_{RM}(\theta, g_2, \alpha_2)`
236
+
237
+ :math:`g_1` characterises the shape and the strength of the forward scattering peak
238
+
239
+ :math:`g_2` characterises the shape and the strength of the backward scattering peak
240
+
241
+ :param theta: scattering angle [degrees]
242
+ :type theta: float or np.ndarray
243
+
244
+ :param g1: parameter :math:`g_1` [-] (must be in the range [0, 1])
245
+ :type g1: float
246
+
247
+ :param g2: parameter :math:`g_2` [-] (must be in the range [-1, 0])
248
+ :type g2: float
249
+
250
+ :param alpha1: parameter :math:`\alpha_1` [-] (must be greater than -1/2)
251
+ :type alpha1: float
252
+
253
+ :param alpha2: parameter :math:`\alpha_2` [-] (must be greater than -1/2)
254
+ :type alpha2: float
255
+
256
+ :param gamma: relative weight of the forward scattering component [-] (must be in the range [0, 1])
257
+ :type gamma: float
258
+
259
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
260
+ '''
261
+
262
+ if g1 < 0 or g1 > 1:
263
+ msg = 'The input g1 = {} is out of the range [0, 1].'.format(g1)
264
+ raise Exception(msg)
265
+ if g2 < -1 or g2 > 0:
266
+ msg = 'The input g2 = {} is out of the range [-1, 0].'.format(g2)
267
+ raise Exception(msg)
268
+ if alpha1 <= -1/2:
269
+ msg = 'The input alpha1 = {} is not greater than -1/2.'.format(alpha1)
270
+ raise Exception(msg)
271
+ if alpha2 <= -1/2:
272
+ msg = 'The input alpha2 = {} is not greater than -1/2.'.format(alpha2)
273
+ raise Exception(msg)
274
+ if gamma < 0 or gamma > 1:
275
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
276
+ raise Exception(msg)
277
+
278
+ return gamma*ptheta_RM(theta = theta, g = g1, alpha = alpha1) + (1 - gamma)*ptheta_RM(theta = theta, g = g2, alpha = alpha2)
279
+
280
+ def ptheta_CS(theta, g):
281
+ r'''
282
+ | The Cornette-Shanks scattering phase function.
283
+ | For details please check Cornette & Shanks 1992 [CS92].
284
+
285
+ :math:`p_{CS}(\theta, g) = \frac{3}{2}\frac{1 + \cos^2\theta}{2 + g^2} \mbox{ } p_{HG}(\theta, g)`
286
+
287
+ | In this model :math:`g` is NOT the anisotropy factor.
288
+
289
+ :param theta: scattering angle [degrees]
290
+ :type theta: float or np.ndarray
291
+
292
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
293
+ :type g: float
294
+
295
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
296
+ '''
297
+
298
+ if g < -1 or g > 1:
299
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
300
+ raise Exception(msg)
301
+
302
+ return (3/2)*(1 + np.cos(theta*np.pi/180.)**2)/(2 + g**2)*ptheta_HG(theta = theta, g = g)
303
+
304
+ def ptheta_D(theta, g, alpha):
305
+ r'''
306
+ | The Draine scattering phase function.
307
+ | For details please check Draine 2003 [D03].
308
+
309
+ :math:`p_{D}(\theta, g, \alpha) = 3\frac{1 + \alpha \cos^2\theta}{3 + \alpha (1 + 2g^2)} \mbox{ } p_{HG}(\theta, g)`
310
+
311
+ | For :math:`\alpha = 1` and :math:`g = 0` it reduces to the Rayleigh phase function.
312
+ | For :math:`\alpha = 0` it reduces to the Henyey-Greenstein scattering phase function.
313
+ | For :math:`\alpha = 1` it reduces to the Cornette-Shanks scattering phase function
314
+
315
+ | In this model :math:`g` is NOT the anisotropy factor.
316
+
317
+ :param theta: scattering angle [degrees]
318
+ :type theta: float or np.ndarray
319
+
320
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
321
+ :type g: float
322
+
323
+ :param alpha: parameter :math:`\alpha` [-]
324
+ :type alpha: float
325
+
326
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
327
+ '''
328
+
329
+ if g < -1 or g > 1:
330
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
331
+ raise Exception(msg)
332
+
333
+ return 3*(1 + alpha*np.cos(theta*np.pi/180.)**2)/(3 + alpha*(1 + 2*g**2))*ptheta_HG(theta = theta, g = g)
334
+
335
+ def ptheta_U2(theta, g):
336
+ r'''
337
+ | The Ultraspherical-2 scattering phase function.
338
+ | For details please check Baes, Camps & Kapoor 2022 [BCK22].
339
+
340
+ :math:`p_{U2}(\theta, g) = \frac{1}{2}\frac{(1 - g^2)^2}{(1 + g^2 - 2g \cos \theta)^2}`
341
+
342
+ | In this model :math:`g` is NOT the anisotropy factor.
343
+
344
+ :param theta: scattering angle [degrees]
345
+ :type theta: float or np.ndarray
346
+
347
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
348
+ :type g: float
349
+
350
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
351
+ '''
352
+
353
+ if g < -1 or g > 1:
354
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
355
+ raise Exception(msg)
356
+
357
+ return (1/2)*(1 - g**2)**2/(1 + g**2 - 2*g*np.cos(theta*np.pi/180.))**2
358
+
359
+ def ptheta_TTU2(theta, g1, g2, gamma):
360
+ r'''
361
+ | The two-term Ultraspherical-2 scattering phase function.
362
+ | For details please check Baes, Camps & Kapoor 2022 [BCK22].
363
+
364
+ :math:`p_{TTU2}(\theta, g_1, g_2, \gamma) = \gamma \mbox{ } p_{U2}(\theta, g_1) + (1 - \gamma) \mbox{ } p_{U2}(\theta, g_2)`
365
+
366
+ :math:`g_1` characterises the shape and the strength of the forward scattering peak
367
+
368
+ :math:`g_2` characterises the shape and the strength of the backward scattering peak
369
+
370
+ :param theta: scattering angle [degrees]
371
+ :type theta: float or np.ndarray
372
+
373
+ :param g1: parameter :math:`g_1` [-] (must be in the range [0, 1])
374
+ :type g1: float
375
+
376
+ :param g2: parameter :math:`g_2` [-] (must be in the range [-1, 0])
377
+ :type g2: float
378
+
379
+ :param gamma: relative weight of the forward scattering component [-] (must be in the range [0, 1])
380
+ :type gamma: float
381
+
382
+ :return: - **ptheta** (*float or np.ndarray*) – scattering phase function [-]
383
+ '''
384
+
385
+ if g1 < 0 or g1 > 1:
386
+ msg = 'The input g1 = {} is out of the range [0, 1].'.format(g1)
387
+ raise Exception(msg)
388
+ if g2 < -1 or g2 > 0:
389
+ msg = 'The input g2 = {} is out of the range [-1, 0].'.format(g2)
390
+ raise Exception(msg)
391
+ if gamma < 0 or gamma > 1:
392
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
393
+ raise Exception(msg)
394
+
395
+ return gamma*ptheta_U2(theta = theta, g = g1) + (1 - gamma)*ptheta_U2(theta = theta, g = g2)
396
+
397
+ def theta_R_from_RND(n_RND = int(1E6)):
398
+ r'''
399
+ | The scattering angle distribution as a function of a set of random numbers uniformly
400
+ | distributed over the interval [0, 1), assuming the Rayleigh scattering phase function.
401
+ | For details please check section 3.B from Frisvad 2011 [F11].
402
+
403
+ | :math:`\theta_{R} = \mbox{arccos}(\sqrt[3]{u + v} + \sqrt[3]{u - v})`
404
+ | with
405
+ | :math:`u = -2(2 \chi - 1)`
406
+ | :math:`v = \sqrt{4(2 \chi - 1)^2 + 1}`
407
+ | in which :math:`\chi` is a random number in the interval [0, 1)
408
+
409
+ :param n_RND: number of random numbers [-] (default to int(1E6))
410
+ :type n_RND: int
411
+
412
+ :return: - **theta** (*np.ndarray*) – scattering angle [degrees]
413
+ '''
414
+
415
+ xi = np.random.rand(n_RND)
416
+ two_times_xi_minus_one = 2*xi - 1
417
+ term1 = -2*two_times_xi_minus_one
418
+ term2 = np.sqrt(4*two_times_xi_minus_one**2 + 1)
419
+
420
+ return np.arccos(np.cbrt(term1 + term2) + np.cbrt(term1 - term2))*180/np.pi
421
+
422
+
423
+ def theta_HG_from_RND(g, n_RND = int(1E6)):
424
+ r'''
425
+ | The scattering angle distribution as a function of the anisotropy factor and a set of random
426
+ | numbers uniformly distributed over the interval [0, 1), assuming the Henyey-Greenstein
427
+ | scattering phase function.
428
+ | For details please check section 3.5 from Wang & Jacques 1992 [WJ92].
429
+
430
+ :math:`\theta_{HG} =
431
+ \left \{ \begin{matrix}
432
+ \mbox{arccos}(2 \chi - 1) , & \mbox{if } g = 0 \\
433
+ \mbox{arccos}\left\{\frac{1}{2g} \left[1 + g^2 - \left(\frac{1 - g^2}{1 - g + 2g \chi}\right)^2\right]\right\}, & \mbox{if } g \ne 0
434
+ \end{matrix} \right.`
435
+
436
+ in which :math:`\chi` is a random number in the interval [0, 1)
437
+
438
+ In this particular model :math:`g` is the anisotropy factor.
439
+
440
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
441
+ :type g: float
442
+
443
+ :param n_RND: number of random numbers [-] (default to int(1E6))
444
+ :type n_RND: int
445
+
446
+ :return: - **theta** (*np.ndarray*) – scattering angle [degrees]
447
+ '''
448
+
449
+ if g < -1 or g > 1:
450
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
451
+ raise Exception(msg)
452
+
453
+ xi = np.random.rand(n_RND)
454
+ if g == 0:
455
+ theta_HG = np.arccos(2*xi - 1)*180./np.pi
456
+ else:
457
+ theta_HG = np.arccos(1/(2*g)*(1 + g**2 - ((1 - g**2)/(1 - g + 2*g*xi))**2))*180./np.pi
458
+
459
+ return theta_HG
460
+
461
+ def theta_U2_from_RND(g, n_RND = int(1E6)):
462
+ r'''
463
+ | The scattering angle distribution as a function of the g parameter and a set of random
464
+ | numbers uniformly distributed over the interval [0, 1), assuming the Ultraspherical-2
465
+ | scattering phase function.
466
+ | For details please check section 4.4.2 from Baes, Camps & Kapoor 2022 [BCK22].
467
+
468
+ :math:`\theta_{U2} = arccos\left[\frac{(1 + g)^2 - 2 \chi (1 + g^2)}{(1 + g)^2 - 4g \chi}\right]`
469
+
470
+ in which :math:`\chi` is a random number in the interval [0, 1)
471
+
472
+ | In this model :math:`g` is NOT the anisotropy factor.
473
+
474
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
475
+ :type g: float
476
+
477
+ :param n_RND: number of random numbers [-] (default to int(1E6))
478
+ :type n_RND: int
479
+
480
+ :return: - **theta** (*np.ndarray*) – scattering angle [degrees]
481
+ '''
482
+
483
+ xi = np.random.rand(n_RND)
484
+
485
+ return np.arccos(((1 + g)**2 - 2*xi*(1 + g**2))/((1 + g)**2 - 4*g*xi))*180./np.pi
486
+
487
+ def costheta_HGIT(g, gamma):
488
+ r'''
489
+ | The anisotropy factor as a function of the parameters g and gamma, assuming the
490
+ | Henyey-Greenstein scattering phase function with an isotropic term.
491
+ | For details please check Jacques, Alter & Prahl 1987 [JAP87] and Yoon et al. [Y*87].
492
+
493
+ :math:`\langle \cos\theta \rangle_{HGIT}(g, \gamma) = (1 - \gamma) \mbox{ } g`
494
+
495
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
496
+ :type g: float
497
+
498
+ :param gamma: fraction of isotropic term contribution [-] (must be in the range [0, 1])
499
+ :type gamma: float
500
+
501
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
502
+ '''
503
+
504
+ if isinstance(g, np.ndarray) == True:
505
+ if np.any(g < -1) or np.any(g > 1):
506
+ msg = 'At least one element in the input g is out of the range [-1, 1].'
507
+ raise Exception(msg)
508
+ elif isinstance(g, (int, float)) == True:
509
+ if g < -1 or g > 1:
510
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
511
+ raise Exception(msg)
512
+ else:
513
+ msg = 'The input g must be int, float or np.ndarray.'
514
+ raise Exception(msg)
515
+
516
+ if isinstance(gamma, np.ndarray) == True:
517
+ if np.any(gamma < 0) or np.any(gamma > 1):
518
+ msg = 'At least one element in the input gamma is out of the range [0, 1].'
519
+ raise Exception(msg)
520
+ elif isinstance(gamma, (int, float)) == True:
521
+ if gamma < 0 or gamma > 1:
522
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
523
+ raise Exception(msg)
524
+ else:
525
+ msg = 'The input gamma must be int, float or np.ndarray.'
526
+ raise Exception(msg)
527
+
528
+ return (1 - gamma)*g
529
+
530
+ def costheta_TTHG(g1, g2, gamma):
531
+ r'''
532
+ | The anisotropy factor as a function of the parameters g1, g2 and gamma, assuming the
533
+ | two-term Henyey-Greenstein scattering phase function.
534
+ | For details please check Baes, Camps & Kapoor 2022 [BCK22].
535
+
536
+ :math:`\langle \cos\theta \rangle_{TTHG}(g_1, g_2, \gamma) = \gamma \mbox{ } g_1 + (1 - \gamma) \mbox { } g_2`
537
+
538
+ :math:`g_1` characterises the shape and the strength of the forward scattering peak
539
+
540
+ :math:`g_2` characterises the shape and the strength of the backward scattering peak
541
+
542
+ :param g1: parameter :math:`g_1` [-] (must be in the range [0, 1])
543
+ :type g1: float
544
+
545
+ :param g2: parameter :math:`g_2` [-] (must be in the range [-1, 0])
546
+ :type g2: float
547
+
548
+ :param gamma: relative weight of the forward scattering component [-] (must be in the range [0, 1])
549
+ :type gamma: float
550
+
551
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
552
+ '''
553
+
554
+ if isinstance(g1, np.ndarray) == True:
555
+ if np.any(g1 < 0) or np.any(g1 > 1):
556
+ msg = 'At least one element in the input g1 is out of the range [0, 1].'
557
+ raise Exception(msg)
558
+ elif isinstance(g1, (int, float)) == True:
559
+ if g1 < 0 or g1 > 1:
560
+ msg = 'The input g1 = {} is out of the range [0, 1].'.format(g1)
561
+ raise Exception(msg)
562
+ else:
563
+ msg = 'The input g1 must be int, float or np.ndarray.'
564
+ raise Exception(msg)
565
+
566
+ if isinstance(g2, np.ndarray) == True:
567
+ if np.any(g2 < -1) or np.any(g2 > 0):
568
+ msg = 'At least one element in the input g2 is out of the range [-1, 0].'
569
+ raise Exception(msg)
570
+ elif isinstance(g2, (int, float)) == True:
571
+ if g2 < -1 or g2 > 0:
572
+ msg = 'The input g2 = {} is out of the range [-1, 0].'.format(g2)
573
+ raise Exception(msg)
574
+ else:
575
+ msg = 'The input g2 must be int, float or np.ndarray.'
576
+ raise Exception(msg)
577
+
578
+ if isinstance(gamma, np.ndarray) == True:
579
+ if np.any(gamma < 0) or np.any(gamma > 1):
580
+ msg = 'At least one element in the input gamma is out of the range [0, 1].'
581
+ raise Exception(msg)
582
+ elif isinstance(gamma, (int, float)) == True:
583
+ if gamma < 0 or gamma > 1:
584
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
585
+ raise Exception(msg)
586
+ else:
587
+ msg = 'The input gamma must be int, float or np.ndarray.'
588
+ raise Exception(msg)
589
+
590
+ return gamma*g1 + (1 - gamma)*g2
591
+
592
+ def costheta_RM(g, alpha):
593
+ r'''
594
+ | The anisotropy factor as a function of the parameters g and alpha, assuming the
595
+ | Reynolds-McCormick scattering phase function.
596
+ | For details please check Reynolds & McCormick 1980 [RM80] and Jacques & McCormick 2023 [JM23].
597
+
598
+ | :math:`\langle \cos\theta \rangle_{RM}(g, \alpha) = \frac{2 \alpha g L - (1+g^2)}{2g(\alpha - 1)}`
599
+ | with
600
+ | :math:`L = \frac{(1+g)^{2\alpha} + (1-g)^{2\alpha}}{(1+g)^{2\alpha} - (1-g)^{2\alpha}}`
601
+
602
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
603
+ :type g: float
604
+
605
+ :param alpha: parameter :math:`\alpha` [-] (must be greater than -0.5)
606
+ :float alpha: float or np.ndarray
607
+
608
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
609
+ '''
610
+
611
+ if isinstance(g, np.ndarray) == True:
612
+ if np.any(g < -1) or np.any(g > 1):
613
+ msg = 'At least one element in the input g is out of the range [-1, 1].'
614
+ raise Exception(msg)
615
+ elif isinstance(g, (int, float)) == True:
616
+ if g < -1 or g > 1:
617
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
618
+ raise Exception(msg)
619
+ else:
620
+ msg = 'The input g must be int, float or np.ndarray.'
621
+ raise Exception(msg)
622
+
623
+ if isinstance(alpha, np.ndarray) == True:
624
+ if np.any(alpha <= -1/2):
625
+ msg = 'At least one element in the input alpha is not greater than -1/2.'
626
+ raise Exception(msg)
627
+ costheta = np.zeros((len(alpha), 5))
628
+ for i in range(len(alpha)):
629
+ if i == 1:
630
+ if isinstance(g, np.ndarray) == True:
631
+ costheta[i] = costheta_U2(g[i])
632
+ else:
633
+ costheta[i] = costheta_U2(g)
634
+ else:
635
+ if isinstance(g, np.ndarray) == True:
636
+ one_plus_g = 1 + g[i]
637
+ one_minus_g = 1 - g[i]
638
+ two_times_alpha = 2*alpha[i]
639
+ L = (one_plus_g**two_times_alpha + one_minus_g**two_times_alpha)/(one_plus_g**two_times_alpha - one_minus_g**two_times_alpha)
640
+ costheta[i] = (2*alpha[i]*g[i]*L - (1 + g[i]**2))/(2*g[i]*(alpha[i] - 1))
641
+ else:
642
+ one_plus_g = 1 + g
643
+ one_minus_g = 1 - g
644
+ two_times_alpha = 2*alpha
645
+ L = (one_plus_g**two_times_alpha + one_minus_g**two_times_alpha)/(one_plus_g**two_times_alpha - one_minus_g**two_times_alpha)
646
+ costheta[i] = (2*alpha[i]*g*L - (1 + g**2))/(2*g*(alpha[i] - 1))
647
+ elif isinstance(alpha, (int, float)) == True:
648
+ if alpha <= -1/2:
649
+ msg = 'The input alpha = {} is not greater than -1/2.'.format(alpha)
650
+ raise Exception(msg)
651
+ if alpha == 1:
652
+ costheta = costheta_U2(g)
653
+ else:
654
+ one_plus_g = 1 + g
655
+ one_minus_g = 1 - g
656
+ two_times_alpha = 2*alpha
657
+ L = (one_plus_g**two_times_alpha + one_minus_g**two_times_alpha)/(one_plus_g**two_times_alpha - one_minus_g**two_times_alpha)
658
+ costheta = (2*alpha*g*L - (1 + g**2))/(2*g*(alpha - 1))
659
+ else:
660
+ msg = 'The input alpha must be int, float or np.ndarray.'
661
+ raise Exception(msg)
662
+
663
+ return costheta
664
+
665
+ def costheta_TTRM(g1, g2, alpha1, alpha2, gamma):
666
+ r'''
667
+ | The anisotropy factor as a function of the parameters g1, g2, alpha1, alpha2 and gamma,
668
+ | assuming the two-term Reynolds-McCormick scattering phase function.
669
+ | For details please check Reynolds & McCormick 1980 [RM80] and Jacques & McCormick 2023 [JM23].
670
+
671
+ :math:`\langle \cos\theta \rangle_{TTRM}(g_1, g_2, \alpha_1, \alpha_2, \gamma) = \gamma \mbox{ } \langle \cos\theta \rangle_{RM}(g_1, \alpha_1) + (1 - \gamma) \mbox{ } \langle \cos\theta \rangle_{RM}(g_2, \alpha_2)`
672
+
673
+ :math:`g_1` characterises the shape and the strength of the forward scattering peak
674
+
675
+ :math:`g_2` characterises the shape and the strength of the backward scattering peak
676
+
677
+ :param g1: parameter :math:`g_1` [-] (must be in the range [0, 1])
678
+ :type g1: float
679
+
680
+ :param g2: parameter :math:`g_2` [-] (must be in the range [-1, 0])
681
+ :type g2: float
682
+
683
+ :param alpha1: parameter :math:`\alpha_1` [-] (must be greater than -0.5)
684
+ :type alpha1: float
685
+
686
+ :param alpha2: parameter :math:`\alpha_2` [-] (must be greater than -0.5)
687
+ :type alpha2: float
688
+
689
+ :param gamma: relative weight of the forward scattering component [-] (must be in the range [0, 1])
690
+ :type gamma: float
691
+
692
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
693
+ '''
694
+
695
+ if isinstance(g1, np.ndarray) == True:
696
+ if np.any(g1 < 0) or np.any(g1 > 1):
697
+ msg = 'At least one element in the input g1 is out of the range [0, 1].'
698
+ raise Exception(msg)
699
+ elif isinstance(g1, (int, float)) == True:
700
+ if g1 < 0 or g1 > 1:
701
+ msg = 'The input g1 = {} is out of the range [0, 1].'.format(g1)
702
+ raise Exception(msg)
703
+ else:
704
+ msg = 'The input g1 must be int, float or np.ndarray.'
705
+ raise Exception(msg)
706
+
707
+ if isinstance(g2, np.ndarray) == True:
708
+ if np.any(g2 < -1) or np.any(g2 > 0):
709
+ msg = 'At least one element in the input g2 is out of the range [-1, 0].'
710
+ raise Exception(msg)
711
+ elif isinstance(g2, (int, float)) == True:
712
+ if g2 < -1 or g2 > 0:
713
+ msg = 'The input g2 = {} is out of the range [-1, 0].'.format(g2)
714
+ raise Exception(msg)
715
+ else:
716
+ msg = 'The input g2 must be int, float or np.ndarray.'
717
+ raise Exception(msg)
718
+
719
+ if isinstance(alpha1, np.ndarray) == True:
720
+ if np.any(alpha1 <= 1/2):
721
+ msg = 'At least one element in the input alpha1 is not greater than 1/2.'
722
+ raise Exception(msg)
723
+ elif isinstance(alpha1, (int, float)) == True:
724
+ if alpha1 <= 1/2:
725
+ msg = 'The input alpha1 = {} is not greater than 1/2.'.format(alpha1)
726
+ raise Exception(msg)
727
+ else:
728
+ msg = 'The input alpha1 must be int, float or np.ndarray.'
729
+ raise Exception(msg)
730
+
731
+ if isinstance(alpha2, np.ndarray) == True:
732
+ if np.any(alpha2 <= 1/2):
733
+ msg = 'At least one element in the input alpha2 is not greater than 1/2.'
734
+ raise Exception(msg)
735
+ elif isinstance(alpha2, (int, float)) == True:
736
+ if alpha2 <= 1/2:
737
+ msg = 'The input alpha2 = {} is not greater than 1/2.'.format(alpha2)
738
+ raise Exception(msg)
739
+ else:
740
+ msg = 'The input alpha2 must be int, float or np.ndarray.'
741
+ raise Exception(msg)
742
+
743
+ if isinstance(gamma, np.ndarray) == True:
744
+ if np.any(gamma < 0) or np.any(gamma > 1):
745
+ msg = 'At least one element in the input gamma is out of the range [0, 1].'
746
+ raise Exception(msg)
747
+ elif isinstance(gamma, (int, float)) == True:
748
+ if gamma < 0 or gamma > 1:
749
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
750
+ raise Exception(msg)
751
+ else:
752
+ msg = 'The input gamma must be int, float or np.ndarray.'
753
+ raise Exception(msg)
754
+
755
+ return gamma*costheta_RM(g = g1, alpha = alpha1) + (1 - gamma)*costheta_RM(g = g2, alpha = alpha2)
756
+
757
+ def costheta_CS(g):
758
+ r'''
759
+ | The anisotropy factor as a function of the parameter g, assuming the Cornette-Shanks
760
+ | scattering phase function.
761
+ | For details please check Cornette & Shanks 1992 [CS92].
762
+
763
+ :math:`\langle \cos\theta \rangle_{CS}(g) = g\frac{3(4 + g^2)}{5(2 + g^2)}`
764
+
765
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
766
+ :type g: float
767
+
768
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
769
+ '''
770
+
771
+ if isinstance(g, np.ndarray) == True:
772
+ if np.any(g < -1) or np.any(g > 1):
773
+ msg = 'At least one element in the input g is out of the range [-1, 1].'
774
+ raise Exception(msg)
775
+ elif isinstance(g, (int, float)) == True:
776
+ if g < -1 or g > 1:
777
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
778
+ raise Exception(msg)
779
+ else:
780
+ msg = 'The input g must be int, float or np.ndarray.'
781
+ raise Exception(msg)
782
+
783
+ return g*(3*(4 + g**2))/(5*(2 + g**2))
784
+
785
+ def costheta_D(g, alpha):
786
+ r'''
787
+ | The anisotropy factor as a function of the parameters g and alpha, assuming the Draine
788
+ | scattering phase function.
789
+ | For details please check Draine 2003 [D03].
790
+
791
+ :math:`\langle \cos\theta \rangle_{D}(g, \alpha) = g\frac{1 + \alpha(3 + 2g^2)/5}{1 + \alpha(1 + 2g^2)/3}`
792
+
793
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
794
+ :type g: float
795
+
796
+ :param alpha: parameter :math:`\alpha` [-]
797
+ :float alpha: float or np.ndarray
798
+
799
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
800
+ '''
801
+
802
+ if isinstance(g, np.ndarray) == True:
803
+ if np.any(g < -1) or np.any(g > 1):
804
+ msg = 'At least one element in the input g is out of the range [-1, 1].'
805
+ raise Exception(msg)
806
+ elif isinstance(g, (int, float)) == True:
807
+ if g < -1 or g > 1:
808
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
809
+ raise Exception(msg)
810
+ else:
811
+ msg = 'The input g must be int, float or np.ndarray.'
812
+ raise Exception(msg)
813
+
814
+ return g*(1 + alpha*(3 + 2*g**2)/5)/(1 + alpha*(1 + 2*g**2)/3)
815
+
816
+ def costheta_U2(g):
817
+ r'''
818
+ | The anisotropy factor as a function of the parameter g, assuming the Ultraspherical-2
819
+ | scattering phase function.
820
+ | For details please check Baes, Camps & Kapoor 2022 [BCK22].
821
+
822
+ :math:`\langle \cos\theta \rangle_{U2}(g) = \frac{1+g^2}{2g} + \left(\frac{1-g^2}{2g}\right)^2 \mbox{ ln} \left(\frac{1-g}{1+g}\right)`
823
+
824
+ :param g: parameter :math:`g` [-] (must be in the range [-1, 1])
825
+ :type g: float
826
+
827
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
828
+ '''
829
+
830
+ if isinstance(g, np.ndarray) == True:
831
+ if np.any(g < -1) or np.any(g > 1):
832
+ msg = 'At least one element in the input g is out of the range [-1, 1].'
833
+ raise Exception(msg)
834
+ elif isinstance(g, (int, float)) == True:
835
+ if g < -1 or g > 1:
836
+ msg = 'The input g = {} is out of the range [-1, 1].'.format(g)
837
+ raise Exception(msg)
838
+ else:
839
+ msg = 'The input g must be int, float or np.ndarray.'
840
+ raise Exception(msg)
841
+
842
+ return (1 + g**2)/(2*g) + ((1 - g**2)/(2*g))**2*np.log((1 - g)/(1 + g))
843
+
844
+ def costheta_TTU2(g1, g2, gamma):
845
+ r'''
846
+ | The anisotropy factor as a function of the parameters g1, g2 and gamma, assuming the
847
+ | two-term Ultraspherical-2 scattering phase function.
848
+ | For details please check Baes, Camps & Kapoor 2022 [BCK22].
849
+
850
+ :math:`\langle \cos\theta \rangle_{TTU2}(g_1, g_2, \gamma) = \gamma \mbox{ } \langle \cos\theta \rangle_{U2}(g_1) + (1 - \gamma) \mbox{ } \langle \cos\theta \rangle_{U2}(g_2)`
851
+
852
+ :math:`g_1` characterises the shape and the strength of the forward scattering peak
853
+
854
+ :math:`g_2` characterises the shape and the strength of the backward scattering peak
855
+
856
+ :param g1: parameter :math:`g_1` [-] (must be in the range [0, 1])
857
+ :type g1: float
858
+
859
+ :param g2: parameter :math:`g_2` [-] (must be in the range [-1, 0])
860
+ :type g2: float
861
+
862
+ :param gamma: relative weight of the forward scattering component [-] (must be in the range [0, 1])
863
+ :type gamma: float
864
+
865
+ :return: - **costheta** (*np.ndarray*) – anisotropy factor [-]
866
+ '''
867
+
868
+ if isinstance(g1, np.ndarray) == True:
869
+ if np.any(g1 < 0) or np.any(g1 > 1):
870
+ msg = 'At least one element in the input g1 is out of the range [0, 1].'
871
+ raise Exception(msg)
872
+ elif isinstance(g1, (int, float)) == True:
873
+ if g1 < 0 or g1 > 1:
874
+ msg = 'The input g1 = {} is out of the range [0, 1].'.format(g1)
875
+ raise Exception(msg)
876
+ else:
877
+ msg = 'The input g1 must be int, float or np.ndarray.'
878
+ raise Exception(msg)
879
+
880
+ if isinstance(g2, np.ndarray) == True:
881
+ if np.any(g2 < -1) or np.any(g2 > 0):
882
+ msg = 'At least one element in the input g2 is out of the range [-1, 0].'
883
+ raise Exception(msg)
884
+ elif isinstance(g2, (int, float)) == True:
885
+ if g2 < -1 or g2 > 0:
886
+ msg = 'The input g2 = {} is out of the range [-1, 0].'.format(g2)
887
+ raise Exception(msg)
888
+ else:
889
+ msg = 'The input g2 must be int, float or np.ndarray.'
890
+ raise Exception(msg)
891
+
892
+ if isinstance(gamma, np.ndarray) == True:
893
+ if np.any(gamma < 0) or np.any(gamma > 1):
894
+ msg = 'At least one element in the input gamma is out of the range [0, 1].'
895
+ raise Exception(msg)
896
+ elif isinstance(gamma, (int, float)) == True:
897
+ if gamma < 0 or gamma > 1:
898
+ msg = 'The input gamma = {} is out of the range [0, 1].'.format(gamma)
899
+ raise Exception(msg)
900
+ else:
901
+ msg = 'The input gamma must be int, float or np.ndarray.'
902
+ raise Exception(msg)
903
+
904
+ return gamma*costheta_U2(g = g1) + (1 - gamma)*costheta_U2(g = g2)
905
+
906
+ def g_vanGemert(lambda0):
907
+ r'''
908
+ | The anisotropy factor of human EPIDERMIS or DERMIS as a function of wavelength.
909
+ | van Gemert et al. 1989 [v*89]'s fit for experimental data from Bruls & van der Leun 1984
910
+ | [Bv84] (epidermis, 302, 365, 436 and 546 nm) and Jacques, Alter & Prahl 1987 [JAP87]
911
+ | (dermis, 633 nm).
912
+
913
+ :math:`g(\lambda) = 0.29 \times 10^{-3} \lambda + 0.62`
914
+
915
+ | wavelength range: [302 nm, 633 nm]
916
+
917
+ :param lambda0: wavelength [nm]
918
+ :type lambda0: float or np.ndarray
919
+
920
+ :return: - **g** (*float or np.ndarray*) – anisotropy factor [-]
921
+ '''
922
+
923
+ return linear(lambda0, 0.29E-3, 0.62)
924
+
925
+ def g_Bosschaart(lambda0):
926
+ r'''
927
+ | The anisotropy factor of OXYGENATED BLOOD as a function of wavelength.
928
+ | Linear interpolation of experimental data compiled by Bosschaart et al. 2014 [B*14].
929
+
930
+ wavelength range: [251 nm, 1000 nm]
931
+
932
+ :param lambda0: wavelength [nm]
933
+ :type lambda0: float or np.ndarray
934
+
935
+ :return: - **g** (*float or np.ndarray*) – anisotropy factor [-]
936
+ '''
937
+
938
+ return interp1d(np.array(oxy_and_deo_Bosschaart_dataframe)[:,0],
939
+ np.array(oxy_and_deo_Bosschaart_dataframe)[:,4],
940
+ bounds_error = False,
941
+ fill_value = (np.array(oxy_and_deo_Bosschaart_dataframe)[0,4],
942
+ np.array(oxy_and_deo_Bosschaart_dataframe)[-1,4]))(lambda0)