Skip to content

Commit b6334e4

Browse files
committed
Further reworking of viscm interface
1 parent c8b6b8e commit b6334e4

2 files changed

Lines changed: 176 additions & 137 deletions

File tree

pycam02ucs/cam02ucs.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ def deltaEp_JMh(self, JMh1, JMh2):
6060
+ (bp1 - bp2) ** 2
6161
)
6262

63+
def deltaEp_sRGB(self, RGB1, RGB2):
64+
XYZ1 = sRGB_to_XYZ(RGB1)
65+
XYZ2 = sRGB_to_XYZ(RGB2)
66+
JMh1 = _XYZ_to_JMh(XYZ1)
67+
JMh2 = _XYZ_to_JMh(XYZ2)
68+
return self.deltaEp_JMh(JMh1, JMh2)
69+
6370
UCS_space = LuoUniformSpace(1.00, 0.007, 0.0228)
6471
LCD_space = LuoUniformSpace(1.24, 0.007, 0.0363)
6572
SCD_space = LuoUniformSpace(0.77, 0.007, 0.0053)

pycam02ucs/cm/viscm.py

Lines changed: 169 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,24 @@
2424
pass
2525

2626
from pycam02ucs import ViewingConditions
27-
from pycam02ucs.cam02ucs import deltaEp_sRGB, UCS_space
27+
from pycam02ucs.cam02ucs import UCS_space, SCD_space, LCD_space
2828
from pycam02ucs.srgb import sRGB_to_XYZ, XYZ_to_sRGB
2929
from pycam02ucs.cm.minimvc import Trigger
3030

31+
# Our preferred space (mostly here so we can easily tweak it when curious)
32+
UNIFORM_SPACE = UCS_space
3133

3234
def _sRGB_to_CIECAM02(RGB):
3335
XYZ = sRGB_to_XYZ(RGB)
3436
return ViewingConditions.sRGB.XYZ_to_CIECAM02(XYZ)
3537

36-
3738
def _CIECAM02_to_JKapbp(ciecam02):
3839
JMh = np.column_stack((ciecam02.J, ciecam02.M, ciecam02.h))
39-
return UCS_space.JMh_to_JKapbp(JMh)
40+
return UNIFORM_SPACE.JMh_to_JKapbp(JMh)
4041

4142

4243
def _JKapbp_to_JMh(JKapbp):
43-
return UCS_space.JKapbp_to_JMh(JKapbp)
44+
return UNIFORM_SPACE.JKapbp_to_JMh(JKapbp)
4445

4546

4647
def _JMh_to_sRGB(JMh):
@@ -148,160 +149,191 @@ def is_gray(self):
148149
return False
149150

