Ejemplo n.º 1
0
def task(args):
    task_id, a, slice_ = args
    width, height = a.shape
    b = io.BytesIO()

    x = np.arange(width)
    y = np.arange(height)
    X, Y = np.meshgrid(x, y)


    fig, ax = plt.subplots(figsize=(width / 100, height / 100), frameon=False, dpi=100)
    fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)
    quad_mesh = ax.pcolormesh(X, Y, a.T, cmap='viridis')
    ax.grid(False)
    ax.axis('off')
    # ax.semilogy()

    rect = Rectangle((0, 0), 0, height, linewidth=0, facecolor='black', alpha=0.5)
    ax.add_patch(rect)

    for i in range(slice_.start, slice_.stop):
        rect.set_bounds((0, 0, i, height))
        fig.savefig(b, format='rgba', dpi=100)
        print('task_id:', task_id, 'i', i)

    return task_id, b.getvalue()
Ejemplo n.º 2
0
class CustomToolbar(NavToolbar):

    toolitems = NavToolbar.toolitems + (
        (None, None, None, None),
        ("ROI", "Select ROI", "selection", "_on_custom_select"),
    )

    def __init__(self, plotCanvas):
        # create the default toolbar
        NavToolbar.__init__(self, plotCanvas)
        self.selector = RectSelector(
            self.canvas.figure.axes[0], self.onSelect, button=[1, 3], minspanx=5, minspany=5  # don't use middle button
        )
        self.selector.set_active(True)
        self.ax = self.canvas.figure.axes[0]
        self.roi = None
        self.fixedSize = False
        if wx.Platform == "__WXMAC__":
            self.to_draw = Rectangle(
                (0, 0), 0, 1, visible=False, facecolor="yellow", edgecolor="black", alpha=0.5, fill=True
            )
            self.ax.add_patch(self.to_draw)
            self.background = None

    def _init_toolbar(self):
        self._parent = self.canvas.GetParent()

        self.wx_ids = {}
        for text, tooltip_text, image_file, callback in self.toolitems:
            if text is None:
                self.AddSeparator()
                continue
            self.wx_ids[text] = wx.NewId()
            try:
                bitmap = _load_bitmap(image_file + ".png")
            except IOError:
                bitmap = wx.Bitmap(image_file + ".png")
            if text in ["Pan", "Zoom", "ROI"]:
                self.AddCheckTool(self.wx_ids[text], bitmap, shortHelp=text, longHelp=tooltip_text)
            else:
                self.AddSimpleTool(self.wx_ids[text], bitmap, text, tooltip_text)
            bind(self, wx.EVT_TOOL, getattr(self, callback), id=self.wx_ids[text])

        self.ToggleTool(self.wx_ids["ROI"], True)
        self.Realize()

    def _set_markers(self):
        self.canvas.parentFrame.set_markers()

    def _update_view(self):
        NavToolbar._update_view(self)
        self._set_markers()
        # MacOS needs a forced draw to update plot
        if wx.Platform == "__WXMAC__":
            self.canvas.draw()

    def draw(self):
        self._set_markers()
        NavToolbar.draw(self)
        # MacOS needs a forced draw to update plot
        if wx.Platform == "__WXMAC__":
            self.canvas.draw()

    def zoom(self, ev):
        if wx.Platform == "__WXMAC__":
            self.ToggleTool(self.wx_ids["Zoom"], self.GetToolState(self.wx_ids["Zoom"]))
        NavToolbar.zoom(self, ev)

    def pan(self, ev):
        if wx.Platform == "__WXMAC__":
            self.ToggleTool(self.wx_ids["Pan"], self.GetToolState(self.wx_ids["Pan"]))
        NavToolbar.pan(self, ev)

    def press_zoom(self, ev):
        if wx.Platform == "__WXMAC__":
            self.update_background()
            self.to_draw.set_visible(True)
        NavToolbar.press_zoom(self, ev)

    def release_zoom(self, ev):
        if wx.Platform == "__WXMAC__":
            self.to_draw.set_visible(False)
        NavToolbar.release_zoom(self, ev)

    def draw_rubberband(self, event, x0, y0, x1, y1):
        # XOR does not work on MacOS ...
        if wx.Platform != "__WXMAC__":
            NavToolbar.draw_rubberband(self, event, x0, y0, x1, y1)
        else:
            if self.background is not None:
                self.canvas.restore_region(self.background)
            c0, c1 = self.ax.transData.inverted().transform([[x0, y0], [x1, y1]])
            l, b = c0
            r, t = c1
            self.to_draw.set_bounds(l, b, r - l, t - b)
            self.ax.draw_artist(self.to_draw)
            self.canvas.blit(self.ax.bbox)

    def update_background(self):
        """force an update of the background"""
        self.background = self.canvas.copy_from_bbox(self.ax.bbox)

    # Turn on selection
    # TODO: Proper handling of states, actual functionality.
    def _on_custom_select(self, evt):
        #        for id in ['Zoom','Pan']:
        #            self.ToggleTool(self.wx_ids[id], False)
        #        print('Select ROI: %s' % (self.GetToolState(self.wx_ids['ROI'])))
        #        self.ToggleTool(self.wx_ids['ROI'],
        #                self.GetToolState(self.wx_ids['ROI']) )
        self.toggle_selector()

    #        print('Select ROI: %s' % (self.GetToolState(self.wx_ids['ROI'])))

    def onSelect(self, eclick, erelease):
        "eclick and erelease are matplotlib events at press and release"
        #        print(' startposition : (%f, %f)' % (eclick.xdata, eclick.ydata))
        #        print(' endposition   : (%f, %f)' % (erelease.xdata, erelease.ydata))
        #        print(' used button   : ', eclick.button)
        self.updateROI(
            min(eclick.xdata, erelease.xdata),
            min(eclick.ydata, erelease.ydata),
            abs(eclick.xdata - erelease.xdata),
            abs(eclick.ydata - erelease.ydata),
        )
        if self.canvas.parentFrame.fixedNumberCB.IsChecked():
            # We are working in the fixed-number mode
            # We need to find new roi for this center point
            # The handler will call the update ROI function for us.
            self.canvas.parentFrame.handleROIforN()

    def updateROI(self, x, y, w, h):
        if self.roi is None:
            # print('upd ROI:', x, y, w, h)
            self.roi = Rectangle((x, y), w, h, ls="solid", lw=2, color="r", fill=False, zorder=5)
            self.canvas.figure.axes[0].add_patch(self.roi)
        else:
            self.roi.set_bounds(x, y, w, h)
        self.updateCanvas()

    def toggle_selector(self):
        self.selector.set_active(not self.selector.active)

    def onFixedSize(self, ev):
        self.fixedSize = ev.IsChecked()
        self.updateCanvas()

    def onWidthChange(self, ev):
        if self.roi:
            x = self.roi.get_x()
            w = self.roi.get_width()
            nw = ev.GetValue()
            dw = {"C": (w - nw) / 2, "L": 0, "R": w - nw}[self.canvas.parentFrame.anchorRB.GetStringSelection()[0]]
            self.roi.set_x(x + dw)
            self.roi.set_width(nw)
            self.updateCanvas()

    def onHeightChange(self, ev):
        if self.roi:
            y = self.roi.get_y()
            h = self.roi.get_height()
            nh = ev.GetValue()
            dh = {"C": (h - nh) / 2, "B": 0, "T": h - nh}[self.canvas.parentFrame.anchorRB.GetStringSelection()[-1]]
            self.roi.set_y(y + dh)
            self.roi.set_height(nh)
            self.updateCanvas()

    def updateCanvas(self, redraw=True):
        if self.roi:
            self.canvas.parentFrame.showROI(
                self.roi.get_x(), self.roi.get_y(), self.roi.get_width(), self.roi.get_height()
            )
            self.canvas.parentFrame.setWH(self.roi.get_width(), self.roi.get_height())
            if self.fixedSize:
                self.selector.setSize(self.roi.get_width(), self.roi.get_height())
            else:
                self.selector.setSize()
        if redraw:
            self.draw()
Ejemplo n.º 3
0
class Alignment(Axes):
    """
    matplotlib.axes.Axes subclass for rendering sequence alignments.
    """
    def __init__(self, fig, rect, *args, **kwargs):
        self.aln = kwargs.pop("aln")
        nrows = len(self.aln)
        ncols = self.aln.get_alignment_length()
        self.alnidx = numpy.arange(ncols)
        self.app = kwargs.pop("app", None)
        self.showy = kwargs.pop('showy', True)
        Axes.__init__(self, fig, rect, *args, **kwargs)
        rgb = mpl_colors.colorConverter.to_rgb
        gray = rgb('gray')
        d = defaultdict(lambda:gray)
        d["A"] = rgb("red")
        d["a"] = rgb("red")
        d["C"] = rgb("blue")
        d["c"] = rgb("blue")
        d["G"] = rgb("green")
        d["g"] = rgb("green")
        d["T"] = rgb("yellow")
        d["t"] = rgb("yellow")
        self.cmap = d
        self.selector = RectangleSelector(
            self, self.select_rectangle, useblit=True
            )
        def f(e):
            if e.button != 1: return True
            else: return RectangleSelector.ignore(self.selector, e)
        self.selector.ignore = f
        self.selected_rectangle = Rectangle(
            [0,0],0,0, facecolor='white', edgecolor='cyan', alpha=0.3
            )
        self.add_patch(self.selected_rectangle)
        self.highlight_find_collection = None

    def plot_aln(self):
        cmap = self.cmap
        self.ntax = len(self.aln); self.nchar = self.aln.get_alignment_length()
        a = numpy.array([ [ cmap[base] for base in x.seq ]
                          for x in self.aln ])
        self.array = a
        self.imshow(a, interpolation='nearest', aspect='auto', origin='lower')
        y = [ i+0.5 for i in xrange(self.ntax) ]
        labels = [ x.id for x in self.aln ]
        ## locator.bin_boundaries(1,ntax)
        ## locator.view_limits(1,ntax)
        if self.showy:
            locator = MaxNLocator(nbins=50, integer=True)
            self.yaxis.set_major_locator(locator)
            def fmt(x, pos=None):
                if x<0: return ""
                try: return labels[int(round(x))]
                except: pass
                return ""
            self.yaxis.set_major_formatter(FuncFormatter(fmt))
        else:
            self.yaxis.set_major_locator(NullLocator())
        
        return self

    def select_rectangle(self, e0, e1):
        x0, x1 = map(int, sorted((e0.xdata+0.5, e1.xdata+0.5)))
        y0, y1 = map(int, sorted((e0.ydata+0.5, e1.ydata+0.5)))
        self.selected_chars = (x0, x1)
        self.selected_taxa = (y0, y1)
        self.selected_rectangle.set_bounds(x0-0.5,y0-0.5,x1-x0+1,y1-y0+1)
        self.app.figure.canvas.draw_idle()

    def highlight_find(self, substr):
        if not substr:
            if self.highlight_find_collection:
                self.highlight_find_collection.remove()
                self.highlight_find_collection = None
            return
            
        N = len(substr)
        v = []
        for y, x in align.find(self.aln, substr):
            r = Rectangle(
                [x-0.5,y-0.5], N, 1,
                facecolor='cyan', edgecolor='cyan', alpha=0.7
                )
            v.append(r)
        if self.highlight_find_collection:
            self.highlight_find_collection.remove()
        c = PatchCollection(v, True)
        self.highlight_find_collection = self.add_collection(c)
        self.app.figure.canvas.draw_idle()

    def extract_selected(self):
        r0, r1 = self.selected_taxa
        c0, c1 = self.selected_chars
        return self.aln[r0:r1+1,c0:c1+1]

    def zoom_cxy(self, x=0.1, y=0.1, cx=None, cy=None):
        """
        Zoom the x and y axes in by the specified proportion of the
        current view, with a fixed data point (cx, cy)
        """
        transform = self.transData.inverted().transform
        xlim = self.get_xlim(); xmid = sum(xlim)*0.5
        ylim = self.get_ylim(); ymid = sum(ylim)*0.5
        bb = self.get_window_extent()
        bbx = bb.expanded(1.0-x,1.0-y)
        points = transform(bbx.get_points())
        x0, x1 = points[:,0]; y0, y1 = points[:,1]
        deltax = xmid-x0; deltay = ymid-y0
        cx = cx or xmid; cy = cy or ymid
        xoff = (cx-xmid)*x
        self.set_xlim(xmid-deltax+xoff, xmid+deltax+xoff)
        yoff = (cy-ymid)*y
        self.set_ylim(ymid-deltay+yoff, ymid+deltay+yoff)

    def zoom(self, x=0.1, y=0.1, cx=None, cy=None):
        """
        Zoom the x and y axes in by the specified proportion of the
        current view.
        """
        # get the function to convert display coordinates to data
        # coordinates
        transform = self.transData.inverted().transform
        xlim = self.get_xlim()
        ylim = self.get_ylim()
        bb = self.get_window_extent()
        bbx = bb.expanded(1.0-x,1.0-y)
        points = transform(bbx.get_points())
        x0, x1 = points[:,0]; y0, y1 = points[:,1]
        deltax = x0 - xlim[0]; deltay = y0 - ylim[0]
        self.set_xlim(xlim[0]+deltax, xlim[1]-deltax)
        self.set_ylim(ylim[0]+deltay, ylim[1]-deltay)

    def center_y(self, y):
        ymin, ymax = self.get_ylim()
        yoff = (ymax - ymin) * 0.5
        self.set_ylim(y-yoff, y+yoff)

    def center_x(self, x, offset=0.3):
        xmin, xmax = self.get_xlim()
        xspan = xmax - xmin
        xoff = xspan*0.5 + xspan*offset
        self.set_xlim(x-xoff, x+xoff)

    def scroll(self, x, y):
        x0, x1 = self.get_xlim()
        y0, y1 = self.get_ylim()
        xd = (x1-x0)*x
        yd = (y1-y0)*y
        self.set_xlim(x0+xd, x1+xd)
        self.set_ylim(y0+yd, y1+yd)

    def home(self):
        self.set_xlim(0, self.nchar)
        self.set_ylim(self.ntax, 0)
