parent
c2ae657f5b
commit
1dddfa040e
|
@ -1,359 +1,355 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
class Color():
|
class Color():
|
||||||
"""Color representation in and conversion between different color-spaces."""
|
"""Color representation in and conversion between different color-spaces."""
|
||||||
|
|
||||||
__slots__ = [
|
__slots__ = [
|
||||||
'model',
|
'model',
|
||||||
'color',
|
'color',
|
||||||
'__dict__',
|
'__dict__',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
def __init__(self,
|
||||||
def __init__(self,
|
model = 'RGB',
|
||||||
model = 'RGB',
|
color = np.zeros(3,'d')):
|
||||||
color = np.zeros(3,'d')):
|
"""
|
||||||
"""
|
Create a Color object.
|
||||||
Create a Color object.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
model : string
|
model : string
|
||||||
color model
|
color model
|
||||||
color : numpy.ndarray
|
color : numpy.ndarray
|
||||||
vector representing the color according to the selected model
|
vector representing the color according to the selected model
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.__transforms__ = \
|
self.__transforms__ = \
|
||||||
{'HSV': {'index': 0, 'next': self._HSV2HSL},
|
{'HSV': {'index': 0, 'next': self._HSV2HSL},
|
||||||
'HSL': {'index': 1, 'next': self._HSL2RGB, 'prev': self._HSL2HSV},
|
'HSL': {'index': 1, 'next': self._HSL2RGB, 'prev': self._HSL2HSV},
|
||||||
'RGB': {'index': 2, 'next': self._RGB2XYZ, 'prev': self._RGB2HSL},
|
'RGB': {'index': 2, 'next': self._RGB2XYZ, 'prev': self._RGB2HSL},
|
||||||
'XYZ': {'index': 3, 'next': self._XYZ2CIELAB, 'prev': self._XYZ2RGB},
|
'XYZ': {'index': 3, 'next': self._XYZ2CIELAB, 'prev': self._XYZ2RGB},
|
||||||
'CIELAB': {'index': 4, 'next': self._CIELAB2MSH, 'prev': self._CIELAB2XYZ},
|
'CIELAB': {'index': 4, 'next': self._CIELAB2MSH, 'prev': self._CIELAB2XYZ},
|
||||||
'MSH': {'index': 5, 'prev': self._MSH2CIELAB},
|
'MSH': {'index': 5, 'prev': self._MSH2CIELAB},
|
||||||
}
|
}
|
||||||
|
|
||||||
model = model.upper()
|
model = model.upper()
|
||||||
if model not in list(self.__transforms__.keys()): model = 'RGB'
|
if model not in list(self.__transforms__.keys()): model = 'RGB'
|
||||||
if model == 'RGB' and max(color) > 1.0: # are we RGB255 ?
|
if model == 'RGB' and max(color) > 1.0: # are we RGB255 ?
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
color[i] /= 255.0 # rescale to RGB
|
color[i] /= 255.0 # rescale to RGB
|
||||||
|
|
||||||
if model == 'HSL': # are we HSL ?
|
if model == 'HSL': # are we HSL ?
|
||||||
if abs(color[0]) > 1.0: color[0] /= 360.0 # with angular hue?
|
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] >= 1.0: color[0] -= 1.0 # rewind to proper range
|
||||||
while color[0] < 0.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.model = model
|
||||||
self.color = np.array(color,'d')
|
self.color = np.array(color,'d')
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
def __repr__(self):
|
||||||
def __repr__(self):
|
"""Color model and values."""
|
||||||
"""Color model and values."""
|
return 'Model: %s Color: %s'%(self.model,str(self.color))
|
||||||
return 'Model: %s Color: %s'%(self.model,str(self.color))
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
def __str__(self):
|
||||||
def __str__(self):
|
"""Color model and values."""
|
||||||
"""Color model and values."""
|
return self.__repr__()
|
||||||
return self.__repr__()
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
def convert_to(self,toModel = 'RGB'):
|
||||||
def convertTo(self,toModel = 'RGB'):
|
"""
|
||||||
"""
|
Change the color model permanently.
|
||||||
Change the color model permanently.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
toModel : string
|
toModel : string
|
||||||
color model
|
color model
|
||||||
|
|
||||||
"""
|
"""
|
||||||
toModel = toModel.upper()
|
toModel = toModel.upper()
|
||||||
if toModel not in list(self.__transforms__.keys()): return
|
if toModel not in list(self.__transforms__.keys()): return
|
||||||
|
|
||||||
sourcePos = self.__transforms__[self.model]['index']
|
sourcePos = self.__transforms__[self.model]['index']
|
||||||
targetPos = self.__transforms__[toModel]['index']
|
targetPos = self.__transforms__[toModel]['index']
|
||||||
|
|
||||||
while sourcePos < targetPos:
|
while sourcePos < targetPos:
|
||||||
self.__transforms__[self.model]['next']()
|
self.__transforms__[self.model]['next']()
|
||||||
sourcePos += 1
|
sourcePos += 1
|
||||||
|
|
||||||
while sourcePos > targetPos:
|
while sourcePos > targetPos:
|
||||||
self.__transforms__[self.model]['prev']()
|
self.__transforms__[self.model]['prev']()
|
||||||
sourcePos -= 1
|
sourcePos -= 1
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
def express_as(self,asModel = 'RGB'):
|
||||||
def expressAs(self,asModel = 'RGB'):
|
"""
|
||||||
"""
|
Return the color in a different model.
|
||||||
Return the color in a different model.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
asModel : string
|
asModel : string
|
||||||
color model
|
color model
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.__class__(self.model,self.color).convertTo(asModel)
|
return self.__class__(self.model,self.color).convert_to(asModel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _HSV2HSL(self):
|
def _HSV2HSL(self):
|
||||||
"""
|
"""
|
||||||
Convert H(ue) S(aturation) V(alue or brightness) to H(ue) S(aturation) L(uminance).
|
Convert H(ue) S(aturation) V(alue or brightness) to H(ue) S(aturation) L(uminance).
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
http://codeitdown.com/hsl-hsb-hsv-color
|
http://codeitdown.com/hsl-hsb-hsv-color
|
||||||
"""
|
"""
|
||||||
if self.model != 'HSV': return
|
if self.model != 'HSV': return
|
||||||
|
|
||||||
converted = Color('HSL',np.array([
|
converted = Color('HSL',np.array([
|
||||||
self.color[0],
|
self.color[0],
|
||||||
1. if self.color[2] == 0.0 or (self.color[1] == 0.0 and self.color[2] == 1.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.)),
|
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]),
|
0.5*self.color[2]*(2.-self.color[1]),
|
||||||
]))
|
]))
|
||||||
|
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
self.color = converted.color
|
||||||
|
|
||||||
|
|
||||||
def _HSL2HSV(self):
|
def _HSL2HSV(self):
|
||||||
"""
|
"""
|
||||||
Convert H(ue) S(aturation) L(uminance) to H(ue) S(aturation) V(alue or brightness).
|
Convert H(ue) S(aturation) L(uminance) to H(ue) S(aturation) V(alue or brightness).
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
http://codeitdown.com/hsl-hsb-hsv-color
|
http://codeitdown.com/hsl-hsb-hsv-color
|
||||||
"""
|
"""
|
||||||
if self.model != 'HSL': return
|
if self.model != 'HSL': return
|
||||||
|
|
||||||
h = self.color[0]
|
h = self.color[0]
|
||||||
b = self.color[2]+0.5*(self.color[1]*(1.-abs(2*self.color[2]-1)))
|
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
|
s = 1.0 if b == 0.0 else 2.*(b-self.color[2])/b
|
||||||
|
|
||||||
converted = Color('HSV',np.array([h,s,b]))
|
converted = Color('HSV',np.array([h,s,b]))
|
||||||
|
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
self.color = converted.color
|
||||||
|
|
||||||
|
|
||||||
def _HSL2RGB(self):
|
def _HSL2RGB(self):
|
||||||
"""
|
"""
|
||||||
Convert H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).
|
Convert H(ue) S(aturation) L(uminance) to R(red) G(reen) B(lue).
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
from http://en.wikipedia.org/wiki/HSL_and_HSV
|
from http://en.wikipedia.org/wiki/HSL_and_HSV
|
||||||
"""
|
"""
|
||||||
if self.model != 'HSL': return
|
if self.model != 'HSL': return
|
||||||
|
|
||||||
sextant = self.color[0]*6.0
|
sextant = self.color[0]*6.0
|
||||||
c = (1.0 - abs(2.0 * self.color[2] - 1.0))*self.color[1]
|
c = (1.0 - abs(2.0 * self.color[2] - 1.0))*self.color[1]
|
||||||
x = c*(1.0 - abs(sextant%2 - 1.0))
|
x = c*(1.0 - abs(sextant%2 - 1.0))
|
||||||
m = self.color[2] - 0.5*c
|
m = self.color[2] - 0.5*c
|
||||||
|
|
||||||
converted = Color('RGB',np.array([
|
converted = Color('RGB',np.array([
|
||||||
[c+m, x+m, m],
|
[c+m, x+m, m],
|
||||||
[x+m, c+m, m],
|
[x+m, c+m, m],
|
||||||
[m, c+m, x+m],
|
[m, c+m, x+m],
|
||||||
[m, x+m, c+m],
|
[m, x+m, c+m],
|
||||||
[x+m, m, c+m],
|
[x+m, m, c+m],
|
||||||
[c+m, m, x+m],
|
[c+m, m, x+m],
|
||||||
][int(sextant)],'d'))
|
][int(sextant)],'d'))
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
self.color = converted.color
|
||||||
|
|
||||||
|
|
||||||
def _RGB2HSL(self):
|
def _RGB2HSL(self):
|
||||||
"""
|
"""
|
||||||
Convert R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).
|
Convert R(ed) G(reen) B(lue) to H(ue) S(aturation) L(uminance).
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
from http://130.113.54.154/~monger/hsl-rgb.html
|
from http://130.113.54.154/~monger/hsl-rgb.html
|
||||||
"""
|
"""
|
||||||
if self.model != 'RGB': return
|
if self.model != 'RGB': return
|
||||||
|
|
||||||
HSL = np.zeros(3,'d')
|
HSL = np.zeros(3,'d')
|
||||||
maxcolor = self.color.max()
|
maxcolor = self.color.max()
|
||||||
mincolor = self.color.min()
|
mincolor = self.color.min()
|
||||||
HSL[2] = (maxcolor + mincolor)/2.0
|
HSL[2] = (maxcolor + mincolor)/2.0
|
||||||
if(mincolor == maxcolor):
|
if(mincolor == maxcolor):
|
||||||
HSL[0] = 0.0
|
HSL[0] = 0.0
|
||||||
HSL[1] = 0.0
|
HSL[1] = 0.0
|
||||||
else:
|
|
||||||
if (HSL[2]<0.5):
|
|
||||||
HSL[1] = (maxcolor - mincolor)/(maxcolor + mincolor)
|
|
||||||
else:
|
else:
|
||||||
HSL[1] = (maxcolor - mincolor)/(2.0 - maxcolor - mincolor)
|
if (HSL[2]<0.5):
|
||||||
if (maxcolor == self.color[0]):
|
HSL[1] = (maxcolor - mincolor)/(maxcolor + mincolor)
|
||||||
HSL[0] = 0.0 + (self.color[1] - self.color[2])/(maxcolor - mincolor)
|
else:
|
||||||
elif (maxcolor == self.color[1]):
|
HSL[1] = (maxcolor - mincolor)/(2.0 - maxcolor - mincolor)
|
||||||
HSL[0] = 2.0 + (self.color[2] - self.color[0])/(maxcolor - mincolor)
|
if (maxcolor == self.color[0]):
|
||||||
elif (maxcolor == self.color[2]):
|
HSL[0] = 0.0 + (self.color[1] - self.color[2])/(maxcolor - mincolor)
|
||||||
HSL[0] = 4.0 + (self.color[0] - self.color[1])/(maxcolor - mincolor)
|
elif (maxcolor == self.color[1]):
|
||||||
HSL[0] = HSL[0]*60.0 # scaling to 360 might be dangerous for small values
|
HSL[0] = 2.0 + (self.color[2] - self.color[0])/(maxcolor - mincolor)
|
||||||
if (HSL[0] < 0.0):
|
elif (maxcolor == self.color[2]):
|
||||||
HSL[0] = HSL[0] + 360.0
|
HSL[0] = 4.0 + (self.color[0] - self.color[1])/(maxcolor - mincolor)
|
||||||
for i in range(2):
|
HSL[0] = HSL[0]*60.0 # scaling to 360 might be dangerous for small values
|
||||||
HSL[i+1] = min(HSL[i+1],1.0)
|
if (HSL[0] < 0.0):
|
||||||
HSL[i+1] = max(HSL[i+1],0.0)
|
HSL[0] = HSL[0] + 360.0
|
||||||
|
for i in range(2):
|
||||||
|
HSL[i+1] = min(HSL[i+1],1.0)
|
||||||
|
HSL[i+1] = max(HSL[i+1],0.0)
|
||||||
|
|
||||||
converted = Color('HSL', HSL)
|
converted = Color('HSL', HSL)
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
self.color = converted.color
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _RGB2XYZ(self):
|
def _RGB2XYZ(self):
|
||||||
"""
|
"""
|
||||||
Convert R(ed) G(reen) B(lue) to CIE XYZ.
|
Convert R(ed) G(reen) B(lue) to CIE XYZ.
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
from http://www.cs.rit.edu/~ncs/color/t_convert.html
|
from http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||||
"""
|
"""
|
||||||
if self.model != 'RGB': return
|
if self.model != 'RGB': return
|
||||||
|
|
||||||
XYZ = np.zeros(3,'d')
|
XYZ = np.zeros(3,'d')
|
||||||
RGB_lin = np.zeros(3,'d')
|
RGB_lin = np.zeros(3,'d')
|
||||||
convert = np.array([[0.412453,0.357580,0.180423],
|
convert = np.array([[0.412453,0.357580,0.180423],
|
||||||
[0.212671,0.715160,0.072169],
|
[0.212671,0.715160,0.072169],
|
||||||
[0.019334,0.119193,0.950227]])
|
[0.019334,0.119193,0.950227]])
|
||||||
|
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
if (self.color[i] > 0.04045): RGB_lin[i] = ((self.color[i]+0.0555)/1.0555)**2.4
|
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
|
else: RGB_lin[i] = self.color[i] /12.92
|
||||||
XYZ = np.dot(convert,RGB_lin)
|
XYZ = np.dot(convert,RGB_lin)
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
|
|
||||||
XYZ[i] = max(XYZ[i],0.0)
|
XYZ[i] = max(XYZ[i],0.0)
|
||||||
|
|
||||||
converted = Color('XYZ', XYZ)
|
converted = Color('XYZ', XYZ)
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
self.color = converted.color
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _XYZ2RGB(self):
|
def _XYZ2RGB(self):
|
||||||
"""
|
"""
|
||||||
Convert CIE XYZ to R(ed) G(reen) B(lue).
|
Convert CIE XYZ to R(ed) G(reen) B(lue).
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
from http://www.cs.rit.edu/~ncs/color/t_convert.html
|
from http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||||
"""
|
"""
|
||||||
if self.model != 'XYZ':
|
if self.model != 'XYZ':
|
||||||
return
|
return
|
||||||
|
|
||||||
convert = np.array([[ 3.240479,-1.537150,-0.498535],
|
convert = np.array([[ 3.240479,-1.537150,-0.498535],
|
||||||
[-0.969256, 1.875992, 0.041556],
|
[-0.969256, 1.875992, 0.041556],
|
||||||
[ 0.055648,-0.204043, 1.057311]])
|
[ 0.055648,-0.204043, 1.057311]])
|
||||||
RGB_lin = np.dot(convert,self.color)
|
RGB_lin = np.dot(convert,self.color)
|
||||||
RGB = np.zeros(3,'d')
|
RGB = np.zeros(3,'d')
|
||||||
|
|
||||||
for i in range(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
|
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
|
else: RGB[i] = RGB_lin[i] *12.92
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
RGB[i] = min(RGB[i],1.0)
|
RGB[i] = min(RGB[i],1.0)
|
||||||
RGB[i] = max(RGB[i],0.0)
|
RGB[i] = max(RGB[i],0.0)
|
||||||
|
|
||||||
maxVal = max(RGB) # clipping colors according to the display gamut
|
maxVal = max(RGB) # clipping colors according to the display gamut
|
||||||
if (maxVal > 1.0): RGB /= maxVal
|
if (maxVal > 1.0): RGB /= maxVal
|
||||||
|
|
||||||
converted = Color('RGB', RGB)
|
converted = Color('RGB', RGB)
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
self.color = converted.color
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _CIELAB2XYZ(self):
|
def _CIELAB2XYZ(self):
|
||||||
"""
|
"""
|
||||||
Convert CIE Lab to CIE XYZ.
|
Convert CIE Lab to CIE XYZ.
|
||||||
|
|
||||||
All values are in the range [0,1]
|
All values are in the range [0,1]
|
||||||
from http://www.easyrgb.com/index.php?X=MATH&H=07#text7
|
from http://www.easyrgb.com/index.php?X=MATH&H=07#text7
|
||||||
"""
|
"""
|
||||||
if self.model != 'CIELAB': return
|
if self.model != 'CIELAB': return
|
||||||
|
|
||||||
ref_white = np.array([.95047, 1.00000, 1.08883],'d') # Observer = 2, Illuminant = D65
|
ref_white = np.array([.95047, 1.00000, 1.08883],'d') # Observer = 2, Illuminant = D65
|
||||||
XYZ = np.zeros(3,'d')
|
XYZ = np.zeros(3,'d')
|
||||||
|
|
||||||
XYZ[1] = (self.color[0] + 16.0 ) / 116.0
|
XYZ[1] = (self.color[0] + 16.0 ) / 116.0
|
||||||
XYZ[0] = XYZ[1] + self.color[1]/ 500.0
|
XYZ[0] = XYZ[1] + self.color[1]/ 500.0
|
||||||
XYZ[2] = XYZ[1] - self.color[2]/ 200.0
|
XYZ[2] = XYZ[1] - self.color[2]/ 200.0
|
||||||
|
|
||||||
for i in range(len(XYZ)):
|
for i in range(len(XYZ)):
|
||||||
if (XYZ[i] > 6./29. ): XYZ[i] = XYZ[i]**3.
|
if (XYZ[i] > 6./29. ): XYZ[i] = XYZ[i]**3.
|
||||||
else: XYZ[i] = 108./841. * (XYZ[i] - 4./29.)
|
else: XYZ[i] = 108./841. * (XYZ[i] - 4./29.)
|
||||||
|
|
||||||
converted = Color('XYZ', XYZ*ref_white)
|
converted = Color('XYZ', XYZ*ref_white)
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
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],'d') # 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):
|
def _XYZ2CIELAB(self):
|
||||||
"""
|
"""
|
||||||
Convert CIE Lab to Msh colorspace.
|
Convert CIE XYZ to CIE Lab.
|
||||||
|
|
||||||
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
|
All values are in the range [0,1]
|
||||||
"""
|
from http://en.wikipedia.org/wiki/Lab_color_space,
|
||||||
if self.model != 'CIELAB': return
|
http://www.cs.rit.edu/~ncs/color/t_convert.html
|
||||||
|
"""
|
||||||
|
if self.model != 'XYZ': return
|
||||||
|
|
||||||
Msh = np.zeros(3,'d')
|
ref_white = np.array([.95047, 1.00000, 1.08883],'d') # Observer = 2, Illuminant = D65
|
||||||
Msh[0] = np.sqrt(np.dot(self.color,self.color))
|
XYZ = self.color/ref_white
|
||||||
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)
|
for i in range(len(XYZ)):
|
||||||
self.model = converted.model
|
if (XYZ[i] > 216./24389 ): XYZ[i] = XYZ[i]**(1.0/3.0)
|
||||||
self.color = converted.color
|
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 _MSH2CIELAB(self):
|
def _CIELAB2MSH(self):
|
||||||
"""
|
"""
|
||||||
Convert Msh colorspace to CIE Lab.
|
Convert CIE Lab to Msh colorspace.
|
||||||
|
|
||||||
with s,h in radians
|
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
|
||||||
from http://www.cs.unm.edu/~kmorel/documents/ColorMaps/DivergingColorMapWorkshop.xls
|
"""
|
||||||
"""
|
if self.model != 'CIELAB': return
|
||||||
if self.model != 'MSH': return
|
|
||||||
|
|
||||||
Lab = np.zeros(3,'d')
|
Msh = np.zeros(3,'d')
|
||||||
Lab[0] = self.color[0] * np.cos(self.color[1])
|
Msh[0] = np.sqrt(np.dot(self.color,self.color))
|
||||||
Lab[1] = self.color[0] * np.sin(self.color[1]) * np.cos(self.color[2])
|
if (Msh[0] > 0.001):
|
||||||
Lab[2] = self.color[0] * np.sin(self.color[1]) * np.sin(self.color[2])
|
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('CIELAB', Lab)
|
converted = Color('MSH', Msh)
|
||||||
self.model = converted.model
|
self.model = converted.model
|
||||||
self.color = converted.color
|
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,'d')
|
||||||
|
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():
|
class Colormap():
|
||||||
|
@ -498,13 +494,13 @@ class Colormap():
|
||||||
def interpolate_linear(lo, hi, frac):
|
def interpolate_linear(lo, hi, frac):
|
||||||
"""Linear interpolation between lo and hi color at given fraction; output in model of lo color."""
|
"""Linear interpolation between lo and hi color at given fraction; output in model of lo color."""
|
||||||
interpolation = (1.0 - frac) * np.array(lo.color[:]) \
|
interpolation = (1.0 - frac) * np.array(lo.color[:]) \
|
||||||
+ frac * np.array(hi.expressAs(lo.model).color[:])
|
+ frac * np.array(hi.express_as(lo.model).color[:])
|
||||||
|
|
||||||
return Color(lo.model,interpolation)
|
return Color(lo.model,interpolation)
|
||||||
|
|
||||||
if self.interpolate == 'perceptualuniform':
|
if self.interpolate == 'perceptualuniform':
|
||||||
return interpolate_Msh(self.left.expressAs('MSH').color,
|
return interpolate_Msh(self.left.express_as('MSH').color,
|
||||||
self.right.expressAs('MSH').color,fraction)
|
self.right.express_as('MSH').color,fraction)
|
||||||
elif self.interpolate == 'linear':
|
elif self.interpolate == 'linear':
|
||||||
return interpolate_linear(self.left,
|
return interpolate_linear(self.left,
|
||||||
self.right,fraction)
|
self.right,fraction)
|
||||||
|
@ -528,7 +524,7 @@ class Colormap():
|
||||||
"""
|
"""
|
||||||
format = format.lower() # consistent comparison basis
|
format = format.lower() # consistent comparison basis
|
||||||
frac = 0.5*(np.array(crop) + 1.0) # rescale crop range to fractions
|
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]).expressAs(model).color for i in range(steps)]
|
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':
|
if format == 'paraview':
|
||||||
colormap = ['[\n {{\n "ColorSpace": "RGB", "Name": "{}", "DefaultMap": true,\n "RGBPoints" : ['.format(name)] \
|
colormap = ['[\n {{\n "ColorSpace": "RGB", "Name": "{}", "DefaultMap": true,\n "RGBPoints" : ['.format(name)] \
|
||||||
+ [' {:4d},{:8.6f},{:8.6f},{:8.6f},'.format(i,color[0],color[1],color[2],) \
|
+ [' {:4d},{:8.6f},{:8.6f},{:8.6f},'.format(i,color[0],color[1],color[2],) \
|
||||||
|
|
|
@ -38,192 +38,193 @@ class bcolors:
|
||||||
self.CROSSOUT = ''
|
self.CROSSOUT = ''
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def srepr(arg,glue = '\n'):
|
def srepr(arg,glue = '\n'):
|
||||||
"""
|
"""
|
||||||
Join arguments as individual lines.
|
Join arguments as individual lines.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
arg : iterable
|
arg : iterable
|
||||||
Items to join.
|
Items to join.
|
||||||
glue : str, optional
|
glue : str, optional
|
||||||
Defaults to \n.
|
Defaults to \n.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if (not hasattr(arg, "strip") and
|
||||||
|
(hasattr(arg, "__getitem__") or
|
||||||
|
hasattr(arg, "__iter__"))):
|
||||||
|
return glue.join(str(x) for x in arg)
|
||||||
|
return arg if isinstance(arg,str) else repr(arg)
|
||||||
|
|
||||||
"""
|
|
||||||
if (not hasattr(arg, "strip") and
|
|
||||||
(hasattr(arg, "__getitem__") or
|
|
||||||
hasattr(arg, "__iter__"))):
|
|
||||||
return glue.join(str(x) for x in arg)
|
|
||||||
return arg if isinstance(arg,str) else repr(arg)
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def croak(what, newline = True):
|
def croak(what, newline = True):
|
||||||
"""
|
"""
|
||||||
Write formated to stderr.
|
Write formated to stderr.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
what : str or iterable
|
what : str or iterable
|
||||||
Content to be displayed
|
Content to be displayed
|
||||||
newline : bool, optional
|
newline : bool, optional
|
||||||
Separate items of what by newline. Defaults to True.
|
Separate items of what by newline. Defaults to True.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not what:
|
||||||
|
sys.stderr.write(srepr(what,glue = '\n') + ('\n' if newline else ''))
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
"""
|
|
||||||
if what is not None:
|
|
||||||
sys.stderr.write(srepr(what,glue = '\n') + ('\n' if newline else ''))
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def report(who = None,
|
def report(who = None,
|
||||||
what = None):
|
what = None):
|
||||||
"""
|
"""
|
||||||
Reports script and file name.
|
Reports script and file name.
|
||||||
|
|
||||||
DEPRECATED
|
DEPRECATED
|
||||||
|
|
||||||
"""
|
"""
|
||||||
croak( (emph(who)+': ' if who is not None else '') + (what if what is not None else '') + '\n' )
|
croak( (emph(who)+': ' if who is not None else '') + (what if what is not None else '') + '\n' )
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def emph(what):
|
def emph(what):
|
||||||
"""Formats string with emphasis."""
|
"""Formats string with emphasis."""
|
||||||
return bcolors.BOLD+srepr(what)+bcolors.ENDC
|
return bcolors.BOLD+srepr(what)+bcolors.ENDC
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def deemph(what):
|
def deemph(what):
|
||||||
"""Formats string with deemphasis."""
|
"""Formats string with deemphasis."""
|
||||||
return bcolors.DIM+srepr(what)+bcolors.ENDC
|
return bcolors.DIM+srepr(what)+bcolors.ENDC
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def delete(what):
|
def delete(what):
|
||||||
"""Formats string as deleted."""
|
"""Formats string as deleted."""
|
||||||
return bcolors.DIM+srepr(what)+bcolors.ENDC
|
return bcolors.DIM+srepr(what)+bcolors.ENDC
|
||||||
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def strikeout(what):
|
def strikeout(what):
|
||||||
"""Formats string as strikeout."""
|
"""Formats string as strikeout."""
|
||||||
return bcolors.CROSSOUT+srepr(what)+bcolors.ENDC
|
return bcolors.CROSSOUT+srepr(what)+bcolors.ENDC
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
def execute(cmd,
|
def execute(cmd,
|
||||||
streamIn = None,
|
streamIn = None,
|
||||||
wd = './',
|
wd = './',
|
||||||
env = None):
|
env = None):
|
||||||
"""
|
|
||||||
Execute command.
|
|
||||||
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
cmd : str
|
|
||||||
Command to be executed.
|
|
||||||
streanIn :, optional
|
|
||||||
Input (via pipe) for executed process.
|
|
||||||
wd : str, optional
|
|
||||||
Working directory of process. Defaults to ./ .
|
|
||||||
env :
|
|
||||||
Environment
|
|
||||||
|
|
||||||
"""
|
|
||||||
initialPath = os.getcwd()
|
|
||||||
os.chdir(wd)
|
|
||||||
myEnv = os.environ if env is None else env
|
|
||||||
process = subprocess.Popen(shlex.split(cmd),
|
|
||||||
stdout = subprocess.PIPE,
|
|
||||||
stderr = subprocess.PIPE,
|
|
||||||
stdin = subprocess.PIPE,
|
|
||||||
env = myEnv)
|
|
||||||
out,error = [i for i in (process.communicate() if streamIn is None
|
|
||||||
else process.communicate(streamIn.read().encode('utf-8')))]
|
|
||||||
out = out.decode('utf-8').replace('\x08','')
|
|
||||||
error = error.decode('utf-8').replace('\x08','')
|
|
||||||
os.chdir(initialPath)
|
|
||||||
if process.returncode != 0: raise RuntimeError('{} failed with returncode {}'.format(cmd,process.returncode))
|
|
||||||
return out,error
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
class extendableOption(Option):
|
|
||||||
"""
|
|
||||||
Used for definition of new option parser action 'extend', which enables to take multiple option arguments.
|
|
||||||
|
|
||||||
Adopted from online tutorial http://docs.python.org/library/optparse.html
|
|
||||||
DEPRECATED
|
|
||||||
"""
|
|
||||||
|
|
||||||
ACTIONS = Option.ACTIONS + ("extend",)
|
|
||||||
STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
|
|
||||||
TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
|
|
||||||
ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)
|
|
||||||
|
|
||||||
def take_action(self, action, dest, opt, value, values, parser):
|
|
||||||
if action == "extend":
|
|
||||||
lvalue = value.split(",")
|
|
||||||
values.ensure_value(dest, []).extend(lvalue)
|
|
||||||
else:
|
|
||||||
Option.take_action(self, action, dest, opt, value, values, parser)
|
|
||||||
|
|
||||||
# from https://gist.github.com/aubricus/f91fb55dc6ba5557fbab06119420dd6a
|
|
||||||
def progressBar(iteration, total, prefix='', bar_length=50):
|
|
||||||
"""
|
|
||||||
Call in a loop to create terminal progress bar.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
iteration : int
|
|
||||||
Current iteration.
|
|
||||||
total : int
|
|
||||||
Total iterations.
|
|
||||||
prefix : str, optional
|
|
||||||
Prefix string.
|
|
||||||
bar_length : int, optional
|
|
||||||
Character length of bar. Defaults to 50.
|
|
||||||
"""
|
|
||||||
fraction = iteration / float(total)
|
|
||||||
if not hasattr(progressBar, "last_fraction"): # first call to function
|
|
||||||
progressBar.start_time = time.time()
|
|
||||||
progressBar.last_fraction = -1.0
|
|
||||||
remaining_time = ' n/a'
|
|
||||||
else:
|
|
||||||
if fraction <= progressBar.last_fraction or iteration == 0: # reset: called within a new loop
|
|
||||||
progressBar.start_time = time.time()
|
|
||||||
progressBar.last_fraction = -1.0
|
|
||||||
remaining_time = ' n/a'
|
|
||||||
else:
|
|
||||||
progressBar.last_fraction = fraction
|
|
||||||
remainder = (total - iteration) * (time.time()-progressBar.start_time)/iteration
|
|
||||||
remaining_time = '{: 3d}:'.format(int( remainder//3600)) + \
|
|
||||||
'{:02d}:'.format(int((remainder//60)%60)) + \
|
|
||||||
'{:02d}' .format(int( remainder %60))
|
|
||||||
|
|
||||||
filled_length = int(round(bar_length * fraction))
|
|
||||||
bar = '█' * filled_length + '░' * (bar_length - filled_length)
|
|
||||||
|
|
||||||
sys.stderr.write('\r{} {} {}'.format(prefix, bar, remaining_time)),
|
|
||||||
|
|
||||||
if iteration == total: sys.stderr.write('\n')
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
|
|
||||||
class return_message():
|
|
||||||
"""Object with formatted return message."""
|
|
||||||
|
|
||||||
def __init__(self,message):
|
|
||||||
"""
|
"""
|
||||||
Sets return message.
|
Execute command.
|
||||||
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
message : str or list of str
|
cmd : str
|
||||||
message for output to screen
|
Command to be executed.
|
||||||
|
streanIn :, optional
|
||||||
|
Input (via pipe) for executed process.
|
||||||
|
wd : str, optional
|
||||||
|
Working directory of process. Defaults to ./ .
|
||||||
|
env :
|
||||||
|
Environment
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.message = message
|
initialPath = os.getcwd()
|
||||||
|
os.chdir(wd)
|
||||||
|
myEnv = os.environ if env is None else env
|
||||||
|
process = subprocess.Popen(shlex.split(cmd),
|
||||||
|
stdout = subprocess.PIPE,
|
||||||
|
stderr = subprocess.PIPE,
|
||||||
|
stdin = subprocess.PIPE,
|
||||||
|
env = myEnv)
|
||||||
|
out,error = [i for i in (process.communicate() if streamIn is None
|
||||||
|
else process.communicate(streamIn.read().encode('utf-8')))]
|
||||||
|
out = out.decode('utf-8').replace('\x08','')
|
||||||
|
error = error.decode('utf-8').replace('\x08','')
|
||||||
|
os.chdir(initialPath)
|
||||||
|
if process.returncode != 0:
|
||||||
|
raise RuntimeError('{} failed with returncode {}'.format(cmd,process.returncode))
|
||||||
|
return out,error
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
"""Return message suitable for interactive shells."""
|
class extendableOption(Option):
|
||||||
return srepr(self.message)
|
"""
|
||||||
|
Used for definition of new option parser action 'extend', which enables to take multiple option arguments.
|
||||||
|
|
||||||
|
Adopted from online tutorial http://docs.python.org/library/optparse.html
|
||||||
|
DEPRECATED
|
||||||
|
"""
|
||||||
|
|
||||||
|
ACTIONS = Option.ACTIONS + ("extend",)
|
||||||
|
STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
|
||||||
|
TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
|
||||||
|
ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)
|
||||||
|
|
||||||
|
def take_action(self, action, dest, opt, value, values, parser):
|
||||||
|
if action == "extend":
|
||||||
|
lvalue = value.split(",")
|
||||||
|
values.ensure_value(dest, []).extend(lvalue)
|
||||||
|
else:
|
||||||
|
Option.take_action(self, action, dest, opt, value, values, parser)
|
||||||
|
|
||||||
|
|
||||||
|
def progressBar(iteration, total, prefix='', bar_length=50):
|
||||||
|
"""
|
||||||
|
Call in a loop to create terminal progress bar.
|
||||||
|
|
||||||
|
From https://gist.github.com/aubricus/f91fb55dc6ba5557fbab06119420dd6a
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
iteration : int
|
||||||
|
Current iteration.
|
||||||
|
total : int
|
||||||
|
Total iterations.
|
||||||
|
prefix : str, optional
|
||||||
|
Prefix string.
|
||||||
|
bar_length : int, optional
|
||||||
|
Character length of bar. Defaults to 50.
|
||||||
|
"""
|
||||||
|
fraction = iteration / float(total)
|
||||||
|
if not hasattr(progressBar, "last_fraction"): # first call to function
|
||||||
|
progressBar.start_time = time.time()
|
||||||
|
progressBar.last_fraction = -1.0
|
||||||
|
remaining_time = ' n/a'
|
||||||
|
else:
|
||||||
|
if fraction <= progressBar.last_fraction or iteration == 0: # reset: called within a new loop
|
||||||
|
progressBar.start_time = time.time()
|
||||||
|
progressBar.last_fraction = -1.0
|
||||||
|
remaining_time = ' n/a'
|
||||||
|
else:
|
||||||
|
progressBar.last_fraction = fraction
|
||||||
|
remainder = (total - iteration) * (time.time()-progressBar.start_time)/iteration
|
||||||
|
remaining_time = '{: 3d}:'.format(int( remainder//3600)) + \
|
||||||
|
'{:02d}:'.format(int((remainder//60)%60)) + \
|
||||||
|
'{:02d}' .format(int( remainder %60))
|
||||||
|
|
||||||
|
filled_length = int(round(bar_length * fraction))
|
||||||
|
bar = '█' * filled_length + '░' * (bar_length - filled_length)
|
||||||
|
|
||||||
|
sys.stderr.write('\r{} {} {}'.format(prefix, bar, remaining_time)),
|
||||||
|
|
||||||
|
if iteration == total:
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
sys.stderr.flush()
|
||||||
|
|
||||||
|
|
||||||
|
class return_message():
|
||||||
|
"""Object with formatted return message."""
|
||||||
|
|
||||||
|
def __init__(self,message):
|
||||||
|
"""
|
||||||
|
Sets return message.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
message : str or list of str
|
||||||
|
message for output to screen
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Return message suitable for interactive shells."""
|
||||||
|
return srepr(self.message)
|
||||||
|
|
||||||
|
|
||||||
class ThreadPool:
|
class ThreadPool:
|
||||||
|
|
Loading…
Reference in New Issue