Source code for cytoflow.utility.log_scale

#!/usr/bin/env python3.4
# coding: latin-1

# (c) Massachusetts Institute of Technology 2015-2018
# (c) Brian Teague 2018-2021
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

'''
cytoflow.utility.log_scale
--------------------------
'''

from traits.api import (Instance, Str, Dict, provides, Constant, Enum, Float, 
                        Property, Tuple, Array) 
                       
import numpy as np
import pandas as pd
import matplotlib.colors

from .scale import IScale, ScaleMixin, register_scale
from .cytoflow_errors import CytoflowError
from .util_functions import is_numeric

[docs]@provides(IScale) class LogScale(ScaleMixin): id = Constant("edu.mit.synbio.cytoflow.utility.log_scale") name = "log" experiment = Instance("cytoflow.Experiment") # must set one of these. they're considered in order. channel = Str condition = Str statistic = Tuple(Str, Str) error_statistic = Tuple(Str, Str) data = Array mode = Enum("mask", "clip") threshold = Property(Float, depends_on = "[experiment, condition, channel, statistic, error_statistic]") _channel_threshold = Float(0.1)
[docs] def get_mpl_params(self, ax): return {"nonpositive" : self.mode}
def _set_threshold(self, threshold): self._channel_threshold = threshold def _get_threshold(self): if self.channel: return self._channel_threshold elif self.condition: cond = self.experiment[self.condition][self.experiment[self.condition] > 0] return cond.min() elif self.statistic in self.experiment.statistics \ and not self.error_statistic in self.experiment.statistics: stat = self.experiment.statistics[self.statistic] assert is_numeric(stat) return stat[stat > 0].min() elif self.statistic in self.experiment.statistics \ and self.error_statistic in self.experiment.statistics: stat = self.experiment.statistics[self.statistic] err_stat = self.experiment.statistics[self.error_statistic] stat_min = stat[stat > 0].min() try: err_min = min([x for x in [min(x) for x in err_stat] if x > 0]) return err_min except (TypeError, IndexError): err_min = min([x for x in err_stat if stat_min - x > 0]) return stat_min - err_min elif self.data.size > 0: return self.data[self.data > 0].min() def __call__(self, data): # this function should work with: int, float, tuple, list, pd.Series, # np.ndframe. it should return the same data type as it was passed. if isinstance(data, (int, float)): if self.mode == "mask": if data < self.threshold: raise CytoflowError("data <= scale.threshold (currently: {})".format(self.threshold)) else: return np.log10(data) else: if data < self.threshold: return np.log10(self.threshold) else: return np.log10(data) elif isinstance(data, (list, tuple)): ret = [self.__call__(x) for x in data] if isinstance(data, tuple): return tuple(ret) else: return ret elif isinstance(data, (np.ndarray, pd.Series)): mask_value = np.nan if self.mode == "mask" else self.threshold x = pd.Series(data) x = x.mask(x < self.threshold, other = mask_value) ret = np.log10(x) if isinstance(data, pd.Series): return ret else: return ret.values else: raise CytoflowError("Unknown type {} passed to log_scale.__call__" .format(type(data)))
[docs] def inverse(self, data): # this function shoujld work with: int, float, tuple, list, pd.Series, # np.ndframe if isinstance(data, (int, float)): return np.power(10, data) elif isinstance(data, (list, tuple)): ret = [np.power(10, x) for x in data] if isinstance(data, tuple): return tuple(ret) else: return ret elif isinstance(data, (np.ndarray, pd.Series)): return np.power(10, data) else: raise CytoflowError("Unknown type {} passed to log_scale.inverse" .format(type(data)))
[docs] def clip(self, data): if isinstance(data, pd.Series): return data.clip(lower = self.threshold) elif isinstance(data, np.ndarray): return data.clip(min = self.threshold) elif isinstance(data, float): return max(data, self.threshold) elif isinstance(data, int): data = float(data) return max(data, self.threshold) else: try: return [max(x, self.threshold) for x in data] except TypeError as e: raise CytoflowError("Unknown data type in LogScale.clip") from e
[docs] def norm(self, vmin = None, vmax = None): if vmin is not None and vmax is not None: pass elif self.channel: vmin = self.experiment[self.channel].min() vmax = self.experiment[self.channel].max() elif self.condition: vmin = self.experiment[self.condition].min() vmax = self.experiment[self.condition].max() elif self.statistic in self.experiment.statistics: stat = self.experiment.statistics[self.statistic] try: vmin = min([min(x) for x in stat]) vmax = max([max(x) for x in stat]) except (TypeError, IndexError): vmin = stat.min() vmax = stat.max() if self.error_statistic in self.experiment.statistics: err_stat = self.experiment.statistics[self.error_statistic] try: vmin = min([min(x) for x in err_stat]) vmax = max([max(x) for x in err_stat]) except (TypeError, IndexError): vmin = vmin - err_stat.min() vmax = vmax + err_stat.max() elif self.data.size > 0: vmin = self.data.min() vmax = self.data.max() else: raise CytoflowError("Must set one of 'channel', 'condition' " "or 'statistic'.") return matplotlib.colors.LogNorm(vmin = self.clip(vmin), vmax = self.clip(vmax))
register_scale(LogScale)