Ejemplo n.º 4
0
class Alignment(Axes):
    """
    matplotlib.axes.Axes subclass for rendering sequence alignments.
    """
    def __init__(self, fig, rect, *args, **kwargs):
        self.aln = kwargs.pop("aln")
        nrows = len(self.aln)
        ncols = self.aln.get_alignment_length()
        self.alnidx = numpy.arange(ncols)
        self.app = kwargs.pop("app", None)
        self.showy = kwargs.pop('showy', True)
        Axes.__init__(self, fig, rect, *args, **kwargs)
        rgb = mpl_colors.colorConverter.to_rgb
        gray = rgb('gray')
        d = defaultdict(lambda: gray)
        d["A"] = rgb("red")
        d["a"] = rgb("red")
        d["C"] = rgb("blue")
        d["c"] = rgb("blue")
        d["G"] = rgb("green")
        d["g"] = rgb("green")
        d["T"] = rgb("yellow")
        d["t"] = rgb("yellow")
        self.cmap = d
        self.selector = RectangleSelector(self,
                                          self.select_rectangle,
                                          useblit=True)

        def f(e):
            if e.button != 1: return True
            else: return RectangleSelector.ignore(self.selector, e)

        self.selector.ignore = f
        self.selected_rectangle = Rectangle([0, 0],
                                            0,
                                            0,
                                            facecolor='white',
                                            edgecolor='cyan',
                                            alpha=0.3)
        self.add_patch(self.selected_rectangle)
        self.highlight_find_collection = None

    def plot_aln(self):
        cmap = self.cmap
        self.ntax = len(self.aln)
        self.nchar = self.aln.get_alignment_length()
        a = numpy.array([[cmap[base] for base in x.seq] for x in self.aln])
        self.array = a
        self.imshow(a, interpolation='nearest', aspect='auto', origin='lower')
        y = [i + 0.5 for i in range(self.ntax)]
        labels = [x.id for x in self.aln]
        ## locator.bin_boundaries(1,ntax)
        ## locator.view_limits(1,ntax)
        if self.showy:
            locator = MaxNLocator(nbins=50, integer=True)
            self.yaxis.set_major_locator(locator)

            def fmt(x, pos=None):
                if x < 0: return ""
                try:
                    return labels[int(round(x))]
                except:
                    pass
                return ""

            self.yaxis.set_major_formatter(FuncFormatter(fmt))
        else:
            self.yaxis.set_major_locator(NullLocator())

        return self

    def select_rectangle(self, e0, e1):
        x0, x1 = list(map(int, sorted((e0.xdata + 0.5, e1.xdata + 0.5))))
        y0, y1 = list(map(int, sorted((e0.ydata + 0.5, e1.ydata + 0.5))))
        self.selected_chars = (x0, x1)
        self.selected_taxa = (y0, y1)
        self.selected_rectangle.set_bounds(x0 - 0.5, y0 - 0.5, x1 - x0 + 1,
                                           y1 - y0 + 1)
        self.app.figure.canvas.draw_idle()

    def highlight_find(self, substr):
        if not substr:
            if self.highlight_find_collection:
                self.highlight_find_collection.remove()
                self.highlight_find_collection = None
            return

        N = len(substr)
        v = []
        for y, x in align.find(self.aln, substr):
            r = Rectangle([x - 0.5, y - 0.5],
                          N,
                          1,
                          facecolor='cyan',
                          edgecolor='cyan',
                          alpha=0.7)
            v.append(r)
        if self.highlight_find_collection:
            self.highlight_find_collection.remove()
        c = PatchCollection(v, True)
        self.highlight_find_collection = self.add_collection(c)
        self.app.figure.canvas.draw_idle()

    def extract_selected(self):
        r0, r1 = self.selected_taxa
        c0, c1 = self.selected_chars
        return self.aln[r0:r1 + 1, c0:c1 + 1]

    def zoom_cxy(self, x=0.1, y=0.1, cx=None, cy=None):
        """
        Zoom the x and y axes in by the specified proportion of the
        current view, with a fixed data point (cx, cy)
        """
        transform = self.transData.inverted().transform
        xlim = self.get_xlim()
        xmid = sum(xlim) * 0.5
        ylim = self.get_ylim()
        ymid = sum(ylim) * 0.5
        bb = self.get_window_extent()
        bbx = bb.expanded(1.0 - x, 1.0 - y)
        points = transform(bbx.get_points())
        x0, x1 = points[:, 0]
        y0, y1 = points[:, 1]
        deltax = xmid - x0
        deltay = ymid - y0
        cx = cx or xmid
        cy = cy or ymid
        xoff = (cx - xmid) * x
        self.set_xlim(xmid - deltax + xoff, xmid + deltax + xoff)
        yoff = (cy - ymid) * y
        self.set_ylim(ymid - deltay + yoff, ymid + deltay + yoff)

    def zoom(self, x=0.1, y=0.1, cx=None, cy=None):
        """
        Zoom the x and y axes in by the specified proportion of the
        current view.
        """
        # get the function to convert display coordinates to data
        # coordinates
        transform = self.transData.inverted().transform
        xlim = self.get_xlim()
        ylim = self.get_ylim()
        bb = self.get_window_extent()
        bbx = bb.expanded(1.0 - x, 1.0 - y)
        points = transform(bbx.get_points())
        x0, x1 = points[:, 0]
        y0, y1 = points[:, 1]
        deltax = x0 - xlim[0]
        deltay = y0 - ylim[0]
        self.set_xlim(xlim[0] + deltax, xlim[1] - deltax)
        self.set_ylim(ylim[0] + deltay, ylim[1] - deltay)

    def center_y(self, y):
        ymin, ymax = self.get_ylim()
        yoff = (ymax - ymin) * 0.5
        self.set_ylim(y - yoff, y + yoff)

    def center_x(self, x, offset=0.3):
        xmin, xmax = self.get_xlim()
        xspan = xmax - xmin
        xoff = xspan * 0.5 + xspan * offset
        self.set_xlim(x - xoff, x + xoff)

    def scroll(self, x, y):
        x0, x1 = self.get_xlim()
        y0, y1 = self.get_ylim()
        xd = (x1 - x0) * x
        yd = (y1 - y0) * y
        self.set_xlim(x0 + xd, x1 + xd)
        self.set_ylim(y0 + yd, y1 + yd)

    def home(self):
        self.set_xlim(0, self.nchar)
        self.set_ylim(self.ntax, 0)
