class PolygonSelection(cytoflow.views.ScatterplotView): """Plots, and lets the user interact with, a 2D polygon selection. Attributes ---------- op : Instance(PolygonOp) The operation on which this selection view is operating huefacet : Str The conditioning variable to show multiple colors on this plot subset : Str The string for subsetting the plot interactive : Bool is this view interactive? Ie, can the user set the polygon verticies with mouse clicks? Notes ----- We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterPlotView`, but they must both be unset! Examples -------- In an IPython notebook with `%matplotlib notebook` >>> s = flow.ScatterplotView(xchannel = "V2-A", ... ychannel = "Y2-A") >>> poly = s.default_view() >>> poly.plot(ex2) >>> poly.interactive = True """ id = Constant('edu.mit.synbio.cytoflow.views.polygon') friendly_id = Constant("Polygon Selection") op = Instance(IOperation) name = DelegatesTo('op') xchannel = DelegatesTo('op') ychannel = DelegatesTo('op') interactive = Bool(False, transient=True) # internal state. _ax = Any(transient=True) _cursor = Instance(Cursor, transient=True) _path = Instance(mpl.path.Path, transient=True) _patch = Instance(mpl.patches.PathPatch, transient=True) _line = Instance(mpl.lines.Line2D, transient=True) _drawing = Bool(transient=True) _last_draw_time = Float(0.0, transient=True) _last_click_time = Float(0.0, transient=True) def plot(self, experiment, **kwargs): """Plot self.view, and then plot the selection on top of it.""" if not experiment: raise util.CytoflowViewError("No experiment specified") if self.xfacet: raise util.CytoflowViewError( "RangeSelection.xfacet must be empty or `Undefined`") if self.yfacet: raise util.CytoflowViewError( "RangeSelection.yfacet must be empty or `Undefined`") super(PolygonSelection, self).plot(experiment, **kwargs) self._ax = plt.gca() self._draw_poly() self._interactive() @on_trait_change('op.vertices', post_init=True) def _draw_poly(self): if not self._ax: return if self._patch and self._patch in self._ax.patches: self._patch.remove() if self._drawing or not self.op.vertices or len(self.op.vertices) < 3 \ or any([len(x) != 2 for x in self.op.vertices]): return patch_vert = np.concatenate( (np.array(self.op.vertices), np.array((0, 0), ndmin=2))) self._patch = \ mpl.patches.PathPatch(mpl.path.Path(patch_vert, closed = True), edgecolor="black", linewidth = 1.5, fill = False) self._ax.add_patch(self._patch) plt.draw_if_interactive() @on_trait_change('interactive', post_init=True) def _interactive(self): if self._ax and self.interactive: self._cursor = Cursor(self._ax, horizOn=False, vertOn=False) self._cursor.connect_event('button_press_event', self._onclick) self._cursor.connect_event('motion_notify_event', self._onmove) elif self._cursor: self._cursor.disconnect_events() self._cursor = None def _onclick(self, event): """Update selection traits""" if not self._ax: return if (self._cursor.ignore(event)): return # we have to check the wall clock time because the IPython notebook # doesn't seem to register double-clicks if event.dblclick or (time.clock() - self._last_click_time < 0.5): self._drawing = False self.op.vertices = map(tuple, self._path.vertices) self.op._xscale = plt.gca().get_xscale() self.op._yscale = plt.gca().get_yscale() self._path = None return self._last_click_time = time.clock() self._drawing = True if self._patch and self._patch in self._ax.patches: self._patch.remove() if self._path: vertices = np.concatenate((self._path.vertices, np.array((event.xdata, event.ydata), ndmin=2))) else: vertices = np.array((event.xdata, event.ydata), ndmin=2) self._path = mpl.path.Path(vertices, closed=False) self._patch = mpl.patches.PathPatch(self._path, edgecolor="black", fill=False) self._ax.add_patch(self._patch) plt.draw_if_interactive() def _onmove(self, event): if not self._ax: return if (self._cursor.ignore(event) or not self._drawing or not self._path or self._path.vertices.shape[0] == 0 or not event.xdata or not event.ydata): return # only draw 5 times/sec if (time.clock() - self._last_draw_time < 0.2): return self._last_draw_time = time.clock() if self._line and self._line in self._ax.lines: self._line.remove() xdata = [self._path.vertices[-1, 0], event.xdata] ydata = [self._path.vertices[-1, 1], event.ydata] self._line = mpl.lines.Line2D(xdata, ydata, linewidth=1, color="black") self._ax.add_line(self._line) plt.gcf().canvas.draw()
class PolygonSelection(HasStrictTraits): """Plots, and lets the user interact with, a 2D selection. Attributes ---------- polygon : Instance(numpy.ndarray) The polygon vertices view : Instance(IView) the IView that this view is wrapping. I suggest that if it's another ISelectionView, that its `interactive` property remain False. >.> interactive : Bool is this view interactive? Ie, can the user set the polygon verticies with mouse clicks? """ id = "edu.mit.synbio.cytoflow.views.polygon" friendly_id = "Polygon Selection" view = Instance(IView, transient = True) interactive = Bool(False, transient = True) vertices = List((Float, Float)) # internal state. _cursor = Instance(Cursor, transient = True) _path = Instance(mpl.path.Path, transient = True) _patch = Instance(mpl.patches.PathPatch, transient = True) _line = Instance(mpl.lines.Line2D, transient = True) _drawing = Bool(transient = True) def plot(self, experiment, **kwargs): """Plot self.view, and then plot the selection on top of it.""" self.view.plot(experiment, **kwargs) self._draw_poly() def is_valid(self, experiment): """If the decorated view is valid, we are too.""" return self.view.is_valid(experiment) @on_trait_change('vertices') def _draw_poly(self): ca = plt.gca() if self._patch and self._patch in ca.patches: self._patch.remove() if self._drawing or not self.vertices or len(self.vertices) < 3 \ or any([len(x) != 2 for x in self.vertices]): return patch_vert = np.concatenate((np.array(self.vertices), np.array((0,0), ndmin = 2))) self._patch = \ mpl.patches.PathPatch(mpl.path.Path(patch_vert, closed = True), edgecolor="black", linewidth = 1.5, fill = False) ca.add_patch(self._patch) plt.gcf().canvas.draw() @on_trait_change('interactive') def _interactive(self): if self.interactive: ax = plt.gca() self._cursor = Cursor(ax, horizOn = False, vertOn = False) self._cursor.connect_event('button_press_event', self._onclick) self._cursor.connect_event('motion_notify_event', self._onmove) else: self._cursor.disconnect_events() self._cursor = None def _onclick(self, event): """Update selection traits""" if(self._cursor.ignore(event)): return if event.dblclick: self._drawing = False self.vertices = map(tuple, self._path.vertices) self._path = None return ca = plt.gca() self._drawing = True if self._patch and self._patch in ca.patches: self._patch.remove() if self._path: vertices = np.concatenate((self._path.vertices, np.array((event.xdata, event.ydata), ndmin = 2))) else: vertices = np.array((event.xdata, event.ydata), ndmin = 2) self._path = mpl.path.Path(vertices, closed = False) self._patch = mpl.patches.PathPatch(self._path, edgecolor = "black", fill = False) ca.add_patch(self._patch) plt.gcf().canvas.draw() def _onmove(self, event): if(self._cursor.ignore(event) or not self._drawing or not self._path or self._path.vertices.shape[0] == 0 or not event.xdata or not event.ydata): return ca = plt.gca() if not ca: return if self._line and self._line in ca.lines: self._line.remove() xdata = [self._path.vertices[-1, 0], event.xdata] ydata = [self._path.vertices[-1, 1], event.ydata] self._line = mpl.lines.Line2D(xdata, ydata, linewidth = 1, color = "black") ca.add_line(self._line) plt.gcf().canvas.draw()
class PolygonSelection(cytoflow.views.ScatterplotView): """Plots, and lets the user interact with, a 2D polygon selection. Attributes ---------- op : Instance(PolygonOp) The operation on which this selection view is operating huefacet : Str The conditioning variable to show multiple colors on this plot subset : Str The string for subsetting the plot interactive : Bool is this view interactive? Ie, can the user set the polygon verticies with mouse clicks? Notes ----- We inherit `xfacet` and `yfacet` from `cytoflow.views.ScatterPlotView`, but they must both be unset! Examples -------- In an IPython notebook with `%matplotlib notebook` >>> s = flow.ScatterplotView(xchannel = "V2-A", ... ychannel = "Y2-A") >>> poly = s.default_view() >>> poly.plot(ex2) >>> poly.interactive = True """ id = Constant('edu.mit.synbio.cytoflow.views.polygon') friendly_id = Constant("Polygon Selection") op = Instance(IOperation) name = DelegatesTo('op') xchannel = DelegatesTo('op') ychannel = DelegatesTo('op') interactive = Bool(False, transient = True) # internal state. _ax = Any(transient = True) _cursor = Instance(Cursor, transient = True) _path = Instance(mpl.path.Path, transient = True) _patch = Instance(mpl.patches.PathPatch, transient = True) _line = Instance(mpl.lines.Line2D, transient = True) _drawing = Bool(transient = True) _last_draw_time = Float(0.0, transient = True) _last_click_time = Float(0.0, transient = True) def plot(self, experiment, **kwargs): """Plot self.view, and then plot the selection on top of it.""" if not experiment: raise util.CytoflowViewError("No experiment specified") if self.xfacet: raise util.CytoflowViewError("RangeSelection.xfacet must be empty or `Undefined`") if self.yfacet: raise util.CytoflowViewError("RangeSelection.yfacet must be empty or `Undefined`") super(PolygonSelection, self).plot(experiment, **kwargs) self._ax = plt.gca() self._draw_poly() self._interactive() @on_trait_change('op.vertices', post_init = True) def _draw_poly(self): if not self._ax: return if self._patch and self._patch in self._ax.patches: self._patch.remove() if self._drawing or not self.op.vertices or len(self.op.vertices) < 3 \ or any([len(x) != 2 for x in self.op.vertices]): return patch_vert = np.concatenate((np.array(self.op.vertices), np.array((0,0), ndmin = 2))) self._patch = \ mpl.patches.PathPatch(mpl.path.Path(patch_vert, closed = True), edgecolor="black", linewidth = 1.5, fill = False) self._ax.add_patch(self._patch) plt.draw_if_interactive() @on_trait_change('interactive', post_init = True) def _interactive(self): if self._ax and self.interactive: self._cursor = Cursor(self._ax, horizOn = False, vertOn = False) self._cursor.connect_event('button_press_event', self._onclick) self._cursor.connect_event('motion_notify_event', self._onmove) elif self._cursor: self._cursor.disconnect_events() self._cursor = None def _onclick(self, event): """Update selection traits""" if not self._ax: return if(self._cursor.ignore(event)): return # we have to check the wall clock time because the IPython notebook # doesn't seem to register double-clicks if event.dblclick or (time.clock() - self._last_click_time < 0.5): self._drawing = False self.op.vertices = map(tuple, self._path.vertices) self.op._xscale = plt.gca().get_xscale() self.op._yscale = plt.gca().get_yscale() self._path = None return self._last_click_time = time.clock() self._drawing = True if self._patch and self._patch in self._ax.patches: self._patch.remove() if self._path: vertices = np.concatenate((self._path.vertices, np.array((event.xdata, event.ydata), ndmin = 2))) else: vertices = np.array((event.xdata, event.ydata), ndmin = 2) self._path = mpl.path.Path(vertices, closed = False) self._patch = mpl.patches.PathPatch(self._path, edgecolor = "black", fill = False) self._ax.add_patch(self._patch) plt.draw_if_interactive() def _onmove(self, event): if not self._ax: return if(self._cursor.ignore(event) or not self._drawing or not self._path or self._path.vertices.shape[0] == 0 or not event.xdata or not event.ydata): return # only draw 5 times/sec if(time.clock() - self._last_draw_time < 0.2): return self._last_draw_time = time.clock() if self._line and self._line in self._ax.lines: self._line.remove() xdata = [self._path.vertices[-1, 0], event.xdata] ydata = [self._path.vertices[-1, 1], event.ydata] self._line = mpl.lines.Line2D(xdata, ydata, linewidth = 1, color = "black") self._ax.add_line(self._line) plt.gcf().canvas.draw()