import colorsys
import os
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.colors import LinearSegmentedColormap, ListedColormap
# Documentation
# - https://github.com/shaharkadmiel/cmaptools/blob/master/cmaptools/__init__.py
# - https://www.pygmt.org/dev/api/generated/pygmt.makecpt.html
# - https://docs.generic-mapping-tools.org/6.2/cookbook/cpts.html
[docs]
def load_cpt(filepath):
"""
Load a color palette table (CPT) from a file and convert it to a color dictionary.
CPT files are used by the Generic Mapping Tools software and Matlab.
Parameters
----------
filepath : str
The file path to the CPT file.
Returns
-------
colorDict : dict
A dictionary with normalized 'red', 'green', and 'blue' values.
Each key holds a list of [x_norm, color_value, color_value] for interpolation.
Notes
-----
The function supports both RGB and HSV color models. If HSV is used,
it converts HSV to RGB. RGB values are normalized between 0 and 1.
"""
try:
with open(filepath) as f:
lines = [line.strip() for line in f if not line.startswith("#")] # Ignore comment lines
except FileNotFoundError:
print(f"File {filepath} not found")
return None
color_model = "RGB"
x, r, g, b = [], [], [], []
for line in lines:
if line.startswith(("B", "F", "N")): # Skip control point lines
# B # COLOR_BACKGROUND
# F # COLOR_FOREGROUND
# N # COLOR_NAN
continue
values = list(map(float, line.split()))
# First color stop
x.append(values[0])
r.append(values[1])
g.append(values[2])
b.append(values[3])
# Second color stop
x.append(values[4])
r.append(values[5])
g.append(values[6])
b.append(values[7])
# Convert HSV to RGB [0-1]
if color_model == "HSV":
r, g, b = zip(
*[colorsys.hsv_to_rgb(rv / 360.0, gv, bv) for rv, gv, bv in zip(r, g, b, strict=False)],
strict=False,
)
# Convert RGB [0-255] to RGB [0-1]
if color_model == "RGB":
r, g, b = np.array(r) / 255.0, np.array(g) / 255.0, np.array(b) / 255.0
# Normalize x values to [0, 1]
x = np.array(x)
x_norm = (x - x[0]) / (x[-1] - x[0])
# Build the color dictionary
color_dict = {
"red": [[x_norm[i], r[i], r[i]] for i in range(len(x))],
"green": [[x_norm[i], g[i], g[i]] for i in range(len(x))],
"blue": [[x_norm[i], b[i], b[i]] for i in range(len(x))],
}
return color_dict
[docs]
def get_cmap_from_cpt(filepath):
"""
Create a matplotlib.colors.Colormap from a color palette table (CPT) file.
Parameters
----------
filepath : str
The file path to the CPT file.
"""
name = os.path.basename(filepath).rsplit(".", maxsplit=1)[0]
segmentdata = load_cpt(filepath)
cmap = LinearSegmentedColormap(name=name, segmentdata=segmentdata)
return cmap
[docs]
def rgb_to_hex(rgb):
"""Convert RGB to hex."""
return f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
[docs]
def plot_colors(colors, name=""):
"""Plot a set of colors."""
from pycolorbar.univariate import plot_colormap
cmap = ListedColormap(colors=colors, name=name)
plot_colormap(cmap)
plt.show()
[docs]
def get_colors_from_colorbar(
image_path,
N=10,
skip_first=0,
skip_last=None,
orientation="vertical",
plot_checker=False,
plot_cmap=True,
):
"""Utility function to infer N equally spaced colors from the middle of a colorbar image.
This routine is useful to attempt reproducing colormaps appearing in scientific visualizations.
"""
from PIL import Image
# Open the image
img = Image.open(image_path)
img = img.convert("RGB") # Ensure the image is in RGB format
# Convert the image to a NumPy array
img_array = np.array(img)
# Remove first/last pixels if asked
if orientation == "vertical":
img_array = img_array[slice(skip_first, skip_last), :, :]
else:
img_array = img_array[:, slice(skip_first, skip_last), :]
# Get image dimensions
height, width, _ = img_array.shape
# Extract color line (based on orientation) and indices to sample
if orientation == "vertical":
middle_column = width // 2
coords = np.linspace(0, height - 1, N).astype(int)
img_line = img_array[:, middle_column]
elif orientation == "horizontal":
middle_row = height // 2
coords = np.linspace(0, width - 1, N).astype(int)
img_line = img_array[middle_row, :]
else:
raise ValueError("Invalid orientation. Choose 'vertical' or 'horizontal'.")
if plot_checker:
plot_colors(img_line[0:5] / 255, name="First five")
plot_colors(img_line[-5:] / 255, name="Last five")
plot_colors(img_line / 255, name="Full colors")
# Convert RGB colors to HEX
hex_colors_img = np.array([rgb_to_hex(color) for color in img_line])
# Extract sample colors
indices = np.unique(coords.astype(int))
hex_colors_sampled = hex_colors_img[indices]
if plot_cmap:
plot_colors(hex_colors_sampled, name=f"{N}")
# Get position from where sampled
# coords_01 = np.linspace(0, 1, len(hex_colors_img))
# is_sampled = np.isin(hex_colors_img, hex_colors_sampled)
# idx_position = coords_01[np.isin(hex_colors_img, hex_colors_sampled)]
return hex_colors_sampled.tolist()