Ejemplo n.º 5
0
class StatsPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize)
        self.ztv_frame = self.GetTopLevelParent()
        self.ztv_frame.primary_image_panel.popup_menu_cursor_modes.append('Stats box')
        self.ztv_frame.primary_image_panel.available_cursor_modes['Stats box'] = {
                'set-to-mode':self.set_cursor_to_stats_box_mode,
                'on_button_press':self.on_button_press,
                'on_motion':self.on_motion,
                'on_button_release':self.on_button_release}
        self.textentry_font = wx.Font(14, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.FONTWEIGHT_LIGHT, False)

        self.stats_info = None
        
        self.last_string_values = {'x0':'', 'xsize':'', 'x1':'', 'y0':'', 'ysize':'', 'y1':''}
        self.stats_rect = Rectangle((0, 0), 10, 10, color='magenta', fill=False, zorder=100)
        # use self.stats_rect as where we store/retrieve the x0,y0,x1,y1
        # x0,y0,x1,y1 should be limited to range of 0 to shape-1
        # but, stats should be calculated over e.g. x0:x1+1  (so that have pixels to do stats on even if x0==x1)
        # and, width/height of stats_rect should always be >= 0
        
        values_sizer = wx.FlexGridSizer( 10, 5, 0, 0 )
        values_sizer.SetFlexibleDirection( wx.BOTH )
        values_sizer.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        self.low_static_text = wx.StaticText( self, wx.ID_ANY, u"Low", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT )
        self.low_static_text.Wrap( -1 )
        values_sizer.Add(self.low_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 0)

        self.low_static_text = wx.StaticText( self, wx.ID_ANY, u"# pix", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.low_static_text.Wrap( -1 )
        values_sizer.Add(self.low_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 0)

        self.high_static_text = wx.StaticText( self, wx.ID_ANY, u"High", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.high_static_text.Wrap( -1 )
        values_sizer.Add(self.high_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL, 0)

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        self.x_static_text = wx.StaticText( self, wx.ID_ANY, u"x", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.x_static_text.Wrap( -1 )
        values_sizer.Add(self.x_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0)

        self.x0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.x0_textctrl.SetFont(self.textentry_font)
        values_sizer.Add(self.x0_textctrl, 0, wx.ALL, 2)
        self.x0_textctrl.Bind(wx.EVT_TEXT, self.x0_textctrl_changed)
        self.x0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x0_textctrl_entered)

        self.xsize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                          wx.TE_PROCESS_ENTER)
        self.xsize_textctrl.SetFont(self.textentry_font)
        values_sizer.Add(self.xsize_textctrl, 0, wx.ALL, 2)
        self.xsize_textctrl.Bind(wx.EVT_TEXT, self.xsize_textctrl_changed)
        self.xsize_textctrl.Bind(wx.EVT_TEXT_ENTER, self.xsize_textctrl_entered)

        self.x1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.x1_textctrl.SetFont(self.textentry_font)
        values_sizer.Add(self.x1_textctrl, 0, wx.ALL, 2)
        self.x1_textctrl.Bind(wx.EVT_TEXT, self.x1_textctrl_changed)
        self.x1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x1_textctrl_entered)

        self.npix_static_text = wx.StaticText( self, wx.ID_ANY, u"# pixels", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.npix_static_text.Wrap( -1 )
        values_sizer.Add(self.npix_static_text, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_BOTTOM, 0)

        self.y_static_text = wx.StaticText( self, wx.ID_ANY, u"y", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.y_static_text.Wrap( -1 )
        values_sizer.Add(self.y_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 0)

        self.y0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.y0_textctrl.SetFont(self.textentry_font)
        values_sizer.Add(self.y0_textctrl, 0, wx.ALL, 2)
        self.y0_textctrl.Bind(wx.EVT_TEXT, self.y0_textctrl_changed)
        self.y0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y0_textctrl_entered)

        self.ysize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                          wx.TE_PROCESS_ENTER)
        self.ysize_textctrl.SetFont(self.textentry_font)
        values_sizer.Add(self.ysize_textctrl, 0, wx.ALL, 2)
        self.ysize_textctrl.Bind(wx.EVT_TEXT, self.ysize_textctrl_changed)
        self.ysize_textctrl.Bind(wx.EVT_TEXT_ENTER, self.ysize_textctrl_entered)

        self.y1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.y1_textctrl.SetFont(self.textentry_font)
        values_sizer.Add(self.y1_textctrl, 0, wx.ALL, 2)
        self.y1_textctrl.Bind(wx.EVT_TEXT, self.y1_textctrl_changed)
        self.y1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y1_textctrl_entered)
        
        self.npix_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                         wx.TE_READONLY)
        self.npix_textctrl.SetFont(self.textentry_font)
        self.npix_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.npix_textctrl, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_LEFT, 0)
  
        values_sizer.AddSpacer((0,15), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        self.median_static_text = wx.StaticText( self, wx.ID_ANY, u"Median", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.median_static_text.Wrap( -1 )
        values_sizer.Add(self.median_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0)
        self.median_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_READONLY)
        self.median_textctrl.SetFont(self.textentry_font)
        self.median_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.median_textctrl, 0, wx.ALL, 2)
        self.robust_static_text = wx.StaticText( self, wx.ID_ANY, u"Robust", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.robust_static_text.Wrap( -1 )
        values_sizer.Add(self.robust_static_text, 0, wx.ALL|wx.ALIGN_BOTTOM|wx.ALIGN_CENTER_HORIZONTAL, 0)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        self.mean_static_text = wx.StaticText( self, wx.ID_ANY, u"Mean", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.mean_static_text.Wrap( -1 )
        values_sizer.Add(self.mean_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0)
        self.mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_READONLY)
        self.mean_textctrl.SetFont(self.textentry_font)
        self.mean_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.mean_textctrl, 0, wx.ALL, 2)
        self.robust_mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_READONLY)
        self.robust_mean_textctrl.SetFont(self.textentry_font)
        self.robust_mean_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.robust_mean_textctrl, 0, wx.ALL, 2)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        self.stdev_static_text = wx.StaticText( self, wx.ID_ANY, u"Stdev", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.stdev_static_text.Wrap( -1 )
        values_sizer.Add(self.stdev_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0)
        self.stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_READONLY)
        self.stdev_textctrl.SetFont(self.textentry_font)
        self.stdev_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.stdev_textctrl, 0, wx.ALL, 2)
        self.robust_stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_READONLY)
        self.robust_stdev_textctrl.SetFont(self.textentry_font)
        self.robust_stdev_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.robust_stdev_textctrl, 0, wx.ALL, 2)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0,15), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        self.min_static_text = wx.StaticText( self, wx.ID_ANY, u"Min", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.min_static_text.Wrap( -1 )
        values_sizer.Add(self.min_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0)
        self.minval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.minval_textctrl.SetFont(self.textentry_font)
        self.minval_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.minval_textctrl, 0, wx.ALL, 2)
        self.minpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.minpos_textctrl.SetFont(self.textentry_font)
        self.minpos_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.minpos_textctrl, 0, wx.ALL, 2)
        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0,0), 0, wx.EXPAND)
        self.max_static_text = wx.StaticText( self, wx.ID_ANY, u"Max", wx.DefaultPosition, wx.DefaultSize, 0 )
        self.max_static_text.Wrap( -1 )
        values_sizer.Add(self.max_static_text, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ALIGN_RIGHT, 0)
        self.maxval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.maxval_textctrl.SetFont(self.textentry_font)
        self.maxval_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.maxval_textctrl, 0, wx.ALL, 2)
        self.maxpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.maxpos_textctrl.SetFont(self.textentry_font)
        self.maxpos_textctrl.SetBackgroundColour(textctrl_output_only_background_color)
        values_sizer.Add(self.maxpos_textctrl, 0, wx.ALL, 2)
             
        self.hideshow_button = wx.Button(self, wx.ID_ANY, u"Show", wx.DefaultPosition, wx.DefaultSize, 0)
        values_sizer.Add(self.hideshow_button, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 2)
        self.hideshow_button.Bind(wx.EVT_BUTTON, self.on_hideshow_button)

        v_sizer1 = wx.BoxSizer(wx.VERTICAL)
        v_sizer1.AddStretchSpacer(1.0)
        v_sizer1.Add(values_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)
        v_sizer1.AddStretchSpacer(1.0)
        self.SetSizer(v_sizer1)
        pub.subscribe(self.queue_update_stats, 'recalc-display-image-called')
        pub.subscribe(self._set_stats_box_parameters, 'set-stats-box-parameters')
        pub.subscribe(self.publish_stats_to_stream, 'get-stats-box-info')

    def publish_stats_to_stream(self, msg=None):
        wx.CallAfter(send_to_stream, sys.stdout, ('stats-box-info', self.stats_info))

    def on_button_press(self, event):
        self.select_panel()
        self.update_stats_box(event.xdata, event.ydata, event.xdata, event.ydata)
        self.redraw_overplot_on_image()
        self.cursor_stats_box_x0, self.cursor_stats_box_y0 = event.xdata, event.ydata

    def on_motion(self, event):
        if event.button is not None:
            self.update_stats_box(self.cursor_stats_box_x0, self.cursor_stats_box_y0, event.xdata, event.ydata)
            self.redraw_overplot_on_image()
            self.update_stats()

    def on_button_release(self, event):
        self.redraw_overplot_on_image()
        self.update_stats()

    def set_cursor_to_stats_box_mode(self, event):
        self.ztv_frame.primary_image_panel.cursor_mode = 'Stats box'
        self.ztv_frame.stats_panel.select_panel()
        self.ztv_frame.stats_panel.highlight_panel()

    def queue_update_stats(self, msg=None):  
        """
        wrapper to call update_stats from CallAfter in order to make GUI as responsive as possible.
        """
        wx.CallAfter(self.update_stats, msg=None)

    def _set_stats_box_parameters(self, msg):
        """
        wrapper to update_stats_box to receive messages & translate them correctly
        """
        x0,x1,y0,y1 = [None]*4
        if msg['xrange'] is not None:
            x0,x1 = msg['xrange']
        if msg['yrange'] is not None:
            y0,y1 = msg['yrange']
        if msg['xrange'] is not None or msg['yrange'] is not None:
            self.update_stats_box(x0, y0, x1, y1)
        if msg['show_overplot'] is not None:
            if msg['show_overplot']:
                self.redraw_overplot_on_image()
            else:
                self.remove_overplot_on_image()
        send_to_stream(sys.stdout, ('set-stats-box-parameters-done', True))

    def update_stats_box(self, x0=None, y0=None, x1=None, y1=None):
        if x0 is None:
            x0 = self.stats_rect.get_x()
        if y0 is None:
            y0 = self.stats_rect.get_y()
        if x1 is None:
            x1 = self.stats_rect.get_x() + self.stats_rect.get_width()
        if y1 is None:
            y1 = self.stats_rect.get_y() + self.stats_rect.get_height()
        if x0 > x1:
            x0, x1 = x1, x0
        if y0 > y1:
            y0, y1 = y1, y0
        x0 = min(max(0, x0), self.ztv_frame.display_image.shape[1] - 1)
        y0 = min(max(0, y0), self.ztv_frame.display_image.shape[0] - 1)
        x1 = min(max(0, x1), self.ztv_frame.display_image.shape[1] - 1)
        y1 = min(max(0, y1), self.ztv_frame.display_image.shape[0] - 1)
        self.stats_rect.set_bounds(x0, y0, x1 - x0, y1 - y0)
        if self.hideshow_button.GetLabel() == 'Hide':  
            self.ztv_frame.primary_image_panel.figure.canvas.draw()
        self.update_stats()

    def remove_overplot_on_image(self):
        self.ztv_frame.primary_image_panel.remove_patch('stats_panel:stats_rect')
        self.hideshow_button.SetLabel(u"Show")

    def redraw_overplot_on_image(self):
        self.ztv_frame.primary_image_panel.add_patch('stats_panel:stats_rect', self.stats_rect)
        self.hideshow_button.SetLabel(u"Hide")        

    def on_hideshow_button(self, evt):
        if self.hideshow_button.GetLabel() == 'Hide':
            self.remove_overplot_on_image()
        else:
            self.redraw_overplot_on_image()

    def get_x0y0x1y1_from_stats_rect(self):
        x0 = self.stats_rect.get_x()
        y0 = self.stats_rect.get_y()
        x1 = x0 + self.stats_rect.get_width()
        y1 = y0 + self.stats_rect.get_height()
        return x0,y0,x1,y1
        
    def update_stats(self, msg=None):
        x0,y0,x1,y1 = self.get_x0y0x1y1_from_stats_rect()
        x0, y0 = int(np.round(x0)), int(np.round(y0))
        x1, y1 = int(np.round(x1)), int(np.round(y1))
        self.last_string_values['x0'] = str(int(x0))
        self.x0_textctrl.SetValue(self.last_string_values['x0'])
        self.last_string_values['y0'] = str(int(y0))
        self.y0_textctrl.SetValue(self.last_string_values['y0'])

        x_npix = int(x1 - x0 + 1)
        self.last_string_values['xsize'] = str(x_npix)
        self.xsize_textctrl.SetValue(self.last_string_values['xsize'])
        y_npix = int(y1 - y0 + 1)
        self.last_string_values['ysize'] = str(y_npix)
        self.ysize_textctrl.SetValue(self.last_string_values['ysize'])

        self.last_string_values['x1'] = str(int(x1))
        self.x1_textctrl.SetValue(self.last_string_values['x1'])
        self.last_string_values['y1'] = str(int(y1))
        self.y1_textctrl.SetValue(self.last_string_values['y1'])
    
        self.npix_textctrl.SetValue(str(x_npix * y_npix))

        stats_data = self.ztv_frame.display_image[y0:y1+1, x0:x1+1]
        finite_mask = np.isfinite(stats_data)
        if finite_mask.max() is np.True_:
            stats_data_mean = stats_data[finite_mask].mean()
            stats_data_median = np.median(stats_data[finite_mask])
            stats_data_std = stats_data[finite_mask].std()
            robust_mean, robust_median, robust_std = sigma_clipped_stats(stats_data[finite_mask])
        else:
            stats_data_mean = np.nan
            stats_data_median = np.nan
            stats_data_std = np.inf
            robust_mean, robust_median, robust_std = np.nan, np.nan, np.inf
        self.stats_info = {'xrange':[x0,x1], 'yrange':[y0,y1],
                           'mean':stats_data_mean, 'median':stats_data_median, 'std':stats_data_std, 
                           'min':stats_data.min(), 'max':stats_data.max()} # want min/max to reflect any Inf/NaN
        self.mean_textctrl.SetValue("{:0.4g}".format(self.stats_info['mean']))
        self.median_textctrl.SetValue("{:0.4g}".format(self.stats_info['median']))
        self.stdev_textctrl.SetValue("{:0.4g}".format(self.stats_info['std']))
        self.stats_info['robust-mean'] = robust_mean
        self.stats_info['robust-median'] = robust_median
        self.stats_info['robust-std'] = robust_std
        self.robust_mean_textctrl.SetValue("{:0.4g}".format(robust_mean)) 
        self.robust_stdev_textctrl.SetValue("{:0.4g}".format(robust_std))
        self.minval_textctrl.SetValue("{:0.4g}".format(self.stats_info['min']))
        self.maxval_textctrl.SetValue("{:0.4g}".format(self.stats_info['max']))
        wmin = np.where(stats_data == stats_data.min())
        wmin = [(wmin[1][i] + x0,wmin[0][i] + y0) for i in np.arange(wmin[0].size)]
        if len(wmin) == 1:
            wmin = wmin[0]
        self.minpos_textctrl.SetValue("{}".format(wmin))
        self.stats_info['wmin'] = wmin
        wmax = np.where(stats_data == stats_data.max())
        wmax = [(wmax[1][i] + x0,wmax[0][i] + y0) for i in np.arange(wmax[0].size)]
        if len(wmax) == 1:
            wmax = wmax[0]
        self.maxpos_textctrl.SetValue("{}".format(wmax))
        self.stats_info['wmax'] = wmax
        set_textctrl_background_color(self.x0_textctrl, 'ok')
        set_textctrl_background_color(self.x1_textctrl, 'ok')
        set_textctrl_background_color(self.xsize_textctrl, 'ok')
        set_textctrl_background_color(self.y0_textctrl, 'ok')
        set_textctrl_background_color(self.y1_textctrl, 'ok')
        set_textctrl_background_color(self.ysize_textctrl, 'ok')
        
    def x0_textctrl_changed(self, evt):
        validate_textctrl_str(self.x0_textctrl, int, self.last_string_values['x0'])

    def x0_textctrl_entered(self, evt):
        if validate_textctrl_str(self.x0_textctrl, int, self.last_string_values['x0']):
            self.last_string_values['x0'] = self.x0_textctrl.GetValue()
            self.update_stats_box(int(self.last_string_values['x0']), None, None, None)
            self.x0_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()
            
    def xsize_textctrl_changed(self, evt):
        validate_textctrl_str(self.xsize_textctrl, int, self.last_string_values['xsize'])

    def xsize_textctrl_entered(self, evt):
        if validate_textctrl_str(self.xsize_textctrl, int, self.last_string_values['xsize']):
            self.last_string_values['xsize'] = self.xsize_textctrl.GetValue()
            xsize = int(self.last_string_values['xsize'])
            sys.stderr.write("\n\nxsize = {}\n\n".format(xsize))
            x0,y0,x1,y1 = self.get_x0y0x1y1_from_stats_rect()
            xc = (x0 + x1) / 2.
            x0 = max(0, int(xc - xsize / 2.))
            x1 = x0 + xsize - 1
            x1 = min(x1, self.ztv_frame.display_image.shape[1] - 1)
            x0 = x1 - xsize + 1
            x0 = max(0, int(xc - xsize / 2.))
            self.update_stats_box(x0, y0, x1, y1)
            self.xsize_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def x1_textctrl_changed(self, evt):
        validate_textctrl_str(self.x1_textctrl, int, self.last_string_values['x1'])

    def x1_textctrl_entered(self, evt):
        if validate_textctrl_str(self.x1_textctrl, int, self.last_string_values['x1']):
            self.last_string_values['x1'] = self.x1_textctrl.GetValue()
            self.update_stats_box(None, None, int(self.last_string_values['x1']), None)
            self.x1_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def y0_textctrl_changed(self, evt):
        validate_textctrl_str(self.y0_textctrl, int, self.last_string_values['y0'])

    def y0_textctrl_entered(self, evt):
        if validate_textctrl_str(self.y0_textctrl, int, self.last_string_values['y0']):
            self.last_string_values['y0'] = self.y0_textctrl.GetValue()
            self.update_stats_box(None, int(self.last_string_values['y0']), None, None)
            self.y0_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def ysize_textctrl_changed(self, evt):
        validate_textctrl_str(self.ysize_textctrl, int, self.last_string_values['ysize'])

    def ysize_textctrl_entered(self, evt):
        if validate_textctrl_str(self.ysize_textctrl, int, self.last_string_values['ysize']):
            self.last_string_values['ysize'] = self.ysize_textctrl.GetValue()
            ysize = int(self.last_string_values['ysize'])
            x0,y0,x1,y1 = self.get_x0y0x1y1_from_stats_rect()
            yc = (y0 + y1) / 2.
            y0 = max(0, int(yc - ysize / 2.))
            y1 = y0 + ysize - 1
            y1 = min(y1, self.ztv_frame.display_image.shape[0] - 1)
            y0 = y1 - ysize + 1
            y0 = max(0, int(yc - ysize / 2.))
            self.update_stats_box(x0, y0, x1, y1)
            self.ysize_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def y1_textctrl_changed(self, evt):
        validate_textctrl_str(self.y1_textctrl, int, self.last_string_values['y1'])

    def y1_textctrl_entered(self, evt):
        if validate_textctrl_str(self.y1_textctrl, int, self.last_string_values['y1']):
            self.last_string_values['y1'] = self.y1_textctrl.GetValue()
            self.update_stats_box(None, None, None, int(self.last_string_values['y1']))
            self.y1_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()