150151
def _vis_axes():
151-
grid = GridSpec(10, 8,
152+
grid = GridSpec(10, 4,
152153
left=0.02,
153154
right=0.98,
154155
bottom=0.02,
155-
width_ratios=[1, 1, 1, 1, 1, 1, 2, 2],
156+
width_ratios=[1] * 4,
156157
height_ratios=[1] * 10)
157-
axes = {'cmap': grid[0, :3],
158-
'deltas': grid[1:4, :3],
159-
'deuteranomaly': grid[0, 3:6],
160-
'deuteranopia': grid[1, 3:6],
161-
'protanomaly': grid[2, 3:6],
162-
'protanopia': grid[3, 3:6],
163-
'lightness': grid[4:6, :2],
164-
'colourfulness': grid[4:6, 2:4],
165-
'hue': grid[4:6, 4:6],
166-
167-
'image0': grid[0:2, 6],
168-
'image0-cb': grid[0:2, 7],
169-
'image1': grid[2:4, 6],
170-
'image1-cb': grid[2:4, 7],
171-
'image2': grid[4:6, 6],
172-
'image2-cb': grid[4:6, 7],
158+
axes = {'cmap': grid[0, 0],
159+
'deltas': grid[1:4, 0],
160+
161+
'cmap-greyscale': grid[0, 1],
162+
'lightness-deltas': grid[1:4, 1],
163+
164+
'deuteranomaly': grid[4, 0],
165+
'deuteranopia': grid[5, 0],
166+
'protanomaly': grid[4, 1],
167+
'protanopia': grid[5, 1],
168+
169+
# 'lightness': grid[4:6, 1],
170+
# 'colourfulness': grid[4:6, 2],
171+
# 'hue': grid[4:6, 3],
172+
173+
'image0': grid[0:3, 2],
174+
'image0-cb': grid[0:3, 3],
175+
'image1': grid[3:6, 2],
176+
'image1-cb': grid[3:6, 3],
177+
'image2': grid[6:9, 2],
178+
'image2-cb': grid[6:9, 3],
179+
180+
'gamut-checkbox': grid[9, 2],
173181
}
174182

175183
axes = {key: plt.subplot(value) for (key, value) in axes.items()}
176-
axes['gamut'] = plt.subplot(grid[6:, :6], projection='3d')
184+
axes['gamut'] = plt.subplot(grid[6:, :2], projection='3d')
177185

178186
return axes
179187

180188

181189
# N=256 matches the default quantization for LinearSegmentedColormap, which
182190
# reduces quantization/aliasing artifacts (esp. in the perceptual deltas
183191
# plot).
184-
def viscm(cm, name=None, N=256, N_dots=50, show_gamut=False):
185-
if isinstance(cm, str):
186-
cm = plt.get_cmap(cm)
187-
if name is None:
188-
name = cm.name
189-
190-
fig = plt.figure()
191-
fig.suptitle("Colormap evaluation: %s" % (name,), fontsize=24)
192-
axes = _vis_axes()
193-
194-
x = np.linspace(0, 1, N)
195-
x_dots = np.linspace(0, 1, N_dots)
196-
RGB = cm(x)[:, :3]
197-
RGB_dots = cm(x_dots)[:, :3]
198-
199-
ax = axes['cmap']
200-
_show_cmap(ax, RGB)
201-
ax.set_title("The colormap in its glory")
202-
ax.get_xaxis().set_visible(False)
203-
ax.get_yaxis().set_visible(False)
204-
205-
def label(ax, s):
206-
ax.text(0.95, 0.05, s,
207-
horizontalalignment="right",
208-
verticalalignment="bottom",
209-
transform=ax.transAxes)
210-
211-
ax = axes['deltas']
212-
local_deltas = N * deltaEp_sRGB(RGB[:-1, :], RGB[1:, :])
213-
ax.plot(x[1:], local_deltas)
214-
arclength = np.sum(local_deltas) / N
215-
label(ax, "Perceptual deltas (total: %0.2f)" % (arclength,))
216-
ax.set_ylim(0, ax.get_ylim()[1])
217-
# ax.text(0.05, 0.9, "Total length: %0.2f" % (arclength,),
218-
# horizontalalignment="left",
219-
# verticalalignment="top",
220-
# transform=ax.transAxes)
221-
222-
def anom(ax, mat, name):
223-
_show_cmap(ax, _apply_rgb_mat(mat, RGB))
224-
label(ax, name)
192+
class viscm(object):
193+
def __init__(self, cm, name=None, N=256, N_dots=50, show_gamut=False):
194+
if isinstance(cm, str):
195+
cm = plt.get_cmap(cm)
196+
if name is None:
197+
name = cm.name
198+
199+
fig = plt.figure()
200+
fig.suptitle("Colormap evaluation: %s" % (name,), fontsize=24)
201+
axes = _vis_axes()
202+
203+
x = np.linspace(0, 1, N)
204+
x_dots = np.linspace(0, 1, N_dots)
205+
RGB = cm(x)[:, :3]
206+
RGB_dots = cm(x_dots)[:, :3]
207+
208+
ax = axes['cmap']
209+
_show_cmap(ax, RGB)
210+
ax.set_title("The colormap in its glory")
225211
ax.get_xaxis().set_visible(False)
226212
ax.get_yaxis().set_visible(False)
227213

228-
anom(axes['deuteranomaly'], DEUTERANOMALY_05, "Moderate deuteranomaly")
229-
anom(axes['deuteranopia'], DEUTERANOMALY_10, "Complete deuteranopia")
230-
231-
anom(axes['protanomaly'], PROTANOMALY_05, "Moderate protanomaly")
232-
anom(axes['protanopia'], PROTANOMALY_10, "Complete protanopia")
233-
234-
ciecam02 = _sRGB_to_CIECAM02(RGB)
235-
ax = axes['lightness']
236-
ax.plot(x, ciecam02.J, label="Lightness (J)")
237-
label(ax, "Lightness (J)")
238-
ax.set_ylim(0, 105)
239-
240-
ax = axes['colourfulness']
241-
ax.plot(x, ciecam02.M, label="Colourfulness (M)")
242-
label(ax, "Colourfulness (M)")
243-
244-
ax = axes['hue']
245-
ax.plot(x, ciecam02.h, label="Hue angle (h)")
246-
label(ax, "Hue angle (h)")
247-
ax.set_ylim(0, 360)
248-
249-
JKapbp = _CIECAM02_to_JKapbp(ciecam02)
250-
ax = axes['gamut']
251-
ax.plot(JKapbp[:, 1], JKapbp[:, 2], JKapbp[:, 0])
252-
JKapbp_dots = _CIECAM02_to_JKapbp(_sRGB_to_CIECAM02(RGB_dots))
253-
ax.scatter(JKapbp_dots[:, 1],
254-
JKapbp_dots[:, 2],
255-
JKapbp_dots[:, 0],
256-
c=RGB_dots[:, :],
257-
s=80)
258-
259-
# Draw a wireframe indicating the sRGB gamut
260-
if show_gamut:
261-
gamut_patch = sRGB_gamut_patch()
262-
# That function returns a patch where each face is colored to match
263-
# the represented colors. For present purposes we want something
264-
# less... colorful.
265-
gamut_patch.set_facecolor([0.5, 0.5, 0.5, 0.1])
266-
gamut_patch.set_edgecolor([0.2, 0.2, 0.2, 0.1])
267-
ax.add_collection3d(gamut_patch)
268-
269-
_setup_JKapbp_axis(ax)
270-
271-
images = []
272-
image_args = []
273-
example_dir = os.path.dirname(__file__) + "/examples/"
274-
275-
images.append(np.loadtxt(example_dir + "hist2d.txt"))
276-
image_args.append({"aspect": "equal",
277-
"origin": "lower",
278-
"interpolation": "nearest",
279-
"vmin": 0})
280-
281-
images.append(np.loadtxt(example_dir + "st-helens_before-modified.txt.gz"))
282-
image_args.append({})
283-
284-
# Adapted from http://matplotlib.org/mpl_examples/images_contours_and_fields/pcolormesh_levels.py
285-
dx = dy = 0.05
286-
y, x = np.mgrid[-5 : 5 + dy : dy, -5 : 10 + dx : dx]
287-
z = np.sin(x) ** 10 + np.cos(10 + y * x) + np.cos(x) + 0.2 * y + 0.1 * x
288-
images.append(z)
289-
image_args.append({})
290-
291-
deuter_cm = TransformedCMap(DEUTERANOMALY_05, cm)
292-
for i, (image, args) in enumerate(zip(images, image_args)):
293-
ax = axes['image%i' % (i,)]
294-
ax.imshow(image, cmap=cm, **args)
214+
def label(ax, s):
215+
ax.text(0.95, 0.05, s,
216+
horizontalalignment="right",
217+
verticalalignment="bottom",
218+
transform=ax.transAxes)
219+
220+
ax = axes['deltas']
221+
local_deltas = N * UNIFORM_SPACE.deltaEp_sRGB(RGB[:-1, :], RGB[1:, :])
222+
ax.plot(x[1:], local_deltas)
223+
arclength = np.sum(local_deltas) / N
224+
label(ax, "Perceptual deltas (total: %0.2f)" % (arclength,))
225+
ax.set_ylim(0, ax.get_ylim()[1])
226+
ax.get_xaxis().set_visible(False)
227+
228+
ciecam02 = _sRGB_to_CIECAM02(RGB)
229+
JKapbp = _CIECAM02_to_JKapbp(ciecam02)
230+
231+
ax = axes['cmap-greyscale']
232+
grey_RGB = _JMh_to_sRGB(np.column_stack((ciecam02.J,
233+
np.zeros_like(ciecam02.M),
234+
ciecam02.h)))
235+
_show_cmap(ax, grey_RGB)
236+
ax.set_title("Black-and-white printed")
295237
ax.get_xaxis().set_visible(False)
296238
ax.get_yaxis().set_visible(False)
297239

298-
ax_cb = axes['image%i-cb' % (i,)]
299-
ax_cb.imshow(image, cmap=deuter_cm, **args)
300-
ax_cb.get_xaxis().set_visible(False)
301-
ax_cb.get_yaxis().set_visible(False)
240+
ax = axes['lightness-deltas']
241+
ax.plot(x[1:], N * np.diff(JKapbp[:, 0]))
242+
label(ax,
243+
"Perceptual lightness deltas (total: %0.2f)"
244+
% (JKapbp[-1, 0] - JKapbp[0, 0]))
245+
ax.set_ylim(0, ax.get_ylim()[1])
246+
ax.get_xaxis().set_visible(False)
302247

303-
axes['image0'].set_title("Sample images")
304-
axes['image0-cb'].set_title("Moderate deuter.")
248+
# ax = axes['lightness']
249+
# ax.plot(x, ciecam02.J)
250+
# label(ax, "Lightness (J)")
251+
# ax.set_ylim(0, 105)
252+
253+
# ax = axes['colourfulness']
254+
# ax.plot(x, ciecam02.M)
255+
# label(ax, "Colourfulness (M)")
256+
257+
# ax = axes['hue']
258+
# ax.plot(x, ciecam02.h)
259+
# label(ax, "Hue angle (h)")
260+
# ax.set_ylim(0, 360)
261+
262+
def anom(ax, mat, name):
263+
_show_cmap(ax, _apply_rgb_mat(mat, RGB))
264+
label(ax, name)
265+
ax.get_xaxis().set_visible(False)
266+
ax.get_yaxis().set_visible(False)
267+
268+
anom(axes['deuteranomaly'], DEUTERANOMALY_05, "Moderate deuteranomaly")
269+
anom(axes['deuteranopia'], DEUTERANOMALY_10, "Complete deuteranopia")
270+
271+
anom(axes['protanomaly'], PROTANOMALY_05, "Moderate protanomaly")
272+
anom(axes['protanopia'], PROTANOMALY_10, "Complete protanopia")
273+
274+
ax = axes['gamut']
275+
ax.plot(JKapbp[:, 1], JKapbp[:, 2], JKapbp[:, 0])
276+
JKapbp_dots = _CIECAM02_to_JKapbp(_sRGB_to_CIECAM02(RGB_dots))
277+
ax.scatter(JKapbp_dots[:, 1],
278+
JKapbp_dots[:, 2],
279+
JKapbp_dots[:, 0],
280+
c=RGB_dots[:, :],
281+
s=80)
282+
283+
# Draw a wireframe indicating the sRGB gamut
284+
self.gamut_patch = sRGB_gamut_patch()
285+
# That function returns a patch where each face is colored to match
286+
# the represented colors. For present purposes we want something
287+
# less... colorful.
288+
self.gamut_patch.set_facecolor([0.5, 0.5, 0.5, 0.1])
289+
self.gamut_patch.set_edgecolor([0.2, 0.2, 0.2, 0.1])
290+
ax.add_collection3d(self.gamut_patch)
291+
self.gamut_patch.set_visible(show_gamut)
292+
293+
self.gamut_patch_toggle = Button(axes['gamut-checkbox'],
294+
"<- Toggle gamut display")
295+
def toggle(*args):
296+
self.gamut_patch.set_visible(not self.gamut_patch.get_visible())
297+
plt.draw()
298+
self.gamut_patch_toggle.on_clicked(toggle)
299+
300+
_setup_JKapbp_axis(ax)
301+
302+
images = []
303+
image_args = []
304+
example_dir = os.path.dirname(__file__) + "/examples/"
305+
306+
images.append(np.loadtxt(example_dir + "hist2d.txt"))
307+
image_args.append({"aspect": "equal",
308+
"origin": "lower",
309+
"interpolation": "nearest",
310+
"vmin": 0})
311+
312+
images.append(np.loadtxt(example_dir
313+
+ "st-helens_before-modified.txt.gz"))
314+
image_args.append({})
315+
316+
# Adapted from http://matplotlib.org/mpl_examples/images_contours_and_fields/pcolormesh_levels.py
317+
dx = dy = 0.05
318+
y, x = np.mgrid[-5 : 5 + dy : dy, -5 : 10 + dx : dx]
319+
z = np.sin(x) ** 10 + np.cos(10 + y * x) + np.cos(x) + 0.2 * y + 0.1 * x
320+
images.append(z)
321+
image_args.append({})
322+
323+
deuter_cm = TransformedCMap(DEUTERANOMALY_05, cm)
324+
for i, (image, args) in enumerate(zip(images, image_args)):
325+
ax = axes['image%i' % (i,)]
326+
ax.imshow(image, cmap=cm, **args)
327+
ax.get_xaxis().set_visible(False)
328+
ax.get_yaxis().set_visible(False)
329+
330+
ax_cb = axes['image%i-cb' % (i,)]
331+
ax_cb.imshow(image, cmap=deuter_cm, **args)
332+
ax_cb.get_xaxis().set_visible(False)
333+
ax_cb.get_yaxis().set_visible(False)
334+
335+
axes['image0'].set_title("Sample images")
336+
axes['image0-cb'].set_title("Moderate deuter.")
305337

306338
def sRGB_gamut_patch(resolution=20):
307339
step = 1.0 / resolution

0 commit comments

Comments
 (0)