Esempio n. 1
0
class PolygonWorkflowOp(WorkflowOperation, PolygonOp):
    name = Str(apply=True)
    xchannel = Str(apply=True)
    ychannel = Str(apply=True)

    vertices = List((Float, Float), apply=True)

    # there's a bit of a subtlety here: if the vertices were
    # selected on a plot with scaled axes, we need to apply that
    # scale function to both the vertices and the data before
    # looking for path membership

    xscale = util.ScaleEnum(apply=True)
    yscale = util.ScaleEnum(apply=True)

    def default_view(self, **kwargs):
        return PolygonSelectionView(op=self, **kwargs)

    def get_notebook_code(self, idx):
        op = PolygonOp()
        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))
Esempio n. 2
0
class DensityGatePluginOp(PluginOpMixin, DensityGateOp):
    handler_factory = Callable(DensityGateHandler)

    # add "estimate" metadata
    xchannel = Str(estimate=True)
    ychannel = Str(estimate=True)
    keep = util.PositiveCFloat(0.9, allow_zero=False, estimate=True)
    by = List(Str, estimate=True)
    xscale = util.ScaleEnum(estimate=True)
    yscale = util.ScaleEnum(estimate=True)

    # bits to support the subset editor

    subset_list = List(ISubset, estimate=True)
    subset = Property(Str, depends_on="subset_list.str")

    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join(
            [subset.str for subset in self.subset_list if subset.str])

    @on_trait_change('subset_list.str')
    def _subset_changed(self, obj, name, old, new):
        self.changed = (Changed.ESTIMATE, ('subset_list', self.subset_list))

    def default_view(self, **kwargs):
        return DensityGatePluginView(op=self, **kwargs)

    def estimate(self, experiment):
        super().estimate(experiment, subset=self.subset)
        self.changed = (Changed.ESTIMATE_RESULT, self)

    def clear_estimate(self):
        self._xscale = self._yscale = None
        self._xbins = np.empty(1)
        self._ybins = np.empty(1)
        self._keep_xbins = dict()
        self._keep_ybins = dict()
        self._histogram = {}

        self.changed = (Changed.ESTIMATE_RESULT, self)

    def should_clear_estimate(self, changed, payload):
        return True

    def get_notebook_code(self, idx):
        op = DensityGateOp()
        op.copy_traits(self, op.copyable_trait_names())

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """.format(repr=repr(op),
                   idx=idx,
                   prev_idx=idx - 1,
                   subset=", subset = " +
                   repr(self.subset) if self.subset else ""))
Esempio n. 3
0
class DensityGateWorkflowOp(WorkflowOperation, DensityGateOp):    
    # add 'estimate' and 'apply' metadata
    name = Str(apply = True)
    xchannel = Str(estimate = True)
    ychannel = Str(estimate = True)
    keep = util.PositiveCFloat(0.9, allow_zero = False, estimate = True)
    by = List(Str, estimate = True)
    xscale = util.ScaleEnum(estimate = True)
    yscale = util.ScaleEnum(estimate = True)
        
    # override the base class's "subset" with one that is dynamically generated /
    # updated from subset_list
    subset = Property(Str, observe = "subset_list.items.str")
    subset_list = List(ISubset, estimate = True)
    
    # add 'estimate_result' metadata
    _histogram = Dict(Any, Array, transient = True, estimate_result = True)

    # bits to support the subset editor
    @observe('subset_list:items.str')
    def _on_subset_changed(self, _):
        self.changed = 'subset_list'
        
    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join([subset.str for subset in self.subset_list if subset.str])
    
    def default_view(self, **kwargs):
        return DensityGateWorkflowView(op = self, **kwargs)
    
    def clear_estimate(self):
        self._xscale = self._yscale = None
        self._xbins = np.empty(1)
        self._ybins = np.empty(1)
        self._keep_xbins = dict()
        self._keep_ybins = dict()
        self._histogram = {}
            
        
    def apply(self, experiment):
        if not self._histogram:
            raise util.CytoflowOpError(None, 'Click "Estimate"!')
        return DensityGateOp.apply(self, experiment)
            
    def get_notebook_code(self, idx):
        op = DensityGateOp()
        op.copy_traits(self, op.copyable_trait_names())      

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """
        .format(repr = repr(op),
                idx = idx,
                prev_idx = idx - 1,
                subset = ", subset = " + repr(self.subset) if self.subset else ""))
