Source code for cytoflowgui.workflow.operations.range2d

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

# (c) Massachusetts Institute of Technology 2015-2018
# (c) Brian Teague 2018-2022
#
# 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/>.

"""
cytoflowgui.workflow.operations.range2d
---------------------------------------

"""

from traits.api import (provides, Instance, Str, Property, Tuple, Constant, 
                        Bool, Enum, observe)

from cytoflow.operations.range2d import Range2DOp, Op2DView, ScatterplotRangeSelection2DView, DensityRangeSelection2DView, _RangeSelection2D
import cytoflow.utility as util

from ..views import IWorkflowView, WorkflowView
from ..views.scatterplot import SCATTERPLOT_MARKERS
from ..views.view_base import Data2DPlotParams
from ..serialization import camel_registry, traits_str, cytoflow_class_repr, dedent

from .operation_base import IWorkflowOperation, WorkflowOperation

Range2DOp.__repr__ = cytoflow_class_repr

[docs] class Range2DPlotParams(Data2DPlotParams): density = Bool(False) # density-specific gridsize = util.PositiveCInt(50, allow_zero = False) smoothed = Bool(False) smoothed_sigma = util.PositiveCFloat(1.0, allow_zero = False) # scatterplot-specific alpha = util.PositiveCFloat(0.25) s = util.PositiveCFloat(2) marker = Enum(SCATTERPLOT_MARKERS)
class _RangeSelection2DWorkflowView(_RangeSelection2D): parent = Instance(IWorkflowView) # data flow: user drags cursor. remote canvas calls _onselect, sets # _range. _range is copied back to local view (because it's # "status = True"). _update_range is called, and because # self.interactive is false, then the operation is updated. the # operation's range is sent back to the remote operation (because # "apply = True"), where the remote operation is updated. # low ahd high are properties (both in the view and in the operation) # so that the update can happen atomically. otherwise, they happen # one after the other, which is noticably slow! def _onselect(self, pos1, pos2): """Update selection traits""" self.parent._range = (min(pos1.xdata, pos2.xdata), max(pos1.xdata, pos2.xdata), min(pos1.ydata, pos2.ydata), max(pos1.ydata, pos2.ydata))
[docs] @provides(IWorkflowView) class ScatterPlotRangeSelection2DWorkflowView(_RangeSelection2DWorkflowView, ScatterplotRangeSelection2DView): pass
[docs] @provides(IWorkflowView) class DensityRangeSelection2DWorkflowView(_RangeSelection2DWorkflowView, DensityRangeSelection2DView): pass
[docs] @provides(IWorkflowView) class Range2DSelectionView(WorkflowView, Op2DView): id = Constant('cytoflowgui.workflow.operations.range2dview') op = Instance(IWorkflowOperation, fixed = True) plot_params = Instance(Range2DPlotParams, ()) xscale = util.ScaleEnum yscale = util.ScaleEnum _view = Instance(IWorkflowView) _range = Tuple(util.FloatOrNone(None), util.FloatOrNone(None), util.FloatOrNone(None), util.FloatOrNone(None), status = True) interactive = Bool(False, transient = True)
[docs] def clear_estimate(self): # no-op return
[docs] def plot(self, experiment, **kwargs): density = kwargs.pop('density') if density: kwargs.pop('alpha') kwargs.pop('s') kwargs.pop('marker') if not self._view or not isinstance(self._view, DensityRangeSelection2DWorkflowView): self._view = DensityRangeSelection2DWorkflowView(op = self.op, parent = self, interactive = self.interactive, huescale = self.huescale) kwargs['patch_props'] = {'edgecolor' : 'white', 'linewidth' : 3, 'fill' : False} else: kwargs.pop('gridsize') kwargs.pop('smoothed') kwargs.pop('smoothed_sigma') if not self._view or not isinstance(self._view, ScatterPlotRangeSelection2DWorkflowView): self._view = ScatterPlotRangeSelection2DWorkflowView(op = self.op, parent = self, interactive = self.interactive, huefacet = self.huefacet) kwargs['patch_props'] = {'edgecolor' : 'black', 'linewidth' : 3, 'fill' : False} self._view.plot(experiment, **kwargs)
[docs] def get_notebook_code(self, idx): if self.plot_params.density: view = DensityRangeSelection2DWorkflowView() plot_params = self.plot_params.clone_traits(copy = "deep") plot_params.reset_traits(traits = ['density', 'alpha', 's', 'marker']) else: view = ScatterPlotRangeSelection2DWorkflowView() plot_params = self.plot_params.clone_traits(copy = "deep") plot_params.reset_traits(traits = ['density', 'gridsize', 'smoothed', 'smoothed_sigma']) view.copy_traits(self, view.copyable_trait_names()) plot_params_str = traits_str(plot_params) return dedent(""" op_{idx}.default_view({traits}{comma}{density}).plot(ex_{prev_idx}{plot_params}) """ .format(idx = idx, traits = traits_str(view), comma = ", " if traits_str(view) and self.plot_params.density else "", density = "density = True" if self.plot_params.density else "", prev_idx = idx - 1, plot_params = ", " + plot_params_str if plot_params_str else ""))
@observe('interactive', post_init = True) def _interactive(self, _): if self._view: self._view.interactive = self.interactive @observe('_range', post_init = True) def _update_range(self, _): self.op._range = self._range @observe('huefacet', post_init = True) def _update_huefacet(self, _): if self._view: self._view.huefacet = self.huefacet @observe('huescale', post_init = True) def _update_huescale(self, _): if self._view: self._view.huescale = self.huescale
[docs] @provides(IWorkflowOperation) class Range2DWorkflowOp(WorkflowOperation, Range2DOp): name = Str(apply = True) xchannel = Str(apply = True) ychannel = Str(apply = True) _range = Tuple(util.FloatOrNone(None), util.FloatOrNone(None), util.FloatOrNone(None), util.FloatOrNone(None), apply = True) xlow = Property(util.FloatOrNone(None), observe = '_range') xhigh = Property(util.FloatOrNone(None), observe = '_range') ylow = Property(util.FloatOrNone(None), observe = '_range') yhigh = Property(util.FloatOrNone(None), observe = '_range') def _get_xlow(self): return self._range[0] def _set_xlow(self, val): self._range = (val, self._range[1], self._range[2], self._range[3]) def _get_xhigh(self): return self._range[1] def _set_xhigh(self, val): self._range = (self._range[0], val, self._range[2], self._range[3]) def _get_ylow(self): return self._range[2] def _set_ylow(self, val): self._range = (self._range[0], self._range[1], val, self._range[3]) def _get_yhigh(self): return self._range[3] def _set_yhigh(self, val): self._range = (self._range[0], self._range[1], self._range[2], val)
[docs] def default_view(self, **kwargs): return Range2DSelectionView(op = self, **kwargs)
[docs] def get_notebook_code(self, idx): op = Range2DOp() op.copy_traits(self, op.copyable_trait_names()) return dedent(""" op_{idx} = {repr} ex_{idx} = op_{idx}.apply(ex_{prev_idx}) """ .format(repr = repr(op), idx = idx, prev_idx = idx - 1))
### Serialization @camel_registry.dumper(Range2DWorkflowOp, 'range2d', version = 1) def _dump(op): return dict(name = op.name, xchannel = op.xchannel, xlow = op.xlow, xhigh = op.xhigh, ychannel = op.ychannel, ylow = op.ylow, yhigh = op.yhigh) @camel_registry.loader('range2d', version = 1) def _load(data, version): return Range2DWorkflowOp(**data) @camel_registry.dumper(Range2DSelectionView, 'range2d-view', version = 3) def _dump_view(view): return dict(op = view.op, xscale = view.xscale, yscale = view.yscale, huefacet = view.huefacet, huescale = view.huescale, subset_list = view.subset_list, plot_params = view.plot_params, current_plot = view.current_plot) @camel_registry.dumper(Range2DSelectionView, 'range2d-view', version = 2) def _dump_view_v2(view): return dict(op = view.op, xscale = view.xscale, yscale = view.yscale, huefacet = view.huefacet, subset_list = view.subset_list, plot_params = view.plot_params, current_plot = view.current_plot) @camel_registry.dumper(Range2DSelectionView, 'range2d-view', version = 1) def _dump_view_v1(view): return dict(op = view.op, xscale = view.xscale, yscale = view.yscale, huefacet = view.huefacet, subset_list = view.subset_list) @camel_registry.loader('range2d-view', version = any) def _load_view(data, version): return Range2DSelectionView(**data) @camel_registry.dumper(Range2DPlotParams, 'range2d-params', version = 1) def _dump_view_params(params): return dict(density = params.density, # BasePlotParams title = params.title, xlabel = params.xlabel, ylabel = params.ylabel, huelabel = params.huelabel, col_wrap = params.col_wrap, sns_style = params.sns_style, sns_context = params.sns_context, legend = params.legend, sharex = params.sharex, sharey = params.sharey, despine = params.despine, # DataplotParams min_quantile = params.min_quantile, max_quantile = params.max_quantile, # Data2DPlotParams xlim = params.xlim, ylim = params.ylim, # Scatterplot params alpha = params.alpha, s = params.s, marker = params.marker, # Density plot params gridsize = params.gridsize, smoothed = params.smoothed, smoothed_sigma = params.smoothed_sigma ) @camel_registry.loader('range2d-params', version = any) def _load_params(data, version): return Range2DPlotParams(**data)