Source code for mcmaps

"""Top-level package for mcmaps."""

__author__ = """Hamid Ali Syed"""
__email__ = "hamidsyed37@gmail.com"


import pandas as pd
import matplotlib as mpl
import importlib.resources

# versioning
try:
    from .version import version as __version__
except Exception:
    # Local copy or not installed with setuptools.
    # Disable minimum version checks on downstream libraries.
    __version__ = "999"


[docs] class ColormapContainer: """ A container for dynamically registering and accessing colormaps. Attributes ---------- All colormap names are dynamically registered as attributes of this class and are available globally in Matplotlib by their names. """ def __init__(self): self._colormaps = {} self._register_colormaps() def _normalize_data(self, data): """ Normalize RGB data to the range [0, 1]. Parameters ---------- data : numpy.ndarray The RGB data array. Returns ------- numpy.ndarray Normalized RGB data in the range [0, 1]. """ max_value = data.max() return data / max_value if max_value > 0 else data def _read_colormap_file(self, file): """ Read and parse the colormap file, skipping lines until the `# R,G,B` marker. Parameters ---------- file : Traversable The file to read. Returns ------- numpy.ndarray Parsed RGB data as a NumPy array. """ with file.open() as f: # Read lines until the marker is found for line in f: if line.strip() == "# R,G,B": break # Read the remaining lines as CSV data return pd.read_csv(f, header=None).to_numpy() def _register_colormaps(self): """ Dynamically register colormaps from the `data` directory and set them as attributes. Colormaps are registered with Matplotlib so they can be used globally by their names. """ try: # Dynamically resolve the data path using importlib.resources data_path = importlib.resources.files("mcmaps.data") try: files = list(data_path.iterdir()) # Safely list files in the directory except Exception as e: print(f"Warning: Unable to list files in `data` directory: {e}") return for file in files: if file.suffix in {".csv", ".txt"}: name = file.stem # Read and normalize colormap data data = self._read_colormap_file(file) normalized_data = self._normalize_data(data) # Create colormap and reversed version cmap = mpl.colors.ListedColormap(normalized_data, name=name) cmap_reversed = mpl.colors.ListedColormap( normalized_data[::-1], name=f"{name}_r" ) # Register with Matplotlib mpl.colormaps.register(cmap, name=name) mpl.colormaps.register(cmap_reversed, name=f"{name}_r") # Add colormap to container attributes self._colormaps[name] = cmap self._colormaps[f"{name}_r"] = cmap_reversed setattr(self, name, cmap) setattr(self, f"{name}_r", cmap_reversed) except ModuleNotFoundError as e: print(f"Error loading colormaps: {e}") except Exception as e: print(f"Unexpected error while registering colormaps: {e}")
[docs] def list_colormaps(self, include_reversed=False): """ List all available colormaps included in the package. Parameters ---------- include_reversed : bool, optional Whether to include reversed colormaps in the list, by default False. Returns ------- list of str A list of colormap names. """ if include_reversed: return list(self._colormaps.keys()) return [name for name in self._colormaps if not name.endswith("_r")]
# Create a global colormap container cm = ColormapContainer()
[docs] def save_all_colormaps_preview(output_dir="docs/colormaps", width=6, height=1): """ Save preview images of all registered colormaps to a directory. Parameters ---------- output_dir : str Directory where the preview images will be saved. width : float Width of the generated figure in inches. height : float Height of the generated figure in inches. """ import os import numpy as np import matplotlib.pyplot as plt os.makedirs(output_dir, exist_ok=True) gradient = np.linspace(0, 1, 256).reshape(1, -1) for name, cmap in cm._colormaps.items(): fig, ax = plt.subplots(figsize=(width, height)) ax.imshow(gradient, aspect="auto", cmap=cmap) ax.set_axis_off() plt.tight_layout() output_path = os.path.join(output_dir, f"{name}.png") plt.savefig(output_path, dpi=150) plt.close(fig)
def _get_cmap_gallery_html(cmaps: dict, sort_d: bool = False) -> str: """ Generate an HTML gallery string for a dictionary of colormaps. Parameters ---------- cmaps : dict A dictionary where keys are colormap names and values are matplotlib colormap objects. sort_d : bool, optional Whether to sort colormap names alphabetically, by default False. Returns ------- str A string containing HTML that visually displays all colormaps. """ import base64 def _cmap_div(cmap): """ Generate a single colormap preview div in base64-encoded PNG. """ png_bytes = cmap._repr_png_() png_base64 = base64.b64encode(png_bytes).decode("ascii") return ( f'<div class="cmap-block" style="margin-bottom: 16px;">' f"<div><strong>{cmap.name}</strong></div>" f'<img alt="{cmap.name}" title="{cmap.name}" ' f'style="border: 1px solid #aaa; display: block;" ' f'src="data:image/png;base64,{png_base64}"/>' f"</div>" ) cm_names = [name for name in cmaps if not name.endswith("_r")] if sort_d: cm_names.sort() html = [ "<html><head><style>", "body { font-family: sans-serif; padding: 20px; background-color: #f8f8f8; }", ".cmap-block { display: inline-block; margin-right: 16px; }", "</style></head><body><h1>Colormap Gallery</h1>", ] html.extend(_cmap_div(cmaps[name]) for name in cm_names) html.append("</body></html>") return "\n".join(html)