Esempio n. 1
0
def _cri_ref(ccts,
             wl3=_WL,
             ref_type='iestm30',
             mix_range=[4000, 5000],
             cieobs='1931_2',
             force_daylight_below4000K=False,
             n=None,
             daylight_locus=None,
             wl=[360, 830, 1]):
    """
    Calculates multiple reference illuminant spectra based on ccts 
    for color rendering index calculations.
    """
    if mix_range is None:
        mix_range = _CRI_REF_TYPES[ref_type]
    if isinstance(ccts, float): ccts = [ccts]
    wlr = getwlr(wl3)
    Srs = np.zeros((len(ccts) + 1, len(wlr)))
    Srs[0] = wlr
    for i, cct in enumerate(ccts):
        Srs[i + 1, :] = _cri_ref_i(
            cct,
            wl3=wl3,
            ref_type=ref_type,
            mix_range=mix_range,
            cieobs=cieobs,
            force_daylight_below4000K=force_daylight_below4000K,
            n=n,
            daylight_locus=daylight_locus)[1:]

    return Srs
def add_to_cmf_dict(bar=None, cieobs='indv', K=683, M=np.eye(3)):
    """
    Add set of cmfs to _CMF dict.
    
    Args:
        :bar: 
            | None, optional
            | Set of CMFs. None: initializes to empty ndarray.
        :cieobs:
            | 'indv' or str, optional
            | Name of CMF set.
        :K: 
            | 683 (lm/W), optional
            | Conversion factor from radiometric to photometric quantity.
        :M: 
            | np.eye, optional
            | Matrix for lms to xyz conversion.

    """
    if bar is None:
        wl3 = getwlr(_WL3)
        bar = np.vstack((wl3, np.empty((3, wl3.shape[0]))))
    _CMF['types'].append(cieobs)
    _CMF[cieobs] = {'bar': bar}
    _CMF[cieobs]['K'] = K
    _CMF[cieobs]['M'] = M
Esempio n. 3
0
def hsi_to_rgb(hsi,
               spd=None,
               cieobs=_CIEOBS,
               srgb=False,
               linear_rgb=False,
               CSF=None,
               wl=[380, 780, 1]):
    """ 
    Convert HyperSpectral Image to rgb.
    
    Args:
        :hsi:
            | ndarray with hyperspectral image [M,N,L]
        :spd:
            | None, optional
            | ndarray with illumination spectrum
        :cieobs:
            | _CIEOBS, optional
            | CMF set to convert spectral data to xyz tristimulus values.
        :srgb:
            | False, optional
            | If False: Use xyz_to_srgb(spd_to_xyz(...)) to convert to srgb values
            | If True: use camera sensitivity functions.
        :linear_rgb:
            | False, optional
            | If False: use gamma = 2.4 in xyz_to_srgb, if False: use gamma = 1.
        :CSF:
            | None, optional
            | ndarray with camera sensitivity functions 
            | If None: use Nikon D700
        :wl:
            | [380,780,1], optional
            | Wavelength range and spacing or ndarray with wavelengths of HSI image.
    
    Returns:
        :rgb:
            | ndarray with rgb image [M,N,3]
    """
    if spd is None:
        spd = _CIE_E.copy()
    wlr = getwlr(wl)
    spd = cie_interp(spd, wl, kind='linear')

    hsi_2d = np.reshape(hsi, (hsi.shape[0] * hsi.shape[1], hsi.shape[2]))

    if srgb:
        xyz = spd_to_xyz(spd,
                         cieobs=cieobs,
                         relative=True,
                         rfl=np.vstack((wlr, hsi_2d)))
        gamma = 1 if linear_rgb else 2.4
        rgb = xyz_to_srgb(xyz, gamma=gamma) / 255
    else:
        if CSF is None: CSF = _CSF_NIKON_D700
        rgb = rfl_to_rgb(hsi_2d, spd=spd, CSF=CSF, wl=wl)
    return np.reshape(rgb, (hsi.shape[0], hsi.shape[1], 3))
Esempio n. 4
0
 def spdBB(CCT=5500, wl=[400, 700, 5], Lw=25000, cieobs='1964_10'):
     wl = getwlr(wl)
     dl = wl[1] - wl[0]
     spd = 2 * np.pi * 6.626068E-34 * (299792458**2) / (
         (wl * 0.000000001)**
         5) / (np.exp(6.626068E-34 * 299792458 /
                      (wl * 0.000000001) / 1.3806503E-23 / CCT) - 1)
     spd = Lw * spd / (dl * 683 * (spd * cie_interp(
         _CMF[cieobs]['bar'].copy(), wl, kind='cmf')[2, :]).sum())
     return np.vstack((wl, spd))
Esempio n. 5
0
def _convert_to_wlr(entries=rgb2spec_entries, wlr=_WL3):
    wlr = getwlr(wlr)
    for entry in entries:
        if entry != 'wlr':
            for (k, v) in entries[entry].items():
                if k != 'scalefactor':
                    entries[entry][k] = cie_interp(_addwlr(entries[entry][k]),
                                                   wlr,
                                                   kind=entry)[1]
    entries['wlr'] = wlr
    return entries
Esempio n. 6
0
def rfl_to_rgb(rfl, spd=None, CSF=None, wl=None, normalize_to_white=True):
    """ 
    Convert spectral reflectance functions (illuminated by spd) to Camera Sensitivity Functions.
    
    Args:
        :rfl:
            | ndarray with spectral reflectance functions (1st row is wavelengths if wl is None).
        :spd:
            | None, optional
            | ndarray with illumination spectrum
        :CSF:
            | None, optional
            | ndarray with camera sensitivity functions 
            | If None: use Nikon D700
        :normalize_to_white:
            | True, optional
            | If True: white-balance output rgb to a perfect white diffuser.
    
    Returns:
        :rgb:
            | ndarray with rgb values for each spectral reflectance functions
    """
    rfl_cp = rfl.copy()
    if (wl is None):
        wl = rfl_cp[0]
        rfl_cp = rfl_cp[1:]
    wlr = getwlr(wl)
    if spd is not None:
        spd = cie_interp(spd, wlr, kind='linear')[1:]
    else:
        spd = np.ones_like(wlr)
    if CSF is None: CSF = _CSF_NIKON_D700
    CSF = cie_interp(CSF, wlr, kind='linear')
    CSF[1:] = CSF[1:] * spd
    rgb = rfl_cp @ CSF[1:].T
    if normalize_to_white:
        white = np.ones_like(spd)
        rgbw = white @ CSF[1:].T
        rgb = rgb / rgbw.max(axis=0, keepdims=True)

    return rgb
