Source code for wolfhece.PyPalette

"""
Author: HECE - University of Liege, Pierre Archambeau
Date: 2024

Copyright (c) 2024 University of Liege. All rights reserved.

This script and its content are protected by copyright law. Unauthorized
copying or distribution of this file, via any medium, is strictly prohibited.
"""

from matplotlib.cm import ScalarMappable
from matplotlib.figure import Figure
from matplotlib.transforms import Bbox
import wx
import numpy as np
import numpy.ma as ma
from bisect import bisect_left
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, Normalize, ListedColormap, BoundaryNorm
from collections import OrderedDict
import typing
import io
import logging

from .PyTranslate import _
from .CpGrid import CpGrid
from .PyVertex import getRGBfromI, getIfromRGB
from .color_constants import Color, RGB, Colors


[docs] class wolfpalette(wx.Frame, LinearSegmentedColormap): """ Color palette based on the "LinearSegmentedColormap" object from Matplotlib (Colormap objects based on lookup tables using linear segments) """
[docs] filename: str
[docs] nb: int
[docs] colors: np.array
[docs] colorsflt: np.array
[docs] colorsuint8: np.array
def __init__(self, parent=None, title=_('Colormap'), w=100, h=500, nseg=1024): self.filename = '' self.nb = 0
[docs] self.values = None
[docs] self.colormin = np.array([1., 1., 1., 1.])
[docs] self.colormax = np.array([0, 0, 0, 1.])
[docs] self.nseg = nseg
[docs] self.automatic = True
[docs] self.interval_cst = False
self.colors = np.zeros((self.nb, 4), dtype=np.float64) self.values = np.zeros((self.nb), dtype=np.float64)
[docs] self.wx_exists = wx.App.Get() is not None
# Call to initialize a general frame if (self.wx_exists): wx.Frame.__init__(self, parent, title=title, size=(w, h), style=wx.DEFAULT_FRAME_STYLE) LinearSegmentedColormap.__init__(self, 'wolf', {}, nseg) self.set_bounds() def __getstate__(self): """ Retrieve the object's state for serialization """ state = self.__dict__.copy() return state def __setstate__(self, state): """ Retrieve the object's state for deserialization """ self.__dict__.update(state) # Reinitialize the LinearSegmentedColormap with the current state self.fill_segmentdata() @property
[docs] def colormin_uint8(self): return self.colormin.astype(np.uint8)*255
@property
[docs] def colormax_uint8(self): return self.colormax.astype(np.uint8)*255
[docs] def get_colors_f32(self): colors = self.colorsflt[:, :3].astype(np.float32) return colors
[docs] def get_colors_uint8(self): colors = self.colorsflt[:, :3].astype(np.uint8) * 255 return colors
[docs] def set_bounds(self): self.set_under(tuple(self.colormin)) self.set_over(tuple(self.colormax))
[docs] def get_rgba(self, x: np.ndarray): """ Retrieve the color based on the value x :param x: array of values """ dval = self.values[-1]-self.values[0] if dval == 0.: dval = 1. xloc = (x-self.values[0])/dval if self.interval_cst: rgba = np.ones((xloc.shape[0], xloc.shape[1], 4), dtype=np.uint8) ij = np.where(xloc < 0.) rgba[ij[0], ij[1]] = self.colormin_uint8 ij = np.where(xloc >= 1.) rgba[ij[0], ij[1]] = self.colormax_uint8 for i in range(self.nb-1): val1 = (self.values[i]-self.values[0])/dval val2 = (self.values[i+1]-self.values[0])/dval # c1 = self.colorsflt[i] c1 = self.colorsuint8[i] ij = np.where((xloc >= val1) & (xloc < val2)) rgba[ij[0], ij[1]] = c1 return rgba else: return self(xloc, bytes=True)
[docs] def get_rgba_oneval(self, x: float): """ Retrieve the color based on the value x """ dval = self.values[-1]-self.values[0] if dval == 0.: dval = 1. xloc = (x-self.values[0])/dval if self.interval_cst: rgba = np.ones((4), dtype=np.uint8) if xloc < 0.: rgba = self.colormin_uint8 elif xloc >= 1.: rgba = self.colormax_uint8 else: for i in range(self.nb-1): val1 = (self.values[i]-self.values[0])/dval val2 = (self.values[i+1]-self.values[0])/dval if (xloc >= val1) & (xloc < val2): c1 = self.colorsuint8[i] rgba = c1 break return rgba else: return self(xloc, bytes=True)
[docs] def export_palette_matplotlib(self, name): cmaps = OrderedDict() cmaps['Perceptually Uniform Sequential'] = ['viridis', 'plasma', 'inferno', 'magma', 'cividis'] cmaps['Sequential'] = ['Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds', 'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu', 'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn'] cmaps['Sequential (2)'] = ['binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink', 'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia', 'hot', 'afmhot', 'gist_heat', 'copper'] cmaps['Diverging'] = ['PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu', 'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic'] cmaps['Cyclic'] = ['twilight', 'twilight_shifted', 'hsv'] cmaps['Qualitative'] = ['Pastel1', 'Pastel2', 'Paired', 'Accent', 'Dark2', 'Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'tab20b', 'tab20c'] cmaps['Miscellaneous'] = ['flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern', 'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg', 'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'] for cmap_category, cmap_list in cmaps.items(): if (name in cmaps[cmap_category]): self = plt.get_cmap(name) self.nb = len(self._segmentdata['blue']) self.values = np.linspace(0., 1., self.nb, dtype=np.float64) self.colorsflt = np.zeros((self.nb, 4), dtype=np.float64) for i in range(self.nb): self.colorsflt[i, 0] = self._segmentdata['red'][i][2] self.colorsflt[i, 1] = self._segmentdata['green'][i][2] self.colorsflt[i, 2] = self._segmentdata['blue'][i][2] self.colorsflt[i, 3] = self._segmentdata['alpha'][i][2] test = 1 break else: test = 1 return self.nb, self.values, self._segmentdata, self.colorsflt
[docs] def distribute_values(self, minval: float = -99999, maxval: float = -99999, step=0, wx_permitted=True): """ Distribution of the palette values :param minval: minimum value :param maxval: maximum value :param step: distribution step If the step is provided, it takes precedence over the maximum value. """ if self.wx_exists and wx_permitted: if minval == -99999: dlg = wx.TextEntryDialog(None, _('Minimum value'), value=str(self.values[0])) ret = dlg.ShowModal() try: self.values[0] = float(dlg.GetValue()) except: logging.warning('Bad value for minimum - No change') dlg.Destroy() else: self.values[0] = minval if maxval == -99999 and step == 0: dlg = wx.MessageDialog(None, _('Would you like to set the incremental step value ?'), style=wx.YES_NO | wx.YES_DEFAULT) ret = dlg.ShowModal() dlg.Destroy if ret == wx.ID_YES: dlg = wx.TextEntryDialog(None, _('Step value'), value='1') ret = dlg.ShowModal() try: step = float(dlg.GetValue()) except: logging.warning('Bad value for step - using default value 0.1 m') step = 0.1 dlg.Destroy() else: dlg = wx.TextEntryDialog(None, _('Maximum value'), value=str(self.values[-1])) ret = dlg.ShowModal() try: self.values[-1] = float(dlg.GetValue()) except: logging.warning('Bad value for maximum - using min value + 1 m') self.values[-1] = self.values[0] + 1. dlg.Destroy() elif maxval != -99999: self.values[-1] = maxval else: if minval != -99999: self.values[0] = minval if maxval != -99999: self.values[-1] = maxval if step == 0: self.values = np.linspace(self.values[0], self.values[-1], num=self.nb, endpoint=True, dtype=np.float64)[0:self.nb] else: self.values = np.arange(self.values[0], self.values[0]+(self.nb)*step, step, dtype=np.float64)[0:self.nb] self.fill_segmentdata()
[docs] def get_ScalarMappable_mpl(self): """ Retrieve the ScalarMappable object via Matplotlib """ if self.interval_cst: discrete_cmap = ListedColormap(self.colorsflt[:, :3]) colorbar = ScalarMappable(BoundaryNorm(self.values, ncolors=self.nb-1), cmap=discrete_cmap) return colorbar else: return ScalarMappable(Normalize(self.values[0], self.values[-1]), cmap=self)
[docs] def export_image(self, fn='', h_or_v: typing.Literal['h', 'v', ''] = '', figax=None): """ Export image from colormap :param : fn : filepath or io.BytesIO() :param : h_or_v : configuration to save 'h' = horizontal, 'v' = vertical, '' = both """ if self.values is None: logging.warning('No values in palette - Nothing to do !') return None, None if len(self.values) == 0: logging.warning('No values in palette - Nothing to do !') logging.info(_('Do you have defined the palette values ?')) logging.info(_('If yes, please check your Global Options. You may not have defined the correct palette to use.')) return None, None if fn == '': file = wx.FileDialog(None, "Choose .pal file", wildcard="png (*.png)|*.png|all (*.*)|*.*", style=wx.FD_SAVE) if file.ShowModal() == wx.ID_CANCEL: return else: # Retrieve the filename with path fn = file.GetPath() if h_or_v == 'v': if figax is None: fig, ax = plt.subplots(1, 1) else: fig, ax = figax if self.interval_cst: discrete_cmap = ListedColormap(self.colorsflt[:, :3]) colorbar = plt.colorbar(ScalarMappable(BoundaryNorm(self.values, ncolors=self.nb-1), cmap=discrete_cmap), cax=ax, orientation='vertical', extend='both', aspect=20, spacing='proportional', ticks=self.values, format='%.3f') else: plt.colorbar(ScalarMappable(Normalize(self.values[0], self.values[-1]), cmap=self), cax=ax, orientation='vertical', extend='both', aspect=20, spacing='proportional', ticks=self.values, format='%.3f') plt.tick_params(labelsize=14) if figax is None: fig.set_size_inches((2, 10)) fig.tight_layout() if fn != '' and fn is not None: plt.savefig(fn, format='png') # plt.savefig(fn,bbox_inches='tight', format='png') elif h_or_v == 'h': if figax is None: fig, ax = plt.subplots(1, 1) else: fig, ax = figax if self.interval_cst: discrete_cmap = ListedColormap(self.colorsflt[:, :3]) colorbar = plt.colorbar(ScalarMappable(BoundaryNorm(self.values, ncolors=self.nb-1), cmap=discrete_cmap), cax=ax, orientation='horizontal', extend='both', aspect=20, spacing='proportional', ticks=self.values, format='%.3f') else: plt.colorbar(ScalarMappable(Normalize(self.values[0], self.values[-1]), cmap=self), cax=ax, orientation='horizontal', extend='both', spacing='proportional', ticks=self.values, format='%.3f') plt.tick_params(labelsize=14, rotation=45) if figax is None: fig.set_size_inches((2, 10)) fig.tight_layout() if fn != '' and fn is not None: plt.savefig(fn, format='png') # plt.savefig(fn,bbox_inches='tight', format='png') else: if isinstance(fn, io.BytesIO): logging.warning('Bad type for "fn" - Nothing to do !') return if figax is None: fig, ax = plt.subplots(1, 1) else: fig, ax = figax if self.interval_cst: discrete_cmap = ListedColormap(self.colorsflt[:, :3]) colorbar = plt.colorbar(ScalarMappable(BoundaryNorm(self.values, ncolors=self.nb-1), cmap=discrete_cmap), cax=ax, orientation='vertical', extend='both', aspect=20, spacing='proportional', ticks=self.values, format='%.3f') else: plt.colorbar(ScalarMappable(Normalize(self.values[0], self.values[-1]), cmap=self), cax=ax, orientation='vertical', extend='both', spacing='proportional', ticks=self.values, format='%.3f') plt.tick_params(labelsize=14) fig.set_size_inches((2, 10)) fig.tight_layout() if fn != '' and fn is not None: plt.savefig(fn[:-4]+'_v.png', format='png') if figax is None: fig, ax = plt.subplots(1, 1) else: fig, ax = figax if self.interval_cst: discrete_cmap = ListedColormap(self.colorsflt[:, :3]) colorbar = plt.colorbar(ScalarMappable(BoundaryNorm(self.values, ncolors=self.nb-1), cmap=discrete_cmap), cax=ax, orientation='horizontal', extend='both', aspect=20, spacing='proportional', ticks=self.values, format='%.3f') else: plt.colorbar(ScalarMappable(Normalize(self.values[0], self.values[-1]), cmap=self), cax=ax, orientation='horizontal', extend='both', spacing='proportional', ticks=self.values, format='%.3f') plt.tick_params(labelsize=14, rotation=45) fig.set_size_inches((10, 2)) fig.tight_layout() if fn != '' and fn is not None: plt.savefig(fn[:-4]+'_h.png', format='png') fig.set_visible(False) return fig, ax
[docs] def plot(self, fig: Figure, ax: plt.Axes): """ Display the color palette """ gradient = np.linspace(0, 1, 256) gradient = np.vstack((gradient, gradient)) pos = [] txt = [] dval = (self.values[-1]-self.values[0]) if dval == 0.: dval = 1. if self.interval_cst: discrete_cmap = ListedColormap(self.colorsflt[:, :3]) ax.imshow(gradient, aspect='auto', cmap=discrete_cmap) for idx, curval in enumerate(self.values): pos.append(idx/self.nb*256.) txt.append("{0:.3f}".format(curval)) else: ax.imshow(gradient, aspect='auto', cmap=self) for curval in self.values: pos.append((curval-self.values[0])/dval*256.) txt.append("{0:.3f}".format(curval)) ax.set_yticklabels([]) ax.set_xticks(pos) ax.set_xticklabels(txt, rotation=30, fontsize=6)
@property
[docs] def cmap(self): """ Retrieve the color palette """ if self.interval_cst: return self._cmap_discrete else: return self
@property
[docs] def _cmap_discrete(self): """ Retrieve the discrete color palette """ discrete_cmap = ListedColormap(self.colorsflt[:, :3]) return discrete_cmap
@property
[docs] def scalarmappable(self): """ Retrieve the ScalarMappable object """ return self.get_ScalarMappable_mpl()
@property
[docs] def vmin(self): """ Retrieve the minimum value """ return self.values[0]
@property
[docs] def vmax(self): """ Retrieve the maximum value """ return self.values[-1]
@property
[docs] def norm(self): """ Retrieve the normalization """ if self.interval_cst: return BoundaryNorm(self.values, ncolors=self.nb-1) else: return Normalize(self.values[0], self.values[-1])
[docs] def fillgrid(self, gridto: CpGrid): """ Fill a grid with the palette values """ gridto.SetColLabelValue(0, 'Value') gridto.SetColLabelValue(1, 'R') gridto.SetColLabelValue(2, 'G') gridto.SetColLabelValue(3, 'B') nb = gridto.GetNumberRows() if len(self.values)-nb > 0: gridto.AppendRows(len(self.values)-nb) k = 0 for curv, rgba in zip(self.values, self.colors): gridto.SetCellValue(k, 0, str(curv)) gridto.SetCellValue(k, 1, str(rgba[0])) gridto.SetCellValue(k, 2, str(rgba[1])) gridto.SetCellValue(k, 3, str(rgba[2])) k += 1 nb = gridto.GetNumberRows() while k < nb: gridto.SetCellValue(k, 0, '') gridto.SetCellValue(k, 1, '') gridto.SetCellValue(k, 2, '') gridto.SetCellValue(k, 3, '') k += 1
[docs] def updatefromgrid(self, gridfrom: CpGrid): """ Update the palette based on a grid """ nbl = gridfrom.GetNumberRows() for i in range(nbl): if gridfrom.GetCellValue(i, 0) == '': nbl = i-1 break if i < self.nb: self.nb = i self.values = self.values[0:i] self.colors = self.colors[0:i, :] else: self.nb = i oldvalues = self.values oldcolors = self.colors self.values = np.zeros(self.nb, dtype=np.float64) self.colors = np.zeros((self.nb, 4), dtype=int) self.values[0:len(oldvalues)] = oldvalues self.colors[0:len(oldcolors), :] = oldcolors update = False for k in range(self.nb): update = update or self.values[k] != float(gridfrom.GetCellValue(k, 0)) update = update or self.colors[k, 0] != float(gridfrom.GetCellValue(k, 1)) update = update or self.colors[k, 1] != float(gridfrom.GetCellValue(k, 2)) update = update or self.colors[k, 2] != float(gridfrom.GetCellValue(k, 3)) self.values[k] = float(gridfrom.GetCellValue(k, 0)) self.colors[k, 0] = int(gridfrom.GetCellValue(k, 1)) self.colors[k, 1] = int(gridfrom.GetCellValue(k, 2)) self.colors[k, 2] = int(gridfrom.GetCellValue(k, 3)) self.fill_segmentdata() return update
[docs] def updatefrompalette(self, srcpal): """ Update the palette based on another one We copy the values, we do not point to the object """ for k in range(len(srcpal.values)): self.values[k] = srcpal.values[k] self.fill_segmentdata()
[docs] def lookupcolor(self, x): if x < self.values[0]: return wx.Colour(self.colormin) if x > self.values[-1]: return wx.Colour(self.colormax) i = bisect_left(self.values, x) k = (x - self.values[i-1])/(self.values[i] - self.values[i-1]) r = int(k*(float(self.colors[i, 0]-self.colors[i-1, 0]))) + self.colors[i-1, 0] g = int(k*(float(self.colors[i, 1]-self.colors[i-1, 1]))) + self.colors[i-1, 1] b = int(k*(float(self.colors[i, 2]-self.colors[i-1, 2]))) + self.colors[i-1, 2] a = int(k*(float(self.colors[i, 3]-self.colors[i-1, 3]))) + self.colors[i-1, 3] y = wx.Colour(r, g, b, a) return y
[docs] def lookupcolorflt(self, x): if x < self.values[0]: return wx.Colour(self.colormin) if x > self.values[-1]: return wx.Colour(self.colormax) i = bisect_left(self.values, x) k = (x - self.values[i-1])/(self.values[i] - self.values[i-1]) r = k*(self.colorsflt[i, 0]-self.colorsflt[i-1, 0]) + self.colorsflt[i-1, 0] g = k*(self.colorsflt[i, 1]-self.colorsflt[i-1, 1]) + self.colorsflt[i-1, 1] b = k*(self.colorsflt[i, 2]-self.colorsflt[i-1, 2]) + self.colorsflt[i-1, 2] a = k*(self.colorsflt[i, 3]-self.colorsflt[i-1, 3]) + self.colorsflt[i-1, 3] y = [r, g, b, a] return y
[docs] def lookupcolorrgb(self, x): if x < self.values[0]: return wx.Colour(self.colormin) if x > self.values[-1]: return wx.Colour(self.colormax) i = bisect_left(self.values, x) k = (x - self.values[i-1])/(self.values[i] - self.values[i-1]) r = int(k*(float(self.colors[i, 0]-self.colors[i-1, 0]))) + self.colors[i-1, 0] g = int(k*(float(self.colors[i, 1]-self.colors[i-1, 1]))) + self.colors[i-1, 1] b = int(k*(float(self.colors[i, 2]-self.colors[i-1, 2]))) + self.colors[i-1, 2] a = int(k*(float(self.colors[i, 3]-self.colors[i-1, 3]))) + self.colors[i-1, 3] return r, g, b, a
[docs] def default16(self): """ Default 16 color palette in WOLF """ self.nb = 16 self.values = np.linspace(0., 1., 16, dtype=np.float64) self.colors = np.zeros((self.nb, 4), dtype=int) self.colorsflt = np.zeros((self.nb, 4), dtype=np.float64) self.colors[0, :] = [128, 255, 255, 255] self.colors[1, :] = [89, 172, 255, 255] self.colors[2, :] = [72, 72, 255, 255] self.colors[3, :] = [0, 0, 255, 255] self.colors[4, :] = [0, 128, 0, 255] self.colors[5, :] = [0, 221, 55, 255] self.colors[6, :] = [128, 255, 128, 255] self.colors[7, :] = [255, 255, 0, 255] self.colors[8, :] = [255, 128, 0, 255] self.colors[9, :] = [235, 174, 63, 255] self.colors[10, :] = [255, 0, 0, 255] self.colors[11, :] = [209, 71, 12, 255] self.colors[12, :] = [128, 0, 0, 255] self.colors[13, :] = [185, 0, 0, 255] self.colors[14, :] = [111, 111, 111, 255] self.colors[15, :] = [192, 192, 192, 255] self.fill_segmentdata()
[docs] def default_difference3(self): """ Default 3 color palette for differences in WOLF """ self.nb = 3 self.values = np.asarray([-10., 0., 10.], dtype=np.float64) self.colors = np.zeros((self.nb, 4), dtype=int) self.colorsflt = np.zeros((self.nb, 4), dtype=np.float64) self.colors[0, :] = [0, 0, 255, 255] # Blue self.colors[1, :] = [255, 255, 255, 255] # White self.colors[2, :] = [255, 0, 0, 255] # Red self.fill_segmentdata()
[docs] def set_values_colors(self, values: typing.Union[list[float], np.ndarray], colors: typing.Union[list[tuple[int]], np.ndarray, list[tuple[str]]] ): """ Update the values and colors of the palette :param values: list or array of values :param colors: list or array of colors (RGB or RGBA) """ assert len(values) == len(colors), "Length of values and colors must be the same" assert len(values) > 1, "At least 2 values are required" # Convert hexa colors to RGB if necessary if isinstance(colors, list): if any(isinstance(col, str) for col in colors) | any(isinstance(col, RGB) for col in colors): converted_colors = [] for col in colors: if isinstance(col, str): if not col.startswith('#'): rgb = Colors.named_colors.get(col.lower()) if not rgb: raise ValueError(f"Unknown color code: {col}") else: rgb = rgb.to_tuple() else: rgb = Colors.convert_hexa_to_rgb(col) elif isinstance(col, RGB): rgb = col.to_tuple() else: rgb = col converted_colors.append(rgb) colors = converted_colors self.nb = len(values) self.values = np.asarray(values, dtype=np.float64) self.colors = np.zeros((self.nb, 4), dtype=int) self.colorsflt = np.zeros((self.nb, 4), dtype=np.float64) if isinstance(colors, list): if len(colors[0]) == 3: for curcol in range(self.nb): self.colors[curcol, 0] = colors[curcol][0] self.colors[curcol, 1] = colors[curcol][1] self.colors[curcol, 2] = colors[curcol][2] self.colors[curcol, 3] = 255 elif len(colors[0]) == 4: for curcol in range(self.nb): self.colors[curcol, 0] = colors[curcol][0] self.colors[curcol, 1] = colors[curcol][1] self.colors[curcol, 2] = colors[curcol][2] self.colors[curcol, 3] = colors[curcol][3] elif isinstance(colors, np.ndarray): if colors.shape[1] == 3: for curcol in range(self.nb): self.colors[curcol, 0] = colors[curcol, 0] self.colors[curcol, 1] = colors[curcol, 1] self.colors[curcol, 2] = colors[curcol, 2] self.colors[curcol, 3] = 255 elif colors.shape[1] == 4: for curcol in range(self.nb): self.colors[curcol, 0] = colors[curcol, 0] self.colors[curcol, 1] = colors[curcol, 1] self.colors[curcol, 2] = colors[curcol, 2] self.colors[curcol, 3] = colors[curcol, 3] self.fill_segmentdata()
[docs] def set_discrete_values_colors(self, values: typing.Union[list[float], np.ndarray], colors: typing.Union[list[tuple[int]], np.ndarray, list[tuple[str]]] ): """ Update the values and colors of the palette in discrete mode :param values: list or array of values :param colors: list or array of colors (RGB or RGBA) """ self.set_values_colors(values, colors) self.interval_cst = True
[docs] def set_linear_values_colors(self, values: typing.Union[list[float], np.ndarray], colors: typing.Union[list[tuple[int]], np.ndarray, list[tuple[str]]] ): """ Update the values and colors of the palette in linear mode :param values: list or array of values :param colors: list or array of colors (RGB or RGBA) """ self.set_values_colors(values, colors) self.interval_cst = False
[docs] def set_values(self, values: typing.Union[list[float], np.ndarray]): """ Update the values of the palette """ assert len(values) == self.nb, "Length of values must match the number of colors" self.values = np.asarray(values, dtype=np.float64) self.fill_segmentdata()
[docs] def defaultgray(self): """ Default gray palette in WOLF """ self.nb = 3 self.values = np.asarray([0., 0.5, 1.], dtype=np.float64) self.colors = np.asarray([[0, 0, 0, 255], [128, 128, 128, 255], [255, 255, 255, 255]], dtype=np.int32) # self.nb = 11 # self.values = np.asarray([0., 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.], dtype=np.float64) # self.colors = np.asarray([[0, 0, 0, 255], # [22, 22, 22, 255], # [44, 44, 44, 255], # [66, 66, 66, 255], # [88, 88, 88, 255], # [110, 110, 110, 255], # [132, 132, 132, 255], # [154, 154, 154, 255], # [176, 176, 176, 255], # [198, 198, 198, 255], # [255, 255, 255, 255]], dtype=np.int32) self.fill_segmentdata()
[docs] def fill_segmentdata(self): """ Update the color palette """ self.colorsflt = self.colors.astype(float)/255. self.colorsuint8 = self.colors.astype(np.uint8) if self.nb > 1: dval = self.values[-1]-self.values[0] normval = np.ones([len(self.values)]) if dval > 0.: normval = (self.values-self.values[0])/dval normval[0] = 0. normval[-1] = 1. segmentdata = {"red": np.column_stack([normval, self.colorsflt[:, 0], self.colorsflt[:, 0]]), "green": np.column_stack([normval, self.colorsflt[:, 1], self.colorsflt[:, 1]]), "blue": np.column_stack([normval, self.colorsflt[:, 2], self.colorsflt[:, 2]]), "alpha": np.column_stack([normval, self.colorsflt[:, 3], self.colorsflt[:, 3]])} LinearSegmentedColormap.__init__(self, 'wolf', segmentdata, self.nseg)
[docs] def readfile(self, *args): """ Read the palette from a WOLF .pal file """ if len(args) > 0: # if there is an argument we take it as is self.filename = str(args[0]) else: if self.wx_exists: # open a dialog box file = wx.FileDialog(None, "Choose .pal file", wildcard="pal (*.pal)|*.pal|all (*.*)|*.*") if file.ShowModal() == wx.ID_CANCEL: file.Destroy() return else: # retrieve the filename with path self.filename = file.GetPath() file.Destroy() else: return # read the content with open(self.filename, 'r') as myfile: # split the lines --> retrieve the info without '\n' at the end of the line # different from .readlines() which does not remove '\n' mypallines = myfile.read().splitlines() myfile.close() self.nb = int(mypallines[0]) self.values = np.zeros(self.nb, dtype=np.float64) self.colors = np.zeros((self.nb, 4), dtype=int) for i in range(self.nb): self.values[i] = mypallines[i*4+1] self.colors[i, 0] = mypallines[i*4+2] self.colors[i, 1] = mypallines[i*4+3] self.colors[i, 2] = mypallines[i*4+4] self.colors[i, 3] = 255 self.fill_segmentdata()
[docs] def is_valid(self): """ Check the validity of the palette """ if self.nb < 2: return False if self.values[0] >= self.values[-1]: return False for i in range(1, self.nb): if self.values[i] <= self.values[i-1]: return False return True
[docs] def is_discrete(self): """Vérification si la palette est en mode discret""" return self.interval_cst
[docs] def set_discrete(self, is_discrete: bool = True): """Définition du mode discret de la palette""" self.interval_cst = is_discrete
[docs] def savefile(self, *args): """Lecture de la palette sur base d'un fichier WOLF .pal""" if len(args) > 0: # s'il y a un argument on le prend tel quel fn = str(args[0]) else: # ouverture d'une boîte de dialogue file = wx.FileDialog(None, "Choose .pal file", wildcard="pal (*.pal)|*.pal|all (*.*)|*.*", style=wx.FD_SAVE) if file.ShowModal() == wx.ID_CANCEL: return else: # récuparétaion du nom de fichier avec chemin d'accès fn = file.GetPath() self.filename = fn # lecture du contenu with open(self.filename, 'w') as myfile: # split des lignes --> récupération des infos sans '\n' en fin de ligne # différent de .readlines() qui lui ne supprime pas les '\n' myfile.write(str(self.nb)+'\n') for i in range(self.nb): myfile.write(str(self.values[i])+'\n') myfile.write(str(self.colors[i, 0])+'\n') myfile.write(str(self.colors[i, 1])+'\n') myfile.write(str(self.colors[i, 2])+'\n')
[docs] def isopop(self, array: ma.masked_array, nbnotnull: int = 99999): """Remplissage des valeurs de palette sur base d'une équirépartition de valeurs""" sortarray = array.flatten(order='F') idx_nan = np.where(np.isnan(sortarray)) if idx_nan[0].size > 0: sortarray = np.delete(sortarray, idx_nan) nbnotnull -= idx_nan[0].size logging.warning('NaN values found in array - removed from palette') sortarray.sort(axis=-1) # valeurs min et max if nbnotnull == 0: self.values[0] = 0 self.values[1:] = 1 else: nbnotnull = min(nbnotnull, sortarray.shape[0]) self.values[0] = sortarray[0] if (nbnotnull == 99999): self.values[-1] = sortarray[-1] nb = sortarray.count() else: self.values[-1] = sortarray[nbnotnull-1] nb = nbnotnull interv = int(nb / (self.nb-1)) if interv == 0: self.values[:] = self.values[-1] self.values[0] = self.values[-1]-1. else: for cur in range(1, self.nb-1): self.values[cur] = sortarray[cur * interv] self.fill_segmentdata()
[docs] def defaultgray_minmax(self, array: ma.masked_array, nbnotnull=99999): """Remplissage des valeurs de palette sur base d'une équirépartition de valeurs""" self.nb = 2 self.values = np.asarray([np.min(array), np.max(array)], dtype=np.float64) self.colors = np.asarray([[0, 0, 0, 255], [255, 255, 255, 255]], dtype=np.int32) self.colorsflt = np.asarray([[0., 0., 0., 1.], [1., 1., 1., 1.]], dtype=np.float64) self.fill_segmentdata()
[docs] def defaultblue_minmax(self, array: ma.masked_array, nbnotnull=99999): """Remplissage des valeurs de palette sur base d'une équirépartition de valeurs""" self.nb = 2 self.values = np.asarray([np.min(array), np.max(array)], dtype=np.float64) self.colors = np.asarray([[255, 255, 255, 255], [0, 0, 255, 255]], dtype=np.int32) self.colorsflt = self.colors.astype(float)/255. # self.colorsflt = np.asarray([[0., 0., 0., 1.], [1., 1., 1., 1.]], dtype=np.float64) self.fill_segmentdata()
[docs] def defaultred_minmax(self, array: ma.masked_array, nbnotnull=99999): """Remplissage des valeurs de palette sur base d'une équirépartition de valeurs""" self.nb = 2 self.values = np.asarray([np.min(array), np.max(array)], dtype=np.float64) self.colors = np.asarray([[255, 255, 255, 255], [255, 0, 0, 255]], dtype=np.int32) self.colorsflt = self.colors.astype(float)/255. # self.colorsflt = np.asarray([[0., 0., 0., 1.], [1., 1., 1., 1.]], dtype=np.float64) self.fill_segmentdata()
[docs] def defaultblue(self): """Remplissage des valeurs de palette sur base d'une équirépartition de valeurs""" self.nb = 2 self.values = np.asarray([0., 1.], dtype=np.float64) self.colors = np.asarray([[255, 255, 255, 255], [0, 0, 255, 255]], dtype=np.int32) self.colorsflt = self.colors.astype(float)/255. # self.colorsflt = np.asarray([[0., 0., 0., 1.], [1., 1., 1., 1.]], dtype=np.float64) self.fill_segmentdata()
[docs] def defaultblue3(self): """Remplissage des valeurs de palette sur base d'une équirépartition de valeurs""" self.nb = 3 self.values = np.asarray([0., 1., 10.], dtype=np.float64) self.colors = np.asarray([[255, 255, 255, 255], [50, 130, 246, 255], [0, 0, 255, 255]], dtype=np.int32) self.colorsflt = self.colors.astype(float)/255. self.fill_segmentdata()