Beispiel #1
0
def get_cie_mesopic_adaptation(Lp, Ls=None, SP=None):
    """
    Get the mesopic adaptation state according to CIE191:2010
    
    Args:
        :Lp: 
            | float or ndarray with photopic adaptation luminance
        :Ls: 
            | None, optional
            | float or ndarray with scotopic adaptation luminance
            | If None: SP must be supplied.
        :SP:
            | None, optional
            | S/P ratio
            | If None: Ls must be supplied.
            
    Returns:
        :Lmes: 
            | mesopic adaptation luminance
        :m: 
            | mesopic adaptation coefficient
    Reference:
        1. `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>`_
    """
    Lp = np.atleast_1d(Lp)
    Ls = np.atleast_1d(Ls)
    SP = np.atleast_1d(SP)
    if not (None in SP):
        Ls = Lp * SP
    elif not (None in Ls):
        SP = Ls / Lp
    else:
        raise Exception(
            'Either the S/P ratio or the scotopic luminance Ls must be supplied in addition to the photopic luminance Lp'
        )
    m = np.ones_like(Ls) * np.nan
    Lmes = m.copy()
    for i in range(Lp.shape[0]):
        mi_ = 0.5
        fLmes = lambda m, Lp, SP: (
            (m * Lp) + (1 - m) * SP * 683 / 1699) / (m + (1 - m) * 683 / 1699)
        fm = lambda m, Lp, SP: 0.767 + 0.3334 * np.log10(fLmes(m, Lp, SP))
        mi = fm(mi_, Lp[i], SP[i])
        while True:
            if np.isclose(mi, mi_): break
            mi_ = mi
            mi = fm(mi_, Lp[i], SP[i])
        m[i] = mi
        Lmes[i] = fLmes(mi, Lp[i], SP[i])
    return Lmes, m
Beispiel #2
0
def positive_arctan(x,y, htype = 'deg'):
    """
    Calculate positive angle (0°-360° or 0 - 2*pi rad.) from x and y.
    
    Args:
        :x: 
            | ndarray of x-coordinates
        :y: 
            | ndarray of y-coordinates
        :htype:
            | 'deg' or 'rad', optional
            |   - 'deg': hue angle between 0° and 360°
            |   - 'rad': hue angle between 0 and 2pi radians
    
    Returns:
        :returns:
            | ndarray of positive angles.
    """
    if htype == 'deg':
        r2d = 180.0/np.pi
        h360 = 360.0
    else:
        r2d = 1.0
        h360 = 2.0*np.pi
    h = np.atleast_1d((np.arctan2(y,x)*r2d))
    h[np.where(h<0)] = h[np.where(h<0)] + h360
    return h
Beispiel #3
0
    def __init__(self, *args, argtype='xyz', vtype='xyz', _TINY=1e-15):
        """
        Initialize 3-dimensional vector.

        Args:
            :`*args`:
                | x,y,z coordinates
            :vtype:
                | 'xyz', optional
                | if 'xyz': cartesian coordinate input
                | if 'tpr': spherical coordinates input (t: theta, p: phi, r: radius)
            :_TINY:
                | Set smallest value considered still different from zero.
        """
        self._TINY = _TINY
        self.vtype = vtype
        if len(args) == 0:
            args = [0.0, 0.0, 0.0]
        args = [np.atleast_1d(args[i])
                for i in range(len(args))]  # make atleast_1d ndarray
        if vtype == 'xyz':
            self.x = args[0]
            self.y = args[1]
            self.z = args[2]
        elif vtype == 'tpr':
            if len(args) == 2:
                args.append(np.ones(args[0].shape))
            self.set_tpr(*args)
        self.shape = self.x.shape
Beispiel #4
0
def plot_color_data(x,y,z=None, axh=None, show = True, cieobs =_CIEOBS, \
                    cspace = _CSPACE,  formatstr = 'k-', legend_loc = None, **kwargs):
    """
    Plot color data from x,y [,z].
    
    Args: 
        :x: 
            | float or ndarray with x-coordinate data
        :y: 
            | float or ndarray with y-coordinate data
        :z: 
            | None or float or ndarray with Z-coordinate data, optional
            | If None: make 2d plot.
        :axh: 
            | None or axes handle, optional
            | Determines axes to plot data in.
            | None: make new figure.
        :show: 
            | True or False, optional
            | Invoke matplotlib.pyplot.show() right after plotting
        :cieobs: 
            | luxpy._CIEOBS or str, optional
            | Determines CMF set to calculate spectrum locus or other.
        :cspace:
            | luxpy._CSPACE or str or None, optional
            | Determines color space / chromaticity diagram to plot data in.
            | Note that data is expected to be in specified :cspace:
            | If None: don't do any formatting of x,y [z] axes.
        :formatstr: 
            | 'k-' or str, optional
            | Format str for plotting (see ?matplotlib.pyplot.plot)
        :kwargs:
            | additional keyword arguments for use with matplotlib.pyplot.
    
    Returns:
        :returns: 
            | None (:show: == True) 
            |  or 
            | handle to current axes (:show: == False)
    """
    x = np.atleast_1d(x)
    y = np.atleast_1d(y)

    if z is not None:
        z = np.atleast_1d(z)
        if axh is None:
            fig = plt.figure()
            axh = plt.axes(projection='3d')
        if 'grid' in kwargs.keys():
            axh.grid(kwargs['grid'])
            kwargs.pop('grid')
        axh.plot3D(x, y, z, formatstr, linewidth=2, **kwargs)
        axh.set_zlabel(_CSPACE_AXES[cspace][0], kwargs)
    else:
        if axh is None:
            fig = plt.figure()
            axh = plt.axes()
        if 'grid' in kwargs.keys():
            axh.grid(kwargs['grid'])
            kwargs.pop('grid')
        axh.plot(x, y, formatstr, linewidth=2, **kwargs)
        axh.set_xlabel(_CSPACE_AXES[cspace][1], kwargs)
        axh.set_ylabel(_CSPACE_AXES[cspace][2], kwargs)
    if 'label' in kwargs.keys():
        axh.legend(loc=legend_loc)
    if show == True:
        plt.show()
    else:
        return axh
