/usr/lib/python2.7/dist-packages/chaco/transform_color_mapper.py is in python-chaco 4.4.1-1.2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 | from numpy import clip, isinf, ones_like, empty
from chaco.api import ColorMapper
from traits.api import Trait, Callable, Tuple, Float, on_trait_change
from speedups import map_colors, map_colors_uint8
class TransformColorMapper(ColorMapper):
"""This class adds arbitrary data transformations to a ColorMapper.
The default ColorMapper is basically a linear mapper from data space to
color space. A TransformColorMapper allows a nonlinear mapper to be
created.
A ColorMapper works by linearly transforming the data from data space to the
unit interval [0,1], and then linearly mapping that interval to the color
space.
A TransformColorMapper allows an arbitrary transform to be inserted at two
places in this process. First, an initial transformation, `data_func` can
be applied to the data *before* is it mapped to [0,1]. Then another
function, `unit_func`, can be applied to the transformed data on [0,1]
before it is mapped to color space. Normally, a `unit_func` is map of the
unit interval [0,1] to itself (e.g. x^2 or sin(pi*x/2)).
"""
data_func = Trait(None, None, Callable)
unit_func = Trait(None, None, Callable)
transformed_bounds = Tuple(Trait(None, None, Float),
Trait(None, None, Float))
#-------------------------------------------------------------------
# Trait handlers
#-------------------------------------------------------------------
@on_trait_change('data_func, range.updated')
def _update_transformed_bounds(self):
if self.range is None:
# The ColorMapper doesn't have a range yet, so don't do anything.
# This apparently occurs during initialization.
return
if self.data_func is not None:
low = self.range.low
high = self.range.high
trans_low = self.data_func(low)
trans_high = self.data_func(high)
self.transformed_bounds = (trans_low, trans_high)
else:
self.transformed_bounds = (None, None)
self.updated = True
def _unit_func_changed(self):
self.updated = True
#-------------------------------------------------------------------
# Class methods
#-------------------------------------------------------------------
@classmethod
def from_color_mapper(cls, color_mapper, data_func=None, unit_func=None,
**traits):
""" Create a TransformColorMapper from an existing ColorMapper instance.
"""
segdata = color_mapper._segmentdata
return cls.from_segment_map(segdata, range=color_mapper.range,
data_func=data_func, unit_func=unit_func,
**traits)
@classmethod
def from_color_map(cls, color_map, data_func=None, unit_func=None,
**traits):
"""Create a TransformColorMapper from a colormap generator function.
The return value is an instance of TransformColorMapper, *not* a factory
function, so this does not provide a direct replacement for a standard
colormap factory function. For that, use the class method
TransoformColorMapper.factory_from_color_map().
"""
# Call the colormap factory function to create an instance of a
# ColorMapper.
color_mapper = color_map(None, **traits)
segdata = color_mapper._segmentdata
return cls.from_segment_map(segdata, range=color_mapper.range,
data_func=data_func, unit_func=unit_func,
**traits)
@classmethod
def factory_from_color_map(cls, color_map, data_func=None, unit_func=None,
**traits):
"""
Create a TransformColorMapper factory function from a standard colormap
factory function.
WARNING: This function is untested; I realized I didn't need it shortly
after writing it, so I haven't tried it yet. --WW
"""
# Call the colormap factory function to create an instance of a
# ColorMapper.
color_mapper = color_map(None, **traits)
def factory(range, **traits):
tcm = cls.from_color_mapper(color_mapper,
data_func=data_func, unit_func=unit_func, **traits)
return tcm
return factory
#-------------------------------------------------------------------
# ColorMapper interface (these override methods from ColorMapper)
#-------------------------------------------------------------------
def map_screen(self, data_array):
""" Maps an array of data values to an array of colors.
"""
norm_data = self._compute_normalized_data(data_array)
# The data are normalized, so we can pass low = 0, high = 1
rgba = map_colors(norm_data, self.steps, 0, 1, self._red_lut,
self._green_lut, self._blue_lut, self._alpha_lut)
return rgba
def map_index(self, data_array):
""" Maps an array of values to their corresponding color band index.
"""
norm_data = self._compute_normalized_data(data_array)
indices = (norm_data * (self.steps-1)).astype(int)
return indices
def map_uint8(self, data_array):
""" Maps an array of data values to an array of colors.
"""
norm_data = self._compute_normalized_data(data_array)
rgba = map_colors_uint8(norm_data, self.steps, 0.0, 1.0,
self._red_lut_uint8, self._green_lut_uint8,
self._blue_lut_uint8, self._alpha_lut_uint8)
return rgba
#-------------------------------------------------------------------
# Private methods
#-------------------------------------------------------------------
def _compute_normalized_data(self, data_array):
"""
Apply `data_func`, then linearly scale to the unit interval, and
then apply `unit_func`.
"""
# FIXME: Deal with nans?
if self._dirty:
self._recalculate()
if self.data_func is not None:
data_array = self.data_func(data_array)
low, high = self.transformed_bounds
else:
low, high = self.range.low, self.range.high
range_diff = high - low
# Linearly transform the values to the unit interval.
if range_diff == 0.0 or isinf(range_diff):
# Handle null range, or infinite range (which can happen during
# initialization before range is connected to a data source).
norm_data = 0.5*ones_like(data_array)
else:
norm_data = empty(data_array.shape, dtype='float32')
norm_data[:] = data_array
norm_data -= low
norm_data /= range_diff
clip(norm_data, 0.0, 1.0, norm_data)
if self.unit_func is not None:
norm_data = self.unit_func(norm_data)
return norm_data
|