280 likes | 295 Views
Learn how to create quick and simple Graphical User Interfaces (GUIs) using QT in Python. This tutorial covers the basics of PyEpics and PySide, and provides examples of creating EPICS Probes.
E N D
Creating quick/simple QT-based GUIs in Python Pete R. Jemian Advanced Photon Source, Argonne National Laboratory 2012 EPICS Collaboration Meeting SLAC, Menlo Park, CA 2012-04-24
Outline • My tool set • A few basics about PyEpics • Our target application: EPICS Probe • PySide Probe, from Matt Newville, CARS • Enthought Tool Suite (ETS) Traits Probe, simple • ETS Traits Probe, more features • NOTE: all code shown in this presentation is available from subversion • svn co http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo ./qtprobe-demo EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Development Tools • OS • Windows 7 • Linux: RHEL5, RHEL6, Ubuntu 10.04, 11.04, 11.10 • Mac OSX • SunOS 5.10 • Python 2.7 • EnthoughtPython Distribution 7.2-2 (http://www.enthought.com/products/epd.php) • 90+ packages including PyQt and wxPythonbackends, plus numpy, scipy, matplotlib • Traits (http://code.enthought.com/projects/traits/) • PyEpics (http://cars9.uchicago.edu/software/python/pyepics3/) • eclipse 3.7 (http://eclipse.org) • PyDev (http://pydev.org/updates) Usual disclaimers apply here: This is not an endorsement. I just use these tools. EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
PyEpics basicshttp://cars9.uchicago.edu/software/python/pyepics3/ • PyEpics is a Python interface for Channel Access (CA) • Provides EPICS package • fairly complete • camodule: thin layer over the low-level Channel Access library • PV module: abstraction of EPICS Process Variable as an easy-to-use Python object • Device module: create higher-level abstractions such as Motor or Scaler • Requires EPICS libca & libComlibraries, numpypackage is optional • Simple, functional approach to CA (similar to EZCA) • Examples of simple commands • epics.caget() • epics.caput() • epics.camonitor() • epics.cainfo() EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Quick PyEpics demo jemian@como-ubuntu64:~/...se/qtprobe-demo$ python Enthought Python Distribution -- www.enthought.com Version: 7.2-2 (64-bit) Python 2.7.2 |EPD 7.2-2 (64-bit)| (default, Jul 3 2011, 15:17:51) [GCC 4.1.2 20080704 (Red Hat 4.1.2-44)] on linux2 Type "packages", "demo" or "enthought" for more information. >>> import epics >>> print epics.caget('prj:datetime') 2012-04-24 08:39:37 >>> epics.caput('prj:m1.DESC', 'This is motor 1') 1 >>> EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Quick PyEpicsdemo … >>> print epics.cainfo('prj:m1.DESC') == prj:m1.DESC (native_string) == value = This is motor 1 char_value = 'This is motor 1' count = 1 nelm = 1 type = string host = dhcpvisitor218180.slac.stanford.edu:5064 access = read/write status = 0 severity = 0 timestamp = 1335274922.470 (2012-04-24 08:42:02.470303) PV is internally monitored, with 0 user-defined callbacks: ============================= None >>> EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Our target application: EPICS Probe • Basic Features • Displays current value of any EPICS process variable • Provides a text entry widget for use to change to a different PV • Options • Display metadata about PV • Display alarm status • Display engineering units • Change display format • Change current value of PV • Display timestamp of PV EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
PySide (http://www.pyside.org/) • What is PySide? • “The PySide project provides LGPL-licensed Python bindings for the Qt cross-platform application and UI framework. PySideQt bindings allow both free open source and proprietary software development and ultimately aim to support all of the platforms as Qt itself.” • Alternative to PyQt4 package • Why? • PySide is LGPL, more permissive license than PyQt4 which uses GPL • See also: http://stackoverflow.com/questions/1297660/pyside-vs-pyqt • PySide (LGPL) allows linking from closed source software • PyQt (GPL) does not, and requires separate commercial license. • Key differences from PyQt4 • http://qt-project.org/wiki/Differences_Between_PySide_and_PyQt • Different import name (PySide instead of PyQt4) • New-style signals and slots use slightly different syntax (PSEP 100) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
HelloWorld using PySide EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
pyside_probe.py : preamble #!/usr/bin/env python ########### SVN repository information ################### # $Date: 2012-04-24 09:42:01 -0500 (Tue, 24 Apr 2012) $ # $Author: jemian $ # $Revision: 819 $ # $URL: https://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo/pyside_probe.py $ # $Id: pyside_probe.py 819 2012-04-24 14:42:01Z jemian $ ########### SVN repository information ################### # from Matt Newville, CARS, University of Chicago import epics import sys from PySide.QtGui import QWidget, QLabel, QLineEdit, QGridLayout, QApplication EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
pyside_probe.py : “main” if __name__ == '__main__': app = QApplication(sys.argv) probe = PVProbe() probe.show() sys.exit(app.exec_()) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
pyside_probe.py : PvProbe class and initialization A signal is emitted when a particular event occurs. A slot can be any Python callable. A slot is called when a signal connected to it is emitted. class PVProbe(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) name_label = QLabel("PV Name:") self.pvname = QLineEdit() value_label = QLabel("PV Value:") self.value = QLabel(" "*4) self.pvname.returnPressed.connect(self.onPVNameReturn) self.pv = None grid = QGridLayout() grid.addWidget(name_label, 0, 0) grid.addWidget(self.pvname, 0, 1) grid.addWidget(value_label, 1, 0) grid.addWidget(self.value, 1, 1) self.setLayout(grid) self.setWindowTitle("PySide PV Probe:") EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
pyside_probe.py : PvProbe class action handlers defonPVNameReturn(self): if self.pv is not None: self.pv.remove_callback() self.pv.disconnect() self.pv = epics.PV(self.pvname.text(), callback=self.onPVChange) defonPVChange(self, pvname=None, char_value=None, **kws): self.value.setText(char_value) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
pyside_probe.py : run it jemian@como-ubuntu64:~/...se/qtprobe-demo$ ./pyside_probe.py & [1] 10323 EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
PyEpics: camonitor()callback handlerhttp://cars9.uchicago.edu/software/python/pyepics3/pv.html#user-supplied-callback-functions defca_callback(self, **kw): ''' process an EPICS CA monitor callback event, event information willbe in kwdictionary ''' logger = logging.getLogger(__name__) logger.debug( "callback: %s = %s" % (kw['pvname'], kw['char_value']) ) • Callback functions will be called with several keyword arguments. • Prepare to handle them as a dictionary: **kw • See the online docs for other variations and the complete list of keywords EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Traits basicshttp://code.enthought.com/projects/traits • “Python does not require the data type of variables to be declared. As any experienced Python programmer knows, this flexibility has both good and bad points. The Traits package was developed to address some of the problems caused by not having declared variable types, in those cases where problems might arise.” • Huh? • Provides strongly-typed objects for Python • The TraitsUI package is a set of user interface tools designed to complement Traits • Used in ETC plotting package: Chaco EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Traits example: simpleton.py from enthought.traits.api import HasTraits, String, Int, Float class Simpleton(HasTraits): desc = String quantity = Int alpha = Float beta = Float gamma = Float if __name__ == '__main__': example = Simpleton() result = example.configure_traits() print result print example.desc, example.quantity print example.alpha, example.beta, example.gamma EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Starting Traits with Qt4 backend • Traits supports either wxPython (default) or Qt4 backends • Here’s how to enforce use of the Qt4 backend • MUST put this code before any other Traits imports! from traits.etsconfig.api import ETSConfig ETSConfig.toolkit= 'qt4' EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe.py : main import argparse if __name__ == '__main__': #pv = '' #if len(sys.argv) == 2: # pv = sys.argv[1] parser = argparse.ArgumentParser( description="traits_probe") parser.add_argument( 'pv', action='store', nargs='?', help="EPICS PV name", default="EpicsDemo1“) results = parser.parse_args() probe = Probe(name=results.pv) probe.configure_traits() EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe.py : Probe class class Probe( HasTraits ): name = Str value = Str _chid = Instance( epics.PV, value = None ) defdo_callback(self, value=None, **kwds): "simple monitor callback" self.value = str(value) traits_view = View( Item('name'), Readonly('value'), resizable=True, width = 250, title='Traits-based EPICS Probe', key_bindings = key_bindings, handler = CodeHandler() ) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe.py : CodeHandler class # TraitsUI Handler class for bound methods class CodeHandler ( Handler ): def connect ( self, info ): if len(info.object.name) > 0: try: if info.object._chid is not None: info.object._chid.cancel_callback() info.object._chid.disconnect() except: pass info.object._chid = epics.PV( str(info.object.name), callback=info.object.do_callback ) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe.py : preamble import argparse import epics import sys #@UnusedImport from traits.etsconfig.api import ETSConfig ETSConfig.toolkit= 'qt4' from traits.api import * #@UnusedWildImport from traitsui.api import * #@UnusedWildImport from traitsui.key_bindings import KeyBinding, KeyBindings key_bindings = KeyBindings( KeyBinding( binding1 = 'Enter', method_name = 'connect‘, description = 'Connect to new PV',), KeyBinding( binding1 = 'Return',method_name = 'connect‘, description = 'Connect to new PV', ), ) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe.py : example EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe_more.py : structure • Four classes: ActionHandler, SimpleEpicsTraitsTest, ShowInfo, ShowAbout EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe_more.py : implementing “info” my_button_actions = [. . ., Action(name="Info", action="show_info", desc="show PV info"), . . . ] class ActionHandler(Handler): . . . defshow_info(self, uinfo): '''information about the EPICS PV''' self.define_gui(uinfo) if self._watching and self._ch is not None: self._gui.SetStatus('showing PV info') ShowInfo(text=self._ch.info).edit_traits() else: self._gui.SetStatus('must Watch a PV first') class ShowInfo( HasTraits ): '''dead simple PV Info box''' text = Str view = View( UReadonly('text'), title='PV info ...' ) view = View(. . ., handler = ActionHandler(), buttons = my_button_actions, . . .,) EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
traits_probe_more.py : run it EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Take-home Remarks: • Traits • Works really well for simple things, good choice for simple GUIs • Easy to add new GUI elements • Program execution flow is not obvious • Some unresolved threading challenges encountered on Win7 and RHEL • Supports both wxPython and Qt4 backends • PySide • Regular Qt4 implementation • Easier to debug • Best choice for GUI with any complexity • LGPL license allows greater use (than PyQt4) • Eclipse + PyDev is a great combination for a developer • Use of EPD allows to define minimum configuration for users EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo
Thank you for your attention! EPICS 2012 @ SLAC: Jemian: Creating quick/simple QT-based GUIs in Python -- http://subversion.xor.aps.anl.gov/bcdaext/qtprobe-demo