class FCGateManager(EventGenerator): """Manages gate creation widgets and gates.""" def __init__(self, ax, callback_list=None): self.gates = [] self.fig = ax.figure self.ax = ax self._plt_data = None self.active_gate = None self.sample = None self.canvas = self.fig.canvas self.key_handler_cid = self.canvas.mpl_connect( 'key_press_event', lambda event: key_press_handler(event, self.canvas, self)) self.pick_event_cid = self.canvas.mpl_connect('pick_event', self.pick_event_handler) self.gate_num = 1 self.current_channels = 'd1', 'd2' self.add_callback(callback_list) def disconnect_events(self): self.canvas.mpl_disconnect(self.key_handler_cid) self.canvas.mpl_disconnect(self.pick_event_cid) def pick_event_handler(self, event): """ Handles pick events """ info = { 'options': self.get_available_channels(), 'guiEvent': event.mouseevent.guiEvent, } if hasattr(self, 'xlabel_artist') and (event.artist == self.xlabel_artist): info['axis_num'] = 0 self.callback(Event('axis_click', info)) if hasattr(self, 'ylabel_artist') and (event.artist == self.ylabel_artist): info['axis_num'] = 1 self.callback(Event('axis_click', info)) def add_gate(self, gate): self.gates.append(gate) self.set_active_gate(gate) def remove_active_gate(self): if self.active_gate is not None: self.gates.remove(self.active_gate) self.active_gate.remove() self.active_gate = None def set_active_gate(self, gate): if self.active_gate is None: self.active_gate = gate gate.activate() elif self.active_gate is not gate: self.active_gate.inactivate() self.active_gate = gate gate.activate() def _get_next_gate_name(self): gate_name = 'gate{0}'.format(self.gate_num) self.gate_num += 1 return gate_name def _handle_gate_events(self, event): self.set_active_gate(event.info['caller']) def create_gate_widget(self, kind): def clean_drawing_tools(): self._drawing_tool.disconnect_events() self.canvas.draw_idle() self._drawing_tool = None def create_gate(*args): cancelled = False # TODO allow drawing tool to cancel verts = args[0] ch = self.current_channels verts = [dict(zip(ch, v)) for v in verts] if kind == 'poly': gate_type = PolyGate elif 'threshold' in kind or 'quad' in kind: gate_type = ThresholdGate # FIXME: This is very specific implementation if 'vertical' in kind: verts = [{ch[0]: v[ch[0]]} for v in verts] elif 'horizontal' in kind: if len(ch) == 1: cancelled = True else: verts = [{ch[1]: v[ch[1]]} for v in verts] if not cancelled: gate = BaseGate(verts, gate_type, name=self._get_next_gate_name(), callback_list=self._handle_gate_events) gate.spawn(ch, self.ax) self.add_gate(gate) clean_drawing_tools() def start_drawing(kind): if kind == 'poly': self._drawing_tool = PolyDrawer(self.ax, oncreated=create_gate, lineprops=dict(color='k', marker='o')) elif kind == 'quad': self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=1) elif kind == 'horizontal threshold': self._drawing_tool = Cursor(self.ax, vertOn=0, horizOn=1) elif kind == 'vertical threshold': self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=0) if isinstance(self._drawing_tool, Cursor): def finish_drawing(event): self._drawing_tool.clear(None) return create_gate([(event.xdata, event.ydata)]) self._drawing_tool.connect_event('button_press_event', finish_drawing) start_drawing(kind) #################### ### Loading Data ### #################### def load_fcs(self, filepath=None, parent=None): ax = self.ax if parent is None: parent = self.fig.canvas if filepath is None: from FlowCytometryTools.gui import dialogs filepath = dialogs.open_file_dialog('Select an FCS file to load', 'FCS files (*.fcs)|*.fcs', parent=parent) if filepath is not None: self.sample = FCMeasurement('temp', datafile=filepath) print('WARNING: Data is raw (not transformation).') self._sample_loaded_event() def load_measurement(self, measurement): self.sample = measurement.copy() self._sample_loaded_event() def _sample_loaded_event(self): if self.sample is not None: self.current_channels = list(self.sample.channel_names[0:2]) self.set_axes(self.current_channels, self.ax) def get_available_channels(self): return self.sample.channel_names def change_axis(self, axis_num, channel_name): """ TODO: refactor that and set_axes what to do with ax? axis_num: int axis number channel_name: str new channel to plot on that axis """ current_channels = list(self.current_channels) if len(current_channels) == 1: if axis_num == 0: new_channels = channel_name, else: new_channels = current_channels[0], channel_name else: new_channels = list(current_channels) new_channels[axis_num] = channel_name self.set_axes(new_channels, self.ax) def set_axes(self, channels, ax): """ channels : iterable of string each value corresponds to a channel names names must be unique """ # To make sure displayed as hist if len(set(channels)) == 1: channels = channels[0], self.current_channels = channels # Remove existing gates for gate in self.gates: gate.remove_spawned_gates() ## # Has a clear axis command inside!! # which will "force kill" spawned gates self.plot_data() for gate in self.gates: sgate = gate.spawn(channels, ax) gate._refresh_activation() def close(self): for gate in self.gates: gate.remove() self.disconnect_events() #################### ### Plotting Data ## #################### def plot_data(self): """Plots the loaded data""" # Clear the plot before plotting onto it self.ax.cla() if self.sample is None: return if self.current_channels is None: self.current_channels = self.sample.channel_names[:2] channels = self.current_channels channels_to_plot = channels[0] if len(channels) == 1 else channels self.sample.plot(channels_to_plot, ax=self.ax) xaxis = self.ax.get_xaxis() yaxis = self.ax.get_yaxis() self.xlabel_artist = xaxis.get_label() self.ylabel_artist = yaxis.get_label() self.xlabel_artist.set_picker(5) self.ylabel_artist.set_picker(5) self.fig.canvas.draw() def get_generation_code(self): """Return python code that generates all drawn gates.""" if len(self.gates) < 1: code = '' else: import_list = set( [gate._gencode_gate_class for gate in self.gates]) import_list = 'from FlowCytometryTools import ' + ', '.join( import_list) code_list = [gate.get_generation_code() for gate in self.gates] code_list.sort() code_list = '\n'.join(code_list) code = import_list + 2 * '\n' + code_list self.callback(Event('generated_code', {'code': code})) return code
class FCGateManager(EventGenerator): """Manages gate creation widgets and gates.""" def __init__(self, ax, callback_list=None): self.gates = [] self.fig = ax.figure self.ax = ax self._plt_data = None self.active_gate = None self.sample = None self.canvas = self.fig.canvas self.key_handler_cid = self.canvas.mpl_connect('key_press_event', lambda event: key_press_handler(event, self.canvas, self)) self.pick_event_cid = self.canvas.mpl_connect('pick_event', self.pick_event_handler) self.gate_num = 1 self.current_channels = 'd1', 'd2' self.add_callback(callback_list) def disconnect_events(self): self.canvas.mpl_disconnect(self.key_handler_cid) self.canvas.mpl_disconnect(self.pick_event_cid) def pick_event_handler(self, event): """ Handles pick events """ info = {'options': self.get_available_channels(), 'guiEvent': event.mouseevent.guiEvent, } if hasattr(self, 'xlabel_artist') and (event.artist == self.xlabel_artist): info['axis_num'] = 0 self.callback(Event('axis_click', info)) if hasattr(self, 'ylabel_artist') and (event.artist == self.ylabel_artist): info['axis_num'] = 1 self.callback(Event('axis_click', info)) def add_gate(self, gate): self.gates.append(gate) self.set_active_gate(gate) def remove_active_gate(self): if self.active_gate is not None: self.gates.remove(self.active_gate) self.active_gate.remove() self.active_gate = None def set_active_gate(self, gate): if self.active_gate is None: self.active_gate = gate gate.activate() elif self.active_gate is not gate: self.active_gate.inactivate() self.active_gate = gate gate.activate() def _get_next_gate_name(self): gate_name = 'gate{0}'.format(self.gate_num) self.gate_num += 1 return gate_name def _handle_gate_events(self, event): self.set_active_gate(event.info['caller']) def create_gate_widget(self, kind): def clean_drawing_tools(): self._drawing_tool.disconnect_events() self.canvas.draw_idle() self._drawing_tool = None def create_gate(*args): cancelled = False # TODO allow drawing tool to cancel verts = args[0] ch = self.current_channels verts = [dict(zip(ch, v)) for v in verts] if kind == 'poly': gate_type = PolyGate elif 'threshold' in kind or 'quad' in kind: gate_type = ThresholdGate # FIXME: This is very specific implementation if 'vertical' in kind: verts = [{ch[0]: v[ch[0]]} for v in verts] elif 'horizontal' in kind: if len(ch) == 1: cancelled = True else: verts = [{ch[1]: v[ch[1]]} for v in verts] if not cancelled: gate = BaseGate(verts, gate_type, name=self._get_next_gate_name(), callback_list=self._handle_gate_events) gate.spawn(ch, self.ax) self.add_gate(gate) clean_drawing_tools() def start_drawing(kind): if kind == 'poly': self._drawing_tool = PolyDrawer(self.ax, oncreated=create_gate, lineprops=dict(color='k', marker='o')) elif kind == 'quad': self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=1) elif kind == 'horizontal threshold': self._drawing_tool = Cursor(self.ax, vertOn=0, horizOn=1) elif kind == 'vertical threshold': self._drawing_tool = Cursor(self.ax, vertOn=1, horizOn=0) if isinstance(self._drawing_tool, Cursor): def finish_drawing(event): self._drawing_tool.clear(None) return create_gate([(event.xdata, event.ydata)]) self._drawing_tool.connect_event('button_press_event', finish_drawing) start_drawing(kind) #################### ### Loading Data ### #################### def load_fcs(self, filepath=None, parent=None): ax = self.ax if parent is None: parent = self.fig.canvas if filepath is None: from FlowCytometryTools.gui import dialogs filepath = dialogs.open_file_dialog('Select an FCS file to load', 'FCS files (*.fcs)|*.fcs', parent=parent) if filepath is not None: self.sample = FCMeasurement('temp', datafile=filepath) print('WARNING: Data is raw (not transformation).') self._sample_loaded_event() def load_measurement(self, measurement): self.sample = measurement.copy() self._sample_loaded_event() def _sample_loaded_event(self): if self.sample is not None: self.current_channels = list(self.sample.channel_names[0:2]) self.set_axes(self.current_channels, self.ax) def get_available_channels(self): return self.sample.channel_names def change_axis(self, axis_num, channel_name): """ TODO: refactor that and set_axes what to do with ax? axis_num: int axis number channel_name: str new channel to plot on that axis """ current_channels = list(self.current_channels) if len(current_channels) == 1: if axis_num == 0: new_channels = channel_name, else: new_channels = current_channels[0], channel_name else: new_channels = list(current_channels) new_channels[axis_num] = channel_name self.set_axes(new_channels, self.ax) def set_axes(self, channels, ax): """ channels : iterable of string each value corresponds to a channel names names must be unique """ # To make sure displayed as hist if len(set(channels)) == 1: channels = channels[0], self.current_channels = channels # Remove existing gates for gate in self.gates: gate.remove_spawned_gates() ## # Has a clear axis command inside!! # which will "force kill" spawned gates self.plot_data() for gate in self.gates: sgate = gate.spawn(channels, ax) gate._refresh_activation() def close(self): for gate in self.gates: gate.remove() self.disconnect_events() #################### ### Plotting Data ## #################### def plot_data(self): """Plots the loaded data""" # Clear the plot before plotting onto it self.ax.cla() if self.sample is None: return if self.current_channels is None: self.current_channels = self.sample.channel_names[:2] channels = self.current_channels channels_to_plot = channels[0] if len(channels) == 1 else channels self.sample.plot(channels_to_plot, ax=self.ax) xaxis = self.ax.get_xaxis() yaxis = self.ax.get_yaxis() self.xlabel_artist = xaxis.get_label() self.ylabel_artist = yaxis.get_label() self.xlabel_artist.set_picker(5) self.ylabel_artist.set_picker(5) self.fig.canvas.draw() def get_generation_code(self): """Return python code that generates all drawn gates.""" if len(self.gates) < 1: code = '' else: import_list = set([gate._gencode_gate_class for gate in self.gates]) import_list = 'from FlowCytometryTools import ' + ', '.join(import_list) code_list = [gate.get_generation_code() for gate in self.gates] code_list.sort() code_list = '\n'.join(code_list) code = import_list + 2 * '\n' + code_list self.callback(Event('generated_code', {'code': code})) return code