Esempio n. 4
0
class BinningPluginView(PluginViewMixin, BinningView):
    handler_factory = Callable(BinningViewHandler)
    op = Instance(IOperation, fixed=True)
    huefacet = Str(status=True)
    huescale = util.ScaleEnum(status=True)

    def plot_wi(self, wi):
        self.plot(wi.previous_wi.result)

    def plot(self, experiment, **kwargs):

        if self.op.name:
            op = self.op
            self.huefacet = op.name
            self.huescale = op.scale
            legend = True
        else:
            op = self.op.clone_traits()
            op.name = ''.join(
                random.choice(string.ascii_uppercase + string.digits)
                for _ in range(6))
            self.huefacet = op.name
            legend = False

        try:
            experiment = op.apply(experiment)
        except util.CytoflowOpError as e:
            warnings.warn(e.__str__(), util.CytoflowViewWarning)
            self.huefacet = ""

        HistogramView.plot(self, experiment, legend=legend, **kwargs)
Esempio n. 5
0
class GaussianMixture2DPluginOp(PluginOpMixin, GaussianMixture2DOp):
    handler_factory = Callable(GaussianMixture2DHandler)

    # add "estimate" metadata
    num_components = util.PositiveInt(1, estimate=True)
    sigma = util.PositiveFloat(0.0, allow_zero=True, estimate=True)
    by = List(Str, estimate=True)
    xscale = util.ScaleEnum(estimate=True)
    yscale = util.ScaleEnum(estimate=True)

    _gmms = Dict(Any, Instance(mixture.GaussianMixture), transient=True)

    # bits to support the subset editor

    subset_list = List(ISubset, estimate=True)
    subset = Property(Str, depends_on="subset_list.str")

    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join(
            [subset.str for subset in self.subset_list if subset.str])

    @on_trait_change('subset_list.str', post_init=True)
    def _subset_changed(self, obj, name, old, new):
        self.changed = (Changed.ESTIMATE, ('subset_list', self.subset_list))

    def default_view(self, **kwargs):
        return GaussianMixture2DPluginView(op=self, **kwargs)

    def estimate(self, experiment):
        GaussianMixture2DOp.estimate(self, experiment, subset=self.subset)
        self.changed = (Changed.ESTIMATE_RESULT, self)

    def clear_estimate(self):
        self._gmms.clear()
        self._xscale = None
        self._yscale = None
        self.changed = (Changed.ESTIMATE_RESULT, self)

    def should_clear_estimate(self, changed):
        if changed == Changed.ESTIMATE:
            return True

        return False
Esempio n. 6
0
class BinningWorkflowOp(WorkflowOperation, BinningOp):
    name = Str(apply=True)
    channel = Str(apply=True)
    bin_width = util.PositiveCFloat(None,
                                    allow_zero=False,
                                    allow_none=True,
                                    apply=True)
    scale = util.ScaleEnum(apply=True)

    def default_view(self, **kwargs):
        return BinningWorkflowView(op=self, **kwargs)

    def get_notebook_code(self, idx):
        op = BinningOp()
        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))
