Repository URL to install this package:
Version:
5.0.0 ▾
|
chaco
/
log_mapper.py
|
---|
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" Defines the LogMapper and InvalidDataRangeException classes.
"""
# Major library imports
from numpy import (
array,
isnan,
log,
log10,
exp,
zeros,
sometrue,
floor,
ceil,
ndarray,
)
import numpy as np
# Enthought library imports
from traits.api import Bool, Float
# Local relative imports
from .base_1d_mapper import Base1DMapper
LOG_MINIMUM = 0.0
class InvalidDataRangeException(Exception):
pass
class LogMapper(Base1DMapper):
"""Defines a 1-D logarithmic scale mapping from a 1-D region in input
space to a 1-D region in output space.
"""
#: The value to map when asked to map values <= LOG_MINIMUM to screen space.
fill_value = Float(1.0)
# ------------------------------------------------------------------------
# Private traits
# ------------------------------------------------------------------------
_inter_scale = Float(0.0)
_inter_offset = Float(0.0)
_screen_scale = Float(0.0)
_screen_offset = Float(0.0)
_null_screen_range = Bool(False)
_null_data_range = Bool(False)
# ------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------
def map_screen(self, data_array):
"""map_screen(data_array) -> screen_array
Overrides AbstractMapper. Maps values from data space to screen space.
"""
# Ensure that data_array is actually an array.
if not isinstance(data_array, ndarray):
data_array = array(data_array, ndmin=1)
# First convert to a [0,1] space, then to the screen space.
if not self._cache_valid:
self._compute_scale()
if self._inter_scale == 0.0:
intermediate = data_array * 0.0
else:
try:
with np.errstate(invalid="ignore"):
mask = (data_array <= LOG_MINIMUM) | isnan(data_array)
if sometrue(mask):
data_array = array(data_array, copy=True, ndmin=1)
data_array[mask] = self.fill_value
intermediate = (
log(data_array) - self._inter_offset
) / self._inter_scale
except ValueError:
intermediate = zeros(len(data_array))
result = intermediate * self._screen_scale + self._screen_offset
return result
def map_data(self, screen_val):
"""map_data(screen_val) -> data_val
Overrides Abstract Mapper. Maps values from screen space into data space.
"""
if not self._cache_valid:
self._compute_scale()
if self._null_screen_range or self._null_data_range:
return array([self.range.low])
# First convert to a [0,1] space, then to the data space
intermediate = (screen_val - self._screen_offset) / self._screen_scale
return exp(self._inter_scale * intermediate + self._inter_offset)
def map_data_array(self, screen_vals):
return self.map_data(screen_vals)
# ------------------------------------------------------------------------
# Private methods
# ------------------------------------------------------------------------
def _get_safe_scale(self, range):
orig_low = range.low
orig_high = range.high
if orig_low < LOG_MINIMUM:
low = LOG_MINIMUM
else:
low = orig_low
if orig_high < LOG_MINIMUM:
high = LOG_MINIMUM
else:
high = orig_high
if low == high:
if low == LOG_MINIMUM:
low = 1.0
high = 10.0
else:
log_val = log10(low)
low = pow(10, floor(log_val))
if ceil(log_val) != floor(log_val):
high = pow(10, ceil(log_val))
else:
high = pow(10, ceil(log_val) + 1)
return (low, high)
def _compute_scale(self):
if self._cache_valid:
return
if self.range is None:
self._cache_valid = False
return
screen_range = self.high_pos - self.low_pos
if screen_range == 0.0:
self._null_screen_range = True
# Get dataspace low and high from the range that are "safe" for a
# logarithmic mapper, i.e. constrained to be between LOG_MINIMUM and inf.
low, high = self._get_safe_scale(self.range)
if high - low == 0:
self._null_data_range = True
else:
if low == LOG_MINIMUM:
self._inter_scale = log(high)
self._inter_offset = 0.0
else:
self._inter_scale = log(high) - log(low)
self._inter_offset = log(low)
self._screen_scale = screen_range
self._screen_offset = self.low_pos
self._cache_valid = True