Esempio n. 7
0
def rgb_to_spec_smits(rgb, intent='rfl', bitdepth=8, wlr=_WL3, rgb2spec=None):
    """
    Convert an array of RGB values to a spectrum using a Smits like conversion as implemented in Mitsuba.
    
    Args:
        :rgb: 
            | ndarray of list of rgb values
        :intent:
            | 'rfl' (or 'spd'), optional
            | type of requested spectrum conversion .
        :bitdepth:
            | 8, optional
            | bit depth of rgb values
        :wlr: 
            | _WL3, optional
            | desired wavelength (nm) range of spectrum.
        :rgb2spec:
            | None, optional
            | Dict with base spectra for white, cyan, magenta, yellow, blue, green and red for each intent.
            | If None: use _BASESPEC_SMITS.
        
    Returns:
        :spec: 
            | ndarray with spectrum or spectra (one for each rgb value, first row are the wavelengths)
    """
    if isinstance(rgb, list):
        rgb = np.atleast_2d(rgb)
    if rgb.max() > 1:
        rgb = rgb / (2**bitdepth - 1)
    if rgb2spec is None:
        rgb2spec = _BASESPEC_SMITS
    if not np.array_equal(rgb2spec['wlr'], getwlr(wlr)):
        rgb2spec = _convert_to_wlr(entries=copy.deepcopy(rgb2spec), wlr=wlr)
    spec = np.zeros((rgb.shape[0], rgb2spec['wlr'].shape[0]))
    for i in range(rgb.shape[0]):
        spec[i, :] = _fromLinearRGB(rgb[i, :],
                                    intent=intent,
                                    rgb2spec=rgb2spec,
                                    wlr=wlr)
    return np.vstack((rgb2spec['wlr'], spec))
import matplotlib.pyplot as plt

from luxpy import (spd_to_xyz, xyz_to_cct, getwld, getwlr, _CMF, blackbody, daylightphase, 
                   _CRI_RFL, _CRI_REF_TYPES, _CRI_REF_TYPE,_CIEOBS, xyzbar, cie_interp)
from luxpy.color.cam import (xyz_to_jab_cam02ucs, hue_angle)
from luxpy.color.cri.utils.DE_scalers import log_scale
from luxpy.color.cri.utils.helpers2 import _get_hue_bin_data 
from luxpy import math

# __all__ = ['spd_to_xyz','spd_to_ler','xyz_to_cct'] # direct imports from luxpy
__all__ = ['_cri_ref','_xyz_to_jab_cam02ucs','spd_to_tm30'] # new or redefined


_DL = 1
_WL3 = [360,830,_DL]
_WL = getwlr(_WL3)
_POS_WL560 = np.where(np.abs(_WL - 560.0) == np.min(np.abs(_WL - 560.0)))[0]
_TM30_SAMPLE_SET = _CRI_RFL['ies-tm30-18']['99']['{:1.0f}nm'.format(_DL)]


def _cri_ref_i(cct, wl3 = _WL, ref_type = 'iestm30', mix_range = [4000,5000], 
            cieobs = '1931_2', force_daylight_below4000K = False, n = None,
            daylight_locus = None):
    """
    Calculates a reference illuminant spectrum based on cct 
    for color rendering index calculations.
    """   
    if mix_range is None:
        mix_range =  _CRI_REF_TYPES[ref_type]
    if (cct < mix_range[0]) | (ref_type == 'BB'):
        return blackbody(cct, wl3, n = n)
