Example #1
0
class TestFrame(wx.Frame):
    def __init__(self, parent=None, *args, **kwds):
        kwds[
            "style"] = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER | wx.TAB_TRAVERSAL
        wx.Frame.__init__(self, parent, wx.NewId(), '', wx.DefaultPosition,
                          wx.Size(-1, -1), **kwds)
        self.SetTitle(" WXMPlot Plotting Demo")
        self.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, False))
        self.plotframe = None
        self.Bind(wx.EVT_TIMER, self.onTimer)
        self.timer = wx.Timer(self)
        self.Refresh()
        self.ShowPlotFrame(do_raise=False, clear=False)
        self.onStartTimer()

    def ShowPlotFrame(self, do_raise=True, clear=True):
        "make sure plot frame is enabled, and visible"
        if self.plotframe is None:
            self.plotframe = PlotFrame(self)
            self.has_plot = False
        try:
            self.plotframe.Show()
        except PyDeadObjectError:
            self.plotframe = PlotFrame(self)
            self.plotframe.Show()

        if do_raise:
            self.plotframe.Raise()
        if clear:
            self.plotframe.panel.clear()
            self.plotframe.reset_config()

    def onStartTimer(self, event=None):
        self.count = 0
        self.timer.Start(1)

    def onTimer(self, event):
        # print 'timer ', self.count, time.time()
        self.count += 1
        self.x = arange(0.0, 3, 0.01)
        self.y = sin(2 * pi * self.x + self.count)
        self.plotframe.plot(self.x, self.y)

    def OnExit(self, event):
        try:
            if self.plotframe != None: self.plotframe.onExit()
        except:
            pass
        self.Destroy()