Esempio n. 7
0
class KMeansPluginOp(PluginOpMixin, KMeansOp):
    handler_factory = Callable(FlowPeaksHandler)

    # add "estimate" metadata
    xchannel = Str(estimate=True)
    ychannel = Str(estimate=True)
    xscale = util.ScaleEnum(estimate=True)
    yscale = util.ScaleEnum(estimate=True)
    num_clusters = util.PositiveCInt(2, allow_zero=False, estimate=True)

    by = List(Str, estimate=True)

    # bits to support the subset editor

    subset_list = List(ISubset, estimate=True)
    subset = Property(Str, depends_on="subset_list.str")

    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join(
            [subset.str for subset in self.subset_list if subset.str])

    @on_trait_change('subset_list.str')
    def _subset_changed(self, obj, name, old, new):
        self.changed = (Changed.ESTIMATE, ('subset_list', self.subset_list))

    @on_trait_change('xchannel, ychannel')
    def _channel_changed(self, obj, name, old, new):
        self.channels = []
        self.scale = {}
        if self.xchannel:
            self.channels.append(self.xchannel)

            if self.xchannel in self.scale:
                del self.scale[self.xchannel]

            self.scale[self.xchannel] = self.xscale

        if self.ychannel:
            self.channels.append(self.ychannel)

            if self.ychannel in self.scale:
                del self.scale[self.ychannel]

            self.scale[self.ychannel] = self.yscale

    @on_trait_change('xscale, yscale')
    def _scale_changed(self, obj, name, old, new):
        self.scale = {}

        if self.xchannel:
            self.scale[self.xchannel] = self.xscale

        if self.ychannel:
            self.scale[self.ychannel] = self.yscale

    def default_view(self, **kwargs):
        return KMeansPluginView(op=self, **kwargs)

    def estimate(self, experiment):
        if not self.xchannel:
            raise util.CytoflowOpError('xchannel', "Must set X channel")

        if not self.ychannel:
            raise util.CytoflowOpError('ychannel', "Must set Y channel")

        try:
            super().estimate(experiment, subset=self.subset)
        except:
            raise
        finally:
            self.changed = (Changed.ESTIMATE_RESULT, self)

    def clear_estimate(self):
        self._kmeans.clear()
        self.changed = (Changed.ESTIMATE_RESULT, self)

    def get_notebook_code(self, idx):
        op = KMeansOp()
        op.copy_traits(self, op.copyable_trait_names())

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """.format(repr=repr(op),
                   idx=idx,
                   prev_idx=idx - 1,
                   subset=", subset = " +
                   repr(self.subset) if self.subset else ""))
Esempio n. 8
0
class PolygonOp(HasStrictTraits):
    """
    Apply a polygon gate to a cytometry experiment.
    
    Attributes
    ----------
    name : Str
        The operation name.  Used to name the new metadata field in the
        experiment that's created by :meth:`apply`
        
    xchannel, ychannel : Str
        The names of the x and y channels to apply the gate.
        
    xscale, yscale : {'linear', 'log', 'logicle'} (default = 'linear')
        The scales applied to the data before drawing the polygon.
        
    vertices : List((Float, Float))
        The polygon verticies.  An ordered list of 2-tuples, representing
        the x and y coordinates of the vertices.
        
    Notes
    -----
    This module uses :meth:`matplotlib.path.Path` to represent the polygon, because
    membership testing is very fast.
    
    You can set the verticies by hand, I suppose, but it's much easier to use
    the interactive view you get from :meth:`default_view` to do so.

    
    Examples
    --------
    
    .. plot::
        :context: close-figs
        
        Make a little data set.
    
        >>> import cytoflow as flow
        >>> import_op = flow.ImportOp()
        >>> import_op.tubes = [flow.Tube(file = "Plate01/RFP_Well_A3.fcs",
        ...                              conditions = {'Dox' : 10.0}),
        ...                    flow.Tube(file = "Plate01/CFP_Well_A4.fcs",
        ...                              conditions = {'Dox' : 1.0})]
        >>> import_op.conditions = {'Dox' : 'float'}
        >>> ex = import_op.apply()
    
    Create and parameterize the operation.
    
    .. plot::
        :context: close-figs
        
        >>> p = flow.PolygonOp(name = "Polygon",
        ...                    xchannel = "V2-A",
        ...                    ychannel = "Y2-A")
        >>> p.vertices = [(23.411982294776319, 5158.7027015021222), 
        ...               (102.22182270573683, 23124.058843387455), 
        ...               (510.94519955277201, 23124.058843387455), 
        ...               (1089.5215641232173, 3800.3424832180476), 
        ...               (340.56382570202402, 801.98947404942271), 
        ...               (65.42597937575897, 1119.3133482602157)]

        
    Show the default view.  

    .. plot::
        :context: close-figs
            
        >>> df = p.default_view(huefacet = "Dox",
        ...                    xscale = 'log',
        ...                    yscale = 'log')
        
        >>> df.plot(ex)
        
    
    .. note::
       If you want to use the interactive default view in a Jupyter notebook,
       make sure you say ``%matplotlib notebook`` in the first cell 
       (instead of ``%matplotlib inline`` or similar).  Then call 
       ``default_view()`` with ``interactive = True``::
       
           df = p.default_view(huefacet = "Dox",
                               xscale = 'log',
                               yscale = 'log',
                               interactive = True)
           df.plot(ex)
        
    Apply the gate, and show the result
    
    .. plot::
        :context: close-figs
        
        >>> ex2 = p.apply(ex)
        >>> ex2.data.groupby('Polygon').size()
        Polygon
        False    15875
        True      4125
        dtype: int64
            
    """

    # traits
    id = Constant('edu.mit.synbio.cytoflow.operations.polygon')
    friendly_id = Constant("Polygon")

    name = CStr()
    xchannel = Str()
    ychannel = Str()
    vertices = List((Float, Float))

    xscale = util.ScaleEnum()
    yscale = util.ScaleEnum()

    _selection_view = Instance('PolygonSelection', transient=True)

    def apply(self, experiment):
        """Applies the threshold to an experiment.
        
        Parameters
        ----------
        experiment : Experiment
            the old :class:`Experiment` to which this op is applied
            
        Returns
        -------
        Experiment
            a new :class:'Experiment`, the same as ``old_experiment`` but with 
            a new column of type `bool` with the same as the operation name.  
            The bool is ``True`` if the event's measurement is within the 
            polygon, and ``False`` otherwise.
            
        Raises
        ------
        util.CytoflowOpError
            if for some reason the operation can't be applied to this
            experiment. The reason is in :attr:`.CytoflowOpError.args`
        """

        if experiment is None:
            raise util.CytoflowOpError('experiment', "No experiment specified")

        if self.name in experiment.data.columns:
            raise util.CytoflowOpError(
                'name', "{} is in the experiment already!".format(self.name))

        if not self.xchannel:
            raise util.CytoflowOpError('xchannel', "Must specify an x channel")

        if not self.ychannel:
            raise util.CytoflowOpError('ychannel', "Must specify a y channel")

        if not self.xchannel in experiment.channels:
            raise util.CytoflowOpError(
                'xchannel',
                "xchannel {0} is not in the experiment".format(self.xchannel))

        if not self.ychannel in experiment.channels:
            raise util.CytoflowOpError(
                'ychannel',
                "ychannel {0} is not in the experiment".format(self.ychannel))

        if len(self.vertices) < 3:
            raise util.CytoflowOpError('vertices',
                                       "Must have at least 3 vertices")

        if any([len(x) != 2 for x in self.vertices]):
            return util.CytoflowOpError(
                'vertices', "All vertices must be lists or tuples "
                "of length = 2")

        # make sure name got set!
        if not self.name:
            raise util.CytoflowOpError(
                'name', "You have to set the Polygon gate's name "
                "before applying it!")

        # make sure old_experiment doesn't already have a column named self.name
        if (self.name in experiment.data.columns):
            raise util.CytoflowOpError(
                'name',
                "Experiment already contains a column {0}".format(self.name))

        # there's a bit of a subtlety here: if the vertices were
        # selected with an interactive plot, and that plot had scaled
        # axes, we need to apply that scale function to both the
        # vertices and the data before looking for path membership
        xscale = util.scale_factory(self.xscale,
                                    experiment,
                                    channel=self.xchannel)
        yscale = util.scale_factory(self.yscale,
                                    experiment,
                                    channel=self.ychannel)

        vertices = [(xscale(x), yscale(y)) for (x, y) in self.vertices]
        data = experiment.data[[self.xchannel, self.ychannel]].copy()
        data[self.xchannel] = xscale(data[self.xchannel])
        data[self.ychannel] = yscale(data[self.ychannel])

        # use a matplotlib Path because testing for membership is a fast C fn.
        path = mpl.path.Path(np.array(vertices))
        xy_data = data[[self.xchannel, self.ychannel]].values

        new_experiment = experiment.clone()
        new_experiment.add_condition(self.name, "bool",
                                     path.contains_points(xy_data))
        new_experiment.history.append(
            self.clone_traits(transient=lambda _: True))

        return new_experiment

    def default_view(self, **kwargs):
        self._selection_view = PolygonSelection(op=self)
        self._selection_view.trait_set(**kwargs)
        return self._selection_view
Esempio n. 9
0
class GaussianMixture1DPluginOp(PluginOpMixin, GaussianMixtureOp):
    id = Constant('edu.mit.synbio.cytoflowgui.operations.gaussian_1d')

    handler_factory = Callable(GaussianMixture1DHandler)

    channel = Str
    channel_scale = util.ScaleEnum(estimate=True)

    # add "estimate" metadata
    num_components = util.PositiveCInt(1, estimate=True)
    sigma = util.PositiveCFloat(0.0, allow_zero=True, estimate=True)
    by = List(Str, estimate=True)

    # bits to support the subset editor

    subset_list = List(ISubset, estimate=True)
    subset = Property(Str, depends_on="subset_list.str")

    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join(
            [subset.str for subset in self.subset_list if subset.str])

    @on_trait_change('subset_list.str')
    def _subset_changed(self, obj, name, old, new):
        self.changed = (Changed.ESTIMATE, ('subset_list', self.subset_list))

    _gmms = Dict(Any, Instance(mixture.GaussianMixture), transient=True)

    @on_trait_change('channel')
    def _channel_changed(self):
        self.channels = [self.channel]
        self.changed = (Changed.ESTIMATE, ('channels', self.channels))

        if self.channel_scale:
            self.scale = {self.channel: self.channel_scale}
            self.changed = (Changed.ESTIMATE, ('scale', self.scale))

    @on_trait_change('channel_scale')
    def _scale_changed(self):
        if self.channel:
            self.scale = {self.channel: self.channel_scale}
        self.changed = (Changed.ESTIMATE, ('scale', self.scale))

    def estimate(self, experiment):
        super().estimate(experiment, subset=self.subset)
        self.changed = (Changed.ESTIMATE_RESULT, self)

    def default_view(self, **kwargs):
        return GaussianMixture1DPluginView(op=self, **kwargs)

    def clear_estimate(self):
        self._gmms = {}
        self._scale = {}
        self.changed = (Changed.ESTIMATE_RESULT, self)

    def get_notebook_code(self, idx):
        op = GaussianMixtureOp()
        op.copy_traits(self, op.copyable_trait_names())

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """.format(repr=repr(op),
                   idx=idx,
                   prev_idx=idx - 1,
                   subset=", subset = " +
                   repr(self.subset) if self.subset else ""))
