Merge branch 'typehints_colormap' into 'development'

Typehints colormap

See merge request damask/DAMASK!460
This commit is contained in:
Philip Eisenlohr 2021-11-24 23:15:30 +00:00
commit dc993bc6f9
2 changed files with 58 additions and 44 deletions

View File

@ -2,6 +2,9 @@ import os
import json import json
import functools import functools
import colorsys import colorsys
from pathlib import Path
from typing import Sequence, Union, TextIO
import numpy as np import numpy as np
import matplotlib as mpl import matplotlib as mpl
@ -39,20 +42,20 @@ class Colormap(mpl.colors.ListedColormap):
""" """
def __add__(self,other): def __add__(self, other: "Colormap") -> "Colormap":
"""Concatenate.""" """Concatenate."""
return Colormap(np.vstack((self.colors,other.colors)), return Colormap(np.vstack((self.colors,other.colors)),
f'{self.name}+{other.name}') f'{self.name}+{other.name}')
def __iadd__(self,other): def __iadd__(self, other: "Colormap") -> "Colormap":
"""Concatenate (in-place).""" """Concatenate (in-place)."""
return self.__add__(other) return self.__add__(other)
def __invert__(self): def __invert__(self) -> "Colormap":
"""Reverse.""" """Reverse."""
return self.reversed() return self.reversed()
def __repr__(self): def __repr__(self) -> str:
"""Show as matplotlib figure.""" """Show as matplotlib figure."""
fig = plt.figure(self.name,figsize=(5,.5)) fig = plt.figure(self.name,figsize=(5,.5))
ax1 = fig.add_axes([0, 0, 1, 1]) ax1 = fig.add_axes([0, 0, 1, 1])
@ -64,7 +67,11 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def from_range(low,high,name='DAMASK colormap',N=256,model='rgb'): def from_range(low: Sequence[float],
high: Sequence[float],
name: str = 'DAMASK colormap',
N: int = 256,
model: str = 'rgb') -> "Colormap":
""" """
Create a perceptually uniform colormap between given (inclusive) bounds. Create a perceptually uniform colormap between given (inclusive) bounds.
@ -145,7 +152,7 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def from_predefined(name,N=256): def from_predefined(name: str, N: int = 256) -> "Colormap":
""" """
Select from a set of predefined colormaps. Select from a set of predefined colormaps.
@ -185,7 +192,10 @@ class Colormap(mpl.colors.ListedColormap):
return Colormap.from_range(definition['low'],definition['high'],name,N) return Colormap.from_range(definition['low'],definition['high'],name,N)
def shade(self,field,bounds=None,gap=None): def shade(self,
field: np.ndarray,
bounds: Sequence[float] = None,
gap: float = None) -> Image:
""" """
Generate PIL image of 2D field using colormap. Generate PIL image of 2D field using colormap.
@ -226,7 +236,7 @@ class Colormap(mpl.colors.ListedColormap):
mode='RGBA') mode='RGBA')
def reversed(self,name=None): def reversed(self, name: str = None) -> "Colormap":
""" """
Reverse. Reverse.
@ -251,7 +261,7 @@ 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,extension): def _get_file_handle(self, fname: Union[TextIO, str, Path, None], suffix: str) -> TextIO:
""" """
Provide file handle. Provide file handle.
@ -259,8 +269,7 @@ class Colormap(mpl.colors.ListedColormap):
---------- ----------
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, will be name of the colormap+extension if None.
suffix: str
extension: str
Extension of the filename. Extension of the filename.
Returns Returns
@ -270,17 +279,14 @@ class Colormap(mpl.colors.ListedColormap):
""" """
if fname is None: if fname is None:
fhandle = open(self.name.replace(' ','_')+'.'+extension,'w',newline='\n') return open(self.name.replace(' ','_')+suffix, 'w', newline='\n')
elif isinstance(fname, (str, Path)):
return open(fname, 'w', newline='\n')
else: else:
try: return fname
fhandle = open(fname,'w',newline='\n')
except TypeError:
fhandle = fname
return fhandle
def save_paraview(self,fname=None): def save_paraview(self, fname: Union[TextIO, str, Path] = None):
""" """
Save as JSON file for use in Paraview. Save as JSON file for use in Paraview.
@ -303,10 +309,10 @@ class Colormap(mpl.colors.ListedColormap):
'RGBPoints':colors 'RGBPoints':colors
}] }]
json.dump(out,self._get_file_handle(fname,'json'),indent=4) json.dump(out,self._get_file_handle(fname,'.json'),indent=4)
def save_ASCII(self,fname=None): def save_ASCII(self, fname: Union[TextIO, str, Path] = None):
""" """
Save as ASCII file. Save as ASCII file.
@ -319,10 +325,10 @@ class Colormap(mpl.colors.ListedColormap):
""" """
labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3} labels = {'RGBA':4} if self.colors.shape[1] == 4 else {'RGB': 3}
t = Table(self.colors,labels,f'Creator: {util.execution_stamp("Colormap")}') t = Table(self.colors,labels,f'Creator: {util.execution_stamp("Colormap")}')
t.save(self._get_file_handle(fname,'txt')) t.save(self._get_file_handle(fname,'.txt'))
def save_GOM(self,fname=None): def save_GOM(self, fname: Union[TextIO, str, Path] = None):
""" """
Save as ASCII file for use in GOM Aramis. Save as ASCII file for use in GOM Aramis.
@ -340,10 +346,10 @@ class Colormap(mpl.colors.ListedColormap):
+ ' '.join([f' 0 {c[0]} {c[1]} {c[2]} 255 1' for c in reversed((self.colors*255).astype(int))]) \ + ' '.join([f' 0 {c[0]} {c[1]} {c[2]} 255 1' for c in reversed((self.colors*255).astype(int))]) \
+ '\n' + '\n'
self._get_file_handle(fname,'legend').write(GOM_str) self._get_file_handle(fname,'.legend').write(GOM_str)
def save_gmsh(self,fname=None): def save_gmsh(self, fname: Union[TextIO, str, Path] = None):
""" """
Save as ASCII file for use in gmsh. Save as ASCII file for use in gmsh.
@ -358,11 +364,13 @@ class Colormap(mpl.colors.ListedColormap):
gmsh_str = 'View.ColorTable = {\n' \ gmsh_str = 'View.ColorTable = {\n' \
+'\n'.join([f'{c[0]},{c[1]},{c[2]},' for c in self.colors[:,:3]*255]) \ +'\n'.join([f'{c[0]},{c[1]},{c[2]},' for c in self.colors[:,:3]*255]) \
+'\n}\n' +'\n}\n'
self._get_file_handle(fname,'msh').write(gmsh_str) self._get_file_handle(fname,'.msh').write(gmsh_str)
@staticmethod @staticmethod
def _interpolate_msh(frac,low,high): def _interpolate_msh(frac,
low: np.ndarray,
high: np.ndarray) -> np.ndarray:
""" """
Interpolate in Msh color space. Interpolate in Msh color space.
@ -439,31 +447,31 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _hsv2rgb(hsv): def _hsv2rgb(hsv: np.ndarray) -> np.ndarray:
"""H(ue) S(aturation) V(alue) to R(red) G(reen) B(lue).""" """H(ue) S(aturation) V(alue) to R(red) G(reen) B(lue)."""
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): def _rgb2hsv(rgb: np.ndarray) -> np.ndarray:
"""R(ed) G(reen) B(lue) to H(ue) S(aturation) V(alue).""" """R(ed) G(reen) B(lue) to H(ue) S(aturation) V(alue)."""
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): def _hsl2rgb(hsl: np.ndarray) -> np.ndarray:
"""H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).""" """H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue)."""
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): def _rgb2hsl(rgb: np.ndarray) -> np.ndarray:
"""R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).""" """R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance)."""
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])
@staticmethod @staticmethod
def _xyz2rgb(xyz): def _xyz2rgb(xyz: np.ndarray) -> np.ndarray:
""" """
CIE Xyz to R(ed) G(reen) B(lue). CIE Xyz to R(ed) G(reen) B(lue).
@ -483,7 +491,7 @@ class Colormap(mpl.colors.ListedColormap):
return np.clip(rgb,0.,1.) return np.clip(rgb,0.,1.)
@staticmethod @staticmethod
def _rgb2xyz(rgb): def _rgb2xyz(rgb: np.ndarray) -> np.ndarray:
""" """
R(ed) G(reen) B(lue) to CIE Xyz. R(ed) G(reen) B(lue) to CIE Xyz.
@ -501,7 +509,7 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _lab2xyz(lab,ref_white=None): def _lab2xyz(lab: np.ndarray, ref_white: np.ndarray = None) -> np.ndarray:
""" """
CIE Lab to CIE Xyz. CIE Lab to CIE Xyz.
@ -520,7 +528,7 @@ class Colormap(mpl.colors.ListedColormap):
])*(ref_white if ref_white is not None else _ref_white) ])*(ref_white if ref_white is not None else _ref_white)
@staticmethod @staticmethod
def _xyz2lab(xyz,ref_white=None): def _xyz2lab(xyz: np.ndarray, ref_white: np.ndarray = None) -> np.ndarray:
""" """
CIE Xyz to CIE Lab. CIE Xyz to CIE Lab.
@ -540,7 +548,7 @@ class Colormap(mpl.colors.ListedColormap):
@staticmethod @staticmethod
def _lab2msh(lab): def _lab2msh(lab: np.ndarray) -> np.ndarray:
""" """
CIE Lab to Msh. CIE Lab to Msh.
@ -558,7 +566,7 @@ class Colormap(mpl.colors.ListedColormap):
]) ])
@staticmethod @staticmethod
def _msh2lab(msh): def _msh2lab(msh: np.ndarray) -> np.ndarray:
""" """
Msh to CIE Lab. Msh to CIE Lab.
@ -575,29 +583,29 @@ class Colormap(mpl.colors.ListedColormap):
]) ])
@staticmethod @staticmethod
def _lab2rgb(lab): def _lab2rgb(lab: np.ndarray) -> np.ndarray:
return Colormap._xyz2rgb(Colormap._lab2xyz(lab)) return Colormap._xyz2rgb(Colormap._lab2xyz(lab))
@staticmethod @staticmethod
def _rgb2lab(rgb): def _rgb2lab(rgb: np.ndarray) -> np.ndarray:
return Colormap._xyz2lab(Colormap._rgb2xyz(rgb)) return Colormap._xyz2lab(Colormap._rgb2xyz(rgb))
@staticmethod @staticmethod
def _msh2rgb(msh): def _msh2rgb(msh: np.ndarray) -> np.ndarray:
return Colormap._lab2rgb(Colormap._msh2lab(msh)) return Colormap._lab2rgb(Colormap._msh2lab(msh))
@staticmethod @staticmethod
def _rgb2msh(rgb): def _rgb2msh(rgb: np.ndarray) -> np.ndarray:
return Colormap._lab2msh(Colormap._rgb2lab(rgb)) return Colormap._lab2msh(Colormap._rgb2lab(rgb))
@staticmethod @staticmethod
def _hsv2msh(hsv): def _hsv2msh(hsv: np.ndarray) -> np.ndarray:
return Colormap._rgb2msh(Colormap._hsv2rgb(hsv)) return Colormap._rgb2msh(Colormap._hsv2rgb(hsv))
@staticmethod @staticmethod
def _hsl2msh(hsl): def _hsl2msh(hsl: np.ndarray) -> np.ndarray:
return Colormap._rgb2msh(Colormap._hsl2rgb(hsl)) return Colormap._rgb2msh(Colormap._hsl2rgb(hsl))
@staticmethod @staticmethod
def _xyz2msh(xyz): def _xyz2msh(xyz: np.ndarray) -> np.ndarray:
return Colormap._lab2msh(Colormap._xyz2lab(xyz)) return Colormap._lab2msh(Colormap._xyz2lab(xyz))

View File

@ -77,6 +77,12 @@ 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)
@pytest.mark.parametrize('low,high',[((0,0,0),(1,1,1)),
([0,0,0],[1,1,1])])
def test_from_range_types(self,low,high):
a = Colormap.from_range(low,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'])