-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathcolormapping.py
256 lines (221 loc) · 9.33 KB
/
colormapping.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Custom colormaps reading and tuning.
"""
import numpy
import json
from bronx.fancies import loggers
from . import config
#: No automatic export
__all__ = []
logger = loggers.getLogger(__name__)
_loaded_colormaps = {}
loaded_ColormapHelpers = {}
def register_colormap_from_json(filename):
"""
Load colormap and metadata from file (json), register to matplotlib and
return metadata.
"""
import matplotlib
import matplotlib.pyplot as plt
with open(filename, 'r') as f:
asdict = json.load(f)
# colormap itself
colormap = asdict['name']
colors = numpy.array(asdict['colors_RGB'], dtype=numpy.float64)
if colors.max() > 1.:
colors /= 255.
asdict['colors_RGB'] = colors
cmap = matplotlib.colors.ListedColormap(colors, name=colormap)
asdict['cmap'] = cmap
if colormap not in plt.colormaps():
matplotlib.colormaps.register(cmap=cmap)
else:
raise ValueError('this colormap is already registered: {}'.format(colormap))
_loaded_colormaps[filename] = asdict
return asdict
def load_colormap(colormap):
"""Load colormap from epygram colormaps if needed."""
import matplotlib.pyplot as plt
if colormap not in plt.colormaps():
if colormap in config.colormaps:
cmapfile = config.colormaps[colormap]
cmap = register_colormap_from_json(cmapfile)['cmap']
else:
raise ValueError("unknown colormap: {}".format(colormap))
else:
cmap = plt.get_cmap(colormap)
return cmap
def get_ColormapHelper(colormap):
if colormap in config.colormaps:
colormap_file = config.colormaps[colormap]
else:
raise ValueError("unknown colormap: {}".format(colormap))
return get_ColormapHelper_fromfile(colormap_file)
def get_ColormapHelper_fromfile(filename):
"""Get colormap from file (json) and build ad hoc ColormapHelper."""
if filename in _loaded_colormaps:
asdict = _loaded_colormaps[filename]
else:
asdict = register_colormap_from_json(filename)
colormap = asdict['name']
# colormap helper
if asdict.get('colorcenters', False):
ch = CenteredColormapHelper(colormap,
explicit_colorcenters=asdict['colorcenters'],
normalize=asdict.get('normalize', False),)
elif asdict.get('colorbounds', False):
ticks = asdict.get('ticks', None)
if ticks == 'colorbounds':
ticks = asdict['colorbounds']
ch = ColormapHelper(colormap,
explicit_colorbounds=asdict['colorbounds'],
normalize=asdict.get('normalize', False),
explicit_ticks=ticks)
else:
ch = ColormapHelper(colormap, normalize=False)
loaded_ColormapHelpers[colormap] = ch
return ch
class ColormapHelper(object):
"""
An integrated object helping for colormapping.
"""
max_ticks = 15
def __init__(self, colormap,
explicit_colorbounds=None,
normalize=False,
explicit_ticks=None):
"""
A ColormapHelper is meant to help dealing with colormapping,
especially mapping values to color changes, and prepares colormapping
arguments for plotting functions.
:param colormap: name of the colormap to be used
:param explicit_colorbounds: to specify explicitly the colorbounds,
i.e. values where colors need to change.
Includes min value as first item, and max value as last item.
:param normalize: if colors need to be normalized, i.e. that each color
interval need to occupy the same space on the colorbar.
:param explicit_ticks: to specify the ticks values to be shown
"""
self.cmap_object = load_colormap(colormap)
self.explicit_colorbounds = explicit_colorbounds
self.normalize = normalize
self.explicit_ticks = explicit_ticks
@property
def colormap(self):
return self.cmap_object.name
def colorbounds(self, minmax=None, number=None, step=None):
"""
Get color bounds, i.e. values where colors change.
Arguments are needed for implicit colorbounds only.
:param minmax: (min, max) values of the values to be plotted
:param number: number of different colors
:param step: step in values from min to max, where to change colors
Arguments number and step are mutually exclusive.
"""
if self.explicit_colorbounds is not None:
return self.explicit_colorbounds
elif minmax is not None:
assert None in (number, step), "Cannot provide both number and step"
if step is None:
if number is None:
number = 50
return numpy.linspace(minmax[0], minmax[1], number)
else:
return numpy.arange(minmax[0], minmax[1] + step, step)
elif minmax is None:
raise ValueError("Must provide minmax if colorbounds is not known a priori.")
def norm(self):
"""
Normalize colormap, for each color occupy the same space on colorbar.
Return the Norm object to be used by matplotlib.
"""
import matplotlib
assert self.explicit_colorbounds is not None, "Cannot compute norm if explicit_colorbounds are not known"
colors = matplotlib.colors
return colors.BoundaryNorm(boundaries=self.explicit_colorbounds,
ncolors=self.cmap_object.N)
def ticks_label(self, *args, **kwargs):
"""
Return the labels of the ticks to be shown on colorbar.
Arguments are passed to ticks_position(), in case of implicit ticks.
"""
return self.ticks_position(*args, **kwargs)
def ticks_position(self, *args, **kwargs):
"""
Return the position of the ticks to be shown on colorbar.
Arguments are passed to colorbounds(), in case of implicit ticks.
"""
if self.explicit_ticks is not None:
return self.explicit_ticks
else:
cbounds = self.colorbounds(*args, **kwargs)
if len(cbounds) <= self.max_ticks:
ticks = cbounds
else:
L = int((len(cbounds) - 1) // self.max_ticks) + 1
ticks = [cbounds[i]
for i in range(len(cbounds) - (L // 3 + 1))
if i % L == 0] + [cbounds[-1]]
return ticks
def kwargs_for_plot(self, plot_method,
minmax=None,
center_cmap_on_0=False,
**colorbounds_kw):
"""
Get kwargs for plot.
:param plot_method: because arguments depend on plotting method.
:param minmax: min and max values to be plot.
:param center_cmap_on_0: if the colormap is to be centered on 0
(diff plots).
Other arguments are passed to colorbounds().
"""
kwargs = dict(cmap=self.colormap)
if plot_method not in ('scatter', 'pcolormesh'):
kwargs['levels'] = self.colorbounds(minmax=minmax,
**colorbounds_kw)
if self.explicit_colorbounds is not None:
if self.normalize:
kwargs['norm'] = self.norm()
kwargs['vmin'] = None
kwargs['vmax'] = None
else:
if center_cmap_on_0:
vmax = max(abs(minmax[0]), minmax[1])
kwargs['vmin'] = -vmax
kwargs['vmax'] = vmax
else:
kwargs['vmin'] = minmax[0]
kwargs['vmax'] = minmax[1]
return kwargs
class CenteredColormapHelper(ColormapHelper):
def __init__(self, colormap, explicit_colorcenters, normalize=True):
"""
A specific ColormapHelper, where the values of ticks are explicitly
defined, and colors must surround each tick.
:param colormap: colormap: name of the colormap to be used
:param explicit_colorcenters: explicit values of the ticks and center
values of each color.
:param normalize: if colors need to be normalized, i.e. that each color
interval need to occupy the same space on the colorbar.
"""
self.cmap_object = load_colormap(colormap)
colorbounds = [float(explicit_colorcenters[0]) -
0.5 * abs(explicit_colorcenters[0])]
colorbounds += [float(explicit_colorcenters[i + 1] +
explicit_colorcenters[i]) / 2.
for i in range(len(explicit_colorcenters) - 1)]
colorbounds += [float(explicit_colorcenters[-1]) +
0.5 * abs(explicit_colorcenters[-1])]
self.explicit_colorbounds = colorbounds
self.normalize = normalize
self.explicit_ticks = explicit_colorcenters
def ticks_label(self, *_, **__):
"""Return the labels of the ticks to be shown on colorbar."""
return self.explicit_ticks
def ticks_position(self, *_, **__):
"""Return the position of the ticks to be shown on colorbar."""
return [(self.explicit_colorbounds[i] +
self.explicit_colorbounds[i + 1]) / 2.
for i in range(len(self.explicit_colorbounds) - 1)]