new colormap class for use in python
- based on matplotlib "ListedColormap" - constructors - Array of RGB values (inherited), - 'from_bounds': perceptual uniform colormap within given bounds - 'from_predefined': from matplotlib or DAMASK templates - export to files (WIP) - preview on screen
This commit is contained in:
parent
9c89d537d2
commit
e779e190ea
|
@ -1,75 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
import damask
|
||||
|
||||
|
||||
scriptName = os.path.splitext(os.path.basename(__file__))[0]
|
||||
scriptID = ' '.join([scriptName,damask.version])
|
||||
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# MAIN
|
||||
# --------------------------------------------------------------------
|
||||
#Borland, D., & Taylor, R. M. (2007). Rainbow Color Map (Still) Considered Harmful. Computer Graphics and Applications, IEEE, 27(2), 14--17.
|
||||
#Moreland, K. (2009). Diverging Color Maps for Scientific Visualization. In Proc. 5th Int. Symp. Visual Computing (pp. 92--103).
|
||||
outtypes = ['paraview','gmsh','raw','GOM']
|
||||
extensions = ['.json','.msh','.txt','.legend']
|
||||
colormodels = ['RGB','HSL','XYZ','CIELAB','MSH']
|
||||
|
||||
parser = OptionParser(option_class=damask.extendableOption, usage='%prog options [file[s]]', description = """
|
||||
Produces perceptually linear diverging and sequential colormaps in formats suitable for visualization software
|
||||
or simply as a list of interpolated colors.
|
||||
|
||||
""", version = scriptID)
|
||||
|
||||
parser.add_option('-N','--steps', dest='N', type='int', nargs=1, metavar='int',
|
||||
help='number of interpolation steps [%default]')
|
||||
parser.add_option('-t','--trim', dest='trim', type='float', nargs=2, metavar='float float',
|
||||
help='relative trim of colormap range [%default]')
|
||||
parser.add_option('-l','--left', dest='left', type='float', nargs=3, metavar='float float float',
|
||||
help='left color [%default]')
|
||||
parser.add_option('-r','--right', dest='right', type='float', nargs=3, metavar='float float float',
|
||||
help='right color [%default]')
|
||||
parser.add_option('-c','--colormodel', dest='colormodel', metavar='string',
|
||||
help='colormodel: '+', '.join(colormodels)+' [%default]')
|
||||
parser.add_option('-p','--predefined', dest='predefined', metavar='string',
|
||||
help='predefined colormap')
|
||||
parser.add_option('-f','--format', dest='format', metavar='string',
|
||||
help='output format: '+', '.join(outtypes)+' [%default]')
|
||||
parser.set_defaults(colormodel = 'RGB')
|
||||
parser.set_defaults(predefined = None)
|
||||
parser.set_defaults(basename = None)
|
||||
parser.set_defaults(format = 'paraview')
|
||||
parser.set_defaults(N = 10)
|
||||
parser.set_defaults(trim = (-1.0,1.0))
|
||||
parser.set_defaults(left = (1.0,1.0,1.0))
|
||||
parser.set_defaults(right = (0.0,0.0,0.0))
|
||||
|
||||
(options,filename) = parser.parse_args()
|
||||
|
||||
if options.format not in outtypes:
|
||||
parser.error('invalid format: "{}" (choices: {}).'.format(options.format,', '.join(outtypes)))
|
||||
|
||||
if options.N < 2:
|
||||
parser.error('too few steps (need at least 2).')
|
||||
|
||||
if options.trim[0] < -1.0 or \
|
||||
options.trim[1] > 1.0 or \
|
||||
options.trim[0] >= options.trim[1]:
|
||||
parser.error('invalid trim range (-1 +1).')
|
||||
|
||||
name = options.format if filename == [] \
|
||||
else filename[0]
|
||||
output = sys.stdout if filename == [] \
|
||||
else open(os.path.basename(filename[0])+extensions[outtypes.index(options.format)],'w')
|
||||
|
||||
colorLeft = damask.Color(options.colormodel.upper(), list(options.left))
|
||||
colorRight = damask.Color(options.colormodel.upper(), list(options.right))
|
||||
colormap = damask.Colormap(colorLeft, colorRight, predefined=options.predefined)
|
||||
|
||||
output.write(colormap.export(name,options.format,options.N,list(options.trim)))
|
||||
output.close()
|
|
@ -10,7 +10,7 @@ with open(_Path(__file__).parent/_Path('VERSION')) as _f:
|
|||
from ._environment import Environment # noqa
|
||||
from ._table import Table # noqa
|
||||
from ._vtk import VTK # noqa
|
||||
from ._colormaps import Colormap, Color # noqa
|
||||
from ._colormap import Colormap # noqa
|
||||
from ._rotation import Rotation # noqa
|
||||
from ._lattice import Symmetry, Lattice# noqa
|
||||
from ._orientation import Orientation # noqa
|
||||
|
|
|
@ -0,0 +1,482 @@
|
|||
import json
|
||||
import functools
|
||||
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import cm
|
||||
|
||||
from damask import Table
|
||||
|
||||
_eps = 216./24389.
|
||||
_kappa = 24389./27.
|
||||
_ref_white = np.array([.95047, 1.00000, 1.08883]) # Observer = 2, Illuminant = D65
|
||||
|
||||
|
||||
class Colormap(mpl.colors.ListedColormap):
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_bounds(low,high,N=256,name='DAMASK colormap',model='rgb'):
|
||||
"""
|
||||
Create a perceptually uniform colormap.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
low : numpy.ndarray of shape (3)
|
||||
high : numpy.ndarray of shape (3)
|
||||
N : integer, optional
|
||||
Number of discrete color values. Defaults to 256.
|
||||
model : str
|
||||
Colormodel used for low and high.
|
||||
|
||||
"""
|
||||
low_,high_ = map(np.array,[low,high])
|
||||
if model.lower() == 'rgb':
|
||||
# ToDo: Sanity check
|
||||
low_,high_ = map(Colormap._rgb2msh,[low_,high_])
|
||||
elif model.lower() == 'hsv':
|
||||
# ToDo: Sanity check
|
||||
low_,high_ = map(Colormap._hsv2msh,[low_,high_])
|
||||
elif model.lower() == 'hsl':
|
||||
# ToDo: Sanity check
|
||||
low_,high_ = map(Colormap._hsl2msh,[low_,high_])
|
||||
elif model.lower() == 'xyz':
|
||||
# ToDo: Sanity check
|
||||
low_,high_ = map(Colormap._xyz2msh,[low_,high_])
|
||||
elif model.lower() == 'lab':
|
||||
# ToDo: Sanity check
|
||||
low_,high_ = map(Colormap._lab2msh,[low_,high_])
|
||||
elif model.lower() == 'msh':
|
||||
# ToDo: Sanity check
|
||||
pass
|
||||
else:
|
||||
raise ValueError(f'Invalid color model: {model}.')
|
||||
|
||||
msh = map(functools.partial(Colormap._interpolate_msh,low=low_,high=high_),np.linspace(0,1,N))
|
||||
rgb = np.array(list(map(Colormap._msh2rgb,msh)))
|
||||
|
||||
return Colormap(rgb,name=name)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def from_predefined(name,N=256):
|
||||
"""
|
||||
Select from set of predefined colormaps.
|
||||
|
||||
Predefined colormaps include matplotlib-native colormaps
|
||||
and common DAMASK colormaps.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
N : int, optional
|
||||
Number of discrete color values. Defaults to 256.
|
||||
This parameter is not used for matplotlib colormaps
|
||||
that are of type `ListedColormap`.
|
||||
|
||||
"""
|
||||
# matplotlib presets
|
||||
for cat in Colormap._predefined_mpl:
|
||||
for n in cat[1]:
|
||||
if n == name:
|
||||
colormap = cm.__dict__[name]
|
||||
if isinstance(colormap,mpl.colors.LinearSegmentedColormap):
|
||||
return Colormap(np.array(list(map(colormap,np.linspace(0,1,N)))),name=name)
|
||||
else:
|
||||
return Colormap(colormap.colors,name=name)
|
||||
|
||||
# DAMASK presets
|
||||
definition = Colormap._predefined_DAMASK[name]
|
||||
return Colormap.from_bounds(definition['left'],definition['right'],N,name)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def list_predefined():
|
||||
"""List predefined colormaps by category."""
|
||||
print('DAMASK colormaps')
|
||||
print(' '+', '.join(Colormap._predefined_DAMASK.keys()))
|
||||
for cat in Colormap._predefined_mpl:
|
||||
print(f'{cat[0]}')
|
||||
print(' '+', '.join(cat[1]))
|
||||
|
||||
|
||||
def show(self):
|
||||
fig, ax = plt.subplots(figsize=(10,1))
|
||||
ax.set_axis_off()
|
||||
im = ax.imshow(np.broadcast_to(np.linspace(0,1,640).reshape(1,-1),(64,640)),cmap=self) # noqa
|
||||
fig.canvas.set_window_title(self.name)
|
||||
plt.show()
|
||||
|
||||
|
||||
def to_file(self,fname=None,format='paraview'):
|
||||
if fname is not None:
|
||||
try:
|
||||
f = open(fname,'w')
|
||||
except TypeError:
|
||||
f = fname
|
||||
else:
|
||||
f = None
|
||||
|
||||
if format.lower() == 'paraview':
|
||||
Colormap._export_paraview(self,f)
|
||||
elif format.lower() == 'ascii':
|
||||
Colormap._export_ASCII(self,f)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _export_paraview(colormap,fhandle=None):
|
||||
colors = []
|
||||
for i,c in enumerate(np.round(colormap.colors,6).tolist()):
|
||||
colors+=[i]+c
|
||||
|
||||
out = [{
|
||||
'ColorSpace':'RGB',
|
||||
'Name':colormap.name,
|
||||
'DefaultMap':True,
|
||||
'RGBPoints':colors
|
||||
}]
|
||||
if fhandle is None:
|
||||
with open(colormap.name.replace(' ','_')+'.json', 'w') as f:
|
||||
json.dump(out, f,indent=4)
|
||||
else:
|
||||
json.dump(out,fhandle,indent=4)
|
||||
|
||||
@staticmethod
|
||||
def _export_ASCII(colormap,fhandle=None):
|
||||
labels = {'R':(1,),'G':(1,),'B':(1,)}
|
||||
if colormap.colors.shape[1] == 4: labels['alpha']=(1,)
|
||||
|
||||
t = Table(colormap.colors,labels)
|
||||
if fhandle is None:
|
||||
with open(colormap.name.replace(' ','_')+'.txt', 'w') as f:
|
||||
t.to_ASCII(f)
|
||||
else:
|
||||
t.to_ASCII(fhandle)
|
||||
|
||||
@staticmethod
|
||||
def _export_GOM(colormap,fhandle=None):
|
||||
pass
|
||||
# a = f'1 1 {name.replace(" ","_"} 9 {name.replace(" ","_"} '
|
||||
# f' 0 1 0 3 0 0 -1 9 \\ 0 0 0 255 255 255 0 0 255 '
|
||||
# f'30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 ' + str(len(colors))
|
||||
# f' '.join([' 0 %s 255 1'%(' '.join([str(int(x*255.0)) for x in color])) for color in reversed(colors)])]
|
||||
|
||||
@staticmethod
|
||||
def _export_gmsh(colormap,fname=None):
|
||||
colors = colormap.colors
|
||||
colormap = ['View.ColorTable = {'] \
|
||||
+ [',\n'.join(['{%s}'%(','.join([str(x*255.0) for x in color])) for color in colors])] \
|
||||
+ ['}']
|
||||
|
||||
@staticmethod
|
||||
def _interpolate_msh(frac,low, high):
|
||||
|
||||
def rad_diff(a,b):
|
||||
return abs(a[2]-b[2])
|
||||
|
||||
def adjust_hue(msh_sat, msh_unsat):
|
||||
"""If saturation of one of the two colors is much less than the other, hue of the less."""
|
||||
if msh_sat[0] >= msh_unsat[0]:
|
||||
return msh_sat[2]
|
||||
else:
|
||||
hSpin = msh_sat[1]/np.sin(msh_sat[1])*np.sqrt(msh_unsat[0]**2.0-msh_sat[0]**2)/msh_sat[0]
|
||||
if msh_sat[2] < - np.pi/3.0: hSpin *= -1.0
|
||||
return msh_sat[2] + hSpin
|
||||
|
||||
|
||||
lo = np.array(low)
|
||||
hi = np.array(high)
|
||||
|
||||
if (lo[1] > 0.05 and hi[1] > 0.05 and rad_diff(lo,hi) > np.pi/3.0):
|
||||
M_mid = max(lo[0],hi[0],88.0)
|
||||
if frac < 0.5:
|
||||
hi = np.array([M_mid,0.0,0.0])
|
||||
frac *= 2.0
|
||||
else:
|
||||
lo = np.array([M_mid,0.0,0.0])
|
||||
frac = 2.0*frac - 1.0
|
||||
if lo[1] < 0.05 and hi[1] > 0.05:
|
||||
lo[2] = adjust_hue(hi,lo)
|
||||
elif lo[1] > 0.05 and hi[1] < 0.05:
|
||||
hi[2] = adjust_hue(lo,hi)
|
||||
|
||||
return (1.0 - frac) * lo + frac * hi
|
||||
|
||||
|
||||
_predefined_mpl= [('Perceptually Uniform Sequential', [
|
||||
'viridis', 'plasma', 'inferno', 'magma', 'cividis']),
|
||||
('Sequential', [
|
||||
'Greys', 'Purples', 'Blues', 'Greens', 'Oranges', 'Reds',
|
||||
'YlOrBr', 'YlOrRd', 'OrRd', 'PuRd', 'RdPu', 'BuPu',
|
||||
'GnBu', 'PuBu', 'YlGnBu', 'PuBuGn', 'BuGn', 'YlGn']),
|
||||
('Sequential (2)', [
|
||||
'binary', 'gist_yarg', 'gist_gray', 'gray', 'bone', 'pink',
|
||||
'spring', 'summer', 'autumn', 'winter', 'cool', 'Wistia',
|
||||
'hot', 'afmhot', 'gist_heat', 'copper']),
|
||||
('Diverging', [
|
||||
'PiYG', 'PRGn', 'BrBG', 'PuOr', 'RdGy', 'RdBu',
|
||||
'RdYlBu', 'RdYlGn', 'Spectral', 'coolwarm', 'bwr', 'seismic']),
|
||||
('Cyclic', ['twilight', 'twilight_shifted', 'hsv']),
|
||||
('Qualitative', [
|
||||
'Pastel1', 'Pastel2', 'Paired', 'Accent',
|
||||
'Dark2', 'Set1', 'Set2', 'Set3',
|
||||
'tab10', 'tab20', 'tab20b', 'tab20c']),
|
||||
('Miscellaneous', [
|
||||
'flag', 'prism', 'ocean', 'gist_earth', 'terrain', 'gist_stern',
|
||||
'gnuplot', 'gnuplot2', 'CMRmap', 'cubehelix', 'brg',
|
||||
'gist_rainbow', 'rainbow', 'jet', 'nipy_spectral', 'gist_ncar'])]
|
||||
|
||||
_predefined_DAMASK = {'orientation': {'low': [0.933334,0.878432,0.878431],
|
||||
'right': [0.250980,0.007843,0.000000]},
|
||||
'strain': {'left': [0.941177,0.941177,0.870588],
|
||||
'right': [0.266667,0.266667,0.000000]},
|
||||
'stress': {'left': [0.878432,0.874511,0.949019],
|
||||
'right': [0.000002,0.000000,0.286275]}}
|
||||
|
||||
@staticmethod
|
||||
def _hsv2rgb(hsv):
|
||||
"""
|
||||
H(ue) S(aturation) V(alue) to R(red) G(reen) B(lue).
|
||||
|
||||
References
|
||||
----------
|
||||
https://www.rapidtables.com/convert/color/hsv-to-rgb.html
|
||||
|
||||
"""
|
||||
sextant = np.clip(int(hsv[0]/60.),0,5)
|
||||
c = hsv[1]*hsv[2]
|
||||
x = c*(1.0 - abs((hsv[0]/60.)%2 - 1.))
|
||||
|
||||
return np.array([
|
||||
[c, x, 0],
|
||||
[x, c, 0],
|
||||
[0, c, x],
|
||||
[0, x, c],
|
||||
[x, 0, c],
|
||||
[c, 0, x],
|
||||
])[sextant] + hsv[2] - c
|
||||
|
||||
@staticmethod
|
||||
def _rgb2hsv(rgb):
|
||||
"""
|
||||
R(ed) G(reen) B(lue) to H(ue) S(aturation) V(alue).
|
||||
|
||||
References
|
||||
----------
|
||||
https://www.rapidtables.com/convert/color/rgb-to-hsv.html
|
||||
|
||||
"""
|
||||
C_max = rgb.max()
|
||||
C_min = rgb.min()
|
||||
Delta = C_max - C_min
|
||||
|
||||
v = C_max
|
||||
s = 0. if np.isclose(C_max,0.) else Delta/C_max
|
||||
if np.isclose(Delta,0.):
|
||||
h = 0.
|
||||
elif rgb.argmax() == 0:
|
||||
h = (rgb[1]-rgb[2])/Delta%6
|
||||
elif rgb.argmax() == 1:
|
||||
h = (rgb[2]-rgb[0])/Delta + 2.
|
||||
elif rgb.argmax() == 2:
|
||||
h = (rgb[0]-rgb[1])/Delta + 4.
|
||||
|
||||
h = np.clip(h,0.,6.) * 60.
|
||||
|
||||
return np.array([h,s,v])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _hsl2rgb(hsl):
|
||||
"""
|
||||
H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).
|
||||
|
||||
References
|
||||
----------
|
||||
https://www.rapidtables.com/convert/color/hsl-to-rgb.html
|
||||
|
||||
"""
|
||||
sextant = np.clip(int(hsl[0]/60.),0,5)
|
||||
c = (1.0 - abs(2.0 * hsl[2] - 1.))*hsl[1]
|
||||
x = c*(1.0 - abs((hsl[0]/60.)%2 - 1.))
|
||||
m = hsl[2] - 0.5*c
|
||||
|
||||
return np.array([
|
||||
[c+m, x+m, m],
|
||||
[x+m, c+m, m],
|
||||
[m, c+m, x+m],
|
||||
[m, x+m, c+m],
|
||||
[x+m, m, c+m],
|
||||
[c+m, m, x+m],
|
||||
])[sextant]
|
||||
|
||||
@staticmethod
|
||||
def _rgb2hsl(rgb):
|
||||
"""
|
||||
R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).
|
||||
|
||||
References
|
||||
----------
|
||||
https://www.rapidtables.com/convert/color/rgb-to-hsl.html
|
||||
|
||||
"""
|
||||
C_max = rgb.max()
|
||||
C_min = rgb.min()
|
||||
Delta = C_max - C_min
|
||||
|
||||
l = np.clip((C_max + C_min)*.5,0.,1.) # noqa
|
||||
s = 0. if np.isclose(C_max,C_min) else Delta/(1.-np.abs(2*l-1.))
|
||||
if np.isclose(Delta,0.):
|
||||
h = 0.
|
||||
elif rgb.argmax() == 0:
|
||||
h = (rgb[1]-rgb[2])/Delta%6
|
||||
elif rgb.argmax() == 1:
|
||||
h = (rgb[2]-rgb[0])/Delta + 2.
|
||||
elif rgb.argmax() == 2:
|
||||
h = (rgb[0]-rgb[1])/Delta + 4.
|
||||
|
||||
h = np.clip(h,0.,6.) * 60.
|
||||
|
||||
return np.array([h,s,l])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _xyz2rgb(xyz):
|
||||
"""
|
||||
CIE Xyz to R(ed) G(reen) B(lue).
|
||||
|
||||
References
|
||||
----------
|
||||
http://www.ryanjuckett.com/programming/rgb-color-space-conversion
|
||||
|
||||
"""
|
||||
rgb_lin = np.dot(np.array([
|
||||
[ 3.240969942,-1.537383178,-0.498610760],
|
||||
[-0.969243636, 1.875967502, 0.041555057],
|
||||
[ 0.055630080,-0.203976959, 1.056971514]
|
||||
]),xyz)
|
||||
with np.errstate(invalid='ignore'):
|
||||
rgb = np.where(rgb_lin>0.0031308,rgb_lin**(1.0/2.4)*1.0555-0.0555,rgb_lin*12.92)
|
||||
|
||||
return np.clip(rgb,0.,1.)
|
||||
|
||||
@staticmethod
|
||||
def _rgb2xyz(rgb):
|
||||
"""
|
||||
R(ed) G(reen) B(lue) to CIE Xyz.
|
||||
|
||||
References
|
||||
----------
|
||||
http://www.ryanjuckett.com/programming/rgb-color-space-conversion
|
||||
|
||||
"""
|
||||
rgb_lin = np.where(rgb>0.04045,((rgb+0.0555)/1.0555)**2.4,rgb/12.92)
|
||||
return np.dot(np.array([
|
||||
[0.412390799,0.357584339,0.180480788],
|
||||
[0.212639006,0.715168679,0.072192315],
|
||||
[0.019330819,0.119194780,0.950532152]
|
||||
]),rgb_lin)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _lab2xyz(lab,ref_white=None):
|
||||
"""
|
||||
CIE Lab to CIE Xyz.
|
||||
|
||||
References
|
||||
----------
|
||||
http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
|
||||
|
||||
"""
|
||||
f_x = (lab[0]+16.)/116. + lab[1]/500.
|
||||
f_z = (lab[0]+16.)/116. - lab[2]/200.
|
||||
|
||||
return np.array([
|
||||
f_x**3. if f_x**3. > _eps else (116.*f_x-16.)/_kappa,
|
||||
((lab[0]+16.)/116.)**3 if lab[0]>_kappa*_eps else lab[0]/_kappa,
|
||||
f_z**3. if f_z**3. > _eps else (116.*f_z-16.)/_kappa
|
||||
])*(ref_white if ref_white is not None else _ref_white)
|
||||
|
||||
@staticmethod
|
||||
def _xyz2lab(xyz,ref_white=None):
|
||||
"""
|
||||
CIE Xyz to CIE Lab.
|
||||
|
||||
References
|
||||
----------
|
||||
http://www.brucelindbloom.com/index.html?Eqn_Lab_to_XYZ.html
|
||||
|
||||
"""
|
||||
ref_white = ref_white if ref_white is not None else _ref_white
|
||||
f = np.where(xyz/ref_white > _eps,(xyz/ref_white)**(1./3.),(_kappa*xyz/ref_white+16.)/116.)
|
||||
|
||||
return np.array([
|
||||
116.0 * f[1] - 16.0,
|
||||
500.0 * (f[0] - f[1]),
|
||||
200.0 * (f[1] - f[2])
|
||||
])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _lab2msh(lab):
|
||||
"""
|
||||
CIE Lab to Msh.
|
||||
|
||||
References
|
||||
----------
|
||||
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
|
||||
"""
|
||||
M = np.linalg.norm(lab)
|
||||
return np.array([
|
||||
M,
|
||||
np.arccos(lab[0]/M) if M>1e-8 else 0.,
|
||||
np.arctan2(lab[2],lab[1]) if M>1e-8 else 0.,
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def _msh2lab(msh):
|
||||
"""
|
||||
Msh to CIE Lab.
|
||||
|
||||
References
|
||||
----------
|
||||
https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf
|
||||
https://www.kennethmoreland.com/color-maps/diverging_map.py
|
||||
|
||||
"""
|
||||
return np.array([
|
||||
msh[0] * np.cos(msh[1]),
|
||||
msh[0] * np.sin(msh[1]) * np.cos(msh[2]),
|
||||
msh[0] * np.sin(msh[1]) * np.sin(msh[2])
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
def _lab2rgb(lab):
|
||||
return Colormap._xyz2rgb(Colormap._lab2xyz(lab))
|
||||
|
||||
@staticmethod
|
||||
def _rgb2lab(rgb):
|
||||
return Colormap._xyz2lab(Colormap._rgb2xyz(rgb))
|
||||
|
||||
@staticmethod
|
||||
def _msh2rgb(msh):
|
||||
return Colormap._lab2rgb(Colormap._msh2lab(msh))
|
||||
|
||||
@staticmethod
|
||||
def _rgb2msh(rgb):
|
||||
return Colormap._lab2msh(Colormap._rgb2lab(rgb))
|
||||
|
||||
@staticmethod
|
||||
def _hsv2msh(hsv):
|
||||
return Colormap._rgb2msh(Colormap._hsv2rgb(hsv))
|
||||
|
||||
@staticmethod
|
||||
def _hsl2msh(hsl):
|
||||
return Colormap._rgb2msh(Colormap._hsl2rgb(hsl))
|
||||
|
||||
@staticmethod
|
||||
def _xyz2msh(xyz):
|
||||
return Colormap._lab2msh(Colormap._xyz2lab(xyz))
|
|
@ -1,541 +0,0 @@
|
|||
import numpy as np
|
||||
from . import util
|
||||
|
||||
class Color:
|
||||
"""Color representation in and conversion between different color-spaces."""
|
||||
|
||||
__slots__ = [
|
||||
'model',
|
||||
'color',
|
||||
'__dict__',
|
||||
]
|
||||
|
||||
|
||||
def __init__(self,
|
||||
model = 'RGB',
|
||||
color = np.zeros(3)):
|
||||
"""
|
||||
Create a Color object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
model : string
|
||||
color model
|
||||
color : numpy.ndarray
|
||||
vector representing the color according to the selected model
|
||||
|
||||
"""
|
||||
self.__transforms__ = \
|
||||
{'HSV': {'index': 0, 'next': self._HSV2HSL},
|
||||
'HSL': {'index': 1, 'next': self._HSL2RGB, 'prev': self._HSL2HSV},
|
||||
'RGB': {'index': 2, 'next': self._RGB2XYZ, 'prev': self._RGB2HSL},
|
||||
'XYZ': {'index': 3, 'next': self._XYZ2CIELAB, 'prev': self._XYZ2RGB},
|
||||
'CIELAB': {'index': 4, 'next': self._CIELAB2MSH, 'prev': self._CIELAB2XYZ},
|
||||
'MSH': {'index': 5, 'prev': self._MSH2CIELAB},
|
||||
}
|
||||
|
||||
model = model.upper()
|
||||
if model not in list(self.__transforms__.keys()): model = 'RGB'
|
||||
if model == 'RGB' and max(color) > 1.0: # are we RGB255 ?
|
||||
for i in range(3):
|
||||
color[i] /= 255.0 # rescale to RGB
|
||||
|
||||
if model == 'HSL': # are we HSL ?
|
||||
if abs(color[0]) > 1.0: color[0] /= 360.0 # with angular hue?
|
||||
while color[0] >= 1.0: color[0] -= 1.0 # rewind to proper range
|
||||
while color[0] < 0.0: color[0] += 1.0 # rewind to proper range
|
||||
|
||||
self.model = model
|
||||
self.color = np.array(color,'d')
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
"""Color model and values."""
|
||||
return 'Model: %s Color: %s'%(self.model,str(self.color))
|
||||
|
||||
|
||||
def __str__(self):
|
||||
"""Color model and values."""
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
def convert_to(self,toModel = 'RGB'):
|
||||
"""
|
||||
Change the color model permanently.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
toModel : string
|
||||
color model
|
||||
|
||||
"""
|
||||
toModel = toModel.upper()
|
||||
if toModel not in list(self.__transforms__.keys()): return
|
||||
|
||||
sourcePos = self.__transforms__[self.model]['index']
|
||||
targetPos = self.__transforms__[toModel]['index']
|
||||
|
||||
while sourcePos < targetPos:
|
||||
self.__transforms__[self.model]['next']()
|
||||
sourcePos += 1
|
||||
|
||||
while sourcePos > targetPos:
|
||||
self.__transforms__[self.model]['prev']()
|
||||
sourcePos -= 1
|
||||
return self
|
||||
|
||||
|
||||
def express_as(self,asModel = 'RGB'):
|
||||
"""
|
||||
Return the color in a different model.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
asModel : string
|
||||
color model
|
||||
|
||||
"""
|
||||
return self.__class__(self.model,self.color).convert_to(asModel)
|
||||
|
||||
|
||||
def _HSV2HSL(self):
|
||||
"""
|
||||
Convert H(ue) S(aturation) V(alue or brightness) to H(ue) S(aturation) L(uminance).
|
||||
|
||||
All values are in the range [0,1]
|
||||
http://codeitdown.com/hsl-hsb-hsv-color
|
||||
"""
|
||||
if self.model != 'HSV': return
|
||||
|
||||
converted = Color('HSL',np.array([
|
||||
self.color[0],
|
||||
1. if self.color[2] == 0.0 or (self.color[1] == 0.0 and self.color[2] == 1.0) \
|
||||
else self.color[1]*self.color[2]/(1.-abs(self.color[2]*(2.-self.color[1])-1.)),
|
||||
0.5*self.color[2]*(2.-self.color[1]),
|
||||
]))
|
||||
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _HSL2HSV(self):
|
||||
"""
|
||||
Convert H(ue) S(aturation) L(uminance) to H(ue) S(aturation) V(alue or brightness).
|
||||
|
||||
All values are in the range [0,1]
|
||||
http://codeitdown.com/hsl-hsb-hsv-color
|
||||
"""
|
||||
if self.model != 'HSL': return
|
||||
|
||||
h = self.color[0]
|
||||
b = self.color[2]+0.5*(self.color[1]*(1.-abs(2*self.color[2]-1)))
|
||||
s = 1.0 if b == 0.0 else 2.*(b-self.color[2])/b
|
||||
|
||||
converted = Color('HSV',np.array([h,s,b]))
|
||||
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _HSL2RGB(self):
|
||||
"""
|
||||
Convert H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).
|
||||
|
||||
All values are in the range [0,1]
|
||||
from http://en.wikipedia.org/wiki/HSL_and_HSV
|
||||
"""
|
||||
if self.model != 'HSL': return
|
||||
|
||||
sextant = self.color[0]*6.0
|
||||
c = (1.0 - abs(2.0 * self.color[2] - 1.0))*self.color[1]
|
||||
x = c*(1.0 - abs(sextant%2 - 1.0))
|
||||
m = self.color[2] - 0.5*c
|
||||
|
||||
converted = Color('RGB',np.array([
|
||||
[c+m, x+m, m],
|
||||
[x+m, c+m, m],
|
||||
[m, c+m, x+m],
|
||||
[m, x+m, c+m],
|
||||
[x+m, m, c+m],
|
||||
[c+m, m, x+m],
|
||||
][int(sextant)]))
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _RGB2HSL(self):
|
||||
"""
|
||||
Convert R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).
|
||||
|
||||
All values are in the range [0,1]
|
||||
from http://130.113.54.154/~monger/hsl-rgb.html
|
||||
"""
|
||||
if self.model != 'RGB': return
|
||||
|
||||
HSL = np.zeros(3)
|
||||
maxcolor = self.color.max()
|
||||
mincolor = self.color.min()
|
||||
HSL[2] = (maxcolor + mincolor)/2.0
|
||||
if(mincolor == maxcolor):
|
||||
HSL[0] = 0.0
|
||||
HSL[1] = 0.0
|
||||
else:
|
||||
if (HSL[2]<0.5):
|
||||
HSL[1] = (maxcolor - mincolor)/(maxcolor + mincolor)
|
||||
else:
|
||||
HSL[1] = (maxcolor - mincolor)/(2.0 - maxcolor - mincolor)
|
||||
if (maxcolor == self.color[0]):
|
||||
HSL[0] = 0.0 + (self.color[1] - self.color[2])/(maxcolor - mincolor)
|
||||
elif (maxcolor == self.color[1]):
|
||||
HSL[0] = 2.0 + (self.color[2] - self.color[0])/(maxcolor - mincolor)
|
||||
elif (maxcolor == self.color[2]):
|
||||
HSL[0] = 4.0 + (self.color[0] - self.color[1])/(maxcolor - mincolor)
|
||||
HSL[0] = HSL[0]*60.0 # scaling to 360 might be dangerous for small values
|
||||
if (HSL[0] < 0.0):
|
||||
HSL[0] = HSL[0] + 360.0
|
||||
HSL[1:] = np.clip(HSL[1:],0.0,1.0)
|
||||
|
||||
converted = Color('HSL', HSL)
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _RGB2XYZ(self):
|
||||
"""
|
||||
Convert R(ed) G(reen) B(lue) to CIE XYZ.
|
||||
|
||||
All values are in the range [0,1]
|
||||
from http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||
"""
|
||||
if self.model != 'RGB': return
|
||||
|
||||
XYZ = np.zeros(3)
|
||||
RGB_lin = np.zeros(3)
|
||||
convert = np.array([[0.412453,0.357580,0.180423],
|
||||
[0.212671,0.715160,0.072169],
|
||||
[0.019334,0.119193,0.950227]])
|
||||
|
||||
for i in range(3):
|
||||
if (self.color[i] > 0.04045): RGB_lin[i] = ((self.color[i]+0.0555)/1.0555)**2.4
|
||||
else: RGB_lin[i] = self.color[i] /12.92
|
||||
XYZ = np.dot(convert,RGB_lin)
|
||||
XYZ = np.clip(XYZ,0.0,None)
|
||||
|
||||
converted = Color('XYZ', XYZ)
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _XYZ2RGB(self):
|
||||
"""
|
||||
Convert CIE XYZ to R(ed) G(reen) B(lue).
|
||||
|
||||
All values are in the range [0,1]
|
||||
from http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||
"""
|
||||
if self.model != 'XYZ': return
|
||||
|
||||
convert = np.array([[ 3.240479,-1.537150,-0.498535],
|
||||
[-0.969256, 1.875992, 0.041556],
|
||||
[ 0.055648,-0.204043, 1.057311]])
|
||||
RGB_lin = np.dot(convert,self.color)
|
||||
RGB = np.zeros(3)
|
||||
|
||||
for i in range(3):
|
||||
if (RGB_lin[i] > 0.0031308): RGB[i] = ((RGB_lin[i])**(1.0/2.4))*1.0555-0.0555
|
||||
else: RGB[i] = RGB_lin[i] *12.92
|
||||
|
||||
RGB = np.clip(RGB,0.0,1.0)
|
||||
|
||||
maxVal = max(RGB) # clipping colors according to the display gamut
|
||||
if (maxVal > 1.0): RGB /= maxVal
|
||||
|
||||
converted = Color('RGB', RGB)
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _CIELAB2XYZ(self):
|
||||
"""
|
||||
Convert CIE Lab to CIE XYZ.
|
||||
|
||||
All values are in the range [0,1]
|
||||
from http://www.easyrgb.com/index.php?X=MATH&H=07#text7
|
||||
"""
|
||||
if self.model != 'CIELAB': return
|
||||
|
||||
ref_white = np.array([.95047, 1.00000, 1.08883]) # Observer = 2, Illuminant = D65
|
||||
XYZ = np.zeros(3)
|
||||
|
||||
XYZ[1] = (self.color[0] + 16.0 ) / 116.0
|
||||
XYZ[0] = XYZ[1] + self.color[1]/ 500.0
|
||||
XYZ[2] = XYZ[1] - self.color[2]/ 200.0
|
||||
|
||||
for i in range(len(XYZ)):
|
||||
if (XYZ[i] > 6./29. ): XYZ[i] = XYZ[i]**3.
|
||||
else: XYZ[i] = 108./841. * (XYZ[i] - 4./29.)
|
||||
|
||||
converted = Color('XYZ', XYZ*ref_white)
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _XYZ2CIELAB(self):
|
||||
"""
|
||||
Convert CIE XYZ to CIE Lab.
|
||||
|
||||
All values are in the range [0,1]
|
||||
from http://en.wikipedia.org/wiki/Lab_color_space,
|
||||
http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||
"""
|
||||
if self.model != 'XYZ': return
|
||||
|
||||
ref_white = np.array([.95047, 1.00000, 1.08883]) # Observer = 2, Illuminant = D65
|
||||
XYZ = self.color/ref_white
|
||||
|
||||
for i in range(len(XYZ)):
|
||||
if (XYZ[i] > 216./24389 ): XYZ[i] = XYZ[i]**(1.0/3.0)
|
||||
else: XYZ[i] = (841./108. * XYZ[i]) + 16.0/116.0
|
||||
|
||||
converted = Color('CIELAB', np.array([ 116.0 * XYZ[1] - 16.0,
|
||||
500.0 * (XYZ[0] - XYZ[1]),
|
||||
200.0 * (XYZ[1] - XYZ[2]) ]))
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _CIELAB2MSH(self):
|
||||
"""
|
||||
Convert CIE Lab to Msh colorspace.
|
||||
|
||||
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
|
||||
"""
|
||||
if self.model != 'CIELAB': return
|
||||
|
||||
Msh = np.zeros(3)
|
||||
Msh[0] = np.sqrt(np.dot(self.color,self.color))
|
||||
if (Msh[0] > 0.001):
|
||||
Msh[1] = np.arccos(self.color[0]/Msh[0])
|
||||
if (self.color[1] != 0.0):
|
||||
Msh[2] = np.arctan2(self.color[2],self.color[1])
|
||||
|
||||
converted = Color('MSH', Msh)
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
def _MSH2CIELAB(self):
|
||||
"""
|
||||
Convert Msh colorspace to CIE Lab.
|
||||
|
||||
with s,h in radians
|
||||
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
|
||||
"""
|
||||
if self.model != 'MSH': return
|
||||
|
||||
Lab = np.zeros(3)
|
||||
Lab[0] = self.color[0] * np.cos(self.color[1])
|
||||
Lab[1] = self.color[0] * np.sin(self.color[1]) * np.cos(self.color[2])
|
||||
Lab[2] = self.color[0] * np.sin(self.color[1]) * np.sin(self.color[2])
|
||||
|
||||
converted = Color('CIELAB', Lab)
|
||||
self.model = converted.model
|
||||
self.color = converted.color
|
||||
|
||||
|
||||
class Colormap:
|
||||
"""Perceptually uniform diverging or sequential colormap."""
|
||||
|
||||
__slots__ = [
|
||||
'left',
|
||||
'right',
|
||||
'interpolate',
|
||||
]
|
||||
__predefined__ = {
|
||||
'gray': {'left': Color('HSL',[0,1,1]),
|
||||
'right': Color('HSL',[0,0,0.15]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'grey': {'left': Color('HSL',[0,1,1]),
|
||||
'right': Color('HSL',[0,0,0.15]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'red': {'left': Color('HSL',[0,1,0.14]),
|
||||
'right': Color('HSL',[0,0.35,0.91]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'green': {'left': Color('HSL',[0.33333,1,0.14]),
|
||||
'right': Color('HSL',[0.33333,0.35,0.91]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'blue': {'left': Color('HSL',[0.66,1,0.14]),
|
||||
'right': Color('HSL',[0.66,0.35,0.91]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'seaweed': {'left': Color('HSL',[0.78,1.0,0.1]),
|
||||
'right': Color('HSL',[0.40000,0.1,0.9]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'bluebrown': {'left': Color('HSL',[0.65,0.53,0.49]),
|
||||
'right': Color('HSL',[0.11,0.75,0.38]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'redgreen': {'left': Color('HSL',[0.97,0.96,0.36]),
|
||||
'right': Color('HSL',[0.33333,1.0,0.14]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'bluered': {'left': Color('HSL',[0.65,0.53,0.49]),
|
||||
'right': Color('HSL',[0.97,0.96,0.36]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'blueredrainbow':{'left': Color('HSL',[2.0/3.0,1,0.5]),
|
||||
'right': Color('HSL',[0,1,0.5]),
|
||||
'interpolate': 'linear' },
|
||||
'orientation': {'left': Color('RGB',[0.933334,0.878432,0.878431]),
|
||||
'right': Color('RGB',[0.250980,0.007843,0.000000]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'strain': {'left': Color('RGB',[0.941177,0.941177,0.870588]),
|
||||
'right': Color('RGB',[0.266667,0.266667,0.000000]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
'stress': {'left': Color('RGB',[0.878432,0.874511,0.949019]),
|
||||
'right': Color('RGB',[0.000002,0.000000,0.286275]),
|
||||
'interpolate': 'perceptualuniform'},
|
||||
}
|
||||
|
||||
|
||||
def __init__(self,
|
||||
left = Color('RGB',[1,1,1]),
|
||||
right = Color('RGB',[0,0,0]),
|
||||
interpolate = 'perceptualuniform',
|
||||
predefined = None
|
||||
):
|
||||
"""
|
||||
Create a Colormap object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
left : Color
|
||||
left color (minimum value)
|
||||
right : Color
|
||||
right color (maximum value)
|
||||
interpolate : str
|
||||
interpolation scheme (either 'perceptualuniform' or 'linear')
|
||||
predefined : bool
|
||||
ignore other arguments and use predefined definition
|
||||
|
||||
"""
|
||||
if predefined is not None:
|
||||
left = self.__predefined__[predefined.lower()]['left']
|
||||
right= self.__predefined__[predefined.lower()]['right']
|
||||
interpolate = self.__predefined__[predefined.lower()]['interpolate']
|
||||
|
||||
if left.__class__.__name__ != 'Color':
|
||||
left = Color()
|
||||
if right.__class__.__name__ != 'Color':
|
||||
right = Color()
|
||||
|
||||
self.left = left
|
||||
self.right = right
|
||||
self.interpolate = interpolate
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
"""Left and right value of colormap."""
|
||||
return 'Left: %s Right: %s'%(self.left,self.right)
|
||||
|
||||
|
||||
def invert(self):
|
||||
"""Switch left/minimum with right/maximum."""
|
||||
(self.left, self.right) = (self.right, self.left)
|
||||
return self
|
||||
|
||||
|
||||
def show_predefined(self):
|
||||
"""Show the labels of the predefined colormaps."""
|
||||
print('\n'.join(self.__predefined__.keys()))
|
||||
|
||||
def color(self,fraction = 0.5):
|
||||
|
||||
def interpolate_Msh(lo, hi, frac):
|
||||
|
||||
def rad_diff(a,b):
|
||||
return abs(a[2]-b[2])
|
||||
|
||||
def adjust_hue(Msh_sat, Msh_unsat):
|
||||
"""If saturation of one of the two colors is too less than the other, hue of the less."""
|
||||
if Msh_sat[0] >= Msh_unsat[0]:
|
||||
return Msh_sat[2]
|
||||
else:
|
||||
hSpin = Msh_sat[1]/np.sin(Msh_sat[1])*np.sqrt(Msh_unsat[0]**2.0-Msh_sat[0]**2)/Msh_sat[0]
|
||||
if Msh_sat[2] < - np.pi/3.0: hSpin *= -1.0
|
||||
return Msh_sat[2] + hSpin
|
||||
|
||||
Msh1 = np.array(lo[:])
|
||||
Msh2 = np.array(hi[:])
|
||||
|
||||
if (Msh1[1] > 0.05 and Msh2[1] > 0.05 and rad_diff(Msh1,Msh2) > np.pi/3.0):
|
||||
M_mid = max(Msh1[0],Msh2[0],88.0)
|
||||
if frac < 0.5:
|
||||
Msh2 = np.array([M_mid,0.0,0.0])
|
||||
frac *= 2.0
|
||||
else:
|
||||
Msh1 = np.array([M_mid,0.0,0.0])
|
||||
frac = 2.0*frac - 1.0
|
||||
if Msh1[1] < 0.05 and Msh2[1] > 0.05: Msh1[2] = adjust_hue(Msh2,Msh1)
|
||||
elif Msh1[1] > 0.05 and Msh2[1] < 0.05: Msh2[2] = adjust_hue(Msh1,Msh2)
|
||||
Msh = (1.0 - frac) * Msh1 + frac * Msh2
|
||||
|
||||
return Color('MSH',Msh)
|
||||
|
||||
def interpolate_linear(lo, hi, frac):
|
||||
"""Linear interpolation between lo and hi color at given fraction; output in model of lo color."""
|
||||
interpolation = (1.0 - frac) * np.array(lo.color[:]) \
|
||||
+ frac * np.array(hi.express_as(lo.model).color[:])
|
||||
|
||||
return Color(lo.model,interpolation)
|
||||
|
||||
if self.interpolate == 'perceptualuniform':
|
||||
return interpolate_Msh(self.left.express_as('MSH').color,
|
||||
self.right.express_as('MSH').color,fraction)
|
||||
elif self.interpolate == 'linear':
|
||||
return interpolate_linear(self.left,self.right,fraction)
|
||||
else:
|
||||
raise NameError('unknown color interpolation method')
|
||||
|
||||
|
||||
def export(self,name = 'uniformPerceptualColorMap',\
|
||||
format = 'paraview',\
|
||||
steps = 2,\
|
||||
crop = [-1.0,1.0],
|
||||
model = 'RGB'):
|
||||
"""
|
||||
[RGB] colormap for use in paraview or gmsh, or as raw string, or array.
|
||||
|
||||
Arguments: name, format, steps, crop.
|
||||
Format is one of (paraview, gmsh, gom, raw, list).
|
||||
Crop selects a (sub)range in [-1.0,1.0].
|
||||
Generates sequential map if one limiting color is either white or black,
|
||||
diverging map otherwise.
|
||||
"""
|
||||
format = format.lower() # consistent comparison basis
|
||||
frac = 0.5*(np.array(crop) + 1.0) # rescale crop range to fractions
|
||||
colors = [self.color(float(i)/(steps-1)*(frac[1]-frac[0])+frac[0]).express_as(model).color for i in range(steps)]
|
||||
if format == 'paraview':
|
||||
colormap = [f'[\n {{\n "ColorSpace": "RGB", "Name": "{name}", "DefaultMap": true,\n "RGBPoints" : ['] \
|
||||
+ [f' {i:4d},{color[0]:8.6f},{color[1]:8.6f},{color[2]:8.6f}{"," if i+1<len(colors) else ""}' \
|
||||
for i,color in enumerate(colors)] \
|
||||
+ [' ]\n }\n]']
|
||||
|
||||
elif format == 'gmsh':
|
||||
colormap = ['View.ColorTable = {'] \
|
||||
+ [',\n'.join([','.join([str(x*255.0) for x in color]) for color in colors])] \
|
||||
+ ['}']
|
||||
|
||||
elif format == 'gom':
|
||||
colormap = [ f'1 1 {name}'
|
||||
+ f' 9 {name}'
|
||||
+ ' 0 1 0 3 0 0 -1 9 \\ 0 0 0 255 255 255 0 0 255 '
|
||||
+ f'30 NO_UNIT 1 1 64 64 64 255 1 0 0 0 0 0 0 3 0 {len(colors)}'
|
||||
+ ' '.join([f' 0 {util.srepr((255*np.array(c)).astype(int)," ")} 255 1' for c in reversed(colors)])]
|
||||
|
||||
elif format == 'raw':
|
||||
colormap = ['\t'.join(map(str,color)) for color in colors]
|
||||
|
||||
elif format == 'list':
|
||||
colormap = colors
|
||||
|
||||
else:
|
||||
raise NameError('unknown color export format')
|
||||
|
||||
return '\n'.join(colormap) + '\n' if type(colormap[0]) is str else colormap
|
|
@ -0,0 +1,40 @@
|
|||
import numpy as np
|
||||
|
||||
from damask import Colormap
|
||||
|
||||
class TestColormap:
|
||||
|
||||
def test_conversion(self):
|
||||
|
||||
specials = np.array([[0.,0.,0.],
|
||||
[1.,0.,0.],
|
||||
[0.,1.,0.],
|
||||
[0.,0.,1.],
|
||||
[1.,1.,0.],
|
||||
[0.,1.,1.],
|
||||
[1.,0.,1.],
|
||||
[1.,1.,1.]
|
||||
])
|
||||
rgbs = np.vstack((specials,np.random.rand(10000,3)))
|
||||
pass # class not integrated
|
||||
for rgb in rgbs:
|
||||
print('rgb',rgb)
|
||||
|
||||
# rgb2hsv2rgb
|
||||
assert np.allclose(Colormap._hsv2rgb(Colormap._rgb2hsv(rgb)),rgb)
|
||||
|
||||
# rgb2hsl2rgb
|
||||
assert np.allclose(Colormap._hsl2rgb(Colormap._rgb2hsl(rgb)),rgb)
|
||||
|
||||
# rgb2xyz2rgb
|
||||
xyz = Colormap._rgb2xyz(rgb)
|
||||
print('xyz',xyz)
|
||||
assert np.allclose(Colormap._xyz2rgb(xyz),rgb,atol=1.e-6,rtol=0)
|
||||
|
||||
# xyz2lab2xyz
|
||||
lab = Colormap._xyz2lab(xyz)
|
||||
print('lab',lab)
|
||||
assert np.allclose(Colormap._lab2xyz(lab),xyz)
|
||||
|
||||
# lab2msh2lab
|
||||
assert np.allclose(Colormap._msh2lab(Colormap._lab2msh(lab)),lab)
|
Loading…
Reference in New Issue