Esempio n. 9
0
def cam18sl(data, datab = None, Lb = [100], fov = 10.0, inputtype = 'xyz', direction = 'forward', outin = 'Q,aW,bW', parameters = None):
    """
    Convert between CIE 2006 10°  XYZ tristimulus values (or spectral data) 
    and CAM18sl color appearance correlates.
    
    Args:
        :data: 
            | ndarray of CIE 2006 10°  absolute XYZ tristimulus values or spectral data
              or color appearance attributes of stimulus
        :datab: 
            | ndarray of CIE 2006 10°  absolute XYZ tristimulus values or spectral data
              of stimulus background
        :Lb: 
            | [100], optional
            | Luminance (cd/m²) value(s) of background(s) calculated using the CIE 2006 10° CMFs 
            | (only used in case datab == None and the background is assumed to be an Equal-Energy-White)
        :fov: 
            | 10.0, optional
            | Field-of-view of stimulus (for size effect on brightness)
        :inputtpe:
            | 'xyz' or 'spd', optional
            | Specifies the type of input: 
            |     tristimulus values or spectral data for the forward mode.
        :direction:
            | 'forward' or 'inverse', optional
            |   -'forward': xyz -> cam18sl
            |   -'inverse': cam18sl -> xyz 
        :outin:
            | 'Q,aW,bW' or str, optional
            | 'Q,aW,bW' (brightness and opponent signals for amount-of-neutral)
            |  other options: 'Q,aM,bM' (colorfulness) and 'Q,aS,bS' (saturation)
            | Str specifying the type of 
            |     input (:direction: == 'inverse') and 
            |     output (:direction: == 'forward')
        :parameters:
            | None or dict, optional
            | Set of model parameters.
            |   - None: defaults to luxpy.cam._CAM18SL_PARAMETERS 
            |    (see references below)
    
    Returns:
        :returns: 
            | ndarray with color appearance correlates (:direction: == 'forward')
            |  or 
            | XYZ tristimulus values (:direction: == 'inverse')
            
    Notes:
        | * Instead of using the CIE 1964 10° CMFs in some places of the model,
        |   the CIE 2006 10° CMFs are used througout, making it more self_consistent.
        |   This has an effect on the k scaling factors (now different those in CAM15u) 
        |   and the illuminant E normalization for use in the chromatic adaptation transform.
        |   (see future erratum to Hermans et al., 2018)
        | * The paper also used an equation for the amount of white W, which is
        |   based on a Q value not expressed in 'bright' ('cA' = 0.937 instead of 123). 
        |   This has been corrected for in the luxpy version of the model, i.e.
        |   _CAM18SL_PARAMETERS['cW'][0] has been changed from 2.29 to 1/11672.
        |   (see future erratum to Hermans et al., 2018)

    References: 
        1. `Hermans, S., Smet, K. A. G., & Hanselaer, P. (2018). 
        "Color appearance model for self-luminous stimuli."
        Journal of the Optical Society of America A, 35(12), 2000–2009. 
        <https://doi.org/10.1364/JOSAA.35.002000>`_ 
     """
    
    if parameters is None:
        parameters = _CAM18SL_PARAMETERS
        
    outin = outin.split(',')    
    
    #unpack model parameters:
    cA, cAlms, cHK, cM, cW, ca, calms, cb, cblms, cfov, k, naka, unique_hue_data = [parameters[x] for x in sorted(parameters.keys())]
    
    # precomputations:
    Mlms2xyz = np.linalg.inv(_CMF['2006_10']['M'])
    MAab = np.array([cAlms,calms,cblms])
    invMAab = np.linalg.inv(MAab)    
    
    #-------------------------------------------------
    # setup EEW reference field and default background field (Lr should be equal to Lb):
    # Get Lb values:
    if datab is not None:
        if inputtype != 'xyz':
            Lb = spd_to_xyz(datab, cieobs = '2006_10', relative = False)[...,1:2]
        else:
            Lb = datab[...,1:2]
    else:
        if isinstance(Lb,list):
            Lb = np2dT(Lb)

    # Setup EEW ref of same luminance as datab:
    if inputtype == 'xyz':
        wlr = getwlr(_CAM18SL_WL3)
    else:
        if datab is None:
            wlr = data[0] # use wlr of stimulus data
        else:
            wlr = datab[0] # use wlr of background data
    datar = np.vstack((wlr,np.ones((Lb.shape[0], wlr.shape[0])))) # create eew
    xyzr = spd_to_xyz(datar, cieobs = '2006_10', relative = False) # get abs. tristimulus values
    datar[1:] = datar[1:]/xyzr[...,1:2]*Lb
    # Create datab if None:
    if (datab is None):
        if inputtype != 'xyz':
            datab = datar.copy()
        else:
            datab = spd_to_xyz(datar, cieobs = '2006_10', relative = False)
            datar = datab.copy()

 
    # prepare data and datab for loop over backgrounds: 
    # make axis 1 of datab have 'same' dimensions as data:         
    if (data.ndim == 2): 
        data = np.expand_dims(data, axis = 1)  # add light source axis 1     

    if inputtype == 'xyz': 
        if datab.shape[0] == 1: #make datab and datar have same lights source dimension (used to store different backgrounds) size as data
            datab = np.repeat(datab,data.shape[1],axis=0)  
            datar = np.repeat(datar,data.shape[1],axis=0)               
    else:
        if datab.shape[0] == 2:
            datab = np.vstack((datab[0],np.repeat(datab[1:], data.shape[1], axis = 0)))
        if datar.shape[0] == 2:
            datar = np.vstack((datar[0],np.repeat(datar[1:], data.shape[1], axis = 0)))

    # Flip light source/ background dim to axis 0:
    data = np.transpose(data, axes = (1,0,2))

    #-------------------------------------------------
    
    #initialize camout:     
    dshape = list(data.shape)
    dshape[-1] = len(outin) # requested number of correlates
    if (inputtype != 'xyz') & (direction == 'forward'):
        dshape[-2] = dshape[-2] - 1 # wavelength row doesn't count & only with forward can the input data be spectral
    camout = np.nan*np.ones(dshape)
    
  
    for i in range(data.shape[0]):
       
        # get rho, gamma, beta of background and reference white:
        if (inputtype != 'xyz'):
            xyzb = spd_to_xyz(np.vstack((datab[0], datab[i+1:i+2,:])), cieobs = '2006_10', relative = False)
            xyzr = spd_to_xyz(np.vstack((datar[0], datar[i+1:i+2,:])), cieobs = '2006_10', relative = False)
        else:
            xyzb = datab[i:i+1,:] 
            xyzr = datar[i:i+1,:] 

        lmsb = np.dot(_CMF['2006_10']['M'],xyzb.T).T # convert to l,m,s
        rgbb = (lmsb / _CMF['2006_10']['K']) * k # convert to rho, gamma, beta
        #lmsr = np.dot(_CMF['2006_10']['M'],xyzr.T).T # convert to l,m,s
        #rgbr = (lmsr / _CMF['2006_10']['K']) * k # convert to rho, gamma, beta
        #rgbr = rgbr/rgbr[...,1:2]*Lb[i] # calculated EEW cone excitations at same luminance values as background
        rgbr = np.ones(xyzr.shape)*Lb[i] # explicitely equal EEW cone excitations at same luminance values as background

        if direction == 'forward':
            # get rho, gamma, beta of stimulus:
            if (inputtype != 'xyz'):
                xyz = spd_to_xyz(data[i], cieobs = '2006_10', relative = False)   
            elif (inputtype == 'xyz'):
                xyz = data[i]
            lms = np.dot(_CMF['2006_10']['M'],xyz.T).T # convert to l,m,s
            rgb = (lms / _CMF['2006_10']['K']) * k # convert to rho, gamma, beta

            # apply von-kries cat with D = 1:
            if (rgbb == 0).any():
                Mcat = np.eye(3)
            else:
                Mcat = np.diag((rgbr/rgbb)[0])
            rgba = np.dot(Mcat,rgb.T).T

            # apply naka-rushton compression:
            rgbc = naka_rushton(rgba, n = naka['n'], sig = naka['sig'](rgbr.mean()), noise = naka['noise'], scaling = naka['scaling'])

            #rgbc = np.ones(rgbc.shape)*rgbc.mean() # test if eew ends up at origin
            
            # calculate achromatic and color difference signals, A, a, b:
            Aab = np.dot(MAab, rgbc.T).T
            A,a,b = asplit(Aab)
            a = ca*a
            b = cb*b

            # calculate colorfullness like signal M:
            M = cM*((a**2.0 + b**2.0)**0.5)

            # calculate brightness Q:
            Q = cA*(A + cHK[0]*M**cHK[1]) # last term is contribution of Helmholtz-Kohlrausch effect on brightness

            # calculate saturation, s:
            s = M / Q

            # calculate amount of white, W:
            W = 1 / (1.0 + cW[0]*(s**cW[1]))

            #  adjust Q for size (fov) of stimulus (matter of debate whether to do this before or after calculation of s or W, there was no data on s, M or W for different sized stimuli: after)
            Q = Q*(fov/10.0)**cfov

            # calculate hue, h and Hue quadrature, H:
            h = hue_angle(a,b, htype = 'deg')
            if 'H' in outin:
                H = hue_quadrature(h, unique_hue_data = unique_hue_data)
            else:
                H = None

            # calculate cart. co.:
            if 'aM' in outin:
                aM = M*np.cos(h*np.pi/180.0)
                bM = M*np.sin(h*np.pi/180.0)
            
            if 'aS' in outin:
                aS = s*np.cos(h*np.pi/180.0)
                bS = s*np.sin(h*np.pi/180.0)
            
            if 'aW' in outin:
                aW = W*np.cos(h*np.pi/180.0)
                bW = W*np.sin(h*np.pi/180.0)

            if (outin != ['Q','aW','bW']):
                camout[i] =  eval('ajoin(('+','.join(outin)+'))')
            else:
                camout[i] = ajoin((Q,aW,bW))
    
        
        elif direction == 'inverse':

            # get Q, M and a, b depending on input type:        
            if 'aW' in outin:
                Q,a,b = asplit(data[i])
                Q = Q / ((fov/10.0)**cfov) #adjust Q for size (fov) of stimulus back to that 10° ref
                W = (a**2.0 + b**2.0)**0.5
                s = (((1.0 / W) - 1.0)/cW[0])**(1.0/cW[1])
                M = s*Q
                
            
            if 'aM' in outin:
                Q,a,b = asplit(data[i])
                Q = Q / ((fov/10.0)**cfov) #adjust Q for size (fov) of stimulus back to that 10° ref
                M = (a**2.0 + b**2.0)**0.5
            
            if 'aS' in outin:
                Q,a,b = asplit(data[i])
                Q = Q / ((fov/10.0)**cfov) #adjust Q for size (fov) of stimulus back to that 10° ref
                s = (a**2.0 + b**2.0)**0.5
                M = s*Q
                      
            if 'h' in outin:
                Q, WsM, h = asplit(data[i])
                Q = Q / ((fov/10.0)**cfov) #adjust Q for size (fov) of stimulus back to that 10° ref
                if 'W' in outin:
                     s = (((1.0 / WsM) - 1.0)/cW[0])**(1.0/cW[1])
                     M = s*Q
                elif 's' in outin:
                     M = WsM*Q
                elif 'M' in outin:
                     M = WsM
            
            # calculate achromatic signal, A from Q and M:
            A = Q/cA - cHK[0]*M**cHK[1]

            # calculate hue angle:
            h = hue_angle(a,b, htype = 'rad')
            
            # calculate a,b from M and h:
            a = (M/cM)*np.cos(h)
            b = (M/cM)*np.sin(h)

            a = a/ca
            b = b/cb

            # create Aab:
            Aab = ajoin((A,a,b))    

            # calculate rgbc:
            rgbc = np.dot(invMAab, Aab.T).T    

            # decompress rgbc to (adapted) rgba :
            rgba = naka_rushton(rgbc, n = naka['n'], sig = naka['sig'](rgbr.mean()), noise = naka['noise'], scaling = naka['scaling'], direction = 'inverse')

            # apply inverse von-kries cat with D = 1:
            rgb = np.dot(np.diag((rgbb/rgbr)[0]),rgba.T).T

            # convert rgb to lms to xyz:
            lms = rgb/k*_CMF['2006_10']['K']  
            xyz = np.dot(Mlms2xyz,lms.T).T 
            
            camout[i] = xyz
    
    if camout.shape[0] == 1:
        camout = np.squeeze(camout,axis = 0)
    
    return camout