Beispiel #5
0
def cie_interp(data,
               wl_new,
               kind=None,
               negative_values_allowed=False,
               extrap_values=None):
    """
    Interpolate / extrapolate spectral data following standard CIE15-2018.
    
    | The kind of interpolation depends on the spectrum type defined in :kind:. 
    | Extrapolation is always done by replicate the closest known values.
    
    Args:
        :data: 
            | ndarray with spectral data 
            | (.shape = (number of spectra + 1, number of original wavelengths))
        :wl_new: 
            | ndarray with new wavelengths
        :kind: 
            | None, optional
            |   - If :kind: is None, return original data.
            |   - If :kind: is a spectrum type (see _INTERP_TYPES), the correct 
            |     interpolation type if automatically chosen.
            |   - Or :kind: can be any interpolation type supported by 
            |     scipy.interpolate.interp1d (math.interp1d if nan's are present!!)
        :negative_values_allowed: 
            | False, optional
            | If False: negative values are clipped to zero.
        :extrap_values:
            | None, optional
            | If None: use CIE recommended 'closest value' approach when extrapolating.
            | If float or list or ndarray, use those values to fill extrapolated value(s).
            | If 'ext': use normal extrapolated values by scipy.interpolate.interp1d
    
    Returns:
        :returns: 
            | ndarray of interpolated spectral data.
            | (.shape = (number of spectra + 1, number of wavelength in wl_new))
    """
    if (kind is not None):
        # Wavelength definition:
        wl_new = getwlr(wl_new)

        if (not np.array_equal(data[0], wl_new)) | np.isnan(data).any():

            extrap_values = np.atleast_1d(extrap_values)

            # Set interpolation type based on data type:
            if kind in _INTERP_TYPES['linear']:
                kind = 'linear'
            elif kind in _INTERP_TYPES['cubic']:
                kind = 'cubic'

            # define wl, S, wl_new:
            wl = np.array(data[0])
            S = data[1:]
            wl_new = np.array(wl_new)

            # Interpolate each spectrum in S:
            N = S.shape[0]
            nan_indices = np.isnan(S)

            # Interpolate all (if not all rows have nan):
            rows_with_nans = np.where(nan_indices.sum(axis=1))[0]
            if not (rows_with_nans.size == N):
                #allrows_nans = False
                if extrap_values[0] is None:
                    fill_value = (0, 0)
                elif (((type(extrap_values[0]) == np.str_) |
                       (type(extrap_values[0]) == str))
                      and (extrap_values[0][:3] == 'ext')):
                    fill_value = 'extrapolate'
                else:
                    fill_value = (extrap_values[0], extrap_values[-1])
                Si = sp.interpolate.interp1d(wl,
                                             S,
                                             kind=kind,
                                             bounds_error=False,
                                             fill_value=fill_value)(wl_new)

                #extrapolate by replicating closest known (in source data!) value (conform CIE15-2004 recommendation)
                if extrap_values[0] is None:
                    Si[:, wl_new < wl[0]] = S[:, :1]
                    Si[:, wl_new > wl[-1]] = S[:, -1:]

            else:
                #allrows_nans = True
                Si = np.zeros([N, wl_new.shape[0]])
                Si.fill(np.nan)

            # Re-interpolate those which have none:
            if nan_indices.any():
                #looping required as some values are NaN's
                for i in rows_with_nans:

                    nonan_indices = np.logical_not(nan_indices[i])
                    wl_nonan = wl[nonan_indices]
                    S_i_nonan = S[i][nonan_indices]
                    Si_nonan = math.interp1(wl_nonan,
                                            S_i_nonan,
                                            wl_new,
                                            kind=kind,
                                            ext='extrapolate')
                    #                    Si_nonan = sp.interpolate.interp1d(wl_nonan, S_i_nonan, kind = kind, bounds_error = False, fill_value = 'extrapolate')(wl_new)

                    #extrapolate by replicating closest known (in source data!) value (conform CIE15-2004 recommendation)
                    if extrap_values[0] is None:
                        Si_nonan[wl_new < wl_nonan[0]] = S_i_nonan[0]
                        Si_nonan[wl_new > wl_nonan[-1]] = S_i_nonan[-1]
                    elif (((type(extrap_values[0]) == np.str_) |
                           (type(extrap_values[0]) == str))
                          and (extrap_values[0][:3] == 'ext')):
                        pass
                    else:
                        Si_nonan[wl_new < wl_nonan[0]] = extrap_values[0]
                        Si_nonan[wl_new > wl_nonan[-1]] = extrap_values[-1]

                    Si[i] = Si_nonan

            # No negative values allowed for spectra:
            if negative_values_allowed == False:
                if np.any(Si): Si[Si < 0.0] = 0.0

            # Add wavelengths to data array:
            return np.vstack((wl_new, Si))

    return data