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))
def plot_rfl_color_patches(rfl, spd=None, cieobs='1931_2', patch_shape=(100, 100), patch_layout=None, ax=None, show=True): """ Create (and plot) an image with colored patches representing a set of reflectance spectra illuminated by a specified illuminant. Args: :rfl: | ndarray with reflectance spectra :spd: | None, optional | ndarray with illuminant spectral power distribution | If None: _CIE_D65 is used. :cieobs: | '1931_2', optional | CIE standard observer to use when converting rfl to xyz. :patch_shape: | (100,100), optional | shape of each of the patches in the image :patch_layout: | None, optional | If None: layout is calculated automatically to give a 'good' aspect ratio :ax: | None, optional | Axes to plot the image in. If None: a new axes is created. :show: | True, optional | If True: plot image in axes and return axes handle; else: return ndarray with image. Return: :ax: or :imagae: | Axes is returned if show == True, else: ndarray with rgb image is returned. """ if spd is None: spd = _CIE_D65 xyz = spd_to_xyz(spd, rfl=rfl, cieobs=cieobs)[:, 0, :] rgb = xyz_to_srgb(xyz).astype('uint8') return plot_rgb_color_patches(rgb, ax=ax, patch_shape=patch_shape, patch_layout=patch_layout, show=show)
def to_srgb(self, gamma=2.4): """ Calculates IEC:61966 sRGB values from xyz. Args: :xyz: | ndarray with relative tristimulus values. :gamma: | 2.4, optional | compression in sRGB Returns: :rgb: | ndarray with R,G,B values (uint8). """ return LAB(value=xyz_to_srgb(self.value, gamma=gamma), relative=self.relative, cieobs=self.cieobs, dtype='srgb')
labw = lx.xyz_to_Vrb_mb(xyzw) lab = lx.xyz_to_Vrb_mb(xyz) xyzw_ = lx.Vrb_mb_to_xyz(labw) xyz_ = lx.Vrb_mb_to_xyz(lab) labw = lx.xyz_to_Ydlep(xyzw) lab = lx.xyz_to_Ydlep(xyz) xyzw_ = lx.Ydlep_to_xyz(labw) xyz_ = lx.Ydlep_to_xyz(lab) labw = lx.xyz_to_lms(xyzw) lab = lx.xyz_to_lms(xyz) xyzw_ = lx.lms_to_xyz(labw) xyz_ = lx.lms_to_xyz(lab) labw = lx.xyz_to_srgb(xyzw) lab = lx.xyz_to_srgb(xyz) xyzw_ = lx.srgb_to_xyz(labw) xyz_ = lx.srgb_to_xyz(lab) print(labw.shape) print(lab.shape) print(xyzw - xyzw_) print(xyz - xyz_) # cam_sww6: xyz, xyzw = lx.spd_to_xyz(spds, rfl=rfls, cieobs='2006_10', out=2) jabw = lx.xyz_to_lab_cam_sww16(xyzw, xyzw=xyzw.copy()) jab = lx.xyz_to_lab_cam_sww16(xyz, xyzw=xyzw) xyzw_ = lx.lab_cam_sww16_to_xyz(jabw, xyzw=xyzw) xyz_ = lx.lab_cam_sww16_to_xyz(jab, xyzw=xyzw)
def plot_spectrum_colors(spd = None, spdmax = None,\ wavelength_height = -0.05, wavelength_opacity = 1.0, wavelength_lightness = 1.0,\ cieobs = _CIEOBS, show = True, axh = None,\ show_grid = False,ylabel = 'Spectral intensity (a.u.)',xlim=None,\ **kwargs): """ Plot the spectrum colors. Args: :spd: | None, optional | Spectrum :spdmax: | None, optional | max ylim is set at 1.05 or (1+abs(wavelength_height)*spdmax) :wavelength_opacity: | 1.0, optional | Sets opacity of wavelength rectangle. :wavelength_lightness: | 1.0, optional | Sets lightness of wavelength rectangle. :wavelength_height: | -0.05 or 'spd', optional | Determine wavelength bar height | if not 'spd': x% of spd.max() :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. :show_grid: | False, optional | Show grid (True) or not (False) :ylabel: | 'Spectral intensity (a.u.)' or str, optional | Set y-axis label. :xlim: | None, optional | list or ndarray with xlimits. :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: """ if isinstance(cieobs, str): cmfs = _CMF[cieobs]['bar'] else: cmfs = cieobs cmfs = cmfs[:, cmfs[1:].sum(axis=0) > 0] # avoid div by zero in xyz-to-Yxy conversion wavs = cmfs[0:1].T SL = cmfs[1:4].T srgb = xyz_to_srgb(wavelength_lightness * 100 * SL) srgb = srgb / srgb.max() if show == True: if axh is None: fig = plt.figure() axh = fig.add_subplot(111) if (wavelength_height == 'spd') & (spd is not None): if spdmax is None: spdmax = np.nanmax(spd[1:, :]) y_min, y_max = 0.0, spdmax * (1.05) if xlim is None: x_min, x_max = spd[0, :].min(), spd[0, :].max() else: x_min, x_max = xlim SLrect = np.vstack([ (x_min, 0.0), spd.T, (x_max, 0.0), ]) wavelength_height = y_max spdmax = 1 else: if (spdmax is None) & (spd is not None): spdmax = np.nanmax(spd[1:, :]) y_min, y_max = wavelength_height * spdmax, spdmax * ( 1 + np.abs(wavelength_height)) elif (spdmax is None) & (spd is None): spdmax = 1 y_min, y_max = wavelength_height, 0 elif (spdmax is not None): y_min, y_max = wavelength_height * spdmax, spdmax #*(1 + np.abs(wavelength_height)) if xlim is None: x_min, x_max = wavs.min(), wavs.max() else: x_min, x_max = xlim SLrect = np.vstack([ (x_min, 0.0), (x_min, wavelength_height * spdmax), (x_max, wavelength_height * spdmax), (x_max, 0.0), ]) axh.set_xlim([x_min, x_max]) axh.set_ylim([y_min, y_max]) polygon = Polygon(SLrect, facecolor=None, edgecolor=None) axh.add_patch(polygon) padding = 0.1 axh.bar(x=wavs - padding, height=wavelength_height * spdmax, width=1 + padding, color=srgb, align='edge', linewidth=0, clip_path=polygon) if spd is not None: axh.plot(spd[0:1, :].T, spd[1:, :].T, color='k', label='spd') if show_grid == True: plt.grid(True) axh.set_xlabel('Wavelength (nm)', kwargs) axh.set_ylabel(ylabel, kwargs) #plt.show() return axh else: return None
def plot_chromaticity_diagram_colors(diagram_samples = 256, diagram_opacity = 1.0, diagram_lightness = 0.25,\ cieobs = _CIEOBS, cspace = 'Yxy', cspace_pars = {},\ show = True, axh = None,\ show_grid = False, label_fontname = 'Times New Roman', label_fontsize = 12,\ **kwargs): """ Plot the chromaticity diagram colors. Args: :diagram_samples: | 256, optional | Sampling resolution of color space. :diagram_opacity: | 1.0, optional | Sets opacity of chromaticity diagram :diagram_lightness: | 0.25, optional | Sets lightness of chromaticity diagram :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, optional | Determines color space / chromaticity diagram to plot data in. | Note that data is expected to be in specified :cspace: :cspace_pars: | {} or dict, optional | Dict with parameters required by color space specified in :cspace: | (for use with luxpy.colortf()) :show_grid: | False, optional | Show grid (True) or not (False) :label_fontname: | 'Times New Roman', optional | Sets font type of axis labels. :label_fontsize: | 12, optional | Sets font size of axis labels. :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: """ if isinstance(cieobs, str): SL = _CMF[cieobs]['bar'][1:4].T else: SL = cieobs[1:4].T SL = 100.0 * SL / (SL[:, 1, None] + _EPS) SL = SL[SL.sum(axis=1) > 0, :] # avoid div by zero in xyz-to-Yxy conversion SL = colortf(SL, tf=cspace, tfa0=cspace_pars) plambdamax = SL[:, 1].argmax() SL = np.vstack( (SL[:(plambdamax + 1), :], SL[0]) ) # add lowest wavelength data and go to max of gamut in x (there is a reversal for some cmf set wavelengths >~700 nm!) Y, x, y = asplit(SL) SL = np.vstack((x, y)).T # create grid for conversion to srgb offset = _EPS min_x = min(offset, x.min()) max_x = max(1, x.max()) min_y = min(offset, y.min()) max_y = max(1, y.max()) ii, jj = np.meshgrid( np.linspace(min_x - offset, max_x + offset, int(diagram_samples)), np.linspace(max_y + offset, min_y - offset, int(diagram_samples))) ij = np.dstack((ii, jj)) ij[ij == 0] = offset ij2D = ij.reshape((diagram_samples**2, 2)) ij2D = np.hstack((diagram_lightness * 100 * np.ones( (ij2D.shape[0], 1)), ij2D)) xyz = colortf(ij2D, tf=cspace + '>xyz', tfa0=cspace_pars) xyz[xyz < 0] = 0 xyz[np.isinf(xyz.sum(axis=1)), :] = np.nan xyz[np.isnan(xyz.sum(axis=1)), :] = offset srgb = xyz_to_srgb(xyz) srgb = srgb / srgb.max() srgb = srgb.reshape((diagram_samples, diagram_samples, 3)) if show == True: if axh is None: fig = plt.figure() axh = fig.add_subplot(111) polygon = Polygon(SL, facecolor='none', edgecolor='none') axh.add_patch(polygon) image = axh.imshow(srgb, interpolation='bilinear', extent=(min_x, max_x, min_y - 0.05, max_y), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) axh.plot(x, y, color='darkgray') if (cspace == 'Yxy') & (isinstance(cieobs, str)): axh.set_xlim([0, 1]) axh.set_ylim([0, 1]) elif (cspace == 'Yuv') & (isinstance(cieobs, str)): axh.set_xlim([0, 0.6]) axh.set_ylim([0, 0.6]) if (cspace is not None): xlabel = _CSPACE_AXES[cspace][1] ylabel = _CSPACE_AXES[cspace][2] if (label_fontname is not None) & (label_fontsize is not None): axh.set_xlabel(xlabel, fontname=label_fontname, fontsize=label_fontsize) axh.set_ylabel(ylabel, fontname=label_fontname, fontsize=label_fontsize) if show_grid == True: axh.grid(True) #plt.show() return axh else: return None
def test_srgb(self, getvars): S, spds, rfls, xyz, xyzw = getvars lab = lx.xyz_to_srgb( xyz[1:, :]) # first xyz row out of srgb gamut, so non-reversible! xyz_ = lx.srgb_to_xyz(lab) assert np.isclose(xyz[1:, :], xyz_).all()
def plot_tm30_Rfi(spd, cri_type = 'ies-tm30', axh = None, font_size = _TM30_FONT_SIZE, **kwargs): """ Plot Sample Color Fidelity values (Rfi). Args: :spd: | ndarray or dict | If ndarray: single spectral power distribution. | If dict: dictionary with pre-computed parameters (using _tm30_process_spd()). | required keys: | 'Rf','Rg','cct','duv','Sr','cri_type','xyzri','xyzrw', | 'hbinnrs','Rfi','Rfhi','Rcshi','Rhshi', | 'jabt_binned','jabr_binned', | 'nhbins','start_hue','normalize_gamut','normalized_chroma_ref' | see cri.spd_to_cri() for more info on parameters. :cri_type: | _CRI_TYPE_DEFAULT or str or dict, optional | -'str: specifies dict with default cri model parameters | (for supported types, see luxpy.cri._CRI_DEFAULTS['cri_types']) | - dict: user defined model parameters | (see e.g. luxpy.cri._CRI_DEFAULTS['cierf'] | for required structure) | Note that any non-None input arguments (in kwargs) | to the function will override default values in cri_type dict. :axh: | None, optional | If None: create new figure with single axes, else plot on specified axes. :font_size: | _TM30_FONT_SIZE, optional | Font size of text, axis labels and axis values. :kwargs: | Additional optional keyword arguments, | the same as in cri.spd_to_cri() Returns: :axh: | handle to figure axes. :data: | dictionary with required parameters for plotting functions. """ data = _tm30_process_spd(spd, cri_type = 'ies-tm30',**kwargs) Rfi = data['Rfi'] # get rgb values representing each sample: N = data['xyzri'].shape[0] xyzw = spd_to_xyz(_CIE_D65, relative = True, cieobs = data['cri_type']['cieobs']['xyz']) xyzri = cat.apply(data['xyzri'][:,0,:],xyzw1 = data['xyzrw'], xyzw2 = xyzw) rgbri = xyz_to_srgb(xyzri)/255 # Create color map: cmap = [] for i in range(N): cmap.append(rgbri[i,...]) # Plot sample color fidelity, Rfi: if axh is None: fig, axh = plt.subplots(nrows = 1, ncols = 1) for j in range(N): axh.bar(j,Rfi[j,0], color = cmap[j], width = 1,edgecolor = None, alpha = 0.9) #axh.text(j,Rfi[j,0]*1.1, '{:1.0f}'.format(Rfi[j,0]) ,fontsize = 9,horizontalalignment='center',verticalalignment='center',color = np.array([1,1,1])*0.3) xticks = np.arange(0,N,step=2) xtickslabels = ['CES{:1.0f}'.format(ii+1) for ii in range(0,N,2)] axh.set_xticks(xticks) axh.set_xticklabels(xtickslabels, fontsize = font_size, rotation = 90) axh.set_ylabel(r'Color Sample Fidelity $(R_{f,CESi})$', fontsize = font_size) axh.set_ylim([0,100]) axh.set_xlim([-0.5,N-0.5]) return axh, data
def _get_hue_map(hbins = 16, start_hue = 0.0, hbinnrs = None, xyzri = None, xyzrw = None, cri_type = None): """ Generate color map for hue bins. Args: :hbins: | 16 or ndarray with sorted hue bin centers (°), optional :start_hue: | 0.0, optional :hbinnrs: | None, optional | ndarray with hue bin number of each sample. | If hbinnrs, xyzri, xyzrw and cri_type are all not-None: | use these to calculate color map, otherwise just use number of | hue bins :hbins: and :start_hue: :xyzri: | None, optional | relative xyz tristimulus values of samples under ref. illuminant. | see :hbinnrs: for more info when this is used. :xyzrw: | None, optional | relative xyz tristimulus values of ref. illuminant white point. | see :hbinnrs: for more info when this is used. :cri_type: | None, optional | Specifies dict with default cri model parameters | (needed to get correct :cieobs:) | see :hbinnrs: for more info when this is used. Returns: :cmap: | list with rgb values (one for each hue bin) for plotting. """ # Setup hbincenters and hsv_hues: if isinstance(hbins,float) | isinstance(hbins,int): nhbins = hbins dhbins = 360/(nhbins) # hue bin width hbincenters = np.arange(start_hue + dhbins/2, 360, dhbins) hbincenters = np.sort(hbincenters) else: hbincenters = hbins idx = np.argsort(hbincenters) hbincenters = hbincenters[idx] nhbins = hbincenters.shape[0] cmap = [] if (hbinnrs is not None) & (xyzri is not None) & (xyzrw is not None) & (cri_type is not None): xyzw = spd_to_xyz(_CIE_D65, relative = True, cieobs = cri_type['cieobs']['xyz']) xyzri = cat.apply(xyzri[:,0,:],xyzw1 = xyzrw, xyzw2 = xyzw) # Create color from xyz average: for i in range(nhbins): xyzrhi = xyzri[hbinnrs[:,0] == i,:].mean(axis=0,keepdims=True) rgbrhi = xyz_to_srgb(xyzrhi)/255 cmap.append(rgbrhi) else: # Create color from hue angle: # Setup color for plotting hue bins: hbincenters = hbincenters*np.pi/180 hsv_hues = hbincenters - 30*np.pi/180 hsv_hues = hsv_hues/hsv_hues.max() for i in range(nhbins): #c = np.abs(np.array(colorsys.hsv_to_rgb(hsv_hues[i], 0.75, 0.85))) c = np.abs(np.array(colorsys.hls_to_rgb(hsv_hues[i], 0.45, 0.5))) cmap.append(c) return cmap
def plot_chromaticity_diagram_colors(diagram_samples = 256, diagram_opacity = 1.0, diagram_lightness = 0.25,\ cieobs = _CIEOBS, cspace = 'Yxy', cspace_pars = {},\ show = True, axh = None,\ show_grid = True, label_fontname = 'Times New Roman', label_fontsize = 12,\ **kwargs): """ Plot the chromaticity diagram colors. Args: :diagram_samples: | 256, optional | Sampling resolution of color space. :diagram_opacity: | 1.0, optional | Sets opacity of chromaticity diagram :diagram_lightness: | 0.25, optional | Sets lightness of chromaticity diagram :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, optional | Determines color space / chromaticity diagram to plot data in. | Note that data is expected to be in specified :cspace: :cspace_pars: | {} or dict, optional | Dict with parameters required by color space specified in :cspace: | (for use with luxpy.colortf()) :show_grid: | True, optional | Show grid (True) or not (False) :label_fontname: | 'Times New Roman', optional | Sets font type of axis labels. :label_fontsize: | 12, optional | Sets font size of axis labels. :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: """ offset = _EPS ii, jj = np.meshgrid(np.linspace(offset, 1 + offset, diagram_samples), np.linspace(1+offset, offset, diagram_samples)) ij = np.dstack((ii, jj)) SL = _CMF[cieobs]['bar'][1:4].T SL = np.vstack((SL,SL[0])) SL = 100.0*SL/SL[:,1,None] SL = colortf(SL, tf = cspace, tfa0 = cspace_pars) Y,x,y = asplit(SL) SL = np.vstack((x,y)).T ij2D = ij.reshape((diagram_samples**2,2)) ij2D = np.hstack((diagram_lightness*100*np.ones((ij2D.shape[0],1)), ij2D)) xyz = colortf(ij2D, tf = cspace + '>xyz', tfa0 = cspace_pars) xyz[xyz < 0] = 0 xyz[np.isinf(xyz.sum(axis=1)),:] = np.nan xyz[np.isnan(xyz.sum(axis=1)),:] = offset srgb = xyz_to_srgb(xyz) srgb = srgb/srgb.max() srgb = srgb.reshape((diagram_samples,diagram_samples,3)) if show == True: if axh is None: fig = plt.figure() axh = fig.add_subplot(111) polygon = Polygon(SL, facecolor='none', edgecolor='none') axh.add_patch(polygon) image = axh.imshow( srgb, interpolation='bilinear', extent = (0.0, 1, -0.05, 1), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) plt.plot(x,y, color = 'darkgray') if cspace == 'Yxy': plt.xlim([0,1]) plt.ylim([0,1]) elif cspace == 'Yuv': plt.xlim([0,0.6]) plt.ylim([0,0.6]) if (cspace is not None): xlabel = _CSPACE_AXES[cspace][1] ylabel = _CSPACE_AXES[cspace][2] if (label_fontname is not None) & (label_fontsize is not None): plt.xlabel(xlabel, fontname = label_fontname, fontsize = label_fontsize) plt.ylabel(ylabel, fontname = label_fontname, fontsize = label_fontsize) if show_grid == True: plt.grid() #plt.show() return axh else: return None