radphen-iadpython 0.7.0__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.
iadpython/iadc.py ADDED
@@ -0,0 +1,1003 @@
1
+ """
2
+ Module for direct and inverse adding-doubling calculations.
3
+
4
+ Example:
5
+ >>> import iadpython as iad
6
+
7
+ >>> a = 0.9
8
+ >>> b = 10
9
+ >>> g = 0.9
10
+ >>> n_top = 1.5
11
+ >>> n_bot = 1.5
12
+ >>> ur1, ut1, uru, utu = iad.rt(n_top, n_bot, a, b, g)
13
+ >>> print("total reflected light for normal incidence %.5f" % UR1)
14
+ >>> print("total transmitted light for normal incidence %.5f" % UT1)
15
+ >>> print("total reflected light for diffuse incidence %.5f" % URU)
16
+ >>> print("total transmitted light for diffuse incidence %.5f" % UTU)
17
+ """
18
+
19
+ import sys
20
+ import ctypes
21
+ import ctypes.util
22
+ import numpy as np
23
+
24
+ __all__ = (
25
+ "basic_rt",
26
+ "basic_rt_unscattered",
27
+ "basic_rt_cone",
28
+ "basic_rt_oblique",
29
+ "basic_rt_inverse",
30
+ "rt",
31
+ "rt_unscattered",
32
+ "rt_cone",
33
+ "rt_oblique",
34
+ "rt_inverse",
35
+ )
36
+
37
+ libiad_path = ctypes.util.find_library("libiad")
38
+
39
+ if not libiad_path:
40
+ print("Unable to find the libiad library.")
41
+ print(" macOS = 'libiad.dylib'")
42
+ print(" unix = 'libiad.so'")
43
+ print(" Windows = 'libiad.dll'")
44
+ print("Paths searched:")
45
+ for p in sys.path:
46
+ print(" ", p)
47
+ sys.exit()
48
+
49
+ try:
50
+ libiad = ctypes.CDLL(libiad_path)
51
+ except OSError:
52
+ print("Unable to load the libiad library")
53
+ print("Sorry")
54
+ sys.exit()
55
+
56
+ libiad.ez_RT.argtypes = (
57
+ ctypes.c_int, # n quadrature points
58
+ ctypes.c_double, # slab index of refraction
59
+ ctypes.c_double, # top slide index of refraction
60
+ ctypes.c_double, # bottom slide index of refraction
61
+ ctypes.c_double, # albedo mus/(mus+mua)
62
+ ctypes.c_double, # optical thickness d*(mua+mus)
63
+ ctypes.c_double, # scattering anisotropy g
64
+ ctypes.POINTER(ctypes.c_double), # UR1
65
+ ctypes.POINTER(ctypes.c_double), # UT1
66
+ ctypes.POINTER(ctypes.c_double), # URU
67
+ ctypes.POINTER(ctypes.c_double), # UTU
68
+ )
69
+
70
+
71
+ def basic_rt(n, nslab, ntop, nbot, a, b, g):
72
+ """
73
+ Calculate the total reflection and transmission for a turbid slab.
74
+
75
+ `n` is the number of quadrature angles (more angles give better accuracy but slow
76
+ the calculation by `n**3`, `n` must be a multiple of 4, typically 16 is good for forward
77
+ calculations and 4 is appropriate for inverse calculations).
78
+
79
+ The semi-infinite slab may be bounded by glass slides.
80
+ The top glass slides have an index of refraction `ntop` and the bottom slide has
81
+ an index `nbot`. If there are no glass slides, set `ntop` and `nbottom` to 1.
82
+
83
+ All input parameters must be scalars.
84
+
85
+ Args:
86
+ n: number of points in quadrature (multiple of 4)
87
+ nslab: index of refraction of the slab
88
+ ntop: index of refraction of the top slide
89
+ nbot: index of refraction of the bottom slide
90
+ a: single scattering albedo of the slab
91
+ b: optical thickness of the slab
92
+ g: anisotropy of single scattering
93
+ Returns:
94
+ - `UR1` is the total reflection for normally incident collimated light.
95
+ - `UT1` is the total transmission for normally incident collimated light.
96
+ - `URU` is the total reflection for diffuse incident light.
97
+ - `UTU` is the total transmission for diffuse incident light.
98
+ """
99
+ ur1 = ctypes.c_double()
100
+ ut1 = ctypes.c_double()
101
+ uru = ctypes.c_double()
102
+ utu = ctypes.c_double()
103
+ libiad.ez_RT(n, nslab, ntop, nbot, a, b, g, ur1, ut1, uru, utu)
104
+ return ur1.value, ut1.value, uru.value, utu.value
105
+
106
+
107
+ libiad.ez_RT_unscattered.argtypes = (
108
+ ctypes.c_int, # n quadrature points
109
+ ctypes.c_double, # slab index of refraction
110
+ ctypes.c_double, # top slide index of refraction
111
+ ctypes.c_double, # bottom slide index of refraction
112
+ ctypes.c_double, # albedo mus/(mus+mua)
113
+ ctypes.c_double, # optical thickness d*(mua+mus)
114
+ ctypes.c_double, # scattering anisotropy g
115
+ ctypes.POINTER(ctypes.c_double), # UR1
116
+ ctypes.POINTER(ctypes.c_double), # UT1
117
+ ctypes.POINTER(ctypes.c_double), # URU
118
+ ctypes.POINTER(ctypes.c_double), # UTU
119
+ )
120
+
121
+
122
+ def basic_rt_unscattered(n, nslab, ntop, nbot, a, b, g):
123
+ """
124
+ Calculate the unscattered reflection and transmission for a turbid slab.
125
+
126
+ `n` is the number of quadrature angles (more angles give better accuracy but slow
127
+ the calculation by `n**3`, `n` must be a multiple of 4, typically 16 is good for forward
128
+ calculations and 4 is appropriate for inverse calculations).
129
+
130
+ The semi-infinite slab may be bounded by glass slides.
131
+ The top glass slides have an index of refraction `ntop` and the bottom slide has
132
+ an index `nbot`. If there are no glass slides, set `ntop` and `nbottom` to 1.
133
+
134
+ All input parameters must be scalars.
135
+
136
+ Args:
137
+ n: number of points in quadrature (multiple of 4)
138
+ nslab: index of refraction of the slab
139
+ ntop: index of refraction of the top slide
140
+ nbot: index of refraction of the bottom slide
141
+ a: single scattering albedo of the slab
142
+ b: optical thickness of the slab
143
+ g: anisotropy of single scattering
144
+ Returns:
145
+ - `UR1` is the total reflection for normally incident collimated light.
146
+ - `UT1` is the total transmission for normally incident collimated light.
147
+ - `URU` is the total reflection for diffuse incident light.
148
+ - `UTU` is the total transmission for diffuse incident light.
149
+ """
150
+ ur1 = ctypes.c_double()
151
+ ut1 = ctypes.c_double()
152
+ uru = ctypes.c_double()
153
+ utu = ctypes.c_double()
154
+ libiad.ez_RT_unscattered(n, nslab, ntop, nbot, a, b, g, ur1, ut1, uru, utu)
155
+ return ur1.value, ut1.value, uru.value, utu.value
156
+
157
+
158
+ libiad.ez_RT_Cone.argtypes = (
159
+ ctypes.c_int, # n quadrature points
160
+ ctypes.c_double, # slab index of refraction
161
+ ctypes.c_double, # top slide index of refraction
162
+ ctypes.c_double, # bottom slide index of refraction
163
+ ctypes.c_double, # albedo mus/(mus+mua)
164
+ ctypes.c_double, # optical thickness d*(mua+mus)
165
+ ctypes.c_double, # scattering anisotropy g
166
+ ctypes.c_double, # cosine of cone angle
167
+ ctypes.POINTER(ctypes.c_double), # UR1
168
+ ctypes.POINTER(ctypes.c_double), # UT1
169
+ ctypes.POINTER(ctypes.c_double), # URU
170
+ ctypes.POINTER(ctypes.c_double), # UTU
171
+ )
172
+
173
+
174
+ def basic_rt_cone(n, nslab, ntop, nbot, a, b, g, cos_cone_angle):
175
+ """
176
+ Calculate reflection and transmission for a turbid slab exiting within a cone.
177
+
178
+ This routine assumes normally incident or uniformly diffuse incident light
179
+ and returns the total reflected or transmitted light that exits within a cone.
180
+ The cosine of the cone angle is given by `cos_cone_angle`.
181
+
182
+ `n` is the number of quadrature angles (more angles give better accuracy but slow
183
+ the calculation by `n**3`, `n` must be a multiple of 4, typically 16 is good for forward
184
+ calculations and 4 is appropriate for inverse calculations).
185
+
186
+ The semi-infinite slab may be bounded by glass slides.
187
+ The top glass slides have an index of refraction `ntop` and the bottom slide has
188
+ an index `nbot`. If there are no glass slides, set `ntop` and `nbottom` to 1.
189
+
190
+ All input parameters must be scalars.
191
+
192
+ Args:
193
+ n: number of points in quadrature (multiple of 4)
194
+ nslab: index of refraction of the slab
195
+ ntop: index of refraction of the top slide
196
+ nbot: index of refraction of the bottom slide
197
+ a: single scattering albedo of the slab
198
+ b: optical thickness of the slab
199
+ g: anisotropy of single scattering
200
+ cos_cone_angle: cosine of cone of exiting light
201
+ Returns:
202
+ - `UR1` is the reflection within cone for normally incident collimated light.
203
+ - `UT1` is the transmission within cone for normally incident collimated light.
204
+ - `URU` is the reflection within cone for diffuse incident light.
205
+ - `UTU` is the transmission within cone for diffuse incident light.
206
+ """
207
+ ur1 = ctypes.c_double()
208
+ ut1 = ctypes.c_double()
209
+ uru = ctypes.c_double()
210
+ utu = ctypes.c_double()
211
+ libiad.ez_RT_Cone(n, nslab, ntop, nbot, a, b, g, cos_cone_angle, ur1, ut1, uru, utu)
212
+ return ur1.value, ut1.value, uru.value, utu.value
213
+
214
+
215
+ libiad.ez_RT_Oblique.argtypes = (
216
+ ctypes.c_int, # n quadrature points
217
+ ctypes.c_double, # slab index of refraction
218
+ ctypes.c_double, # top slide index of refraction
219
+ ctypes.c_double, # bottom slide index of refraction
220
+ ctypes.c_double, # albedo mus/(mus+mua)
221
+ ctypes.c_double, # optical thickness d*(mua+mus)
222
+ ctypes.c_double, # scattering anisotropy g
223
+ ctypes.c_double, # cosine of oblique angle
224
+ ctypes.POINTER(ctypes.c_double), # UR1
225
+ ctypes.POINTER(ctypes.c_double), # UT1
226
+ ctypes.POINTER(ctypes.c_double), # URU
227
+ ctypes.POINTER(ctypes.c_double), # UTU
228
+ )
229
+
230
+
231
+ def basic_rt_oblique(n, nslab, ntop, nbot, a, b, g, cos_oblique):
232
+ """
233
+ Calculate reflection and transmission for light incident at a oblique angle.
234
+
235
+ This returns the total R and T for light incident at an oblique angle or
236
+ incident within a cone. The cosine of the oblique angle (or the cone) is
237
+ `cos_oblique`.
238
+
239
+ n is the number of quadrature angles (more angles give better accuracy but slow
240
+ the calculation by n**3, n must be a multiple of 4, typically 16 is good for forward
241
+ calculations and 4 is appropriate for inverse calculations).
242
+
243
+ The semi-infinite slab may be bounded by glass slides.
244
+ The top glass slides have an index of refraction `ntop` and the bottom slide has
245
+ an index `nbot`. If there are no glass slides, set `ntop` and `nbottom` to 1.
246
+
247
+ All input parameters must be scalars.
248
+
249
+ Args:
250
+ n: number of points in quadrature (multiple of 4)
251
+ nslab: index of refraction of the slab
252
+ ntop: index of refraction of the top slide
253
+ nbot: index of refraction of the bottom slide
254
+ a: single scattering albedo of the slab
255
+ b: optical thickness of the slab
256
+ g: anisotropy of single scattering
257
+ cos_oblique: cosine of cone of incident light
258
+ Returns:
259
+ - `URx` is the total reflection for obliquely incident collimated light.
260
+ - `UTx` is the total transmission for obliquely incident collimated light.
261
+ - `URU` is the total reflection for diffuse light incident within a cone.
262
+ - `UTU` is the total transmission for diffuse light incident within a cone.
263
+ """
264
+ ur1 = ctypes.c_double()
265
+ ut1 = ctypes.c_double()
266
+ uru = ctypes.c_double()
267
+ utu = ctypes.c_double()
268
+ libiad.ez_RT_Oblique(n, nslab, ntop, nbot, a, b, g, cos_oblique, ur1, ut1, uru, utu)
269
+ return ur1.value, ut1.value, uru.value, utu.value
270
+
271
+
272
+ libiad.ez_Inverse_RT.argtypes = (
273
+ ctypes.c_double, # slab index of refraction
274
+ ctypes.c_double, # slide index of refraction
275
+ ctypes.c_double, # UR1
276
+ ctypes.c_double, # UT1
277
+ ctypes.c_double, # unscattered transmission
278
+ ctypes.POINTER(ctypes.c_double), # a
279
+ ctypes.POINTER(ctypes.c_double), # b
280
+ ctypes.POINTER(ctypes.c_double), # g
281
+ ctypes.POINTER(ctypes.c_int), # error
282
+ )
283
+
284
+
285
+ def basic_rt_inverse(nslab, nslide, ur1, ut1, tc):
286
+ """
287
+ Calculate optical properties given reflection and transmission values.
288
+
289
+ Finds the optical properties `[a,b,g]` for a slab
290
+ with total reflectance `ur1`, total transmission `ut1`, unscattered transmission `Tc`.
291
+ The index of refraction of the slab is `nslab`, the index of refraction of the
292
+ top and bottom slides is `nslide`.
293
+
294
+ The semi-infinite slab may be bounded by glass slides.
295
+ The top glass slides have an index of refraction `ntop` and the bottom slide has
296
+ an index `nbot`. If there are no glass slides, set `ntop` and `nbottom` to 1.
297
+
298
+ All input parameters must be scalars.
299
+
300
+ Args:
301
+ nslab: index of refraction of the slab
302
+ nslide: index of refraction of the slides
303
+ ur1: is total reflection for normally incident collimated light.
304
+ ut1: is total transmission for normally incident collimated light.
305
+ tc: unscattered transmission through sample
306
+ Returns:
307
+ - `a` is the single scattering albedo of the slab
308
+ - `b` is the optical thickness of the slab
309
+ - `g` is the anisotropy of single scattering
310
+ """
311
+ a = ctypes.c_double()
312
+ b = ctypes.c_double()
313
+ g = ctypes.c_double()
314
+ error = ctypes.c_int()
315
+ libiad.ez_Inverse_RT(nslab, nslide, ur1, ut1, tc, a, b, g, error)
316
+ return a.value, b.value, g.value, error.value
317
+
318
+
319
+ def rt(nslab, nslide, a, b, g):
320
+ """
321
+ Calculate the total reflection and transmission for a turbid slab.
322
+
323
+ This routine should be the primary entry point because the optical properties
324
+ can be either scalars or arrays.
325
+
326
+ Args:
327
+ nslab: index of refraction of the slab
328
+ nslide: index of refraction of the slides
329
+ a: single scattering albedo of the slab
330
+ b: optical thickness of the slab
331
+ g: anisotropy of single scattering
332
+ Returns:
333
+ - `UR1` is the total reflection for normally incident collimated light.
334
+ - `UT1` is the total transmission for normally incident collimated light.
335
+ - `URU` is the total reflection for diffuse incident light.
336
+ - `UTU` is the total transmission for diffuse incident light.
337
+ """
338
+ N_QUADRATURE = 16 # should be a multiple of 16
339
+
340
+ if np.isscalar(a):
341
+ len_a = 0
342
+ aa = a
343
+ else:
344
+ len_a = len(a)
345
+
346
+ if np.isscalar(b):
347
+ len_b = 0
348
+ bb = b
349
+ else:
350
+ len_b = len(b)
351
+
352
+ if np.isscalar(g):
353
+ len_g = 0
354
+ gg = g
355
+ else:
356
+ len_g = len(g)
357
+
358
+ thelen = max(len_a, len_b, len_g)
359
+
360
+ if thelen == 0:
361
+ return basic_rt(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg)
362
+
363
+ if len_a and len_b and len_a != len_b:
364
+ raise RuntimeError("rt: a and b arrays must be same length")
365
+
366
+ if len_a and len_g and len_a != len_g:
367
+ raise RuntimeError("rt: a and g arrays must be same length")
368
+
369
+ if len_b and len_g and len_b != len_g:
370
+ raise RuntimeError("rt: b and g arrays must be same length")
371
+
372
+ ur1 = np.empty(thelen)
373
+ ut1 = np.empty(thelen)
374
+ uru = np.empty(thelen)
375
+ utu = np.empty(thelen)
376
+
377
+ for i in range(thelen):
378
+ if len_a > 0:
379
+ aa = a[i]
380
+
381
+ if len_b > 0:
382
+ bb = b[i]
383
+
384
+ if len_g > 0:
385
+ gg = g[i]
386
+
387
+ ur1[i], ut1[i], uru[i], utu[i] = basic_rt(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg)
388
+
389
+ return ur1, ut1, uru, utu
390
+
391
+
392
+ def rt_unscattered(nslab, nslide, a, b, g):
393
+ """
394
+ Calculate the unscattered reflection and transmission for a turbid slab.
395
+
396
+ This routine should be the primary entry point because the optical properties
397
+ can be either scalars or arrays.
398
+
399
+ Args:
400
+ nslab: index of refraction of the slab
401
+ nslide: index of refraction of the slides
402
+ a: single scattering albedo of the slab
403
+ b: optical thickness of the slab
404
+ g: anisotropy of single scattering
405
+ Returns:
406
+ - `UR1` is the unscattered reflection for normally incident collimated light.
407
+ - `UT1` is the unscattered transmission for normally incident collimated light.
408
+ - `URU` is the unscattered reflection for diffuse incident light.
409
+ - `UTU` is the unscattered transmission for diffuse incident light.
410
+ """
411
+ N_QUADRATURE = 16 # should be a multiple of 16
412
+
413
+ if np.isscalar(a):
414
+ len_a = 0
415
+ aa = a
416
+ else:
417
+ len_a = len(a)
418
+
419
+ if np.isscalar(b):
420
+ len_b = 0
421
+ bb = b
422
+ else:
423
+ len_b = len(b)
424
+
425
+ if np.isscalar(g):
426
+ len_g = 0
427
+ gg = g
428
+ else:
429
+ len_g = len(g)
430
+
431
+ thelen = max(len_a, len_b, len_g)
432
+
433
+ if thelen == 0:
434
+ return basic_rt_unscattered(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg)
435
+
436
+ if len_a and len_b and len_a != len_b:
437
+ raise RuntimeError("rt_unscattered: a and b arrays must be same length")
438
+
439
+ if len_a and len_g and len_a != len_g:
440
+ raise RuntimeError("rt_unscattered: a and g arrays must be same length")
441
+
442
+ if len_b and len_g and len_b != len_g:
443
+ raise RuntimeError("rt_unscattered: b and g arrays must be same length")
444
+
445
+ ur1 = np.empty(thelen)
446
+ ut1 = np.empty(thelen)
447
+ uru = np.empty(thelen)
448
+ utu = np.empty(thelen)
449
+
450
+ for i in range(thelen):
451
+ if len_a > 0:
452
+ aa = a[i]
453
+
454
+ if len_b > 0:
455
+ bb = b[i]
456
+
457
+ if len_g > 0:
458
+ gg = g[i]
459
+
460
+ ur1[i], ut1[i], uru[i], utu[i] = basic_rt_unscattered(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg)
461
+
462
+ return ur1, ut1, uru, utu
463
+
464
+
465
+ def rt_cone(nslab, nslide, a, b, g, cos_cone):
466
+ """
467
+ Calculate reflection and transmission for a turbid slab exiting within a cone.
468
+
469
+ This routine should be the primary entry point because the optical properties
470
+ can be either scalars or arrays.
471
+
472
+ Args:
473
+ n: number of points in quadrature (multiple of 4)
474
+ nslab: index of refraction of the slab
475
+ nslide: index of refraction of the top and bottom slides
476
+ a: single scattering albedo of the slab
477
+ b: optical thickness of the slab
478
+ g: anisotropy of single scattering
479
+ cos_cone: cosine of cone of exiting light
480
+ Returns:
481
+ - `UR1` is the reflection within cone for normally incident collimated light.
482
+ - `UT1` is the transmission within cone for normally incident collimated light.
483
+ - `URU` is the reflection within cone for diffuse incident light.
484
+ - `UTU` is the transmission within cone for diffuse incident light.
485
+ """
486
+ N_QUADRATURE = 16 # should be a multiple of 16
487
+
488
+ if np.isscalar(a):
489
+ len_a = 0
490
+ aa = a
491
+ else:
492
+ len_a = len(a)
493
+
494
+ if np.isscalar(b):
495
+ len_b = 0
496
+ bb = b
497
+ else:
498
+ len_b = len(b)
499
+
500
+ if np.isscalar(g):
501
+ len_g = 0
502
+ gg = g
503
+ else:
504
+ len_g = len(g)
505
+
506
+ if np.isscalar(cos_cone):
507
+ len_mu = 0
508
+ mu = cos_cone
509
+ else:
510
+ len_mu = len(cos_cone)
511
+
512
+ thelen = max(len_a, len_b, len_g, len_mu)
513
+
514
+ if thelen == 0:
515
+ return basic_rt_cone(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg, mu)
516
+
517
+ if len_a and len_b and len_a != len_b:
518
+ raise RuntimeError("rt_cone: a and b arrays must be same length")
519
+
520
+ if len_a and len_g and len_a != len_g:
521
+ raise RuntimeError("rt_cone: a and g arrays must be same length")
522
+
523
+ if len_a and len_mu and len_a != len_mu:
524
+ raise RuntimeError("rt_cone: a and mu arrays must be same length")
525
+
526
+ if len_b and len_g and len_b != len_g:
527
+ raise RuntimeError("rt_cone: b and g arrays must be same length")
528
+
529
+ if len_b and len_mu and len_b != len_mu:
530
+ raise RuntimeError("rt_cone: b and mu arrays must be same length")
531
+
532
+ if len_g and len_mu and len_g != len_mu:
533
+ raise RuntimeError("rt_cone: g and mu arrays must be same length")
534
+
535
+ ur1 = np.empty(thelen)
536
+ ut1 = np.empty(thelen)
537
+ uru = np.empty(thelen)
538
+ utu = np.empty(thelen)
539
+
540
+ for i in range(thelen):
541
+ if len_a > 0:
542
+ aa = a[i]
543
+
544
+ if len_b > 0:
545
+ bb = b[i]
546
+
547
+ if len_g > 0:
548
+ gg = g[i]
549
+
550
+ if len_mu > 0:
551
+ mu = cos_cone[i]
552
+
553
+ ur1[i], ut1[i], uru[i], utu[i] = basic_rt_cone(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg, mu)
554
+
555
+ return ur1, ut1, uru, utu
556
+
557
+
558
+ def rt_oblique(nslab, nslide, a, b, g, cos_oblique):
559
+ """
560
+ Calculate reflection and transmission for light incident at a oblique angle.
561
+
562
+ This routine should be the primary entry point because the optical properties
563
+ can be either scalars or arrays.
564
+
565
+ All input parameters must be scalars.
566
+
567
+ Args:
568
+ nslab: index of refraction of the slab
569
+ nslide: index of refraction of the top and bottom slides
570
+ a: single scattering albedo of the slab
571
+ b: optical thickness of the slab
572
+ g: anisotropy of single scattering
573
+ cos_oblique: cosine of cone of incident light
574
+ Returns:
575
+ - `URx` is the total reflection for obliquely incident collimated light.
576
+ - `UTx` is the total transmission for obliquely incident collimated light.
577
+ - `URU` is the total reflection for diffuse light incident within a cone.
578
+ - `UTU` is the total transmission for diffuse light incident within a cone.
579
+ """
580
+ N_QUADRATURE = 16 # should be a multiple of 16
581
+
582
+ if np.isscalar(a):
583
+ len_a = 0
584
+ aa = a
585
+ else:
586
+ len_a = len(a)
587
+
588
+ if np.isscalar(b):
589
+ len_b = 0
590
+ bb = b
591
+ else:
592
+ len_b = len(b)
593
+
594
+ if np.isscalar(g):
595
+ len_g = 0
596
+ gg = g
597
+ else:
598
+ len_g = len(g)
599
+
600
+ if np.isscalar(cos_oblique):
601
+ len_mu = 0
602
+ mu = cos_oblique
603
+ else:
604
+ len_mu = len(cos_oblique)
605
+
606
+ thelen = max(len_a, len_b, len_g, len_mu)
607
+
608
+ if thelen == 0:
609
+ return basic_rt_oblique(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg, mu)
610
+
611
+ if len_a and len_b and len_a != len_b:
612
+ raise RuntimeError("rt_oblique: a and b arrays must be same length")
613
+
614
+ if len_a and len_g and len_a != len_g:
615
+ raise RuntimeError("rt_oblique: a and g arrays must be same length")
616
+
617
+ if len_a and len_mu and len_a != len_mu:
618
+ raise RuntimeError("rt_oblique: a and mu arrays must be same length")
619
+
620
+ if len_b and len_g and len_b != len_g:
621
+ raise RuntimeError("rt_oblique: b and g arrays must be same length")
622
+
623
+ if len_b and len_mu and len_b != len_mu:
624
+ raise RuntimeError("rt_oblique: b and mu arrays must be same length")
625
+
626
+ if len_g and len_mu and len_g != len_mu:
627
+ raise RuntimeError("rt_oblique: g and mu arrays must be same length")
628
+
629
+ ur1 = np.empty(thelen)
630
+ ut1 = np.empty(thelen)
631
+ uru = np.empty(thelen)
632
+ utu = np.empty(thelen)
633
+
634
+ for i in range(thelen):
635
+ if len_a > 0:
636
+ aa = a[i]
637
+
638
+ if len_b > 0:
639
+ bb = b[i]
640
+
641
+ if len_g > 0:
642
+ gg = g[i]
643
+
644
+ if len_mu > 0:
645
+ mu = cos_oblique[i]
646
+
647
+ ur1[i], ut1[i], uru[i], utu[i] = basic_rt_oblique(N_QUADRATURE, nslab, nslide, nslide, aa, bb, gg, mu)
648
+
649
+ return ur1, ut1, uru, utu
650
+
651
+
652
+ def rt_inverse(nslab, nslide, ur1, ut1, t_unscattered):
653
+ """
654
+ Calculate [a,b,g] for a slab.
655
+
656
+ This routine should be the primary entry point because the reflection
657
+ and transmission can be either scalars or arrays.
658
+
659
+ All input parameters must be scalars.
660
+
661
+ Args:
662
+ nslab: index of refraction of the slab
663
+ nslide: index of refraction of the slides
664
+ ur1: is total reflection for normally incident collimated light.
665
+ ut1: is total transmission for normally incident collimated light.
666
+ t_unscattered: unscattered transmission through sample
667
+ Returns:
668
+ - `a` is the single scattering albedo of the slab
669
+ - `b` is the optical thickness of the slab
670
+ - `g` is the anisotropy of single scattering
671
+ """
672
+ if np.isscalar(ur1):
673
+ len_r1 = 0
674
+ else:
675
+ len_r1 = len(ur1)
676
+
677
+ if np.isscalar(ut1):
678
+ len_t1 = 0
679
+ else:
680
+ len_t1 = len(ut1)
681
+
682
+ if np.isscalar(t_unscattered):
683
+ len_tc = 0
684
+ tc = t_unscattered
685
+ else:
686
+ len_tc = len(t_unscattered)
687
+
688
+ thelen = max(len_r1, len_t1, len_tc)
689
+
690
+ if thelen == 0:
691
+ return basic_rt_inverse(nslab, nslide, ur1, ut1, tc)
692
+
693
+ if len_r1 and len_t1 and len_r1 != len_t1:
694
+ raise RuntimeError("inverse_rt: ur1 and ut1 arrays must be same length")
695
+
696
+ if len_r1 and len_tc and len_r1 != len_tc:
697
+ raise RuntimeError("inverse_rt: ur1 and tc arrays must be same length")
698
+
699
+ if len_t1 and len_tc and len_t1 != len_tc:
700
+ raise RuntimeError("inverse_rt: t1 and tc arrays must be same length")
701
+
702
+ a = np.empty(thelen)
703
+ b = np.empty(thelen)
704
+ g = np.empty(thelen)
705
+ error = np.empty(thelen)
706
+
707
+ for i in range(thelen):
708
+ if len_r1 > 0:
709
+ r1 = ur1[i]
710
+
711
+ if len_t1 > 0:
712
+ t1 = ut1[i]
713
+
714
+ if len_tc > 0:
715
+ tc = t_unscattered[i]
716
+
717
+ a[i], b[i], g[i], error[i] = basic_rt_inverse(nslab, nslide, r1, t1, tc)
718
+
719
+ return a, b, g, error
720
+
721
+
722
+ class CSample:
723
+ """Class for samples."""
724
+
725
+ def __init__(self):
726
+ """Initialize class."""
727
+ self.sample_index = 1.4
728
+ self.top_slide_index = 1.5
729
+ self.bot_slide_index = 1.5
730
+ self.sample_thickness = 1 # mm
731
+ self.top_slide_thickness = 1 # mm
732
+ self.bot_slide_thickness = 1 # mm
733
+
734
+ def as_array(self):
735
+ """Representation class as an array."""
736
+ return [
737
+ self.sample_index,
738
+ self.top_slide_index,
739
+ self.bot_slide_index,
740
+ self.sample_thickness,
741
+ self.top_slide_thickness,
742
+ self.bot_slide_thickness,
743
+ ]
744
+
745
+ def init_from_array(self, a):
746
+ """Initialize with an array."""
747
+ self.sample_index = a[0]
748
+ self.top_slide_index = a[1]
749
+ self.bot_slide_index = a[2]
750
+ self.sample_thickness = a[3]
751
+ self.top_slide_thickness = a[4]
752
+ self.bot_slide_thickness = a[5]
753
+
754
+ def as_c_array(self):
755
+ """Represent class as C array."""
756
+ pyarr = self.as_array()
757
+ return (ctypes.c_double * len(pyarr))(*pyarr)
758
+
759
+ def __str__(self):
760
+ """Represent class as string."""
761
+ s = ""
762
+ s += "sample index = %.3f\n" % self.sample_index
763
+ s += "top slide index = %.3f\n" % self.top_slide_index
764
+ s += "bottom slide index = %.3f\n" % self.bot_slide_index
765
+
766
+ s += "sample thickness = %.1f mm\n" % self.sample_thickness
767
+ s += "top slide thickness = %.1f mm\n" % self.top_slide_thickness
768
+ s += "bottom slide thickness = %.1f mm" % self.bot_slide_thickness
769
+ return s
770
+
771
+
772
+ class CSphere:
773
+ """Class for spheres."""
774
+
775
+ def __init__(self):
776
+ """Initialize class."""
777
+ self.d_sphere = 8.0 * 25.4
778
+ self.d_sample = 1.0 * 25.4
779
+ self.d_empty = 1.0 * 25.4
780
+ self.d_detector = 0.5 * 25.4
781
+ self.refl_wall = 0.98
782
+ self.refl_detector = 0.05
783
+
784
+ def init_from_array(self, a):
785
+ """Initialize with an array."""
786
+ self.d_sphere = a[0]
787
+ self.d_sample = a[1]
788
+ self.d_empty = a[2]
789
+ self.d_detector = a[3]
790
+ self.refl_wall = a[4]
791
+ self.refl_detector = a[5]
792
+
793
+ def as_array(self):
794
+ """Representation class as an array."""
795
+ return [
796
+ self.d_sphere,
797
+ self.d_sample,
798
+ self.d_empty,
799
+ self.d_detector,
800
+ self.refl_wall,
801
+ self.refl_detector,
802
+ ]
803
+
804
+ def as_c_array(self):
805
+ """Represent class as C array."""
806
+ pyarr = self.as_array()
807
+ return (ctypes.c_double * len(pyarr))(*pyarr)
808
+
809
+ def __str__(self):
810
+ """Represent class as string."""
811
+ s = ""
812
+ s += "sphere diameter = %.1f mm\n" % self.d_sphere
813
+ s += "sample port diameter = %.1f mm\n" % self.d_sample
814
+ s += "empty port diameter = %.1f mm\n" % self.d_empty
815
+ s += "detector port diameter = %.1f mm\n" % self.d_detector
816
+ s += "wall reflectivity = %.3f\n" % self.refl_wall
817
+ s += "detector reflectivity = %.3f" % self.refl_detector
818
+ return s
819
+
820
+
821
+ class CIllumination:
822
+ """Class for illumination."""
823
+
824
+ def __init__(self):
825
+ """Initialize class."""
826
+ self.beam_diameter = 5 # mm
827
+ self.specular_reflection_excluded = 0
828
+ self.direct_transmission_excluded = 0
829
+ self.diffuse_illumination = 0
830
+ self.num_spheres = 0
831
+ self.lambda0 = 632.8 # nm (unused, tracks wavelength)
832
+
833
+ def init_from_array(self, a):
834
+ """Initialize with an array."""
835
+ self.beam_diameter = a[0]
836
+ self.specular_reflection_excluded = a[1]
837
+ self.direct_transmission_excluded = a[2]
838
+ self.diffuse_illumination = a[3]
839
+ self.lambda0 = a[4]
840
+ self.num_spheres = a[5]
841
+
842
+ def as_array(self):
843
+ """Representation class as an array."""
844
+ return [
845
+ self.beam_diameter,
846
+ self.specular_reflection_excluded,
847
+ self.direct_transmission_excluded,
848
+ self.diffuse_illumination,
849
+ self.lambda0,
850
+ self.num_spheres,
851
+ ]
852
+
853
+ def as_c_array(self):
854
+ """Represent class as C array."""
855
+ pyarr = self.as_array()
856
+ return (ctypes.c_double * len(pyarr))(*pyarr)
857
+
858
+ def __str__(self):
859
+ """Represent class as string."""
860
+ s = ""
861
+ s += "diameter of beam = %.1f mm\n" % self.beam_diameter
862
+ s += "wavelength = %.1f nm\n" % self.lambda0
863
+ s += "exclude specular refl? = %s\n" % (bool(self.specular_reflection_excluded))
864
+ s += "exclude direct trans? = %s\n" % (bool(self.direct_transmission_excluded))
865
+ s += "illumination diffuse? = %s\n" % (bool(self.diffuse_illumination))
866
+ s += "number of spheres = %.0f" % self.num_spheres
867
+ return s
868
+
869
+
870
+ class CAnalysis:
871
+ """Class for analysis."""
872
+
873
+ def __init__(self):
874
+ """Initialize class."""
875
+ self.quadrature_points = 8
876
+ self.monte_carlo_runs = 0
877
+ self.num_photons = 10000
878
+
879
+ def init_from_array(self, a):
880
+ """Initialize with an array."""
881
+ self.quadrature_points = a[0]
882
+ self.monte_carlo_runs = a[1]
883
+ self.num_photons = a[2]
884
+
885
+ def as_array(self):
886
+ """Representation class as an array."""
887
+ return [self.quadrature_points, self.monte_carlo_runs, self.num_photons]
888
+
889
+ def as_c_array(self):
890
+ """Represent class as C array."""
891
+ pyarr = self.as_array()
892
+ return (ctypes.c_double * len(pyarr))(*pyarr)
893
+
894
+ def __str__(self):
895
+ """Represent class as string."""
896
+ s = ""
897
+ s += "quadrature points = %.0f\n" % self.quadrature_points
898
+ s += "monte carlo runs = %.0f\n" % self.monte_carlo_runs
899
+ s += "number of photons = %.0f" % self.num_photons
900
+ return s
901
+
902
+
903
+ class CMeasurement:
904
+ """Class for a measurement."""
905
+
906
+ def __init__(self):
907
+ """Initialize class."""
908
+ self.standard_reflectance = 0.98
909
+ self.reflectance = 0.5
910
+ self.transmittance = 0.1
911
+ self.unscattered_transmittance = 0
912
+
913
+ def init_from_array(self, a):
914
+ """Initialize with an array."""
915
+ self.standard_reflectance = a[0]
916
+ self.reflectance = a[1]
917
+ self.transmittance = a[2]
918
+ self.unscattered_transmittance = a[3]
919
+
920
+ def as_array(self):
921
+ """Representation class as an array."""
922
+ return [
923
+ self.standard_reflectance,
924
+ self.reflectance,
925
+ self.transmittance,
926
+ self.unscattered_transmittance,
927
+ ]
928
+
929
+ def as_c_array(self):
930
+ """Represent class as C array."""
931
+ pyarr = self.as_array()
932
+ return (ctypes.c_double * len(pyarr))(*pyarr)
933
+
934
+ def __str__(self):
935
+ """Represent class as string."""
936
+ s = ""
937
+ s += "standard reflectance = %8.5f\n" % self.standard_reflectance
938
+ s += "reflectance = %8.5f\n" % self.reflectance
939
+ s += "transmittance = %8.5f\n" % self.transmittance
940
+ s += "unscattered trans = %8.5f\n" % self.unscattered_transmittance
941
+ return s
942
+
943
+
944
+ libiad.Spheres_Inverse_RT2.argtypes = (
945
+ ctypes.POINTER(ctypes.c_double), # sample
946
+ ctypes.POINTER(ctypes.c_double), # illumination
947
+ ctypes.POINTER(ctypes.c_double), # Refl Sphere
948
+ ctypes.POINTER(ctypes.c_double), # Trans Sphere
949
+ ctypes.POINTER(ctypes.c_double), # analysis
950
+ ctypes.POINTER(ctypes.c_double), # measurement
951
+ ctypes.POINTER(ctypes.c_double), # a
952
+ ctypes.POINTER(ctypes.c_double), # b
953
+ ctypes.POINTER(ctypes.c_double), # g
954
+ )
955
+
956
+
957
+ def _indent(s):
958
+ return " " + s.replace("\n", "\n ")
959
+
960
+
961
+ class Experiment:
962
+ """Class for experiments."""
963
+
964
+ def __init__(self):
965
+ """Initialize class."""
966
+ self.illumination = CIllumination()
967
+ self.sample = CSample()
968
+ self.r_sphere = CSphere()
969
+ self.t_sphere = CSphere()
970
+ self.analysis = CAnalysis()
971
+ self.measurement = CMeasurement()
972
+
973
+ def __str__(self):
974
+ """Represent class as string."""
975
+ s = "e.sample.\n"
976
+ s += _indent(self.sample.__str__())
977
+ s += "\n\ne.illumination.\n"
978
+ s += _indent(self.illumination.__str__())
979
+ s += "\n\ne.r_sphere.\n"
980
+ s += _indent(self.r_sphere.__str__())
981
+ s += "\n\ne.t_sphere.\n"
982
+ s += _indent(self.t_sphere.__str__())
983
+ s += "\n\ne.analysis.\n"
984
+ s += _indent(self.analysis.__str__())
985
+ s += "\n\ne.measurement.\n"
986
+ s += _indent(self.measurement.__str__())
987
+ return s
988
+
989
+ def invert(self):
990
+ """Calculate optical properties than match experiment."""
991
+ a_s = self.sample.as_c_array()
992
+ a_i = self.illumination.as_c_array()
993
+ a_r = self.r_sphere.as_c_array()
994
+ a_t = self.t_sphere.as_c_array()
995
+ a_a = self.analysis.as_c_array()
996
+ a_m = self.measurement.as_c_array()
997
+
998
+ a = ctypes.c_double()
999
+ b = ctypes.c_double()
1000
+ g = ctypes.c_double()
1001
+
1002
+ libiad.Spheres_Inverse_RT2(a_s, a_i, a_r, a_t, a_a, a_m, a, b, g)
1003
+ return [a.value, b.value, g.value]