示例#1
0
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
示例#2
0
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