Example #2
0
class TestFrame(wx.Frame):
    def __init__(self, parent=None, *args, **kwds):

        kwds[
            "style"] = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER | wx.TAB_TRAVERSAL

        wx.Frame.__init__(self, parent, -1, '', wx.DefaultPosition,
                          wx.Size(-1, -1), **kwds)
        self.SetTitle(" WXMPlot Plotting Demo")

        self.SetFont(wx.Font(12, wx.SWISS, wx.NORMAL, wx.BOLD, False))
        menu = wx.Menu()
        menu_exit = menu.Append(-1, "E&xit", "Terminate the program")

        menuBar = wx.MenuBar()
        menuBar.Append(menu, "&File")
        self.SetMenuBar(menuBar)

        self.Bind(wx.EVT_MENU, self.OnExit, menu_exit)

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

        self.plotframe = None

        self.create_data()
        framesizer = wx.BoxSizer(wx.VERTICAL)

        panel = wx.Panel(self, -1, size=(-1, -1))
        panelsizer = wx.BoxSizer(wx.VERTICAL)

        panelsizer.Add(
            wx.StaticText(panel, -1, 'wxmplot 2D PlotPanel examples '), 0,
            wx.ALIGN_LEFT | wx.LEFT | wx.EXPAND, 10)

        b10 = wx.Button(panel, -1, 'Example #1', size=(-1, -1))
        b20 = wx.Button(panel, -1, 'Example #2', size=(-1, -1))
        b22 = wx.Button(panel, -1, 'Plot with 2 axes', size=(-1, -1))
        b31 = wx.Button(panel, -1, 'Plot with Errorbars', size=(-1, -1))
        b32 = wx.Button(panel, -1, 'SemiLog Plot', size=(-1, -1))
        b40 = wx.Button(panel, -1, 'Start Timed Plot', size=(-1, -1))
        b50 = wx.Button(panel, -1, 'Stop Timed Plot', size=(-1, -1))
        b60 = wx.Button(panel, -1, 'Plot 500,000 points', size=(-1, -1))
        bmany1 = wx.Button(panel,
                           -1,
                           'Plot 20 traces (delay_draw=False)',
                           size=(-1, -1))
        bmany2 = wx.Button(panel,
                           -1,
                           'Plot 20 traces (delay_draw=True)',
                           size=(-1, -1))
        bmany3 = wx.Button(panel,
                           -1,
                           'Plot 20 traces (use plot_many())',
                           size=(-1, -1))

        b10.Bind(wx.EVT_BUTTON, self.onPlot1)
        b20.Bind(wx.EVT_BUTTON, self.onPlot2)
        b22.Bind(wx.EVT_BUTTON, self.onPlot2Axes)
        b31.Bind(wx.EVT_BUTTON, self.onPlotErr)
        b32.Bind(wx.EVT_BUTTON, self.onPlotSLog)
        b40.Bind(wx.EVT_BUTTON, self.onStartTimer)
        b50.Bind(wx.EVT_BUTTON, self.onStopTimer)
        b60.Bind(wx.EVT_BUTTON, self.onPlotBig)

        bmany1.Bind(wx.EVT_BUTTON, self.onPlotMany_Slow)
        bmany2.Bind(wx.EVT_BUTTON, self.onPlotMany_Delay)
        bmany3.Bind(wx.EVT_BUTTON, self.onPlotMany_Fast)

        panelsizer.Add(b10, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b20, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b22, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b31, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b32, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b40, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b50, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(b60, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)

        panelsizer.Add(bmany1, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(bmany2, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)
        panelsizer.Add(bmany3, 0, wx.ALIGN_LEFT | wx.ALIGN_CENTER | wx.LEFT, 5)

        panel.SetSizer(panelsizer)
        panelsizer.Fit(panel)

        framesizer.Add(panel, 0, wx.ALIGN_LEFT | wx.EXPAND, 2)
        self.SetSizer(framesizer)
        framesizer.Fit(self)

        self.Bind(wx.EVT_TIMER, self.onTimer)
        self.timer = wx.Timer(self)
        self.Refresh()

    def create_data(self):
        self.count = 0
        self.x = x = arange(0.0, 25.0, 0.1)
        self.y1 = 4 * cos(2 * pi * (x - 1) / 5.7) / (6 + x) + 2 * sin(
            2 * pi * (x - 1) / 2.2) / (10)
        self.y2 = sin(2 * pi * x / 30.0)
        self.y3 = -pi + 2 * (x / 10. + exp(-(x - 3) / 5.0))
        self.y4 = exp(0.01 + 0.5 * x) / (x + 2)
        self.y5 = 3000 * self.y3
        self.npts = len(self.x)
        self.bigx = linspace(0, 2500, 500000)
        self.bigy = (sin(pi * self.bigx / 140.0) +
                     cos(pi * self.bigx / 277.0) + cos(pi * self.bigx / 820.0))

        self.many_dlist = [(self.x, self.y1)]
        for i in range(19):
            self.many_dlist.append((self.x, sin(2 * (i + 1) * x / 23.0)))

    def ShowPlotFrame(self, do_raise=True, clear=True):
        "make sure plot frame is enabled, and visible"
        if self.plotframe is None:
            self.plotframe = PlotFrame(self)
            self.has_plot = False
        try:
            self.plotframe.Show()
        except PyDeadObjectError:
            self.plotframe = PlotFrame(self)
            self.plotframe.Show()

        if do_raise:
            self.plotframe.Raise()
        if clear:
            self.plotframe.panel.clear()
            self.plotframe.reset_config()

    def onPlot1(self, event=None):
        self.ShowPlotFrame()
        self.plotframe.plot(self.x, self.y1)
        self.plotframe.oplot(self.x, self.y2)
        self.plotframe.write_message("Plot 1")

    def onPlot2(self, event=None):
        self.ShowPlotFrame()
        x = arange(100)
        y1 = cos(pi * x / 72)
        y2 = sin(pi * x / 23)
        self.plotframe.plot(x, y1, color='red')
        self.plotframe.oplot(x, y2, color='green3', marker='+')
        self.plotframe.write_message("Plot 2")

    def onPlotErr(self, event=None):
        self.ShowPlotFrame()
        npts = 81
        x = linspace(0, 40.0, npts)
        y = 0.4 * cos(x / 2.0) + random.normal(scale=0.03, size=npts)
        dy = 0.03 * (ones(npts) + random.normal(scale=0.2, size=npts))

        self.plotframe.plot(x,
                            y,
                            dy=dy,
                            color='red',
                            linewidth=0,
                            xlabel='x',
                            ylabel='y',
                            marker='o',
                            title='Plot with error bars')
        self.plotframe.write_message("Errorbars!")

    def onPlot2Axes(self, event=None):
        self.ShowPlotFrame()

        self.plotframe.plot(self.x, self.y2, color='black', style='dashed')
        self.plotframe.oplot(self.x, self.y5, color='red', side='right')
        self.plotframe.write_message("Plot with 2 axes")

    def onPlotSLog(self, event=None):
        self.ShowPlotFrame()

        self.plotframe.plot(self.x,
                            self.y4,
                            ylog_scale=True,
                            color='black',
                            style='dashed')
        self.plotframe.write_message("Semi Log Plot")

    def onPlotBig(self, event=None):
        self.ShowPlotFrame()

        t0 = time.time()
        self.plotframe.plot(self.bigx, self.bigy, marker='+', linewidth=0)
        dt = time.time() - t0
        self.plotframe.write_message(
            "Plot array with npts=%i, elapsed time=%8.3f s" %
            (len(self.bigx), dt))

    def onPlotMany_Slow(self, event=None):
        self.ShowPlotFrame()
        dlist = self.many_dlist

        t0 = time.time()
        opts = dict(title='Plot 20 traces without delay_draw',
                    show_legend=True,
                    xlabel='x')

        self.plotframe.plot(dlist[0][0], dlist[0][1], **opts)
        for tdat in dlist[1:]:
            self.plotframe.oplot(tdat[0], tdat[1])

        dt = time.time() - t0
        self.plotframe.write_message(
            "Plot 20 traces without delay_draw=True, elapsed time=%8.3f s" %
            (dt))

    def onPlotMany_Delay(self, event=None):
        self.ShowPlotFrame()
        dlist = self.many_dlist

        t0 = time.time()
        opts = dict(title='Plot 20 traces with delay_draw',
                    show_legend=True,
                    xlabel='x')

        self.plotframe.plot(dlist[0][0], dlist[0][1], delay_draw=True, **opts)
        for tdat in dlist[1:-1]:
            self.plotframe.oplot(tdat[0], tdat[1], delay_draw=True)
        self.plotframe.oplot(dlist[-1][0], dlist[-1][1])
        dt = time.time() - t0
        self.plotframe.write_message(
            "Plot 20 traces with delay_draw=True, elapsed time=%8.3f s" % (dt))

    def onPlotMany_Fast(self, event=None):
        self.ShowPlotFrame()
        dlist = self.many_dlist

        t0 = time.time()
        opts = dict(title='Plot 20 traces using plot_many()',
                    show_legend=True,
                    xlabel='x')
        self.plotframe.plot_many(dlist, **opts)

        dt = time.time() - t0
        self.plotframe.write_message(
            "Plot 20 traces with plot_many(), elapsed time=%8.3f s" % (dt))

    def report_memory(i):
        pid = os.getpid()
        if os.name == 'posix':
            mem = os.popen("ps -o rss -p %i" % pid).readlines()[1].split()[0]
        else:
            mem = 0
        return int(mem)

    def onStartTimer(self, event=None):
        self.count = 0
        self.up_count = 0
        self.n_update = 1
        self.datrange = None
        self.time0 = time.time()
        self.start_mem = self.report_memory()
        self.timer.Start(10)

    def timer_results(self):
        if (self.count < 2): return
        etime = time.time() - self.time0
        tpp = etime / max(1, self.count)
        s = "drew %i points in %8.3f s: time/point= %8.4f s" % (self.count,
                                                                etime, tpp)
        self.plotframe.write_message(s)
        self.time0 = 0
        self.count = 0
        self.datrange = None

    def onStopTimer(self, event=None):
        self.timer.Stop()
        try:
            self.timer_results()
        except:
            pass

    def onTimer(self, event):
        # print 'timer ', self.count, time.time()
        self.count += 1
        n = self.count
        if n < 2:
            self.ShowPlotFrame(do_raise=False, clear=False)
            return
        if n >= self.npts:
            self.timer.Stop()
            self.timer_results()
        elif n <= 3:
            self.plotframe.plot(self.x[:n], self.y1[:n])  # , grid=False)

        else:
            self.plotframe.update_line(0,
                                       self.x[:n],
                                       self.y1[:n],
                                       update_limits=True,
                                       draw=True)
            etime = time.time() - self.time0
            s = " %i / %i points in %8.4f s" % (n, self.npts, etime)
            self.plotframe.write_message(s)

    def OnAbout(self, event):
        dlg = wx.MessageDialog(
            self, "This sample program shows some\n"
            "examples of WXMPlot PlotFrame.\n"
            "message dialog.", "About WXMPlot test",
            wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def OnExit(self, event):
        try:
            if self.plotframe != None: self.plotframe.onExit()
        except:
            pass
        self.Destroy()
class TestFrame(wx.Frame):
    def __init__(self, parent=None, *args,**kwds):
        kwds["style"] = wx.DEFAULT_FRAME_STYLE|wx.RESIZE_BORDER|wx.TAB_TRAVERSAL
        wx.Frame.__init__(self, parent, wx.NewId(), '',
                         wx.DefaultPosition, wx.Size(-1,-1), **kwds)
        self.SetTitle("signal test")
        self.SetFont(wx.Font(12,wx.SWISS,wx.NORMAL,wx.BOLD,False))
        self.plotframe  = None
        self.Bind(wx.EVT_TIMER, self.onTimer)
        self.timer = wx.Timer(self)

        #slider
        self.sld = wx.Slider(self, value = 973, minValue = 800, maxValue = 1100, style = wx.SL_HORIZONTAL)
        self.sld.Bind(wx.EVT_SLIDER, self.OnSliderScroll)

        self.Refresh()
        self.ShowPlotFrame(do_raise=False, clear=False)
        self.onStartTimer()

    def ShowPlotFrame(self, do_raise=True, clear=True):
        "make sure plot frame is enabled, and visible"
        if self.plotframe is None:
            self.plotframe = PlotFrame(self)
            self.has_plot = False
        try:
            self.plotframe.Show()
        except PyDeadObjectError:
            self.plotframe = PlotFrame(self)
            self.plotframe.Show()

        if do_raise:
            self.plotframe.Raise()
        if clear:
            self.plotframe.panel.clear()
            self.plotframe.reset_config()

    def onStartTimer(self,event=None):
        self.count = 0
        self.timer.Start(1)

    def onTimer(self, event):
        # print 'timer ', self.count, time.time()
        self.count += 1
        N_Samples = 256 * 1024
        samples = sdr.read_samples(N_Samples)

        interval = 2048
        N = N_Samples//interval * interval
        y = samples[:N]

        y = y[:len(y//interval*interval)]
        y = y.reshape(N//interval, interval)
        y_windowed = y*np.kaiser(interval, 6)
        Y = fftshift(fft(y_windowed,axis=1),axes=1)

        Pspect = np.mean(abs(Y)*abs(Y),axis=0);
        self.plotframe.plot(np.arange(0, len(Pspect), 1),Pspect)

    def OnExit(self, event):
        try:
            if self.plotframe != None:  self.plotframe.onExit()
        except:
            pass
        self.Destroy()

    def OnSliderScroll(self, e):
        obj = e.GetEventObject()
        val = obj.GetValue()
        sdr.center_freq = val * 1e5
        print("frequency: "+str(sdr.center_freq / 10e6)+"MHz")
Example #4
0
class AD_Display(wx.Frame):
    """AreaDetector Display """
    img_attrs = ('ArrayData', 'UniqueId_RBV', 'NDimensions_RBV',
                 'ArraySize0_RBV', 'ArraySize1_RBV', 'ArraySize2_RBV',
                 'ColorMode_RBV')

    cam_attrs = ('Acquire', 'ArrayCounter', 'ArrayCounter_RBV',
                 'DetectorState_RBV', 'NumImages', 'ColorMode', 'DataType_RBV',
                 'Gain', 'AcquireTime', 'AcquirePeriod', 'ImageMode',
                 'MaxSizeX_RBV', 'MaxSizeY_RBV', 'TriggerMode', 'SizeX',
                 'SizeY', 'MinX', 'MinY')

    # plugins to enable
    enabled_plugins = ('image1', 'Over1', 'ROI1', 'JPEG1', 'TIFF1')

    stat_msg = 'Read %.1f%% of images: rate=%.1f frames/sec'

    def __init__(self,
                 prefix=None,
                 app=None,
                 scale=1.0,
                 approx_height=1200,
                 known_cameras=None):
        self.app = app
        self.ad_img = None
        self.ad_cam = None
        self.imgcount = 0
        self.prefix = prefix
        self.fname = 'AD_Image.tiff'
        self.scale = scale
        self.known_cameras = known_cameras
        self.arrsize = [0, 0, 0]
        self.imbuff = None
        self.d_size = None
        self.im_size = None
        self.colormode = 0
        self.last_update = 0.0
        self.n_img = 0
        self.imgcount_start = 0
        self.n_drawn = 0
        self.img_id = 0
        self.starttime = time.time()
        self.drawing = False
        self.lineplotter = None
        self.zoom_lims = []

        wx.Frame.__init__(self,
                          None,
                          -1,
                          "Epics Area Detector Display",
                          style=wx.DEFAULT_FRAME_STYLE)

        if known_cameras is not None:
            self.ConnectToCamera(name=self.prefix)
        else:
            self.ConnectToPV(name=self.prefix)

        self.img_w = 0
        self.img_h = 0
        self.wximage = wx.EmptyImage(
            1024, 1360)  # 1360, 1024) # approx_height, 1.5*approx_height)
        self.buildMenus()
        self.buildFrame()

    def OnLeftUp(self, event):
        if self.image is not None:
            self.image.OnLeftUp(event)

    def ConnectToCamera(self, name=None, event=None):
        if name is None:
            name = ''
        if self.known_cameras is None:
            return
        cam_names = self.known_cameras.keys()
        cam_names.sort()
        dlg = wx.SingleChoiceDialog(self,
                                    'Select Camera',
                                    caption='Select Camera',
                                    choices=cam_names)
        dlg.Raise()
        if dlg.ShowModal() == wx.ID_OK:
            cname = dlg.GetStringSelection()
            if cname in self.known_cameras:
                self.prefix = self.known_cameras[cname]
            wx.CallAfter(self.connect_pvs)
        dlg.Destroy()

    def ConnectToPV(self, event=None, name=None):
        print 'Connect To PV ', name, event
        if name is None:
            name = ''
        dlg = wx.TextEntryDialog(self,
                                 'Enter PV for Area Detector',
                                 caption='Enter PV for Area Detector',
                                 defaultValue=name)
        dlg.Raise()
        if dlg.ShowModal() == wx.ID_OK:
            self.prefix = dlg.GetValue()
            wx.CallAfter(self.connect_pvs)
        dlg.Destroy()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.BitmapFromImage(self.wximage))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    @EpicsFunction
    def CameraOff(self):
        try:
            self.ad_cam.Acquire = 0
        except:
            pass

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff" % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None,
                            message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())

        dlg.Destroy()
        if path is not None and self.data is not None:
            Image.frombuffer(self.im_mode, self.im_size, self.data.flatten(),
                             'raw', self.im_mode, 0, 1).save(path)

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.CameraOff()
        self.Destroy()

    def onAbout(self, event=None):
        msg = """Epics Image Display version 0.2

http://pyepics.github.com/epicsapps/

Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About Epics Image Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        add_menu(self, fmenu, "&Connect to Pre-defiend Camera",
                 "Connect to PV", self.ConnectToCamera)
        add_menu(self, fmenu, "&Connect to AreaDetector PV\tCtrl+O",
                 "Connect to PV", self.ConnectToPV)
        add_menu(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        add_menu(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        fmenu.AppendSeparator()
        add_menu(self, fmenu, "E&xit\tCtrl+Q", "Exit Program", self.onExit)

        omenu = wx.Menu()
        add_menu(self, omenu, "&Zoom out\tCtrl+Z", "Zoom Out", self.unZoom)
        add_menu(self, omenu, "Reset Image Counter", "Set Image Counter to 0",
                 self.onResetImageCounter)
        omenu.AppendSeparator()
        add_menu(self, omenu, "&Rotate Clockwise\tCtrl+R", "Rotate Clockwise",
                 self.onRotCW)
        add_menu(self, omenu, "Rotate CounterClockwise",
                 "Rotate Counter Clockwise", self.onRotCCW)
        add_menu(self, omenu, "Flip Up/Down\tCtrl+T", "Flip Up/Down",
                 self.onFlipV)
        add_menu(self, omenu, "Flip Left/Right\tCtrl+F", "Flip Left/Right",
                 self.onFlipH)
        omenu.AppendSeparator()

        self.CM_ZOOM = wx.NewId()
        self.CM_SHOW = wx.NewId()
        self.CM_PROF = wx.NewId()
        omenu.Append(self.CM_ZOOM, "Cursor Mode: Zoom to Box\tCtrl+B",
                     "Zoom to box by clicking and dragging", wx.ITEM_RADIO)
        omenu.Append(self.CM_SHOW, "Cursor Mode: Show X,Y\tCtrl+X",
                     "Show X,Y, Intensity Values", wx.ITEM_RADIO)
        omenu.Append(self.CM_PROF, "Cursor Mode: Line Profile\tCtrl+L",
                     "Show Line Profile", wx.ITEM_RADIO)
        self.Bind(wx.EVT_MENU, self.onCursorMode, id=self.CM_ZOOM)
        self.Bind(wx.EVT_MENU, self.onCursorMode, id=self.CM_PROF)
        self.Bind(wx.EVT_MENU, self.onCursorMode, id=self.CM_SHOW)

        hmenu = wx.Menu()
        add_menu(self, hmenu, "About", "About Epics AreadDetector Display",
                 self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")
        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onCursorMode(self, event=None):
        if event.Id == self.CM_ZOOM:
            self.image.cursor_mode = 'zoom'
        elif event.Id == self.CM_PROF:
            self.image.cursor_mode = 'profile'
        elif event.Id == self.CM_SHOW:
            self.image.cursor_mode = 'show'

    @DelayedEpicsCallback
    def onResetImageCounter(self, event=None):
        self.ad_cam.ArrayCounter = 0

    def onRotCW(self, event):
        self.image.rot90 = (self.image.rot90 + 1) % 4
        self.image.Refresh()

    def onRotCCW(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4
        self.image.Refresh()

    def onFlipV(self, event):
        self.image.flipv = not self.image.flipv
        self.image.Refresh()

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph
        self.image.Refresh()

    def buildFrame(self):
        sbar = self.CreateStatusBar(3, wx.CAPTION | wx.THICK_FRAME)
        sfont = sbar.GetFont()
        sfont.SetWeight(wx.BOLD)
        sfont.SetPointSize(10)
        sbar.SetFont(sfont)

        self.SetStatusWidths([-3, -1, -1])
        self.SetStatusText('', 0)

        sizer = wx.GridBagSizer(10, 4)
        panel = wx.Panel(self)
        self.panel = panel
        labstyle = wx.ALIGN_LEFT | wx.ALIGN_BOTTOM | wx.EXPAND
        ctrlstyle = wx.ALIGN_LEFT | wx.ALIGN_BOTTOM

        rlabstyle = wx.ALIGN_RIGHT | wx.RIGHT | wx.TOP | wx.EXPAND

        txtstyle = wx.ALIGN_LEFT | wx.ST_NO_AUTORESIZE | wx.TE_PROCESS_ENTER
        self.wids = {}
        self.wids['exptime'] = PVFloatCtrl(panel, pv=None, size=(100, -1))
        self.wids['period'] = PVFloatCtrl(panel, pv=None, size=(100, -1))
        self.wids['numimages'] = PVFloatCtrl(panel, pv=None, size=(100, -1))
        self.wids['gain'] = PVFloatCtrl(panel,
                                        pv=None,
                                        size=(100, -1),
                                        minval=0,
                                        maxval=20)

        self.wids['imagemode'] = PVEnumChoice(panel, pv=None, size=(100, -1))
        self.wids['triggermode'] = PVEnumChoice(panel, pv=None, size=(100, -1))
        self.wids['color'] = PVEnumChoice(panel, pv=None, size=(100, -1))
        self.wids['start'] = wx.Button(panel, -1, label='Start', size=(50, -1))
        self.wids['stop'] = wx.Button(panel, -1, label='Stop', size=(50, -1))

        if HAS_OVERLAY_DEVICE:
            self.wids['o1color'] = csel.ColourSelect(panel,
                                                     -1,
                                                     "",
                                                     '#FEFEFE',
                                                     size=(60, 25))
            self.wids['o1color'].Bind(csel.EVT_COLOURSELECT,
                                      Closure(self.onColor, item=1))
            self.wids['o1posx'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o1posy'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o1sizx'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o1sizy'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o1shape'] = PVEnumChoice(panel, pv=None, size=(100, -1))
            self.wids['o1use'] = PVEnumChoice(panel, pv=None, size=(50, -1))
            self.wids['o1name'] = PVTextCtrl(panel, pv=None, size=(100, -1))

            self.wids['o2color'] = csel.ColourSelect(panel,
                                                     -1,
                                                     "",
                                                     '#FEFEFE',
                                                     size=(60, 25))
            self.wids['o2color'].Bind(csel.EVT_COLOURSELECT,
                                      Closure(self.onColor, item=2))
            self.wids['o2posx'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o2posy'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o2sizx'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o2sizy'] = PVFloatCtrl(panel,
                                              pv=None,
                                              size=(50, -1),
                                              minval=0)
            self.wids['o2shape'] = PVEnumChoice(panel, pv=None, size=(100, -1))
            self.wids['o2use'] = PVEnumChoice(panel, pv=None, size=(50, -1))
            self.wids['o2name'] = PVTextCtrl(panel, pv=None, size=(100, -1))

        for key in ('start', 'stop'):
            self.wids[key].Bind(wx.EVT_BUTTON, Closure(self.onEntry, key=key))

        self.wids['zoomsize'] = wx.StaticText(panel,
                                              -1,
                                              size=(250, -1),
                                              style=txtstyle)
        self.wids['fullsize'] = wx.StaticText(panel,
                                              -1,
                                              size=(250, -1),
                                              style=txtstyle)

        def txt(label, size=100):
            return wx.StaticText(panel,
                                 label=label,
                                 size=(size, -1),
                                 style=labstyle)

        def lin(len=30, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)

        sizer.Add(txt(' '), (0, 0), (1, 1), labstyle)
        sizer.Add(txt('Image Mode '), (1, 0), (1, 1), labstyle)
        sizer.Add(self.wids['imagemode'], (1, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('# Images '), (2, 0), (1, 1), labstyle)
        sizer.Add(self.wids['numimages'], (2, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('Trigger Mode '), (3, 0), (1, 1), labstyle)
        sizer.Add(self.wids['triggermode'], (3, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('Period '), (4, 0), (1, 1), labstyle)
        sizer.Add(self.wids['period'], (4, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('Exposure Time '), (5, 0), (1, 1), labstyle)
        sizer.Add(self.wids['exptime'], (5, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('Gain '), (6, 0), (1, 1), labstyle)
        sizer.Add(self.wids['gain'], (6, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('Color Mode'), (7, 0), (1, 1), labstyle)
        sizer.Add(self.wids['color'], (7, 1), (1, 2), ctrlstyle)

        sizer.Add(txt('Acquire '), (9, 0), (1, 1), labstyle)

        sizer.Add(self.wids['start'], (9, 1), (1, 1), ctrlstyle)
        sizer.Add(self.wids['stop'], (9, 2), (1, 1), ctrlstyle)

        sizer.Add(self.wids['fullsize'], (12, 0), (1, 3), labstyle)
        sizer.Add(self.wids['zoomsize'], (13, 0), (1, 3), labstyle)

        sizer.Add(lin(75), (15, 0), (1, 3), labstyle)

        if HAS_OVERLAY_DEVICE:
            ir = 16
            sizer.Add(txt('Overlay 1:'), (ir + 0, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o1use'], (ir + 0, 1), (1, 2), ctrlstyle)
            sizer.Add(txt('Shape:'), (ir + 1, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o1shape'], (ir + 1, 1), (1, 2), ctrlstyle)
            sizer.Add(txt('Name:'), (ir + 2, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o1name'], (ir + 2, 1), (1, 2), ctrlstyle)

            sizer.Add(txt('Position '), (ir + 3, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o1posx'], (ir + 3, 1), (1, 1), ctrlstyle)
            sizer.Add(self.wids['o1posy'], (ir + 3, 2), (1, 1), ctrlstyle)
            sizer.Add(txt('Size '), (ir + 4, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o1sizx'], (ir + 4, 1), (1, 1), ctrlstyle)
            sizer.Add(self.wids['o1sizy'], (ir + 4, 2), (1, 1), ctrlstyle)
            sizer.Add(txt('Color '), (ir + 5, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o1color'], (ir + 5, 1), (1, 2), ctrlstyle)
            sizer.Add(lin(75), (ir + 6, 0), (1, 3), labstyle)

            ir = ir + 7
            sizer.Add(txt('Overlay 1:'), (ir + 0, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o2use'], (ir + 0, 1), (1, 2), ctrlstyle)
            sizer.Add(txt('Shape:'), (ir + 1, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o2shape'], (ir + 1, 1), (1, 2), ctrlstyle)
            sizer.Add(txt('Name:'), (ir + 2, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o2name'], (ir + 2, 1), (1, 2), ctrlstyle)

            sizer.Add(txt('Position '), (ir + 3, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o2posx'], (ir + 3, 1), (1, 1), ctrlstyle)
            sizer.Add(self.wids['o2posy'], (ir + 3, 2), (1, 1), ctrlstyle)
            sizer.Add(txt('Size '), (ir + 4, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o2sizx'], (ir + 4, 1), (1, 1), ctrlstyle)
            sizer.Add(self.wids['o2sizy'], (ir + 4, 2), (1, 1), ctrlstyle)
            sizer.Add(txt('Color '), (ir + 5, 0), (1, 1), labstyle)
            sizer.Add(self.wids['o2color'], (ir + 5, 1), (1, 2), ctrlstyle)
            sizer.Add(lin(75), (ir + 6, 0), (1, 3), labstyle)

        self.image = ImageView(self,
                               size=(1360, 1024),
                               onzoom=self.onZoom,
                               onprofile=self.onProfile,
                               onshow=self.onShowXY)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT | wx.GROW | wx.ALL, 5)
        mainsizer.Add(self.image, 1, wx.CENTER | wx.GROW | wx.ALL, 5)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)

        try:
            self.SetIcon(wx.Icon(ICON_FILE, wx.BITMAP_TYPE_ICO))
        except:
            pass

        self.RefreshImage()
        wx.CallAfter(self.connect_pvs)

    def messag(self, s, panel=0):
        """write a message to the Status Bar"""
        wx.CallAfter(Closure(self.SetStatusText, text=s, number=panel))
        # self.SetStatusText(s, panel)

    @EpicsFunction
    def unZoom(self, event=None, full=False):
        if self.zoom_lims is None or full:
            self.zoom_lims = []

        if len(self.zoom_lims) == 0:
            xmin, ymin = 0, 0
            width = self.ad_cam.MaxSizeX_RBV
            height = self.ad_cam.MaxSizeY_RBV
            self.zoom_lims = []
        else:
            xmin, ymin, width, height = self.zoom_lims.pop()
            if (self.ad_cam.MinX == xmin and self.ad_cam.MinY == ymin
                    and self.ad_cam.SizeX == width
                    and self.ad_cam.SizeY == height):
                try:
                    xmin, ymin, width, height = self.zoom_lims.pop()
                except:
                    xmin, ymin = 0, 0
                    width = self.ad_cam.MaxSizeX_RBV
                    height = self.ad_cam.MaxSizeY_RBV

        self.ad_cam.MinX = xmin
        self.ad_cam.MinY = ymin
        self.ad_cam.SizeX = width
        self.ad_cam.SizeY = height
        self.zoom_lims.append((xmin, ymin, width, height))
        time.sleep(0.05)
        self.showZoomsize()
        if self.ad_cam.Acquire == 0 and self.im_size is not None:
            self.img_w = width
            self.img_h = height
            try:
                if self.colormode == 2:
                    self.data.shape = [self.im_size[1], self.im_size[0], 3]
                    zdata = self.data[ymin:ymin + height, xmin:xmin + width, :]
                else:
                    self.data.shape = self.im_size[1], self.im_size[0]
                    zdata = self.data[ymin:ymin + height, xmin:xmin + width]
            except ValueError:
                pass
            self.data = zdata  #self.data.flatten()
            self.im_size = (width, height)
            # print zdata.shape, width, height, self.im_mode
            self.DatatoImage()  # zdata, (width, height), self.im_mode)

        self.RefreshImage()
        self.image.Refresh()

    @EpicsFunction
    def onColor(self, event=None, item=None):
        if HAS_OVERLAY_DEVICE:
            color = event.GetValue()
            over = self.ad_overlays[item - 1]
            over.Red = color[0]
            over.Green = color[1]
            over.Blue = color[2]

    @EpicsFunction
    def showZoomsize(self):
        try:
            msg = 'Showing:  %i x %i pixels' % (self.ad_cam.SizeX,
                                                self.ad_cam.SizeY)
            self.wids['zoomsize'].SetLabel(msg)
        except:
            pass

    @EpicsFunction
    def onZoom(self, x0, y0, x1, y1):
        width = self.ad_cam.SizeX
        height = self.ad_cam.SizeY
        xmin = max(0, int(self.ad_cam.MinX + x0 * width))
        ymin = max(0, int(self.ad_cam.MinY + y0 * height))

        width = int(x1 * width)
        height = int(y1 * height)
        if width < 2 or height < 2:
            return
        self.ad_cam.MinX = xmin
        self.ad_cam.MinY = ymin
        self.ad_cam.SizeX = width
        self.ad_cam.SizeY = height
        if self.zoom_lims is None:
            self.zoom_lims = []
        self.zoom_lims.append((xmin, ymin, width, height))

        time.sleep(0.05)
        self.showZoomsize()

        if self.ad_cam.Acquire == 0:
            self.img_w = width
            self.img_h = height
            if self.colormode == 2:
                self.data.shape = [self.im_size[1], self.im_size[0], 3]
                zdata = self.data[ymin:ymin + height, xmin:xmin + width, :]
            else:
                self.data.shape = self.im_size[1], self.im_size[0]
                zdata = self.data[ymin:ymin + height, xmin:xmin + width]
            self.data = zdata  #. flatten()
            self.im_size = (width, height)
            self.DatatoImage()
        self.image.Refresh()

    def DatatoImage(self):  #,  data, size, mode):
        """convert raw data to image"""
        #x = debugtime()

        width, height = self.im_size
        d_size = (int(width * self.scale), int(height * self.scale))
        data = self.data.flatten()
        #x.add('flatten')
        if self.imbuff is None or d_size != self.d_size or self.im_mode == 'L':
            try:
                self.imbuff = Image.frombuffer(self.im_mode, self.im_size,
                                               data, 'raw', self.im_mode, 0, 1)
                #x.add('made image')
            except:
                return
        self.d_size = d_size = (int(width * self.scale),
                                int(height * self.scale))
        if self.imbuff.size != d_size:
            self.imbuff = self.imbuff.resize(d_size)
            #x.add('resized imbuff')

        if self.wximage.GetSize() != self.imbuff.size:
            self.wximage = wx.EmptyImage(d_size[0], d_size[1])
        #x.add('created wximage %s  ' % (repr(self.wximage.GetSize())))
        if self.im_mode == 'L':
            self.wximage.SetData(self.imbuff.convert('RGB').tostring())
        elif self.im_mode == 'RGB':
            data.shape = (3, width, height)
            self.wximage = wx.ImageFromData(width, height, data)
        #x.add('set wx image wximage : %i, %i ' % d_size)
        self.image.SetValue(self.wximage)
        #x.add('set image value')
        #x.show()

    def onProfile(self, x0, y0, x1, y1):
        width = self.ad_cam.SizeX
        height = self.ad_cam.SizeY

        x0, y0 = int(x0 * width), int(y0 * height)
        x1, y1 = int(x1 * width), int(y1 * height)
        dx, dy = abs(x1 - x0), abs(y1 - y0)

        if dx < 2 and dy < 2:
            return
        outdat = []
        if self.colormode == 2:
            self.data.shape = (self.im_size[1], self.im_size[0], 3)
        else:
            self.data.shape = self.im_size[1], self.im_size[0]

        if dy > dx:
            _y0 = min(int(y0), int(y1 + 0.5))
            _y1 = max(int(y0), int(y1 + 0.5))

            for iy in range(_y0, _y1):
                ix = int(x0 + (iy - int(y0)) * (x1 - x0) / (y1 - y0))
                outdat.append((ix, iy))
        else:
            _x0 = min(int(x0), int(x1 + 0.5))
            _x1 = max(int(x0), int(x1 + 0.5))
            for ix in range(_x0, _x1):
                iy = int(y0 + (ix - int(x0)) * (y1 - y0) / (x1 - x0))
                outdat.append((ix, iy))

        if self.lineplotter is None:
            self.lineplotter = PlotFrame(self, title='Image Profile')
        else:
            try:
                self.lineplotter.Raise()
            except PyDeadObjectError:
                self.lineplotter = PlotFrame(self, title='Image Profile')

        if self.colormode == 2:
            x, y, r, g, b = [], [], [], [], []
            for ix, iy in outdat:
                x.append(ix)
                y.append(iy)
                r.append(self.data[iy, ix, 0])
                g.append(self.data[iy, ix, 1])
                b.append(self.data[iy, ix, 2])
            xlabel = 'Pixel (x)'
            if dy > dx:
                x = y
                xlabel = 'Pixel (y)'
            self.lineplotter.plot(x,
                                  r,
                                  color='red',
                                  label='red',
                                  xlabel=xlabel,
                                  ylabel='Intensity',
                                  title='Image %i' %
                                  self.ad_cam.ArrayCounter_RBV)
            self.lineplotter.oplot(x, g, color='green', label='green')
            self.lineplotter.oplot(x, b, color='blue', label='blue')

        else:
            x, y, z = [], [], []
            for ix, iy in outdat:
                x.append(ix)
                y.append(iy)
                z.append(self.data[iy, ix])
            xlabel = 'Pixel (x)'
            if dy > dx:
                x = y
            xlabel = 'Pixel (y)'
            self.lineplotter.plot(x,
                                  z,
                                  color='k',
                                  xlabel=xlabel,
                                  ylabel='Intensity',
                                  title='Image %i' %
                                  self.ad_cam.ArrayCounter_RBV)
        self.lineplotter.Show()
        self.lineplotter.Raise()

    def onShowXY(self, xval, yval):
        ix = max(0, int(xval * self.ad_cam.SizeX))
        iy = max(0, int(yval * self.ad_cam.SizeY))

        if self.colormode == 2:
            self.data.shape = (self.im_size[1], self.im_size[0], 3)
            ival = tuple(self.data[iy, ix, :])
            smsg = 'Pixel %i, %i, (R, G, B) = %s' % (ix, iy, repr(ival))
        else:
            self.data.shape = self.im_size[1], self.im_size[0]
            ival = self.data[iy, ix]
            smsg = 'Pixel %i, %i, Intensity = %i' % (ix, iy, ival)

        self.messag(smsg, panel=1)

    def onName(self, evt=None, **kws):
        if evt is None:
            return
        s = evt.GetString()
        s = str(s).strip()
        if s.endswith(':image1:'): s = s[:-8]
        if s.endswith(':cam1:'): s = s[:-6]
        if s.endswith(':'): s = s[:-1]
        self.prefix = s
        self.connect_pvs()

    @EpicsFunction
    def onEntry(self, evt=None, key='name', **kw):
        if evt is None:
            return
        if key == 'start':
            self.n_img = 0
            self.n_drawn = 0
            self.starttime = time.time()
            self.imgcount_start = self.ad_cam.ArrayCounter_RBV
            self.ad_cam.Acquire = 1
        elif key == 'stop':
            self.ad_cam.Acquire = 0
        elif key == 'unzoom':
            self.unZoom()
        else:
            print 'unknown Entry ? ', key

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return

        try:
            self.ad_cam.Acquire = 0
        except:
            pass

        if self.prefix.endswith(':'):
            self.prefix = self.prefix[:-1]
        if self.prefix.endswith(':image1'):
            self.prefix = self.prefix[:-7]
        if self.prefix.endswith(':cam1'):
            self.prefix = self.prefix[:-5]

        if verbose:
            self.messag('Connecting to AD %s' % self.prefix)
        self.ad_img = epics.Device(self.prefix + ':image1:',
                                   delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + ':cam1:',
                                   delim='',
                                   attrs=self.cam_attrs)
        self.ad_overlays = []
        if HAS_OVERLAY_DEVICE:
            for ix in (1, 2):
                pvn = '%s:Over1:%i:' % (self.prefix, ix)
                self.ad_overlays.append(AD_OverlayPlugin(pvn))

        time.sleep(0.010)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.messag('Warning:  Camera seems to not be connected!')
                return
        if verbose:
            self.messag('Connected to AD %s' % self.prefix)

        self.SetTitle("Epics Image Display: %s" % self.prefix)

        self.wids['color'].SetPV(self.ad_cam.PV('ColorMode'))
        self.wids['exptime'].SetPV(self.ad_cam.PV('AcquireTime'))
        self.wids['period'].SetPV(self.ad_cam.PV('AcquirePeriod'))
        self.wids['gain'].SetPV(self.ad_cam.PV('Gain'))
        self.wids['numimages'].SetPV(self.ad_cam.PV('NumImages'))
        self.wids['imagemode'].SetPV(self.ad_cam.PV('ImageMode'))
        self.wids['triggermode'].SetPV(self.ad_cam.PV('TriggerMode'))

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        if HAS_OVERLAY_DEVICE:
            over = self.ad_overlays[0]
            c1 = (over.Red, over.Green, over.Blue)
            self.wids['o1color'].SetColour(hexcolor(c1))
            self.wids['o1posx'].SetPV(over.PV('PositionX'))
            self.wids['o1posx'].SetMax(sizex)
            self.wids['o1posy'].SetPV(over.PV('PositionY'))
            self.wids['o1posy'].SetMax(sizey)
            self.wids['o1sizx'].SetPV(over.PV('SizeX'))
            self.wids['o1sizx'].SetMax(sizex)
            self.wids['o1sizy'].SetPV(over.PV('SizeY'))
            self.wids['o1sizy'].SetMax(sizey)
            self.wids['o1shape'].SetPV(over.PV('Shape'))
            self.wids['o1name'].SetPV(over.PV('Name'))
            self.wids['o1use'].SetPV(over.PV('Use'))

            over = self.ad_overlays[1]
            c1 = (over.Red, over.Green, over.Blue)
            self.wids['o2color'].SetColour(hexcolor(c1))
            self.wids['o2posx'].SetPV(over.PV('PositionX'))
            self.wids['o2posx'].SetMax(sizex)
            self.wids['o2posy'].SetPV(over.PV('PositionY'))
            self.wids['o2posy'].SetMax(sizey)
            self.wids['o2sizx'].SetPV(over.PV('SizeX'))
            self.wids['o2sizx'].SetMax(sizex)
            self.wids['o2sizy'].SetPV(over.PV('SizeY'))
            self.wids['o2sizy'].SetMax(sizey)
            self.wids['o2shape'].SetPV(over.PV('Shape'))
            self.wids['o2name'].SetPV(over.PV('Name'))
            self.wids['o2use'].SetPV(over.PV('Use'))

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel % (sizex, sizey)
        except:
            sizelabel = sizelabel % (0, 0)

        self.wids['fullsize'].SetLabel(sizelabel)
        self.showZoomsize()

        self.ad_img.add_callback('ArrayCounter_RBV', self.onNewImage)
        self.ad_img.add_callback('ArraySize0_RBV', self.onProperty, dim=0)
        self.ad_img.add_callback('ArraySize1_RBV', self.onProperty, dim=1)
        self.ad_img.add_callback('ArraySize2_RBV', self.onProperty, dim=2)
        self.ad_img.add_callback('ColorMode_RBV', self.onProperty, dim='color')
        self.ad_cam.add_callback('DetectorState_RBV', self.onDetState)

        epics.caput("%s:cam1:ArrayCallbacks" % self.prefix, 1)
        for p in self.enabled_plugins:
            epics.caput("%s:%s:EnableCallbacks" % (self.prefix, p), 1)
        epics.caput("%s:JPEG1:NDArrayPort" % self.prefix, "OVER1")
        epics.caput("%s:TIFF1:NDArrayPort" % self.prefix, "OVER1")
        epics.caput("%s:image1:NDArrayPort" % self.prefix, "OVER1")

        self.ad_cam.Acquire = 1
        self.GetImageSize()
        self.unZoom()

        epics.poll()
        self.RefreshImage()

    @EpicsFunction
    def GetImageSize(self):
        self.arrsize = [1, 1, 1]
        self.arrsize[0] = self.ad_img.ArraySize0_RBV
        self.arrsize[1] = self.ad_img.ArraySize1_RBV
        self.arrsize[2] = self.ad_img.ArraySize2_RBV
        self.colormode = self.ad_img.ColorMode_RBV

        self.img_w = self.arrsize[1]
        self.img_h = self.arrsize[0]
        if self.colormode == 2:
            self.img_w = self.arrsize[2]
            self.img_h = self.arrsize[1]

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.messag(char_value, panel=1)

    @DelayedEpicsCallback
    def onProperty(self, pvname=None, value=None, dim=None, **kw):
        if dim == 'color':
            self.colormode = value
        else:
            self.arrsize[dim] = value

    @DelayedEpicsCallback
    def onNewImage(self, pvname=None, value=None, **kw):
        if value != self.img_id:
            self.img_id = value
            if not self.drawing:
                self.drawing = True
                self.RefreshImage()

    @EpicsFunction
    def RefreshImage(self, pvname=None, **kws):
        try:
            wx.Yield()
        except:
            pass
        d = debugtime()

        if self.ad_img is None or self.ad_cam is None:
            return
        imgdim = self.ad_img.NDimensions_RBV
        imgcount = self.ad_cam.ArrayCounter_RBV
        now = time.time()
        if (imgcount == self.imgcount or abs(now - self.last_update) < 0.025):
            self.drawing = False
            return
        d.add('refresh img start')
        self.imgcount = imgcount
        self.drawing = True
        self.n_drawn += 1
        self.n_img = imgcount - self.imgcount_start
        #print 'ImgCount, n_drawn: ', imgcount, self.n_img, self.n_drawn

        self.last_update = time.time()
        self.image.can_resize = False

        xmin = self.ad_cam.MinX
        ymin = self.ad_cam.MinY
        width = self.ad_cam.SizeX
        height = self.ad_cam.SizeY

        arraysize = self.arrsize[0] * self.arrsize[1]
        if imgdim == 3:
            arraysize = arraysize * self.arrsize[2]
        if not self.ad_img.PV('ArrayData').connected:
            self.drawing = False
            return

        d.add('refresh img before raw get %i' % arraysize)
        rawdata = self.ad_img.PV('ArrayData').get(count=arraysize)
        d.add('refresh img after raw get')
        im_mode = 'L'
        im_size = (self.arrsize[0], self.arrsize[1])

        if self.colormode == 2:
            im_mode = 'RGB'
            im_size = [self.arrsize[1], self.arrsize[2]]
        if (self.colormode == 0 and isinstance(rawdata, np.ndarray)
                and rawdata.dtype != np.uint8):
            im_mode = 'I'
            rawdata = rawdata.astype(np.uint32)

        d.add('refresh img before msg')
        self.messag(' Image # %i ' % self.ad_cam.ArrayCounter_RBV, panel=2)
        d.add('refresh img before get image size')
        self.GetImageSize()

        self.im_size = im_size
        self.im_mode = im_mode
        self.data = rawdata
        d.add('refresh img before data to image')
        self.DatatoImage()
        d.add('refresh img after data to image')
        self.image.can_resize = True
        nmissed = max(0, self.n_img - self.n_drawn)

        delt = time.time() - self.starttime
        percent_drawn = self.n_drawn * 100 / (self.n_drawn + nmissed)
        smsg = self.stat_msg % (percent_drawn, self.n_drawn / delt)
        self.messag(smsg, panel=0)

        self.drawing = False
        d.add('refresh img done')
Example #5
0
class ADFrame(wx.Frame):
    """
    AreaDetector Display Frame
    """
    def __init__(self, configfile=None):
        wx.Frame.__init__(self,
                          None,
                          -1,
                          'AreaDetector Viewer',
                          style=wx.DEFAULT_FRAME_STYLE)

        if configfile is None:
            wcard = 'Detector Config Files (*.yaml)|*.yaml|All files (*.*)|*.*'
            configfile = FileOpen(self,
                                  "Read Detector Configuration File",
                                  default_file='det.yaml',
                                  wildcard=wcard)
        if configfile is None:
            sys.exit()

        self.config = read_adconfig(configfile)
        self.prefix = self.config['general']['prefix']
        self.fname = self.config['general']['name']
        self.colormode = self.config['general']['colormode'].lower()
        self.cam_attrs = self.config['cam_attributes']
        self.img_attrs = self.config['img_attributes']
        self.fsaver = self.config['general']['filesaver']

        self.SetTitle(self.config['general']['title'])
        self.scandb = None
        if ScanDB is not None:
            self.scandb = ScanDB()
            if self.scandb.engine is None:  # not connected to running scandb server
                self.scandb = None

        self.calib = None
        self.ad_img = None
        self.ad_cam = None
        self.lineplotter = None
        self.integrator = None
        self.int_panel = None
        self.int_lastid = None
        self.contrast_levels = None
        self.thumbnail = None

        self.buildMenus()
        self.buildFrame()

    def buildFrame(self):
        self.SetFont(Font(11))

        sbar = self.CreateStatusBar(3, wx.CAPTION)
        self.SetStatusWidths([-1, -1, -1])
        self.SetStatusText('', 0)

        sizer = wx.GridBagSizer(3, 3)
        panel = self.panel = wx.Panel(self)
        pvpanel = PVConfigPanel(panel, self.prefix, self.config['controls'])

        wsize = (100, -1)
        lsize = (250, -1)

        start_btn = wx.Button(panel, label='Start', size=wsize)
        stop_btn = wx.Button(panel, label='Stop', size=wsize)
        start_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='start'))
        stop_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='stop'))

        self.contrast = ContrastControl(panel,
                                        callback=self.set_contrast_level)
        self.imagesize = wx.StaticText(panel,
                                       label='? x ?',
                                       size=(150, 30),
                                       style=txtstyle)

        def lin(len=200, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)

        irow = 0
        sizer.Add(pvpanel, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(start_btn, (irow, 0), (1, 1), labstyle)
        sizer.Add(stop_btn, (irow, 1), (1, 1), labstyle)

        if self.config['general'].get('show_free_run', False):
            free_btn = wx.Button(panel, label='Free Run', size=wsize)
            free_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='free'))
            irow += 1
            sizer.Add(free_btn, (irow, 0), (1, 2), labstyle)

        irow += 1
        sizer.Add(lin(200, wid=4), (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(self.imagesize, (irow, 0), (1, 3), labstyle)

        if self.colormode.startswith('mono'):
            self.cmap_choice = wx.Choice(panel,
                                         size=(80, -1),
                                         choices=self.config['colormaps'])
            self.cmap_choice.SetSelection(0)
            self.cmap_choice.Bind(wx.EVT_CHOICE, self.onColorMap)
            self.cmap_reverse = wx.CheckBox(panel,
                                            label='Reverse',
                                            size=(60, -1))
            self.cmap_reverse.Bind(wx.EVT_CHECKBOX, self.onColorMap)

            irow += 1
            sizer.Add(wx.StaticText(panel, label='Color Map: '), (irow, 0),
                      (1, 1), labstyle)

            sizer.Add(self.cmap_choice, (irow, 1), (1, 1), labstyle)
            sizer.Add(self.cmap_reverse, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.contrast.label, (irow, 0), (1, 1), labstyle)
        sizer.Add(self.contrast.choice, (irow, 1), (1, 1), labstyle)

        if self.config['general']['show_1dintegration']:
            self.show1d_btn = wx.Button(panel,
                                        label='Show 1D Integration',
                                        size=(200, -1))
            self.show1d_btn.Bind(wx.EVT_BUTTON, self.onShowIntegration)
            self.show1d_btn.Disable()
            irow += 1
            sizer.Add(self.show1d_btn, (irow, 0), (1, 2), labstyle)

        if self.config['general']['show_thumbnail']:
            t_size = self.config['general'].get('thumbnail_size', 100)

            self.thumbnail = ThumbNailImagePanel(panel,
                                                 imgsize=t_size,
                                                 size=(350, 350),
                                                 motion_writer=partial(
                                                     self.write, panel=0))

            label = wx.StaticText(panel,
                                  label='Thumbnail size (pixels): ',
                                  size=(200, -1),
                                  style=txtstyle)

            self.thumbsize = FloatSpin(panel,
                                       value=100,
                                       min_val=10,
                                       increment=5,
                                       action=self.onThumbSize,
                                       size=(150, -1),
                                       style=txtstyle)

            irow += 1
            sizer.Add(label, (irow, 0), (1, 1), labstyle)
            sizer.Add(self.thumbsize, (irow, 1), (1, 1), labstyle)
            irow += 1
            sizer.Add(self.thumbnail, (irow, 0), (1, 2), labstyle)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        # image panel
        self.image = ADMonoImagePanel(
            self,
            prefix=self.prefix,
            rot90=self.config['general']['default_rotation'],
            size=(750, 750),
            writer=partial(self.write, panel=1),
            thumbnail=self.thumbnail,
            motion_writer=partial(self.write, panel=2))

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT | wx.GROW | wx.ALL)
        mainsizer.Add(self.image, 1, wx.CENTER | wx.GROW | wx.ALL)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)
        iconfile = self.config['general'].get('iconfile', None)
        if iconfile is None or not os.path.exists(iconfile):
            iconfile = DEFAULT_ICONFILE
        try:
            self.SetIcon(wx.Icon(iconfile, wx.BITMAP_TYPE_ICO))
        except:
            pass
        self.connect_pvs()

    def onThumbSize(self, event=None):
        self.thumbnail.imgsize = int(self.thumbsize.GetValue())

    def onColorMap(self, event=None):
        cmap_name = self.cmap_choice.GetStringSelection()
        if self.cmap_reverse.IsChecked():
            cmap_name = cmap_name + '_r'
        self.image.colormap = getattr(colormap, cmap_name)
        self.image.Refresh()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.Bitmap(self.image.GrabWxImage()))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    def onReadCalibFile(self, event=None):
        "read calibration file"
        wcards = "Poni Files(*.poni)|*.poni|All files (*.*)|*.*"
        dlg = wx.FileDialog(None,
                            message='Read Calibration File',
                            defaultDir=os.getcwd(),
                            wildcard=wcards,
                            style=wx.FD_OPEN)
        ppath = None
        if dlg.ShowModal() == wx.ID_OK:
            ppath = os.path.abspath(dlg.GetPath())

        if os.path.exists(ppath):
            self.setup_calibration(ppath)

    def setup_calibration(self, ponifile):
        """set up calibration from PONI file"""
        calib = read_poni(ponifile)
        # if self.image.rot90 in (1, 3):
        #     calib['rot3'] = np.pi/2.0
        self.calib = calib
        if HAS_PYFAI:
            self.integrator = AzimuthalIntegrator(**calib)
            self.show1d_btn.Enable()
        else:
            self.write('Warning: PyFAI is not installed')

        if self.scandb is not None:
            _, calname = os.path.split(ponifile)
            self.scandb.set_detectorconfig(calname, json.dumps(calib))
            self.scandb.set_info('xrd_calibration', calname)

    def onShowIntegration(self, event=None):

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()

    def onAutoIntegration(self, event=None):
        if not event.IsChecked():
            self.int_timer.Stop()
            return

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()
        self.int_timer.Start(500)

    def show_1dpattern(self, init=False):
        if self.calib is None or not HAS_PYFAI:
            return

        img = self.ad_img.PV('ArrayData').get()

        h, w = self.image.GetImageSize()
        img.shape = (w, h)

        # may need to trim outer pixels (int1d_trimx/int1d_trimy in config)
        xstride = 1
        if self.config['general'].get('int1d_flipx', False):
            xstride = -1

        xslice = slice(None, None, xstride)
        trimx = int(self.config['general'].get('int1d_trimx', 0))
        if trimx != 0:
            xslice = slice(trimx * xstride, -trimx * xstride, xstride)

        ystride = 1
        if self.config['general'].get('int1d_flipy', True):
            ystride = -1

        yslice = slice(None, None, ystride)
        trimy = int(self.config['general'].get('int1d_trimy', 0))
        if trimy > 0:
            yslice = slice(trimy * ystride, -trimy * ystride, ystride)

        img = img[yslice, xslice]

        img_id = self.ad_cam.ArrayCounter_RBV
        q, xi = self.integrator.integrate1d(img,
                                            2048,
                                            unit='q_A^-1',
                                            correctSolidAngle=True,
                                            polarization_factor=0.999)
        if init:
            self.int_panel.plot(q,
                                xi,
                                xlabel=r'$Q (\rm\AA^{-1})$',
                                marker='+',
                                title='Image %d' % img_id)
            self.int_panel.Raise()
            self.int_panel.Show()
        else:
            self.int_panel.update_line(0, q, xi, draw=True)
            self.int_panel.set_title('Image %d' % img_id)

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff" % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None,
                            message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.FD_SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())

        root, fname = os.path.split(path)
        epics.caput("%s%sFileName" % self.prefix, self.fsaver, fname)
        epics.caput("%s%sFileWriteMode" % self.prefix, self.fsaver, 0)
        time.sleep(0.05)
        epics.caput("%s%sWriteFile" % self.prefix, self.fsaver, 1)
        time.sleep(0.05)

        file_pv = "%s%sFullFileName_RBV" % (self.prefix, self.prefix)
        print("Saved image File ", epics.caget(file_pv, as_string=True))

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.Destroy()

    def onAbout(self, event=None):
        msg = """areaDetector Display version 0.2
Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About areaDetector Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        MenuItem(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        MenuItem(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        MenuItem(self, fmenu, "Read Calibration File", "Read PONI Calibration",
                 self.onReadCalibFile)
        fmenu.AppendSeparator()
        MenuItem(self, fmenu, "E&xit\tCtrl+Q", "Exit Program", self.onExit)

        omenu = wx.Menu()
        MenuItem(self, omenu, "&Rotate CCW\tCtrl+R",
                 "Rotate Counter Clockwise", self.onRot90)
        MenuItem(self, omenu, "Flip Up/Down\tCtrl+T", "Flip Up/Down",
                 self.onFlipV)
        MenuItem(self, omenu, "Flip Left/Right\tCtrl+F", "Flip Left/Right",
                 self.onFlipH)
        MenuItem(self, omenu, "Reset Rotations and Flips", "Reset",
                 self.onResetRotFlips)
        omenu.AppendSeparator()

        hmenu = wx.Menu()
        MenuItem(self, hmenu, "About", "About areaDetector Display",
                 self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")

        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onResetRotFlips(self, event):
        self.image.rot90 = 0
        self.image.flipv = self.image.fliph = False

    def onRot90(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4

    def onFlipV(self, event):
        self.image.flipv = not self.image.flipv

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph

    def set_contrast_level(self, contrast_level=0):
        self.image.contrast_levels = [contrast_level, 100.0 - contrast_level]
        self.image.Refresh()

    def write(self, s, panel=0):
        """write a message to the Status Bar"""
        self.SetStatusText(text=s, number=panel)

    @EpicsFunction
    def onButton(self, event=None, key='free'):
        key = key.lower()
        if key.startswith('free'):
            ftime = self.config['general']['free_run_time']
            self.image.restart_fps_counter()
            self.ad_cam.AcquireTime = ftime
            self.ad_cam.AcquirePeriod = ftime
            self.ad_cam.NumImages = int((3 * 86400.) / ftime)
            self.ad_cam.Acquire = 1
        elif key.startswith('start'):
            self.image.restart_fps_counter()
            self.ad_cam.Acquire = 1
        elif key.startswith('stop'):
            self.ad_cam.Acquire = 0

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return
        self.write('Connecting to areaDetector %s' % self.prefix)

        self.ad_img = epics.Device(self.prefix + 'image1:',
                                   delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + 'cam1:',
                                   delim='',
                                   attrs=self.cam_attrs)

        if self.config['general']['use_filesaver']:
            epics.caput("%s%sEnableCallbacks" % (self.prefix, self.fsaver), 1)
            epics.caput("%s%sAutoSave" % (self.prefix, self.fsaver), 0)
            epics.caput("%s%sAutoIncrement" % (self.prefix, self.fsaver), 0)
            epics.caput("%s%sFileWriteMode" % (self.prefix, self.fsaver), 0)

        time.sleep(0.002)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.write('Warning: detector seems to not be connected!')
                return
        if verbose:
            self.write('Connected to detector %s' % self.prefix)

        self.SetTitle("Epics areaDetector Display: %s" % self.prefix)

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel % (sizex, sizey)
        except:
            sizelabel = sizelabel % (0, 0)

        self.imagesize.SetLabel(sizelabel)

        self.ad_cam.add_callback('DetectorState_RBV', self.onDetState)
        self.contrast.set_level_str('0.01')

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.write(char_value, panel=0)
Example #6
0
class EigerFrame(wx.Frame):
    """AreaDetector Display """

    img_attrs = ('ArrayData', 'UniqueId_RBV')
    cam_attrs = ('Acquire', 'DetectorState_RBV', 'ArrayCounter',
                 'ArrayCounter_RBV', 'ThresholdEnergy', 'ThresholdEnergy_RBV',
                 'PhotonEnergy', 'PhotonEnergy_RBV', 'NumImages',
                 'NumImages_RBV', 'AcquireTime', 'AcquireTime_RBV',
                 'AcquirePeriod', 'AcquirePeriod_RBV', 'TriggerMode',
                 'TriggerMode_RBV')

    # plugins to enable
    enabled_plugins = ('image1', 'Over1', 'ROI1', 'JPEG1', 'TIFF1')

    def __init__(self, prefix=None, url=None, scale=1.0):
        self.ad_img = None
        self.ad_cam = None
        if prefix is None:
            dlg = SavedParameterDialog(label='Detector Prefix',
                                       title='Connect to Eiger Detector',
                                       configfile='.ad_eigerdisplay.dat')
            res = dlg.GetResponse()
            dlg.Destroy()
            if res.ok:
                prefix = res.value

        self.prefix = prefix
        self.fname = 'Eiger.tif'
        self.esimplon = None
        if url is not None and HAS_SIMPLON:
            self.esimplon = EigerSimplon(url, prefix=prefix + 'cam1:')

        self.lineplotter = None
        self.calib = {}
        self.integrator = None
        self.int_panel = None
        self.int_lastid = None
        self.contrast_levels = None
        self.scandb = None
        wx.Frame.__init__(self,
                          None,
                          -1,
                          "Eiger500K Area Detector Display",
                          style=wx.DEFAULT_FRAME_STYLE)

        self.buildMenus()
        self.buildFrame()

        wx.CallAfter(self.connect_escandb)

    def connect_escandb(self):
        if HAS_ESCAN and os.environ.get('ESCAN_CREDENTIALS', None) is not None:
            self.scandb = ScanDB()
            calib_loc = self.scandb.get_info('eiger_calibration')
            cal = self.scandb.get_detectorconfig(calib_loc)
            self.setup_calibration(json.loads(cal.text))

    def buildFrame(self):
        sbar = self.CreateStatusBar(3, wx.CAPTION)  # |wx.THICK_FRAME)
        self.SetStatusWidths([-1, -1, -1])
        sfont = sbar.GetFont()
        sfont.SetWeight(wx.BOLD)
        sfont.SetPointSize(10)
        sbar.SetFont(sfont)

        self.SetStatusText('', 0)

        sizer = wx.GridBagSizer(3, 3)
        panel = self.panel = wx.Panel(self)

        pvpanel = PVConfigPanel(panel, self.prefix, display_pvs)

        wsize = (100, -1)
        lsize = (250, -1)

        start_btn = wx.Button(panel, label='Start', size=wsize)
        stop_btn = wx.Button(panel, label='Stop', size=wsize)
        free_btn = wx.Button(panel, label='Free Run', size=wsize)
        start_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='start'))
        stop_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='stop'))
        free_btn.Bind(wx.EVT_BUTTON, partial(self.onButton, key='free'))

        self.cmap_choice = wx.Choice(panel, size=(80, -1), choices=colormaps)
        self.cmap_choice.SetSelection(0)
        self.cmap_choice.Bind(wx.EVT_CHOICE, self.onColorMap)

        self.cmap_reverse = wx.CheckBox(panel, label='Reverse', size=(60, -1))
        self.cmap_reverse.Bind(wx.EVT_CHECKBOX, self.onColorMap)

        self.show1d_btn = wx.Button(panel,
                                    label='Show 1D Integration',
                                    size=(200, -1))
        self.show1d_btn.Bind(wx.EVT_BUTTON, self.onShowIntegration)
        self.show1d_btn.Disable()

        self.imagesize = wx.StaticText(panel,
                                       label='? x ?',
                                       size=(250, 30),
                                       style=txtstyle)

        self.contrast = ContrastChoice(panel, callback=self.set_contrast_level)

        def lin(len=200, wid=2, style=wx.LI_HORIZONTAL):
            return wx.StaticLine(panel, size=(len, wid), style=style)

        irow = 0
        sizer.Add(pvpanel, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(start_btn, (irow, 0), (1, 1), labstyle)
        sizer.Add(stop_btn, (irow, 1), (1, 1), labstyle)
        sizer.Add(free_btn, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(lin(300), (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(self.imagesize, (irow, 0), (1, 3), labstyle)

        irow += 1
        sizer.Add(wx.StaticText(panel, label='Color Map: '), (irow, 0), (1, 1),
                  labstyle)

        sizer.Add(self.cmap_choice, (irow, 1), (1, 1), labstyle)
        sizer.Add(self.cmap_reverse, (irow, 2), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.contrast.label, (irow, 0), (1, 1), labstyle)
        sizer.Add(self.contrast.choice, (irow, 1), (1, 1), labstyle)

        irow += 1
        sizer.Add(self.show1d_btn, (irow, 0), (1, 2), labstyle)

        panel.SetSizer(sizer)
        sizer.Fit(panel)

        # image panel
        self.image = ADMonoImagePanel(self,
                                      prefix=self.prefix,
                                      rot90=DEFAULT_ROTATION,
                                      size=(400, 750),
                                      writer=partial(self.write, panel=2))

        mainsizer = wx.BoxSizer(wx.HORIZONTAL)
        mainsizer.Add(panel, 0, wx.LEFT | wx.GROW | wx.ALL)
        mainsizer.Add(self.image, 1, wx.CENTER | wx.GROW | wx.ALL)
        self.SetSizer(mainsizer)
        mainsizer.Fit(self)

        self.SetAutoLayout(True)

        try:
            self.SetIcon(wx.Icon(ICONFILE, wx.BITMAP_TYPE_ICO))
        except:
            pass

        wx.CallAfter(self.connect_pvs)

    def onColorMap(self, event=None):
        cmap_name = self.cmap_choice.GetStringSelection()
        if self.cmap_reverse.IsChecked():
            cmap_name = cmap_name + '_r'
        self.image.colormap = getattr(colormap, cmap_name)
        self.image.Refresh()

    def onCopyImage(self, event=None):
        "copy bitmap of canvas to system clipboard"
        bmp = wx.BitmapDataObject()
        bmp.SetBitmap(wx.Bitmap(self.image.GrabWxImage()))
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(bmp)
        wx.TheClipboard.Close()
        wx.TheClipboard.Flush()

    def onReadCalibFile(self, event=None):
        "read calibration file"
        wcards = "Poni Files(*.poni)|*.poni|All files (*.*)|*.*"
        dlg = wx.FileDialog(None,
                            message='Read Calibration File',
                            defaultDir=os.getcwd(),
                            wildcard=wcards,
                            style=wx.FD_OPEN)
        ppath = None
        if dlg.ShowModal() == wx.ID_OK:
            ppath = os.path.abspath(dlg.GetPath())

        if os.path.exists(ppath):
            if self.scandb is not None:
                CalibrationDialog(self, ppath).Show()
            else:
                self.setup_calibration(read_poni(ppath))

    def setup_calibration(self, calib):
        """set up calibration from calibration dict"""
        if self.image.rot90 in (1, 3):
            calib['rot3'] = np.pi / 2.0
        self.calib = calib
        if HAS_PYFAI:
            self.integrator = AzimuthalIntegrator(**calib)
            self.show1d_btn.Enable()

    def onShowIntegration(self, event=None):

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()

    def onAutoIntegration(self, event=None):
        if not event.IsChecked():
            self.int_timer.Stop()
            return

        if self.calib is None or 'poni1' not in self.calib:
            return
        shown = False
        try:
            self.int_panel.Raise()
            shown = True
        except:
            self.int_panel = None
        if not shown:
            self.int_panel = PlotFrame(self)
            self.show_1dpattern(init=True)
        else:
            self.show_1dpattern()
        self.int_timer.Start(500)

    def show_1dpattern(self, init=False):
        if self.calib is None or not HAS_PYFAI:
            return

        img = self.ad_img.PV('ArrayData').get()

        h, w = self.image.GetImageSize()
        img.shape = (w, h)
        img = img[3:-3, 1:-1][::-1, :]

        img_id = self.ad_cam.ArrayCounter_RBV
        q, xi = self.integrator.integrate1d(img,
                                            2048,
                                            unit='q_A^-1',
                                            correctSolidAngle=True,
                                            polarization_factor=0.999)
        if init:
            self.int_panel.plot(q,
                                xi,
                                xlabel=r'$Q (\rm\AA^{-1})$',
                                marker='+',
                                title='Image %d' % img_id)
            self.int_panel.Raise()
            self.int_panel.Show()
        else:
            self.int_panel.update_line(0, q, xi, draw=True)
            self.int_panel.set_title('Image %d' % img_id)

    @EpicsFunction
    def onSaveImage(self, event=None):
        "prompts for and save image to file"
        defdir = os.getcwd()
        self.fname = "Image_%i.tiff" % self.ad_cam.ArrayCounter_RBV
        dlg = wx.FileDialog(None,
                            message='Save Image as',
                            defaultDir=os.getcwd(),
                            defaultFile=self.fname,
                            style=wx.FD_SAVE)
        path = None
        if dlg.ShowModal() == wx.ID_OK:
            path = os.path.abspath(dlg.GetPath())

        root, fname = os.path.split(path)
        epics.caput("%sTIFF1:FileName" % self.prefix, fname)
        epics.caput("%sTIFF1:FileWriteMode" % self.prefix, 0)
        time.sleep(0.05)
        epics.caput("%sTIFF1:WriteFile" % self.prefix, 1)
        time.sleep(0.05)

        print(
            "Saved TIFF File ",
            epics.caget("%sTIFF1:FullFileName_RBV" % self.prefix,
                        as_string=True))

    def onExit(self, event=None):
        try:
            wx.Yield()
        except:
            pass
        self.Destroy()

    def onAbout(self, event=None):
        msg = """Eiger Image Display version 0.1
Matt Newville <*****@*****.**>"""

        dlg = wx.MessageDialog(self, msg, "About Epics Image Display",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def buildMenus(self):
        fmenu = wx.Menu()
        MenuItem(self, fmenu, "&Save\tCtrl+S", "Save Image", self.onSaveImage)
        MenuItem(self, fmenu, "&Copy\tCtrl+C", "Copy Image to Clipboard",
                 self.onCopyImage)
        MenuItem(self, fmenu, "Read Calibration File", "Read PONI Calibration",
                 self.onReadCalibFile)
        fmenu.AppendSeparator()
        MenuItem(self, fmenu, "E&xit\tCtrl+Q", "Exit Program", self.onExit)

        omenu = wx.Menu()
        MenuItem(self, omenu, "&Rotate CCW\tCtrl+R",
                 "Rotate Counter Clockwise", self.onRot90)
        MenuItem(self, omenu, "Flip Up/Down\tCtrl+T", "Flip Up/Down",
                 self.onFlipV)
        MenuItem(self, omenu, "Flip Left/Right\tCtrl+F", "Flip Left/Right",
                 self.onFlipH)
        MenuItem(self, omenu, "Reset Rotations and Flips", "Reset",
                 self.onResetRotFlips)
        omenu.AppendSeparator()

        hmenu = wx.Menu()
        MenuItem(self, hmenu, "About", "About Epics AreadDetector Display",
                 self.onAbout)

        mbar = wx.MenuBar()
        mbar.Append(fmenu, "File")
        mbar.Append(omenu, "Options")

        mbar.Append(hmenu, "&Help")
        self.SetMenuBar(mbar)

    def onResetRotFlips(self, event):
        self.image.rot90 = DEFAULT_ROTATION
        self.image.flipv = self.fliph = False

    def onRot90(self, event):
        self.image.rot90 = (self.image.rot90 - 1) % 4

    def onFlipV(self, event):
        self.image.flipv = not self.image.flipv

    def onFlipH(self, event):
        self.image.fliph = not self.image.fliph

    def set_contrast_level(self, contrast_level=0):
        self.image.contrast_levels = [contrast_level, 100.0 - contrast_level]

    def write(self, s, panel=0):
        """write a message to the Status Bar"""
        self.SetStatusText(text=s, number=panel)

    @EpicsFunction
    def onButton(self, event=None, key='free'):
        key = key.lower()
        if key.startswith('free'):
            self.image.restart_fps_counter()
            self.ad_cam.AcquireTime = 0.25
            self.ad_cam.AcquirePeriod = 0.25
            self.ad_cam.NumImages = 345600
            self.ad_cam.Acquire = 1
        elif key.startswith('start'):
            self.image.restart_fps_counter()
            self.ad_cam.Acquire = 1
        elif key.startswith('stop'):
            self.ad_cam.Acquire = 0

    @EpicsFunction
    def connect_pvs(self, verbose=True):
        if self.prefix is None or len(self.prefix) < 2:
            return

        if self.prefix.endswith(':'):
            self.prefix = self.prefix[:-1]
        if self.prefix.endswith(':image1'):
            self.prefix = self.prefix[:-7]
        if self.prefix.endswith(':cam1'):
            self.prefix = self.prefix[:-5]

        self.write('Connecting to AD %s' % self.prefix)
        self.ad_img = epics.Device(self.prefix + ':image1:',
                                   delim='',
                                   attrs=self.img_attrs)
        self.ad_cam = epics.Device(self.prefix + ':cam1:',
                                   delim='',
                                   attrs=self.cam_attrs)

        epics.caput("%s:TIFF1:EnableCallbacks" % self.prefix, 1)
        epics.caput("%s:TIFF1:AutoSave" % self.prefix, 0)
        epics.caput("%s:TIFF1:AutoIncrement" % self.prefix, 0)
        epics.caput("%s:TIFF1:FileWriteMode" % self.prefix, 0)

        time.sleep(0.002)
        if not self.ad_img.PV('UniqueId_RBV').connected:
            epics.poll()
            if not self.ad_img.PV('UniqueId_RBV').connected:
                self.write('Warning:  Camera seems to not be connected!')
                return
        if verbose:
            self.write('Connected to AD %s' % self.prefix)

        self.SetTitle("Epics Image Display: %s" % self.prefix)

        sizex = self.ad_cam.MaxSizeX_RBV
        sizey = self.ad_cam.MaxSizeY_RBV

        sizelabel = 'Image Size: %i x %i pixels'
        try:
            sizelabel = sizelabel % (sizex, sizey)
        except:
            sizelabel = sizelabel % (0, 0)

        self.imagesize.SetLabel(sizelabel)

        self.ad_cam.add_callback('DetectorState_RBV', self.onDetState)
        self.contrast.set_level_str('0.05')

    @DelayedEpicsCallback
    def onDetState(self, pvname=None, value=None, char_value=None, **kw):
        self.write(char_value, panel=1)