Merge remote-tracking branch 'origin/colormap-docstrings' into development

This commit is contained in:
Martin Diehl 2021-12-03 12:18:24 +01:00
commit 99166a7ce2
2 changed files with 189 additions and 72 deletions

View File

@ -5,6 +5,7 @@ import colorsys
from pathlib import Path from pathlib import Path
from typing import Sequence, Union, TextIO from typing import Sequence, Union, TextIO
import numpy as np import numpy as np
import matplotlib as mpl import matplotlib as mpl
if os.name == 'posix' and 'DISPLAY' not in os.environ: if os.name == 'posix' and 'DISPLAY' not in os.environ:
@ -41,6 +42,11 @@ class Colormap(mpl.colors.ListedColormap):
""" """
def __eq__(self, other) -> bool:
"""Test equality of colormaps."""
return len(self.colors) == len(other.colors) \
and bool(np.all(self.colors == other.colors))
def __add__(self, other: 'Colormap') -> 'Colormap': def __add__(self, other: 'Colormap') -> 'Colormap':
"""Concatenate.""" """Concatenate."""
return Colormap(np.vstack((self.colors,other.colors)), return Colormap(np.vstack((self.colors,other.colors)),
@ -80,20 +86,20 @@ class Colormap(mpl.colors.ListedColormap):
Parameters Parameters
---------- ----------
low : numpy.ndarray of shape (3) low : iterable of float (3)
Color definition for minimum value. Color definition for minimum value.
high : numpy.ndarray of shape (3) high : iterable of float (3)
Color definition for maximum value. Color definition for maximum value.
N : int, optional
The number of color quantization levels. Defaults to 256.
name : str, optional name : str, optional
The name of the colormap. Defaults to `DAMASK colormap`. Name of the colormap. Defaults to `DAMASK colormap`.
N : int, optional
Number of color quantization levels. Defaults to 256.
model : {'rgb', 'hsv', 'hsl', 'xyz', 'lab', 'msh'} model : {'rgb', 'hsv', 'hsl', 'xyz', 'lab', 'msh'}
Colormodel used for input color definitions. Defaults to `rgb`. Color model used for input color definitions. Defaults to `rgb`.
The available color models are: The available color models are:
- 'rgb': R(ed) G(green) B(lue). - 'rgb': Red Green Blue.
- 'hsv': H(ue) S(aturation) V(alue). - 'hsv': Hue Saturation Value.
- 'hsl': H(ue) S(aturation) L(uminance). - 'hsl': Hue Saturation Luminance.
- 'xyz': CIE Xyz. - 'xyz': CIE Xyz.
- 'lab': CIE Lab. - 'lab': CIE Lab.
- 'msh': Msh (for perceptual uniform interpolation). - 'msh': Msh (for perceptual uniform interpolation).
@ -109,41 +115,34 @@ class Colormap(mpl.colors.ListedColormap):
>>> damask.Colormap.from_range((0,0,1),(0,0,0),'blue_to_black') >>> damask.Colormap.from_range((0,0,1),(0,0,0),'blue_to_black')
""" """
low_high = np.vstack((low,high)) toMsh = dict(
if model.lower() == 'rgb': rgb=Colormap._rgb2msh,
if np.any(low_high<0) or np.any(low_high>1): hsv=Colormap._hsv2msh,
raise ValueError(f'RGB color {low} | {high} are out of range.') hsl=Colormap._hsl2msh,
xyz=Colormap._xyz2msh,
lab=Colormap._lab2msh,
msh=lambda x:x,
)
low_,high_ = map(Colormap._rgb2msh,low_high) if model.lower() not in toMsh:
elif model.lower() == 'hsv':
if np.any(low_high<0) or np.any(low_high>[360,1,1]):
raise ValueError(f'HSV color {low} | {high} are out of range.')
low_,high_ = map(Colormap._hsv2msh,low_high)
elif model.lower() == 'hsl':
if np.any(low_high<0) or np.any(low_high>[360,1,1]):
raise ValueError(f'HSL color {low} | {high} are out of range.')
low_,high_ = map(Colormap._hsl2msh,low_high)
elif model.lower() == 'xyz':
low_,high_ = map(Colormap._xyz2msh,low_high)
elif model.lower() == 'lab':
if np.any(low_high[:,0]<0):
raise ValueError(f'CIE Lab color {low} | {high} are out of range.')
low_,high_ = map(Colormap._lab2msh,low_high)
elif model.lower() == 'msh':
low_,high_ = low_high[0],low_high[1]
else:
raise ValueError(f'Invalid color model: {model}.') raise ValueError(f'Invalid color model: {model}.')
low_high = np.vstack((low,high))
out_of_bounds = np.bool_(False)
if model.lower() == 'rgb':
out_of_bounds = np.any(low_high<0) or np.any(low_high>1)
elif model.lower() == 'hsv':
out_of_bounds = np.any(low_high<0) or np.any(low_high>[360,1,1])
elif model.lower() == 'hsl':
out_of_bounds = np.any(low_high<0) or np.any(low_high>[360,1,1])
elif model.lower() == 'lab':
out_of_bounds = np.any(low_high[:,0]<0)
if out_of_bounds:
raise ValueError(f'{model.upper()} colors {low} | {high} are out of bounds.')
low_,high_ = map(toMsh[model.lower()],low_high)
msh = map(functools.partial(Colormap._interpolate_msh,low=low_,high=high_),np.linspace(0,1,N)) msh = map(functools.partial(Colormap._interpolate_msh,low=low_,high=high_),np.linspace(0,1,N))
rgb = np.array(list(map(Colormap._msh2rgb,msh))) rgb = np.array(list(map(Colormap._msh2rgb,msh)))
@ -155,17 +154,17 @@ class Colormap(mpl.colors.ListedColormap):
""" """
Select from a set of predefined colormaps. Select from a set of predefined colormaps.
Predefined colormaps include native matplotlib colormaps Predefined colormaps (Colormap.predefined) include
and common DAMASK colormaps. native matplotlib colormaps and common DAMASK colormaps.
Parameters Parameters
---------- ----------
name : str name : str
The name of the colormap. Name of the colormap.
N : int, optional N : int, optional
The number of color quantization levels. Defaults to 256. Number of color quantization levels. Defaults to 256.
This parameter is not used for matplotlib colormaps This parameter is not used for matplotlib colormaps
that are of type `ListedColormap`. that are of type `ListedColormap`.
Returns Returns
------- -------
@ -178,8 +177,8 @@ class Colormap(mpl.colors.ListedColormap):
>>> damask.Colormap.from_predefined('strain') >>> damask.Colormap.from_predefined('strain')
""" """
# matplotlib presets
try: try:
# matplotlib presets
colormap = cm.__dict__[name] colormap = cm.__dict__[name]
return Colormap(np.array(list(map(colormap,np.linspace(0,1,N))) return Colormap(np.array(list(map(colormap,np.linspace(0,1,N)))
if isinstance(colormap,mpl.colors.LinearSegmentedColormap) else if isinstance(colormap,mpl.colors.LinearSegmentedColormap) else
@ -202,8 +201,8 @@ class Colormap(mpl.colors.ListedColormap):
---------- ----------
field : numpy.array of shape (:,:) field : numpy.array of shape (:,:)
Data to be shaded. Data to be shaded.
bounds : iterable of len (2), optional bounds : iterable of float (2), optional
Colormap value range (low,high). Value range (low,high) spanned by colormap.
gap : field.dtype, optional gap : field.dtype, optional
Transparent value. NaN will always be rendered transparent. Transparent value. NaN will always be rendered transparent.
@ -242,13 +241,13 @@ class Colormap(mpl.colors.ListedColormap):
Parameters Parameters
---------- ----------
name : str, optional name : str, optional
The name for the reversed colormap. Name of the reversed colormap.
A name of None will be replaced by the name of the parent colormap + "_r". If None, parent colormap name + "_r".
Returns Returns
------- -------
damask.Colormap damask.Colormap
The reversed colormap. Reversed colormap.
Examples Examples
-------- --------
@ -260,16 +259,19 @@ class Colormap(mpl.colors.ListedColormap):
return Colormap(np.array(rev.colors),rev.name[:-4] if rev.name.endswith('_r_r') else rev.name) return Colormap(np.array(rev.colors),rev.name[:-4] if rev.name.endswith('_r_r') else rev.name)
def _get_file_handle(self, fname: Union[TextIO, str, Path, None], suffix: str) -> TextIO: def _get_file_handle(self,
fname: Union[TextIO, str, Path, None],
dsuffix: str = '') -> TextIO:
""" """
Provide file handle. Provide file handle.
Parameters Parameters
---------- ----------
fname : file, str, pathlib.Path, or None fname : file, str, pathlib.Path, or None
Filename or filehandle, will be name of the colormap+extension if None. Filename or filehandle.
suffix: str If None, colormap name + suffix.
Extension of the filename. suffix: str, optional
Extension to use for colormap filename.
Returns Returns
------- -------
@ -293,10 +295,10 @@ class Colormap(mpl.colors.ListedColormap):
---------- ----------
fname : file, str, or pathlib.Path, optional fname : file, str, or pathlib.Path, optional
Filename to store results. If not given, the filename will Filename to store results. If not given, the filename will
consist of the name of the colormap and extension '.json'. consist of the name of the colormap with extension '.json'.
""" """
colors = [] colors: List = []
for i,c in enumerate(np.round(self.colors,6).tolist()): for i,c in enumerate(np.round(self.colors,6).tolist()):
colors+=[i]+c colors+=[i]+c
@ -321,7 +323,7 @@ class Colormap(mpl.colors.ListedColormap):
---------- ----------
fname : file, str, or pathlib.Path, optional fname : file, str, or pathlib.Path, optional
Filename to store results. If not given, the filename will Filename to store results. If not given, the filename will
consist of the name of the colormap and extension '.txt'. consist of the name of the colormap with extension '.txt'.
""" """
labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3} labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3}
@ -337,7 +339,7 @@ class Colormap(mpl.colors.ListedColormap):
---------- ----------
fname : file, str, or pathlib.Path, optional fname : file, str, or pathlib.Path, optional
Filename to store results. If not given, the filename will Filename to store results. If not given, the filename will
consist of the name of the colormap and extension '.legend'. consist of the name of the colormap with extension '.legend'.
""" """
# ToDo: test in GOM # ToDo: test in GOM
@ -358,7 +360,7 @@ class Colormap(mpl.colors.ListedColormap):
---------- ----------
fname : file, str, or pathlib.Path, optional fname : file, str, or pathlib.Path, optional
Filename to store results. If not given, the filename will Filename to store results. If not given, the filename will
consist of the name of the colormap and extension '.msh'. consist of the name of the colormap with extension '.msh'.
""" """
# ToDo: test in gmsh # ToDo: test in gmsh
@ -369,7 +371,7 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _interpolate_msh(frac, def _interpolate_msh(frac: float,
low: np.ndarray, low: np.ndarray,
high: np.ndarray) -> np.ndarray: high: np.ndarray) -> np.ndarray:
""" """
@ -449,24 +451,76 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _hsv2rgb(hsv: np.ndarray) -> np.ndarray: def _hsv2rgb(hsv: np.ndarray) -> np.ndarray:
"""H(ue) S(aturation) V(alue) to R(red) G(reen) B(lue).""" """
Hue Saturation Value to Red Green Blue.
Parameters
----------
hsv : numpy.ndarray of shape (3)
HSV values.
Returns
-------
rgb : numpy.ndarray of shape (3)
RGB values.
"""
return np.array(colorsys.hsv_to_rgb(hsv[0]/360.,hsv[1],hsv[2])) return np.array(colorsys.hsv_to_rgb(hsv[0]/360.,hsv[1],hsv[2]))
@staticmethod @staticmethod
def _rgb2hsv(rgb: np.ndarray) -> np.ndarray: def _rgb2hsv(rgb: np.ndarray) -> np.ndarray:
"""R(ed) G(reen) B(lue) to H(ue) S(aturation) V(alue).""" """
Red Green Blue to Hue Saturation Value.
Parameters
----------
rgb : numpy.ndarray of shape (3)
RGB values.
Returns
-------
hsv : numpy.ndarray of shape (3)
HSV values.
"""
h,s,v = colorsys.rgb_to_hsv(rgb[0],rgb[1],rgb[2]) h,s,v = colorsys.rgb_to_hsv(rgb[0],rgb[1],rgb[2])
return np.array([h*360,s,v]) return np.array([h*360,s,v])
@staticmethod @staticmethod
def _hsl2rgb(hsl: np.ndarray) -> np.ndarray: def _hsl2rgb(hsl: np.ndarray) -> np.ndarray:
"""H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).""" """
Hue Saturation Luminance to Red Green Blue.
Parameters
----------
hsl : numpy.ndarray of shape (3)
HSL values.
Returns
-------
rgb : numpy.ndarray of shape (3)
RGB values.
"""
return np.array(colorsys.hls_to_rgb(hsl[0]/360.,hsl[2],hsl[1])) return np.array(colorsys.hls_to_rgb(hsl[0]/360.,hsl[2],hsl[1]))
@staticmethod @staticmethod
def _rgb2hsl(rgb: np.ndarray) -> np.ndarray: def _rgb2hsl(rgb: np.ndarray) -> np.ndarray:
"""R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).""" """
Red Green Blue to Hue Saturation Luminance.
Parameters
----------
rgb : numpy.ndarray of shape (3)
RGB values.
Returns
-------
hsl : numpy.ndarray of shape (3)
HSL values.
"""
h,l,s = colorsys.rgb_to_hls(rgb[0],rgb[1],rgb[2]) h,l,s = colorsys.rgb_to_hls(rgb[0],rgb[1],rgb[2])
return np.array([h*360,s,l]) return np.array([h*360,s,l])
@ -474,7 +528,17 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _xyz2rgb(xyz: np.ndarray) -> np.ndarray: def _xyz2rgb(xyz: np.ndarray) -> np.ndarray:
""" """
CIE Xyz to R(ed) G(reen) B(lue). CIE Xyz to Red Green Blue.
Parameters
----------
xyz : numpy.ndarray of shape (3)
CIE Xyz values.
Returns
-------
rgb : numpy.ndarray of shape (3)
RGB values.
References References
---------- ----------
@ -494,7 +558,17 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _rgb2xyz(rgb: np.ndarray) -> np.ndarray: def _rgb2xyz(rgb: np.ndarray) -> np.ndarray:
""" """
R(ed) G(reen) B(lue) to CIE Xyz. Red Green Blue to CIE Xyz.
Parameters
----------
rgb : numpy.ndarray of shape (3)
RGB values.
Returns
-------
xyz : numpy.ndarray of shape (3)
CIE Xyz values.
References References
---------- ----------
@ -514,6 +588,16 @@ class Colormap(mpl.colors.ListedColormap):
""" """
CIE Lab to CIE Xyz. CIE Lab to CIE Xyz.
Parameters
----------
lab : numpy.ndarray of shape (3)
CIE lab values.
Returns
-------
xyz : numpy.ndarray of shape (3)
CIE Xyz values.
References References
---------- ----------
http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
@ -533,6 +617,16 @@ class Colormap(mpl.colors.ListedColormap):
""" """
CIE Xyz to CIE Lab. CIE Xyz to CIE Lab.
Parameters
----------
xyz : numpy.ndarray of shape (3)
CIE Xyz values.
Returns
-------
lab : numpy.ndarray of shape (3)
CIE lab values.
References References
---------- ----------
http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
@ -553,6 +647,16 @@ class Colormap(mpl.colors.ListedColormap):
""" """
CIE Lab to Msh. CIE Lab to Msh.
Parameters
----------
lab : numpy.ndarray of shape (3)
CIE lab values.
Returns
-------
msh : numpy.ndarray of shape (3)
Msh values.
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
@ -571,6 +675,16 @@ class Colormap(mpl.colors.ListedColormap):
""" """
Msh to CIE Lab. Msh to CIE Lab.
Parameters
----------
msh : numpy.ndarray of shape (3)
Msh values.
Returns
-------
lab : numpy.ndarray of shape (3)
CIE lab values.
References References
---------- ----------
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf

View File

@ -77,12 +77,15 @@ class TestColormap:
# xyz2msh # xyz2msh
assert np.allclose(Colormap._xyz2msh(xyz),msh,atol=1.e-6,rtol=0) assert np.allclose(Colormap._xyz2msh(xyz),msh,atol=1.e-6,rtol=0)
def test_eq(self):
assert Colormap.from_predefined('strain') == Colormap.from_predefined('strain')
assert Colormap.from_predefined('strain') != Colormap.from_predefined('stress')
assert Colormap.from_predefined('strain',N=128) != Colormap.from_predefined('strain',N=64)
@pytest.mark.parametrize('low,high',[((0,0,0),(1,1,1)), @pytest.mark.parametrize('low,high',[((0,0,0),(1,1,1)),
([0,0,0],[1,1,1])]) ([0,0,0],[1,1,1])])
def test_from_range_types(self,low,high): def test_from_range_types(self,low,high):
a = Colormap.from_range(low,high) assert Colormap.from_range(low,high) == Colormap.from_range(np.array(low),np.array(high))
b = Colormap.from_range(np.array(low),np.array(high))
assert np.all(a.colors == b.colors)
@pytest.mark.parametrize('format',['ASCII','paraview','GOM','gmsh']) @pytest.mark.parametrize('format',['ASCII','paraview','GOM','gmsh'])
@pytest.mark.parametrize('model',['rgb','hsv','hsl','xyz','lab','msh']) @pytest.mark.parametrize('model',['rgb','hsv','hsl','xyz','lab','msh'])