Source code for cytoflowgui.editors.subset_list_editor

#!/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.editors.subset_list_editor
--------------------------------------

An editor for lists of `ISubset`. For each entry in the list, creates 
an appropriate instance editor, each set up with appropriate ranges, 
values, etc. from the experiment conditions and metadata.
"""

import pandas as pd

from traits.api import Dict, Str, Bool, Any, Trait, on_trait_change
from traits.trait_handlers import TraitPrefixList

import cytoflow.utility as util

from cytoflowgui.workflow.subset import BoolSubset, RangeSubset, CategorySubset

from .vertical_list_editor import _VerticalListEditor, VerticalListEditor

class _SubsetListEditor(_VerticalListEditor):

    conditions = Dict(Str, pd.Series)
    metadata = Dict(Str, Any)
    when = Str
    scrollable = False

    def init(self, parent):
        
        if self.factory.metadata:
            self.sync_value(self.factory.metadata, 'metadata', 'from', is_list = True)
             
        self.when = self.factory.when
        
        self.sync_value(self.factory.conditions, 'conditions', 'from', is_list = True)
        
        _VerticalListEditor.init(self, parent)
        
    @on_trait_change('conditions, metadata', dispatch = 'ui')
    def _on_conditions_change(self, obj, name, old, new):
        value_names = set([subset.name for subset in self.value])
        condition_names = set([x for x in list(self.conditions.keys()) if self.include_condition(x)])
        
        loading = (self.ui.context["context"].status == "loading")
        
        if not loading:
            for name in value_names - condition_names:
                # remove subsets that aren't in conditions
                subset = next((x for x in self.value if x.name == name))
                self.value.remove(subset)
            
        for name in condition_names - value_names:
            # add subsets that are new conditions
            values = self.conditions[name].sort_values()
            dtype = pd.Series(list(values)).dtype
            if dtype.kind == 'b':
                subset = BoolSubset(name = name)
            elif dtype.kind in "ifu":
                subset = RangeSubset(name = name,
                                     values = list(values))
            elif dtype.kind in "OSU":
                subset = CategorySubset(name = name,
                                        values = sorted(list(values)))
            else:
                raise util.CytoflowError("Unknown dtype {} in ViewController"
                                         .format(dtype))
             
            self.value.append(subset)
        
        for name in condition_names & value_names:
            # update values for subsets we're already tracking
            subset = next((x for x in self.value if x.name == name))
            if set(subset.values) != set(self.conditions[name]):
                subset.values = list(self.conditions[name].sort_values())
                
        self.value = sorted(self.value, key = lambda x: x.name)
                
    def include_condition(self, condition):
        if not self.when:
            return True
         
        if condition in self.metadata:
            try:
                return eval(self.when, globals(), self.metadata[condition])
            except:
                raise util.CytoflowError("Bad when statement: {}"
                                         .format(self.when))
        else:
            return False


[docs]class SubsetListEditor(VerticalListEditor): conditions = Str """The name of the trait containing the names --> values dict""" metadata = Str """The name of the trait containing the metadata dictionary""" when = Str """ A string to evaluate on the metadata to see if we include this condition in the editor """ # override some defaults style = Trait("custom", TraitPrefixList('simple', 'custom', 'text', 'readonly')) mutable = Bool(False) # use the custom editor above, which extends the qt4.ListEditor class def _get_simple_editor_class(self): return _SubsetListEditor