diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3b3429c..8c530bf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,3 +1,3 @@
-This repo welcomes additions from anyone passionate about colourmaps! This is my first Python package so it's a little rough around the edges.
+This repo welcomes additions from anyone passionate about colormaps! This is my first Python package so it's a little rough around the edges.
If you do make a PR, please check it against the tests first, this will make it easier for me to merge it and push a new release out to PyPI and conda-forge.
diff --git a/LICENSE b/LICENSE
index 0729b87..fc6cda0 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Colourmaps in cmcrameri/cm/cmaps:
+Colormaps in cmcrameri/cm/cmaps:
Copyright (c) 2020 Fabio Crameri
Python scripts and packaging:
diff --git a/MAINTAINERS.md b/MAINTAINERS.md
index 040c7d0..a15eede 100644
--- a/MAINTAINERS.md
+++ b/MAINTAINERS.md
@@ -1,14 +1,14 @@
-# To update the colourmaps
+# To update the colormaps
- Download latest version from Zenodo
https://zenodo.org/record/4491293
-- From directory above the colourmaps master folder, create a new directrory called cmaps and do some Bash magic to extract colormap .txt files
+- From directory above the colormaps master folder, create a new directrory called cmaps and do some Bash magic to extract colormap .txt files
`find . -name '*.txt' -exec cp {} ./cmaps \;`
-- Remove the discrete colourmaps (those with numbers at the end)
+- Remove the discrete colormaps (those with numbers at the end)
-- Paste colourmaps to the cmaps directory in cmcrameri
+- Paste colormaps to the cmaps directory in cmcrameri
-- Run the `show_cmaps`function and save the figure in teh home directory of the project
+- Run the `show_cmaps`function and save the figure in the home directory of the project
diff --git a/README.md b/README.md
index e94e994..91da5bd 100644
--- a/README.md
+++ b/README.md
@@ -11,59 +11,72 @@
# cmcrameri
-This is a Python wrapper around Fabio Crameri's perceptually uniform colour maps
+This is a Python wrapper around Fabio Crameri's perceptually uniform colormaps.
-http://www.fabiocrameri.ch/colourmaps.php
+
-All credit for creating the colourmaps to Fabio. Any errors in the Python implementation of colourmaps are my own.
+All credit for creating the colormaps to Fabio.
+Any errors in the Python implementation of colormaps are my own.
-This version is based on Scientific Colourmaps Version 7.0 (02.02.2021)
+This version is based on *Scientific colour maps* [version 7.0](https://zenodo.org/record/4491293) (02.02.2021).
### Install
-With pip:
-
-`pip install cmcrameri`
-
-With conda:
+With `pip`:
+```
+pip install cmcrameri
+```
+With `conda`:
```
-conda config --add channels conda-forge
-conda install cmcrameri
+conda install -c conda-forge cmcrameri
```
+
### Usage example
```python
-from cmcrameri import cm
+import cmcrameri.cm as cmc
import matplotlib.pyplot as plt
import numpy as np
-x = np.linspace(0, 100, 100)[None, :]
-plt.imshow(x, aspect='auto', cmap=cm.batlow) # or any of the other colourmaps made by Fabio Crameri
+
+x = np.linspace(0, 1, 100)[np.newaxis, :]
+
+plt.imshow(x, aspect='auto', cmap=cmc.batlow)
plt.axis('off')
plt.show()
```
+Alternatively, the registered name string can be used.
+```python
+import cmrameri # required in order to register the colormaps with Matplotlib
+...
+plt.imshow(x, aspect='auto', cmap='cmc.batlow')
+```
+
### Extra instructions
-You can access all the core colourmaps from Fabio Crameri's list by `cm.`
-You can use tab autocompletion on `cm` if your editor supports it
+You can access all the core colormaps from Fabio Crameri's list by `cmcrameri.cm.`.
-For a reversed colourmap, append `_r` to the colourmap name
+You can use tab autocompletion on `cmcrameri.cm` if your editor supports it.
-Categorical colormaps have the suffix `S`
+For a reversed colormap, append `_r` to the colormap name.
-For an image of all the available colourmaps without leaving the comfort of your Python session
+Categorical colormaps have the suffix `S`.
+For an image of all the available colormaps without leaving the comfort of your Python session:
```python
-from cmcrameri.cm import show_cmaps
+from cmcrameri import show_cmaps
+
show_cmaps()
```
+![Figure demonstrating the colormaps](cmcrameri/colormaps.png)
-To make the underlying RGB values available, the original text files are shipped as part of the package. Find them on your system with:
+The original colormap text files are shipped as part of the package.
+Find them on your system with:
```python
-from cmcrameri import cm
-cm.paths
+from cmcrameri.cm import paths
+
+paths
```
### License
This work is licensed under an [MIT license](https://mit-license.org/).
-
diff --git a/cmcrameri/__init__.py b/cmcrameri/__init__.py
index 6f5d33b..e1a36d4 100644
--- a/cmcrameri/__init__.py
+++ b/cmcrameri/__init__.py
@@ -1,12 +1,21 @@
"""
-cmcrameri is a package of perceptuallt uniform colourmaps for the geosciences
-This is mererly a wrapper for previosuly created colour maps. All credit to Fabio Crameri
-http://www.fabiocrameri.ch/colourmaps.php
-See README.md for an overview and instructions
-"""
+cmcrameri is a package of perceptually uniform colormaps for the geosciences.
+This is merely a wrapper for previously created colormaps,
+all credit to Fabio Crameri
+https://www.fabiocrameri.ch/colourmaps/
+See README.md for an overview and instructions.
+"""
from __future__ import absolute_import
+
from . import cm
+from .cm import show_cmaps
+
+
+__all__ = (
+ "cm",
+ "show_cmaps",
+)
__authors__ = ['Callum Rollo ']
diff --git a/cmcrameri/cm.py b/cmcrameri/cm.py
index 3f6cda8..85c0ad9 100755
--- a/cmcrameri/cm.py
+++ b/cmcrameri/cm.py
@@ -1,58 +1,193 @@
"""
-Perceptually uniform colourmaps for geosciences
-
-Packaging of colourmaps created by Fabio Crameri http://www.fabiocrameri.ch/colourmaps.php
+Packaging of colormaps created by Fabio Crameri
+https://www.fabiocrameri.ch/colourmaps/
Created by Callum Rollo
2020-05-06
"""
-
-import numpy as np
-from pathlib import Path
-from matplotlib.colors import LinearSegmentedColormap
import matplotlib.pyplot as plt
-import os
-# Find the colormap text files and make a list of the paths
-text_file_folder = os.path.join(os.path.dirname(__file__), 'cmaps')
-paths = list(Path(text_file_folder).glob('*.txt'))
-crameri_cmaps = dict()
-crameri_cmaps_r = dict()
-crameri_cmaps_s = dict()
-for cmap_path in paths:
- # Name of colour map taken from text file
- cmap_name = os.path.split(cmap_path)[1][:-4]
- cm_data = np.loadtxt(str(cmap_path))
- # Make a linear segmented colour map
- if cmap_name[-1] == 'S':
- crameri_cmaps_s[cmap_name] = LinearSegmentedColormap.from_list(cmap_name, cm_data)
- plt.cm.register_cmap(name='cmc.' + cmap_name, cmap=crameri_cmaps_s[cmap_name])
- continue
- crameri_cmaps[cmap_name] = LinearSegmentedColormap.from_list(cmap_name, cm_data)
- plt.cm.register_cmap(name='cmc.' + cmap_name, cmap=crameri_cmaps[cmap_name])
- # reverse the colour map and add this to the dictionary crameri_cmaps_r, mpt fpr categorical maps
- crameri_cmaps_r[cmap_name + '_r'] = LinearSegmentedColormap.from_list(cmap_name + '_r', cm_data[::-1, :])
- plt.cm.register_cmap(name='cmc.' + cmap_name + '_r', cmap=crameri_cmaps_r[cmap_name + '_r'])
-
-
-def show_cmaps():
+import numpy as np
+
+
+_cmap_names_sequential = (
+ "batlow", "batlowW", "batlowK",
+ "devon", "lajolla", "bamako",
+ "davos", "bilbao", "nuuk",
+ "oslo", "grayC", "hawaii",
+ "lapaz", "tokyo", "buda",
+ "acton", "turku", "imola",
+)
+
+_cmap_names_diverging = (
+ "broc", "cork", "vik",
+ "lisbon", "tofino", "berlin",
+ "roma", "bam", "vanimo",
+)
+
+_cmap_names_multi_sequential = (
+ "oleron", "bukavu", "fes",
+)
+
+_cmap_base_names_categorical = tuple(
+ name
+ for name in _cmap_names_sequential
+ if name not in {"batlowW", "batlowK"}
+)
+_cmap_names_categorical = tuple(
+ f"{name}S"
+ for name in _cmap_base_names_categorical
+)
+
+_cmap_base_names_cyclic = (
+ "roma", "bam",
+ "broc", "cork", "vik",
+)
+_cmap_names_cyclic = tuple(
+ f"{name}O"
+ for name in _cmap_base_names_cyclic
+)
+
+def _load_cmaps():
+ from pathlib import Path
+ from matplotlib.colors import ListedColormap
+
+ # Prepended to cmap names when registering
+ cmap_reg_prefix = "cmc."
+
+ cmaps = {}
+
+ def register(cmap):
+ # Register in Matplotlib
+ plt.cm.register_cmap(name=f"{cmap_reg_prefix}{cmap.name}", cmap=cmap)
+ # Add to dict
+ cmaps[cmap.name] = cmap
+
+ # Find the colormap text files and make a list of the paths
+ cmap_data_dir = Path(__file__).parent / 'cmaps'
+ paths = sorted(cmap_data_dir.glob('*.txt'))
+
+ # Load data and generate Colormap objects
+ for cmap_path in paths:
+ # Name of colormap is taken from the text file name
+ cmap_name = cmap_path.stem
+
+ # Categorize
+ is_categorical = cmap_name.endswith("S")
+ is_cyclic = cmap_name.endswith("O")
+ cmap_name_base = cmap_name if not (is_categorical or is_cyclic) else cmap_name[:-1]
+ if not is_cyclic:
+ is_sequential = cmap_name_base in _cmap_names_sequential
+ is_diverging = cmap_name_base in _cmap_names_diverging
+ is_multi_sequential = cmap_name_base in _cmap_names_multi_sequential
+ else:
+ is_sequential = is_diverging = is_multi_sequential = False
+
+ # Check categorization
+ assert sum(
+ [is_cyclic, is_sequential, is_diverging, is_multi_sequential]
+ ) == 1, f"{cmap_name} not categorized properly"
+ assert not is_categorical or cmap_name_base in _cmap_base_names_categorical, cmap_name
+ assert not is_cyclic or cmap_name_base in _cmap_base_names_cyclic, cmap_name
+
+ # Load data
+ data = np.loadtxt(cmap_path)
+ N = data.shape[0]
+ N0 = 256 if not is_categorical else 100
+ assert N == N0, f"N should be {N0} but is {N}"
+
+ # Create and register colormap
+ cmap = ListedColormap(colors=data, name=cmap_name)
+ register(cmap)
+
+ # For non-categorical, also create and register reverse version
+ if not is_categorical:
+ register(cmap.reversed())
+
+ return paths, cmaps
+
+
+paths, cmaps = _load_cmaps()
+
+# Add all cmaps to the `cmcrameri.cm` namespace
+locals().update(cmaps)
+
+
+def show_cmaps(*, ncols=6, figwidth=8):
"""
- A rough function for a quick plot of the colourmaps. Nowhere near as pretty as the original
- see http://www.fabiocrameri.ch/colourmaps.php
- :return:
+ For the original, see
+ https://www.fabiocrameri.ch/colourmaps/
"""
- x = np.linspace(0, 100, 100)[None, :]
- fig, axs = plt.subplots(int(np.ceil(len(crameri_cmaps) / 7)), 7, figsize=(22, 10))
- fig.subplots_adjust(hspace=.8, wspace=.08)
- axs = axs.ravel()
- for ax in axs:
- ax.axis('off')
- for c, cmap_selected in enumerate(sorted(crameri_cmaps.keys())):
- colourmap = crameri_cmaps[cmap_selected]
- axs[c].pcolor(x, cmap=colourmap)
- axs[c].text(5, -0.3, cmap_selected, fontsize=26)
-
-
-# So colourmaps can be called in other programs
-locals().update(crameri_cmaps)
-locals().update(crameri_cmaps_r)
-locals().update(crameri_cmaps_s)
+ import math
+
+ x = np.linspace(0, 1, 256)[np.newaxis, :]
+
+ groups = (
+ ("Sequential", _cmap_names_sequential),
+ ("Diverging", _cmap_names_diverging),
+ ("Multi-sequential", _cmap_names_multi_sequential),
+ ("Cyclic", _cmap_names_cyclic),
+ )
+
+ nrows = 1
+ istarts = []
+ for group_name, group in groups:
+ n = len(group)
+ istarts.append(nrows)
+ nrows += math.ceil(n / ncols)
+ if group_name != groups[-1][0]:
+ nrows += 1 # group spacer row
+
+ nrows_titles = len(groups)
+ nrows_cmaps = nrows - nrows_titles
+
+ hrel_spacer = 0.3 # spacer height relative cmap row height
+ hratios = [1 for _ in range(nrows)]
+ for i in istarts:
+ hratios[i-1] = hrel_spacer # group spacer row
+
+ hrow = 0.4 # size of cmap row
+ hspace = 0.7 # hspace, relative to `hrow`
+ hbottom = 0.2
+ htop = 0.05
+ figheight = (
+ hbottom +
+ htop +
+ hrow*nrows_cmaps +
+ hrow*hrel_spacer*nrows_titles +
+ hrow*hspace*(nrows - 1)
+ )
+
+ fig, axs = plt.subplots(nrows, ncols,
+ figsize=(figwidth, figheight),
+ gridspec_kw=dict(
+ left=0.01, right=0.99,
+ top=1 - htop/figheight,
+ bottom=hbottom/figheight,
+ hspace=hspace/np.mean(hratios),
+ wspace=0.08,
+ height_ratios=hratios
+ )
+ )
+ fig.set_tight_layout(False)
+
+ for ax in axs.flat:
+ ax.set_axis_off()
+
+ for istart, (group_name, group) in zip(istarts, groups):
+
+ # Group label
+ ax0 = axs[istart, 0]
+ ax0.text(0.01, 1.02, group_name, size=24, c="0.4", style="italic",
+ va="bottom", ha="left", transform=ax0.transAxes)
+
+ for ax, cmap_name in zip(axs[istart:].flat, group):
+
+ cmap = cmaps[cmap_name]
+ ax.imshow(x, cmap=cmap, aspect="auto")
+ ax.text(0.01 * ncols/6, -0.03, cmap_name, size=14, color="0.2",
+ va="top", transform=ax.transAxes)
+
+
+if __name__ == "__main__":
+ show_cmaps()
+ plt.savefig("colormaps.png", dpi=200)
diff --git a/cmcrameri/colormaps.png b/cmcrameri/colormaps.png
index b691866..6f86819 100644
Binary files a/cmcrameri/colormaps.png and b/cmcrameri/colormaps.png differ
diff --git a/setup.py b/setup.py
index d96ed0e..df9ecf0 100644
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@
license='MIT',
long_description=long_description,
long_description_content_type='text/markdown',
- description = 'Perceptually uniform colourmaps',
+ description = 'Perceptually uniform colormaps by Fabio Crameri',
author = 'Callum Rollo',
author_email = 'c.rollo@outlook.com',
url = 'https://github.com/callumrollo/cmcrameri',
diff --git a/tests/test_cmcrameri.py b/tests/test_cmcrameri.py
index a677bda..d7c2d37 100644
--- a/tests/test_cmcrameri.py
+++ b/tests/test_cmcrameri.py
@@ -1,10 +1,11 @@
"""
-Test that the program a) finds the text files and b) creates colourmaps
+Test that the program a) finds the text files and b) creates colormaps
"""
-from matplotlib.colors import LinearSegmentedColormap
-from matplotlib.pyplot import get_cmap
-from pathlib import Path
import sys
+from pathlib import Path
+
+from matplotlib.colors import Colormap
+from matplotlib.pyplot import get_cmap
library_dir = Path(__file__).parent.parent.absolute()
sys.path.append(str(library_dir))
@@ -22,18 +23,18 @@ def test_cmap_import():
for name, cmap in vars(cm).items():
increment = 1
# See if it is a colormap.
- if isinstance(cmap, LinearSegmentedColormap):
+ if isinstance(cmap, Colormap):
if name[-1] != 'S':
increment = 0.5
no_cmaps += increment
cmap_names.append(name)
- # Should be as many colour maps as files plus reversed for non categorical ones
+ # Should be as many colormaps as files plus reversed for non categorical ones
assert int(no_cmaps) == len(cm.paths)
def test_get_cmap():
for name, cmap in vars(cm).items():
# See if it is a colormap.
- if isinstance(cmap, LinearSegmentedColormap):
+ if isinstance(cmap, Colormap):
# if cmap hasn't been correctly registered as
# cmc.name, it will raise a ValueError
alt_cmap = get_cmap('cmc.' + name)
@@ -41,6 +42,7 @@ def test_get_cmap():
assert alt_cmap is cmap
-test_find_files()
-test_cmap_import()
-test_get_cmap()
+if __name__ == "__main__":
+ test_find_files()
+ test_cmap_import()
+ test_get_cmap()