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 ._environment import Environment # noqa
|
||||||
from ._table import Table # noqa
|
from ._table import Table # noqa
|
||||||
from ._vtk import VTK # noqa
|
from ._vtk import VTK # noqa
|
||||||
from ._colormaps import Colormap, Color # noqa
|
from ._colormap import Colormap # noqa
|
||||||
from ._rotation import Rotation # noqa
|
from ._rotation import Rotation # noqa
|
||||||
from ._lattice import Symmetry, Lattice# noqa
|
from ._lattice import Symmetry, Lattice# noqa
|
||||||
from ._orientation import Orientation # 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