Esempio n. 10
0
    for i in range(C.shape[0]-1):
        C[i+1] = Lw*C[i+1]/xyzw2[i,1]

    
    xyz, xyzw = spd_to_xyz(C, cieobs = cieobs, relative = True, rfl = rflM, out = 2)
    qab = xyz_to_qabW_cam18sl(xyzw, xyzb = None, Lb = [100], fov = 10.0)
    print('qab: ',qab)
    qab2 = cam18sl(C, datab = None, Lb = [100], fov = 10.0, direction = 'forward', inputtype = 'spd', outin = 'Q,aW,bW', parameters = None)
    print('qab2: ',qab2)       
    xyz_ = qabW_cam18sl_to_xyz(qab, xyzb = None, Lb = [100], fov = 10.0)
    print('delta: ', xyzw-xyz_)
    
    # test 2:
    cieobs = '2006_10'
    Lb = np2d([100])
    wlr = getwlr(_CAM18SL_WL3)
    EEW = np.vstack((wlr,np.ones((Lb.shape[1], wlr.shape[0])))) 
    E = cie_interp(_CIE_ILLUMINANTS['E'],EEW[0],kind='spd')
    D65 = cie_interp(_CIE_ILLUMINANTS['D65'],EEW[0],kind='spd')
    A = cie_interp(_CIE_ILLUMINANTS['A'],EEW[0],kind='spd')
    C = cie_interp(_CIE_ILLUMINANTS['C'],EEW[0],kind='spd')
    
    STIM = np.vstack((EEW, E[1:], C[1:], D65[1,:], A[1:]))
    xyz = spd_to_xyz(STIM, cieobs = cieobs, relative = False)
    STIM[1:] = STIM[1:]/xyz[...,1:2]*Lw 
    xyz = spd_to_xyz(STIM, cieobs = cieobs, relative = False)
    
    BG = EEW
    qab = cam18sl(EEW, datab = EEW, Lb = [100], fov = 10.0, direction = 'forward', inputtype = 'spd', outin = 'Q,aW,bW', parameters = None)
    print('test 2 qab: ',qab)
    
