class _ColumnLinkerPanel(wx.Panel): def __init__(self, parent, table_a, table_b, col_a=None, col_b=None, allow_delete=True, **kwargs): wx.Panel.__init__(self, parent, **kwargs) self.table_a = table_a self.table_b = table_b self.aChoice = ComboBox(self, choices=db.GetColumnNames(table_a), size=(100,-1), style=wx.CB_READONLY) self.bChoice = ComboBox(self, choices=db.GetColumnNames(table_b), size=(100,-1), style=wx.CB_READONLY) if col_a in self.aChoice.Strings: self.aChoice.Select(self.aChoice.Strings.index(col_a)) if col_b in self.bChoice.Strings: self.bChoice.Select(self.bChoice.Strings.index(col_b)) if allow_delete: self.x_btn = wx.Button(self, -1, 'x', size=(30,-1)) self.Sizer = wx.BoxSizer(wx.HORIZONTAL) self.Sizer.Add(wx.StaticText(self, -1, table_a+'.'), 0, wx.TOP, 4) self.Sizer.Add(self.aChoice, 1, wx.EXPAND) self.Sizer.AddSpacer(10) self.Sizer.Add(wx.StaticText(self, -1, '<=>'), 0, wx.TOP, 4) self.Sizer.AddSpacer(10) self.Sizer.Add(wx.StaticText(self, -1, table_b+'.'), 0, wx.TOP, 4) self.Sizer.Add(self.bChoice, 1, wx.EXPAND) if allow_delete: self.Sizer.AddSpacer(10) self.Sizer.Add(self.x_btn) self.x_btn.Bind(wx.EVT_BUTTON, self.GrandParent.on_remove_panel) def get_column_pair(self): '''returns ((table_a, col_a), (table_b, col_b)) ''' return ((self.table_a, self.aChoice.Value), (self.table_b, self.bChoice.Value))
class DataSourcePanel(wx.Panel): ''' A panel with controls for selecting the source data for a boxplot ''' def __init__(self, parent, figpanel, **kwargs): wx.Panel.__init__(self, parent, **kwargs) # the panel to draw charts on self.SetBackgroundColour('white') # color for the background of panel self.figpanel = figpanel sizer = wx.BoxSizer(wx.VERTICAL) self.x_columns = [] # column names to plot if selecting multiple columns self.table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY) self.x_choice = ComboBox(self, -1, size=(200,-1), style=wx.CB_READONLY) self.x_multiple = wx.Button(self, -1, 'select multiple') self.group_choice = ComboBox(self, -1, choices=[NO_GROUP]+p._groups_ordered, style=wx.CB_READONLY) self.group_choice.Select(0) self.filter_choice = ui.FilterComboBox(self, style=wx.CB_READONLY) self.filter_choice.Select(0) self.update_chart_btn = wx.Button(self, -1, "Update Chart") self.update_column_fields() sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "table:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.table_choice, 1, wx.EXPAND) sz.AddSpacer(3) sz.Add(wx.StaticText(self, -1, "measurement:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.x_choice, 2, wx.EXPAND) sz.AddSpacer(3) sz.Add(self.x_multiple, 0, wx.EXPAND|wx.TOP, 2) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 3, 0) sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "group x-axis by:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.group_choice, 1, wx.EXPAND) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 3, 0) sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "filter:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.filter_choice, 1, wx.EXPAND) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 3, 0) sizer.Add(self.update_chart_btn) self.x_multiple.Bind(wx.EVT_BUTTON, self.on_select_multiple) self.table_choice.Bind(wx.EVT_COMBOBOX, self.on_table_selected) self.x_choice.Bind(wx.EVT_COMBOBOX, self.on_column_selected) self.update_chart_btn.Bind(wx.EVT_BUTTON, self.update_figpanel) self.SetSizer(sizer) self.Show(1) def on_select_multiple(self, evt): tablename = self.table_choice.GetString(self.table_choice.GetSelection()) column_names = self.get_numeric_columns_from_table(tablename) dlg = wx.MultiChoiceDialog(self, 'Select the columns you would like to plot', 'Select Columns', column_names) dlg.SetSelections([column_names.index(v) for v in self.x_columns]) if (dlg.ShowModal() == wx.ID_OK): self.x_choice.SetValue(SELECT_MULTIPLE) self.x_columns = [column_names[i] for i in dlg.GetSelections()] self.group_choice.Disable() self.group_choice.SetStringSelection(NO_GROUP) def on_table_selected(self, evt): table = self.table_choice.Value if table == ui.TableComboBox.OTHER_TABLE: t = ui.get_other_table_from_user(self) if t is not None: self.table_choice.Items = self.table_choice.Items[:-1] + [t] + self.table_choice.Items[-1:] self.table_choice.Select(self.table_choice.Items.index(t)) else: self.table_choice.Select(0) return self.group_choice.Enable() self.x_columns = [] self.update_column_fields() def on_column_selected(self, evt): self.group_choice.Enable() def update_column_fields(self): tablename = self.table_choice.GetString(self.table_choice.GetSelection()) fieldnames = self.get_numeric_columns_from_table(tablename) self.x_choice.Clear() self.x_choice.AppendItems(fieldnames) self.x_choice.Append(SELECT_MULTIPLE) self.x_choice.SetSelection(0) def get_numeric_columns_from_table(self, table): ''' Fetches names of numeric columns for the given table. ''' measurements = db.GetColumnNames(table) types = db.GetColumnTypes(table) return [m for m,t in zip(measurements, types) if t in (float, int)] def update_figpanel(self, evt=None): table = self.table_choice.Value fltr = self.filter_choice.get_filter_or_none() grouping = self.group_choice.Value if self.x_choice.Value == SELECT_MULTIPLE: points_dict = {} for col in self.x_columns: pts = self.loadpoints(table, col, fltr, NO_GROUP) for k in list(pts.keys()): assert k not in points_dict points_dict.update(pts) else: col = self.x_choice.Value points_dict = self.loadpoints(table, col, fltr, grouping) # Check if the user is creating a plethora of plots by accident if 100 >= len(points_dict) > 25: res = wx.MessageDialog(self, 'Are you sure you want to show %s box ' 'plots on one axis?'%(len(points_dict)), 'Warning', style=wx.YES_NO|wx.NO_DEFAULT ).ShowModal() if res != wx.ID_YES: return elif len(points_dict) > 100: wx.MessageBox('Sorry, boxplot can not show more than 100 plots on\n' 'a single axis. Your current settings would plot %d.\n' 'Try using a filter to narrow your query.' %(len(points_dict)), 'Too many groups to plot') return self.figpanel.setpoints(points_dict) if self.group_choice.Value != NO_GROUP: self.figpanel.set_x_axis_label(grouping) self.figpanel.set_y_axis_label(self.x_choice.Value) self.figpanel.draw() def loadpoints(self, tablename, col, fltr=None, grouping=NO_GROUP): ''' Returns a dict mapping x label values to lists of values from col ''' q = sql.QueryBuilder() select = [sql.Column(tablename, col)] if grouping != NO_GROUP: dm = datamodel.DataModel() group_cols = dm.GetGroupColumnNames(grouping, include_table_name=True) select += [sql.Column(*col.split('.')) for col in group_cols] q.set_select_clause(select) if fltr is not None: q.add_filter(fltr) res = db.execute(str(q)) res = np.array(res, dtype=object) # replaces Nones with NaNs for row in res: if row[0] is None: row[0] = np.nan points_dict = {} if self.group_choice.Value != NO_GROUP: for row in res: groupkey = tuple(row[1:]) points_dict[groupkey] = points_dict.get(groupkey, []) + [row[0]] else: points_dict = {col : [r[0] for r in res]} return points_dict def save_settings(self): ''' Called when saving a workspace to file. returns a dictionary mapping setting names to values encoded as strings ''' if self.x_choice.Value == SELECT_MULTIPLE: cols = self.x_columns else: cols = [self.x_choice.GetString(self.x_choice.GetSelection())] return {'table' : self.table_choice.Value, 'x-axis' : ','.join(cols), 'filter' : self.filter_choice.Value, 'x-lim' : self.figpanel.subplot.get_xlim(), 'y-lim' : self.figpanel.subplot.get_ylim(), ## 'grouping': self.group_choice.Value, ## 'version': '1', } def load_settings(self, settings): '''load_settings is called when loading a workspace from file. settings - a dictionary mapping setting names to values encoded as strings. ''' ## if 'version' not in settings: ## settings['grouping'] = NO_GROUP ## settings['version'] = '1' if 'table' in settings: self.table_choice.SetStringSelection(settings['table']) self.update_column_fields() if 'x-axis' in settings: cols = list(map(str.strip, settings['x-axis'].split(','))) if len(cols) == 1: self.x_choice.SetStringSelection(cols[0]) else: self.x_choice.SetValue(SELECT_MULTIPLE) self.x_columns = cols if 'filter' in settings: self.filter_choice.SetStringSelection(settings['filter']) self.update_figpanel() if 'x-lim' in settings: self.figpanel.subplot.set_xlim(eval(settings['x-lim'])) if 'y-lim' in settings: self.figpanel.subplot.set_ylim(eval(settings['y-lim'])) self.figpanel.draw()
class DataSourcePanel(wx.Panel): ''' A panel with controls for selecting the source data for a histogramplot ''' def __init__(self, parent, figpanel, **kwargs): wx.Panel.__init__(self, parent, **kwargs) # the panel to draw charts on self.SetBackgroundColour('white') # color for the background of panel self.figpanel = figpanel sizer = wx.BoxSizer(wx.VERTICAL) self.table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY) self.x_choice = ComboBox(self, -1, size=(200, -1), choices=[''], style=wx.CB_READONLY) self.x_choice.Select(0) self.bins_input = wx.SpinCtrl(self, -1, '100') self.bins_input.SetRange(1, 400) self.x_scale_choice = ComboBox( self, -1, choices=[LINEAR_SCALE, LOG_SCALE, LOG2_SCALE], style=wx.CB_READONLY) self.x_scale_choice.Select(0) self.y_scale_choice = ComboBox(self, -1, choices=[LINEAR_SCALE, LOG_SCALE], style=wx.CB_READONLY) self.y_scale_choice.Select(0) self.filter_choice = ui.FilterComboBox(self, style=wx.CB_READONLY) self.filter_choice.Select(0) self.gate_choice = ui.GateComboBox(self, style=wx.CB_READONLY) self.gate_choice.set_gatable_columns([self.x_column]) self.update_chart_btn = wx.Button(self, -1, "Update Chart") self.update_column_fields() sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "x-axis:"), 0, wx.TOP, 4) sz.AddSpacer(2) sz.Add(self.table_choice, 1, wx.EXPAND) sz.AddSpacer(3) sz.Add(self.x_choice, 2, wx.EXPAND) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 2, 0) sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "x-scale:"), 0, wx.TOP, 4) sz.AddSpacer(2) sz.Add(self.x_scale_choice, 1, wx.EXPAND) sz.AddSpacer(5) sz.Add(wx.StaticText(self, -1, "y-scale:"), 0, wx.TOP, 4) sz.AddSpacer(2) sz.Add(self.y_scale_choice, 1, wx.EXPAND) sz.AddSpacer(5) sz.Add(wx.StaticText(self, -1, "bins:"), 0, wx.TOP, 4) sz.AddSpacer(2) sz.Add(self.bins_input) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 2, 0) sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "filter:"), 0, wx.TOP, 4) sz.AddSpacer(2) sz.Add(self.filter_choice, 1, wx.EXPAND) sz.AddSpacer(5) sz.Add(wx.StaticText(self, -1, "gate:"), 0, wx.TOP, 4) sz.AddSpacer(2) sz.Add(self.gate_choice, 1, wx.EXPAND) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 2, 0) sizer.Add(self.update_chart_btn) self.table_choice.Bind(wx.EVT_COMBOBOX, self.on_table_selected) self.update_chart_btn.Bind(wx.EVT_BUTTON, self.update_figpanel) self.gate_choice.addobserver(self.on_gate_selected) self.SetSizer(sizer) self.Show(1) @property def x_column(self): return sql.Column( self.table_choice.GetString(self.table_choice.GetSelection()), self.x_choice.GetString(self.x_choice.GetSelection())) @property def filter(self): return self.filter_choice.get_filter_or_none() def on_table_selected(self, evt): table = self.table_choice.Value if table == ui.TableComboBox.OTHER_TABLE: t = ui.get_other_table_from_user(self) if t is not None: self.table_choice.Items = self.table_choice.Items[:-1] + [ t ] + self.table_choice.Items[-1:] self.table_choice.Select(self.table_choice.Items.index(t)) else: self.table_choice.Select(0) return self.update_column_fields() def on_gate_selected(self, gate_name): self.update_gate_helper() def update_gate_helper(self): gate_name = self.gate_choice.get_gatename_or_none() if gate_name: self.figpanel.gate_helper.set_displayed_gate( p.gates[gate_name], self.x_column, None) else: self.figpanel.gate_helper.disable() def update_column_fields(self): tablename = self.table_choice.GetString( self.table_choice.GetSelection()) fieldnames = self.get_numeric_columns_from_table(tablename) self.x_choice.Clear() self.x_choice.AppendItems(fieldnames) self.x_choice.SetSelection(0) def get_numeric_columns_from_table(self, table): ''' Fetches names of numeric columns for the given table. ''' measurements = db.GetColumnNames(table) types = db.GetColumnTypes(table) return [m for m, t in zip(measurements, types) if t in (float, int)] def _plotting_per_object_data(self): return (p.object_table and p.object_table in [self.x_column.table, self.x_column.table] or (self.x_column.table != p.image_table and db.adjacent(p.object_table, self.x_column.table))) def update_figpanel(self, evt=None): self.gate_choice.set_gatable_columns([self.x_column]) points = self._load_points() bins = int(self.bins_input.GetValue()) self.figpanel.set_x_label(self.x_column.col) self.figpanel.set_x_scale( self.x_scale_choice.GetString(self.x_scale_choice.GetSelection())) self.figpanel.set_y_scale( self.y_scale_choice.GetString(self.y_scale_choice.GetSelection())) self.figpanel.setpoints(points, bins) self.update_gate_helper() self.figpanel.draw() def _load_points(self): q = sql.QueryBuilder() select = [self.x_column] q.set_select_clause(select) if self.filter is not None: q.add_filter(self.filter) return np.array(db.execute(str(q))).T[0] def save_settings(self): '''save_settings is called when saving a workspace to file. returns a dictionary mapping setting names to values encoded as strings ''' d = { 'table': self.table_choice.GetString(self.table_choice.GetSelection()), 'x-axis': self.x_choice.GetString(self.x_choice.GetSelection()), 'bins': self.bins_input.GetValue(), 'x-scale': self.x_scale_choice.GetString(self.x_scale_choice.GetSelection()), 'y-scale': self.y_scale_choice.GetString(self.y_scale_choice.GetSelection()), 'filter': self.filter_choice.GetString(self.filter_choice.GetSelection()), 'x-lim': self.figpanel.subplot.get_xlim(), 'y-lim': self.figpanel.subplot.get_ylim(), } if self.gate_choice.get_gatename_or_none(): d['gate'] = self.gate_choice.GetString( self.gate_choice.GetSelection()) return d def load_settings(self, settings): '''load_settings is called when loading a workspace from file. settings - a dictionary mapping setting names to values encoded as strings. ''' if 'table' in settings: self.table_choice.SetStringSelection(settings['table']) self.update_column_fields() if 'x-axis' in settings: self.x_choice.SetStringSelection(settings['x-axis']) if 'bins' in settings: self.bins_input.SetValue(int(settings['bins'])) if 'x-scale' in settings: self.x_scale_choice.SetStringSelection(settings['x-scale']) if 'y-scale' in settings: self.y_scale_choice.SetStringSelection(settings['y-scale']) if 'filter' in settings: self.filter_choice.SetStringSelection(settings['filter']) self.update_figpanel() if 'x-lim' in settings: self.figpanel.subplot.set_xlim(eval(settings['x-lim'])) if 'y-lim' in settings: self.figpanel.subplot.set_ylim(eval(settings['y-lim'])) if 'gate' in settings: self.gate_choice.SetStringSelection(settings['gate']) self.figpanel.gate_helper.set_displayed_gate( p.gates[settings['gate']], self.x_column, None) self.figpanel.draw()
class ColumnFilterPanel(wx.Panel): ''' Creates a UI that allows the user to create WHERE clauses by selecting 1) a DB column name, 2) a comparator, and 3) a value ''' def __init__(self, parent, tables, allow_delete=True, expression=None, **kwargs): wx.Panel.__init__(self, parent, **kwargs) self.fieldSets = [] self.tables = tables self.types = {} self.types[p.image_table] = db.GetColumnTypes(p.image_table) self.tableChoice = ComboBox(self, choices=self.tables, size=(150, -1), style=wx.CB_READONLY) self.tableChoice.Select(0) self.colChoice = ComboBox(self, choices=db.GetColumnNames(p.image_table), size=(150, -1), style=wx.CB_READONLY) self.colChoice.Select(0) self.comparatorChoice = ComboBox(self, size=(80, -1), style=wx.CB_READONLY) self.update_comparator_choice() self.valueField = wx.ComboBox(self, -1, value='') if allow_delete: self.x_btn = wx.Button(self, -1, 'x', size=(30, -1)) ## if expression is not None: ## self.set_expression(expression) colSizer = wx.BoxSizer(wx.HORIZONTAL) colSizer.Add(self.tableChoice, 1, wx.EXPAND) colSizer.AddSpacer(5) colSizer.Add(self.colChoice, 1, wx.EXPAND) colSizer.AddSpacer(5) colSizer.Add(self.comparatorChoice, 0.5, wx.EXPAND) colSizer.AddSpacer(5) colSizer.Add(self.valueField, 1, wx.EXPAND) if allow_delete: colSizer.AddSpacer(5) colSizer.Add(self.x_btn, 0, wx.EXPAND) self.SetSizer(colSizer) self.tableChoice.Bind(wx.EVT_COMBOBOX, self.on_select_table) self.colChoice.Bind(wx.EVT_COMBOBOX, self.on_select_col) if allow_delete: self.x_btn.Bind(wx.EVT_BUTTON, self.on_remove) self.Fit() def on_remove(self, evt): self.GrandParent.remove(self) def on_select_col(self, evt): self.update_comparator_choice() self.update_value_choice() def on_select_table(self, evt): self.update_col_choice() self.update_comparator_choice() self.update_value_choice() def update_col_choice(self): table = self.tableChoice.Value self.colChoice.SetItems(db.GetColumnNames(table)) self.colChoice.Select(0) def _get_col_type(self): table = self.tableChoice.Value colidx = self.colChoice.GetSelection() return db.GetColumnTypes(table)[colidx] def update_comparator_choice(self): coltype = self._get_col_type() comparators = [] if coltype == str: comparators = ['=', '!=', 'REGEXP', 'IS', 'IS NOT'] if coltype in (int, float): comparators = ['=', '!=', '<', '>', '<=', '>=', 'IS', 'IS NOT'] self.comparatorChoice.SetItems(comparators) self.comparatorChoice.Select(0) def update_value_choice(self): table = self.tableChoice.Value column = self.colChoice.Value colidx = self.colChoice.GetSelection() coltype = db.GetColumnTypes(table)[colidx] vals = [] # if coltype == str:# or coltype == int or coltype == long: # res = db.execute('SELECT DISTINCT %s FROM %s ORDER BY %s'%(column, table, column)) # vals = [str(row[0]) for row in res] self.valueField.SetItems(vals) def get_filter(self): table = self.tableChoice.Value column = self.colChoice.Value comparator = self.comparatorChoice.GetValue() value = self.valueField.GetValue() if self._get_col_type() in (int, float): # Don't quote numbers return sql.Filter(sql.Column(table, column), comparator, '%s' % (value)) if comparator.upper() in ['IS', 'IS NOT'] and value.upper() == 'NULL': # Don't quote comparisons to NULL return sql.Filter(sql.Column(table, column), comparator, '%s' % (value)) return sql.Filter(sql.Column(table, column), comparator, '"%s"' % (value))
class PlotControl(wx.Panel): ''' Control panel for the dimensionality reduction analysis ''' def __init__(self, parent, fig_sco, fig_load, **kwargs): wx.Panel.__init__(self, parent, **kwargs) self.fig_sco = fig_sco self.fig_load = fig_load sizer = wx.BoxSizer(wx.VERTICAL) self.method_choice = ComboBox(self, -1, choices=[SVD, TSNE], style=wx.CB_READONLY) self.method_choice.Select(0) self.update_chart_btn = wx.Button(self, -1, "Show plot") self.help_btn = wx.Button(self, -1, "About") sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "Method:")) sz.AddSpacer(5) sz.Add(self.method_choice, 1, wx.EXPAND) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 5, 0) sz2 = wx.BoxSizer(wx.HORIZONTAL) sz2.Add(self.help_btn, wx.LEFT) sz2.AddSpacer(400) sz2.Add(self.update_chart_btn, wx.RIGHT) sizer.Add(sz2, 1, wx.EXPAND) sizer.Add(-1, 5, 0) wx.EVT_BUTTON(self.update_chart_btn, -1, self.on_show_pressed) wx.EVT_BUTTON(self.help_btn, -1, self.on_show_about) self.SetSizer(sizer) self.Show(1) def on_show_about(self, evt): ''' Shows a message box with the version number etc. ''' message = ( 'Dimensionality Reduction Plot was developed at the Intelligent Systems Dept., ' 'Radboud Universiteit Nijmegen as part of the CellProfiler project and is' ' distributed under the GNU General Public License version 2.\n' '\n' 'For more information about the dimensionality reduction algorithms check:\n' '\n' '*Singular Value Decomposition: http://www.snl.salk.edu/~shlens/pca.pdf\n' '\n' '*t-SNE: http://homepage.tudelft.nl/19j49/t-SNE.html\n') dlg = wx.MessageDialog(self, message, 'CellProfiler Analyst 2.0', style=wx.OK | wx.ICON_INFORMATION) dlg.ShowModal() def on_show_pressed(self, evt): ''' Show the selected dimensionality reduction plot on the canvas ''' selected_method = self.method_choice.GetStringSelection() if selected_method == SVD: self.fig_sco.plot_pca() self.fig_load.plot_pca() elif selected_method == TSNE: self.fig_sco.plot_tsne() self.fig_load.clean_canvas()
class PlateViewer(wx.Frame, CPATool): def __init__(self, parent, size=(800, -1), **kwargs): wx.Frame.__init__(self, parent, -1, size=size, title='Plate Viewer', **kwargs) CPATool.__init__(self) self.SetName(self.tool_name) self.SetBackgroundColour("white") # Fixing the color # Check for required properties fields. fail = False for field in required_fields: if not p.field_defined(field): fail = True raise Exception( 'Properties field "%s" is required for PlateViewer.' % (field)) if fail: self.Destroy() return self.chMap = p.image_channel_colors[:] self.menuBar = wx.MenuBar() self.SetMenuBar(self.menuBar) self.fileMenu = wx.Menu() self.exitMenuItem = self.fileMenu.Append( id=wx.ID_EXIT, item='Exit\tCtrl+Q', helpString='Close Plate Viewer') self.GetMenuBar().Append(self.fileMenu, 'File') self.menuBar.Append( cpa.helpmenu.make_help_menu( self, manual_url="https://cellprofiler.org/viii-plate-viewer"), 'Help') save_csv_menu_item = self.fileMenu.Append(-1, 'Save table to CSV\tCtrl+S') self.Bind(wx.EVT_MENU, self.on_save_csv, save_csv_menu_item) self.Bind(wx.EVT_MENU, lambda _: self.Close(), id=wx.ID_EXIT) dataSourceSizer = wx.StaticBoxSizer( wx.StaticBox(self, label='Source:'), wx.VERTICAL) dataSourceSizer.Add(wx.StaticText(self, label='Data source:')) self.sourceChoice = TableComboBox(self, -1, size=fixed_width) dataSourceSizer.Add(self.sourceChoice) dataSourceSizer.Add(-1, 3, 0) dataSourceSizer.Add(wx.StaticText(self, label='Measurement:')) measurements = get_non_blob_types_from_table(p.image_table) self.measurementsChoice = ComboBox(self, choices=measurements, size=fixed_width, style=wx.CB_READONLY) self.measurementsChoice.Select(0) dataSourceSizer.Add(self.measurementsChoice) dataSourceSizer.Add(wx.StaticText(self, label='Filter:')) self.filterChoice = FilterComboBox(self, size=fixed_width, style=wx.CB_READONLY) dataSourceSizer.Add(self.filterChoice) groupingSizer = wx.StaticBoxSizer( wx.StaticBox(self, label='Data aggregation:'), wx.VERTICAL) groupingSizer.Add(wx.StaticText(self, label='Aggregation method:')) aggregation = ['mean', 'sum', 'median', 'stdev', 'cv%', 'min', 'max'] self.aggregationMethodsChoice = ComboBox(self, choices=aggregation, size=fixed_width) self.aggregationMethodsChoice.Select(0) groupingSizer.Add(self.aggregationMethodsChoice) viewSizer = wx.StaticBoxSizer( wx.StaticBox(self, label='View options:'), wx.VERTICAL) viewSizer.Add(wx.StaticText(self, label='Color map:')) maps = [ m for m in list(matplotlib.cm.datad.keys()) if not m.endswith("_r") ] maps.sort() self.colorMapsChoice = ComboBox(self, choices=maps, size=fixed_width) self.colorMapsChoice.SetSelection(maps.index('jet')) viewSizer.Add(self.colorMapsChoice) viewSizer.Add(-1, 3, 0) viewSizer.Add(wx.StaticText(self, label='Well display:')) if p.image_thumbnail_cols: choices = pmp.all_well_shapes else: choices = list(pmp.all_well_shapes) choices.remove(pmp.THUMBNAIL) self.wellDisplayChoice = ComboBox(self, choices=choices, size=fixed_width) self.wellDisplayChoice.Select(0) viewSizer.Add(self.wellDisplayChoice) viewSizer.Add(-1, 3, 0) viewSizer.Add(wx.StaticText(self, label='Number of plates:')) self.numberOfPlatesTE = wx.TextCtrl(self, -1, '1', style=wx.TE_PROCESS_ENTER) viewSizer.Add(self.numberOfPlatesTE) if not p.plate_id: self.numberOfPlatesTE.Disable() annotationSizer = wx.StaticBoxSizer( wx.StaticBox(self, label='Annotation:'), wx.VERTICAL) annotationSizer.Add(wx.StaticText(self, label='Annotation column:')) annotationColSizer = wx.BoxSizer(wx.HORIZONTAL) self.annotation_cols = dict([ (col, db.GetColumnType(p.image_table, col)) for col in db.GetUserColumnNames(p.image_table) ]) self.annotationCol = ComboBox(self, choices=list( self.annotation_cols.keys()), size=(120, -1)) if len(self.annotation_cols) > 0: self.annotationCol.SetSelection(0) annotationColSizer.Add(self.annotationCol, flag=wx.ALIGN_CENTER_VERTICAL) annotationColSizer.AddSpacer(3) self.addAnnotationColBtn = wx.Button(self, -1, 'Add', size=(44, -1)) annotationColSizer.Add(self.addAnnotationColBtn, flag=wx.ALIGN_CENTER_VERTICAL) annotationSizer.Add(annotationColSizer) annotationSizer.Add(-1, 3, 0) annotationSizer.Add(wx.StaticText(self, label='Label:')) self.annotationLabel = wx.TextCtrl( self, -1, 'Select wells') #, style=wx.TE_PROCESS_ENTER) self.annotationLabel.Disable() self.annotationLabel.SetForegroundColour(wx.Colour(80, 80, 80)) self.annotationLabel.SetBackgroundColour(wx.LIGHT_GREY) annotationSizer.Add(self.annotationLabel) annotationSizer.Add(-1, 3, 0) self.outlineMarked = wx.CheckBox(self, -1, label='Outline annotated wells') annotationSizer.Add(self.outlineMarked) annotationSizer.Add(-1, 3, 0) self.annotationShowVals = wx.CheckBox(self, -1, label='Show values on plate') annotationSizer.Add(self.annotationShowVals) if len(db.GetUserColumnNames(p.image_table)) == 0: self.outlineMarked.Disable() self.annotationShowVals.Disable() controlSizer = wx.BoxSizer(wx.VERTICAL) controlSizer.Add(dataSourceSizer, 0, wx.EXPAND) controlSizer.Add(-1, 3, 0) controlSizer.Add(groupingSizer, 0, wx.EXPAND) controlSizer.Add(-1, 3, 0) controlSizer.Add(viewSizer, 0, wx.EXPAND) controlSizer.Add(-1, 3, 0) controlSizer.Add(annotationSizer, 0, wx.EXPAND) self.plateMapSizer = wx.GridSizer(1, 1, 5, 5) self.plateMaps = [] self.plateMapChoices = [] self.rightSizer = wx.BoxSizer(wx.VERTICAL) self.rightSizer.Add(self.plateMapSizer, 1, wx.EXPAND | wx.BOTTOM, 10) self.colorBar = ColorBarPanel(self, 'jet', size=(-1, 25)) self.rightSizer.Add(self.colorBar, 0, wx.EXPAND) mainSizer = wx.BoxSizer(wx.HORIZONTAL) mainSizer.Add(controlSizer, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10) mainSizer.Add(self.rightSizer, 1, wx.EXPAND | wx.ALL, 10) self.SetSizer(mainSizer) self.SetClientSize((self.Size[0], self.Sizer.CalcMin()[1])) self.sourceChoice.Bind(wx.EVT_COMBOBOX, self.UpdateMeasurementChoice) self.measurementsChoice.Bind(wx.EVT_COMBOBOX, self.OnSelectMeasurement) self.measurementsChoice.Select(0) self.aggregationMethodsChoice.Bind(wx.EVT_COMBOBOX, self.OnSelectAggregationMethod) self.colorMapsChoice.Bind(wx.EVT_COMBOBOX, self.OnSelectColorMap) self.numberOfPlatesTE.Bind(wx.EVT_TEXT_ENTER, self.OnEnterNumberOfPlates) self.wellDisplayChoice.Bind(wx.EVT_COMBOBOX, self.OnSelectWellDisplay) self.annotationCol.Bind(wx.EVT_COMBOBOX, self.OnSelectAnnotationCol) self.addAnnotationColBtn.Bind(wx.EVT_BUTTON, self.OnAddAnnotationCol) self.annotationLabel.Bind(wx.EVT_KEY_UP, self.OnEnterAnnotation) self.outlineMarked.Bind(wx.EVT_CHECKBOX, self.OnOutlineMarked) self.annotationShowVals.Bind(wx.EVT_CHECKBOX, self.OnShowAnnotationValues) self.filterChoice.Bind(wx.EVT_COMBOBOX, self.OnSelectFilter) self.AddPlateMap() self.OnSelectMeasurement() def AddPlateMap(self, plateIndex=0): ''' Adds a new blank plateMap to the PlateMapSizer. ''' data = np.ones(p.plate_shape) # Try to get explicit labels for all wells. res = db.execute( 'SELECT DISTINCT %s FROM %s WHERE %s != "" and %s IS NOT NULL' % (dbconnect.UniqueWellClause(), p.image_table, p.well_id, p.well_id)) if p.plate_id: self.plateMapChoices += [ ComboBox(self, choices=db.GetPlateNames(), size=(400, -1)) ] self.plateMapChoices[-1].Select(plateIndex) self.plateMapChoices[-1].Bind(wx.EVT_COMBOBOX, self.OnSelectPlate) #plate_col_type = db.GetColumnType(p.image_table, p.plate_id) #plate_id = plate_col_type(self.plateMapChoices[-1].GetString(plateIndex)) plateMapChoiceSizer = wx.BoxSizer(wx.HORIZONTAL) plateMapChoiceSizer.Add(wx.StaticText(self, label='Plate:'), flag=wx.ALIGN_CENTER_VERTICAL) plateMapChoiceSizer.Add(self.plateMapChoices[-1], flag=wx.ALIGN_CENTER_VERTICAL) well_keys = res platemap = pmp.PlateMapPanel(self, data, well_keys, p.plate_shape, colormap=self.colorMapsChoice.Value, well_disp=self.wellDisplayChoice.Value) platemap.add_well_selection_handler(self.OnSelectWell) self.plateMaps += [platemap] singlePlateMapSizer = wx.BoxSizer(wx.VERTICAL) if p.plate_id: singlePlateMapSizer.Add(plateMapChoiceSizer, 0, wx.ALIGN_CENTER) singlePlateMapSizer.Add(platemap, 1, wx.EXPAND) self.plateMapSizer.Add(singlePlateMapSizer, 1, wx.EXPAND) def UpdatePlateMaps(self): self.measurement = self.measurementsChoice.Value measurement = self.measurement table = self.sourceChoice.Value self.aggMethod = self.aggregationMethodsChoice.Value categorical = measurement not in get_numeric_columns_from_table(table) fltr = self.filterChoice.Value self.colorBar.ClearNotifyWindows() q = sql.QueryBuilder() well_key_cols = [ sql.Column(p.image_table, col) for col in well_key_columns() ] select = list(well_key_cols) if not categorical: if self.aggMethod == 'mean': select += [sql.Column(table, measurement, 'AVG')] elif self.aggMethod == 'stdev': select += [sql.Column(table, measurement, 'STDDEV')] elif self.aggMethod == 'cv%': # stddev(col) / avg(col) * 100 select += [ sql.Expression(sql.Column(table, measurement, 'STDDEV'), ' / ', sql.Column(table, measurement, 'AVG'), ' * 100') ] elif self.aggMethod == 'sum': select += [sql.Column(table, measurement, 'SUM')] elif self.aggMethod == 'min': select += [sql.Column(table, measurement, 'MIN')] elif self.aggMethod == 'max': select += [sql.Column(table, measurement, 'MAX')] elif self.aggMethod == 'median': select += [sql.Column(table, measurement, 'MEDIAN')] elif self.aggMethod == 'none': select += [sql.Column(table, measurement)] else: select += [sql.Column(table, measurement)] q.set_select_clause(select) q.set_group_columns(well_key_cols) if fltr not in (FilterComboBox.NO_FILTER, FilterComboBox.NEW_FILTER, ''): if fltr in p._filters: q.add_filter(p._filters[fltr]) elif fltr in p.gates: q.add_filter(p.gates[fltr].as_filter()) else: raise Exception( 'Could not find filter "%s" in gates or filters' % (fltr)) wellkeys_and_values = db.execute(str(q)) wellkeys_and_values = np.array(wellkeys_and_values, dtype=object) # Replace measurement None's with nan for row in wellkeys_and_values: if row[-1] is None: row[-1] = np.nan data = [] key_lists = [] dmax = -np.inf dmin = np.inf if p.plate_id: for plateChoice, plateMap in zip(self.plateMapChoices, self.plateMaps): plate = plateChoice.Value plateMap.SetPlate(plate) self.colorBar.AddNotifyWindow(plateMap) self.keys_and_vals = [ v for v in wellkeys_and_values if str(v[0]) == plate ] platedata, wellkeys, ignore = FormatPlateMapData( self.keys_and_vals, categorical) data += [platedata] key_lists += [wellkeys] if not categorical: dmin = np.nanmin( [float(kv[-1]) for kv in self.keys_and_vals] + [dmin]) dmax = np.nanmax( [float(kv[-1]) for kv in self.keys_and_vals] + [dmax]) else: self.colorBar.AddNotifyWindow(self.plateMaps[0]) platedata, wellkeys, ignore = FormatPlateMapData( wellkeys_and_values, categorical) data += [platedata] key_lists += [wellkeys] if not categorical: dmin = np.nanmin([float(kv[-1]) for kv in wellkeys_and_values]) dmax = np.nanmax([float(kv[-1]) for kv in wellkeys_and_values]) if not categorical: if len(wellkeys_and_values) > 0: # Compute the global extents if there is any data whatsoever gmin = np.nanmin( [float(vals[-1]) for vals in wellkeys_and_values]) gmax = np.nanmax( [float(vals[-1]) for vals in wellkeys_and_values]) if np.isinf(dmin) or np.isinf(dmax): gmin = gmax = dmin = dmax = 1. # Warn if there was no data for this plate (and no filter was used) if fltr == FilterComboBox.NO_FILTER: wx.MessageBox( 'No numeric data was found in "%s.%s" for plate "%s"' % (table, measurement, plate), 'Warning') else: gmin = gmax = 1. if fltr == FilterComboBox.NO_FILTER: wx.MessageBox( 'No numeric data was found in %s.%s' % (table, measurement), 'Warning') if categorical: self.colorBar.Hide() else: self.colorBar.Show() self.colorBar.SetLocalExtents([dmin, dmax]) self.colorBar.SetGlobalExtents([gmin, gmax]) self.rightSizer.Layout() for keys, d, plateMap in zip(key_lists, data, self.plateMaps): plateMap.SetWellKeys(keys) if categorical: plateMap.SetData(np.ones(d.shape) * np.nan) plateMap.SetTextData(d) else: plateMap.SetData( d, data_range=self.colorBar.GetLocalExtents(), clip_interval=self.colorBar.GetLocalInterval(), clip_mode=self.colorBar.GetClipMode()) for keys, d, plateMap in zip(key_lists, data, self.plateMaps): plateMap.SetWellKeys(keys) if categorical: plateMap.SetData(np.ones(d.shape) * np.nan) plateMap.SetTextData(d) else: plateMap.SetData( d, data_range=self.colorBar.GetLocalExtents(), clip_interval=self.colorBar.GetLocalInterval(), clip_mode=self.colorBar.GetClipMode()) def UpdateMeasurementChoice(self, evt=None): ''' Handles the selection of a source table (per-image or per-object) from a choice box. The measurement choice box is populated with the names of numeric columns from the selected table. ''' table = self.sourceChoice.Value if table == TableComboBox.OTHER_TABLE: t = get_other_table_from_user(self) if t is not None: self.sourceChoice.Items = self.sourceChoice.Items[:-1] + [ t ] + self.sourceChoice.Items[-1:] self.sourceChoice.Select(self.sourceChoice.Items.index(t)) table = t else: self.sourceChoice.Select(0) return self.measurementsChoice.SetItems(get_non_blob_types_from_table(table)) self.measurementsChoice.Select(0) self.colorBar.ResetInterval() self.UpdatePlateMaps() def on_save_csv(self, evt): defaultFileName = 'my_plate_table.csv' saveDialog = wx.FileDialog(self, message="Save as:", defaultDir=os.getcwd(), defaultFile=defaultFileName, wildcard='csv|*', style=(wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR)) if saveDialog.ShowModal() == wx.ID_OK: filename = saveDialog.GetPath() self.save_to_csv(filename) self.Title = filename saveDialog.Destroy() def save_to_csv(self, filename): with open(filename, 'w', newline="") as f: w = csv.writer(f) w.writerow( ['Plate', 'Well', self.measurement + ' ' + self.aggMethod]) w.writerows(self.keys_and_vals) logging.info('Table saved to %s' % filename) def OnSelectPlate(self, evt): ''' Handles the selection of a plate from the plate choice box. ''' self.UpdatePlateMaps() def OnSelectMeasurement(self, evt=None): ''' Handles the selection of a measurement to plot from a choice box. ''' selected_measurement = self.measurementsChoice.Value table = self.sourceChoice.Value numeric_measurements = get_numeric_columns_from_table(table) if (selected_measurement in numeric_measurements): self.aggregationMethodsChoice.Enable() self.colorMapsChoice.Enable() else: self.aggregationMethodsChoice.Disable() self.colorMapsChoice.Disable() self.colorBar.ResetInterval() self.UpdatePlateMaps() def OnSelectAggregationMethod(self, evt=None): ''' Handles the selection of an aggregation method from the choice box. ''' self.colorBar.ResetInterval() self.UpdatePlateMaps() def OnSelectColorMap(self, evt=None): ''' Handles the selection of a color map from a choice box. ''' map = self.colorMapsChoice.Value cm = matplotlib.cm.get_cmap(map) self.colorBar.SetMap(map) for plateMap in self.plateMaps: plateMap.SetColorMap(map) def OnSelectWellDisplay(self, evt=None): ''' Handles the selection of a well display choice from a choice box. ''' sel = self.wellDisplayChoice.Value if sel.lower() == 'image': dlg = wx.MessageDialog( self, 'This mode will render each well as a shrunken image loaded ' 'from that well. This feature is currently VERY SLOW since it ' 'requires loading hundreds of full sized images. Are you sure ' 'you want to continue?', 'Load all images?', wx.OK | wx.CANCEL | wx.ICON_QUESTION) if dlg.ShowModal() != wx.ID_OK: self.wellDisplayChoice.SetSelection(0) return if sel.lower() in ['image', 'thumbnail']: self.colorBar.Hide() else: self.colorBar.Show() for platemap in self.plateMaps: platemap.SetWellDisplay(sel) def OnEnterNumberOfPlates(self, evt=None): ''' Handles the entry of a plates to view from a choice box. ''' try: nPlates = int(self.numberOfPlatesTE.GetValue()) except: logging.warn( 'Invalid # of plates! Please enter a number between 1 and 100') return if nPlates > 100: logging.warn( 'Too many plates! Please enter a number between 1 and 100') return if nPlates < 1: logging.warn('You must display at least 1 plate.') self.numberOfPlatesTE.SetValue('1') nPlates = 1 # Record the indices of the plates currently selected. # Pad the list with sequential plate indices then crop to the new number of plates. currentPlates = [ plateChoice.GetSelection() for plateChoice in self.plateMapChoices ] currentPlates = (currentPlates + [(currentPlates[-1] + 1 + p) % len(db.GetPlateNames()) for p in range(nPlates)])[:nPlates] # Remove all plateMaps self.plateMapSizer.Clear(delete_windows=True) self.plateMaps = [] self.plateMapChoices = [] # Restructure the plateMapSizer appropriately rows = cols = np.ceil(np.sqrt(nPlates)) self.plateMapSizer.SetRows(rows) self.plateMapSizer.SetCols(cols) # Add the plate maps for plateIndex in currentPlates: self.AddPlateMap(plateIndex) self.UpdatePlateMaps() self.plateMapSizer.Layout() # Update outlines self.OnOutlineMarked() def OnSelectWell(self): '''When wells are selected: display their annotation in the annotation label control. If multiple annotations are found then make sure the user knows. ''' wellkeys = [] for pm in self.plateMaps: wellkeys += pm.get_selected_well_keys() if len(wellkeys) > 0 and self.annotationCol.Value != '': self.annotationLabel.Enable() self.annotationLabel.SetForegroundColour(wx.BLACK) self.annotationLabel.SetBackgroundColour(wx.WHITE) annotations = db.execute('SELECT %s FROM %s WHERE %s' % (self.annotationCol.Value, p.image_table, GetWhereClauseForWells(wellkeys))) annotations = list(set([a[0] for a in annotations])) if len(annotations) == 1: if annotations[0] == None: self.annotationLabel.SetValue('') else: self.annotationLabel.SetValue(str(annotations[0])) else: self.annotationLabel.SetValue(','.join( [str(a) for a in annotations if a is not None])) else: self.annotationLabel.Disable() self.annotationLabel.SetForegroundColour(wx.Colour(80, 80, 80)) self.annotationLabel.SetBackgroundColour(wx.LIGHT_GREY) self.annotationLabel.SetValue('Select wells') def OnAddAnnotationCol(self, evt): '''Add a new user annotation column to the database. ''' dlg = wx.TextEntryDialog(self, 'New annotation column name: User_', 'Add Annotation Column') if dlg.ShowModal() != wx.ID_OK: return new_column = 'User_' + dlg.GetValue() # user-type ==> (sql-type, python-type) coltypes = {'Text': ('VARCHAR(255)', str), 'Number': ('FLOAT', float)} dlg = wx.SingleChoiceDialog( self, 'What type of annotation column would you like to add?\nThis can not be changed.', 'Add Annotation Column', list(coltypes.keys()), wx.CHOICEDLG_STYLE) if dlg.ShowModal() != wx.ID_OK: return usertype = dlg.GetStringSelection() db.AppendColumn(p.image_table, new_column, coltypes[usertype][0]) self.annotation_cols[new_column] = coltypes[usertype][1] self.annotationCol.Items += [new_column] self.annotationCol.SetSelection(len(self.annotation_cols) - 1) current_selection = self.measurementsChoice.GetSelection() self.measurementsChoice.SetItems(self.measurementsChoice.Strings + [new_column]) if self.annotationShowVals.IsChecked(): column = self.annotationCol.Value self.sourceChoice.SetStringSelection(p.image_table) self.measurementsChoice.SetStringSelection(column) self.UpdatePlateMaps() else: self.measurementsChoice.SetSelection(current_selection) self.annotationShowVals.Enable() self.outlineMarked.Enable() self.OnSelectWell() def OnSelectAnnotationCol(self, evt=None): '''Handles selection of an annotation column. ''' col = self.annotationCol.Value if col == '': return coltype = self.annotation_cols[col] self.OnSelectWell() self.OnOutlineMarked() if self.annotationShowVals.IsChecked(): if coltype != str: self.colorMapsChoice.Enable() else: self.colorMapsChoice.Disable() self.measurementsChoice.SetStringSelection(col) self.UpdatePlateMaps() def OnEnterAnnotation(self, evt=None): '''Store the annotation value in the annotation column of the db. ''' if evt.KeyCode < 32 or evt.KeyCode > 127: return column = self.annotationCol.Value value = self.annotationLabel.Value wellkeys = [] for pm in self.plateMaps: wellkeys += pm.get_selected_well_keys() if value == '': value = None elif self.annotation_cols[column] == float: try: value = float(value) self.annotationLabel.SetForegroundColour(wx.BLACK) except: self.annotationLabel.SetForegroundColour(wx.Color(255, 0, 0)) return db.UpdateWells(p.image_table, column, value, wellkeys) if self.outlineMarked.IsChecked(): for pm in self.plateMaps: if value is None: pm.UnOutlineWells(wellkeys) else: pm.OutlineWells(wellkeys) if (self.sourceChoice.Value == p.image_table and self.measurementsChoice.Value == column): self.UpdatePlateMaps() def OnOutlineMarked(self, evt=None): '''Outlines all non-NULL values of the current annotation ''' # Disable filters when outlining marked wells #if self.outlineMarked.IsChecked(): #self.filterChoice.SetStringSelection(FilterComboBox.NO_FILTER) #self.filterChoice.Disable() #else: #if not self.annotationShowVals.IsChecked(): #self.filterChoice.Enable() # Update outlined wells in PlateMapPanels for pm in self.plateMaps: if self.outlineMarked.IsChecked(): column = self.annotationCol.Value if p.plate_id: res = db.execute('SELECT %s, %s FROM %s WHERE %s="%s"' % (dbconnect.UniqueWellClause(), column, p.image_table, p.plate_id, pm.plate)) else: # if there's no plate_id, we assume there is only 1 plate # and fetch all the data res = db.execute( 'SELECT %s, %s FROM %s' % (dbconnect.UniqueWellClause(), column, p.image_table)) keys = [tuple(r[:-1]) for r in res if r[-1] is not None] pm.SetOutlinedWells(keys) else: pm.SetOutlinedWells([]) self.UpdatePlateMaps() def OnShowAnnotationValues(self, evt=None): '''Handler for the show values checkbox. ''' if self.annotationShowVals.IsChecked(): column = self.annotationCol.Value self.sourceChoice.SetStringSelection(p.image_table) self.measurementsChoice.SetItems( get_non_blob_types_from_table(p.image_table)) self.measurementsChoice.SetStringSelection(column) self.filterChoice.SetStringSelection(FilterComboBox.NO_FILTER) self.sourceChoice.Disable() self.measurementsChoice.Disable() self.filterChoice.Disable() self.aggregationMethodsChoice.Disable() self.aggregationMethodsChoice.SetValue('none') else: self.sourceChoice.Enable() self.measurementsChoice.Enable() if not self.outlineMarked.IsChecked(): self.filterChoice.Enable() if db.GetColumnType(self.sourceChoice.Value, self.measurementsChoice.Value) != str: self.aggregationMethodsChoice.Enable() self.aggregationMethodsChoice.SetSelection(0) self.UpdatePlateMaps() def OnSelectFilter(self, evt): self.filterChoice.on_select(evt) self.UpdatePlateMaps() self.colorBar.ResetInterval() def save_settings(self): '''save_settings is called when saving a workspace to file. returns a dictionary mapping setting names to values encoded as strings ''' settings = { 'table': self.sourceChoice.Value, 'measurement': self.measurementsChoice.Value, 'aggregation': self.aggregationMethodsChoice.Value, 'colormap': self.colorMapsChoice.Value, 'well display': self.wellDisplayChoice.Value, 'number of plates': self.numberOfPlatesTE.GetValue(), } for i, choice in enumerate(self.plateMapChoices): settings['plate %d' % (i + 1)] = choice.Value return settings def load_settings(self, settings): '''load_settings is called when loading a workspace from file. settings - a dictionary mapping setting names to values encoded as strings. ''' if 'table' in settings: self.sourceChoice.SetStringSelection(settings['table']) self.UpdateMeasurementChoice() if 'measurement' in settings: self.measurementsChoice.SetStringSelection(settings['measurement']) self.OnSelectMeasurement() if 'aggregation' in settings: self.aggregationMethodsChoice.SetStringSelection( settings['aggregation']) self.OnSelectAggregationMethod() if 'colormap' in settings: self.colorMapsChoice.SetStringSelection(settings['colormap']) self.OnSelectColorMap() if 'number of plates' in settings: self.numberOfPlatesTE.SetValue(settings['number of plates']) self.OnEnterNumberOfPlates() for s, v in list(settings.items()): if s.startswith('plate '): self.plateMapChoices[int(s.strip('plate ')) - 1].SetValue(v) # set well display last since each step currently causes a redraw and # this could take a long time if they are displaying images if 'well display' in settings: self.wellDisplayChoice.SetStringSelection(settings['well display']) self.OnSelectWellDisplay()
class ScatterControlPanel(wx.Panel): ''' A panel with controls for selecting the source data for a scatterplot ''' def __init__(self, parent, figpanel, **kwargs): wx.Panel.__init__(self, parent, **kwargs) # the panel to draw charts on self.figpanel = figpanel self.SetBackgroundColour('white') # color for the background of panel sizer = wx.BoxSizer(wx.VERTICAL) self.x_table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY) self.y_table_choice = ui.TableComboBox(self, -1, style=wx.CB_READONLY) self.x_choice = ComboBox(self, -1, choices=[''], size=(200,-1), style=wx.CB_READONLY) self.x_choice.Select(0) self.y_choice = ComboBox(self, -1, choices=[''], size=(200,-1), style=wx.CB_READONLY) self.y_choice.Select(0) self.x_scale_choice = ComboBox(self, -1, choices=[LINEAR_SCALE, LOG_SCALE], size=(90,-1), style=wx.CB_READONLY) self.x_scale_choice.Select(0) self.y_scale_choice = ComboBox(self, -1, choices=[LINEAR_SCALE, LOG_SCALE], size=(90,-1), style=wx.CB_READONLY) self.y_scale_choice.Select(0) self.filter_choice = ui.FilterComboBox(self, style=wx.CB_READONLY) self.filter_choice.Select(0) self.gate_choice = ui.GateComboBox(self, style=wx.CB_READONLY) self.gate_choice.set_gatable_columns([self.x_column, self.y_column]) self.update_chart_btn = wx.Button(self, -1, "Update Chart") self.update_x_choices() self.update_y_choices() sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "x-axis:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.x_table_choice, 1, wx.EXPAND) sz.AddSpacer(3) sz.Add(self.x_choice, 2, wx.EXPAND) sz.AddSpacer(3) sz.Add(wx.StaticText(self, -1, "scale:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.x_scale_choice) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 2, 0) sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "y-axis:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.y_table_choice, 1, wx.EXPAND) sz.AddSpacer(3) sz.Add(self.y_choice, 2, wx.EXPAND) sz.AddSpacer(3) sz.Add(wx.StaticText(self, -1, "scale:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.y_scale_choice) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 2, 0) sz = wx.BoxSizer(wx.HORIZONTAL) sz.Add(wx.StaticText(self, -1, "filter:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.filter_choice, 1, wx.EXPAND) sz.AddSpacer(3) sz.Add(wx.StaticText(self, -1, "gate:"), 0, wx.TOP, 4) sz.AddSpacer(3) sz.Add(self.gate_choice, 1, wx.EXPAND) sizer.Add(sz, 1, wx.EXPAND) sizer.Add(-1, 2, 0) sizer.Add(self.update_chart_btn) self.x_table_choice.Bind(wx.EVT_COMBOBOX, self.on_x_table_selected) self.y_table_choice.Bind(wx.EVT_COMBOBOX, self.on_y_table_selected) self.gate_choice.addobserver(self.on_gate_selected) self.update_chart_btn.Bind(wx.EVT_BUTTON, self.update_figpanel) self.SetSizer(sizer) self.Show(1) @property def x_column(self): x_table_choice_id = self.x_table_choice.GetSelection() x_choice_id = self.x_choice.GetSelection() return sql.Column(self.x_table_choice.GetString(x_table_choice_id), self.x_choice.GetString(x_choice_id)) @property def y_column(self): y_table_choice_id = self.y_table_choice.GetSelection() y_choice_id = self.y_choice.GetSelection() return sql.Column(self.y_table_choice.GetString(y_table_choice_id), self.y_choice.GetString(y_choice_id)) @property def filter(self): return self.filter_choice.get_filter_or_none() def on_x_table_selected(self, evt): table = self.x_table_choice.Value if table == ui.TableComboBox.OTHER_TABLE: t = ui.get_other_table_from_user(self) if t is not None: self.x_table_choice.Items = self.x_table_choice.Items[:-1] + [t] + self.x_table_choice.Items[-1:] self.x_table_choice.Select(self.x_table_choice.Items.index(t)) sel = self.y_table_choice.GetSelection() self.y_table_choice.Items = self.y_table_choice.Items[:-1] + [t] + self.y_table_choice.Items[-1:] self.y_table_choice.SetSelection(sel) else: self.x_table_choice.Select(0) return self.update_x_choices() def on_y_table_selected(self, evt): table = self.y_table_choice.Value if table == ui.TableComboBox.OTHER_TABLE: t = ui.get_other_table_from_user(self) if t is not None: self.y_table_choice.Items = self.y_table_choice.Items[:-1] + [t] + self.y_table_choice.Items[-1:] self.y_table_choice.Select(self.y_table_choice.Items.index(t)) sel = self.x_table_choice.GetSelection() self.x_table_choice.Items = self.x_table_choice.Items[:-1] + [t] + self.x_table_choice.Items[-1:] self.x_table_choice.SetSelection(sel) else: self.y_table_choice.Select(0) return self.update_y_choices() def on_gate_selected(self, gate_name): self.update_gate_helper() def update_gate_helper(self): gate_name = self.gate_choice.get_gatename_or_none() if gate_name: # Deactivate the lasso tool self.figpanel.get_toolbar().toggle_user_tool('lasso', False) self.figpanel.gate_helper.set_displayed_gate(p.gates[gate_name], self.x_column, self.y_column) else: self.figpanel.gate_helper.disable() def update_x_choices(self): tablename = self.x_table_choice.Value fieldnames = db.GetColumnNames(tablename) self.x_choice.Clear() self.x_choice.AppendItems(fieldnames) self.x_choice.SetSelection(0) def update_y_choices(self): tablename = self.y_table_choice.Value fieldnames = db.GetColumnNames(tablename) self.y_choice.Clear() self.y_choice.AppendItems(fieldnames) self.y_choice.SetSelection(0) def _plotting_per_object_data(self): return (p.object_table is not None and p.object_table in [self.x_column.table, self.y_column.table] or (self.x_column.table != p.image_table and db.adjacent(p.object_table, self.x_column.table)) or (self.y_column.table != p.image_table and db.adjacent(p.object_table, self.y_column.table)) ) def update_figpanel(self, evt=None): self.gate_choice.set_gatable_columns([self.x_column, self.y_column]) self.gate_choice.update_info() keys_and_points = self._load_points() col_types = self.get_selected_column_types() # Convert keys and points into a np array # NOTE: We must set dtype "object" on creation or values like 0.34567e-9 # may be truncated to 0.34567e (error) or 0.345 (no error) when # the array contains strings. kps = np.array(keys_and_points, dtype='object') # Strip out keys if self._plotting_per_object_data(): key_indices = list(range(len(object_key_columns()))) else: key_indices = list(range(len(image_key_columns()))) keys = kps[:,key_indices].astype(int) # Strip out x coords if col_types[0] in (float, int): xpoints = kps[:,-2].astype('float32') else: xpoints = kps[:,-2] # Strip out y coords if col_types[1] in (float, int): ypoints = kps[:,-1].astype('float32') else: ypoints = kps[:,-1] # plot the points self.figpanel.set_points(xpoints, ypoints) self.figpanel.set_keys(keys) self.figpanel.set_x_scale(self.x_scale_choice.Value) self.figpanel.set_y_scale(self.y_scale_choice.Value) self.figpanel.set_x_label(self.x_column.col) self.figpanel.set_y_label(self.y_column.col) self.update_gate_helper() self.figpanel.redraw() self.figpanel.draw() def _load_points(self): q = sql.QueryBuilder() select = [] # # If there's an object table fetch object keys. Else fetch image keys. # # TODO: linking per-well data doesn't work if we fetch keys this way # if self._plotting_per_object_data(): select += [sql.Column(p.object_table, col) for col in object_key_columns()] else: select += [sql.Column(p.image_table, col) for col in image_key_columns()] select += [self.x_column, self.y_column] q.set_select_clause(select) if self.filter != None: q.add_filter(self.filter) q.add_where(sql.Expression(self.x_column, 'IS NOT NULL')) q.add_where(sql.Expression(self.y_column, 'IS NOT NULL')) return db.execute(str(q)) def get_selected_column_types(self): ''' Returns a tuple containing the x and y column types. ''' return (db.GetColumnType(self.x_table_choice.Value, self.x_choice.Value), db.GetColumnType(self.y_table_choice.Value, self.y_choice.Value)) def save_settings(self): '''save_settings is called when saving a workspace to file. returns a dictionary mapping setting names to values encoded as strings ''' d = {'x-table' : self.x_table_choice.Value, 'y-table' : self.y_table_choice.Value, 'x-axis' : self.x_choice.Value, 'y-axis' : self.y_choice.Value, 'x-scale' : self.x_scale_choice.Value, 'y-scale' : self.y_scale_choice.Value, 'filter' : self.filter_choice.Value, 'x-lim' : self.figpanel.subplot.get_xlim(), 'y-lim' : self.figpanel.subplot.get_ylim(), 'version' : '1', } if self.gate_choice.get_gatename_or_none(): gate_choice_id = self.gate_choice.GetSelection() d['gate'] = self.gate_choice.GetString(gate_choice_id) return d def load_settings(self, settings): '''load_settings is called when loading a workspace from file. settings - a dictionary mapping setting names to values encoded as strings. ''' if 'version' not in settings: if 'table' in settings: settings['x-table'] = settings['table'] settings['y-table'] = settings['table'] settings['version'] = '1' if 'x-table' in settings: self.x_table_choice.SetStringSelection(settings['x-table']) self.update_x_choices() if 'y-table' in settings: self.y_table_choice.SetStringSelection(settings['y-table']) self.update_y_choices() if 'x-axis' in settings: self.x_choice.SetStringSelection(settings['x-axis']) if 'y-axis' in settings: self.y_choice.SetStringSelection(settings['y-axis']) if 'x-scale' in settings: self.x_scale_choice.SetStringSelection(settings['x-scale']) if 'y-scale' in settings: self.y_scale_choice.SetStringSelection(settings['y-scale']) if 'filter' in settings: self.filter_choice.SetStringSelection(settings['filter']) self.update_figpanel() if 'x-lim' in settings: self.figpanel.subplot.set_xlim(eval(settings['x-lim'])) if 'y-lim' in settings: self.figpanel.subplot.set_ylim(eval(settings['y-lim'])) if 'gate' in settings: self.gate_choice.SetStringSelection(settings['gate']) self.figpanel.gate_helper.set_displayed_gate( p.gates[settings['gate']], self.x_column, self.y_column) self.figpanel.draw()