Ejemplo n.º 6
0
class StatsPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, wx.ID_ANY, wx.DefaultPosition,
                          wx.DefaultSize)
        self.ztv_frame = self.GetTopLevelParent()
        self.ztv_frame.primary_image_panel.popup_menu_cursor_modes.append(
            'Stats box')
        self.ztv_frame.primary_image_panel.available_cursor_modes[
            'Stats box'] = {
                'set-to-mode': self.set_cursor_to_stats_box_mode,
                'on_button_press': self.on_button_press,
                'on_motion': self.on_motion,
                'on_button_release': self.on_button_release
            }

        self.stats_info = None

        self.last_string_values = {
            'x0': '',
            'xsize': '',
            'x1': '',
            'y0': '',
            'ysize': '',
            'y1': ''
        }
        self.stats_rect = Rectangle((0, 0),
                                    10,
                                    10,
                                    color='magenta',
                                    fill=False,
                                    zorder=100)
        # use self.stats_rect as where we store/retrieve the x0,y0,x1,y1
        # x0,y0,x1,y1 should be limited to range of 0 to shape-1
        # but, stats should be calculated over e.g. x0:x1+1  (so that have pixels to do stats on even if x0==x1)
        # and, width/height of stats_rect should always be >= 0

        textentry_font = wx.Font(14, wx.FONTFAMILY_MODERN, wx.NORMAL,
                                 wx.FONTWEIGHT_LIGHT, False)

        values_sizer = wx.FlexGridSizer(10, 5, 0, 0)
        values_sizer.SetFlexibleDirection(wx.BOTH)
        values_sizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        self.low_static_text = wx.StaticText(self, wx.ID_ANY, u"Low",
                                             wx.DefaultPosition,
                                             wx.DefaultSize, wx.ALIGN_RIGHT)
        self.low_static_text.Wrap(-1)
        values_sizer.Add(self.low_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 0)

        self.low_static_text = wx.StaticText(self, wx.ID_ANY, u"# pix",
                                             wx.DefaultPosition,
                                             wx.DefaultSize, 0)
        self.low_static_text.Wrap(-1)
        values_sizer.Add(self.low_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 0)

        self.high_static_text = wx.StaticText(self, wx.ID_ANY, u"High",
                                              wx.DefaultPosition,
                                              wx.DefaultSize, 0)
        self.high_static_text.Wrap(-1)
        values_sizer.Add(self.high_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 0)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        self.x_static_text = wx.StaticText(self, wx.ID_ANY, u"x",
                                           wx.DefaultPosition, wx.DefaultSize,
                                           0)
        self.x_static_text.Wrap(-1)
        values_sizer.Add(self.x_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL, 0)

        self.x0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                       wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.x0_textctrl.SetFont(textentry_font)
        values_sizer.Add(self.x0_textctrl, 0, wx.ALL, 2)
        self.x0_textctrl.Bind(wx.EVT_TEXT, self.x0_textctrl_changed)
        self.x0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x0_textctrl_entered)

        self.xsize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                          wx.DefaultPosition, wx.DefaultSize,
                                          wx.TE_PROCESS_ENTER)
        self.xsize_textctrl.SetFont(textentry_font)
        values_sizer.Add(self.xsize_textctrl, 0, wx.ALL, 2)
        self.xsize_textctrl.Bind(wx.EVT_TEXT, self.xsize_textctrl_changed)
        self.xsize_textctrl.Bind(wx.EVT_TEXT_ENTER,
                                 self.xsize_textctrl_entered)

        self.x1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                       wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.x1_textctrl.SetFont(textentry_font)
        values_sizer.Add(self.x1_textctrl, 0, wx.ALL, 2)
        self.x1_textctrl.Bind(wx.EVT_TEXT, self.x1_textctrl_changed)
        self.x1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.x1_textctrl_entered)

        self.npix_static_text = wx.StaticText(self, wx.ID_ANY, u"# pixels",
                                              wx.DefaultPosition,
                                              wx.DefaultSize, 0)
        self.npix_static_text.Wrap(-1)
        values_sizer.Add(self.npix_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_BOTTOM,
                         0)

        self.y_static_text = wx.StaticText(self, wx.ID_ANY, u"y",
                                           wx.DefaultPosition, wx.DefaultSize,
                                           0)
        self.y_static_text.Wrap(-1)
        values_sizer.Add(self.y_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL, 0)

        self.y0_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                       wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.y0_textctrl.SetFont(textentry_font)
        values_sizer.Add(self.y0_textctrl, 0, wx.ALL, 2)
        self.y0_textctrl.Bind(wx.EVT_TEXT, self.y0_textctrl_changed)
        self.y0_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y0_textctrl_entered)

        self.ysize_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                          wx.DefaultPosition, wx.DefaultSize,
                                          wx.TE_PROCESS_ENTER)
        self.ysize_textctrl.SetFont(textentry_font)
        values_sizer.Add(self.ysize_textctrl, 0, wx.ALL, 2)
        self.ysize_textctrl.Bind(wx.EVT_TEXT, self.ysize_textctrl_changed)
        self.ysize_textctrl.Bind(wx.EVT_TEXT_ENTER,
                                 self.ysize_textctrl_entered)

        self.y1_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                       wx.DefaultPosition, wx.DefaultSize,
                                       wx.TE_PROCESS_ENTER)
        self.y1_textctrl.SetFont(textentry_font)
        values_sizer.Add(self.y1_textctrl, 0, wx.ALL, 2)
        self.y1_textctrl.Bind(wx.EVT_TEXT, self.y1_textctrl_changed)
        self.y1_textctrl.Bind(wx.EVT_TEXT_ENTER, self.y1_textctrl_entered)

        self.npix_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                         wx.DefaultPosition, wx.DefaultSize,
                                         wx.TE_READONLY)
        self.npix_textctrl.SetFont(textentry_font)
        self.npix_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.npix_textctrl, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_LEFT, 0)

        values_sizer.AddSpacer((0, 15), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        self.median_static_text = wx.StaticText(self, wx.ID_ANY, u"Median",
                                                wx.DefaultPosition,
                                                wx.DefaultSize, 0)
        self.median_static_text.Wrap(-1)
        values_sizer.Add(self.median_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)
        self.median_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                           wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.median_textctrl.SetFont(textentry_font)
        self.median_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.median_textctrl, 0, wx.ALL, 2)
        self.robust_static_text = wx.StaticText(self, wx.ID_ANY, u"Robust",
                                                wx.DefaultPosition,
                                                wx.DefaultSize, 0)
        self.robust_static_text.Wrap(-1)
        values_sizer.Add(self.robust_static_text, 0,
                         wx.ALL | wx.ALIGN_BOTTOM | wx.ALIGN_CENTER_HORIZONTAL,
                         0)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        self.mean_static_text = wx.StaticText(self, wx.ID_ANY, u"Mean",
                                              wx.DefaultPosition,
                                              wx.DefaultSize, 0)
        self.mean_static_text.Wrap(-1)
        values_sizer.Add(self.mean_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)
        self.mean_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                         wx.DefaultPosition, wx.DefaultSize,
                                         wx.TE_READONLY)
        self.mean_textctrl.SetFont(textentry_font)
        self.mean_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.mean_textctrl, 0, wx.ALL, 2)
        self.robust_mean_textctrl = wx.TextCtrl(self, wx.ID_ANY,
                                                wx.EmptyString,
                                                wx.DefaultPosition,
                                                wx.DefaultSize, wx.TE_READONLY)
        self.robust_mean_textctrl.SetFont(textentry_font)
        self.robust_mean_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.robust_mean_textctrl, 0, wx.ALL, 2)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        self.stdev_static_text = wx.StaticText(self, wx.ID_ANY, u"Stdev",
                                               wx.DefaultPosition,
                                               wx.DefaultSize, 0)
        self.stdev_static_text.Wrap(-1)
        values_sizer.Add(self.stdev_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)
        self.stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                          wx.DefaultPosition, wx.DefaultSize,
                                          wx.TE_READONLY)
        self.stdev_textctrl.SetFont(textentry_font)
        self.stdev_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.stdev_textctrl, 0, wx.ALL, 2)
        self.robust_stdev_textctrl = wx.TextCtrl(self, wx.ID_ANY,
                                                 wx.EmptyString,
                                                 wx.DefaultPosition,
                                                 wx.DefaultSize,
                                                 wx.TE_READONLY)
        self.robust_stdev_textctrl.SetFont(textentry_font)
        self.robust_stdev_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.robust_stdev_textctrl, 0, wx.ALL, 2)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0, 15), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        self.min_static_text = wx.StaticText(self, wx.ID_ANY, u"Min",
                                             wx.DefaultPosition,
                                             wx.DefaultSize, 0)
        self.min_static_text.Wrap(-1)
        values_sizer.Add(self.min_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)
        self.minval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                           wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.minval_textctrl.SetFont(textentry_font)
        self.minval_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.minval_textctrl, 0, wx.ALL, 2)
        self.minpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                           wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.minpos_textctrl.SetFont(textentry_font)
        self.minpos_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.minpos_textctrl, 0, wx.ALL, 2)
        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)

        values_sizer.AddSpacer((0, 0), 0, wx.EXPAND)
        self.max_static_text = wx.StaticText(self, wx.ID_ANY, u"Max",
                                             wx.DefaultPosition,
                                             wx.DefaultSize, 0)
        self.max_static_text.Wrap(-1)
        values_sizer.Add(self.max_static_text, 0,
                         wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)
        self.maxval_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                           wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.maxval_textctrl.SetFont(textentry_font)
        self.maxval_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.maxval_textctrl, 0, wx.ALL, 2)
        self.maxpos_textctrl = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString,
                                           wx.DefaultPosition, wx.DefaultSize,
                                           wx.TE_READONLY)
        self.maxpos_textctrl.SetFont(textentry_font)
        self.maxpos_textctrl.SetBackgroundColour(
            textctrl_output_only_background_color)
        values_sizer.Add(self.maxpos_textctrl, 0, wx.ALL, 2)

        self.hideshow_button = wx.Button(self, wx.ID_ANY, u"Show",
                                         wx.DefaultPosition, wx.DefaultSize, 0)
        values_sizer.Add(
            self.hideshow_button, 0,
            wx.ALL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL, 2)
        self.hideshow_button.Bind(wx.EVT_BUTTON, self.on_hideshow_button)

        v_sizer1 = wx.BoxSizer(wx.VERTICAL)
        v_sizer1.AddStretchSpacer(1.0)
        v_sizer1.Add(values_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL)
        v_sizer1.AddStretchSpacer(1.0)
        self.SetSizer(v_sizer1)
        pub.subscribe(self.queue_update_stats, 'recalc-display-image-called')
        pub.subscribe(self._set_stats_box_parameters,
                      'set-stats-box-parameters')
        pub.subscribe(self.publish_stats_to_stream, 'get-stats-box-info')

    def publish_stats_to_stream(self, msg=None):
        wx.CallAfter(send_to_stream, sys.stdout,
                     ('stats-box-info', self.stats_info))

    def on_button_press(self, event):
        self.select_panel()
        self.stats_start_timestamp = event.guiEvent.GetTimestamp()  # millisec
        self.update_stats_box(event.xdata, event.ydata, event.xdata,
                              event.ydata)
        self.redraw_overplot_on_image()
        self.cursor_stats_box_x0, self.cursor_stats_box_y0 = event.xdata, event.ydata

    def on_motion(self, event):
        self.update_stats_box(self.cursor_stats_box_x0,
                              self.cursor_stats_box_y0, event.xdata,
                              event.ydata)
        self.redraw_overplot_on_image()
        self.update_stats()

    def on_button_release(self, event):
        self.redraw_overplot_on_image()
        self.update_stats()

    def set_cursor_to_stats_box_mode(self, event):
        self.ztv_frame.primary_image_panel.cursor_mode = 'Stats box'
        self.ztv_frame.stats_panel.select_panel()
        self.ztv_frame.stats_panel.highlight_panel()

    def queue_update_stats(self, msg=None):
        """
        wrapper to call update_stats from CallAfter in order to make GUI as responsive as possible.
        """
        wx.CallAfter(self.update_stats, msg=None)

    def _set_stats_box_parameters(self, msg):
        """
        wrapper to update_stats_box to receive messages & translate them correctly
        """
        x0, x1, y0, y1 = [None] * 4
        if msg['xrange'] is not None:
            x0, x1 = msg['xrange']
        if msg['yrange'] is not None:
            y0, y1 = msg['yrange']
        if msg['xrange'] is not None or msg['yrange'] is not None:
            self.update_stats_box(x0, y0, x1, y1)
        if msg['show_overplot'] is not None:
            if msg['show_overplot']:
                self.redraw_overplot_on_image()
            else:
                self.remove_overplot_on_image()
        send_to_stream(sys.stdout, ('set-stats-box-parameters-done', True))

    def update_stats_box(self, x0=None, y0=None, x1=None, y1=None):
        if x0 is None:
            x0 = self.stats_rect.get_x()
        if y0 is None:
            y0 = self.stats_rect.get_y()
        if x1 is None:
            x1 = self.stats_rect.get_x() + self.stats_rect.get_width()
        if y1 is None:
            y1 = self.stats_rect.get_y() + self.stats_rect.get_height()
        if x0 > x1:
            x0, x1 = x1, x0
        if y0 > y1:
            y0, y1 = y1, y0
        x0 = min(max(0, x0), self.ztv_frame.display_image.shape[1] - 1)
        y0 = min(max(0, y0), self.ztv_frame.display_image.shape[0] - 1)
        x1 = min(max(0, x1), self.ztv_frame.display_image.shape[1] - 1)
        y1 = min(max(0, y1), self.ztv_frame.display_image.shape[0] - 1)
        self.stats_rect.set_bounds(x0, y0, x1 - x0, y1 - y0)
        self.ztv_frame.primary_image_panel.figure.canvas.draw()
        self.update_stats()

    def remove_overplot_on_image(self):
        if self.stats_rect in self.ztv_frame.primary_image_panel.axes.patches:
            self.ztv_frame.primary_image_panel.axes.patches.remove(
                self.stats_rect)
        self.ztv_frame.primary_image_panel.figure.canvas.draw()
        self.hideshow_button.SetLabel(u"Show")

    def redraw_overplot_on_image(self):
        if self.stats_rect not in self.ztv_frame.primary_image_panel.axes.patches:
            self.ztv_frame.primary_image_panel.axes.add_patch(self.stats_rect)
        self.ztv_frame.primary_image_panel.figure.canvas.draw()
        self.hideshow_button.SetLabel(u"Hide")

    def on_hideshow_button(self, evt):
        if self.hideshow_button.GetLabel() == 'Hide':
            self.remove_overplot_on_image()
        else:
            self.redraw_overplot_on_image()

    def get_x0y0x1y1_from_stats_rect(self):
        x0 = self.stats_rect.get_x()
        y0 = self.stats_rect.get_y()
        x1 = x0 + self.stats_rect.get_width()
        y1 = y0 + self.stats_rect.get_height()
        return x0, y0, x1, y1

    def update_stats(self, msg=None):
        x0, y0, x1, y1 = self.get_x0y0x1y1_from_stats_rect()
        x0, y0 = int(np.round(x0)), int(np.round(y0))
        x1, y1 = int(np.round(x1)), int(np.round(y1))
        self.last_string_values['x0'] = str(int(x0))
        self.x0_textctrl.SetValue(self.last_string_values['x0'])
        self.last_string_values['y0'] = str(int(y0))
        self.y0_textctrl.SetValue(self.last_string_values['y0'])

        x_npix = int(x1 - x0 + 1)
        self.last_string_values['xsize'] = str(x_npix)
        self.xsize_textctrl.SetValue(self.last_string_values['xsize'])
        y_npix = int(y1 - y0 + 1)
        self.last_string_values['ysize'] = str(y_npix)
        self.ysize_textctrl.SetValue(self.last_string_values['ysize'])

        self.last_string_values['x1'] = str(int(x1))
        self.x1_textctrl.SetValue(self.last_string_values['x1'])
        self.last_string_values['y1'] = str(int(y1))
        self.y1_textctrl.SetValue(self.last_string_values['y1'])

        self.npix_textctrl.SetValue(str(x_npix * y_npix))

        stats_data = self.ztv_frame.display_image[y0:y1 + 1, x0:x1 + 1]
        finite_mask = np.isfinite(stats_data)
        if finite_mask.max() is np.True_:
            stats_data_mean = stats_data[finite_mask].mean()
            stats_data_median = np.median(stats_data[finite_mask])
            stats_data_std = stats_data[finite_mask].std()
            robust_mean, robust_median, robust_std = sigma_clipped_stats(
                stats_data[finite_mask])
        else:
            stats_data_mean = np.nan
            stats_data_median = np.nan
            stats_data_std = np.inf
            robust_mean, robust_median, robust_std = np.nan, np.nan, np.inf
        self.stats_info = {
            'xrange': [x0, x1],
            'yrange': [y0, y1],
            'mean': stats_data_mean,
            'median': stats_data_median,
            'std': stats_data_std,
            'min': stats_data.min(),
            'max': stats_data.max()
        }  # want min/max to reflect any Inf/NaN
        self.mean_textctrl.SetValue("{:0.4g}".format(self.stats_info['mean']))
        self.median_textctrl.SetValue("{:0.4g}".format(
            self.stats_info['median']))
        self.stdev_textctrl.SetValue("{:0.4g}".format(self.stats_info['std']))
        self.stats_info['robust-mean'] = robust_mean
        self.stats_info['robust-median'] = robust_median
        self.stats_info['robust-std'] = robust_std
        self.robust_mean_textctrl.SetValue("{:0.4g}".format(robust_mean))
        self.robust_stdev_textctrl.SetValue("{:0.4g}".format(robust_std))
        self.minval_textctrl.SetValue("{:0.4g}".format(self.stats_info['min']))
        self.maxval_textctrl.SetValue("{:0.4g}".format(self.stats_info['max']))
        wmin = np.where(stats_data == stats_data.min())
        wmin = [(wmin[1][i] + x0, wmin[0][i] + y0)
                for i in np.arange(wmin[0].size)]
        if len(wmin) == 1:
            wmin = wmin[0]
        self.minpos_textctrl.SetValue("{}".format(wmin))
        self.stats_info['wmin'] = wmin
        wmax = np.where(stats_data == stats_data.max())
        wmax = [(wmax[1][i] + x0, wmax[0][i] + y0)
                for i in np.arange(wmax[0].size)]
        if len(wmax) == 1:
            wmax = wmax[0]
        self.maxpos_textctrl.SetValue("{}".format(wmax))
        self.stats_info['wmax'] = wmax
        set_textctrl_background_color(self.x0_textctrl, 'ok')
        set_textctrl_background_color(self.x1_textctrl, 'ok')
        set_textctrl_background_color(self.xsize_textctrl, 'ok')
        set_textctrl_background_color(self.y0_textctrl, 'ok')
        set_textctrl_background_color(self.y1_textctrl, 'ok')
        set_textctrl_background_color(self.ysize_textctrl, 'ok')

    def x0_textctrl_changed(self, evt):
        validate_textctrl_str(self.x0_textctrl, int,
                              self.last_string_values['x0'])

    def x0_textctrl_entered(self, evt):
        if validate_textctrl_str(self.x0_textctrl, int,
                                 self.last_string_values['x0']):
            self.last_string_values['x0'] = self.x0_textctrl.GetValue()
            self.update_stats_box(int(self.last_string_values['x0']), None,
                                  None, None)
            self.x0_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def xsize_textctrl_changed(self, evt):
        validate_textctrl_str(self.xsize_textctrl, int,
                              self.last_string_values['xsize'])

    def xsize_textctrl_entered(self, evt):
        if validate_textctrl_str(self.xsize_textctrl, int,
                                 self.last_string_values['xsize']):
            self.last_string_values['xsize'] = self.xsize_textctrl.GetValue()
            xsize = int(self.last_string_values['xsize'])
            sys.stderr.write("\n\nxsize = {}\n\n".format(xsize))
            x0, y0, x1, y1 = self.get_x0y0x1y1_from_stats_rect()
            xc = (x0 + x1) / 2.
            x0 = max(0, int(xc - xsize / 2.))
            x1 = x0 + xsize - 1
            x1 = min(x1, self.ztv_frame.display_image.shape[1] - 1)
            x0 = x1 - xsize + 1
            x0 = max(0, int(xc - xsize / 2.))
            self.update_stats_box(x0, y0, x1, y1)
            self.xsize_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def x1_textctrl_changed(self, evt):
        validate_textctrl_str(self.x1_textctrl, int,
                              self.last_string_values['x1'])

    def x1_textctrl_entered(self, evt):
        if validate_textctrl_str(self.x1_textctrl, int,
                                 self.last_string_values['x1']):
            self.last_string_values['x1'] = self.x1_textctrl.GetValue()
            self.update_stats_box(None, None,
                                  int(self.last_string_values['x1']), None)
            self.x1_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def y0_textctrl_changed(self, evt):
        validate_textctrl_str(self.y0_textctrl, int,
                              self.last_string_values['y0'])

    def y0_textctrl_entered(self, evt):
        if validate_textctrl_str(self.y0_textctrl, int,
                                 self.last_string_values['y0']):
            self.last_string_values['y0'] = self.y0_textctrl.GetValue()
            self.update_stats_box(None, int(self.last_string_values['y0']),
                                  None, None)
            self.y0_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def ysize_textctrl_changed(self, evt):
        validate_textctrl_str(self.ysize_textctrl, int,
                              self.last_string_values['ysize'])

    def ysize_textctrl_entered(self, evt):
        if validate_textctrl_str(self.ysize_textctrl, int,
                                 self.last_string_values['ysize']):
            self.last_string_values['ysize'] = self.ysize_textctrl.GetValue()
            ysize = int(self.last_string_values['ysize'])
            x0, y0, x1, y1 = self.get_x0y0x1y1_from_stats_rect()
            yc = (y0 + y1) / 2.
            y0 = max(0, int(yc - ysize / 2.))
            y1 = y0 + ysize - 1
            y1 = min(y1, self.ztv_frame.display_image.shape[0] - 1)
            y0 = y1 - ysize + 1
            y0 = max(0, int(yc - ysize / 2.))
            self.update_stats_box(x0, y0, x1, y1)
            self.ysize_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()

    def y1_textctrl_changed(self, evt):
        validate_textctrl_str(self.y1_textctrl, int,
                              self.last_string_values['y1'])

    def y1_textctrl_entered(self, evt):
        if validate_textctrl_str(self.y1_textctrl, int,
                                 self.last_string_values['y1']):
            self.last_string_values['y1'] = self.y1_textctrl.GetValue()
            self.update_stats_box(None, None, None,
                                  int(self.last_string_values['y1']))
            self.y1_textctrl.SetSelection(-1, -1)
            self.redraw_overplot_on_image()