Esempio n. 10
0
class FlowPeaksWorkflowOp(WorkflowOperation, FlowPeaksOp):
    # add "apply" and "estimate" metadata
    name = Str(apply = True)
    xchannel = Str(estimate = True)
    ychannel = Str(estimate = True)
    xscale = util.ScaleEnum(estimate = True)
    yscale = util.ScaleEnum(estimate = True)
    h = util.PositiveCFloat(1.5, allow_zero = False, estimate = True)
    h0 = util.PositiveCFloat(1, allow_zero = False, estimate = True)
    tol = util.PositiveCFloat(0.5, allow_zero = False, estimate = True)
    merge_dist = util.PositiveCFloat(5, allow_zero = False, estimate = True)
    
    by = List(Str, estimate = True)
    
    # add the 'estimate_result' metadata
    _cluster_peak = Dict(Any, List, transient = True, estimate_result = True)

    # override the base class's "subset" with one that is dynamically generated /
    # updated from subset_list
    subset = Property(Str, observe = "subset_list.items.str")
    subset_list = List(ISubset, estimate = True)
        
    # bits to support the subset editor
    @observe('subset_list:items.str')
    def _on_subset_changed(self, _):
        self.changed = 'subset_list'
        
    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join([subset.str for subset in self.subset_list if subset.str])
    
    # @on_trait_change('xchannel, ychannel')
    # def _channel_changed(self):
    #     self.channels = []
    #     self.scale = {}
    #     if self.xchannel:
    #         self.channels.append(self.xchannel)
    #
    #         if self.xchannel in self.scale:
    #             del self.scale[self.xchannel]
    #
    #         self.scale[self.xchannel] = self.xscale
    #
    #     if self.ychannel:
    #         self.channels.append(self.ychannel)
    #
    #         if self.ychannel in self.scale:
    #             del self.scale[self.ychannel]
    #
    #         self.scale[self.ychannel] = self.yscale
    #
    #
    # @on_trait_change('xscale, yscale')
    # def _scale_changed(self):
    #     self.scale = {}
    #
    #     if self.xchannel:
    #         self.scale[self.xchannel] = self.xscale
    #
    #     if self.ychannel:
    #         self.scale[self.ychannel] = self.yscale
            

    def default_view(self, **kwargs):
        return FlowPeaksWorkflowView(op = self, **kwargs)
    
    def estimate(self, experiment):
        if not self.xchannel:
            raise util.CytoflowOpError('xchannel',
                                       "Must set X channel")
            
        if not self.ychannel:
            raise util.CytoflowOpError('ychannel',
                                       "Must set Y channel")
            
            
        self.channels = [self.xchannel, self.ychannel]
        self.scale = {self.xchannel : self.xscale,
                      self.ychannel : self.yscale}
            
        super().estimate(experiment, subset = self.subset)
        
    def apply(self, experiment):
        if not self._cluster_group:
            raise util.CytoflowOpError(None, 'Click "Estimate"!')
        return super().apply(experiment)
    
    def clear_estimate(self):
        self._kmeans = {}
        self._means = {}
        self._normals = {}
        self._density = {}
        self._peaks = {}
        self._peak_clusters = {}
        self._cluster_peak = {}
        self._cluster_group = {}
        self._scale = {}
            
    def get_notebook_code(self, idx):
        op = FlowPeaksOp()
        op.copy_traits(self, op.copyable_trait_names())    
        op.channels = [self.xchannel, self.ychannel]  
        op.scale = {self.xchannel : self.xscale,
                    self.ychannel : self.yscale}

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """
        .format(repr = repr(op),
                idx = idx,
                prev_idx = idx - 1,
                subset = ", subset = " + repr(self.subset) if self.subset else ""))
Esempio n. 11
0
class KMeansWorkflowOp(WorkflowOperation, KMeansOp):
    # add "apply", "estimate" metadata
    name = Str(apply=True)
    xchannel = Str(estimate=True)
    ychannel = Str(estimate=True)
    xscale = util.ScaleEnum(estimate=True)
    yscale = util.ScaleEnum(estimate=True)
    num_clusters = util.PositiveCInt(2, allow_zero=False, estimate=True)

    by = List(Str, estimate=True)

    # add the 'estimate_result' metadata
    _kmeans = Dict(Any,
                   Instance(sklearn.cluster.MiniBatchKMeans),
                   transient=True,
                   estimate_result=True)

    # override the base class's "subset" with one that is dynamically generated /
    # updated from subset_list
    subset = Property(Str, observe="subset_list.items.str")
    subset_list = List(ISubset, estimate=True)

    # bits to support the subset editor
    @observe('subset_list:items.str')
    def _on_subset_changed(self, _):
        self.changed = 'subset_list'

    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join(
            [subset.str for subset in self.subset_list if subset.str])

    def default_view(self, **kwargs):
        return KMeansWorkflowView(op=self, **kwargs)

    def estimate(self, experiment):
        if not self.xchannel:
            raise util.CytoflowOpError('xchannel', "Must set X channel")

        if not self.ychannel:
            raise util.CytoflowOpError('ychannel', "Must set Y channel")

        self.channels = [self.xchannel, self.ychannel]
        self.scale = {self.xchannel: self.xscale, self.ychannel: self.yscale}

        super().estimate(experiment, subset=self.subset)

    def apply(self, experiment):
        if not self._kmeans:
            raise util.CytoflowOpError(None, 'Click "Estimate"!')
        return KMeansOp.apply(self, experiment)

    def clear_estimate(self):
        self._kmeans = {}
        self._scale = {}

    def get_notebook_code(self, idx):
        op = KMeansOp()
        op.copy_traits(self, op.copyable_trait_names())
        op.channels = [self.xchannel, self.ychannel]
        op.scale = {self.xchannel: self.xscale, self.ychannel: self.yscale}

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """.format(repr=repr(op),
                   idx=idx,
                   prev_idx=idx - 1,
                   subset=", subset = " +
                   repr(self.subset) if self.subset else ""))
