def default_traits_view(self): return View(Item("channels", editor = CheckListEditor(cols = 2, name = 'context.previous_wi.channels'), style = 'custom'), VGroup( Item('blank_file'), label = "Autofluorescence"), VGroup( Item('bleedthrough_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.bleedthrough_traits_view()), style = 'custom', mutable = False), style = 'custom'), label = "Bleedthrough Correction", show_border = False, show_labels = False), VGroup( Item('beads_name', editor = EnumEditor(name = 'handler.beads_name_choices'), label = "Beads", width = -125), Item('beads_file'), Item('beads_unit', editor = EnumEditor(name = 'handler.beads_units')), Item('bead_peak_quantile', label = "Peak\nQuantile"), Item('bead_brightness_threshold', label = "Peak\nThreshold "), Item('bead_brightness_cutoff', label = "Peak\nCutoff"), label = "Bead Calibration", show_border = False), VGroup( Item('to_channel', editor = EnumEditor(name = 'channels')), Item('mixture_model', label = "Use mixture\nmodel?"), label = "Color Translation"), VGroup( Item('translation_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.translation_traits_view()), style = 'custom', mutable = False), style = 'custom'), show_labels = False), VGroup(Item('subset_list', show_label = False, editor = SubsetListEditor(conditions = "context.previous_wi.conditions", metadata = "context.previous_wi.metadata", when = "'experiment' not in vars() or not experiment")), label = "Subset", show_border = False, show_labels = False), Item('do_estimate', editor = ButtonEditor(value = True, label = "Estimate!"), show_label = False), shared_op_traits)
def default_traits_view(self): return View(VGroup(Item('controls_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.control_traits_view()), style = 'custom', mutable = False), style = 'custom'), Item('handler.add_control', editor = ButtonEditor(value = True, label = "Add a control")), Item('handler.remove_control', editor = ButtonEditor(value = True, label = "Remove a control")), label = "Controls", show_labels = False), Item('mixture_model', label = "Use mixture\nmodel?"), VGroup(Item('subset_list', show_label = False, editor = SubsetListEditor(conditions = "context.previous_wi.conditions", metadata = "context.previous_wi.metadata", when = "'experiment' not in vars() or not experiment")), label = "Subset", show_border = False, show_labels = False), Item('do_estimate', editor = ButtonEditor(value = True, label = "Estimate!"), show_label = False), shared_op_traits)
def default_traits_view(self): return View( VGroup( Item('beads_name', editor=EnumEditor(name='handler.beads_name_choices'), label="Beads", width=-125), Item('beads_file', width=-125)), VGroup(Item('units_list', editor=VerticalListEditor(editor=InstanceEditor( view=self.unit_traits_view()), style='custom', mutable=False), style='custom'), Item('handler.add_channel', editor=ButtonEditor(value=True, label="Add a channel")), Item('handler.remove_channel', editor=ButtonEditor(value=True, label="Remove a channel")), label="Controls", show_labels=False), Item('bead_peak_quantile', label="Peak\nQuantile"), Item('bead_brightness_threshold', label="Peak\nThreshold "), Item('bead_brightness_cutoff', label="Peak\nCutoff"), Item('do_estimate', editor=ButtonEditor(value=True, label="Estimate!"), show_label=False), shared_op_traits)
def default_traits_view(self): return View( VGroup(Item('channels_list', editor=VerticalListEditor(editor=InstanceEditor( view=self.channel_traits_view()), style='custom', mutable=False), style='custom'), Item('handler.add_channel', editor=ButtonEditor(value=True, label="Add a channel")), Item('handler.remove_channel', editor=ButtonEditor(value=True, label="Remove a channel")), show_labels=False), VGroup(Item('xfacet', editor=ExtendableEnumEditor( name='handler.conditions_names', extra_items={"None": ""}), label="Horizontal\nFacet"), Item('yfacet', editor=ExtendableEnumEditor( name='handler.conditions_names', extra_items={"None": ""}), label="Vertical\nFacet"), Item('huefacet', editor=ExtendableEnumEditor( name='handler.conditions_names', extra_items={"None": ""}), label="Color\nFacet"), Item('huescale', label="Color\nScale"), Item('plotfacet', editor=ExtendableEnumEditor( name='handler.conditions_names', extra_items={"None": ""}), label="Tab\nFacet"), label="2D Histogram", show_border=False), VGroup(Item( 'subset_list', show_label=False, editor=SubsetListEditor(conditions="context.conditions")), label="Subset", show_border=False, show_labels=False), Item('context.view_warning', resizable=True, visible_when='context.view_warning', editor=ColorTextEditor(foreground_color="#000000", background_color="#ffff99")), Item('context.view_error', resizable=True, visible_when='context.view_error', editor=ColorTextEditor(foreground_color="#000000", background_color="#ff9191")))
def default_traits_view(self): return View(Item('name', editor = TextEditor(auto_set = False)), VGroup(Item('channels_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.channel_traits_view()), style = 'custom', mutable = False), style = 'custom'), Item('handler.add_channel', editor = ButtonEditor(value = True, label = "Add a channel"), show_label = False), Item('handler.remove_channel', editor = ButtonEditor(value = True, label = "Remove a channel")), show_labels = False), VGroup(Item('num_components', editor = TextEditor(auto_set = False), label = "Num\nComponents"), Item('whiten'), Item('by', editor = CheckListEditor(cols = 2, name = 'handler.previous_conditions_names'), label = 'Group\nEstimates\nBy', style = 'custom'), label = "Estimate parameters"), VGroup(Item('subset_list', show_label = False, editor = SubsetListEditor(conditions = "context.previous_wi.conditions", metadata = "context.previous_wi.metadata", when = "'experiment' not in vars() or not experiment")), label = "Subset", show_border = False, show_labels = False), Item('do_estimate', editor = ButtonEditor(value = True, label = "Estimate!"), show_label = False), shared_op_traits)
def default_traits_view(self): return View( VGroup(Label(label="Channels", visible_when='model.tubes'), Item('object.channels_list', editor=VerticalListEditor(editor=InstanceEditor(), style='custom', mutable=False, deletable=True), show_label=False), Item('handler.reset_channels', show_label=False), visible_when='object.channels_list'), Item('object.events', editor=TextEditor(auto_set=False, format_func=lambda x: "" if x == None else str(x)), label="Events per\nsample"), Item('handler.samples', label='Samples', style='readonly'), Item('ret_events', label='Events', style='readonly'), Item('handler.setup_event', show_label=False), Item('do_estimate', editor=ButtonEditor(value=True, label="Import!"), show_label=False), shared_op_traits)
def default_traits_view(self): return View( VGroup( Item('blank_file'), label = "Autofluorescence"), VGroup( Item('fsc_channel', editor = EnumEditor(name = '_blank_exp_channels'), label = "Forward Scatter Channel"), Item('ssc_channel', editor = EnumEditor(name = '_blank_exp_channels'), label = "Side Scatter Channel"), label = "Morphology"), VGroup( Item("channels", editor = CheckListEditor(cols = 2, name = '_blank_exp_channels'), style = 'custom'), label = "Channels To Calibrate", show_labels = False), VGroup( Item('bleedthrough_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.bleedthrough_traits_view()), style = 'custom', mutable = False), style = 'custom'), label = "Bleedthrough Correction", show_border = False, show_labels = False), VGroup( Item('beads_name', editor = EnumEditor(name = 'handler.beads_name_choices'), label = "Beads", width = -125), Item('beads_file'), Item('units_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.unit_traits_view()), style = 'custom', mutable = False), style = 'custom', label = "Bead\nunits"), Item('bead_peak_quantile', label = "Peak\nQuantile"), Item('bead_brightness_threshold', label = "Peak\nThreshold "), Item('bead_brightness_cutoff', label = "Peak\nCutoff"), label = "Bead Calibration", show_border = False), VGroup( Item('do_color_translation', label = "Do color translation?", editor = ToggleButtonEditor(), show_label = False), Item('to_channel', editor = EnumEditor(name = 'channels'), visible_when = 'do_color_translation == True'), Item('mixture_model', label = "Use mixture\nmodel?", visible_when = 'do_color_translation == True'), VGroup( Item('translation_list', editor = VerticalListEditor(editor = InstanceEditor(view = self.translation_traits_view()), style = 'custom', mutable = False), style = 'custom'), show_labels = False, visible_when = 'do_color_translation == True'), label = "Color Translation", show_border = False), VGroup( Item('status', style = 'readonly'), Item('output_directory'), Item('do_estimate', editor = ButtonEditor(value = True, label = "Estimate parameters"), show_label = False), Item('handler.do_convert', editor = ButtonEditor(value = True, label = "Convert files..."), enabled_when = "valid_model == True", show_label = False), label = "Output", show_border = False), Item('do_exit', editor = ButtonEditor(value = True, label = "Return to Cytoflow"), show_label = False), shared_op_traits)
class ExperimentDialogHandler(Controller): # bits for model initialization import_op = Instance('cytoflowgui.op_plugins.import_op.ImportPluginOp') # events add_tubes = Event remove_tubes = Event add_variable = Event import_csv = Event # traits to communicate with the TabularEditor selected_tubes = List default_view = View( HGroup( VGroup( Label("Variables"), Item('tube_traits', editor = VerticalListEditor(editor = InstanceEditor(), style = 'custom', mutable = False, deletable = True), show_label = False), HGroup( Item('handler.add_variable', editor = ButtonEditor(label = "Add a variable"), show_label = False))), VGroup( Label("Tubes"), Item(name = 'tubes', id = 'table', editor = TableEditor(editable = True, sortable = True, auto_size = True, configurable = False, selection_mode = 'rows', selected = 'handler.selected_tubes', columns = [ObjectColumn(name = 'index', read_only_cell_color = 'lightgrey', editable = False)]), enabled_when = "object.tubes", show_label = False), HGroup( Item('handler.add_tubes', editor = ButtonEditor(label = "Add tubes..."), show_label = False), Item('handler.remove_tubes', editor = ButtonEditor(label = "Remove tubes"), show_label = False, enabled_when = 'object.tubes'), Item('handler.import_csv', editor = ButtonEditor(label = "Import from CSV..."), show_label = False)))), title = 'Experiment Setup', buttons = OKCancelButtons, resizable = True, width = 0.3, height = 0.3 ) # keep a ref to the table editor so we can add columns dynamically table_editor = Instance(TableEditorQt) updating = Bool(False) def init(self, info): # save a reference to the table editor self.table_editor = info.ui.get_editors('tubes')[0] # init the model self.model.init(self.import_op) return True def close(self, info, is_ok): """ Handles the user attempting to close a dialog-based user interface. This method is called when the user attempts to close a window, by clicking an **OK** or **Cancel** button, or clicking a Close control on the window). It is called before the window is actually destroyed. Override this method to perform any checks before closing a window. While Traits UI handles "OK" and "Cancel" events automatically, you can use the value of the *is_ok* parameter to implement additional behavior. Parameters ---------- info : UIInfo object The UIInfo object associated with the view is_ok : Boolean Indicates whether the user confirmed the changes (such as by clicking **OK**.) Returns ------- allow_close : bool A Boolean, indicating whether the window should be allowed to close. """ if is_ok: if not self.model.valid: error(None, "Each tube must have a unique set of experimental conditions", "Invalid experiment!") return False if not is_ok: # we don't need to "undo" anything, we're throwing this model away info.ui.history = None return True def closed(self, info, is_ok): for trait in self.model.tube_traits: if trait.type != 'metadata': for tube in self.model.tubes: tube.on_trait_change(self._try_multiedit, trait.name, remove = True) if is_ok: self.model.update_import_op(self.import_op) @on_trait_change('add_variable') def _on_add_variable(self): self.model.tube_traits.append(TubeTrait(model = self.model)) @on_trait_change('import_csv') def _on_import(self): """ Import format: CSV, first column is filename, path relative to CSV. others are conditions, type is autodetected. first row is header with names. """ file_dialog = FileDialog() file_dialog.wildcard = "CSV files (*.csv)|*.csv|" file_dialog.action = 'open' file_dialog.open() if file_dialog.return_code != PyfaceOK: return csv = pandas.read_csv(file_dialog.path) csv_folder = Path(file_dialog.path).parent if self.model.tubes or self.model.tube_traits: if confirm(parent = None, message = "This will clear the current conditions and tubes! " "Are you sure you want to continue?", title = "Clear tubes and conditions?") != YES: return for col in csv.columns[1:]: self.model.tube_traits.append(TubeTrait(model = self.model, name = util.sanitize_identifier(col), type = 'category')) for _, row in csv.iterrows(): filename = csv_folder / row[0] try: metadata, _ = parse_tube(str(filename), metadata_only = True) except Exception as e: warning(None, "Had trouble loading file {}: {}".format(filename, str(e))) continue metadata['CF_File'] = Path(filename).stem new_tube = Tube(file = str(filename), parent = self.model, metadata = sanitize_metadata(metadata)) self.model.tubes.append(new_tube) for col in csv.columns[1:]: new_tube.trait_set(**{util.sanitize_identifier(col) : row[col]}) @on_trait_change('add_tubes') def _on_add_tubes(self): """ Handle "Add tubes..." button. Add tubes to the experiment. """ file_dialog = FileDialog() file_dialog.wildcard = "Flow cytometry files (*.fcs *.lmd)|*.fcs *.lmd|" file_dialog.action = 'open files' file_dialog.open() if file_dialog.return_code != PyfaceOK: return for path in file_dialog.paths: try: metadata, _ = parse_tube(path, metadata_only = True) except Exception as e: raise RuntimeError("FCS reader threw an error on tube {0}: {1}"\ .format(path, e.value)) # if we're the first tube loaded, create a dummy experiment # and setup default metadata columns if not self.model.dummy_experiment: self.model.dummy_experiment = \ ImportOp(tubes = [CytoflowTube(file = path)]).apply(metadata_only = True) # check the next tube against the dummy experiment try: check_tube(path, self.model.dummy_experiment) except util.CytoflowError as e: error(None, e.__str__(), "Error importing tube") return metadata['CF_File'] = Path(path).stem tube = Tube(file = path, parent = self.model, metadata = sanitize_metadata(metadata)) self.model.tubes.append(tube) for trait in self.model.tube_traits: if trait.type != 'metadata' and trait.name: tube.on_trait_change(self._try_multiedit, trait.name) @on_trait_change('remove_tubes') def _on_remove_tubes(self): conf = confirm(None, "Are you sure you want to remove the selected tube(s)?", "Remove tubes?") if conf == YES: for tube in self.selected_tubes: self.model.tubes.remove(tube) if not self.model.tubes: self.model.dummy_experiment = None @on_trait_change('model:tube_traits_items', post_init = True) def _tube_traits_changed(self, event): for trait in event.added: if not trait.name: continue if self.table_editor: self.table_editor.columns.append(ExperimentColumn(name = trait.name, editable = (trait.type != 'metadata'))) for tube in self.model.tubes: if trait.type != 'metadata' and trait.name: tube.on_trait_change(self._try_multiedit, trait.name) for trait in event.removed: if not trait.name: continue table_column = next((x for x in self.table_editor.columns if x.name == trait.name)) self.table_editor.columns.remove(table_column) for tube in self.model.tubes: if trait.type != 'metadata' and trait.name: tube.on_trait_change(self._try_multiedit, trait.name, remove = True) @on_trait_change('model:tube_traits:name') def _tube_trait_name_changed(self, trait, _, old_name, new_name): if old_name: old_table_column = next((x for x in self.table_editor.columns if x.name == old_name)) column_idx = self.table_editor.columns.index(old_table_column) else: column_idx = len(self.table_editor.columns) if new_name: self.table_editor.columns.insert(column_idx, ExperimentColumn(name = new_name, editable = (trait.type != 'metadata'))) if old_name: self.table_editor.columns.remove(old_table_column) for tube in self.model.tubes: if trait.type != 'metadata': if old_name: tube.on_trait_change(self._try_multiedit, old_name, remove = True) if new_name: tube.on_trait_change(self._try_multiedit, new_name) if old_name: tube.remove_trait(old_name) self.model.counter.clear() for tube in self.model.tubes: tube_hash = tube.conditions_hash() if tube_hash in self.model.counter: self.model.counter[tube_hash] += 1 else: self.model.counter[tube_hash] = 1 @on_trait_change('model:tube_traits:type') def _tube_trait_type_changed(self, trait, _, old_type, new_type): if not trait.name: return table_column = next((x for x in self.table_editor.columns if x.name == trait.name)) table_column.editable = (new_type != 'metadata') for tube in self.model.tubes: if trait.name: if old_type != 'metadata': tube.on_trait_change(self._try_multiedit, trait.name, remove = True) if new_type != 'metadata': tube.on_trait_change(self._try_multiedit, trait.name) def _try_multiedit(self, obj, name, old, new): """ See if there are multiple elements selected when a tube's trait changes and if so, edit the same trait for all the selected tubes. """ if self.updating: return self.updating = True for tube in self.selected_tubes: if tube != obj: old_hash = tube.conditions_hash() self.model.counter[old_hash] -= 1 if self.model.counter[old_hash] == 0: del self.model.counter[old_hash] # update the underlying traits without notifying the editor # we do this all here for performance reasons tube.trait_setq(**{name: new}) tube.conditions[name] = new new_hash = tube.conditions_hash() if new_hash not in self.model.counter: self.model.counter[new_hash] = 1 else: self.model.counter[new_hash] += 1 # now refresh the editor all at once self.table_editor.refresh_editor() self.updating = False