Ejemplo n.º 7
0
class SimGui(wx.Frame):
    StateNone, StateSetStartPos, StateSetGoalPos = range(3)
    SimStateStopped, SimStateRunning, SimStatePaused = range(3)

    def __init__(self, size=(1800, 1200)):
        super(SimGui, self).__init__(None, size=size)
        self.plotter = Plot(self)

        self.map = None
        self.map_visualizer = None

        self.Bind(wx.EVT_CLOSE, self.on_close)

        self.control_panel = Controls(self)
        self.status_window = StatusWindow(self, textwidth=256)

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        self.record_slider = wx.Slider(self,
                                       value=0,
                                       minValue=0,
                                       maxValue=0,
                                       style=wx.SL_HORIZONTAL | wx.SL_LABELS)
        self.record_slider.Bind(wx.EVT_SLIDER, self.on_record_slider_scroll)
        self.record_slider.Disable()

        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(self.plotter, 1, wx.EXPAND)
        vbox.Add(self.record_slider, 0, wx.ALL | wx.EXPAND, border=5)
        self.left_vbox = vbox

        hbox.Add(self.status_window, 0, wx.EXPAND)
        hbox.Add(vbox, 1, wx.EXPAND)
        hbox.Add(self.control_panel)

        self.SetSizer(hbox)

        self.control_panel.start_sim_button.Bind(wx.EVT_BUTTON,
                                                 self.cmd_simulate)
        self.control_panel.stop_sim_button.Bind(wx.EVT_BUTTON,
                                                self.cmd_stop_simulation)
        self.control_panel.stop_sim_button.Disable()

        self.control_panel.set_start_pos_button.Bind(
            wx.EVT_BUTTON, lambda e: self.set_state(SimGui.StateSetStartPos))
        self.control_panel.set_goal_pos_button.Bind(
            wx.EVT_BUTTON, lambda e: self.set_state(SimGui.StateSetGoalPos))

        self.control_panel.heading_text.SetValue('0.0')
        self.control_panel.max_steps_text.SetValue('2000')
        self.control_panel.step_size_text.SetValue('0.1')

        self.control_panel.show_waypoints.SetValue(True)
        self.control_panel.show_waypoints.Bind(
            wx.EVT_CHECKBOX, lambda e: self.cmd_show_waypoints())

        self.plotter.canvas.mpl_connect('button_press_event',
                                        lambda ev: self.on_click_canvas(ev))

        self.state = None

        # Simulation related
        self.sim_timer = wx.Timer(self)
        self.Bind(wx.EVT_TIMER, self.on_timer, self.sim_timer)

        self.sim_steps = 0
        self.sim_state = SimGui.SimStateStopped
        self.sim_max_steps = 0
        self.last_start_pos = None
        self.last_goal_pos = None
        self.waypoints = []
        self.last_waypoints = None
        self.destination = None
        self.reset_simulation()
        self.sim_thread = None
        self.agents = {}

        # graph elements
        self.cm = matplotlib.cm.get_cmap('Dark2')
        self.h_start_point = None
        self.h_goal_point = None
        self.h_waypoints = None
        self.h_legend = None

        self.h_background_rect = None  # A white patch to cover the whole area
        self.background = None
        self.background_bbox = None
        self.background_ax_limit = None

    def load_map(self, map):
        self.map = map
        self.map_visualizer = FindMapVisualizer(self.map)(self.map,
                                                          self.plotter.ax)
        self.plotter.ax.set_title(map.__repr__(), fontsize=16)
        self.plotter.canvas.draw()
        self.map_visualizer.draw_map()

    def reset_simulation(self):
        self.waypoints = []
        self.destination = None
        self.sim_steps = 0

    def set_state(self, state):
        self.state = state

    def draw_start_pos(self, x=None, y=None):
        if x is None or y is None:
            x, y = self.get_start_pos()

        if self.h_start_point:
            self.h_start_point.set_xdata(x)
            self.h_start_point.set_ydata(y)
        else:
            self.h_start_point, = self.plotter.ax.plot(x,
                                                       y,
                                                       'o',
                                                       markeredgecolor=(0.8, 0,
                                                                        0),
                                                       markerfacecolor='none')
        self.plotter.canvas.draw()

    def draw_goal_pos(self, x=None, y=None):
        if x is None or y is None:
            x, y = self.get_goal_pos()
        if self.h_goal_point:
            self.h_goal_point.set_xdata(x)
            self.h_goal_point.set_ydata(y)
        else:
            self.h_goal_point, = self.plotter.ax.plot(x,
                                                      y,
                                                      '*',
                                                      color=(0.8, 0, 0))
        self.plotter.canvas.draw()

    def get_start_pos(self):
        toks = self.control_panel.start_pos_text.GetValue().split(',')
        return [float(t) for t in toks]

    def get_goal_pos(self):
        toks = self.control_panel.goal_pos_text.GetValue().split(',')
        return [float(t) for t in toks]

    def set_start_pos(self, x, y):
        self.control_panel.start_pos_text.SetValue('%.2f, %.2f' % (x, y))
        self.draw_start_pos(x, y)

    def set_goal_pos(self, x, y):
        self.control_panel.goal_pos_text.SetValue('%.2f, %.2f' % (x, y))
        self.draw_goal_pos(x, y)

    def set_heading(self, heading):
        self.control_panel.heading_text.SetValue('%f' % heading)

    def draw_waypoints(self, xs, ys):
        if self.h_waypoints:
            self.h_waypoints.set_xdata(xs)
            self.h_waypoints.set_ydata(ys)
            return
        self.h_waypoints, = self.plotter.ax.plot(xs, ys, color='g', alpha=0.2)

    def set_sim_state(self, state):
        if state == SimGui.SimStateRunning:
            self.control_panel.start_sim_button.SetLabel('Pause')
            self.control_panel.stop_sim_button.Enable()
        elif state == SimGui.SimStatePaused:
            self.control_panel.start_sim_button.SetLabel('Resume')
            self.control_panel.stop_sim_button.Enable()
        elif state == SimGui.SimStateStopped:
            self.control_panel.start_sim_button.SetLabel('Start')
            self.control_panel.stop_sim_button.Disable()
        self.sim_state = state

    def cmd_stop_simulation(self, e):
        self.stop()
        print('simulation stopped')

    def _finish_current_step(self):
        if self.sim_thread is not None:
            self.sim_thread.join()
            self.sim_thread = None
            start_time = time.time()
            self.simulate_render(self.sim_steps)
            print('render time: %.3f' % (time.time() - start_time))
            self.sim_steps += 1

    def on_timer(self, e):
        if self.sim_thread is not None:
            if self.sim_thread.is_alive():
                return

            self.sim_thread = None

            start_time = time.time()
            self.simulate_render(self.sim_steps)
            print('render time: %.3f' % (time.time() - start_time))

            if self.control_panel.save_screenshot.GetValue():
                buf = self.make_screenshot()
                cv2.imwrite('sim_images/%05d.png' % self.sim_steps, buf)

                for name in self.agents:
                    agent = self.agents[name]['agent']
                    if hasattr(agent, 'img'):
                        img = self._prepare_img(agent.img, 256, 0.1)
                        cv2.imwrite(
                            'sim_images/%s_%05d.png' % (name, self.sim_steps),
                            img)

            if self.sim_steps == 0:
                # Call Layout() after rendering the first frame because the plot view may change
                # its size.
                self.Layout()

            self.sim_steps += 1

            n_sim_completed = 0
            for name in self.agents:
                agent = self.agents[name]['agent']
                if agent.stopped():
                    print('agent %s stopped' % name)
                    n_sim_completed += 1
                elif agent.reached_goal():
                    print('agent %s reached goal' % name)
                    n_sim_completed += 1

            if n_sim_completed == len(self.agents):
                print('simulation completes')
                self.stop()
                return

            if self.sim_steps >= self.sim_max_steps:
                print('max steps reached')
                self.stop()
                return

        self.sim_thread = threading.Thread(target=lambda: self.simulate_step())
        self.sim_thread.start()

    def on_record_slider_scroll(self, e):
        step = self.record_slider.GetValue()

        for name in self.agents:
            history = self.agents[name]['history']
            state = history[min(step, len(history) - 1)]
            self.agents[name]['agent'].restore_state(state)

        self.simulate_render(step)

    def pause(self):
        self.sim_timer.Stop()
        self._finish_current_step()
        self.set_sim_state(SimGui.SimStatePaused)
        self.record_slider.Enable()
        self.record_slider.SetMin(0)
        self.record_slider.SetMax(self.sim_steps - 1)
        self.record_slider.SetValue(self.sim_steps - 1)

    def resume(self):
        self.set_sim_state(SimGui.SimStateRunning)
        self.record_slider.Disable()

        for name in self.agents:
            history = self.agents[name]['history']
            state = history[-1]
            self.agents[name]['agent'].restore_state(state)

        self.sim_timer.Start(1, oneShot=wx.TIMER_CONTINUOUS)

    def stop(self):
        if self.sim_state == SimGui.SimStateRunning:
            self.pause()
        self.reset_simulation()
        self.sim_timer.Stop()
        self.set_sim_state(SimGui.SimStateStopped)
        self.record_slider.Disable()

    def cmd_simulate(self, e):
        if self.sim_state == SimGui.SimStateRunning:
            self.pause()
            return
        elif self.sim_state == SimGui.SimStatePaused:
            self.resume()
            return

        self.reset_simulation()

        param_utils.global_params.reload()

        start_pos = np.array(self.get_start_pos())
        goal_pos = np.array(self.get_goal_pos())

        if self.control_panel.no_goal.GetValue():
            waypoints = []
        elif self.control_panel.no_planning.GetValue():
            waypoints = [goal_pos]
        else:
            if self.last_start_pos is not None and self.last_goal_pos is not None \
                    and np.all(self.last_start_pos == start_pos) and np.all(self.last_goal_pos == goal_pos):
                print('reuse waypoints')
                waypoints = self.last_waypoints
            else:
                if self.control_panel.goal_destination_mode.GetValue():
                    dest = tuple(
                        self.map.get_destination_from_map_coord(
                            goal_pos[0], goal_pos[1]))
                    waypoints = self.map.find_path_destination(start_pos, dest)
                else:
                    waypoints = self.map.find_path(start_pos, goal_pos)

                self.last_waypoints = waypoints
                self.last_start_pos = start_pos
                self.last_goal_pos = goal_pos

        if waypoints is None:
            print('infeasible')
            return

        self.init_agents()

        if self.control_panel.replan.GetValue():
            # TODO: remove code duplication
            dest = tuple(
                self.map.get_destination_from_map_coord(
                    goal_pos[0], goal_pos[1]))
            if dest not in self.map.reachable_locs_per_destination:
                print('invalid destination')
            self.destination = dest

        for name in self.agents:
            agent = self.agents[name]['agent']
            agent.reset()
            agent.set_map(self.map)
            agent.set_pos(start_pos)
            agent.set_heading(
                np.deg2rad(float(self.control_panel.heading_text.GetValue())))
            agent.set_waypoints(waypoints)

            if hasattr(agent, 'set_destination'):
                # This agent uses destination as its goal
                dest = tuple(
                    self.map.get_destination_from_map_coord(
                        goal_pos[0], goal_pos[1]))
                if dest not in self.map.reachable_locs_per_destination:
                    print('invalid destination')
                else:
                    agent.set_destination(dest)
                print('destination label', dest)

        if waypoints is not None and len(waypoints) > 0:
            self.draw_waypoints(*zip(*waypoints))
        self.waypoints = waypoints

        self.draw_start_pos()
        self.draw_goal_pos()

        show_ticks = self.control_panel.show_axes_ticks.GetValue()
        self.plotter.ax.get_xaxis().set_visible(show_ticks)
        self.plotter.ax.get_yaxis().set_visible(show_ticks)

        if self.control_panel.show_title.GetValue():
            self.plotter.ax.set_title(self.map.__repr__(), fontsize=16)
        else:
            self.plotter.ax.set_title('')

        agent_names = sorted(list(self.agents.keys()))

        def to_uint8(color):
            return int(color[0] * 255), int(color[1] * 255), int(color[2] *
                                                                 255)

        agent_colors = [to_uint8(self.agents[_]['color']) for _ in agent_names]
        self.status_window.reset(agent_names, agent_colors)

        self.sim_max_steps = int(self.control_panel.max_steps_text.GetValue())

        self.sim_timer.Start(1, oneShot=wx.TIMER_CONTINUOUS)
        self.set_sim_state(SimGui.SimStateRunning)

    def _get_color(self, idx):
        return self.cm(idx % self.cm.N)

    def init_agents(self):
        # Clear current agents' visualization items.
        for _, agent in iteritems(self.agents):
            agent['visualizer'].clear()

        agent_switches = [
            b.GetValue() for b in self.control_panel.agent_checkedbuttons
        ]

        new_agents = {}

        agent_idx = 0
        for i in range(len(agent_switches)):
            if not agent_switches[i]:
                continue

            agent_constructor = agent_factory.public_agents[i]
            name = agent_constructor.__name__

            # Reuse agent object if possible because some agents
            # take a long time to initialize.
            # FIXME: HACK!
            # We don't reuse non-visual agents because we might change their parameters
            # if name in self.agents and hasattr(self.agents[name]['agent'], 'img'):
            #     instance = self.agents[name]['agent']
            # else:

            agent_kwargs = str_to_dict(
                self.control_panel.agent_preference_texts[i])
            instance = agent_constructor(**agent_kwargs)

            c = self._get_color(agent_idx)
            vis = FindAgentVisualizer(instance)(self.plotter.ax,
                                                draw_control_point=False,
                                                traj_color=c,
                                                obstacle_color=c,
                                                heading_color=c,
                                                active_wp_color=c,
                                                goal_color=c,
                                                label=name)

            new_agents[name] = {
                'agent': instance,
                'color': c,
                'visualizer': vis,
                'legend_handler': AgentLegendHandler(c),
                'history': [],
                'sim_state': {
                    'traj': [],
                    'collisions': 0
                }
            }

            agent_idx += 1

        # Delete agents that are no longer used
        for name, agent in iteritems(self.agents):
            if name not in new_agents:
                del agent['agent']

        self.agents = new_agents

    def draw_background(self, force_redraw=False):
        '''
        :return: draw background and cache it.
        '''
        # Pan/zoom can change axis limits. In that case we need to redraw the background.
        x1, x2 = self.plotter.ax.get_xlim()
        y1, y2 = self.plotter.ax.get_ylim()

        def draw_legends():
            agent_ids = self.agents.keys()
            if self.control_panel.show_legends.GetValue():
                self.h_legend = self.plotter.ax.legend(
                    agent_ids,
                    agent_ids,
                    handler_map={
                        agent_id: self.agents[agent_id]['legend_handler']
                        for agent_id in agent_ids
                    },
                    loc='lower left',
                    bbox_to_anchor=(0, -0.1),
                    ncol=5)
            elif self.h_legend:
                self.h_legend.remove()
                self.h_legend = None

        if self.background is None or \
           self.background_bbox != self.plotter.ax.bbox.__repr__() or \
           self.background_ax_limit != (x1, x2, y1, y2) or\
           force_redraw:

            self.plotter.ax.autoscale(False)

            draw_legends()

            # Cover all graphical elements
            if self.h_background_rect is None:
                self.h_background_rect = Rectangle((x1, y1),
                                                   x2 - x1,
                                                   y2 - y1,
                                                   color='w')
                self.h_background_rect.set_zorder(10**5)
                self.plotter.ax.add_patch(self.h_background_rect)
            else:
                self.h_background_rect.set_bounds(x1, y1, x2 - x1, y2 - y1)
                self.h_background_rect.set_visible(True)
                self.plotter.ax.draw_artist(self.h_background_rect)

            self.plotter.canvas.draw()

            # Draw the map
            self.map_visualizer.draw_map()

            self.h_background_rect.set_visible(False)

            self.plotter.canvas.blit(self.plotter.ax.bbox)

            self.background = self.plotter.canvas.copy_from_bbox(
                self.plotter.ax.bbox)
            self.background_bbox = self.plotter.ax.bbox.__repr__()

            # limits might get changed. Retrieve new limits here.
            x1, x2 = self.plotter.ax.get_xlim()
            y1, y2 = self.plotter.ax.get_ylim()
            self.background_ax_limit = (x1, x2, y1, y2)
        else:
            self.plotter.canvas.restore_region(self.background)

    def simulate_step(self):
        # Note that this runs in another thread.
        step_size = float(self.control_panel.step_size_text.GetValue())

        print('step %d step_size: %.3f' % (self.sim_steps, step_size))

        # Update agent states
        for name in self.agents:
            start_time = time.time()

            agent = self.agents[name]['agent']
            if agent.collide(
            ) and self.control_panel.stop_on_collision.GetValue():
                pass
            else:
                if self.control_panel.replan.GetValue():
                    waypoints = self.map.find_path_destination(
                        agent.pos, self.destination)
                    if waypoints is None or len(waypoints) == 0:
                        print('no valid waypoint. Use previous.')
                    else:
                        agent.set_waypoints(waypoints)
                        agent.wp_idx = 0

                agent.step(step_size)
                if agent.collide():
                    self.agents[name]['sim_state']['collisions'] += 1

                self.agents[name]['history'].append(agent.save_state())

                print('agent %s step time: %.3f '
                      'pos %.3f %.3f heading %.3f deg vel_norm %.3f collisions: %d' % \
                      (name, time.time() - start_time,
                       agent.pos[0], agent.pos[1],
                       np.rad2deg(agent.heading),
                       np.linalg.norm(agent.velocity),
                       self.agents[name]['sim_state']['collisions']))

            pos = agent.pos
            self.agents[name]['sim_state']['traj'].append([pos[0], pos[1]])

    def simulate_render(self, step_idx):
        # Always redraw at step 0
        self.draw_background(force_redraw=(step_idx == 0))

        self.plotter.ax.draw_artist(self.h_start_point)
        self.plotter.ax.draw_artist(self.h_goal_point)

        if self.h_waypoints is not None:
            self.plotter.ax.draw_artist(self.h_waypoints)

        for name in self.agents:
            v = self.agents[name]
            agent = v['agent']
            sim_state = v['sim_state']
            visualizer = v['visualizer']

            visualizer.draw_trajectory(*zip(*sim_state['traj'][:step_idx + 1]))
            visualizer.draw_agent_state(agent)

            visualizer.set_show_trajectory(
                self.control_panel.show_traj.GetValue())
            visualizer.set_show_accel(self.control_panel.show_accel.GetValue())
            visualizer.set_show_control_point_accels(
                self.control_panel.show_control_point_accels.GetValue())
            visualizer.set_show_control_point_metrics(
                self.control_panel.show_control_point_metrics.GetValue())
            visualizer.set_show_obstacles(
                self.control_panel.show_obstacles.GetValue())

            if hasattr(agent, 'img') and agent.img is not None:
                img = (np.array(agent.img.transpose(1, 2, 0), copy=True) *
                       255.0).astype(np.uint8)
                self._draw_all_waypoints(img, agent)
                self.status_window.set_image(name, img)

            self.status_window.set_text(
                name, 'velocity', '%.3f m/s' % np.linalg.norm(agent.velocity))
            self.status_window.set_text(
                name, 'angular_vel',
                '%.1f deg/s' % np.rad2deg(agent.angular_velocity))

        self.plotter.canvas.blit(self.plotter.ax.bbox)

    def _prepare_img(self, img, img_width, brightness_adj):
        img = np.transpose(img, (1, 2, 0))
        height = int(float(img_width) / img.shape[1] * img.shape[0])
        img = cv2.resize(img, (img_width, height))
        if img.dtype == np.float32:
            img = np.clip(((img + brightness_adj) * 255), 0.0,
                          255.0).astype(np.uint8)
        img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
        return img

    def _draw_all_waypoints(self, img, agent):
        """
        Draw the groundtruth and neural waypoints if available
        TODO: remove hardcoded colors.
        :param img: 3-channel uint8 image
        """
        if len(agent.goals_global) > 0:
            self._draw_waypoint(img, agent,
                                agent.global_to_local(agent.goals_global[0]),
                                (0, 255, 0))

    def _draw_waypoint(self, img, agent, wp_local, color):
        if hasattr(agent, 'camera_pos'):
            cam_pos = agent.camera_pos
        else:
            return

        # Select a sensor that has 'h_fov' attribute.
        for sensor in agent.sensors:
            if hasattr(sensor, 'h_fov'):
                fov = sensor.h_fov

        img_width = img.shape[1]

        def to_image_space(p):
            size = max(int(10.0 / (np.linalg.norm(p) + 0.01)), 5)
            l = img_width * 0.5 / np.tan(0.5 * fov)
            d = p[1] / p[0] * l
            return d, size

        wp_local[0] -= cam_pos[0]
        wp_local[1] -= cam_pos[1]

        if wp_local[0] < 0:
            # Waypoint behind the agent. Ignore.
            return

        d, s = to_image_space(wp_local)
        cv2.circle(img, (img.shape[1] // 2 - int(d), img.shape[0] // 2), s,
                   color, 2)

    def make_screenshot(self):
        w, h = self.plotter.canvas.get_width_height()
        buf = np.fromstring(self.plotter.canvas.tostring_rgb(), dtype=np.uint8)
        buf = buf.reshape((h, w, 3))
        buf = cv2.cvtColor(buf, cv2.COLOR_RGB2BGR)

        # If the agent uses visual images, also save them.
        img_width = 512
        brightness_adj = 0.1
        strip = np.ones((h, img_width, 3), np.uint8) * 128
        y = 0

        for name in self.agents:
            agent = self.agents[name]['agent']
            if hasattr(agent, 'img'):
                img = self._prepare_img(agent.img, img_width, brightness_adj)
                self._draw_all_waypoints(img, agent)

                if hasattr(agent, 'img_left'):
                    img_left = self._prepare_img(agent.img_left, img_width,
                                                 brightness_adj)
                    img = np.concatenate([img_left, img], axis=1)

                if hasattr(agent, 'img_right'):
                    img_right = self._prepare_img(agent.img_right, img_width,
                                                  brightness_adj)
                    img = np.concatenate([img, img_right], axis=1)

                label_banner_cairo = np.zeros((32, img.shape[1]), np.uint32)

                surface = cairo.ImageSurface.create_for_data(
                    label_banner_cairo, cairo.FORMAT_ARGB32,
                    label_banner_cairo.shape[1], label_banner_cairo.shape[0])
                cr = cairo.Context(surface)

                if agent.collide():
                    cr.set_source_rgb(255, 0, 0)
                else:
                    cr.set_source_rgb(255, 255, 255)

                cr.select_font_face('Helvetica', cairo.FONT_SLANT_NORMAL,
                                    cairo.FONT_WEIGHT_NORMAL)
                cr.set_font_size(24)
                cr.move_to(0, 24)
                cr.show_text(name)

                label_banner = cairo_argb_to_opencv_bgr(label_banner_cairo)

                img = np.concatenate([label_banner, img], axis=0)

                if y + img.shape[0] > strip.shape[0]:
                    # Create new if current is full
                    buf = np.concatenate([buf, strip], axis=1)
                    strip = np.zeros((h, img_width, 3), np.uint8)
                    y = 0

                # Enlarge the strip's width if img's width is larger than the strip's width
                if strip.shape[1] < img.shape[1]:
                    padding = np.zeros(
                        (strip.shape[0], img.shape[1] - strip.shape[1],
                         strip.shape[2]),
                        dtype=strip.dtype)
                    strip = np.concatenate([strip, padding], axis=1)

                strip[y:y + img.shape[0], 0:img.shape[1], :] = img
                y += img.shape[0]

        if y > 0:
            buf = np.concatenate([buf, strip], axis=1)
        return buf

    def on_click_canvas(self, event):
        if self.state == SimGui.StateSetStartPos:
            self.set_start_pos(event.xdata, event.ydata)
            self.state = SimGui.StateNone
        elif self.state == SimGui.StateSetGoalPos:
            self.set_goal_pos(event.xdata, event.ydata)
            self.state = SimGui.StateNone

    def on_close(self, e):
        self.sim_timer.Stop()
        del self.agents
        e.Skip()

    def cmd_show_waypoints(self):
        if self.h_waypoints:
            self.h_waypoints.set_visible(
                self.control_panel.show_waypoints.GetValue())
Ejemplo n.º 8
0
class SimRenderer(object):
    def __init__(self, map, ax, canvas):
        self.ax = ax
        self.canvas = canvas
        self.map = map
        self.map_visualizer = FindMapVisualizer(self.map)(self.map, self.ax)
        self.agents = {}

        self.show_legends = True
        self.h_legend = None

        self.background = None
        self.background_ax_limit = None
        self.background_bbox = None
        self.h_background_rect = None

        self.plotter = CachedPlotter(ax)

    def set_agents(self, agents):
        self.agents = agents

    def clear(self):
        self.plotter.clear()

    def draw_background(self, force_redraw=False):
        '''
        :return: draw background and cache it.
        '''
        # Pan/zoom can change axis limits. In that case we need to redraw the background.
        x1, x2 = self.ax.get_xlim()
        y1, y2 = self.ax.get_ylim()

        if self.background is None or \
                self.background_bbox != self.ax.bbox.__repr__() or \
                self.background_ax_limit != (x1, x2, y1, y2) or \
                force_redraw:

            self.ax.autoscale(False)

            # Cover all graphical elements
            if self.h_background_rect is None:
                self.h_background_rect = Rectangle((x1, y1),
                                                   x2 - x1,
                                                   y2 - y1,
                                                   color='w')
                self.h_background_rect.set_zorder(10**5)
                self.ax.add_patch(self.h_background_rect)
            else:
                self.h_background_rect.set_bounds(x1, y1, x2 - x1, y2 - y1)
                self.h_background_rect.set_visible(True)
                self.ax.draw_artist(self.h_background_rect)

            self.canvas.draw()

            self.map_visualizer.draw_map()

            self.h_background_rect.set_visible(False)

            self.canvas.blit(self.ax.bbox)

            self.background = self.canvas.copy_from_bbox(self.ax.bbox)
            self.background_bbox = self.ax.bbox.__repr__()

            # limits might get changed. Retrieve new limits here.
            x1, x2 = self.ax.get_xlim()
            y1, y2 = self.ax.get_ylim()
            self.background_ax_limit = (x1, x2, y1, y2)
        else:
            self.canvas.restore_region(self.background)

    def set_limits(self, x1, x2, y1, y2):
        self.ax.set_xlim(x1, x2)
        self.ax.set_ylim(y1, y2)

    def get_limits(self):
        x1, x2 = self.ax.get_xlim()
        y1, y2 = self.ax.get_ylim()
        return x1, x2, y1, y2

    def set_viewport(self, x, y, scale):
        """
        :param x, y: center of the viewport
        :param scale: zoom factor w.r.t current viewport
        """
        x_min, x_max, y_min, y_max = self.map.visible_map_bbox

        w = (x_max - x_min) * scale
        h = (y_max - y_min) * scale

        xx1, xx2 = x - w / 2., x + w / 2.
        yy1, yy2 = y - h / 2., y + h / 2.

        self.ax.set_xlim(xx1, xx2)
        self.ax.set_ylim(yy1, yy2)

    def reset_viewport(self):
        x_min, x_max, y_min, y_max = self.map.visible_map_bbox
        self.ax.set_xlim([x_min, x_max])
        self.ax.set_ylim([y_min, y_max])

    def draw_agents(self):
        for name in self.agents:
            v = self.agents[name]
            agent = v['agent']
            visualizer = v['visualizer']
            visualizer.draw_agent_state(agent)

    def render(self, force_redraw=False, blit=True):
        self.draw_background(force_redraw)
        self.draw_agents()
        if blit:
            self.canvas.blit(self.ax.bbox)

    def blit(self):
        self.canvas.blit(self.ax.bbox)

    def get_image(self):
        w, h = self.canvas.get_width_height()
        buf = np.fromstring(self.canvas.tostring_rgb(), dtype=np.uint8)
        buf = buf.reshape((h, w, 3))
        return buf

    def save(self, filename, **kwargs):
        self.canvas.print_figure(filename, **kwargs)
Ejemplo n.º 9
0
    '-s',
    f'{width}x{height}',  # size of image string
    '-f',
    'rawvideo',
    '-pix_fmt',
    'rgba',  # format
    '-i',
    '-',  # tell ffmpeg to expect raw video from the pipe
    '-c:v',
    'hevc_videotoolbox',
    '-tag:v',
    'hvc1',
    '-profile:v',
    'main10',
    '-b:v',
    '16M',
    OUTPUT_VIDEO,
)

