# -*- coding: utf-8 -*-
"""
Module for (CIE) illuminants
============================
:_BB: Dict with constants for blackbody radiator calculation
constant are (c1, c2, n, na, c, h, k, e).
:_S012_DAYLIGHTPHASE: ndarray with CIE S0,S1, S2 curves for daylight
phase calculation (linearly interpolated to 1 nm).
:_CRI_REF_TYPES: Dict with blackbody to daylight transition (mixing) ranges for
various types of reference illuminants used in color rendering
index calculations.
:blackbody(): Calculate blackbody radiator spectrum.
:_DAYLIGHT_LOCI_PARAMETERS: dict with parameters for daylight loci for various CMF sets; used by daylightlocus().
:_DAYLIGHT_M12_COEFFS: dict with coefficients in weights M1 & M2 for daylight phases for various CMF sets.
:get_daylightloci_parameters(): Get parameters for the daylight loci functions xD(1000/CCT) and yD(xD); used by daylightlocus().
:get_daylightphase_Mi_coeffs(): Get coefficients of Mi weights of daylight phase for specific cieobs following Judd et al. (1964).
:_get_daylightphase_Mi_values(): Get daylight phase coefficients M1, M2 following Judd et al. (1964).
:daylightlocus(): Calculates daylight chromaticity from cct.
:daylightphase(): Calculate daylight phase spectrum.
:cri_ref(): Calculates a reference illuminant spectrum based on cct for color
rendering index calculations.
(`CIE15:2018, “Colorimetry,” CIE, Vienna, Austria, 2018. <https://doi.org/10.25039/TR.015.2018>`_,
`cie224:2017, CIE 2017 Colour Fidelity Index for accurate scientific use. (2017), ISBN 978-3-902842-61-9. <http://www.cie.co.at/index.php?i_ca_id=1027>`_,
`IES-TM-30-15: Method for Evaluating Light Source Color Rendition. New York, NY: The Illuminating Engineering Society of North America. <https://www.ies.org/store/technical-memoranda/ies-method-for-evaluating-light-source-color-rendition/>`_
:spd_to_indoor(): Convert spd to indoor variant by multiplying it with the CIE spectral transmission for glass.
References
----------
1. `CIE15:2018, “Colorimetry,” CIE, Vienna, Austria, 2018. <https://doi.org/10.25039/TR.015.2018>`_
2. `cie224:2017, CIE 2017 Colour Fidelity Index for accurate scientific use. (2017),
ISBN 978-3-902842-61-9.
<http://www.cie.co.at/index.php?i_ca_id=1027>`_
3. `IES-TM-30-15: Method for Evaluating Light Source Color Rendition.
New York, NY: The Illuminating Engineering Society of North America.
<https://www.ies.org/store/technical-memoranda/ies-method-for-evaluating-light-source-color-rendition/>`_
4. `CIE 191:2010 Recommended System for Mesopic Photometry Based on Visual Performance.
(ISBN 978-3-901906-88-6), http://cie.co.at/publications/recommended-system-mesopic-photometry-based-visual-performance>`_
5. Judd, D. B., MacAdam, D. L., Wyszecki, G., Budde, H. W., Condit, H. R., Henderson, S. T., & Simonds, J. L. (1964). Spectral Distribution of Typical Daylight as a Function of Correlated Color Temperature. J. Opt. Soc. Am., 54(8), 1031–1040. https://doi.org/10.1364/JOSA.54.001031
Created on Wed Jul 8 22:05:17 2020
@author: ksmet1977@gmail.com
"""
import numpy as np
from luxpy import _CIEOBS
from luxpy.utils import np2d, _EPS
from . import spd, cie_interp, spd_to_xyz, xyzbar, getwlr, getwld, _WL3, _BB
from . import _CMF, _CIE_GLASS_ID
__all__ = ['_BB','_S012_DAYLIGHTPHASE',
'_CRI_REF_TYPE', '_CRI_REF_TYPES','cri_ref',
'blackbody','spd_to_indoor',
'daylightlocus','daylightphase',
'get_daylightloci_parameters','get_daylightphase_Mi_coeffs',
'_DAYLIGHT_LOCI_PARAMETERS','_DAYLIGHT_M12_COEFFS']
#--------------------------------------------------------------------------------------------------
# set some colorimetric constants related to illuminants
#_BB = {'c1' : 3.74183e-16, 'c2' : 1.4388*0.01,'n': 1.000, 'na': 1.00028, 'c' : 299792458, 'h' : 6.626070040e-34, 'k' : 1.38064852e-23} # blackbody c1,c2 & n standard values
# IMPORTED FROM spectral.py AS THIS IS ALSO NEEDED in spd_to_power() !!!
# Daylight component spectra S0,S1,S2 (linearly interpolated to 1 nm)
_S012_DAYLIGHTPHASE=np.array([[360.000,361.000,362.000,363.000,364.000,365.000,366.000,367.000,368.000,369.000,370.000,371.000,372.000,373.000,374.000,375.000,376.000,377.000,378.000,379.000,380.000,381.000,382.000,383.000,384.000,385.000,386.000,387.000,388.000,389.000,390.000,391.000,392.000,393.000,394.000,395.000,396.000,397.000,398.000,399.000,400.000,401.000,402.000,403.000,404.000,405.000,406.000,407.000,408.000,409.000,410.000,411.000,412.000,413.000,414.000,415.000,416.000,417.000,418.000,419.000,420.000,421.000,422.000,423.000,424.000,425.000,426.000,427.000,428.000,429.000,430.000,431.000,432.000,433.000,434.000,435.000,436.000,437.000,438.000,439.000,440.000,441.000,442.000,443.000,444.000,445.000,446.000,447.000,448.000,449.000,450.000,451.000,452.000,453.000,454.000,455.000,456.000,457.000,458.000,459.000,460.000,461.000,462.000,463.000,464.000,465.000,466.000,467.000,468.000,469.000,470.000,471.000,472.000,473.000,474.000,475.000,476.000,477.000,478.000,479.000,480.000,481.000,482.000,483.000,484.000,485.000,486.000,487.000,488.000,489.000,490.000,491.000,492.000,493.000,494.000,495.000,496.000,497.000,498.000,499.000,500.000,501.000,502.000,503.000,504.000,505.000,506.000,507.000,508.000,509.000,510.000,511.000,512.000,513.000,514.000,515.000,516.000,517.000,518.000,519.000,520.000,521.000,522.000,523.000,524.000,525.000,526.000,527.000,528.000,529.000,530.000,531.000,532.000,533.000,534.000,535.000,536.000,537.000,538.000,539.000,540.000,541.000,542.000,543.000,544.000,545.000,546.000,547.000,548.000,549.000,550.000,551.000,552.000,553.000,554.000,555.000,556.000,557.000,558.000,559.000,560.000,561.000,562.000,563.000,564.000,565.000,566.000,567.000,568.000,569.000,570.000,571.000,572.000,573.000,574.000,575.000,576.000,577.000,578.000,579.000,580.000,581.000,582.000,583.000,584.000,585.000,586.000,587.000,588.000,589.000,590.000,591.000,592.000,593.000,594.000,595.000,596.000,597.000,598.000,599.000,600.000,601.000,602.000,603.000,604.000,605.000,606.000,607.000,608.000,609.000,610.000,611.000,612.000,613.000,614.000,615.000,616.000,617.000,618.000,619.000,620.000,621.000,622.000,623.000,624.000,625.000,626.000,627.000,628.000,629.000,630.000,631.000,632.000,633.000,634.000,635.000,636.000,637.000,638.000,639.000,640.000,641.000,642.000,643.000,644.000,645.000,646.000,647.000,648.000,649.000,650.000,651.000,652.000,653.000,654.000,655.000,656.000,657.000,658.000,659.000,660.000,661.000,662.000,663.000,664.000,665.000,666.000,667.000,668.000,669.000,670.000,671.000,672.000,673.000,674.000,675.000,676.000,677.000,678.000,679.000,680.000,681.000,682.000,683.000,684.000,685.000,686.000,687.000,688.000,689.000,690.000,691.000,692.000,693.000,694.000,695.000,696.000,697.000,698.000,699.000,700.000,701.000,702.000,703.000,704.000,705.000,706.000,707.000,708.000,709.000,710.000,711.000,712.000,713.000,714.000,715.000,716.000,717.000,718.000,719.000,720.000,721.000,722.000,723.000,724.000,725.000,726.000,727.000,728.000,729.000,730.000,731.000,732.000,733.000,734.000,735.000,736.000,737.000,738.000,739.000,740.000,741.000,742.000,743.000,744.000,745.000,746.000,747.000,748.000,749.000,750.000,751.000,752.000,753.000,754.000,755.000,756.000,757.000,758.000,759.000,760.000,761.000,762.000,763.000,764.000,765.000,766.000,767.000,768.000,769.000,770.000,771.000,772.000,773.000,774.000,775.000,776.000,777.000,778.000,779.000,780.000,781.000,782.000,783.000,784.000,785.000,786.000,787.000,788.000,789.000,790.000,791.000,792.000,793.000,794.000,795.000,796.000,797.000,798.000,799.000,800.000,801.000,802.000,803.000,804.000,805.000,806.000,807.000,808.000,809.000,810.000,811.000,812.000,813.000,814.000,815.000,816.000,817.000,818.000,819.000,820.000,821.000,822.000,823.000,824.000,825.000,826.000,827.000,828.000,829.000,830.000],
[61.500,62.230,62.960,63.690,64.420,65.150,65.880,66.610,67.340,68.070,68.800,68.260,67.720,67.180,66.640,66.100,65.560,65.020,64.480,63.940,63.400,63.640,63.880,64.120,64.360,64.600,64.840,65.080,65.320,65.560,65.800,68.700,71.600,74.500,77.400,80.300,83.200,86.100,89.000,91.900,94.800,95.800,96.800,97.800,98.800,99.800,100.800,101.800,102.800,103.800,104.800,104.910,105.020,105.130,105.240,105.350,105.460,105.570,105.680,105.790,105.900,104.990,104.080,103.170,102.260,101.350,100.440,99.530,98.620,97.710,96.800,98.510,100.220,101.930,103.640,105.350,107.060,108.770,110.480,112.190,113.900,115.070,116.240,117.410,118.580,119.750,120.920,122.090,123.260,124.430,125.600,125.590,125.580,125.570,125.560,125.550,125.540,125.530,125.520,125.510,125.500,125.080,124.660,124.240,123.820,123.400,122.980,122.560,122.140,121.720,121.300,121.300,121.300,121.300,121.300,121.300,121.300,121.300,121.300,121.300,121.300,120.520,119.740,118.960,118.180,117.400,116.620,115.840,115.060,114.280,113.500,113.460,113.420,113.380,113.340,113.300,113.260,113.220,113.180,113.140,113.100,112.870,112.640,112.410,112.180,111.950,111.720,111.490,111.260,111.030,110.800,110.370,109.940,109.510,109.080,108.650,108.220,107.790,107.360,106.930,106.500,106.730,106.960,107.190,107.420,107.650,107.880,108.110,108.340,108.570,108.800,108.450,108.100,107.750,107.400,107.050,106.700,106.350,106.000,105.650,105.300,105.210,105.120,105.030,104.940,104.850,104.760,104.670,104.580,104.490,104.400,103.960,103.520,103.080,102.640,102.200,101.760,101.320,100.880,100.440,100.000,99.600,99.200,98.800,98.400,98.000,97.600,97.200,96.800,96.400,96.000,95.910,95.820,95.730,95.640,95.550,95.460,95.370,95.280,95.190,95.100,94.500,93.900,93.300,92.700,92.100,91.500,90.900,90.300,89.700,89.100,89.240,89.380,89.520,89.660,89.800,89.940,90.080,90.220,90.360,90.500,90.480,90.460,90.440,90.420,90.400,90.380,90.360,90.340,90.320,90.300,90.110,89.920,89.730,89.540,89.350,89.160,88.970,88.780,88.590,88.400,87.960,87.520,87.080,86.640,86.200,85.760,85.320,84.880,84.440,84.000,84.110,84.220,84.330,84.440,84.550,84.660,84.770,84.880,84.990,85.100,84.780,84.460,84.140,83.820,83.500,83.180,82.860,82.540,82.220,81.900,81.970,82.040,82.110,82.180,82.250,82.320,82.390,82.460,82.530,82.600,82.830,83.060,83.290,83.520,83.750,83.980,84.210,84.440,84.670,84.900,84.540,84.180,83.820,83.460,83.100,82.740,82.380,82.020,81.660,81.300,80.360,79.420,78.480,77.540,76.600,75.660,74.720,73.780,72.840,71.900,72.140,72.380,72.620,72.860,73.100,73.340,73.580,73.820,74.060,74.300,74.510,74.720,74.930,75.140,75.350,75.560,75.770,75.980,76.190,76.400,75.090,73.780,72.470,71.160,69.850,68.540,67.230,65.920,64.610,63.300,64.140,64.980,65.820,66.660,67.500,68.340,69.180,70.020,70.860,71.700,72.230,72.760,73.290,73.820,74.350,74.880,75.410,75.940,76.470,77.000,75.820,74.640,73.460,72.280,71.100,69.920,68.740,67.560,66.380,65.200,63.450,61.700,59.950,58.200,56.450,54.700,52.950,51.200,49.450,47.700,49.790,51.880,53.970,56.060,58.150,60.240,62.330,64.420,66.510,68.600,68.240,67.880,67.520,67.160,66.800,66.440,66.080,65.720,65.360,65.000,65.100,65.200,65.300,65.400,65.500,65.600,65.700,65.800,65.900,66.000,65.500,65.000,64.500,64.000,63.500,63.000,62.500,62.000,61.500,61.000,60.230,59.460,58.690,57.920,57.150,56.380,55.610,54.840,54.070,53.300,53.860,54.420,54.980,55.540,56.100,56.660,57.220,57.780,58.340,58.900,59.200,59.500,59.800,60.100,60.400,60.700,61.000,61.300,61.600,61.900],
[38.000,38.440,38.880,39.320,39.760,40.200,40.640,41.080,41.520,41.960,42.400,42.010,41.620,41.230,40.840,40.450,40.060,39.670,39.280,38.890,38.500,38.150,37.800,37.450,37.100,36.750,36.400,36.050,35.700,35.350,35.000,35.840,36.680,37.520,38.360,39.200,40.040,40.880,41.720,42.560,43.400,43.690,43.980,44.270,44.560,44.850,45.140,45.430,45.720,46.010,46.300,46.060,45.820,45.580,45.340,45.100,44.860,44.620,44.380,44.140,43.900,43.220,42.540,41.860,41.180,40.500,39.820,39.140,38.460,37.780,37.100,37.060,37.020,36.980,36.940,36.900,36.860,36.820,36.780,36.740,36.700,36.620,36.540,36.460,36.380,36.300,36.220,36.140,36.060,35.980,35.900,35.570,35.240,34.910,34.580,34.250,33.920,33.590,33.260,32.930,32.600,32.130,31.660,31.190,30.720,30.250,29.780,29.310,28.840,28.370,27.900,27.540,27.180,26.820,26.460,26.100,25.740,25.380,25.020,24.660,24.300,23.880,23.460,23.040,22.620,22.200,21.780,21.360,20.940,20.520,20.100,19.710,19.320,18.930,18.540,18.150,17.760,17.370,16.980,16.590,16.200,15.900,15.600,15.300,15.000,14.700,14.400,14.100,13.800,13.500,13.200,12.740,12.280,11.820,11.360,10.900,10.440,9.980,9.520,9.060,8.600,8.350,8.100,7.850,7.600,7.350,7.100,6.850,6.600,6.350,6.100,5.910,5.720,5.530,5.340,5.150,4.960,4.770,4.580,4.390,4.200,3.970,3.740,3.510,3.280,3.050,2.820,2.590,2.360,2.130,1.900,1.710,1.520,1.330,1.140,0.950,0.760,0.570,0.380,0.190,0.000,-0.160,-0.320,-0.480,-0.640,-0.800,-0.960,-1.120,-1.280,-1.440,-1.600,-1.790,-1.980,-2.170,-2.360,-2.550,-2.740,-2.930,-3.120,-3.310,-3.500,-3.500,-3.500,-3.500,-3.500,-3.500,-3.500,-3.500,-3.500,-3.500,-3.500,-3.730,-3.960,-4.190,-4.420,-4.650,-4.880,-5.110,-5.340,-5.570,-5.800,-5.940,-6.080,-6.220,-6.360,-6.500,-6.640,-6.780,-6.920,-7.060,-7.200,-7.340,-7.480,-7.620,-7.760,-7.900,-8.040,-8.180,-8.320,-8.460,-8.600,-8.690,-8.780,-8.870,-8.960,-9.050,-9.140,-9.230,-9.320,-9.410,-9.500,-9.640,-9.780,-9.920,-10.060,-10.200,-10.340,-10.480,-10.620,-10.760,-10.900,-10.880,-10.860,-10.840,-10.820,-10.800,-10.780,-10.760,-10.740,-10.720,-10.700,-10.830,-10.960,-11.090,-11.220,-11.350,-11.480,-11.610,-11.740,-11.870,-12.000,-12.200,-12.400,-12.600,-12.800,-13.000,-13.200,-13.400,-13.600,-13.800,-14.000,-13.960,-13.920,-13.880,-13.840,-13.800,-13.760,-13.720,-13.680,-13.640,-13.600,-13.440,-13.280,-13.120,-12.960,-12.800,-12.640,-12.480,-12.320,-12.160,-12.000,-12.130,-12.260,-12.390,-12.520,-12.650,-12.780,-12.910,-13.040,-13.170,-13.300,-13.260,-13.220,-13.180,-13.140,-13.100,-13.060,-13.020,-12.980,-12.940,-12.900,-12.670,-12.440,-12.210,-11.980,-11.750,-11.520,-11.290,-11.060,-10.830,-10.600,-10.700,-10.800,-10.900,-11.000,-11.100,-11.200,-11.300,-11.400,-11.500,-11.600,-11.660,-11.720,-11.780,-11.840,-11.900,-11.960,-12.020,-12.080,-12.140,-12.200,-12.000,-11.800,-11.600,-11.400,-11.200,-11.000,-10.800,-10.600,-10.400,-10.200,-9.960,-9.720,-9.480,-9.240,-9.000,-8.760,-8.520,-8.280,-8.040,-7.800,-8.140,-8.480,-8.820,-9.160,-9.500,-9.840,-10.180,-10.520,-10.860,-11.200,-11.120,-11.040,-10.960,-10.880,-10.800,-10.720,-10.640,-10.560,-10.480,-10.400,-10.420,-10.440,-10.460,-10.480,-10.500,-10.520,-10.540,-10.560,-10.580,-10.600,-10.510,-10.420,-10.330,-10.240,-10.150,-10.060,-9.970,-9.880,-9.790,-9.700,-9.560,-9.420,-9.280,-9.140,-9.000,-8.860,-8.720,-8.580,-8.440,-8.300,-8.400,-8.500,-8.600,-8.700,-8.800,-8.900,-9.000,-9.100,-9.200,-9.300,-9.350,-9.400,-9.450,-9.500,-9.550,-9.600,-9.650,-9.700,-9.750,-9.800],
[5.300,5.380,5.460,5.540,5.620,5.700,5.780,5.860,5.940,6.020,6.100,5.790,5.480,5.170,4.860,4.550,4.240,3.930,3.620,3.310,3.000,2.820,2.640,2.460,2.280,2.100,1.920,1.740,1.560,1.380,1.200,0.970,0.740,0.510,0.280,0.050,-0.180,-0.410,-0.640,-0.870,-1.100,-1.040,-0.980,-0.920,-0.860,-0.800,-0.740,-0.680,-0.620,-0.560,-0.500,-0.520,-0.540,-0.560,-0.580,-0.600,-0.620,-0.640,-0.660,-0.680,-0.700,-0.750,-0.800,-0.850,-0.900,-0.950,-1.000,-1.050,-1.100,-1.150,-1.200,-1.340,-1.480,-1.620,-1.760,-1.900,-2.040,-2.180,-2.320,-2.460,-2.600,-2.630,-2.660,-2.690,-2.720,-2.750,-2.780,-2.810,-2.840,-2.870,-2.900,-2.890,-2.880,-2.870,-2.860,-2.850,-2.840,-2.830,-2.820,-2.810,-2.800,-2.780,-2.760,-2.740,-2.720,-2.700,-2.680,-2.660,-2.640,-2.620,-2.600,-2.600,-2.600,-2.600,-2.600,-2.600,-2.600,-2.600,-2.600,-2.600,-2.600,-2.520,-2.440,-2.360,-2.280,-2.200,-2.120,-2.040,-1.960,-1.880,-1.800,-1.770,-1.740,-1.710,-1.680,-1.650,-1.620,-1.590,-1.560,-1.530,-1.500,-1.480,-1.460,-1.440,-1.420,-1.400,-1.380,-1.360,-1.340,-1.320,-1.300,-1.290,-1.280,-1.270,-1.260,-1.250,-1.240,-1.230,-1.220,-1.210,-1.200,-1.180,-1.160,-1.140,-1.120,-1.100,-1.080,-1.060,-1.040,-1.020,-1.000,-0.950,-0.900,-0.850,-0.800,-0.750,-0.700,-0.650,-0.600,-0.550,-0.500,-0.480,-0.460,-0.440,-0.420,-0.400,-0.380,-0.360,-0.340,-0.320,-0.300,-0.270,-0.240,-0.210,-0.180,-0.150,-0.120,-0.090,-0.060,-0.030,0.000,0.020,0.040,0.060,0.080,0.100,0.120,0.140,0.160,0.180,0.200,0.230,0.260,0.290,0.320,0.350,0.380,0.410,0.440,0.470,0.500,0.660,0.820,0.980,1.140,1.300,1.460,1.620,1.780,1.940,2.100,2.210,2.320,2.430,2.540,2.650,2.760,2.870,2.980,3.090,3.200,3.290,3.380,3.470,3.560,3.650,3.740,3.830,3.920,4.010,4.100,4.160,4.220,4.280,4.340,4.400,4.460,4.520,4.580,4.640,4.700,4.740,4.780,4.820,4.860,4.900,4.940,4.980,5.020,5.060,5.100,5.260,5.420,5.580,5.740,5.900,6.060,6.220,6.380,6.540,6.700,6.760,6.820,6.880,6.940,7.000,7.060,7.120,7.180,7.240,7.300,7.430,7.560,7.690,7.820,7.950,8.080,8.210,8.340,8.470,8.600,8.720,8.840,8.960,9.080,9.200,9.320,9.440,9.560,9.680,9.800,9.840,9.880,9.920,9.960,10.000,10.040,10.080,10.120,10.160,10.200,10.010,9.820,9.630,9.440,9.250,9.060,8.870,8.680,8.490,8.300,8.430,8.560,8.690,8.820,8.950,9.080,9.210,9.340,9.470,9.600,9.490,9.380,9.270,9.160,9.050,8.940,8.830,8.720,8.610,8.500,8.350,8.200,8.050,7.900,7.750,7.600,7.450,7.300,7.150,7.000,7.060,7.120,7.180,7.240,7.300,7.360,7.420,7.480,7.540,7.600,7.640,7.680,7.720,7.760,7.800,7.840,7.880,7.920,7.960,8.000,7.870,7.740,7.610,7.480,7.350,7.220,7.090,6.960,6.830,6.700,6.550,6.400,6.250,6.100,5.950,5.800,5.650,5.500,5.350,5.200,5.420,5.640,5.860,6.080,6.300,6.520,6.740,6.960,7.180,7.400,7.340,7.280,7.220,7.160,7.100,7.040,6.980,6.920,6.860,6.800,6.820,6.840,6.860,6.880,6.900,6.920,6.940,6.960,6.980,7.000,6.940,6.880,6.820,6.760,6.700,6.640,6.580,6.520,6.460,6.400,6.310,6.220,6.130,6.040,5.950,5.860,5.770,5.680,5.590,5.500,5.560,5.620,5.680,5.740,5.800,5.860,5.920,5.980,6.040,6.100,6.140,6.180,6.220,6.260,6.300,6.340,6.380,6.420,6.460,6.500]
])
#--------------------------------------------------------------------------------------------------
# reference illuminant default and mixing range settings, cieobs,
# and cieobs_Y_normalization (for mixed illuminants) settings:
_CRI_REF_TYPE = 'ciera'
_CRI_REF_TYPES = {'ciera': {'mix_range' : [5000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : None},
'cierf': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : None},
'cierf-224-2017': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : None},
'iesrf': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : '1964_10'},
'iesrf-tm30-15': {'mix_range' : [4500.0 , 5500.0], 'cieobs' : None, 'cieobs_Y_normalization' : None},
'iesrf-tm30-18': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : '1964_10'},
'iesrf-tm30-20': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : '1964_10'},
'iesrf-tm30-24': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : '1964_10'},
'iesrf-tm30': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : '1964_10'},
'ies-tm30': {'mix_range' : [4000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : '1964_10'},
'BB':{'mix_range' : [5000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : None},
'DL':{'mix_range' : [5000.0 , 5000.0], 'cieobs' : None, 'cieobs_Y_normalization' : None}
} #mixing ranges, cieobs (for DL), cieobs_Y_normalization (for normalization of mixed illuminants) for various cri_reference_illuminant types
_DAYLIGHT_LOCI_PARAMETERS = None # temporary initialization
#------------------------------------------------------------------------------
#---CIE illuminant functions---------------------------------------------------
#------------------------------------------------------------------------------
[docs]
def blackbody(cct, wl3 = None, n = None, relative = True):
"""
Calculate blackbody radiator spectrum for correlated color temperature (cct).
Args:
:cct:
| int or float
| (for list of cct values, use cri_ref() with ref_type = 'BB')
:wl3:
| None, optional
| New wavelength range for interpolation.
| Defaults to wavelengths specified by luxpy._WL3.
:n:
| None, optional
| Refractive index.
| If None: use the one stored in _BB['n']
:relative:
| False, optional
| True: return relative spectrum normalized to 560 nm
| False: return absolute spectral radiance (Planck's law; W/(sr.m².nm))
Returns:
:returns:
| ndarray with blackbody radiator spectrum
| (:returns:[0] contains wavelengths)
References:
1. `CIE15:2018, “Colorimetry,” CIE, Vienna, Austria, 2018. <https://doi.org/10.25039/TR.015.2018>`_
"""
cct = float(cct)
if wl3 is None: wl3 = _WL3
if n is None: n = _BB['n']
wl = getwlr(wl3)
def fSr(x):
return (1/np.pi)*_BB['c1']*((x*1.0e-9)**(-5))*(n**(-2.0))*(np.exp(_BB['c2']*((n*x*1.0e-9*(cct+_EPS))**(-1.0)))-1.0)**(-1.0)
if relative:
return np.vstack((wl,(fSr(wl)/fSr(560.0))))
else:
return np.vstack((wl,fSr(wl)))
#------------------------------------------------------------------------------
def _get_daylightlocus_parameters(ccts, spds, cieobs, interp_settings = None):
"""
Get daylight locus parameters for a single cieobs from daylight phase spectra
determined based on parameters for '1931_2' as reported in CIE15-20xx.
"""
#------------------------------------------------
# Get locus parameters:
#======================
# get xy coordinates for new cieobs:
xyz_ = spd_to_xyz(spds, cieobs = cieobs, interp_settings = interp_settings)
xy = xyz_[...,:2]/xyz_.sum(axis=-1,keepdims=True)
# Fit 3e order polynomal xD(1/T) [4000 K < T <= 7000 K]:
l7 = ccts<7000
pxT_l7 = np.polyfit((1000/ccts[l7]), xy[l7,0],3)
# Fit 3e order polynomal xD(1/T) [T > 7000 K]:
L7 = ccts>=7000
pxT_L7 = np.polyfit((1000/ccts[L7]), xy[L7,0],3)
# Fit 2nd order polynomal yD(xD):
pxy = np.round(np.polyfit(xy[:,0],xy[:,1],2),3)
#pxy = np.hstack((0,pxy)) # make also 3e order for easy stacking
return (xy, pxy, pxT_l7, pxT_L7, l7, L7)
#------------------------------------------------------------------------------
[docs]
def daylightlocus(cct, force_daylight_below4000K = False, cieobs = None, daylight_locus = None,
use_published_daylightlocus_coeffs_when_cieobs_is_1931_2 = True, interp_settings = None):
"""
Calculates daylight chromaticity (xD,yD) from correlated color temperature (cct).
Args:
:cct:
| int or float or list of int/floats or ndarray
:force_daylight_below4000K:
| False or True, optional
| Daylight locus approximation is not defined below 4000 K,
| but by setting this to True, the calculation can be forced to
| calculate it anyway.
:cieobs:
| CMF set corresponding to xD, yD output.
| If None: use default CIE15-20xx locus for '1931_2'
| Else: use the locus specified in :daylight_locus:
:daylight_locus:
| None, optional
| dict with xD(T) and yD(xD) parameters to calculate daylight locus
| for specified cieobs.
| If None: use pre-calculated values.
| If 'calc': calculate them on the fly.
:use_published_daylightlocus_coeffs_when_cieobs_is_1931_2:
| True, optional
| Use published coefficients for CIE 1931 2° CMFs (see CIE015 colorimetry)
Returns:
:(xD, yD):
| (ndarray of x-coordinates, ndarray of y-coordinates)
References:
1. `CIE15:2018, “Colorimetry,” CIE, Vienna, Austria, 2018. <https://doi.org/10.25039/TR.015.2018>`_
"""
cct = np2d(cct)
if np.any((cct < 4000.0) & (force_daylight_below4000K == False)):
raise Exception('spectral.daylightlocus(): Daylight locus approximation not defined below 4000 K')
if (cieobs is None) | (use_published_daylightlocus_coeffs_when_cieobs_is_1931_2 & (cieobs == '1931_2')): # use default values for '1931_2' reported in CIE15-20xx
xD = -4.607*((1e3/cct)**3.0)+2.9678*((1e3/cct)**2.0)+0.09911*(1000.0/cct)+0.244063
p = cct>=7000.0
xD[p] = -2.0064*((1.0e3/cct[p])**3.0)+1.9018*((1.0e3/cct[p])**2.0)+0.24748*(1.0e3/cct[p])+0.23704
yD = -3.0*xD**2.0+2.87*xD-0.275
else:
if isinstance(cieobs, str):
if daylight_locus is None:
daylight_locus = _DAYLIGHT_LOCI_PARAMETERS[cieobs]
else:
if isinstance(daylight_locus,str):
if daylight_locus == 'calc':
daylight_locus = get_daylightloci_parameters(cieobs = [cieobs],
interp_settings = interp_settings)[cieobs]
else:
daylight_locus = get_daylightloci_parameters(cieobs = cieobs,
interp_settings = interp_settings)['cmf_0']
pxy, pxT_l7, pxT_L7 = daylight_locus['pxy'], daylight_locus['pxT_l7k'], daylight_locus['pxT_L7k']
xD = np.polyval(pxT_l7, 1000/cct)
p = cct>=7000.0
xD[p] = np.polyval(pxT_L7, 1000/cct[p])
yD = np.polyval(pxy, xD)
return xD,yD
[docs]
def get_daylightphase_Mi_coeffs(cieobs = None, wl3 = None, S012_daylightphase = None,
use_1931_2_published_Mcoeffs = False,
interp_settings = None):
"""
Get coefficients of Mi weights of daylight phase for specific cieobs
Args:
:cieobs:
| None or str or ndarray or list of str or list of ndarrays, optional
| CMF set to get coefficients for.
| If None: get coeffs for all CMFs in _CMF
:wl3:
| None, optional
| Wavelength range to interpolate S012_daylightphase to.
:S012_daylightphase:
| None, optional
| Daylight phase component functions.
| If None: use _S012_DAYLIGHTPHASE
:use_1931_2_published_Mcoeffs:
| False, optional
| Use published coefficients of CIE 1931 2° CMFs (see CIE015 colorimetry), even when cieobs is not '1931_2'!
Returns:
:Mcoeffs:
| Dictionary with i,j,k,i1,j1,k1,i2,j2,k2 for each cieobs in :cieobs:
| If cieobs contains ndarrays, then keys in dict will be
| labeled 'cmf_0', 'cmf_1', ...
"""
#-------------------------------------------------
# Get Mi coefficients:
#=====================
# Get tristimulus values of daylight phase component functions:
if S012_daylightphase is None:
S012_daylightphase = _S012_DAYLIGHTPHASE
if wl3 is not None:
S012_daylightphase = cie_interp(S012_daylightphase,wl_new = wl3, kind='linear',negative_values_allowed = True,
interp_settings = interp_settings)
if cieobs is None: cieobs = _CMF['types']
if not isinstance(cieobs, list):
cieobs = [cieobs]
Mcoeffs = {}
i = 0
for cieobs_ in cieobs:
if isinstance(cieobs_,str):
if 'scotopic' in cieobs_:
continue
if 'std_dev_obs' in cieobs_:
continue
key = cieobs_
else:
key = 'cmf_{:1.0f}'.format(i)
if use_1931_2_published_Mcoeffs:
Mcoeffs[key] = {'i':0.0241,'j':0.2562,'k':-0.7341,
'i1':-1.3515,'j1':-1.7703,'k1':5.9114,
'i2':0.0300,'j2':-31.4424,'k2':30.0717}
continue
# Get tristimulus values of daylight phase component functions:
xyz = spd_to_xyz(S012_daylightphase, cieobs = cieobs_, relative = False, K = 1,
interp_settings = interp_settings)
S = xyz.sum(axis=-1)
# Get coefficients in Mi:
f = 1000/S[0]**2
r = 4 # rounding of i,j,k,i1,j1,k1,i2,j2,k2
c = {'i': np.round(f*(xyz[2,0]*xyz[1,1] - xyz[1,0]*xyz[2,1]),r)}
c['j'] = np.round(f*(xyz[2,1]*S[1] - xyz[1,1]*S[2]),r)
c['k'] = np.round(f*(xyz[1,0]*S[2] - xyz[2,0]*S[1]),r)
c['i1'] = np.round(f*(xyz[0,0]*xyz[2,1] - xyz[2,0]*xyz[0,1]),r)
c['j1'] = np.round(f*(xyz[0,1]*S[2] - xyz[2,1]*S[0]),r)
c['k1'] = np.round(f*(xyz[2,0]*S[0] - xyz[0,0]*S[2]),r)
c['i2'] = np.round(f*(xyz[1,0]*xyz[0,1] - xyz[0,0]*xyz[1,1]),r)
c['j2'] = np.round(f*(xyz[1,1]*S[0] - xyz[0,1]*S[1]),r)
c['k2']= np.round(f*(xyz[0,0]*S[1] - xyz[1,0]*S[0]),r)
Mcoeffs[key] = c
i+=1
return Mcoeffs
def _get_daylightphase_Mi_values(xD,yD, Mcoeffs = None, cieobs = None, S012_daylightphase = None,
use_1931_2_published_Mcoeffs = False,
round_Mi_to_cie_recommended = False,
interp_settings = None):
"""
Get daylight phase coefficients M1, M2 following Judd et al. (1964)
Args:
:xD,yD:
| ndarray of x-coordinates, ndarray of y-coordinates of daylight phase
:Mcoeffs:
| Coefficients in M1 & M2 weights for specific cieobs.
| If None and cieobs is not None: they will be calculated.
:cieobs:
| CMF set to use when calculating coefficients in M1, M2 weights.
| If None: Mcoeffs must be supplied.
:S012_daylightphase:
| ndarray with CIE S0, S1, S2 daylight phase component functions
:use_1931_2_published_Mcoeffs:
| False, optional
| Use published coefficients of CIE 1931 2° CMFs (see CIE015 colorimetry), even when cieobs is not '1931_2'!
:interp_settings:
| None, optional
| Interpolation settings for spd_to_xyz() and cie_interp()
| None: use default settings.
:round_daylightphase_Mi_to_cie_recommended:
| False, optional
| If True: Round M1, M2 values to 3 decimals as recommended by CIE (not that TM30 does not do this, which gives slight errors when calculating a daylight phase (equivalent of around 0.75 K for 6500 K illuminant))
| Rounding causes larger errors from target CCT! Therefore, the default is set to False!
| Note that neither CIE224-2017 or IES TM30 rounds M1 and M2 to 3 decimals!
Returns:
:M1,M2:
| daylight phase coefficients M1, M2
Reference:
1. `Judd et al.(1964). Spectral Distribution of Typical Daylight as a
Function of Correlated Color Temperature.
JOSA A, 54(8), pp. 1031-1040 (1964) <https://doi.org/10.1364/JOSA.54.001031>`_
"""
if use_1931_2_published_Mcoeffs:
Mcoeffs = {'i':0.0241,'j':0.2562,'k':-0.7341,
'i1':-1.3515,'j1':-1.7703,'k1':5.9114,
'i2':0.0300,'j2':-31.4424,'k2':30.0717}
if (Mcoeffs is None) & (cieobs is None):
raise Exception("_get_daylightphase_Mi_values(): Mcoeffs and cieobs can't both be None")
if (Mcoeffs is None) & isinstance(cieobs,str): # use pre-calculated coeffs.
Mcoeffs = _DAYLIGHT_M12_COEFFS[cieobs]
if isinstance(Mcoeffs,str) & (cieobs is not None): # calculate coeffs.
if (Mcoeffs == 'calc'):
Mcoeffs = get_daylightphase_Mi_coeffs(cieobs = cieobs, S012_daylightphase = S012_daylightphase,
interp_settings = interp_settings)
Mcoeffs = Mcoeffs[cieobs] if isinstance(cieobs,str) else Mcoeffs['cmf_0']
else:
raise Exception("Mcoeffs is a string, but not 'calc': unknown option.")
if Mcoeffs is not None:
c = Mcoeffs
# Calculate M1, M2 and round to 3 decimals (CIE recommendation):
denom = c['i'] + c['j']*xD + c['k']*yD
M1 = (c['i1'] + c['j1']*xD + c['k1']*yD) / denom
M2 = (c['i2'] + c['j2']*xD + c['k2']*yD) / denom
if round_Mi_to_cie_recommended:
rr = 3 # rounding of Mi
M1 = np.round(M1, rr)
M2 = np.round(M2, rr)
return M1, M2, c
#------------------------------------------------------------------------------
[docs]
def daylightphase(cct, wl3 = None, nominal_cct = False, force_daylight_below4000K = False, verbosity = None,
n = None, cieobs = None, daylight_locus = None, daylight_Mi_coeffs = None,
force_tabulated_xyD_Mi_when_cieobs_is_1931_2 = True,
round_Mi_to_cie_recommended = False,
interp_settings = None):
"""
Calculate daylight phase spectrum for correlated color temperature (cct).
Args:
:cct:
| int or float
| (for list of cct values, use cri_ref() with ref_type = 'DL')
:wl3:
| None, optional
| New wavelength range for interpolation.
| Defaults to wavelengths specified by luxpy._WL3.
:nominal_cct:
| False, optional
| If cct is nominal (e.g. when calculating D65): multiply cct first
| by 1.4388/1.4380 to account for change in 'c2' in definition of Planckian.
:cieobs:
| None or str or ndarray, optional
| CMF set to use when calculating coefficients for daylight locus and for M1, M2 weights.
| If None: use standard coefficients for CIE 1931 2° CMFs (for Si at 10 nm).
| Else: calculate coefficients following Appendix C of CIE15-2004 and Judd (1964).
:force_daylight_below4000K:
| False or True, optional
| Daylight locus approximation is not defined below 4000 K,
| but by setting this to True, the calculation can be forced to
| calculate it anyway.
:verbosity:
| None, optional
| If None: do not print warning when CCT < 4000 K.
:n:
| None, optional
| Refractive index (for use in calculation of blackbody radiators).
| If None: use the one stored in _BB['n']
:daylight_locus:
| None, optional
| dict with xD(T) and yD(xD) parameters to calculate daylight locus
| for specified cieobs.
| If None: use pre-calculated values.
| If 'calc': calculate them on the fly.
:daylight_Mi_coeffs:
| None, optional
| dict with coefficients for M1 & M2 weights for specified cieobs.
| If None: use pre-calculated values.
| If 'calc': calculate them on the fly.
:force_tabulated_xyD_Mi_when_cieobs_is_1931_2:
| True, optional
| If cieobs is '1931_2', then use tabulated values for xD, yD and Mi coefficients.
:round_daylightphase_Mi_to_cie_recommended:
| False, optional
| If True: Round M1, M2 values to 3 decimals as recommended by CIE (not that TM30 does not do this, which gives slight errors when calculating a daylight phase (equivalent of around 0.75 K for 6500 K illuminant))
| Rounding causes larger errors from target CCT! Therefore, the default is set to False!
| Note that neither CIE224-2017 or IES TM30 rounds M1 and M2 to 3 decimals!
Returns:
:returns:
| ndarray with daylight phase spectrum
| (:returns:[0] contains wavelengths)
References:
1. `CIE15:2018, “Colorimetry,” CIE, Vienna, Austria, 2018. <https://doi.org/10.25039/TR.015.2018>`_
2. `Judd, MacAdam, Wyszecki, Budde, Condit, Henderson, & Simonds (1964).
Spectral Distribution of Typical Daylight as a Function of Correlated Color Temperature.
J. Opt. Soc. Am., 54(8), 1031–1040.
<https://doi.org/10.1364/JOSA.54.001031>`_
"""
cct = float(cct)
if wl3 is None: wl3 = _WL3
if (cct < (4000.0)) & (force_daylight_below4000K == False):
if verbosity is not None:
print('Warning daylightphase spd not defined below 4000 K. Using blackbody radiator instead.')
return blackbody(cct,wl3, n = n)
else:
if nominal_cct: cct*=(1.4388/1.4380) # account for change in c2 in def. of Planckian
wl = getwlr(wl3)
#interpolate _S012_DAYLIGHTPHASE first to wl range:
if not np.array_equal(_S012_DAYLIGHTPHASE[0],wl):
S012_daylightphase = cie_interp(data = _S012_DAYLIGHTPHASE, wl_new = wl, kind = 'linear',negative_values_allowed = True,
interp_settings = interp_settings)
else:
S012_daylightphase = _S012_DAYLIGHTPHASE
# Get coordinates of daylight locus corresponding to cct:
xD, yD = daylightlocus(cct, force_daylight_below4000K = force_daylight_below4000K, cieobs = cieobs, daylight_locus = daylight_locus,
use_published_daylightlocus_coeffs_when_cieobs_is_1931_2 = force_tabulated_xyD_Mi_when_cieobs_is_1931_2,
interp_settings = interp_settings)
# Get M1 & M2 component weights:
if (cieobs is None) | (force_tabulated_xyD_Mi_when_cieobs_is_1931_2 & (cieobs == '1931_2')): # original M1,M2 for Si at 10 nm spacing and CIE 1931 xy
use_1931_2_published_Mcoeffs = True
Mcoeffs = None
else:
Mcoeffs = daylight_Mi_coeffs
M1, M2, _ = _get_daylightphase_Mi_values(xD, yD, Mcoeffs = Mcoeffs, cieobs = cieobs, S012_daylightphase = S012_daylightphase,
use_1931_2_published_Mcoeffs = use_1931_2_published_Mcoeffs,
round_Mi_to_cie_recommended = round_Mi_to_cie_recommended,
interp_settings = interp_settings)
# Calculate weigthed combination of S0, S1 & S2 components:
Sr = S012_daylightphase[1,:] + M1*S012_daylightphase[2,:] + M2*S012_daylightphase[3,:]
# Normalize to 1 at (or near) 560 nm:
Sr560 = Sr[:,np.where(np.abs(S012_daylightphase[0,:] - 560.0) == np.min(np.abs(S012_daylightphase[0,:] - 560)))[0]]
Sr /= Sr560
Sr[Sr==float('NaN')] = 0
return np.vstack((wl,Sr))
[docs]
def get_daylightloci_parameters(ccts = None, cieobs = None, wl3 = [300,830,10], verbosity = 0,
use_1931_2_published_daylightlocus_coeffs = False, interp_settings = None):
"""
Get parameters for the daylight loci functions xD(1000/CCT) and yD(xD).
Args:
:ccts:
| None, optional
| ndarray with CCTs, if None: ccts = np.arange(4000,25000,250)
:cieobs:
| None or list of str or list of ndarrays, optional
| CMF sets to determine parameters for.
| If None: get for all CMFs sets in _CMF (except scoptopic and deviate observer)
:wl3:
| [300,830,10], optional
| Wavelength range and spacing of daylight phases to be determined
| from '1931_2'. The default setting results in parameters very close
| to that in CIE15-2004/2018.
:use_1931_2_published_daylightlocus_coeffs:
| False, optional
| Use published coefficients for CIE 1931 2° CMFs (see CIE015 colorimetry)
:verbosity:
| 0, optional
| print parameters and make plots.
Returns:
:dayloci:
| dict with parameters for each cieobs
| If cieobs contains ndarrays, then keys in dict will be
| labeled 'cmf_0', 'cmf_1', ...
"""
if ccts is None:
ccts = np.arange(4000,25000,250)
# Get daylight phase spds using cieobs '1931_2':
# wl3 = [300,830,10] # results in Judd's (1964) coefficients for the function yD(xD)x; other show slight deviations
for i, cct in enumerate(ccts):
spd = daylightphase(cct, cieobs = None, wl3 = wl3, force_daylight_below4000K = False,
force_tabulated_xyD_Mi_when_cieobs_is_1931_2 = use_1931_2_published_daylightlocus_coeffs,
interp_settings = interp_settings)
if i == 0:
spds = spd
else:
spds = np.vstack((spds,spd[1:]))
if verbosity > 0:
import matplotlib.pyplot as plt # lazy import
fig,axs = plt.subplots(nrows = 2, ncols = len(_CMF['types']) - 2) # -2: don't include scoptopic and dev observers
dayloci = {'WL':wl3}
i = 0
if cieobs is None: cieobs = _CMF['types']
if not isinstance(cieobs, list):
cieobs = [cieobs]
for cieobs_ in cieobs:
if isinstance(cieobs_,str):
if 'scotopic' in cieobs_:
continue
if 'std_dev_obs' in cieobs_:
continue
key = cieobs_
else:
key = 'cmf_{:1.0f}'.format(i)
# get parameters for cieobs:
xy, pxy, pxT_l7, pxT_L7, l7, L7 = _get_daylightlocus_parameters(ccts, spds, cieobs_, interp_settings = interp_settings)
dayloci[key] = {'pxT_l7k':pxT_l7, 'pxT_L7k':pxT_L7, 'pxy':pxy}
if verbosity > 0:
print('\n cieobs:', key)
print('pxT_l7 (Tcp<7000K):',pxT_l7)
print('pxT_L7 (Tcp>=7000K):',pxT_L7)
print('p:xy',pxy)
axs[0,i].plot(ccts, xy[:,0],'r-', label = 'Data')
axs[0,i].plot(ccts[l7], np.polyval(pxT_l7,1000/ccts[l7]),'b--', label = 'Fit (Tcp<7000K)')
axs[0,i].plot(ccts[L7], np.polyval(pxT_L7,1000/ccts[L7]),'c--', label = 'Fit (Tcp>=7000K)')
axs[0,i].set_title(key)
axs[0,i].set_xlabel('Tcp (K)')
axs[0,i].set_ylabel('xD')
axs[0,i].legend()
#plotSL(cieobs = cieobs_, cspace = 'Yxy', DL = False, axh = axs[1,i])
axs[1,i].plot(xy[:,0],xy[:,1],'r-', label = 'Data')
axs[1,i].plot(xy[:,0],np.polyval(pxy,xy[:,0]),'b--', label = 'Fit')
# axs[1,i].plot(xy[:,0],np.polyval(pxy_31,xy[:,0]),'g:')
axs[1,i].set_xlabel('xD')
axs[1,i].set_ylabel('yD')
axs[1,i].legend()
i+=1
return dayloci
#------------------------------------------------------------------------------
# Pre-calculate daylight loci parameters and Mi coefficients for each CMF in _CMF
# (except 'scotopic' and 'cie_std_dev_...'):
wl3 = [360,830,1]
_DAYLIGHT_LOCI_PARAMETERS = get_daylightloci_parameters(ccts = None, cieobs = None, wl3 = wl3, use_1931_2_published_daylightlocus_coeffs = False, verbosity = 0)
_DAYLIGHT_M12_COEFFS = get_daylightphase_Mi_coeffs(cieobs = None, wl3 = wl3, use_1931_2_published_Mcoeffs = False)
#------------------------------------------------------------------------------
[docs]
def cri_ref(ccts, wl3 = None, ref_type = _CRI_REF_TYPE, mix_range = None,
cieobs = None, cieobs_Y_normalization = None,
norm_type = None, norm_f = None,
force_daylight_below4000K = False, n = None,
daylight_locus = None,
round_daylightphase_Mi_to_cie_recommended = False,
interp_settings = None):
"""
Calculates a reference illuminant spectrum based on cct
for color rendering index calculations .
Args:
:ccts:
| list of int/floats or ndarray with ccts.
:wl3:
| None, optional
| New wavelength range for interpolation.
| Defaults to wavelengths specified by luxpy._WL3.
:ref_type:
| str or list[str], optional
| Specifies the type of reference spectrum to be calculated.
| Defaults to luxpy._CRI_REF_TYPE.
| If :ref_type: is list of strings, then for each cct in :ccts:
| a different reference illuminant can be specified.
| If :ref_type: == 'spd', then :ccts: is assumed to be an ndarray
| of reference illuminant spectra.
:mix_range:
| None or ndarray, optional
| Determines the cct range between which the reference illuminant is
| a weigthed mean of a Planckian and Daylight Phase spectrum.
| Weighthing is done as described in IES TM30:
| SPDreference = (Te-T)/(Te-Tb)*Planckian+(T-Tb)/(Te-Tb)*daylight
| with Tb and Te are resp. the starting and end CCTs of the
| mixing range and whereby the Planckian and Daylight SPDs
| have been normalized for equal luminous flux.
| If None: use the default specified for :ref_type:.
| Can be a ndarray with shape[0] > 1, in which different mixing
| ranges will be used for cct in :ccts:.
:cieobs:
| None, optional
| Required when calculating daylightphase (adjust locus parameters to cieobs)
| If None: value in _CRI_REF_TYPES will be used (with None here corresponding to _CIEOBS).
:cieobs_Y_normalization:
| None, optional
| Required for the normalization of the Planckian and Daylight SPDs
| when calculating a 'mixed' reference illuminant.
| If None: value in _CRI_REF_TYPES will be used,
| with None here resulting in the use of the value as specified in :cieobs:
:norm_type:
| None, optional
| - 'lambda': make lambda in norm_f equal to 1
| - 'area': area-normalization times norm_f
| - 'max': max-normalization times norm_f
| - 'ru': to :norm_f: radiometric units
| - 'pu': to :norm_f: photometric units
| - 'pusa': to :norm_f: photometric units (with Km corrected
| to standard air, cfr. CIE TN003-2015)
| - 'qu': to :norm_f: quantal energy units
:norm_f:
| 1, optional
| Normalization factor that determines the size of normalization
| for 'max' and 'area'
| or which wavelength is normalized to 1 for 'lambda' option.
:force_daylight_below4000K:
| False or True, optional
| Daylight locus approximation is not defined below 4000 K,
| but by setting this to True, the calculation can be forced to
| calculate it anyway.
:n:
| None, optional
| Refractive index (for use in calculation of blackbody radiators).
| If None: use the one stored in _BB['n']
:daylight_locus:
| None, optional
| dict with xD(T) and yD(xD) parameters to calculate daylight locus
| for specified cieobs.
| If None: use pre-calculated values.
| If 'calc': calculate them on the fly.
:round_daylightphase_Mi_to_cie_recommended:
| False, optional
| If True: Round M1, M2 values to 3 decimals as recommended by CIE (not that TM30 does not do this, which gives slight errors when calculating a daylight phase (equivalent of around 0.75 K for 6500 K illuminant))
| Rounding causes larger errors from target CCT! Therefore, the default is set to False!
| Note that neither CIE224-2017 or IES TM30 rounds M1 and M2 to 3 decimals!
Returns:
:returns:
| ndarray with reference illuminant spectra.
| (:returns:[0] contains wavelengths)
Note:
Future versions will have the ability to take a dict as input
for ref_type. This way other reference illuminants can be specified
than the ones in _CRI_REF_TYPES.
"""
if ref_type == 'spd':
# ccts already contains spectrum of reference:
return spd(ccts, wl = wl3, norm_type = norm_type, norm_f = norm_f, interp_settings = interp_settings)
else:
if mix_range is not None: mix_range = np2d(mix_range)
if cieobs is not None: cieobs = np.atleast_1d(cieobs)
if cieobs_Y_normalization is not None: cieobs_Y_normalization = np.atleast_1d(cieobs_Y_normalization)
if not (isinstance(ref_type,list) | isinstance(ref_type,dict)): ref_type = [ref_type]
for i in range(len(ccts)):
cct = ccts[i]
# get ref_type and mix_range:
if isinstance(ref_type,dict):
raise Exception("cri_ref(): dictionary ref_type: Not yet implemented")
else:
ref_type_ = ref_type[i] if (len(ref_type)>1) else ref_type[0]
if mix_range is None:
mix_range_ = _CRI_REF_TYPES[ref_type_]['mix_range']
else:
mix_range_ = mix_range[i] if (mix_range.shape[0]>1) else mix_range[0] #must be np2d !!!
if cieobs is None:
cieobs_ = _CRI_REF_TYPES[ref_type_]['cieobs']
else:
cieobs_ = cieobs[i] if (cieobs.shape[0]>1) else cieobs[0]
if cieobs_Y_normalization is None:
cieobs_Y_normalization_ = _CRI_REF_TYPES[ref_type_]['cieobs_Y_normalization']
else:
cieobs_Y_normalization_ = cieobs_Y_normalization[i] if (cieobs_Y_normalization.shape[0]>1) else cieobs_Y_normalization[0]
if cieobs_Y_normalization_ is None: cieobs_Y_normalization_ = cieobs_
if cieobs_Y_normalization_ is None: cieobs_Y_normalization_ = _CIEOBS # cieobs_Y_normalization_ might still be None as cieobs_ == None results in specific use of fixed published coeff. in the calculation of the daylight phase, while a string will result in calculation of these coeff.
if (mix_range_[0] == mix_range_[1]) | (ref_type_[0:2] == 'BB') | (ref_type_[0:2] == 'DL'):
if ((cct < mix_range_[0]) & (not (ref_type_[0:2] == 'DL'))) | (ref_type_[0:2] == 'BB'):
Sr = blackbody(cct, wl3, n = n)
elif ((cct >= mix_range_[0]) & (not (ref_type_[0:2] == 'BB'))) | (ref_type_[0:2] == 'DL') :
Sr = daylightphase(cct,wl3,force_daylight_below4000K = force_daylight_below4000K, cieobs = cieobs_, daylight_locus = daylight_locus,
round_Mi_to_cie_recommended = round_daylightphase_Mi_to_cie_recommended,
interp_settings = interp_settings)
else:
SrBB = blackbody(cct, wl3, n = n)
SrDL = daylightphase(cct,wl3,verbosity = None,force_daylight_below4000K = force_daylight_below4000K,
cieobs = cieobs_, daylight_locus = daylight_locus,
round_Mi_to_cie_recommended = round_daylightphase_Mi_to_cie_recommended,
interp_settings = interp_settings)
#cieobs_ = _CIEOBS if cieobs_ is None else cieobs_ # cieobs_ might still be None as that results in specific use of fixed published coeff. in the calculation of the daylight phase, while a string will result in calculation of these coeff.
cmf = xyzbar(cieobs = cieobs_Y_normalization_, src = 'dict', wl_new = wl3,
interp_settings = interp_settings)
wl = SrBB[0]
ld = getwld(wl)
SrBB = 100.0*SrBB[1]/np.array(np.sum(SrBB[1]*cmf[2]*ld))
SrDL = 100.0*SrDL[1]/np.array(np.sum(SrDL[1]*cmf[2]*ld))
Tb, Te = float(mix_range_[0]), float(mix_range_[1])
cBB, cDL = (Te-cct)/(Te-Tb), (cct-Tb)/(Te-Tb)
if cBB < 0.0:
cBB = 0.0
elif cBB > 1:
cBB = 1.0
if cDL < 0.0:
cDL = 0.0
elif cDL > 1:
cDL = 1.0
Sr = SrBB*cBB + SrDL*cDL
Sr[Sr==float('NaN')] = 0.0
Sr560 = Sr[np.where(np.abs(wl - 560.0) == np.min(np.abs(wl - 560.0)))[0]]
Sr = np.vstack((wl,(Sr/Sr560)))
if i == 0:
Srs = Sr[1]
else:
Srs = np.vstack((Srs,Sr[1]))
Srs = np.vstack((Sr[0],Srs))
return spd(Srs, wl = None, norm_type = norm_type, norm_f = norm_f,
interp_settings = interp_settings)
#------------------------------------------------------------------------------
[docs]
def spd_to_indoor(spd, interp_settings = None):
"""
Convert spd to indoor variant by multiplying it with the CIE spectral transmission for glass.
"""
Tglass = cie_interp(_CIE_GLASS_ID['T'].copy(), spd[0,:], datatype = 'rfl',
interp_settings = interp_settings)[1:,:]
spd_ = spd.copy()
spd_[1:,:] *= Tglass
return spd_