Esempio n. 12
0
class GaussianMixture1DWorkflowOp(WorkflowOperation, GaussianMixtureOp):
    # override id so we can differentiate the 1D and 2D ops
    id = Constant('edu.mit.synbio.cytoflowgui.operations.gaussian_1d')

    # add 'estimate' and 'apply' metadata
    name = Str(apply=True)
    channel = Str(estimate=True)
    channel_scale = util.ScaleEnum(estimate=True)
    num_components = util.PositiveCInt(1, allow_zero=False, estimate=True)
    sigma = util.PositiveCFloat(None,
                                allow_zero=True,
                                allow_none=True,
                                estimate=True)
    by = List(Str, estimate=True)

    # add the 'estimate_result' metadata
    _gmms = Dict(Any,
                 Instance(mixture.GaussianMixture),
                 transient=True,
                 estimate_result=True)

    # override the base class's "subset" with one that is dynamically generated /
    # updated from subset_list
    subset = Property(Str, observe="subset_list.items.str")
    subset_list = List(ISubset, estimate=True)

    # bits to support the subset editor
    @observe('subset_list:items.str')
    def _on_subset_changed(self, _):
        self.changed = 'subset_list'

    # MAGIC - returns the value of the "subset" Property, above
    def _get_subset(self):
        return " and ".join(
            [subset.str for subset in self.subset_list if subset.str])

    def estimate(self, experiment):
        self.channels = [self.channel]
        self.scale = {self.channel: self.channel_scale}
        super().estimate(experiment, subset=self.subset)

    def apply(self, experiment):
        if not self._gmms:
            raise util.CytoflowOpError(None, 'Click "Estimate"!')
        return GaussianMixtureOp.apply(self, experiment)

    def default_view(self, **kwargs):
        return GaussianMixture1DWorkflowView(op=self, **kwargs)

    def clear_estimate(self):
        self._gmms = {}
        self._scale = {}

    def get_notebook_code(self, idx):
        op = GaussianMixtureOp()
        op.copy_traits(self, op.copyable_trait_names())

        op.channels = [self.channel]
        op.scale = {self.channel: self.channel_scale}

        return dedent("""
        op_{idx} = {repr}
        
        op_{idx}.estimate(ex_{prev_idx}{subset})
        ex_{idx} = op_{idx}.apply(ex_{prev_idx})
        """.format(repr=repr(op),
                   idx=idx,
                   prev_idx=idx - 1,
                   subset=", subset = " +
                   repr(self.subset) if self.subset else ""))