skinoptics 0.0.1b8__py3-none-any.whl → 0.0.2__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.
- skinoptics/__init__.py +3 -3
- skinoptics/absorption_coefficient.py +0 -1
- skinoptics/anisotropy_factor.py +954 -955
- skinoptics/colors.py +1543 -1403
- skinoptics/dataframes.py +98 -98
- skinoptics/datasets/colors/Sharma2004_TableI.txt +69 -0
- skinoptics/datasets/optical_properties/ext_and_molarext_oxy_and_deo_Prahl.txt +376 -376
- skinoptics/datasets/optical_properties/molarext_bil_Li.txt +1842 -1842
- skinoptics/datasets/optical_properties/mua_wat_Hale.txt +231 -231
- skinoptics/refractive_index.py +413 -414
- skinoptics/scattering_coefficient.py +0 -1
- skinoptics/utils.py +419 -420
- {skinoptics-0.0.1b8.dist-info → skinoptics-0.0.2.dist-info}/METADATA +31 -31
- {skinoptics-0.0.1b8.dist-info → skinoptics-0.0.2.dist-info}/RECORD +17 -16
- {skinoptics-0.0.1b8.dist-info → skinoptics-0.0.2.dist-info}/WHEEL +1 -1
- {skinoptics-0.0.1b8.dist-info → skinoptics-0.0.2.dist-info}/licenses/LICENSE.txt +674 -674
- {skinoptics-0.0.1b8.dist-info → skinoptics-0.0.2.dist-info}/top_level.txt +0 -0
skinoptics/colors.py
CHANGED
|
@@ -1,1403 +1,1543 @@
|
|
|
1
|
-
'''
|
|
2
|
-
| SkinOptics
|
|
3
|
-
| Copyright (C) 2024-2025 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
|
-
|
|
|
20
|
-
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
|
|
|
53
|
-
|
|
54
|
-
|
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
58
|
-
|
|
|
59
|
-
|
|
60
|
-
|
|
|
61
|
-
|
|
62
|
-
|
|
|
63
|
-
|
|
64
|
-
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
|
72
|
-
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
| CIE
|
|
80
|
-
|
|
|
81
|
-
|
|
82
|
-
| [
|
|
83
|
-
| CIE standard illuminant
|
|
84
|
-
| https://doi.org/10.25039/CIE.DS.
|
|
85
|
-
|
|
86
|
-
| [
|
|
87
|
-
| CIE
|
|
88
|
-
| https://doi.org/10.25039/CIE.DS.
|
|
89
|
-
|
|
90
|
-
| [
|
|
91
|
-
| CIE
|
|
92
|
-
| https://doi.org/10.25039/CIE.DS.
|
|
93
|
-
|
|
94
|
-
| [
|
|
95
|
-
|
|
|
96
|
-
| https://doi.org/10.
|
|
97
|
-
|
|
98
|
-
| [
|
|
99
|
-
| CIE
|
|
100
|
-
| https://doi.org/10.25039/CIE.DS.
|
|
101
|
-
|
|
102
|
-
| [
|
|
103
|
-
|
|
|
104
|
-
| https://doi.org/10.
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
msg
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
| '
|
|
339
|
-
| '
|
|
340
|
-
| '
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
''
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
''
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
R_{linear} \\
|
|
494
|
-
G_{linear} \\
|
|
495
|
-
B_{linear}
|
|
496
|
-
\end{bmatrix}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
\end{
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
:
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
:
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
:
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
R, G, B =
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
R_{linear} \\
|
|
595
|
-
G_{linear} \\
|
|
596
|
-
B_{linear}
|
|
597
|
-
\end{bmatrix}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
\
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
\end{
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
:
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
:
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
X, Y, Z = np.
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
:
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
\
|
|
759
|
-
|
|
760
|
-
:
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
:
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
:
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
:
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
|
788
|
-
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
''
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
L =
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
\
|
|
838
|
-
|
|
839
|
-
:
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
:
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
:
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
:
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
|
867
|
-
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
''
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
X =
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
:
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
:
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
:
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
:
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
+---------------------------+-----------------------------------------------+
|
|
994
|
-
|
|
|
995
|
-
|
|
996
|
-
|
|
|
997
|
-
+---------------------------+-----------------------------------------------+
|
|
998
|
-
|
|
|
999
|
-
+---------------------------+-----------------------------------------------+
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
:
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
ITA_class_list[i] = '
|
|
1029
|
-
|
|
1030
|
-
ITA_class_list[i] = '
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
ITA_class = '
|
|
1043
|
-
|
|
1044
|
-
ITA_class = '
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
:
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
:
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
:
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
:
|
|
1115
|
-
|
|
1116
|
-
:
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
:
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
:
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
:
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
:
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
:
|
|
1146
|
-
|
|
1147
|
-
:
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
:
|
|
1159
|
-
|
|
1160
|
-
:param
|
|
1161
|
-
:type
|
|
1162
|
-
|
|
1163
|
-
:
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
:
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
:
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
:
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
|
1296
|
-
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
|
1318
|
-
|
|
|
1319
|
-
|
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
:
|
|
1327
|
-
|
|
1328
|
-
:param
|
|
1329
|
-
:type
|
|
1330
|
-
|
|
1331
|
-
:param
|
|
1332
|
-
:type
|
|
1333
|
-
|
|
1334
|
-
:param
|
|
1335
|
-
:type
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
:type
|
|
1339
|
-
|
|
1340
|
-
:param
|
|
1341
|
-
:type
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
:type
|
|
1345
|
-
|
|
1346
|
-
:param
|
|
1347
|
-
:type
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
| '
|
|
1359
|
-
| '
|
|
1360
|
-
| '
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
''
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1
|
+
'''
|
|
2
|
+
| SkinOptics
|
|
3
|
+
| Copyright (C) 2024-2025 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
|
+
| victor.lima\@ufscar.br
|
|
20
|
+
| victorportog.github.io
|
|
21
|
+
|
|
22
|
+
| Release date:
|
|
23
|
+
| October 2024
|
|
24
|
+
| Last modification:
|
|
25
|
+
| October 2025
|
|
26
|
+
|
|
27
|
+
| References:
|
|
28
|
+
|
|
29
|
+
| [CCH91] Chardon, Cretois & Hourseau 1991.
|
|
30
|
+
| Skin colour typology and suntanning pathways.
|
|
31
|
+
| https://doi.org/10.1111/j.1467-2494.1991.tb00561.x
|
|
32
|
+
|
|
33
|
+
| [T*94] Takiwaki, Shirai, Kanno, Watanabe & Arase 1994.
|
|
34
|
+
| Quantification of erythema and pigmentation using a videomicroscope and a computer.
|
|
35
|
+
| https://doi.org/10.1111/j.1365-2133.1994.tb08462.x
|
|
36
|
+
|
|
37
|
+
| [F*96] Fullerton, Fischer, Lahti, Wilhelm, Takiwaki & Serup 1996.
|
|
38
|
+
| Guidetines for measurement of skin colour and erythema: A report from the Standardization Group of the European Society of Contact Dermatitis.
|
|
39
|
+
| https://doi.org/10.1111/j.1600-0536.1996.tb02258.x
|
|
40
|
+
|
|
41
|
+
| [S*96] Stokes, Anderson, Chandrasekar & Motta 1996.
|
|
42
|
+
| A Standard Default Color Space for the Internet - sRGB.
|
|
43
|
+
| https://www.w3.org/Graphics/Color/sRGB.html
|
|
44
|
+
|
|
45
|
+
| [IEC99] IEC 1999.
|
|
46
|
+
| IEC 61966-2-1:1999 - Multimedia systems and equipment - Colour measurement and management - Part 2-1: Colour management - Default RGB colour space - sRGB.
|
|
47
|
+
|
|
48
|
+
| [CIE04] CIE 2004.
|
|
49
|
+
| Colorimetry, 3rd edition.
|
|
50
|
+
| CIE 15:2004
|
|
51
|
+
|
|
52
|
+
| [SWD04] Sharma, Wu & Dalal 2004.
|
|
53
|
+
| The CIEDE2000 Color-Difference Formula: Implementation Notes, Supplementary Test Data, and Mathematical Observations.
|
|
54
|
+
| https://doi.org/10.1002/col.20070
|
|
55
|
+
|
|
56
|
+
| [D*06] Del Bino, Sok, Bessac & Bernerd 2006.
|
|
57
|
+
| Relationship between skin response to ultraviolet exposure and skin color type.
|
|
58
|
+
| https://doi.org/10.1111/j.1600-0749.2006.00338.x
|
|
59
|
+
|
|
60
|
+
| [S07] Schanda (editor) 2007.
|
|
61
|
+
| Colorimetry: Understanding the CIE System.
|
|
62
|
+
| http://dx.doi.org/10.1002/9780470175637
|
|
63
|
+
|
|
64
|
+
| [IC08] ISO/CIE 2008.
|
|
65
|
+
| ISO 11664-4:2008 - CIE S 014-4/E:2007 - Colorimetry - Part 4: CIE 1976 L*a*b* Colour space.
|
|
66
|
+
|
|
67
|
+
| [HP11] Hunt & Pointer 2011.
|
|
68
|
+
| Measuring Colour.
|
|
69
|
+
| https://doi.org/10.1002/9781119975595
|
|
70
|
+
|
|
71
|
+
| [DB13] Del Bino & Bernerd 2013.
|
|
72
|
+
| Variations in skin colour and the biological consequences of ultraviolet radiation exposure.
|
|
73
|
+
| https://doi.org/10.1111/bjd.12529
|
|
74
|
+
|
|
75
|
+
| [WSS13] Wyman, Sloan & Shirley 2013.
|
|
76
|
+
| Simple Analytic Approximations to the CIE XYZ Color Matching Functions.
|
|
77
|
+
| https://jcgt.org/published/0002/02/01/
|
|
78
|
+
|
|
79
|
+
| [IC14] ISO/CIE 2014.
|
|
80
|
+
| ISO/CIE 11664-6:2014 - Colorimetry - Part 6: CIEDE2000 Colour-difference formula.
|
|
81
|
+
|
|
82
|
+
| [CIE18a] CIE 2018.
|
|
83
|
+
| CIE standard illuminant A - 1 nm.
|
|
84
|
+
| https://doi.org/10.25039/CIE.DS.8jsxjrsn
|
|
85
|
+
|
|
86
|
+
| [CIE18b] CIE 2018.
|
|
87
|
+
| CIE standard illuminant D55.
|
|
88
|
+
| https://doi.org/10.25039/CIE.DS.qewfb3kp
|
|
89
|
+
|
|
90
|
+
| [CIE18c] CIE 2018.
|
|
91
|
+
| CIE standard illuminant D75.
|
|
92
|
+
| https://doi.org/10.25039/CIE.DS.9fvcmrk4
|
|
93
|
+
|
|
94
|
+
| [CIE19a] CIE 2019.
|
|
95
|
+
| CIE 1931 colour-matching functions, 2 degree observer.
|
|
96
|
+
| https://doi.org/10.25039/CIE.DS.xvudnb9b
|
|
97
|
+
|
|
98
|
+
| [CIE19b] CIE 2019.
|
|
99
|
+
| CIE 1964 colour-matching functions, 10 degree observer
|
|
100
|
+
| https://doi.org/10.25039/CIE.DS.sqksu2n5
|
|
101
|
+
|
|
102
|
+
| [L*20] Ly, Dyer, Feig, Chien & Del Bino 2020.
|
|
103
|
+
| Research Techniques Made Simple: Cutaneous Colorimetry: A Reliable Technique for Objective Skin Color Measurement.
|
|
104
|
+
| https://doi.org/10.1016/j.jid.2019.11.003
|
|
105
|
+
|
|
106
|
+
| [CIE22a] CIE 2022.
|
|
107
|
+
| CIE standard illuminant D50.
|
|
108
|
+
| https://doi.org/10.25039/CIE.DS.etgmuqt5
|
|
109
|
+
|
|
110
|
+
| [CIE22b] CIE 2022.
|
|
111
|
+
| CIE standard illuminant D65.
|
|
112
|
+
| https://doi.org/10.25039/CIE.DS.hjfjmt59
|
|
113
|
+
'''
|
|
114
|
+
|
|
115
|
+
import numpy as np
|
|
116
|
+
from scipy.interpolate import interp1d
|
|
117
|
+
from scipy.integrate import trapezoid
|
|
118
|
+
|
|
119
|
+
from skinoptics.utils import *
|
|
120
|
+
from skinoptics.dataframes import *
|
|
121
|
+
|
|
122
|
+
def rspd(lambda0, illuminant):
|
|
123
|
+
r'''
|
|
124
|
+
| The relative spectral power distribution S(:math:`\lambda`) of a chosen standard illuminant
|
|
125
|
+
| as a function of wavelength.
|
|
126
|
+
| Linear interpolation of data from CIE datasets [CIE18a] [CIE22a] [CIE18b] [CIE22b] [CIE18c].
|
|
127
|
+
|
|
128
|
+
| wavelength range:
|
|
129
|
+
| [300 nm, 830 nm] (at 1 nm intervals, for illuminant = 'A', 'D50' or 'D65')
|
|
130
|
+
| or [300 nm, 780 nm] (at 5 nm intervals, for illuminant = 'D55' or 'D75')
|
|
131
|
+
|
|
132
|
+
:param lambda0: wavelength [nm] (must be in range [300 nm, 830 nm] or [300 nm, 780 nm])
|
|
133
|
+
:type lambda0: float or np.ndarray
|
|
134
|
+
|
|
135
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
136
|
+
:type illuminant: str
|
|
137
|
+
|
|
138
|
+
| 'A' refers to the CIE standard illuminant A
|
|
139
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
140
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
141
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
142
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
143
|
+
|
|
144
|
+
:return: - **rspd** (*float or np.ndarray*) – relative spectral power distribution [-]
|
|
145
|
+
'''
|
|
146
|
+
|
|
147
|
+
if illuminant == 'A' or 'D50' or 'D65':
|
|
148
|
+
if isinstance(lambda0, np.ndarray) == True:
|
|
149
|
+
if np.any(lambda0 < 300) or np.any(lambda0 > 830):
|
|
150
|
+
msg = 'At least one element in the input lambda0 is out of the range [300 nm, 830 nm].'
|
|
151
|
+
raise Exception(msg)
|
|
152
|
+
else:
|
|
153
|
+
if lambda0 < 300 or lambda0 > 830:
|
|
154
|
+
msg = 'The input lambda0 = {} nm is out of the range [300 nm, 830 nm].'.format(lambda0)
|
|
155
|
+
raise Exception(msg)
|
|
156
|
+
elif illuminant == 'D55' or 'D75':
|
|
157
|
+
if isinstance(lambda0, np.ndarray) == True:
|
|
158
|
+
if np.any(lambda0 < 300) or np.any(lambda0 > 780):
|
|
159
|
+
msg = 'At least one element in the input lambda0 is out of the range [300 nm, 780 nm].'
|
|
160
|
+
raise Exception(msg)
|
|
161
|
+
else:
|
|
162
|
+
if lambda0 < 300 or lambda0 > 780:
|
|
163
|
+
msg = 'The input lambda0 = {} nm is out of the range [300 nm, 780 nm].'.format(lambda0)
|
|
164
|
+
raise Exception(msg)
|
|
165
|
+
|
|
166
|
+
if illuminant == 'A':
|
|
167
|
+
rspd = interp1d(np.array(rspds_A_D50_D65_dataframe)[:,0],
|
|
168
|
+
np.array(rspds_A_D50_D65_dataframe)[:,1])(lambda0)
|
|
169
|
+
elif illuminant == 'D50':
|
|
170
|
+
rspd = interp1d(np.array(rspds_A_D50_D65_dataframe)[:,0],
|
|
171
|
+
np.array(rspds_A_D50_D65_dataframe)[:,2])(lambda0)
|
|
172
|
+
elif illuminant == 'D55':
|
|
173
|
+
rspd = interp1d(np.array(rspds_D55_D75_dataframe)[:,0],
|
|
174
|
+
np.array(rspds_D55_D75_dataframe)[:,1])(lambda0)
|
|
175
|
+
elif illuminant == 'D65':
|
|
176
|
+
rspd = interp1d(np.array(rspds_A_D50_D65_dataframe)[:,0],
|
|
177
|
+
np.array(rspds_A_D50_D65_dataframe)[:,3])(lambda0)
|
|
178
|
+
elif illuminant == 'D75':
|
|
179
|
+
rspd = interp1d(np.array(rspds_D55_D75_dataframe)[:,0],
|
|
180
|
+
np.array(rspds_D55_D75_dataframe)[:,2])(lambda0)
|
|
181
|
+
else:
|
|
182
|
+
msg = 'The input illuminant = {} is not valid.'.format(illuminant)
|
|
183
|
+
raise Exception(msg)
|
|
184
|
+
|
|
185
|
+
return rspd
|
|
186
|
+
|
|
187
|
+
def cmfs(lambda0, observer, cmfs_model = 'CIE'):
|
|
188
|
+
r'''
|
|
189
|
+
| The CIE color-matching functions :math:`\bar{x}(\lambda)`, :math:`\bar{y}(\lambda)` and :math:`\bar{z}(\lambda)` for a chosen standard observer
|
|
190
|
+
| as a function of wavelength.
|
|
191
|
+
|
|
192
|
+
| wavelength range: [360 nm, 830 nm] (at 1 nm intervals for cmfs_model = 'CIE')
|
|
193
|
+
|
|
194
|
+
:param lambda0: wavelength [nm] (must be in range [360., 830.] for cmfs_model = 'CIE')
|
|
195
|
+
:type lambda0: float or np.ndarray
|
|
196
|
+
|
|
197
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
198
|
+
:type observer: str
|
|
199
|
+
|
|
200
|
+
:param cmfs_model: the user can choose one of the following... 'CIE', 'Wyman_singlelobe' or 'Wyman_multilobe' (default to 'CIE')
|
|
201
|
+
:type cmfs_model: str
|
|
202
|
+
|
|
203
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
204
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
205
|
+
|
|
206
|
+
| 'CIE' for the linear interpolation of data from CIE datasets [CIE19a] [CIE19b]
|
|
207
|
+
| 'Wyman_singlelobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.1)
|
|
208
|
+
| 'Wyman_multilobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.2)
|
|
209
|
+
|
|
210
|
+
:return: - **xbar** (*float or np.ndarray*) – :math:`\bar{x}(\lambda`) color-matching function [-]
|
|
211
|
+
- **ybar** (*float or np.ndarray*) – :math:`\bar{y}(\lambda`) color-matching function [-]
|
|
212
|
+
- **zbar** (*float or np.ndarray*) – :math:`\bar{z}(\lambda`) color-matching function [-]
|
|
213
|
+
'''
|
|
214
|
+
|
|
215
|
+
if cmfs_model == 'CIE':
|
|
216
|
+
if isinstance(lambda0, np.ndarray) == True:
|
|
217
|
+
if np.any(lambda0 < 360) or np.any(lambda0 > 830):
|
|
218
|
+
msg = 'At least one element in the input lambda0 is out of the range [360 nm, 830 nm].'
|
|
219
|
+
raise Exception(msg)
|
|
220
|
+
else:
|
|
221
|
+
if lambda0 < 360 or lambda0 > 830:
|
|
222
|
+
msg = 'The input lambda0 = {} nm is out of the range [360 nm, 830 nm].'.format(lambda0)
|
|
223
|
+
raise Exception(msg)
|
|
224
|
+
if observer == '2o':
|
|
225
|
+
xbar = interp1d(np.array(cmfs_dataframe)[:,0],
|
|
226
|
+
np.array(cmfs_dataframe)[:,1])(lambda0)
|
|
227
|
+
ybar = interp1d(np.array(cmfs_dataframe)[:,0],
|
|
228
|
+
np.array(cmfs_dataframe)[:,2])(lambda0)
|
|
229
|
+
zbar = interp1d(np.array(cmfs_dataframe)[:,0],
|
|
230
|
+
np.array(cmfs_dataframe)[:,3])(lambda0)
|
|
231
|
+
elif observer == '10o':
|
|
232
|
+
xbar = interp1d(np.array(cmfs_dataframe)[:,0],
|
|
233
|
+
np.array(cmfs_dataframe)[:,4])(lambda0)
|
|
234
|
+
ybar = interp1d(np.array(cmfs_dataframe)[:,0],
|
|
235
|
+
np.array(cmfs_dataframe)[:,5])(lambda0)
|
|
236
|
+
zbar = interp1d(np.array(cmfs_dataframe)[:,0],
|
|
237
|
+
np.array(cmfs_dataframe)[:,6])(lambda0)
|
|
238
|
+
else:
|
|
239
|
+
msg = 'The input observer = {} is not valid.'.format(observer)
|
|
240
|
+
raise Exception(msg)
|
|
241
|
+
elif cmfs_model == 'Wyman_singlelobe':
|
|
242
|
+
if observer == '2o':
|
|
243
|
+
xbar = gaussian(lambda0, 1.065, 595.8, 33.33) \
|
|
244
|
+
+ gaussian(lambda0, 0.366, 446.8, 19.44)
|
|
245
|
+
ybar = gaussian(np.log(lambda0), 1.014, np.log(556.3), 0.075)
|
|
246
|
+
zbar = gaussian(np.log(lambda0), 1.839, np.log(449.8), 0.051)
|
|
247
|
+
elif observer == '10o':
|
|
248
|
+
xbar = mod_gaussian_Wyman(lambda0, 0.398, -570.1, 1014, 1250) \
|
|
249
|
+
+ mod_gaussian_Wyman(-lambda0, 1.132, -1338, 743.5, 234)
|
|
250
|
+
ybar = gaussian(lambda0, 1.011, 556.1, 46.14)
|
|
251
|
+
zbar = mod_gaussian_Wyman(lambda0, 2.06, 265.8, 180.4,32)
|
|
252
|
+
else:
|
|
253
|
+
msg = 'The input observer = {} is not valid.'.format(observer)
|
|
254
|
+
raise Exception(msg)
|
|
255
|
+
elif cmfs_model == 'Wyman_multilobe':
|
|
256
|
+
if observer == '2o':
|
|
257
|
+
coeffs = [[0.362, 1.056, -0.065, 0.821, 0.286, 0., 1.217, 0.681, 0.],
|
|
258
|
+
[442.0, 599.8, 501.1, 568.8, 530.9, 0., 437.0, 459.0, 0.],
|
|
259
|
+
[0.0624, 0.0264, 0.0490, 0.0213, 0.0613, 0., 0.0845, 0.0385, 0.],
|
|
260
|
+
[0.0374, 0.0323, 0.0382, 0.0247, 0.0322, 0., 0.0278, 0.0725, 0.]]
|
|
261
|
+
if isinstance(lambda0, np.ndarray) == True:
|
|
262
|
+
xbar, ybar, zbar = np.zeros((3,len(lambda0)))
|
|
263
|
+
c = 0
|
|
264
|
+
for j in range(len(lambda0)):
|
|
265
|
+
X, Y, Z = 0., 0., 0.
|
|
266
|
+
for i in range(3):
|
|
267
|
+
X += piecewise_gaussian_Wyman(lambda0[j], coeffs[0][i], coeffs[1][i],
|
|
268
|
+
coeffs[2][i], coeffs[3][i])
|
|
269
|
+
Y += piecewise_gaussian_Wyman(lambda0[j], coeffs[0][i+3], coeffs[1][i+3],
|
|
270
|
+
coeffs[2][i+3], coeffs[3][i+3])
|
|
271
|
+
Z += piecewise_gaussian_Wyman(lambda0[j], coeffs[0][i+6], coeffs[1][i+6],
|
|
272
|
+
coeffs[2][i+6], coeffs[3][i+6])
|
|
273
|
+
xbar[j], ybar[j], zbar[j] = X, Y, Z
|
|
274
|
+
elif isinstance(lambda0, (int, float)) == True:
|
|
275
|
+
X, Y, Z = 0., 0., 0.
|
|
276
|
+
for i in range(3):
|
|
277
|
+
X += piecewise_gaussian_Wyman(lambda0, coeffs[0][i], coeffs[1][i],
|
|
278
|
+
coeffs[2][i], coeffs[3][i])
|
|
279
|
+
Y += piecewise_gaussian_Wyman(lambda0, coeffs[0][i+3], coeffs[1][i+3],
|
|
280
|
+
coeffs[2][i+3], coeffs[3][i+3])
|
|
281
|
+
Z += piecewise_gaussian_Wyman(lambda0, coeffs[0][i+6], coeffs[1][i+6],
|
|
282
|
+
coeffs[2][i+6], coeffs[3][i+6])
|
|
283
|
+
xbar, ybar, zbar = X, Y, Z
|
|
284
|
+
else:
|
|
285
|
+
msg = 'The input lambda0 must be int, float or np.ndarray.'
|
|
286
|
+
raise Exception(msg)
|
|
287
|
+
else:
|
|
288
|
+
msg = 'The input observer = {} is not valid for cmfs_model = Wyman_multilobe.'.format(observer)
|
|
289
|
+
raise Exception(msg)
|
|
290
|
+
else:
|
|
291
|
+
msg = 'The input cmfs_model = {} is not valid.'.format(cmfs_model)
|
|
292
|
+
raise Exception(msg)
|
|
293
|
+
|
|
294
|
+
return xbar, ybar, zbar
|
|
295
|
+
|
|
296
|
+
def xy_from_XYZ(X, Y, Z):
|
|
297
|
+
r'''
|
|
298
|
+
| Calculate CIE xy chromaticities from CIE XYZ coordinates.
|
|
299
|
+
|
|
300
|
+
| :math:`x = \frac{X}{X + Y + Z}`
|
|
301
|
+
| :math:`y = \frac{Y}{X + Y + Z}`
|
|
302
|
+
|
|
303
|
+
:param X: X coordinate [-]
|
|
304
|
+
:type X: float or np.ndarray
|
|
305
|
+
|
|
306
|
+
:param Y: Y coordinate [-]
|
|
307
|
+
:type Y: float or np.ndarray
|
|
308
|
+
|
|
309
|
+
:param Z: Z coordinate [-]
|
|
310
|
+
:type Z: float or np.ndarray
|
|
311
|
+
|
|
312
|
+
:return: - **x** (*float or np.ndarray*) – x chromaticity [-]
|
|
313
|
+
- **y** (*float or np.ndarray*) – y chromaticity [-]
|
|
314
|
+
'''
|
|
315
|
+
|
|
316
|
+
x = X/(X + Y + Z)
|
|
317
|
+
y = Y/(X + Y + Z)
|
|
318
|
+
|
|
319
|
+
return x, y
|
|
320
|
+
|
|
321
|
+
def XYZ_wp(illuminant, observer, cmfs_model = 'CIE', K = 1.):
|
|
322
|
+
r'''
|
|
323
|
+
The white point CIE XYZ coordinates for a chosen standard illuminant and standard observer.
|
|
324
|
+
|
|
325
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
326
|
+
:type illuminant: str
|
|
327
|
+
|
|
328
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
329
|
+
:type observer: str
|
|
330
|
+
|
|
331
|
+
:param cmfs_model: the user can choose one of the following... 'CIE', 'Wyman_singlelobe' or 'Wyman_multilobe' (default to 'CIE')
|
|
332
|
+
:type cmfs_model: str
|
|
333
|
+
|
|
334
|
+
:param K: scaling factor (usually 1. or 100.) [-] (default to 1.)
|
|
335
|
+
:type K: float
|
|
336
|
+
|
|
337
|
+
| 'A' refers to the CIE standard illuminant A
|
|
338
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
339
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
340
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
341
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
342
|
+
|
|
343
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
344
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
345
|
+
|
|
346
|
+
| 'CIE' for the linear interpolation of data from CIE datasets [CIE19a] [CIE19b]
|
|
347
|
+
| 'Wyman_singlelobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.1)
|
|
348
|
+
| 'Wyman_multilobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.2)
|
|
349
|
+
|
|
350
|
+
| K = 1. for CIE XYZ coordinates in range [0, 1]
|
|
351
|
+
| K = 100. for CIE XYZ coordinates in range [0, 100]
|
|
352
|
+
|
|
353
|
+
:return: - **Xn** (*float*) – white point X coordinate [-]
|
|
354
|
+
- **Yn** (*float*) – white point Y coordinate [-]
|
|
355
|
+
- **Zn** (*float*) – white point Z coordinate [-]
|
|
356
|
+
'''
|
|
357
|
+
|
|
358
|
+
if illuminant == 'D55' or illuminant == 'D75':
|
|
359
|
+
Xn, Yn, Zn = XYZ_from_spectrum(np.arange(360, 780, 1), np.ones(len(np.arange(360, 780, 1)))*100,
|
|
360
|
+
lambda_max = 780, illuminant = illuminant, observer = observer, cmfs_model = cmfs_model, K = K)
|
|
361
|
+
else:
|
|
362
|
+
Xn, Yn, Zn = XYZ_from_spectrum(np.arange(360, 830, 1), np.ones(len(np.arange(360, 830, 1)))*100,
|
|
363
|
+
illuminant = illuminant, observer = observer, cmfs_model = cmfs_model, K = K)
|
|
364
|
+
|
|
365
|
+
return Xn, Yn, Zn
|
|
366
|
+
|
|
367
|
+
def xy_wp(illuminant, observer):
|
|
368
|
+
r'''
|
|
369
|
+
| The white point CIE xy chromaticities for a chosen standard illuminant and standard observer.
|
|
370
|
+
| Calculated from the white point CIE XYZ coordinates (see function :meth:`skinoptics.colors.XYZ_wp`).
|
|
371
|
+
|
|
372
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
373
|
+
:type illuminant: str
|
|
374
|
+
|
|
375
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
376
|
+
:type observer: str
|
|
377
|
+
|
|
378
|
+
| 'A' refers to the CIE standard illuminant A
|
|
379
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
380
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
381
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
382
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
383
|
+
|
|
384
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
385
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
386
|
+
|
|
387
|
+
:return: - **xn** (*float*) – white point CIE x chromaticity [-]
|
|
388
|
+
- **yn** (*float*) – white point CIE y chromaticity [-]
|
|
389
|
+
'''
|
|
390
|
+
|
|
391
|
+
Xn, Yn, Zn = XYZ_wp(illuminant = illuminant, observer = observer)
|
|
392
|
+
xn, yn = xy_from_XYZ(Xn, Yn, Zn)
|
|
393
|
+
|
|
394
|
+
return xn, yn
|
|
395
|
+
|
|
396
|
+
def transf_matrix_sRGB_linear_from_XYZ():
|
|
397
|
+
r'''
|
|
398
|
+
The transformation matrix employed to obtain linear sRGB coordinates from CIE XYZ coordinates.
|
|
399
|
+
|
|
400
|
+
:math:`\mathcal{M} =
|
|
401
|
+
\begin{bmatrix}
|
|
402
|
+
3.24062 & -1.5372 & -0.4986 \\
|
|
403
|
+
-0.9689 & 1.8758 & 0.0415 \\
|
|
404
|
+
0.0557 & -0.2040 & 1.0570
|
|
405
|
+
\end{bmatrix}`
|
|
406
|
+
|
|
407
|
+
:returns: - **M** (*np.ndarray*) – transformation matrix
|
|
408
|
+
'''
|
|
409
|
+
|
|
410
|
+
return np.array([[3.24062, -1.5372, -0.4986], [-0.9689, 1.8758, 0.0415], [0.0557, -0.2040, 1.0570]])
|
|
411
|
+
|
|
412
|
+
def nonlinear_corr_sRGB(u):
|
|
413
|
+
r'''
|
|
414
|
+
The nonlinear correction for sRGB coordinates.
|
|
415
|
+
|
|
416
|
+
:math:`\gamma(u) =
|
|
417
|
+
\left \{ \begin{matrix}
|
|
418
|
+
12.92 \mbox{ } u, & \mbox{if } u \le 0.0031308 \\
|
|
419
|
+
1.055 \mbox{ } u^{1/2.4} - 0.055, & \mbox{if } u > 0.0031308 \\
|
|
420
|
+
\end{matrix} \right.`
|
|
421
|
+
|
|
422
|
+
:param u: linear R, G or B coordinate [-]
|
|
423
|
+
:type u: float or np.ndarray
|
|
424
|
+
|
|
425
|
+
:return: - **gamma** (*float or np.ndarray*) – nonlinear R, G or B coordinate [-]
|
|
426
|
+
'''
|
|
427
|
+
|
|
428
|
+
if isinstance(u, np.ndarray) == True:
|
|
429
|
+
gamma = np.zeros(len(u))
|
|
430
|
+
for i in range(len(u)):
|
|
431
|
+
if u[i] <= 0.0031308:
|
|
432
|
+
gamma[i] = 12.92*u[i]
|
|
433
|
+
else:
|
|
434
|
+
gamma[i] = 1.055*u[i]**(1./2.4) - 0.055
|
|
435
|
+
elif isinstance(u, (int, float)) == True:
|
|
436
|
+
if u <= 0.0031308:
|
|
437
|
+
gamma = 12.92*u
|
|
438
|
+
else:
|
|
439
|
+
gamma = 1.055*u**(1./2.4) - 0.055
|
|
440
|
+
else:
|
|
441
|
+
msg = 'u must be int, float or np.ndarray.'
|
|
442
|
+
raise Exception(msg)
|
|
443
|
+
|
|
444
|
+
return gamma
|
|
445
|
+
|
|
446
|
+
def inv_nonlinear_corr_sRGB(u):
|
|
447
|
+
r'''
|
|
448
|
+
The inverse nonlinear correction for sRGB coordinates.
|
|
449
|
+
|
|
450
|
+
:math:`\gamma^{-1}(u) =
|
|
451
|
+
\left \{ \begin{matrix}
|
|
452
|
+
u/12.92, & \mbox{if } u \le 0.04045 \\
|
|
453
|
+
[(u + 0.055)/1.055]^{2.4}, & \mbox{if } u > 0.04045 \\
|
|
454
|
+
\end{matrix} \right.`
|
|
455
|
+
|
|
456
|
+
:param u: nonlinear R, G or B coordinate [-]
|
|
457
|
+
:type u: float or np.ndarray
|
|
458
|
+
|
|
459
|
+
:return: - **inv_gamma** (*float or np.ndarray*) – linear R, G or B coordinate [-]
|
|
460
|
+
'''
|
|
461
|
+
|
|
462
|
+
if isinstance(u, np.ndarray) == True:
|
|
463
|
+
inv_gamma = np.zeros(len(u))
|
|
464
|
+
for i in range(len(u)):
|
|
465
|
+
if u[i] <= 0.04045:
|
|
466
|
+
inv_gamma[i] = u[i]/12.92
|
|
467
|
+
else:
|
|
468
|
+
inv_gamma[i] = ((u[i] + 0.055)/1.055)**(2.4)
|
|
469
|
+
elif isinstance(u, (int, float)) == True:
|
|
470
|
+
if u <= 0.04045:
|
|
471
|
+
inv_gamma = u/12.92
|
|
472
|
+
else:
|
|
473
|
+
inv_gamma = ((u + 0.055)/1.055)**(2.4)
|
|
474
|
+
else:
|
|
475
|
+
msg = 'u must be int, float or np.ndarray.'
|
|
476
|
+
raise Exception(msg)
|
|
477
|
+
|
|
478
|
+
return inv_gamma
|
|
479
|
+
|
|
480
|
+
def sRGB_from_XYZ(X, Y, Z, K = 1., sRGB_scale = 'norm'):
|
|
481
|
+
r'''
|
|
482
|
+
| Calculate sRGB coordinates from CIE XYZ coordinates.
|
|
483
|
+
| CIE XYZ coordinates must be for the standard illuminant D65 and the 2 degree standard observer.
|
|
484
|
+
| For details please check Stokes et al. [S*96] and IEC [IEC99].
|
|
485
|
+
|
|
486
|
+
:math:`\begin{bmatrix}
|
|
487
|
+
R \\
|
|
488
|
+
G \\
|
|
489
|
+
B
|
|
490
|
+
\end{bmatrix}
|
|
491
|
+
=
|
|
492
|
+
\begin{bmatrix}
|
|
493
|
+
\gamma(R_{linear}) \\
|
|
494
|
+
\gamma(G_{linear}) \\
|
|
495
|
+
\gamma(B_{linear})
|
|
496
|
+
\end{bmatrix}`
|
|
497
|
+
|
|
498
|
+
in which
|
|
499
|
+
|
|
500
|
+
:math:`\begin{bmatrix}
|
|
501
|
+
R_{linear} \\
|
|
502
|
+
G_{linear} \\
|
|
503
|
+
B_{linear}
|
|
504
|
+
\end{bmatrix}
|
|
505
|
+
=
|
|
506
|
+
\mathcal{M}
|
|
507
|
+
\begin{bmatrix}
|
|
508
|
+
X \\
|
|
509
|
+
Y \\
|
|
510
|
+
Z
|
|
511
|
+
\end{bmatrix}`
|
|
512
|
+
|
|
513
|
+
and
|
|
514
|
+
|
|
515
|
+
:math:`\gamma(u) =
|
|
516
|
+
\left \{ \begin{matrix}
|
|
517
|
+
12.92 \mbox{ } u, & \mbox{if } u \le 0.0031308 \\
|
|
518
|
+
1.055 \mbox{ } u^{1/2.4} - 0.055, & \mbox{if } u > 0.0031308 \\
|
|
519
|
+
\end{matrix} \right.`
|
|
520
|
+
|
|
521
|
+
:param X: X coordinate [-]
|
|
522
|
+
:type X: float or np.ndarray
|
|
523
|
+
|
|
524
|
+
:param Y: Y coordinate [-]
|
|
525
|
+
:type Y: float or np.ndarray
|
|
526
|
+
|
|
527
|
+
:param Z: Z coordinate [-]
|
|
528
|
+
:type Z: float or np.ndarray
|
|
529
|
+
|
|
530
|
+
:param K: scaling factor (usually 1. or 100.) [-] (default to 1.)
|
|
531
|
+
:type K: float
|
|
532
|
+
|
|
533
|
+
:param sRGB_scale: the user can choose one of the following... 'norm' or '8bit'
|
|
534
|
+
:type sRGB_scale:: str (default to 'norm')
|
|
535
|
+
|
|
536
|
+
| K = 1. for CIE XYZ coordinates in range [0, 1]
|
|
537
|
+
| K = 100. for CIE XYZ coordinates in range [0, 100]
|
|
538
|
+
|
|
539
|
+
| 'norm' for sRGB coordinates in range [0,1] (normalized scale)
|
|
540
|
+
| '8bit' for sRGB coordinates in range [0, 255] (8-bit scale)
|
|
541
|
+
|
|
542
|
+
:return: - **R** (*float or np.ndarray*) – R coordinate [-]
|
|
543
|
+
- **G** (*float or np.ndarray*) – G coordinate [-]
|
|
544
|
+
- **B** (*float or np.ndarray*) – B coordinate [-]
|
|
545
|
+
'''
|
|
546
|
+
|
|
547
|
+
M = transf_matrix_sRGB_linear_from_XYZ()
|
|
548
|
+
|
|
549
|
+
if isinstance(X, np.ndarray) == True and \
|
|
550
|
+
isinstance(Y, np.ndarray) == True and \
|
|
551
|
+
isinstance(Z, np.ndarray) == True:
|
|
552
|
+
if len(X) - len(Y) != 0 or len(X) - len(Z) != 0:
|
|
553
|
+
msg = 'X, Y and Z must have the same length.'
|
|
554
|
+
raise Exception(msg)
|
|
555
|
+
R, G, B = np.zeros((3, len(X)))
|
|
556
|
+
for i in range(len(X)):
|
|
557
|
+
R_linear, G_linear, B_linear = np.clip(np.matmul(M, np.array([X[i]/K, Y[i]/K, Z[i]/K])), 0, 1)
|
|
558
|
+
R[i], G[i], B[i] = nonlinear_corr_sRGB(np.array([R_linear, G_linear, B_linear]))
|
|
559
|
+
elif isinstance(X, (int, float)) == True and \
|
|
560
|
+
isinstance(Y, (int, float)) == True and \
|
|
561
|
+
isinstance(Z, (int, float)):
|
|
562
|
+
R_linear, G_linear, B_linear = np.clip(np.matmul(M, np.array([X/K, Y/K, Z/K])), 0 , 1)
|
|
563
|
+
R, G, B = nonlinear_corr_sRGB(np.array([R_linear, G_linear, B_linear]))
|
|
564
|
+
else:
|
|
565
|
+
msg = 'X, Y and Z must be int, float or np.ndarray.'
|
|
566
|
+
raise Exception(msg)
|
|
567
|
+
|
|
568
|
+
if sRGB_scale == 'norm':
|
|
569
|
+
pass
|
|
570
|
+
elif sRGB_scale == '8bit':
|
|
571
|
+
scaling = 255
|
|
572
|
+
R, G, B = np.round(scaling*np.array([R, G, B]))
|
|
573
|
+
else:
|
|
574
|
+
msg = 'The input sRGB_scale = {} is not valid.'.format(sRGB_scale)
|
|
575
|
+
raise Exception(msg)
|
|
576
|
+
|
|
577
|
+
return R, G, B
|
|
578
|
+
|
|
579
|
+
def XYZ_from_sRGB(R, G, B, K = 1., sRGB_scale = 'norm'):
|
|
580
|
+
r'''
|
|
581
|
+
| Calculate CIE XYZ coordinates from sRGB coordinates.
|
|
582
|
+
| The obtained CIE XYZ coordinates are respective to the standard illuminant D65 and the
|
|
583
|
+
| 2 degree standard observer.
|
|
584
|
+
| For details please check Stokes et al. [S*96] and IEC [IEC99].
|
|
585
|
+
|
|
586
|
+
:math:`\begin{bmatrix}
|
|
587
|
+
X \\
|
|
588
|
+
Y \\
|
|
589
|
+
Z
|
|
590
|
+
\end{bmatrix}
|
|
591
|
+
=
|
|
592
|
+
\mathcal{M}^{-1}
|
|
593
|
+
\begin{bmatrix}
|
|
594
|
+
R_{linear} \\
|
|
595
|
+
G_{linear} \\
|
|
596
|
+
B_{linear}
|
|
597
|
+
\end{bmatrix}`
|
|
598
|
+
|
|
599
|
+
in which
|
|
600
|
+
|
|
601
|
+
:math:`\begin{bmatrix}
|
|
602
|
+
R_{linear} \\
|
|
603
|
+
G_{linear} \\
|
|
604
|
+
B_{linear}
|
|
605
|
+
\end{bmatrix}
|
|
606
|
+
=
|
|
607
|
+
\begin{bmatrix}
|
|
608
|
+
\gamma^{-1}(R) \\
|
|
609
|
+
\gamma^{-1}(G) \\
|
|
610
|
+
\gamma^{-1}(B)
|
|
611
|
+
\end{bmatrix}`
|
|
612
|
+
|
|
613
|
+
and
|
|
614
|
+
|
|
615
|
+
:math:`\gamma^{-1}(u) =
|
|
616
|
+
\left \{ \begin{matrix}
|
|
617
|
+
u/12.92, & \mbox{if } u \le 0.04045 \\
|
|
618
|
+
[(u + 0.055)/1.055]^{2.4}, & \mbox{if } u > 0.04045 \\
|
|
619
|
+
\end{matrix} \right.`
|
|
620
|
+
|
|
621
|
+
:param R: R coordinate [-]
|
|
622
|
+
:type R: float or np.ndarray
|
|
623
|
+
|
|
624
|
+
:param G: G coordinate [-]
|
|
625
|
+
:type G: float or np.ndarray
|
|
626
|
+
|
|
627
|
+
:param B: B coordinate [-]
|
|
628
|
+
:type B: float or np.ndarray
|
|
629
|
+
|
|
630
|
+
:param K: scaling factor (usually 1. or 100.) [-] (default to 1.)
|
|
631
|
+
:type K: float
|
|
632
|
+
|
|
633
|
+
:param sRGB_scale: the user can choose one of the following... 'norm' or '8bit'
|
|
634
|
+
:type sRGB_scale:: str (default to 'norm')
|
|
635
|
+
|
|
636
|
+
| K = 1. for CIE XYZ coordinates in range [0, 1]
|
|
637
|
+
| K = 100. for CIE XYZ coordinates in range [0, 100]
|
|
638
|
+
|
|
639
|
+
| 'norm' for sRGB coordinates in range [0,1] (normalized scale)
|
|
640
|
+
| '8bit' for sRGB coordinates in range [0, 255] (8-bit scale)
|
|
641
|
+
|
|
642
|
+
:return: - **X** (*float or np.ndarray*) – X coordinate [-]
|
|
643
|
+
- **Y** (*float or np.ndarray*) – Y coordinate [-]
|
|
644
|
+
- **Z** (*float or np.ndarray*) – Z coordinate [-]
|
|
645
|
+
'''
|
|
646
|
+
|
|
647
|
+
if sRGB_scale == 'norm':
|
|
648
|
+
pass
|
|
649
|
+
elif sRGB_scale == '8bit':
|
|
650
|
+
scaling = 255
|
|
651
|
+
R, G, B = np.array([R, G, B])/scaling
|
|
652
|
+
else:
|
|
653
|
+
msg = 'The input sRGB_scale = {} is not valid.'.format(sRGB_scale)
|
|
654
|
+
raise Exception(msg)
|
|
655
|
+
|
|
656
|
+
inv_M = np.round(np.linalg.inv(transf_matrix_sRGB_linear_from_XYZ()), 4)
|
|
657
|
+
|
|
658
|
+
if isinstance(R, np.ndarray) == True and \
|
|
659
|
+
isinstance(G, np.ndarray) == True and \
|
|
660
|
+
isinstance(B, np.ndarray) == True:
|
|
661
|
+
if len(R) - len(G) != 0 or len(R) - len(B) != 0:
|
|
662
|
+
msg = 'R, G and B must have the same length.'
|
|
663
|
+
raise Exception(msg)
|
|
664
|
+
X, Y, Z = np.zeros((3, len(R)))
|
|
665
|
+
for i in range(len(R)):
|
|
666
|
+
R_linear, G_linear, B_linear = inv_nonlinear_corr_sRGB(np.array([R[i], G[i], B[i]]))
|
|
667
|
+
X[i], Y[i], Z[i] = np.matmul(inv_M, np.array([R_linear, G_linear, B_linear]))
|
|
668
|
+
elif isinstance(R, (int, float)) == True and \
|
|
669
|
+
isinstance(G, (int, float)) == True and \
|
|
670
|
+
isinstance(B, (int, float)):
|
|
671
|
+
R_linear, G_linear, B_linear = inv_nonlinear_corr_sRGB(np.array([R, G, B]))
|
|
672
|
+
X, Y, Z = np.matmul(inv_M, np.array([R_linear, G_linear, B_linear]))
|
|
673
|
+
else:
|
|
674
|
+
msg = 'R, G and B must be int, float or np.ndarray.'
|
|
675
|
+
raise Exception(msg)
|
|
676
|
+
|
|
677
|
+
return X*K, Y*K, Z*K
|
|
678
|
+
|
|
679
|
+
def f_Lab_from_XYZ(u):
|
|
680
|
+
r'''
|
|
681
|
+
| The function :math:`f(u)` used to calculate CIE L*a*b* coordinates from CIE XYZ coordinates
|
|
682
|
+
| (see function :meth:`skinoptics.colors.Lab_from_XYZ`).
|
|
683
|
+
|
|
684
|
+
:math:`f(u) = \left\{
|
|
685
|
+
\begin{matrix}
|
|
686
|
+
\sqrt[3]{u}, & \mbox{if } u > \left(\frac{6}{29}\right)^3 \\
|
|
687
|
+
\frac{1}{3}\left(\frac{29}{6}\right)^2 u + \frac{4}{29}, & \mbox{if } u \le \left(\frac{6}{29}\right)^3
|
|
688
|
+
\end{matrix}\right.`
|
|
689
|
+
|
|
690
|
+
:param u: X/Xn, Y/Yn or Z/Zn ratio[-]
|
|
691
|
+
:type u: float or np.ndarray
|
|
692
|
+
|
|
693
|
+
:return: - **f** (*float or np.ndarray*) – evaluated function [-]
|
|
694
|
+
'''
|
|
695
|
+
|
|
696
|
+
delta = 6./29.
|
|
697
|
+
if isinstance(u, np.ndarray) == True:
|
|
698
|
+
f = np.zeros(len(u))
|
|
699
|
+
for i in range(len(u)):
|
|
700
|
+
if u[i] > delta**3.:
|
|
701
|
+
f[i] = np.cbrt(u[i])
|
|
702
|
+
else:
|
|
703
|
+
f[i] = u[i]/3./delta**2. + 4./29.
|
|
704
|
+
elif isinstance(u, (int, float)) == True:
|
|
705
|
+
if u > delta**3.:
|
|
706
|
+
f = np.cbrt(u)
|
|
707
|
+
else:
|
|
708
|
+
f = u/3./delta**2. + 4./29.
|
|
709
|
+
else:
|
|
710
|
+
msg = 'u must be int, float or np.ndarray.'
|
|
711
|
+
raise Exception(msg)
|
|
712
|
+
|
|
713
|
+
return f
|
|
714
|
+
|
|
715
|
+
def inv_f_Lab_from_XYZ(u):
|
|
716
|
+
r'''
|
|
717
|
+
The :math:`f^{-1}(u)` function, i.e. the inverse of the :math:`f(u)` function :meth:`skinoptics.colors.f_Lab_from_XYZ`.
|
|
718
|
+
|
|
719
|
+
:math:`f^{-1}(u) = \left\{
|
|
720
|
+
\begin{matrix}
|
|
721
|
+
u^3, & \mbox{if } u > \frac{6}{29} \\
|
|
722
|
+
3 \mbox{ } \left(\frac{6}{29}\right)^2\left(u - \frac{4}{29} \right), & \mbox{if } u \le \frac{6}{29}
|
|
723
|
+
\end{matrix}\right.`
|
|
724
|
+
|
|
725
|
+
:param u: function variable [-]
|
|
726
|
+
:type u: float or np.ndarray
|
|
727
|
+
|
|
728
|
+
:return: - **f** (*float or np.ndarray*) – evaluated function [-]
|
|
729
|
+
'''
|
|
730
|
+
|
|
731
|
+
delta = 6./29.
|
|
732
|
+
if isinstance(u, np.ndarray) == True:
|
|
733
|
+
inv_f = np.zeros(len(u))
|
|
734
|
+
for i in range(len(u)):
|
|
735
|
+
if u[i] > delta:
|
|
736
|
+
inv_f[i] = u[i]**3
|
|
737
|
+
else:
|
|
738
|
+
inv_f[i] = 3.*delta**2.*(u[i] - 4./29.)
|
|
739
|
+
elif isinstance(u, (int, float)) == True:
|
|
740
|
+
if u > delta:
|
|
741
|
+
inv_f = u**3
|
|
742
|
+
else:
|
|
743
|
+
inv_f = 3.*delta**2.*(u - 4./29.)
|
|
744
|
+
else:
|
|
745
|
+
msg = 'u must be int, float or np.ndarray.'
|
|
746
|
+
raise Exception(msg)
|
|
747
|
+
|
|
748
|
+
return inv_f
|
|
749
|
+
|
|
750
|
+
def Lab_from_XYZ(X, Y, Z, illuminant = 'D65', observer = '10o', K = 1.):
|
|
751
|
+
r'''
|
|
752
|
+
| Calculate CIE L*a*b* coordinates from CIE XYZ coordinates.
|
|
753
|
+
| CIE XYZ and CIE L*a*b* coordinates must be for the same standard illuminant and standard observer.
|
|
754
|
+
| For detailts please check CIE [CIE04], Schanda 2006 [S06], ISO/CIE [IC08] and Hunt & Pointer 2011 [HP11].
|
|
755
|
+
|
|
756
|
+
| :math:`L^* = 116 \mbox{ } f(Y/Y_n) - 16`
|
|
757
|
+
| :math:`a^* = 500 \mbox{ } [f(X/X_n) - f(Y/Y_n)]`
|
|
758
|
+
| :math:`b^* = 200 \mbox{ } [f(Y/Y_n) - f(Z/Z_n)]`
|
|
759
|
+
|
|
760
|
+
in which (:math:`X_n`, :math:`Y_n`, :math:`Z_n`) is the white point and
|
|
761
|
+
|
|
762
|
+
:math:`f(u) = \left\{
|
|
763
|
+
\begin{matrix}
|
|
764
|
+
\sqrt[3]{u}, & \mbox{if } u > \left(\frac{6}{29}\right)^3 \\
|
|
765
|
+
\frac{1}{3}\left(\frac{29}{6}\right)^2 u + \frac{4}{29}, & \mbox{if } u \le \left(\frac{6}{29}\right)^3
|
|
766
|
+
\end{matrix}\right.`
|
|
767
|
+
|
|
768
|
+
:param X: X coordinate [-]
|
|
769
|
+
:type X: float or np.ndarray
|
|
770
|
+
|
|
771
|
+
:param Y: Y coordinate [-]
|
|
772
|
+
:type Y: float or np.ndarray
|
|
773
|
+
|
|
774
|
+
:param Z: Z coordinate [-]
|
|
775
|
+
:type Z: float or np.ndarray
|
|
776
|
+
|
|
777
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
778
|
+
:type illuminant: str
|
|
779
|
+
|
|
780
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
781
|
+
:type observer: str
|
|
782
|
+
|
|
783
|
+
:param K: scaling factor (usually 1. or 100.) [-] (default to 1.)
|
|
784
|
+
:type K: float
|
|
785
|
+
|
|
786
|
+
| 'A' refers to the CIE standard illuminant A
|
|
787
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
788
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
789
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
790
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
791
|
+
|
|
792
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
793
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
794
|
+
|
|
795
|
+
| K = 1. for CIE XYZ coordinates in range [0, 1]
|
|
796
|
+
| K = 100. for CIE XYZ coordinates in range [0, 100]
|
|
797
|
+
|
|
798
|
+
:return: - **L** (*float or np.ndarray*) – L* coordinate [-]
|
|
799
|
+
- **a** (*float or np.ndarray*) – a* coordinate [-]
|
|
800
|
+
- **b** (*float or np.ndarray*) – b* coordinate [-]
|
|
801
|
+
'''
|
|
802
|
+
|
|
803
|
+
Xn, Yn, Zn = XYZ_wp(illuminant = illuminant, observer = observer, K = K)
|
|
804
|
+
f = f_Lab_from_XYZ
|
|
805
|
+
|
|
806
|
+
if isinstance(X, np.ndarray) == True and \
|
|
807
|
+
isinstance(Y, np.ndarray) == True and \
|
|
808
|
+
isinstance(Z, np.ndarray) == True:
|
|
809
|
+
if len(X) - len(Y) != 0 or len(X) - len(Z) != 0:
|
|
810
|
+
msg = 'X, Y and Z must have the same length.'
|
|
811
|
+
raise Exception(msg)
|
|
812
|
+
L, a, b = np.zeros((3, len(X)))
|
|
813
|
+
for i in range(len(X)):
|
|
814
|
+
L[i] = 116.*f(Y[i]/Yn) - 16.
|
|
815
|
+
a[i] = 500.*(f(X[i]/Xn) - f(Y[i]/Yn))
|
|
816
|
+
b[i] = 200.*(f(Y[i]/Yn) - f(Z[i]/Zn))
|
|
817
|
+
elif isinstance(X, (int, float)) == True and \
|
|
818
|
+
isinstance(Y, (int, float)) == True and \
|
|
819
|
+
isinstance(Z, (int, float)):
|
|
820
|
+
L = 116.*f(Y/Yn) - 16.
|
|
821
|
+
a = 500.*(f(X/Xn) - f(Y/Yn))
|
|
822
|
+
b = 200.*(f(Y/Yn) - f(Z/Zn))
|
|
823
|
+
else:
|
|
824
|
+
msg = 'X, Y and Z must be int, float or np.ndarray.'
|
|
825
|
+
raise Exception(msg)
|
|
826
|
+
|
|
827
|
+
return L, a, b
|
|
828
|
+
|
|
829
|
+
def XYZ_from_Lab(L, a, b, illuminant = 'D65', observer = '10o', K = 1.):
|
|
830
|
+
r'''
|
|
831
|
+
| Calculate CIE XYZ coordinates from CIE L*a*b* coordinates.
|
|
832
|
+
| CIE XYZ and CIE L*a*b* coordinates must be for the same standard illuminant and standard observer.
|
|
833
|
+
| For detailts please check CIE [CIE04], Schanda 2006 [S06] and Hunt & Pointer 2011 [HP11].
|
|
834
|
+
|
|
835
|
+
| :math:`X = f^{-1}[(L^* + 16)/116 + a^*/500] \mbox{ } X_n`
|
|
836
|
+
| :math:`Y = f^{-1}[(L^* + 16)/116] \mbox{ } Y_n`
|
|
837
|
+
| :math:`Z = f^{-1}[(L^* + 16)/116 - b^*/200] \mbox{ } Z_n`
|
|
838
|
+
|
|
839
|
+
in which (:math:`X_n`, :math:`Y_n`, :math:`Z_n`) is the white point and
|
|
840
|
+
|
|
841
|
+
:math:`f^{-1}(u) = \left\{
|
|
842
|
+
\begin{matrix}
|
|
843
|
+
u^3, & \mbox{if } u > \frac{6}{29} \\
|
|
844
|
+
3 \mbox{ } \left(\frac{6}{29}\right)^2\left(u - \frac{4}{29} \right), & \mbox{if } u \le \frac{6}{29}
|
|
845
|
+
\end{matrix}\right.`
|
|
846
|
+
|
|
847
|
+
:param L: L* coordinate [-] (must be in range [0, 100])
|
|
848
|
+
:type L: float or np.ndarray
|
|
849
|
+
|
|
850
|
+
:param a: a* coordinate [-]
|
|
851
|
+
:type a: float or np.ndarray
|
|
852
|
+
|
|
853
|
+
:param b: b* coordinate [-]
|
|
854
|
+
:type b: float or np.ndarray
|
|
855
|
+
|
|
856
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
857
|
+
:type illuminant: str
|
|
858
|
+
|
|
859
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
860
|
+
:type observer: str
|
|
861
|
+
|
|
862
|
+
:param K: scaling factor (usually 1. or 100.) [-] (default to 1.)
|
|
863
|
+
:type K: float
|
|
864
|
+
|
|
865
|
+
| 'A' refers to the CIE standard illuminant A
|
|
866
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
867
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
868
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
869
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
870
|
+
|
|
871
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
872
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
873
|
+
|
|
874
|
+
| K = 1. for CIE XYZ coordinates in range [0, 1]
|
|
875
|
+
| K = 100. for CIE XYZ coordinates in range [0, 100]
|
|
876
|
+
|
|
877
|
+
:return: - **X** (*float or np.ndarray*) – X* coordinate [-]
|
|
878
|
+
- **Y** (*float or np.ndarray*) – Y* coordinate [-]
|
|
879
|
+
- **Z** (*float or np.ndarray*) – Z* coordinate [-]
|
|
880
|
+
'''
|
|
881
|
+
|
|
882
|
+
if isinstance(L, np.ndarray) == True:
|
|
883
|
+
if np.any(L < 0) or np.any(L > 100):
|
|
884
|
+
msg = 'At least one element in the input L is out of the range [0, 100].'
|
|
885
|
+
raise Exception(msg)
|
|
886
|
+
else:
|
|
887
|
+
if L < 0 or L > 100:
|
|
888
|
+
msg = 'The input L = {} is out of the range [0, 100].'.format(L)
|
|
889
|
+
raise Exception(msg)
|
|
890
|
+
|
|
891
|
+
Xn, Yn, Zn = XYZ_wp(illuminant = illuminant, observer = observer, K = K)
|
|
892
|
+
inv_f = inv_f_Lab_from_XYZ
|
|
893
|
+
|
|
894
|
+
if isinstance(L, np.ndarray) == True and \
|
|
895
|
+
isinstance(a, np.ndarray) == True and \
|
|
896
|
+
isinstance(b, np.ndarray) == True:
|
|
897
|
+
if len(L) - len(a) != 0 or len(L) - len(b) != 0:
|
|
898
|
+
msg = 'L, a and b must have the same length.'
|
|
899
|
+
raise Exception(msg)
|
|
900
|
+
X, Y, Z = np.zeros((3, len(L)))
|
|
901
|
+
for i in range(len(L)):
|
|
902
|
+
X[i] = Xn*inv_f((L[i] + 16.)/116. + a[i]/500.)
|
|
903
|
+
Y[i] = Yn*inv_f((L[i] + 16.)/116.)
|
|
904
|
+
Z[i] = Zn*inv_f((L[i] + 16.)/116. - b[i]/200.)
|
|
905
|
+
elif isinstance(L, (int, float)) == True and \
|
|
906
|
+
isinstance(a, (int, float)) == True and \
|
|
907
|
+
isinstance(b, (int, float)):
|
|
908
|
+
X = Xn*inv_f((L + 16.)/116. + a/500.)
|
|
909
|
+
Y = Yn*inv_f((L + 16.)/116.)
|
|
910
|
+
Z = Zn*inv_f((L + 16.)/116. - b/200.)
|
|
911
|
+
else:
|
|
912
|
+
msg = 'L, a and b must be float or np.ndarray.'
|
|
913
|
+
raise Exception(msg)
|
|
914
|
+
|
|
915
|
+
return X, Y, Z
|
|
916
|
+
|
|
917
|
+
def chroma(a, b):
|
|
918
|
+
r'''
|
|
919
|
+
Calculate the chroma C* from a* and b* coordinates.
|
|
920
|
+
|
|
921
|
+
:math:`C^* = \sqrt{a^{*2} + b^{*2}}`
|
|
922
|
+
|
|
923
|
+
:param a: a* coordinate [-]
|
|
924
|
+
:type a: float or np.ndarray
|
|
925
|
+
|
|
926
|
+
:param b: b* coordinate [-]
|
|
927
|
+
:type b: float or np.ndarray
|
|
928
|
+
|
|
929
|
+
:return: - **chroma** (*float or np.ndarray*) – chroma [-]
|
|
930
|
+
'''
|
|
931
|
+
|
|
932
|
+
return np.sqrt(a**2. + b**2.)
|
|
933
|
+
|
|
934
|
+
def hue(a, b):
|
|
935
|
+
r'''
|
|
936
|
+
Calculate the hue angle h* from a* and b* coordinates.
|
|
937
|
+
|
|
938
|
+
:math:`h^* = \mbox{arctan2 } (b^*, a^*) \times \frac{180}{\pi}`
|
|
939
|
+
|
|
940
|
+
:param a: a* coordinate [-]
|
|
941
|
+
:type a: float or np.ndarray
|
|
942
|
+
|
|
943
|
+
:param b: b* coordinate [-]
|
|
944
|
+
:type b: float or np.ndarray
|
|
945
|
+
|
|
946
|
+
:return: - **hue** (*float or np.ndarray*) – hue angle [degrees] (in range [0, 360])
|
|
947
|
+
'''
|
|
948
|
+
|
|
949
|
+
hue = np.arctan2(b,a)*180./np.pi
|
|
950
|
+
|
|
951
|
+
hue_shape = hue.shape
|
|
952
|
+
hue_flatten = hue.flatten()
|
|
953
|
+
|
|
954
|
+
if isinstance(hue, np.ndarray) == True:
|
|
955
|
+
for i in hue_flatten:
|
|
956
|
+
if i < 0:
|
|
957
|
+
i += 360
|
|
958
|
+
hue = hue_flatten.reshape(hue_shape)
|
|
959
|
+
elif isinstance(hue, (int, float)) == True:
|
|
960
|
+
if hue < 0:
|
|
961
|
+
hue += 360
|
|
962
|
+
|
|
963
|
+
return hue
|
|
964
|
+
|
|
965
|
+
def ITA(L, b, L0 = 50.):
|
|
966
|
+
r'''
|
|
967
|
+
| Calculate the Individual Typology Angle (ITA) from L* and b* coordinates.
|
|
968
|
+
| For details please check Chardon, Cretois & Hourseau 1991 [CCH91], Del Bino et al. 2006 [D*06],
|
|
969
|
+
| Del Bino & Bernerd 2013 [DB13] and Ly et al. 2020 [L*20].
|
|
970
|
+
|
|
971
|
+
:math:`\mbox{ITA} = \arctan\left(\frac{L^*-L_0^*}{b^*}\right) \times \frac{180}{\pi}`
|
|
972
|
+
|
|
973
|
+
:param L: L* coordinate [-]
|
|
974
|
+
:type L: float or np.ndarray
|
|
975
|
+
|
|
976
|
+
:param b: b* coordinate [-]
|
|
977
|
+
:type b: float or np.ndarray
|
|
978
|
+
|
|
979
|
+
:param L0: L0 coordinate [-] (default to 50.)
|
|
980
|
+
:type L0: float
|
|
981
|
+
|
|
982
|
+
:return: - **ITA** (*float or np.ndarray*) – Individual Typology Angle [degrees]
|
|
983
|
+
'''
|
|
984
|
+
|
|
985
|
+
return np.arctan((L - L0)/b)*180./np.pi
|
|
986
|
+
|
|
987
|
+
def ITA_class(ITA):
|
|
988
|
+
r'''
|
|
989
|
+
| Skin color classification based on the Individual Typology Angle :meth:`skinoptics.colors.ITA`.
|
|
990
|
+
| For details please check Chardon, Cretois & Hourseau 1991 [CCH91], Del Bino et al. 2006 [D*06],
|
|
991
|
+
| Del Bino & Bernerd 2013 [DB13] and Ly et al. [L*20].
|
|
992
|
+
|
|
993
|
+
+---------------------------+-----------------------------------------------+
|
|
994
|
+
| skin color classification | ITA range |
|
|
995
|
+
+===========================+===============================================+
|
|
996
|
+
| very light | ITA :math:`> 55^\circ` |
|
|
997
|
+
+---------------------------+-----------------------------------------------+
|
|
998
|
+
| light | :math:`41^\circ <` ITA :math:`\le 55^\circ` |
|
|
999
|
+
+---------------------------+-----------------------------------------------+
|
|
1000
|
+
| intermediate | :math:`28^\circ <` ITA :math:`\le 41^\circ` |
|
|
1001
|
+
+---------------------------+-----------------------------------------------+
|
|
1002
|
+
| tan | :math:`10^\circ <` ITA :math:`\le 28^\circ` |
|
|
1003
|
+
+---------------------------+-----------------------------------------------+
|
|
1004
|
+
| brown | :math:`-30^\circ <` ITA :math:`\le 10^\circ` |
|
|
1005
|
+
+---------------------------+-----------------------------------------------+
|
|
1006
|
+
| dark | ITA :math:`\le -30^\circ` |
|
|
1007
|
+
+---------------------------+-----------------------------------------------+
|
|
1008
|
+
|
|
1009
|
+
:param ITA: Individual Typology Angle [degrees] (must be greater than -90 and less than 90)
|
|
1010
|
+
:type ITA: float or np.ndarray
|
|
1011
|
+
|
|
1012
|
+
:return: - **ITA_class** (*str or np.ndarray*) – skin color classification based on the Individual Typology Angle
|
|
1013
|
+
'''
|
|
1014
|
+
|
|
1015
|
+
if isinstance(ITA, np.ndarray) == True:
|
|
1016
|
+
if np.any(ITA < -90) or np.any(ITA > 90):
|
|
1017
|
+
msg = 'At least one element in the input ITA is out of the range [-90, 90].'
|
|
1018
|
+
raise Exception(msg)
|
|
1019
|
+
else:
|
|
1020
|
+
if ITA < -90 or ITA > 90:
|
|
1021
|
+
msg = 'The input ITA = {} is out of the range [-90, 90].'.format(ITA)
|
|
1022
|
+
raise Exception(msg)
|
|
1023
|
+
|
|
1024
|
+
if isinstance(ITA, np.ndarray) == True:
|
|
1025
|
+
ITA_class_list = ['']*len(ITA)
|
|
1026
|
+
for i in range(len(ITA)):
|
|
1027
|
+
if ITA[i] > 55:
|
|
1028
|
+
ITA_class_list[i] = 'very light'
|
|
1029
|
+
elif ITA[i] > 41 and ITA[i] <= 55:
|
|
1030
|
+
ITA_class_list[i] = 'light'
|
|
1031
|
+
elif ITA[i] > 28 and ITA[i] <= 41:
|
|
1032
|
+
ITA_class_list[i] = 'intermediate'
|
|
1033
|
+
elif ITA[i] > 10 and ITA[i] <= 28:
|
|
1034
|
+
ITA_class_list[i] = 'tan'
|
|
1035
|
+
elif ITA[i] > -30 and ITA[i] <= 10:
|
|
1036
|
+
ITA_class_list[i] = 'brown'
|
|
1037
|
+
else:
|
|
1038
|
+
ITA_class_list[i] = 'dark'
|
|
1039
|
+
ITA_class = np.array(ITA_class_list)
|
|
1040
|
+
else:
|
|
1041
|
+
if ITA > 55:
|
|
1042
|
+
ITA_class = 'very light'
|
|
1043
|
+
elif ITA > 41 and ITA <= 55:
|
|
1044
|
+
ITA_class = 'light'
|
|
1045
|
+
elif ITA > 28 and ITA <= 41:
|
|
1046
|
+
ITA_class = 'intermediate'
|
|
1047
|
+
elif ITA > 10 and ITA <= 28:
|
|
1048
|
+
ITA_class = 'tan'
|
|
1049
|
+
elif ITA > -30 and ITA <= 10:
|
|
1050
|
+
ITA_class = 'brown'
|
|
1051
|
+
else:
|
|
1052
|
+
ITA_class = 'dark'
|
|
1053
|
+
|
|
1054
|
+
return ITA_class
|
|
1055
|
+
|
|
1056
|
+
def Delta_L(L0, L1):
|
|
1057
|
+
r'''
|
|
1058
|
+
Calculate the lightness difference :math:`\Delta L^*` between a reference color lightness :math:`L^*_0`
|
|
1059
|
+
and a test color lightness :math:`L^*_1`.
|
|
1060
|
+
|
|
1061
|
+
:math:`\Delta L^* = L^*_1 - L^*_0`
|
|
1062
|
+
|
|
1063
|
+
:param L0: reference color L* coordinate [-]
|
|
1064
|
+
:type L0: float or np.ndarray
|
|
1065
|
+
|
|
1066
|
+
:param L1: test color L* coordinate [-]
|
|
1067
|
+
:type L1: float or np.ndarray
|
|
1068
|
+
|
|
1069
|
+
:return: - **delta_L** (*float or np.ndarray*) – lightness difference [-]
|
|
1070
|
+
'''
|
|
1071
|
+
|
|
1072
|
+
return L1 - L0
|
|
1073
|
+
|
|
1074
|
+
def Delta_a(a0, a1):
|
|
1075
|
+
r'''
|
|
1076
|
+
Calculate the difference :math:`\Delta a^*` between a reference color :math:`a^*_0` coordinate
|
|
1077
|
+
and a test color :math:`a^*_1` coordinate.
|
|
1078
|
+
|
|
1079
|
+
:math:`\Delta a^* = a^*_1 - a^*_0`
|
|
1080
|
+
|
|
1081
|
+
:param a0: reference color a* coordinate [-]
|
|
1082
|
+
:type a0: float or np.ndarray
|
|
1083
|
+
|
|
1084
|
+
:param a1: test color a* coordinate [-]
|
|
1085
|
+
:type a1: float or np.ndarray
|
|
1086
|
+
|
|
1087
|
+
:return: - **delta_a** (*float or np.ndarray*) – a* difference [-]
|
|
1088
|
+
'''
|
|
1089
|
+
|
|
1090
|
+
return a1 - a0
|
|
1091
|
+
|
|
1092
|
+
def Delta_b(b0, b1):
|
|
1093
|
+
r'''
|
|
1094
|
+
Calculate the difference :math:`\Delta b^*` between a reference color :math:`b^*_0` coordinate
|
|
1095
|
+
and a test color :math:`b^*_1` coordinate.
|
|
1096
|
+
|
|
1097
|
+
:math:`\Delta b^* = b^*_1 - b^*_0`
|
|
1098
|
+
|
|
1099
|
+
:param b0: reference color b* coordinate [-]
|
|
1100
|
+
:type b0: float or np.ndarray
|
|
1101
|
+
|
|
1102
|
+
:param b1: test color b* coordinate [-]
|
|
1103
|
+
:type b1: float or np.ndarray
|
|
1104
|
+
|
|
1105
|
+
:return: - **delta_b** (*float or np.ndarray*) – b* difference [-]
|
|
1106
|
+
'''
|
|
1107
|
+
|
|
1108
|
+
return b1 - b0
|
|
1109
|
+
|
|
1110
|
+
def Delta_E(L0, a0, b0, L1, a1, b1):
|
|
1111
|
+
r'''
|
|
1112
|
+
Calculate the CIELAB color difference :math:`\Delta E^*_{ab}` between
|
|
1113
|
+
a reference color (:math:`L^*_0`, :math:`a^*_0`, :math:`b^*_0`) and
|
|
1114
|
+
a test color (:math:`L^*_1`, :math:`a^*_1`, :math:`b^*_1`).
|
|
1115
|
+
|
|
1116
|
+
:math:`\Delta E^*_{ab} = \sqrt{(L^*_1 - L^*_0)^2 + (a^*_1 - a^*_0)^2 + (b^*_1 - b^*_0)^2}`
|
|
1117
|
+
|
|
1118
|
+
:param L0: reference color L* coordinate [-]
|
|
1119
|
+
:type L0: float or np.ndarray
|
|
1120
|
+
|
|
1121
|
+
:param a0: reference color a* coordinate [-]
|
|
1122
|
+
:type a0: float or np.ndarray
|
|
1123
|
+
|
|
1124
|
+
:param b0: reference color b* coordinate [-]
|
|
1125
|
+
:type b0: float or np.ndarray
|
|
1126
|
+
|
|
1127
|
+
:param L1: test color L* coordinate [-]
|
|
1128
|
+
:type L1: float or np.ndarray
|
|
1129
|
+
|
|
1130
|
+
:param a1: test color a* coordinate [-]
|
|
1131
|
+
:type a1: float or np.ndarray
|
|
1132
|
+
|
|
1133
|
+
:param b1: test color b* coordinate [-]
|
|
1134
|
+
:type b1: float or np.ndarray
|
|
1135
|
+
|
|
1136
|
+
:return: - **delta_E** (*float or np.ndarray*) – CIELAB color difference [-]
|
|
1137
|
+
'''
|
|
1138
|
+
|
|
1139
|
+
return np.sqrt(Delta_L(L0 = L0, L1 = L1)**2 + Delta_a(a0 = a0, a1 = a1)**2 + Delta_b(b0 = b0, b1 = b1)**2)
|
|
1140
|
+
|
|
1141
|
+
def Delta_E_00(L0, a0, b0, L1, a1, b1, kL = 1., kC = 1., kH = 1.):
|
|
1142
|
+
r'''
|
|
1143
|
+
Calculate the CIEDE2000 color difference :math:`\Delta E^*_{00}` between
|
|
1144
|
+
a reference color (:math:`L^*_0`, :math:`a^*_0`, :math:`b^*_0`) and
|
|
1145
|
+
a test color (:math:`L^*_1`, :math:`a^*_1`, :math:`b^*_1`).
|
|
1146
|
+
|
|
1147
|
+
:math:`\Delta E^*_{00} = \sqrt{\left(\frac{\Delta L'}{k_L S_L}\right)^2 + \left(\frac{\Delta C'}{k_C S_C}\right)^2 + \left(\frac{\Delta H'}{k_H S_H}\right)^2 + R_T \left(\frac{\Delta C'}{k_C S_C}\right) \left(\frac{\Delta H'}{k_H S_H}\right)}`
|
|
1148
|
+
|
|
1149
|
+
| For details please check Sharma, Wu & Dalal 2004 [SWD04] and ISO/CIE 2014 [IC14].
|
|
1150
|
+
|
|
1151
|
+
:param L0: reference color L* coordinate [-]
|
|
1152
|
+
:type L0: float or np.ndarray
|
|
1153
|
+
|
|
1154
|
+
:param a0: reference color a* coordinate [-]
|
|
1155
|
+
:type a0: float or np.ndarray
|
|
1156
|
+
|
|
1157
|
+
:param b0: reference color b* coordinate [-]
|
|
1158
|
+
:type b0: float or np.ndarray
|
|
1159
|
+
|
|
1160
|
+
:param L1: test color L* coordinate [-]
|
|
1161
|
+
:type L1: float or np.ndarray
|
|
1162
|
+
|
|
1163
|
+
:param a1: test color a* coordinate [-]
|
|
1164
|
+
:type a1: float or np.ndarray
|
|
1165
|
+
|
|
1166
|
+
:param b1: test color b* coordinate [-]
|
|
1167
|
+
:type b1: float or np.ndarray
|
|
1168
|
+
|
|
1169
|
+
:param kL: lightness parametric factor [-] (default to 1.)
|
|
1170
|
+
:type kL: float
|
|
1171
|
+
|
|
1172
|
+
:param kC: chroma parametric factor [-] (default to 1.)
|
|
1173
|
+
:type kC: float
|
|
1174
|
+
|
|
1175
|
+
:param kH: hue parametric factor [-] (default to 1.)
|
|
1176
|
+
:type kH: float
|
|
1177
|
+
|
|
1178
|
+
:return: - **delta_E_00** (*float or np.ndarray*) – CIEDE2000 color difference [-]
|
|
1179
|
+
'''
|
|
1180
|
+
|
|
1181
|
+
C0 = chroma(a = a0, b = b0)
|
|
1182
|
+
C1 = chroma(a = a1, b = b1)
|
|
1183
|
+
|
|
1184
|
+
C_bar = np.mean((C0, C1))
|
|
1185
|
+
|
|
1186
|
+
G = 0.5*(1 - np.sqrt(C_bar**7/(C_bar**7 + 25**7)))
|
|
1187
|
+
|
|
1188
|
+
Ll0 = L0
|
|
1189
|
+
al0 = (1 + G)*a0
|
|
1190
|
+
bl0 = b0
|
|
1191
|
+
Cl0 = chroma(a = al0, b = bl0)
|
|
1192
|
+
hl0 = hue(a = al0, b = bl0)
|
|
1193
|
+
|
|
1194
|
+
Ll1 = L1
|
|
1195
|
+
al1 = (1 + G)*a1
|
|
1196
|
+
bl1 = b1
|
|
1197
|
+
Cl1 = chroma(a = al1, b = bl1)
|
|
1198
|
+
hl1 = hue(a = al1, b = bl1)
|
|
1199
|
+
|
|
1200
|
+
abs_diff_hl = np.abs(hl1 - hl0)
|
|
1201
|
+
|
|
1202
|
+
Delta_Ll = Ll1 - Ll0
|
|
1203
|
+
Delta_Cl = Cl1 - Cl0
|
|
1204
|
+
if isinstance(abs_diff_hl, np.ndarray) == True:
|
|
1205
|
+
Delta_hl = np.zeros(len(abs_diff_hl))
|
|
1206
|
+
for i in range(len(Delta_hl)):
|
|
1207
|
+
if Cl0[i]*Cl1[i] == 0:
|
|
1208
|
+
Delta_hl[i] = 0
|
|
1209
|
+
elif Cl0[i]*Cl1[i] != 0 and abs_diff_hl[i] <= 180:
|
|
1210
|
+
Delta_hl[i] = hl1[i] - hl0[i]
|
|
1211
|
+
elif Cl0[i]*Cl1[i] != 0 and hl1[i] - hl0[i] > 180:
|
|
1212
|
+
Delta_hl[i] = hl1[i] - hl0[i] - 360
|
|
1213
|
+
elif Cl0[i]*Cl1[i] != 0 and hl1[i] - hl0[i] < -180:
|
|
1214
|
+
Delta_hl[i] = hl1[i] - hl0[i] + 360
|
|
1215
|
+
elif isinstance(abs_diff_hl, (int, float)) == True:
|
|
1216
|
+
if Cl0*Cl1 == 0:
|
|
1217
|
+
Delta_hl = 0
|
|
1218
|
+
elif Cl0*Cl1 != 0 and abs_diff_hl <= 180:
|
|
1219
|
+
Delta_hl = hl1 - hl0
|
|
1220
|
+
elif Cl0*Cl1 != 0 and hl1 - hl0 > 180:
|
|
1221
|
+
Delta_hl = hl1 - hl0 - 360
|
|
1222
|
+
elif Cl0*Cl1 != 0 and hl1 - hl0 < -180:
|
|
1223
|
+
Delta_hl = hl1 - hl0 + 360
|
|
1224
|
+
else:
|
|
1225
|
+
msg = 'The input (L0, a0, b0, L1, a1, b1 = {} is not valid.'.format(np.array(L0, a0, b0, L1, a1, b1))
|
|
1226
|
+
raise Exception(msg)
|
|
1227
|
+
Delta_Hl = 2*np.sqrt(Cl0*Cl1)*np.sin(np.radians(Delta_hl/2))
|
|
1228
|
+
|
|
1229
|
+
Ll_bar = np.mean((Ll0, Ll1))
|
|
1230
|
+
Cl_bar = np.mean((Cl0, Cl1))
|
|
1231
|
+
if isinstance(abs_diff_hl, np.ndarray) == True:
|
|
1232
|
+
hl_bar = np.zeros(len(abs_diff_hl))
|
|
1233
|
+
for i in range(len(hl_bar)):
|
|
1234
|
+
if Cl0[i]*Cl1[i] == 0:
|
|
1235
|
+
hl_bar[i] = hl0[i] + hl1[i]
|
|
1236
|
+
elif Cl0[i]*Cl1[i] != 0 and abs_diff_hl[i] <= 180:
|
|
1237
|
+
hl_bar[i] = np.mean((hl0[i], hl1[i]))
|
|
1238
|
+
elif Cl0[i]*Cl1[i] != 0 and abs_diff_hl[i] > 180 and hl0[i] + hl1[i] < 360:
|
|
1239
|
+
hl_bar[i] = (hl0[i] + hl1[i] + 360)/2
|
|
1240
|
+
elif Cl0[i]*Cl1[i] != 0 and abs_diff_hl[i] > 180 and hl0[i] + hl1[i] >= 360:
|
|
1241
|
+
hl_bar[i] = (hl0[i] + hl1[i] - 360)/2
|
|
1242
|
+
elif isinstance(abs_diff_hl, (int, float)) == True:
|
|
1243
|
+
if Cl0*Cl1 == 0:
|
|
1244
|
+
hl_bar = hl0 + hl1
|
|
1245
|
+
elif Cl0*Cl1 != 0 and abs_diff_hl <= 180:
|
|
1246
|
+
hl_bar = np.mean((hl0, hl1))
|
|
1247
|
+
elif Cl0*Cl1 != 0 and abs_diff_hl > 180 and hl0 + hl1 < 360:
|
|
1248
|
+
hl_bar = (hl0 + hl1 + 360)/2
|
|
1249
|
+
elif Cl0*Cl1 != 0 and abs_diff_hl > 180 and hl0 + hl1 >= 360:
|
|
1250
|
+
hl_bar = (hl0 + hl1 - 360)/2
|
|
1251
|
+
else:
|
|
1252
|
+
msg = 'The input (L0, a0, b0, L1, a1, b1 = {} is not valid.'.format(np.array(L0, a0, b0, L1, a1, b1))
|
|
1253
|
+
raise Exception(msg)
|
|
1254
|
+
|
|
1255
|
+
T = 1 - 0.17*np.cos(np.radians(hl_bar - 30)) + 0.24*np.cos(np.radians(2*hl_bar)) \
|
|
1256
|
+
+ 0.32*np.cos(np.radians(3*hl_bar + 6)) - 0.2*np.cos(np.radians(4*hl_bar - 63))
|
|
1257
|
+
|
|
1258
|
+
SL = 1 + (0.015*(Ll_bar - 50)**2)/np.sqrt(20 + (Ll_bar - 50)**2)
|
|
1259
|
+
SC = 1 + 0.045*Cl_bar
|
|
1260
|
+
SH = 1 + 0.015*Cl_bar*T
|
|
1261
|
+
|
|
1262
|
+
Delta_theta = 30*np.exp(-((hl_bar - 275)/25)**2)
|
|
1263
|
+
RC = 2*np.sqrt(Cl_bar**7/(Cl_bar**7 + 25**7))
|
|
1264
|
+
RT = - np.sin(np.radians(2*Delta_theta))*RC
|
|
1265
|
+
|
|
1266
|
+
Delta_E_00_1st = (Delta_Ll/(kL*SL))**2
|
|
1267
|
+
Delta_E_00_2nd = (Delta_Cl/(kC*SC))**2
|
|
1268
|
+
Delta_E_00_3rd = (Delta_Hl/(kH*SH))**2
|
|
1269
|
+
Delta_E_00_4th = RT*(Delta_Cl/(kC*SC))*(Delta_Hl/(kH*SH))
|
|
1270
|
+
|
|
1271
|
+
return np.sqrt(Delta_E_00_1st + Delta_E_00_2nd + Delta_E_00_3rd + Delta_E_00_4th)
|
|
1272
|
+
|
|
1273
|
+
def EI(R_green, R_red):
|
|
1274
|
+
r'''
|
|
1275
|
+
| Calculate the Erythema Index (EI) from the reflectances on chosen green
|
|
1276
|
+
| (usually approx. 568 nm) and red bands (usually approx. 655 nm).
|
|
1277
|
+
| For details please check Takiwaki et al. 1994 [T*94] and Fullerton et al. 1996 [F*96].
|
|
1278
|
+
|
|
1279
|
+
:math:`\mbox{EI} = 100 \mbox{ } [\mbox{log}_{10}(R_\mbox{red}) - \mbox{log}_{10}(R_\mbox{green})]`
|
|
1280
|
+
|
|
1281
|
+
:param R_green: reflectance on a chosen green band [%]
|
|
1282
|
+
:type R_green: float or np.ndarray
|
|
1283
|
+
|
|
1284
|
+
:param R_red: reflectance on a chosen red band [%]
|
|
1285
|
+
:type R_red: float or np.ndarray
|
|
1286
|
+
|
|
1287
|
+
:return: - **EI** (*float or np.ndarray*) – Erythema Index [-]
|
|
1288
|
+
'''
|
|
1289
|
+
|
|
1290
|
+
return 100*(np.log10(R_red/100) - np.log10(R_green/100))
|
|
1291
|
+
|
|
1292
|
+
def MI(R_red):
|
|
1293
|
+
r'''
|
|
1294
|
+
| Calculate the Melanin Index (MI) from the reflectance on a chosen red band
|
|
1295
|
+
| (usually approx. 655 nm).
|
|
1296
|
+
| For details please check Takiwaki et al. 1994 [T*94] and Fullerton et al. 1996 [F*96].
|
|
1297
|
+
|
|
1298
|
+
:math:`\mbox{MI} = 100 \mbox{ } [-\mbox{log}_{10}(R_\mbox{red})]`
|
|
1299
|
+
|
|
1300
|
+
:param R_red: reflectance on a chosen red band [%]
|
|
1301
|
+
:type R_red: float or np.ndarray
|
|
1302
|
+
|
|
1303
|
+
:return: - **MI** (*float or np.ndarray* – Melanin Index [-]
|
|
1304
|
+
'''
|
|
1305
|
+
|
|
1306
|
+
return 100*(-np.log10(R_red/100))
|
|
1307
|
+
|
|
1308
|
+
def XYZ_from_spectrum(all_lambda, spectrum, lambda_min = 360., lambda_max = 830., lambda_step = 1.,
|
|
1309
|
+
illuminant = 'D65', observer = '10o', cmfs_model = 'CIE', K = 1., interp1d_kind = 'cubic'):
|
|
1310
|
+
r'''
|
|
1311
|
+
| Calculate the CIE XYZ coordinates from the reflectance spectrum :math:`R(\lambda)` or the
|
|
1312
|
+
| transmittance spectrum :math:`T(\lambda)` for a chosen standard illuminant and standard observer.
|
|
1313
|
+
| Integration using the composite trapezoid rule from 360 nm to 830 nm (as default).
|
|
1314
|
+
| If the wavelength array does not cover the whole region, a constant extrapolation is perfomed.
|
|
1315
|
+
| For details please check CIE [CIE04] (see their section 7).
|
|
1316
|
+
|
|
1317
|
+
| :math:`X = \frac{K}{N} \int_\lambda \mbox{ } R(\lambda) \mbox{ } S(\lambda) \mbox{ } \bar{x}(\lambda) \mbox{ } d\lambda`
|
|
1318
|
+
| :math:`Y = \frac{K}{N} \int_\lambda \mbox{ } R(\lambda) \mbox{ } S(\lambda) \mbox{ } \bar{y}(\lambda) \mbox{ } d\lambda`
|
|
1319
|
+
| :math:`Z = \frac{K}{N} \int_\lambda \mbox{ } R(\lambda) \mbox{ } S(\lambda) \mbox{ } \bar{z}(\lambda) \mbox{ } d\lambda`
|
|
1320
|
+
|
|
1321
|
+
in which
|
|
1322
|
+
|
|
1323
|
+
| :math:`N = \int_\lambda \mbox{ } S(\lambda) \mbox{ } \bar{y}(\lambda) \mbox{ } d\lambda`
|
|
1324
|
+
|
|
1325
|
+
The reflectance spectrum :math:`R(\lambda)` is replaced by the transmittance spectrum
|
|
1326
|
+
:math:`T(\lambda)` when dealing with color in some cases.
|
|
1327
|
+
|
|
1328
|
+
:param all_lambda: wavelength array
|
|
1329
|
+
:type all_lambda: np.ndarray
|
|
1330
|
+
|
|
1331
|
+
:param spectrum: reflectance or transmittance spectrum respective to the wavelength array [%]
|
|
1332
|
+
:type spectrum: np.ndarray
|
|
1333
|
+
|
|
1334
|
+
:param lambda_min: lower limit of summation/integration (minimum wavelength to take into account) [nm] (default to 360.)
|
|
1335
|
+
:type lambda_min: float
|
|
1336
|
+
|
|
1337
|
+
:param lambda_max: upper limit of summation/integration (maximum wavelength to take into account) [nm] (default to 830.)
|
|
1338
|
+
:type lambda_max: float
|
|
1339
|
+
|
|
1340
|
+
:param lambda_step: summation interval (wavelength step) [nm] (default to 1.)
|
|
1341
|
+
:type lambda_step: float
|
|
1342
|
+
|
|
1343
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
1344
|
+
:type illuminant: str
|
|
1345
|
+
|
|
1346
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
1347
|
+
:type observer: str
|
|
1348
|
+
|
|
1349
|
+
:param cmfs_model: the user can choose one of the following... 'CIE', 'Wyman_singlelobe' or 'Wyman_multilobe' (default to 'CIE')
|
|
1350
|
+
:type cmfs_model: str
|
|
1351
|
+
|
|
1352
|
+
:param K: scaling factor (usually 1. or 100.) [-] (default to 1.)
|
|
1353
|
+
:type K: float
|
|
1354
|
+
|
|
1355
|
+
:param interp1d_kind: kind argument of scipy.interpolation.interp1d (default to 'cubic' [CIE04] (see their section 7.2.1.1))
|
|
1356
|
+
:type interp1d_kind: str
|
|
1357
|
+
|
|
1358
|
+
| 'A' refers to the CIE standard illuminant A
|
|
1359
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
1360
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
1361
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
1362
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
1363
|
+
|
|
1364
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
1365
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
1366
|
+
|
|
1367
|
+
| 'CIE' for the linear interpolation of data from CIE datasets [CIE19a] [CIE19b]
|
|
1368
|
+
| 'Wyman_singlelobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.1)
|
|
1369
|
+
| 'Wyman_multilobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.2)
|
|
1370
|
+
|
|
1371
|
+
| K = 1. for CIE XYZ coordinates in range [0, 1]
|
|
1372
|
+
| K = 100. for CIE XYZ coordinates in range [0, 100]
|
|
1373
|
+
|
|
1374
|
+
:return: - **X** (*float*) – X coordinate [-]
|
|
1375
|
+
- **Y** (*float*) – Y coordinate [-]
|
|
1376
|
+
- **Z** (*float*) – Z coordinate [-]
|
|
1377
|
+
'''
|
|
1378
|
+
|
|
1379
|
+
x = np.arange(lambda_min, lambda_max + lambda_step, lambda_step)
|
|
1380
|
+
R_or_T_lambda = interp1d(all_lambda, spectrum/100, kind = interp1d_kind,
|
|
1381
|
+
bounds_error = False, fill_value = (spectrum[0]/100,
|
|
1382
|
+
spectrum[-1]/100))(x)
|
|
1383
|
+
S_lambda = rspd(x, illuminant = illuminant)
|
|
1384
|
+
xbar_lambda, ybar_lambda, zbar_lambda = cmfs(x, observer = observer, cmfs_model = cmfs_model)
|
|
1385
|
+
|
|
1386
|
+
y0 = S_lambda*ybar_lambda
|
|
1387
|
+
N = trapezoid(y0, x = x, dx = lambda_step)
|
|
1388
|
+
|
|
1389
|
+
R_or_T_lambda_times_S_lambda = R_or_T_lambda*S_lambda
|
|
1390
|
+
|
|
1391
|
+
y1 = R_or_T_lambda_times_S_lambda*xbar_lambda
|
|
1392
|
+
X = K/N*trapezoid(y1, x = x, dx = lambda_step)
|
|
1393
|
+
|
|
1394
|
+
y2 = R_or_T_lambda_times_S_lambda*ybar_lambda
|
|
1395
|
+
Y = K/N*trapezoid(y2, x = x, dx = lambda_step)
|
|
1396
|
+
|
|
1397
|
+
y3 = R_or_T_lambda_times_S_lambda*zbar_lambda
|
|
1398
|
+
Z = K/N*trapezoid(y3, x = x, dx = lambda_step)
|
|
1399
|
+
|
|
1400
|
+
return X, Y, Z
|
|
1401
|
+
|
|
1402
|
+
def sRGB_from_spectrum(all_lambda, spectrum, lambda_min = 360, lambda_max = 830, lambda_step = 1,
|
|
1403
|
+
cmfs_model = 'CIE', interp1d_kind = 'cubic', sRGB_scale = 'norm'):
|
|
1404
|
+
r'''
|
|
1405
|
+
| Calculate the sRGB coordinates from the reflectance or the transmittance spectrum.
|
|
1406
|
+
| First calculate CIE XYZ coordinates (respective to the standard illuminant D65 and
|
|
1407
|
+
| the 2 degree standard observer) from the spectrum and then calculate sRGB coordinates
|
|
1408
|
+
| from CIE XYZ coordinates (see functions :meth:`skinoptics.colors.sRGB_from_XYZ` and
|
|
1409
|
+
| :meth:`skinoptics.colors.XYZ_from_spectrum`).
|
|
1410
|
+
|
|
1411
|
+
:param all_lambda: wavelength array
|
|
1412
|
+
:type all_lambda: np.ndarray
|
|
1413
|
+
|
|
1414
|
+
:param spectrum: reflectance or transmittance spectrum respective to the wavelength array [%]
|
|
1415
|
+
:type spectrum: np.ndarray
|
|
1416
|
+
|
|
1417
|
+
:param lambda_min: lower limit of summation/integration (minimum wavelength to take into account) [nm] (default to 360.)
|
|
1418
|
+
:type lambda_min: float
|
|
1419
|
+
|
|
1420
|
+
:param lambda_max: upper limit of summation/integration (maximum wavelength to take into account) [nm] (default to 830.)
|
|
1421
|
+
:type lambda_max: float
|
|
1422
|
+
|
|
1423
|
+
:param lambda_step: summation interval (wavelength step) [nm] (default to 1.)
|
|
1424
|
+
:type lambda_step: float
|
|
1425
|
+
|
|
1426
|
+
:param cmfs_model: the user can choose one of the following... 'CIE', 'Wyman_singlelobe' or 'Wyman_multilobe' (default to 'CIE')
|
|
1427
|
+
:type cmfs_model: str
|
|
1428
|
+
|
|
1429
|
+
:param interp1d_kind: kind argument of scipy.interpolation.interp1d (default to 'cubic' [CIE04] (see their section 7.2.1.1))
|
|
1430
|
+
:type interp1d_kind: str
|
|
1431
|
+
|
|
1432
|
+
:param sRGB_scale: the user can choose one of the following... 'norm' or '8bit' (default to 'norm')
|
|
1433
|
+
:type sRGB_scale: str
|
|
1434
|
+
|
|
1435
|
+
| 'CIE' for the linear interpolation of data from CIE datasets [CIE19a] [CIE19b]
|
|
1436
|
+
| 'Wyman_singlelobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.1)
|
|
1437
|
+
| 'Wyman_multilobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.2)
|
|
1438
|
+
|
|
1439
|
+
| 'norm' for sRGB coordinates in range [0,1] (normalized scale)
|
|
1440
|
+
| '8bit' for sRGB coordinates in range [0, 255] (8-bit scale)
|
|
1441
|
+
|
|
1442
|
+
:return: - **R** (*float*) – R coordinate [-]
|
|
1443
|
+
- **G** (*float*) – G coordinate [-]
|
|
1444
|
+
- **B** (*float*) – B coordinate [-]
|
|
1445
|
+
'''
|
|
1446
|
+
|
|
1447
|
+
return sRGB_from_XYZ(*XYZ_from_spectrum(all_lambda = all_lambda, spectrum = spectrum,
|
|
1448
|
+
lambda_min = lambda_min, lambda_max = lambda_max,
|
|
1449
|
+
lambda_step = lambda_step,
|
|
1450
|
+
illuminant = 'D65', observer = '2o', cmfs_model = cmfs_model,
|
|
1451
|
+
K = 1., interp1d_kind = interp1d_kind),
|
|
1452
|
+
K = 1., sRGB_scale = sRGB_scale)
|
|
1453
|
+
|
|
1454
|
+
def Lab_from_spectrum(all_lambda, spectrum, lambda_min = 360, lambda_max = 830, lambda_step = 1,
|
|
1455
|
+
illuminant = 'D65', observer = '10o', cmfs_model = 'CIE', interp1d_kind = 'cubic'):
|
|
1456
|
+
r'''
|
|
1457
|
+
| Calculate the CIE L*a*b* coordinates from the reflectance or the transmittance spectrum.
|
|
1458
|
+
| First calculate CIE XYZ coordinates from the spectrum for a chosen standard illuminant
|
|
1459
|
+
| and standard observer and then calculate CIE L*a*b* coordinates from CIE XYZ coordinates
|
|
1460
|
+
| (see functions :meth:`skinoptics.colors.Lab_from_XYZ` and :meth:`skinoptics.colors.XYZ_from_spectrum`).
|
|
1461
|
+
|
|
1462
|
+
:param all_lambda: wavelength array
|
|
1463
|
+
:type all_lambda: np.ndarray
|
|
1464
|
+
|
|
1465
|
+
:param spectrum: reflectance or transmittance spectrum respective to the wavelength array [%]
|
|
1466
|
+
:type spectrum: np.ndarray
|
|
1467
|
+
|
|
1468
|
+
:param lambda_min: lower limit of summation/integration (minimum wavelength to take into account) [nm] (default to 360.)
|
|
1469
|
+
:type lambda_min: float
|
|
1470
|
+
|
|
1471
|
+
:param lambda_max: upper limit of summation/integration (maximum wavelength to take into account) [nm] (default to 830.)
|
|
1472
|
+
:type lambda_max: float
|
|
1473
|
+
|
|
1474
|
+
:param lambda_step: summation interval (wavelength step) [nm] (default to 1.)
|
|
1475
|
+
:type lambda_step: float
|
|
1476
|
+
|
|
1477
|
+
:param illuminant: the user can choose one of the following... 'A', 'D50', 'D55', 'D65' or 'D75'
|
|
1478
|
+
:type illuminant: str
|
|
1479
|
+
|
|
1480
|
+
:param observer: the user can choose one of the following... '2o' or '10o'
|
|
1481
|
+
:type observer: str
|
|
1482
|
+
|
|
1483
|
+
:param cmfs_model: the user can choose one of the following... 'CIE', 'Wyman_singlelobe' or 'Wyman_multilobe' (default to 'CIE')
|
|
1484
|
+
:type cmfs_model: str
|
|
1485
|
+
|
|
1486
|
+
:param interp1d_kind: kind argument of scipy.interpolation.interp1d (default to 'cubic' [CIE04] (see their section 7.2.1.1))
|
|
1487
|
+
:type interp1d_kind: str
|
|
1488
|
+
|
|
1489
|
+
| 'A' refers to the CIE standard illuminant A
|
|
1490
|
+
| 'D50' refers to the CIE standard illuminant D50
|
|
1491
|
+
| 'D55' refers to the CIE standard illuminant D55
|
|
1492
|
+
| 'D65' refers to the CIE standard illuminant D65
|
|
1493
|
+
| 'D75' refers to the CIE standard illuminant D75
|
|
1494
|
+
|
|
1495
|
+
| '2o' refers to the CIE 1931 2 degree standard observer
|
|
1496
|
+
| '10o' refers to the CIE 1964 10 degree standard observer
|
|
1497
|
+
|
|
1498
|
+
| 'CIE' for the linear interpolation of data from CIE datasets [CIE19a] [CIE19b]
|
|
1499
|
+
| 'Wyman_singlelobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.1)
|
|
1500
|
+
| 'Wyman_multilobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.2)
|
|
1501
|
+
|
|
1502
|
+
:return: - **L** (*float*) – L* coordinate [-]
|
|
1503
|
+
- **a** (*float*) – a* coordinate [-]
|
|
1504
|
+
- **b** (*float*) – b* coordinate [-]
|
|
1505
|
+
'''
|
|
1506
|
+
|
|
1507
|
+
return Lab_from_XYZ(*XYZ_from_spectrum(all_lambda = all_lambda, spectrum = spectrum,
|
|
1508
|
+
lambda_min = lambda_min, lambda_max = lambda_max,
|
|
1509
|
+
lambda_step = lambda_step,
|
|
1510
|
+
illuminant = illuminant, observer = observer, cmfs_model = cmfs_model,
|
|
1511
|
+
K = 1., interp1d_kind = interp1d_kind),
|
|
1512
|
+
illuminant = illuminant, observer = observer, K = 1.)
|
|
1513
|
+
|
|
1514
|
+
def sRGB_from_lambda0(lambda0, cmfs_model = 'CIE', sRGB_scale = 'norm'):
|
|
1515
|
+
r'''
|
|
1516
|
+
| Calculate the sRGB coordinates respective to the color of a monochromatic light
|
|
1517
|
+
| (single wavelength).
|
|
1518
|
+
|
|
1519
|
+
wavelength range: [360 nm, 830 nm]
|
|
1520
|
+
|
|
1521
|
+
:param lambda0: wavelength of the monochromatic light [nm]
|
|
1522
|
+
:type lambda0: float or np.ndarray
|
|
1523
|
+
|
|
1524
|
+
:param cmfs_model: the user can choose one of the following... 'CIE', 'Wyman_singlelobe' or 'Wyman_multilobe' (default to 'CIE')
|
|
1525
|
+
:type cmfs_model: str
|
|
1526
|
+
|
|
1527
|
+
:param sRGB_scale: the user can choose one of the following... 'norm' or '8bit' (default to 'norm')
|
|
1528
|
+
:type sRGB_scale: str
|
|
1529
|
+
|
|
1530
|
+
| 'CIE' for the linear interpolation of data from CIE datasets [CIE19a] [CIE19b]
|
|
1531
|
+
| 'Wyman_singlelobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.1)
|
|
1532
|
+
| 'Wyman_multilobe' for the functions from Wyman, Sloan & Shirley 2013 [WSS13] (section 2.2)
|
|
1533
|
+
|
|
1534
|
+
| 'norm' for sRGB coordinates in range [0,1] (normalized scale)
|
|
1535
|
+
| '8bit' for sRGB coordinates in range [0, 255] (8-bit scale)
|
|
1536
|
+
|
|
1537
|
+
:return: - **R** (*float or np.ndarray*) – R coordinate [-]
|
|
1538
|
+
- **G** (*float or np.ndarray*) – G coordinate [-]
|
|
1539
|
+
- **B** (*float or np.ndarray*) – B coordinate [-]
|
|
1540
|
+
'''
|
|
1541
|
+
|
|
1542
|
+
return sRGB_from_XYZ(*cmfs(lambda0, observer = '2o', cmfs_model = cmfs_model),
|
|
1543
|
+
K = 1., sRGB_scale = sRGB_scale)
|