Scientific colormaps for making accessible, informative and 'cmashing' plots
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Register custom colormaps and import colormap data from various formats. These functions enable extending CMasher with custom colormaps and creating portable colormap modules.
Register new colormaps in both CMasher and matplotlib for use throughout the visualization ecosystem.
def register_cmap(name: str, data: list) -> None:
"""
Creates and registers a custom colormap in CMasher and matplotlib.
Parameters:
- name: Name for the colormap (will be prefixed with 'cmr.' in matplotlib)
- data: RGB color data as 2D array-like (N, 3) or 1D array of hex strings
Float values (normalized), int values (8-bit), or hex strings
Returns:
None
Notes:
Creates both normal and reversed versions. Colormap accessible as
cmasher.cm.{name} and matplotlib 'cmr.{name}'.
"""import cmasher as cmr
import numpy as np
import matplotlib.pyplot as plt
# Register colormap from normalized RGB values
rgb_data = [
[0.0, 0.0, 0.5], # Dark blue
[0.0, 0.5, 1.0], # Light blue
[1.0, 1.0, 0.5], # Light yellow
[1.0, 0.5, 0.0] # Orange
]
cmr.register_cmap('custom_gradient', rgb_data)
# Register colormap from 8-bit RGB values
rgb_8bit = [
[0, 0, 128], # Dark blue
[0, 128, 255], # Light blue
[255, 255, 128], # Light yellow
[255, 128, 0] # Orange
]
cmr.register_cmap('custom_8bit', rgb_8bit)
# Register colormap from hex strings
hex_colors = ['#000080', '#0080FF', '#FFFF80', '#FF8000']
cmr.register_cmap('custom_hex', hex_colors)
# Use registered colormap
data = np.random.rand(10, 10)
plt.imshow(data, cmap='cmr.custom_gradient')
plt.colorbar()
plt.title('Custom Registered Colormap')
plt.show()
# Access from CMasher module
import cmasher.cm as cmrcm
custom_cmap = cmrcm.custom_gradientImport colormaps from various file formats including NumPy arrays, text files, and viscm source files.
def import_cmaps(
cmap_path: str,
*,
_skip_registration: bool = False
) -> None:
"""
Import custom colormaps from files or directories.
Parameters:
- cmap_path: Path to colormap file or directory containing colormap files
- _skip_registration: Skip automatic registration (for testing)
Returns:
None
Notes:
Supports .npy (NumPy binary), .txt (text), .jscm (viscm) formats.
Files must have 'cm_' prefix. Cyclic colormaps get shifted versions.
Raises:
FileNotFoundError: If path doesn't exist
OSError: If file doesn't have 'cm_' prefix
ValueError: If viscm package missing for .jscm files
"""import cmasher as cmr
import numpy as np
import os
# Create example colormap data file
colormap_data = np.array([
[0.0, 0.0, 0.0], # Black
[0.5, 0.0, 0.5], # Purple
[1.0, 0.5, 0.0], # Orange
[1.0, 1.0, 1.0] # White
])
# Save as NumPy binary file
np.save('cm_example.npy', colormap_data)
# Import single colormap file
cmr.import_cmaps('cm_example.npy')
# Now colormap is available
import matplotlib.pyplot as plt
data = np.random.rand(8, 8)
plt.imshow(data, cmap='cmr.example')
plt.show()
# Import from directory
os.makedirs('my_colormaps', exist_ok=True)
np.save('my_colormaps/cm_gradient1.npy', colormap_data)
np.save('my_colormaps/cm_gradient2.npy', colormap_data[::-1]) # Reversed
cmr.import_cmaps('my_colormaps/')
# Import from text file
with open('cm_text_example.txt', 'w') as f:
for rgb in colormap_data:
f.write(f"{rgb[0]:.6f} {rgb[1]:.6f} {rgb[2]:.6f}\n")
cmr.import_cmaps('cm_text_example.txt')
# Clean up example files
os.remove('cm_example.npy')
os.remove('cm_text_example.txt')Create portable Python modules containing individual colormaps for sharing without CMasher dependency.
def create_cmap_mod(
cmap: str,
*,
save_dir: str = ".",
_copy_name: str | None = None
) -> str:
"""
Creates a standalone Python module for a CMasher colormap.
Parameters:
- cmap: Name of CMasher colormap (with or without 'cmr.' prefix)
- save_dir: Directory to save the module file
- _copy_name: Alternative name for the module (internal use)
Returns:
str: Path to the created Python module file
Notes:
Creates a .py file that registers the colormap in matplotlib when
imported. Includes reversed version and shifted version for cyclic
colormaps.
Raises:
ValueError: If colormap name is not valid CMasher colormap
"""import cmasher as cmr
import os
# Create standalone module for rainforest colormap
module_path = cmr.create_cmap_mod('rainforest')
print(f"Created module: {module_path}")
# Create module in specific directory
os.makedirs('colormap_modules', exist_ok=True)
ocean_path = cmr.create_cmap_mod('ocean', save_dir='colormap_modules')
# Create modules for multiple colormaps
colormaps_to_export = ['iceburn', 'wildfire', 'seasons']
for cmap_name in colormaps_to_export:
path = cmr.create_cmap_mod(cmap_name, save_dir='colormap_modules')
print(f"Exported {cmap_name} to {path}")
# Use standalone module (in a new Python session)
# import rainforest # This would register the colormap
# plt.imshow(data, cmap='cmr.rainforest')import cmasher as cmr
import numpy as np
import os
def create_colormap_collection(output_dir='my_colormaps'):
"""Create a collection of custom colormaps."""
os.makedirs(output_dir, exist_ok=True)
# Create various colormap types
colormaps = {
'cm_fire': np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [1, 1, 1]]),
'cm_ice': np.array([[0, 0, 0.5], [0, 0.5, 1], [0.8, 0.9, 1], [1, 1, 1]]),
'cm_earth': np.array([[0.2, 0.1, 0], [0.5, 0.3, 0.1], [0.3, 0.6, 0.2], [0.8, 0.8, 0.6]])
}
for name, data in colormaps.items():
np.save(f"{output_dir}/{name}.npy", data)
# Import entire collection
cmr.import_cmaps(output_dir)
print(f"Imported {len(colormaps)} custom colormaps")
create_colormap_collection()import cmasher as cmr
import numpy as np
def convert_colormap_formats(source_file, output_formats=['npy', 'txt', 'hex']):
"""Convert colormap between different file formats."""
# Load colormap data
if source_file.endswith('.npy'):
data = np.load(source_file)
else:
data = np.loadtxt(source_file)
base_name = source_file.replace('.npy', '').replace('.txt', '')
# Save in different formats
if 'npy' in output_formats:
np.save(f'{base_name}.npy', data)
if 'txt' in output_formats:
np.savetxt(f'{base_name}_norm.txt', data, fmt='%.6f')
if 'hex' in output_formats:
hex_colors = []
for rgb in data:
r, g, b = (rgb * 255).astype(int)
hex_colors.append(f'#{r:02X}{g:02X}{b:02X}')
with open(f'{base_name}_hex.txt', 'w') as f:
for hex_color in hex_colors:
f.write(hex_color + '\n')
# Example usage
data = np.random.rand(10, 3) # Random colormap
np.save('cm_random.npy', data)
convert_colormap_formats('cm_random.npy')import cmasher as cmr
import numpy as np
import matplotlib.pyplot as plt
def validate_custom_colormap(data, name='test_cmap'):
"""Validate custom colormap data before registration."""
try:
# Convert to numpy array
data_array = np.array(data)
# Check dimensions
if data_array.ndim != 2 or data_array.shape[1] != 3:
print(f"Error: Data must be (N, 3) shape, got {data_array.shape}")
return False
# Check value ranges for normalized RGB
if np.any(data_array < 0) or np.any(data_array > 1):
print("Warning: Values outside [0, 1] range, assuming 8-bit RGB")
if np.any(data_array > 255):
print("Error: Values too large for 8-bit RGB")
return False
# Test registration
cmr.register_cmap(name, data)
# Test visualization
test_data = np.random.rand(5, 5)
plt.figure(figsize=(8, 3))
plt.subplot(1, 2, 1)
plt.imshow(test_data, cmap=f'cmr.{name}')
plt.title(f'Custom colormap: {name}')
plt.colorbar()
plt.subplot(1, 2, 2)
plt.imshow(test_data, cmap=f'cmr.{name}_r')
plt.title(f'Reversed: {name}_r')
plt.colorbar()
plt.tight_layout()
plt.show()
print(f"✓ Colormap '{name}' validated successfully")
return True
except Exception as e:
print(f"✗ Validation failed: {e}")
return False
# Test with example data
test_data = [[0, 0, 0.5], [0.5, 0, 1], [1, 0.5, 0.5], [1, 1, 1]]
validate_custom_colormap(test_data, 'validation_test')import os
import cmasher as cmr
# CMasher supports CMR_CMAP_PKGS environment variable
# for specifying additional colormap packages to import
# Set environment variable to include additional packages
os.environ['CMR_CMAP_PKGS'] = 'colorcet:cmocean'
# This would be used by CLI tools to import additional colormaps
# The packages are separated by ':' on Unix or ';' on WindowsInstall with Tessl CLI
npx tessl i tessl/pypi-cmasher