ffmpeg = subprocess.Popen(cmd, stdin=subprocess.PIPE, bufsize=10**8)

for i in range(40):
    print('task.i:', i)
    rect.set_bounds((0, 0, i, height))
    fig.savefig(ffmpeg.stdin, format='rgba', dpi=100)

ffmpeg.communicate()
# out, err = ffmpeg.communicate()

subprocess.run(('open', OUTPUT_VIDEO))
class ThreeCompVisualisation:
    """
    Basis to visualise power flow within the hydraulics model as an animation or simulation
    """

    def __init__(self, agent: ThreeCompHydAgent,
                 axis: plt.axis = None,
                 animated: bool = False,
                 detail_annotations: bool = False,
                 basic_annotations: bool = True,
                 black_and_white: bool = False):
        """
        Whole visualisation setup using given agent's parameters
        :param agent: The agent to be visualised
        :param axis: If set, the visualisation will be drawn using the provided axis object.
        Useful for animations or to display multiple models in one plot
        :param animated: If true the visualisation is set up to deal with frame updates.
        See animation script for more details.
        :param detail_annotations: If true, tank distances and sizes are annotated as well.
        :param basic_annotations: If true, U, LF, and LS are visible
        :param black_and_white: If true, the visualisation is in black and white
        """
        # matplotlib fontsize
        rcParams['font.size'] = 10

        # plot if no axis was assigned
        if axis is None:
            fig = plt.figure(figsize=(8, 5))
            self._ax1 = fig.add_subplot(1, 1, 1)
        else:
            fig = None
            self._ax1 = axis

        if black_and_white:
            self.__u_color = (0.7, 0.7, 0.7)
            self.__lf_color = (0.5, 0.5, 0.5)
            self.__ls_color = (0.3, 0.3, 0.3)
            self.__ann_color = (0, 0, 0)
            self.__p_color = (0.5, 0.5, 0.5)

        elif not black_and_white:
            self.__u_color = "tab:cyan"
            self.__lf_color = "tab:orange"
            self.__ls_color = "tab:red"
            self.__ann_color = "tab:blue"
            self.__p_color = "tab:green"

        # basic parameters for setup
        self._animated = animated
        self.__detail_annotations = detail_annotations
        self._agent = agent
        self.__offset = 0.2

        # U tank with three stripes
        self.__width_u = 0.3
        self._u = None
        self._u1 = None
        self._u2 = None
        self._r1 = None  # line marking flow from U to LF
        self._ann_u = None  # U annotation

        # LF tank
        self._lf = None
        self._h = None  # fill state
        self._ann_lf = None  # annotation

        # LS tank
        self._ls = None
        self._g = None
        self._ann_ls = None  # annotation LS
        self._r2 = None  # line marking flow from LS to LF

        # finish the basic layout
        self.__set_basic_layout()
        self.update_basic_layout(agent)

        # now the animation components
        if self._animated:
            # U flow
            self._arr_u_flow = None
            self._ann_u_flow = None

            # flow out of tap
            self._arr_power_flow = None
            self._ann_power_flow = None

            # LS flow (R2)
            self._arr_r2_l_pos = None
            self._arr_r2_flow = None
            self._ann_r2_flow = None

            # time information annotation
            self._ann_time = None

            self.__set_animation_layout()
            self._ax1.add_artist(self._ann_time)

        # basic annotations are U, LF, and LS
        if not basic_annotations:
            self.hide_basic_annotations()

        # add layout for detailed annotations
        # detail annotation add greek letters for distances and positions
        if self.__detail_annotations:
            self.__set_detailed_annotations_layout()
            self._ax1.set_xlim(0, 1.05)
            self._ax1.set_ylim(0, 1.2)
        else:
            self._ax1.set_xlim(0, 1.0)
            self._ax1.set_ylim(0, 1.2)

        if self.__detail_annotations and self._animated:
            raise UserWarning("Detailed annotations and animation cannot be combined")

        self._ax1.set_axis_off()

        # display plot if no axis object was assigned
        if fig is not None:
            plt.subplots_adjust(left=0.02, bottom=0.02, right=0.98, top=0.98)
            plt.show()
            plt.close(fig)

    def __set_detailed_annotations_layout(self):
        """
        Adds components required for a detailed
        annotations view with denoted positions and distances
        """

        u_width = self.__width_u
        ls_left = self._ls.get_x()
        ls_width = self._ls.get_width()
        lf_left = self._lf.get_x()
        lf_width = self._lf.get_width()
        ls_height = self._ls.get_height()

        # some offset to the bottom
        offset = self.__offset
        phi_o = self._agent.phi + offset
        gamma_o = self._agent.gamma + offset

        rcParams['text.usetex'] = True

        self._ann_u_flow = Text(text="$M_{U}$", ha='right', fontsize="xx-large", x=u_width + 0.09,
                                y=phi_o - 0.08)
        ann_p_ae = Text(text="$p_{U}$", ha='right', fontsize="xx-large", x=u_width + 0.07,
                        y=phi_o + 0.03)
        self._arr_u_flow = FancyArrowPatch((u_width, phi_o),
                                           (u_width + 0.1, phi_o),
                                           arrowstyle='-|>',
                                           mutation_scale=30,
                                           lw=2,
                                           color=self.__u_color)
        self._ax1.annotate('$\phi$',
                           xy=(u_width / 2, phi_o),
                           xytext=(u_width / 2, (phi_o - offset) / 2 + offset - 0.015),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.annotate('$\phi$',
                           xy=(u_width / 2, offset),
                           xytext=(u_width / 2, (phi_o - offset) / 2 + offset - 0.015),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )

        self._ann_power_flow = Text(text="$p$", ha='center', fontsize="xx-large", x=self._ann_lf.get_position()[0],
                                    y=offset - 0.06)
        self._arr_power_flow = FancyArrowPatch((self._ann_lf.get_position()[0], offset - 0.078),
                                               (self._ann_lf.get_position()[0], 0.0),
                                               arrowstyle='-|>',
                                               mutation_scale=30,
                                               lw=2,
                                               color=self.__p_color)

        self._ax1.annotate('$h$',
                           xy=(self._ann_lf.get_position()[0] + 0.07, 1 + offset),
                           xytext=(self._ann_lf.get_position()[0] + 0.07, 1 + offset - 0.30),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.annotate('$h$',
                           xy=(self._ann_lf.get_position()[0] + 0.07, 1 + offset - 0.55),
                           xytext=(self._ann_lf.get_position()[0] + 0.07, 1 + offset - 0.30),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )

        self._h.update(dict(xy=(lf_left, offset),
                            width=lf_width,
                            height=1 - 0.55,
                            color=self.__lf_color))

        self._ax1.annotate('$g$',
                           xy=(ls_left + ls_width + 0.02, ls_height + gamma_o),
                           xytext=(ls_left + ls_width + 0.02, ls_height * 0.61 + gamma_o),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.annotate('$g$',
                           xy=(ls_left + ls_width + 0.02, ls_height * 0.3 + gamma_o),
                           xytext=(ls_left + ls_width + 0.02, ls_height * 0.61 + gamma_o),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._g.update(dict(xy=(ls_left, gamma_o),
                            width=ls_width,
                            height=ls_height * 0.3,
                            color=self.__ls_color))

        ann_p_an = Text(text="$p_{L}$", ha='left', usetex=True, fontsize="xx-large", x=ls_left - 0.06,
                        y=gamma_o + 0.11)
        ann_arr_flow = Text(text="$M_{LS}$", ha='left', usetex=True, fontsize="xx-large", x=ls_left - 0.09,
                            y=gamma_o + 0.03)
        self._ann_r2_flow = Text(text="$M_{LF}$", ha='left', usetex=True, fontsize="xx-large", x=ls_left - 0.04,
                                 y=gamma_o - 0.07)

        self._arr_r2_flow = FancyArrowPatch((ls_left - 0.1, gamma_o),
                                            (ls_left, gamma_o),
                                            arrowstyle='<|-|>',
                                            mutation_scale=30,
                                            lw=2,
                                            color=self.__ls_color)

        self._ax1.annotate('$\\theta$',
                           xy=(ls_left + ls_width / 2, 1 + offset),
                           xytext=(
                               ls_left + ls_width / 2,
                               1 - (1 - (ls_height + gamma_o - offset)) / 2 + offset - 0.015),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.annotate('$\\theta$',
                           xy=(ls_left + ls_width / 2, ls_height + gamma_o),
                           xytext=(
                               ls_left + ls_width / 2,
                               1 - (1 - (ls_height + gamma_o - offset)) / 2 + offset - 0.015),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )

        self._ax1.annotate('$\\gamma$',
                           xy=(ls_left + ls_width / 2, offset),
                           xytext=(ls_left + ls_width / 2, (gamma_o - offset) / 2 + offset - 0.015),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.annotate('$\\gamma$',
                           xy=(ls_left + ls_width / 2, gamma_o),
                           xytext=(ls_left + ls_width / 2, (gamma_o - offset) / 2 + offset - 0.015),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )

        self._ax1.annotate('$1$',
                           xy=(1.05, 0 + offset),
                           xytext=(1.05, 0.5 + offset),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.annotate('$1$',
                           xy=(1.05, 1 + offset),
                           xytext=(1.05, 0.5 + offset),
                           ha='center',
                           fontsize="xx-large",
                           arrowprops=dict(arrowstyle='-|>',
                                           ls='-',
                                           fc=self.__ann_color)
                           )
        self._ax1.add_artist(ann_arr_flow)
        # self._ax1.add_artist(ann_p_an)
        # self._ax1.add_artist(ann_p_ae)
        self._ax1.axhline(offset, linestyle='--', color=self.__ann_color)
        self._ax1.axhline(1 + offset - 0.001, linestyle='--', color=self.__ann_color)
        self._ax1.add_artist(self._ann_power_flow)
        self._ax1.add_artist(self._arr_power_flow)
        self._ax1.add_artist(self._arr_u_flow)
        self._ax1.add_artist(self._ann_u_flow)
        self._ax1.add_artist(self._arr_r2_flow)
        self._ax1.add_artist(self._ann_r2_flow)

    def __set_animation_layout(self):
        """
        Adds layout components that are required for an animation
        """

        offset = self.__offset
        o_width = self.__width_u
        phi_o = self._agent.phi + offset
        gamma_o = self._agent.gamma + offset

        # U flow (R1)
        self._arr_u_flow = FancyArrowPatch((o_width, phi_o),
                                           (o_width + 0.1, phi_o),
                                           arrowstyle='simple',
                                           mutation_scale=0,
                                           ec='white',
                                           fc=self.__u_color)
        self._ann_u_flow = Text(text="flow: ", ha='right', fontsize="large", x=o_width, y=phi_o - 0.05)

        # Tap flow (Power)
        self._arr_power_flow = FancyArrowPatch((self._ann_lf.get_position()[0], offset - 0.05),
                                               (self._ann_lf.get_position()[0], 0.0),
                                               arrowstyle='simple',
                                               mutation_scale=0,
                                               ec='white',
                                               color=self.__p_color)
        self._ann_power_flow = Text(text="flow: ", ha='center', fontsize="large", x=self._ann_lf.get_position()[0],
                                    y=offset - 0.05)

        # LS flow (R2)
        self._arr_r2_l_pos = [(self._ls.get_x(), gamma_o),
                              (self._ls.get_x() - 0.1, gamma_o)]
        self._arr_r2_flow = FancyArrowPatch(self._arr_r2_l_pos[0],
                                            self._arr_r2_l_pos[1],
                                            arrowstyle='simple',
                                            mutation_scale=0,
                                            ec='white',
                                            color=self.__ls_color)
        self._ann_r2_flow = Text(text="flow: ", ha='left', fontsize="large", x=self._ls.get_x(),
                                 y=gamma_o - 0.05)

        # information annotation
        self._ann_time = Text(x=1, y=0.9 + offset, ha="right")

        self._ax1.add_artist(self._ann_power_flow)
        self._ax1.add_artist(self._arr_power_flow)
        self._ax1.add_artist(self._arr_u_flow)
        self._ax1.add_artist(self._ann_u_flow)
        self._ax1.add_artist(self._arr_r2_flow)
        self._ax1.add_artist(self._ann_r2_flow)

    def __set_basic_layout(self):
        """
        updates position estimations and layout
        """

        # get sizes from agent
        lf = self._agent.lf
        ls = self._agent.ls
        ls_height = self._agent.height_ls

        # u_left is 0
        u_width = self.__width_u

        # determine width with size ratio retained
        lf_left = u_width + 0.1
        lf_width = ((lf * ls_height) * (1 - lf_left - 0.1)) / ls
        ls_left = lf_left + lf_width + 0.1
        ls_width = 1 - ls_left

        # some offset to the bottom
        offset = self.__offset
        phi_o = self._agent.phi + offset
        gamma_o = self._agent.gamma + offset

        # S tank
        self._u = Rectangle((0.0, phi_o), 0.05, 1 - self._agent.phi, color=self.__u_color, alpha=0.3)
        self._u1 = Rectangle((0.05, phi_o), 0.05, 1 - self._agent.phi, color=self.__u_color, alpha=0.6)
        self._u2 = Rectangle((0.1, phi_o), u_width - 0.1, 1 - self._agent.phi, color=self.__u_color)
        self._r1 = Line2D([u_width, u_width + 0.1],
                          [phi_o, phi_o],
                          color=self.__u_color)
        self._ann_u = Text(text="$U$", ha='center', fontsize="xx-large",
                           x=u_width / 2,
                           y=((1 - self._agent.phi) / 2) + phi_o - 0.02)

        # LF vessel
        self._lf = Rectangle((lf_left, offset), lf_width, 1, fill=False, ec="black")
        self._h = Rectangle((lf_left, offset), lf_width, 1, color=self.__lf_color)
        self._ann_lf = Text(text="$LF$", ha='center', fontsize="xx-large",
                            x=lf_left + (lf_width / 2),
                            y=offset + 0.5 - 0.02)

        # LS vessel
        self._ls = Rectangle((ls_left, gamma_o), ls_width, ls_height, fill=False, ec="black")
        self._g = Rectangle((ls_left, gamma_o), ls_width, ls_height, color=self.__ls_color)
        self._r2 = Line2D([ls_left, ls_left - 0.1],
                          [gamma_o, gamma_o],
                          color=self.__ls_color)
        self._ann_ls = Text(text="$LS$", ha='center', fontsize="xx-large",
                            x=ls_left + (ls_width / 2),
                            y=gamma_o + (ls_height / 2) - 0.02)

        # the basic layout
        self._ax1.add_line(self._r1)
        self._ax1.add_line(self._r2)
        self._ax1.add_artist(self._u)
        self._ax1.add_artist(self._u1)
        self._ax1.add_artist(self._u2)
        self._ax1.add_artist(self._lf)
        self._ax1.add_artist(self._ls)
        self._ax1.add_artist(self._h)
        self._ax1.add_artist(self._g)
        self._ax1.add_artist(self._ann_u)
        self._ax1.add_artist(self._ann_lf)
        self._ax1.add_artist(self._ann_ls)

    def update_basic_layout(self, agent):
        """
        updates tank positions and sizes according to new agent
        :param agent: agent to be visualised
        """

        self._agent = agent

        # get sizes from agent
        lf = agent.lf
        ls = agent.ls
        ls_heigh = agent.height_ls

        # o_left is 0
        u_width = self.__width_u

        # determine width with size ratio retained
        lf_left = u_width + 0.1
        lf_width = ((lf * ls_heigh) * (1 - lf_left - 0.1)) / (ls + lf * ls_heigh)
        ls_left = lf_left + lf_width + 0.1
        ls_width = 1 - ls_left

        # some offset to the bottom
        offset = self.__offset
        phi_o = agent.phi + offset
        gamma_o = agent.gamma + offset

        # S tank
        self._u.set_bounds(0.0, phi_o, 0.05, 1 - self._agent.phi)
        self._u1.set_bounds(0.05, phi_o, 0.05, 1 - self._agent.phi)
        self._u2.set_bounds(0.1, phi_o, u_width - 0.1, 1 - self._agent.phi)
        self._r1.set_xdata([u_width, u_width + 0.1])
        self._r1.set_ydata([phi_o, phi_o])
        self._ann_u.set_position(xy=(u_width / 2, ((1 - self._agent.phi) / 2) + phi_o - 0.02))

        # LF vessel
        self._lf.set_bounds(lf_left, offset, lf_width, 1)
        self._h.set_bounds(lf_left, offset, lf_width, 1)
        self._ann_lf.set_position(xy=(lf_left + (lf_width / 2),
                                      offset + 0.5 - 0.02))

        # LS vessel
        self._ls.set_bounds(ls_left, gamma_o, ls_width, ls_heigh)
        self._g.set_bounds(ls_left, gamma_o, ls_width, ls_heigh)
        self._r2.set_xdata([ls_left, ls_left - 0.1])
        self._r2.set_ydata([gamma_o, gamma_o])
        self._ann_ls.set_position(xy=(ls_left + (ls_width / 2), gamma_o + (ls_heigh / 2) - 0.02))

        # update levels
        self._h.set_height(1 - self._agent.get_h())
        self._g.set_height(self._agent.height_ls - self._agent.get_g())

    def hide_basic_annotations(self):
        """
        Simply hides the S, LF, and LS text
        """
        self._ann_u.set_text("")
        self._ann_lf.set_text("")
        self._ann_ls.set_text("")

    def update_animation_data(self, frame_number):
        """
        For animations and simulations.
        The function to call at each frame.
        :param frame_number: frame number has to be taken because of parent class method
        :return: an iterable of artists
        """

        if not self._animated:
            raise UserWarning("Animation flag has to be enabled in order to use this function")

        # perform one step
        cur_time = self._agent.get_time()
        power = self._agent.perform_one_step()

        # draw some information
        self._ann_time.set_text("agent \n time: {}".format(int(cur_time)))

        # power arrow
        self._ann_power_flow.set_text("power: {}".format(round(power)))
        self._arr_power_flow.set_mutation_scale(math.log(power + 1) * 10)

        # oxygen arrow
        p_u = round(self._agent.get_p_u() * self._agent.hz, 1)
        max_str = "(MAX)" if p_u == self._agent.m_u else ""
        self._ann_u_flow.set_text("flow: {} {}".format(p_u, max_str))
        self._arr_u_flow.set_mutation_scale(math.log(p_u + 1) * 10)

        # lactate arrow
        p_g = round(self._agent.get_p_l() * self._agent.hz, 1)
        if p_g < 0:
            max_str = "(MAX)" if p_g == self._agent.m_lf else ""
            self._arr_r2_flow.set_positions(self._arr_r2_l_pos[1], self._arr_r2_l_pos[0])
        else:
            max_str = "(MAX)" if p_g == self._agent.m_ls else ""
            self._arr_r2_flow.set_positions(self._arr_r2_l_pos[0], self._arr_r2_l_pos[1])
        self._ann_r2_flow.set_text("flow: {} {}".format(p_g, max_str))
        self._arr_r2_flow.set_mutation_scale(math.log(abs(p_g) + 1) * 10)

        # update levels
        self._h.set_height(1 - self._agent.get_h())
        self._g.set_height(self._agent.height_ls - self._agent.get_g())

        # list of artists to be drawn
        return [self._ann_time,
                self._ann_power_flow,
                self._arr_power_flow,
                self._g,
                self._h]