Esempio n. 11
0
    else:
        f = lambda xyz, xyzw: cam.xyz_to_jabC_ciecam02(
            xyz, xyzw=xyzw, La=1000 * 20 / np.pi / 100, Yb=20, surround='avg')
        lab = f(xyz, xyzw)
        labd65 = np.repeat(f(_XYZ_D65_REF, _XYZW_D65_REF),
                           lab.shape[1],
                           axis=1)

    fci = 100 * (_polyarea3D(lab) / _polyarea3D(labd65))**1.5

    return fci


if __name__ == '__main__':
    import luxpy as lx
    F6 = lx.cie_interp(lx._CIE_ILLUMINANTS['F6'],
                       wl_new=lx.getwlr([360, 830, 1]),
                       kind='spd')
    F4 = lx.cie_interp(lx._CIE_F4, wl_new=lx.getwlr([360, 830, 1]), kind='spd')
    D65 = lx.cie_interp(lx._CIE_D65,
                        wl_new=lx.getwlr([360, 830, 1]),
                        kind='spd')
    spds = np.vstack((F6, F4[1:, :], D65[1:, :]))

    fci1a = spd_to_fci(F6, True)
    print(fci1a)
    fci1b = spd_to_fci(F6, False)
    print(fci1b)
    fci2 = spd_to_fci(spds)
    print(fci2)
def getCatObs(n_cat=10,
              fieldsize=2,
              out='LMS',
              wl=None,
              allow_negative_values=False):
    """
    Generate cone fundamentals for categorical observers.
    
    Args: 
        :n_cat: 
            | 10, optional
            | Number of observer CMFs to generate.
        :fieldsize:
            | fieldsize in degrees (between 2° and 10°), optional
            | Defaults to 10°.
        :out: 
            | 'LMS' or str, optional
            | Determines output.
        :wl: 
            | None, optional
            | Interpolation/extraplation of :LMS: output to specified wavelengths.
            |  None: output original _WL = np.array([390,780,5])
        :allow_negative_values:
            | False, optional
            | Cone fundamentals or color matching functions 
            |  should not have negative values.
            |     If False: X[X<0] = 0.
    
    Returns:
        :returns:
            | LMS [,var_age, vAll] 
            |   - LMS: ndarray with population LMS functions.
            |   - var_age: ndarray with population observer ages.
            |   - vAll: dict with population physiological factors (see .keys()) 
    
    Notes:
        1. Categorical observers are observer functions that would represent 
        color-normal populations. They are finite and discrete as opposed to 
        observer functions generated from the individual colorimetric observer 
        model. Thus, they would offer more convenient and practical approaches
        for the personalized color imaging workflow and color matching analyses.
        Categorical observers were derived in two steps. 
        At the first step, 10000 observer functions were generated from the 
        individual colorimetric observer model using Monte Carlo simulation. 
        At the second step, the cluster analysis, a modified k-medoids 
        algorithm, was applied to the 10000 observers minimizing the squared 
        Euclidean distance in cone fundamentals space, and categorical 
        observers were derived iteratively. Since the proposed categorical 
        observers are defined by their physiological parameters and ages, their
        CMFs can be derived for any target field size.

        2. Categorical observers were ordered by the importance; 
        the first categorical observer vas the average observer equivalent to 
        CIEPO06 with 38 year-old for a given field size, followed by the second
        most important categorical observer, the third, and so on.
        
        3. see: https://www.rit.edu/cos/colorscience/re_AsanoObserverFunctions.php
    """
    # Use Iteratively Derived Cat.Obs.:
    var_age = _INDVCMF_CATOBSPFCTR['age'].copy()
    vAll = _INDVCMF_CATOBSPFCTR.copy()
    vAll.pop('age')

    # Set requested wavelength range:
    if wl is not None:
        wl = getwlr(wl3=wl)
    else:
        wl = _WL

    LMS_All = np.nan * np.ones((3 + 1, _WL.shape[0], n_cat))
    for k in range(n_cat):
        t_LMS = cie2006cmfsEx(age = var_age[k],fieldsize = fieldsize, wl = wl,\
                              var_od_lens = vAll['od_lens'][k],\
                              var_od_macula = vAll['od_macula'][k],\
                              var_od_L = vAll['od_L'][k],\
                              var_od_M = vAll['od_M'][k],\
                              var_od_S = vAll['od_S'][k],\
                              var_shft_L = vAll['shft_L'][k],\
                              var_shft_M = vAll['shft_M'][k],\
                              var_shft_S = vAll['shft_S'][k],\
                              out = 'LMS')

        LMS_All[:, :, k] = t_LMS

    LMS_All[np.where(LMS_All < 0)] = 0

    if n_cat == 1:
        LMS_All = np.squeeze(LMS_All, axis=2)

    if ('xyz' in out.lower().split(',')):
        LMS_All = lmsb_to_xyzb(LMS_All,
                               fieldsize,
                               out='xyz',
                               allow_negative_values=allow_negative_values)
        out = out.replace('xyz', 'LMS').replace('XYZ', 'LMS')
    if ('lms' in out.lower().split(',')):
        out = out.replace('lms', 'LMS')

    if (out == 'LMS'):
        return LMS_All
    elif (out == 'LMS,var_age,vAll'):
        return LMS_All, var_age, vAll
    else:
        return eval(out)
