Source code for porescene.color.palette._palette

# SPDX-License-Identifier: GPL-3.0-only
# Copyright (C) 2026 Felix Faber /
# Otto von Guericke University Magdeburg, Thermal Process Engineering

import random
import re
from enum import Enum
from importlib import resources
from typing import Self

import numpy as np

import porescene.utility as util
from porescene.color import Color


[docs] class Colormap(Enum): """ All colormaps bundled with :mod:`porescene`. Each member's value is the file name (without extension) of the corresponding colormap in ``porescene/data/colormap`` and can be passed to :meth:`Palette.load`. The collection comprises - `Fabio Crameri's scientific colour maps <https://www.fabiocrameri.ch/colourmaps/>`_, - `cmocean <https://matplotlib.org/cmocean/>`_, - `colorcet <https://colorcet.holoviz.org/>`_, - `ColorBrewer <https://colorbrewer2.org/>`_, and - a selection of :mod:`matplotlib` colormaps. See ``porescene/data/colormap/README.txt`` for the per-collection provenance and licenses. """ ACTON = "acton" ACTONS = "actonS" BAM = "bam" BAMO = "bamO" BAMAKO = "bamako" BAMAKOS = "bamakoS" BATLOW = "batlow" BATLOWK = "batlowK" BATLOWKS = "batlowKS" BATLOWS = "batlowS" BATLOWW = "batlowW" BATLOWWS = "batlowWS" BERLIN = "berlin" BILBAO = "bilbao" BILBAOS = "bilbaoS" BROC = "broc" BROCO = "brocO" BUDA = "buda" BUDAS = "budaS" BUKAVU = "bukavu" CIVIDIS = "cividis" CORK = "cork" CORKO = "corkO" CUBEHELIX = "cubehelix" DAVOS = "davos" DAVOSS = "davosS" DEVON = "devon" DEVONS = "devonS" EMBER = "ember" FES = "fes" GLASGOW = "glasgow" GLASGOWS = "glasgowS" GRAYC = "grayC" GRAYCS = "grayCS" HAWAII = "hawaii" HAWAIIS = "hawaiiS" IMOLA = "imola" IMOLAS = "imolaS" INFERNO = "inferno" LAJOLLA = "lajolla" LAJOLLAS = "lajollaS" LAPAZ = "lapaz" LAPAZS = "lapazS" LIPARI = "lipari" LIPARIS = "lipariS" LISBON = "lisbon" MAGMA = "magma" MANAGUA = "managua" NAVIA = "navia" NAVIAS = "naviaS" NUUK = "nuuk" NUUKS = "nuukS" OLERON = "oleron" OSLO = "oslo" OSLOS = "osloS" PLASMA = "plasma" ROMA = "roma" ROMAO = "romaO" SET1 = "Set1" SET2 = "Set2" SET3 = "Set3" TAB10 = "tab10" TOFINO = "tofino" TOKYO = "tokyo" TOKYOS = "tokyoS" TURBO = "turbo" TURKU = "turku" TURKUS = "turkuS" TWILIGHT = "twilight" VANIMO = "vanimo" VIK = "vik" VIKO = "vikO" VIRIDIS = "viridis" # cmocean maps (Kristen M. Thyng) -- https://matplotlib.org/cmocean/ ALGAE = "algae" AMP = "amp" BALANCE = "balance" CURL = "curl" DEEP = "deep" DELTA = "delta" DENSE = "dense" DIFF = "diff" GRAY = "gray" HALINE = "haline" ICE = "ice" MATTER = "matter" OXY = "oxy" PHASE = "phase" RAIN = "rain" SOLAR = "solar" SPEED = "speed" TARN = "tarn" TEMPO = "tempo" THERMAL = "thermal" TOPO = "topo" TURBID = "turbid" # Additional ColorBrewer maps (Cynthia A. Brewer) -- see Set1/2/3 above ACCENT = "Accent" BLUES = "Blues" BRBG = "BrBG" BUGN = "BuGn" BUPU = "BuPu" DARK2 = "Dark2" GNBU = "GnBu" GREENS = "Greens" GREYS = "Greys" ORANGES = "Oranges" ORRD = "OrRd" PAIRED = "Paired" PASTEL1 = "Pastel1" PASTEL2 = "Pastel2" PIYG = "PiYG" PRGN = "PRGn" PUBU = "PuBu" PUBUGN = "PuBuGn" PUOR = "PuOr" PURD = "PuRd" PURPLES = "Purples" RDBU = "RdBu" RDGY = "RdGy" RDPU = "RdPu" RDYLBU = "RdYlBu" RDYLGN = "RdYlGn" REDS = "Reds" SPECTRAL = "Spectral" YLGN = "YlGn" YLGNBU = "YlGnBu" YLORBR = "YlOrBr" YLORRD = "YlOrRd" # colorcet perceptually uniform maps (Peter Kovesi) -- https://colorcet.holoviz.org/ BGY = "bgy" BGYW = "bgyw" BJY = "bjy" BKR = "bkr" BKY = "bky" BMW = "bmw" BMY = "bmy" BWY = "bwy" COLORWHEEL = "colorwheel" COOLWARM = "coolwarm" CWR = "cwr" DIMGRAY = "dimgray" FIRE = "fire" GOULDIAN = "gouldian" GWV = "gwv" ISOLUM = "isolum" KB = "kb" KBC = "kbc" KBGYW = "kbgyw" KG = "kg" KGY = "kgy" KR = "kr" RAINBOW = "rainbow" RAINBOW4 = "rainbow4"
[docs] class Palette: """ A basic wrapper for a collection of selected colors. """ def __init__(self, colors: list[Color]) -> None: self.colors = colors def __iter__(self) -> Self: self.__i = 0 return self def __len__(self) -> int: return len(self.colors) def __next__(self) -> Color: if self.__i < len(self.colors) and self.__i >= 0: color = self.colors[self.__i] self.__i += 1 return color else: raise StopIteration
[docs] @classmethod def load(cls, name: str | Colormap) -> Self: """ Creates a :class:`Palette` instance by loading colormap data from file. :mod:`porescene` includes a large set of scientific colormaps, including: - `Fabio Crameri's collection <https://www.fabiocrameri.ch/colourmaps/>`_ - :mod:`matplotlib` colormaps Parameters ---------- name : str | Colormap Colormap to be loaded from file, either as a :class:`Colormap` member or the corresponding colormap name. """ colorlist: list[Color] = [] if isinstance(name, Colormap): name = name.value pattern = re.compile("^[a-zA-Z0-9]+$") if not pattern.match(name): raise Exception("Specified colormap is not available") ref = resources.files("porescene").joinpath("data/colormap/" + name + ".txt") with resources.as_file(ref) as pth: colors = np.fromfile(pth, sep=" ").reshape((-1, 3)) for color in colors: colorlist.append(Color.from_nrgb(*color)) ins = cls(colorlist) return ins
[docs] def all(self) -> list[Color]: """ Returns the full color palette. """ return self.colors
[docs] def first(self) -> Color: """ Returns the first color of the palette. """ return self.colors[0]
[docs] def last(self) -> Color: """ Returns the last color of the palette. """ return self.colors[-1]
[docs] def subset(self, n: int = 5) -> list[Color]: """ Returns a subset of ``n`` equidistant sampled colors from the palette. Parameters ---------- n : int, optional Number of colors in the subset, by default 5 """ return util.n_equidistant(self.colors, n)
[docs] def random(self, n: int = 1) -> list[Color]: """ Returns randomly selected colors from the palette. Parameters ---------- n : int, optional Number of colors to return, by default 1 """ colors = [] for k in range(n): colors.append(random.choice(self.colors)) return colors
[docs] def reversed(self) -> list[Color]: """ Returns the colors from the palette in reversed order. """ return list(reversed(self.colors))