def genMonteCarloObs(n_obs=1,
                     fieldsize=10,
                     list_Age=[32],
                     out='LMS',
                     wl=None,
                     allow_negative_values=False):
    """
    Monte-Carlo generation of individual observer cone fundamentals.
    
    Args: 
        :n_obs: 
            | 1, optional
            | Number of observer CMFs to generate.
        :list_Age:
            | list of observer ages or str, optional
            | Defaults to 32 (cfr. CIE2006 CMFs)
            | If 'us_census': use US population census of 2010 
              to generate list_Age.
        :fieldsize: 
            | fieldsize in degrees (between 2° and 10°), optional
            | Defaults to 10°.
        :out: 
            | 'LMS' or str, optional
            | Determines output.
        :wl: 
            | None, optional
            | Interpolation/extraplation of :LMS: output to specified wavelengths.
            | None: output original _WL = np.array([390,780,5])
        :allow_negative_values: 
            | False, optional
            | Cone fundamentals or color matching functions 
            |   should not have negative values.
            |     If False: X[X<0] = 0.
    
    Returns:
        :returns: 
            | LMS [,var_age, vAll] 
            |   - LMS: ndarray with population LMS functions.
            |   - var_age: ndarray with population observer ages.
            |   - vAll: dict with population physiological factors (see .keys()) 
            
    References:
         1. `Asano Y, Fairchild MD, and Blondé L (2016). 
         Individual Colorimetric Observer Model. 
         PLoS One 11, 1–19. 
         <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0145671>`_
         
         2. `Asano Y, Fairchild MD, Blondé L, and Morvan P (2016). 
         Color matching experiment for highlighting interobserver variability. 
         Color Res. Appl. 41, 530–539. 
         <https://onlinelibrary.wiley.com/doi/abs/10.1002/col.21975>`_
         
         3. `CIE, and CIE (2006). 
         Fundamental Chromaticity Diagram with Physiological Axes - Part I 
         (Vienna: CIE). 
         <http://www.cie.co.at/publications/fundamental-chromaticity-diagram-physiological-axes-part-1>`_ 
         
         4. `Asano's Individual Colorimetric Observer Model 
         <https://www.rit.edu/cos/colorscience/re_AsanoObserverFunctions.php>`_
    """

    # Scale down StdDev by scalars optimized using Asano's 75 observers
    # collected in Germany:
    stdDevAllParam = _INDVCMF_STD_DEV_ALL_PARAM.copy()
    scale_factors = [0.98, 0.98, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
    scale_factors = dict(zip(list(stdDevAllParam.keys()), scale_factors))
    stdDevAllParam = {
        k: v * scale_factors[k]
        for (k, v) in stdDevAllParam.items()
    }

    # Get Normally-distributed Physiological Factors:
    vAll = getMonteCarloParam(n_obs=n_obs)

    if list_Age is 'us_census':
        list_Age = getUSCensusAgeDist()

    # Generate Random Ages with the same probability density distribution
    # as color matching experiment:
    sz_interval = 1
    list_AgeRound = np.round(np.array(list_Age) / sz_interval) * sz_interval
    h = math.histogram(list_AgeRound,
                       bins=np.unique(list_AgeRound),
                       bin_center=True)[0]
    p = h / h.sum()  # probability density distribution

    var_age = np.random.choice(np.unique(list_AgeRound), \
                               size = n_obs, replace = True,\
                               p = p)

    # Set requested wavelength range:
    if wl is not None:
        wl = getwlr(wl3=wl)
    else:
        wl = _WL

    LMS_All = np.nan * np.ones((3 + 1, wl.shape[0], n_obs))
    for k in range(n_obs):
        t_LMS, t_trans_lens, t_trans_macula, t_sens_photopig = cie2006cmfsEx(age = var_age[k], fieldsize = fieldsize, wl = wl,\
                                                                          var_od_lens = vAll['od_lens'][k], var_od_macula = vAll['od_macula'][k], \
                                                                          var_od_L = vAll['od_L'][k], var_od_M = vAll['od_M'][k], var_od_S = vAll['od_S'][k],\
                                                                          var_shft_L = vAll['shft_L'][k], var_shft_M = vAll['shft_M'][k], var_shft_S = vAll['shft_S'][k],\
                                                                          out = 'LMS,trans_lens,trans_macula,sens_photopig')
        LMS_All[:, :, k] = t_LMS


#        listout = out.split(',')
#        if ('trans_lens' in listout) | ('trans_macula' in listout) | ('trans_photopig' in listout):
#            trans_lens[:,k] = t_trans_lens
#            trans_macula[:,k] = t_trans_macula
#            sens_photopig[:,:,k] = t_sens_photopig

    if n_obs == 1:
        LMS_All = np.squeeze(LMS_All, axis=2)

    if ('xyz' in out.lower().split(',')):
        LMS_All = lmsb_to_xyzb(LMS_All,
                               fieldsize,
                               out='xyz',
                               allow_negative_values=allow_negative_values)
        out = out.replace('xyz', 'LMS').replace('XYZ', 'LMS')
    if ('lms' in out.lower().split(',')):
        out = out.replace('lms', 'LMS')

    if (out == 'LMS'):
        return LMS_All
    elif (out == 'LMS,var_age,vAll'):
        return LMS_All, var_age, vAll
    else:
        return eval(out)
_INDVCMF_CATOBSPFCTR['age'] = t_data[:, 0]

# Matrices for conversion from LMS cone fundamentals to XYZ CMFs:
# (https://www.rit.edu/cos/colorscience/re_AsanoObserverFunctions.php)
# For 2-degree, the 3x3 matrix is:

_INDVCMF_M_2d = np.array([[0.4151, -0.2424, 0.0425], [0.1355, 0.0833, -0.0043],
                          [-0.0093, 0.0125, 0.2136]])

# For 10-degree, the 3x3 matrix is:
_INDVCMF_M_10d = np.array([[0.4499, -0.2630,
                            0.0460], [0.1617, 0.0726, -0.0011],
                           [-0.0036, 0.0054, 0.2291]])

_WL_CRIT = 620  # Asano: 620 nm: wavelenght at which interpolation fails for S-cones
_WL = getwlr([390, 780,
              5])  # wavelength range of specrtal data in _INDVCMF_DATA

def cie2006cmfsEx(age = 32,fieldsize = 10, wl = None,\
                  var_od_lens = 0, var_od_macula = 0, \
                  var_od_L = 0, var_od_M = 0, var_od_S = 0,\
                  var_shft_L = 0, var_shft_M = 0, var_shft_S = 0,\
                  out = 'LMS', allow_negative_values = False):
    """
    Generate Individual Observer CMFs (cone fundamentals) 
    based on CIE2006 cone fundamentals and published literature 
    on observer variability in color matching and in physiological parameters.
    
    Args:
        :age: 
            | 32 or float or int, optional
            | Observer age
Esempio n. 15
0
The equivalent luminance is calculated as:
    
    E,α = Km ⋅ ∫ Ee,λ(λ) sα(λ) dλ ⋅ ∫ V(λ) dλ / ∫ sα(λ) dλ

To avoid ambiguity, the weighting function used must be stated, so, for example, 
cyanopic refers to the cyanopic irradiance weighted using 
the s-cone or ssc(λ) spectral efficiency function.
----------------------------------------------------------------------------------------------------


----------------------------------------------------------------------------------------------------

Created on Tue Apr 17 12:25:29 2018

@author: kevin.smet
"""

if __name__ == '__main__':
    import luxpy as lx
    import numpy as np
    sid = np.vstack((lx.getwlr([378, 782, 1]), 0.1 * np.ones(
        (1, lx.getwlr([378, 782, 1]).shape[0]))))
    #sid= lx.blackbody(3000,wl3=lx.getwlr([378,782,1]))

    Ees, Es = lx.ciephotbio.spd_to_aopicE(sid, E=100, sid_units='uW/cm2')

    print('Ees:')
    print(Ees)
    print('Es:')
    print(Es)
Esempio n. 16
0
def get_superresolution_hsi(lrhsi,
                            hrci,
                            CSF,
                            wl=[380, 780, 1],
                            interp_type='nd',
                            k_neighbours=4,
                            verbosity=0):
    """ 
    Get a HighResolution HyperSpectral Image (super-resolution HSI) based on a LowResolution HSI and a HighResolution Color Image.
    
    Args:
        :lrhsi:
            | ndarray with LowResolution HSI [m,m,L].
        :hrci:
            | ndarray with HighResolution HSI [M,N,3].
        :CSF:
            | None, optional
            | ndarray with camera sensitivity functions 
            | If None: use Nikon D700
        :wl:
            | [380,780,1], optional
            | Wavelength range and spacing or ndarray with wavelengths of HSI image.
        :interp_type:
            | 'nd', optional
            | Options:
            | - 'nd': perform n-dimensional linear interpolation using Delaunay triangulation.
            | - 'nearest': perform nearest neighbour interpolation. 
        :k_neighbours:
            | 4 or int, optional
            | Number of nearest neighbours for reflectance spectrum interpolation.
            | Neighbours are found using scipy.spatial.cKDTree
        :verbosity:
            | 0, optional
            | Verbosity level for sub-call to render_image().
            | If > 0: make a plot of the color coordinates of original and 
            | rendered image pixels.
    Returns:
        :hrhsi:
            | ndarray with HighResolution HSI [M,N,L].
        
    Procedure:
        | Call render_image(hrci, rfl = lrhsi_2, CSF = ...) to estimate a hyperspectral image
        | from the high-resolution color image hrci with the reflectance spectra 
        | in the low-resolution hyper-spectral image as database for the estimation.
        | Estimation is done in raw RGB space with the lrhsi converted using the
        | camera sensitivity functions in CSF.
    """
    wlr = getwlr(wl)
    eew = np.vstack((wlr, np.ones_like(wlr)))
    lrhsi_2d = np.vstack(
        (wlr,
         np.reshape(lrhsi, (lrhsi.shape[0] * lrhsi.shape[1],
                            lrhsi.shape[2]))))  # create 2D rfl database
    if CSF is None: CSF = _CSF_NIKON_D700
    hrhsi = render_image(
        hrci,
        spd=eew,
        refspd=eew,
        rfl=lrhsi_2d,
        D=None,
        interp_type=interp_type,
        k_neighbours=k_neighbours,
        verbosity=verbosity,
        CSF=CSF)  # render HR-hsi from HR-ci using LR-HSI rfls as database
    return hrhsi
Esempio n. 17
0
    #---------------------------------------------------
    # get an image:
    im = imageio.imread(file) / 255

    # rescale to n x dimensions of typical hyperspectral camera:
    n = 2  # downscale factor
    w, h = 512, 512
    cr, cc = np.array(im.shape[:2]) // 2
    crop = lambda im, cr, cc, h, w: im[(cr - h // 2):(cr + h // 2),
                                       (cc - w // 2):(cc + w // 2), :].copy()
    im = crop(im, cr, cc, h * n, w * n)
    print('New image shape:', im.shape)

    # simulate HR hyperspectral image:
    hrhsi = render_image(im, show=False)
    wlr = getwlr([380, 780, 1])  #  = wavelength range of default TM30 rfl set
    wlr = wlr[20:-80:10]  # wavelength range from 400nm-700nm every 10 nm
    hrhsi = hrhsi[...,
                  20:-80:10]  # wavelength range from 400nm-700nm every 10 nm
    print('Simulated HR-HSI shape:', hrhsi.shape)
    # np.save(file[:-4]+'.npy',{'hrhsi':hrhsi,'im':im, 'wlr':wlr})

    # Illumination spectrum of HSI:
    eew = np.vstack((wlr, np.ones_like(wlr)))

    # Create fig and axes for plots:
    if verbosity > 0: fig, axs = plt.subplots(1, 3)

    # convert HR hsi to HR rgb image:
    hrci = hsi_to_rgb(hrhsi,
                      spd=eew,
Esempio n. 18
0
# -*- coding: utf-8 -*-
"""

Created on Sat Sep 23 20:06:06 2017

@author: kevin.smet
"""

import luxpy as lx
import numpy as np


spd = np.vstack((lx._CIE_ILLUMINANTS['D65'],lx._CIE_ILLUMINANTS['A'][1],lx._CIE_ILLUMINANTS['F4'][1],lx._CIE_ILLUMINANTS['F5'][1]))
spd = lx._IESTM30["S"]["data"].copy()
HL17 = lx._CRI_RFL["cri2012"]["HL17"].copy()
rfl = HL17
rfl = lx._IESTM30["R"]["99"]["5nm"].copy()
xyz1 = lx.spd_to_xyz(spd,rfl=rfl,cieobs = "1931_2",relative=True)
xyz2 = lx.spd_to_xyz(spd)
D65=lx._CIE_ILLUMINANTS['D65'].copy()
E=lx._CIE_ILLUMINANTS['E'].copy()

spd1 = np.vstack((lx.getwlr([400,700,1]),np.zeros((1,301))))
spd1[1,155] = 1
spd2 = spd1.copy()
spd2[1,195] =1
Esempio n. 19
0
def get_test_spectra(M, N):
    spd = np.vstack((lx.getwlr([360, 830, 1]), np.ones((N, 471))))
    rfl = np.vstack((lx.getwlr([360, 830, 1]), 0.5 * np.ones((M, 471))))
    return spd, rfl
Esempio n. 20
0
def test_model():

    import pandas as pd
    import luxpy as lx

    # Read selected set of Munsell samples and LMS10(lambda):
    M = pd.read_csv('Munsell_LMS_nonlin_Nov18_2015_version.dat',
                    header=None,
                    sep='\t').values
    YLMS10_ = pd.read_csv('YLMS10_LMS_nonlin_Nov18_2015_version.dat',
                          header=None,
                          sep='\t').values
    Y10_ = YLMS10_[[0, 1], :].copy()
    LMS10_ = YLMS10_[[0, 2, 3, 4], :].copy()

    # Calculate lms:
    Y10 = cie_interp(_CMF['1964_10']['bar'].copy(),
                     getwlr([400, 700, 5]),
                     kind='cmf')[[0, 2], :]
    XYZ10_lx = _CMF['2006_10']['bar'].copy()
    XYZ10_lx = cie_interp(XYZ10_lx, getwlr([400, 700, 5]), kind='cmf')
    LMS10_lx = np.vstack(
        (XYZ10_lx[:1, :],
         np.dot(
             math.normalize_3x3_matrix(_CMF['2006_10']['M'],
                                       np.array([[1, 1, 1]])),
             XYZ10_lx[1:, :])))
    LMS10 = cie_interp(LMS10_lx, getwlr([400, 700, 5]), kind='cmf')

    #LMS10 = np.vstack((XYZ10[:1,:],np.dot(lx.math.normalize_3x3_matrix(_CMF['2006_10']['M'],np.array([[1,1,1]])),XYZ10_lx[1:,:])))

    #LMS10[1:,:] = LMS10[1:,:]/LMS10[1:,:].sum(axis=1,keepdims=True)*Y10[1:,:].sum()

    # test python model vs excel calculator:
    def spdBB(CCT=5500, wl=[400, 700, 5], Lw=25000, cieobs='1964_10'):
        wl = getwlr(wl)
        dl = wl[1] - wl[0]
        spd = 2 * np.pi * 6.626068E-34 * (299792458**2) / (
            (wl * 0.000000001)**
            5) / (np.exp(6.626068E-34 * 299792458 /
                         (wl * 0.000000001) / 1.3806503E-23 / CCT) - 1)
        spd = Lw * spd / (dl * 683 * (spd * cie_interp(
            _CMF[cieobs]['bar'].copy(), wl, kind='cmf')[2, :]).sum())
        return np.vstack((wl, spd))

    # Create long term and applied spds:
    spd5500 = spdBB(5500, Lw=25000, wl=[400, 700, 5], cieobs='1964_10')
    spd6500 = spdBB(6500, Lw=400, wl=[400, 700, 5], cieobs='1964_10')

    # Calculate lms0 as a check:
    clms = np.array(
        [0.98446776, 0.98401909, 0.98571412]
    )  # correction factor for slight differences in _CMF and the cmfs from the excel calculator
    lms0 = 5 * 683 * (spd5500[1:] * LMS10[1:, :] * 0.2).sum(axis=1).T

    # Full excel parameters for testing:
    parameters = {
        'cLMS':
        np.array([1, 1, 1]),
        'lms0':
        np.array([4985.02802565, 5032.49518502, 4761.27272226]) * 1,
        'Cc':
        0.251617118325755,
        'Cf':
        -0.4,
        'clambda': [0.5, 0.5, 0.0],
        'calpha': [1.0, -1.0, 0.0],
        'cbeta': [0.5, 0.5, -1.0],
        'cga1': [26.1047711317923, 33.9721745703298],
        'cgb1': [6.76038379211498, 10.9220216677629],
        'cga2': [0.587271269247578],
        'cgb2': [-0.952412544980473],
        'cl_int': [14.0035243121804, 1.0],
        'cab_int': [4.99218965716342, 65.7869547646456],
        'cab_out': [-0.1, -1.0],
        'Ccwb':
        None,
        'Mxyz2lms': [[0.21701045, 0.83573367, -0.0435106],
                     [-0.42997951, 1.2038895, 0.08621089],
                     [0., 0., 0.46579234]]
    }

    # Note cLMS is a relative scaling factor between CIE2006 10° and 1964 10°:
    #    clms = np.array([1.00164919, 1.00119269, 1.0029173 ]) = (Y10[1:,:].sum(axis=1)/LMS10[1:,:].sum(axis=1))*(406.98099078/400)

    #parameters =_CAM_SWW16_PARAMETERS['JOSA']
    # Calculate Munsell spectra multiplied with spd6500:
    spd6500xM = np.vstack((spd6500[:1, :], spd6500[1:, :] * M[1:, :]))

    # Test spectral input:
    print('SPD INPUT -----')
    jab = cam_sww16(spd6500xM,
                    dataw=spd6500,
                    Yb=20.0,
                    Lw=400.0,
                    Ccwb=1,
                    relative=True,
                    inputtype='spd',
                    direction='forward',
                    parameters=parameters,
                    cieobs='2006_10',
                    match_to_conversionmatrix_to_cieobs=True)

    #    # Test xyz input:
    print('\nXYZ INPUT -----')
    xyz = lx.spd_to_xyz(spd6500xM, cieobs='2006_10', relative=False)
    xyzw = lx.spd_to_xyz(spd6500, cieobs='2006_10', relative=False)
    xyz2, xyzw2 = lx.spd_to_xyz(spd6500,
                                cieobs='2006_10',
                                relative=False,
                                rfl=M,
                                out=2)

    print(xyzw)
    jab = cam_sww16(xyz,
                    dataw=xyzw,
                    Yb=20.0,
                    Lw=400,
                    Ccwb=1,
                    relative=True,
                    inputtype='xyz',
                    direction='forward',
                    parameters=parameters,
                    cieobs='2006_10',
                    match_to_conversionmatrix_to_cieobs=True)