import os import re import sys import time import base64 import string from casa_stack_manip import stack_frame_find import __casac__ try: import dbus try: bus = dbus.SessionBus( ) have_dbus_module = True except: print "warning: dbus is not properly configured, viewer scripting will not be available" have_dbus_module = False bus = None except: print "warning: dbus is not available, viewer scripting will not be available" have_dbus_module = False bus = None def dbus_connection( ): return bus def seqselect(test, list): """ Select the elements from a sequence that satisfy the given test function - compare The test function should have following signature def test(item): and must return a boolean - list The List from which element need to be selected """ selected = [ ] for item in list: if test(item) == True: selected.append(item) return selected class viewertool(object): "manage task engines" __t = string.maketrans("abcdefghijklmnopqrstuvwxyz0123456789/*:%$#@!&()~+,.:;{}[]|\\\"'^","abcdefghijklmnopqrstuvwxyz0123456789__________________________") ### ### 'use_existing' defaults to false because: ### o for linux a new dbus session daemon is started for each casapy ### o for osx it would result in multiple casapy sessions using the same viewer ### o for casa.py included into python it makes sense to avoid a new viewer ### appearing (and sticking around) for each include ### def __init__( self, with_gui=True, pre_launch=False, use_existing=False ): if type(with_gui) != bool: raise Exception, "the 'with_gui' parameter must be a boolean" self.__state = { } self.__state['proxy'] = None self.__state['gui'] = with_gui self.__state['launched'] = False self.__state['dbus name'] = None if type(with_gui) == bool and with_gui == False: basename = "vtoolng" else: basename = "vtool" ## for viewer used from plain python, see if a viewer is already available on dbus first... bus = dbus_connection( ) if bus != None and type(use_existing) == bool and use_existing == True : candidates = seqselect(lambda x: x.startswith('edu.nrao.casa.%s_' % (basename)),map(str,bus.list_names( ))) if len( candidates ) > 0 : candidate = candidates[0] p = re.compile('[^\.]+') segments = p.findall(candidate) if len(segments) == 4 : self.__state['dbus name'] = segments[3] self.__state['launched'] = True if self.__state['dbus name'] == None: self.__state['dbus name'] = "%s_%s" % (basename, (base64.b64encode(os.urandom(16))).translate(self.__t,'=')) if pre_launch: self.__launch( ) def __launch( self ): ## if we've already launched the viewer if type(self.__state['launched']) == bool and self.__state['launched'] == True: return if dbus_connection( ) == None: raise Exception, "dbus is not available; cannot script the viewer" myf=stack_frame_find( ) self.__rgm = __casac__.regionmanager.regionmanager( ) viewer_path = None if type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == dict : if myf['casa'].has_key('helpers') \ and type(myf['casa']['helpers']) == dict and myf['casa']['helpers'].has_key('viewer'): viewer_path = myf['casa']['helpers']['viewer'] #### set in casapy.py if len(os.path.dirname(viewer_path)) == 0: for dir in os.getenv('PATH').split(':') : dd = dir + os.sep + viewer_path if os.path.exists(dd) and os.access(dd,os.X_OK) : viewer_path = dd break args = [ viewer_path, "--casapy" ] else: for exe in ['casaviewer']: for dir in os.getenv('PATH').split(':') : dd = dir + os.sep + exe if os.path.exists(dd) and os.access(dd,os.X_OK) : viewer_path = dd break if viewer_path is not None: break args = [ viewer_path ] if myf['casa'].has_key('state') and myf['casa']['state'].has_key('init_version'): if myf['casa']['state']['init_version'] <= 0: args += [ '--daemon' ] else: args += [ '--daemon' ] if viewer_path == None or not os.access(viewer_path,os.X_OK): raise RuntimeError("cannot find casa viewer executable") if self.__state['gui']: args += [ '--server=' + self.__state['dbus name'] ] else: args += [ '--nogui=' + self.__state['dbus name'] ] if type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == dict and myf['casa'].has_key('files') \ and type(myf['casa']['files']) == dict and myf['casa']['files'].has_key('logfile'): args += [ '--casalogfile=' + myf['casa']['files']['logfile'] ] if type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == dict and myf['casa'].has_key('flags') \ and type(myf['casa']['flags']) == dict and myf['casa']['flags'].has_key('--rcdir'): args += [ "--rcdir=" + myf['casa']['flags']['--rcdir'] ] if (os.uname()[0]=='Darwin'): if myf['casa']['state']['init_version'] > 0: from casa_system import procmgr procmgr.create(self.__state['dbus name'],args,procmgr.output_option.DISCARD) else: vwrpid=os.spawnvp( os.P_NOWAIT, viewer_path, args ) elif (os.uname()[0]=='Linux'): if myf['casa']['state']['init_version'] > 0: from casa_system import procmgr procmgr.create(self.__state['dbus name'],args,procmgr.output_option.DISCARD) else: vwrpid=os.spawnlp( os.P_NOWAIT, viewer_path, *args ) else: raise Exception,'unrecognized operating system' self.__state['launched'] = True def __connect( self ): if not self.__state['launched']: self.__launch( ) if not self.__state['launched']: raise Exception, 'launch failed' error = None for i in range(1,500): time.sleep(0.25) try: self.__state['proxy'] = bus.get_object( "edu.nrao.casa." + self.__state['dbus name'], "/casa/" + self.__state['dbus name'] ) if self.__state['proxy'] == None: time.sleep(0.25) continue error = None break except dbus.DBusException, e: if e.get_dbus_name() == 'org.freedesktop.DBus.Error.Disconnected' : raise RuntimeError('DBus daemon has died...') elif e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown' : error = RuntimeError('DBus Viewer service failed to start...') continue else: raise RuntimeError('Unexpected DBus problem: ' + e.get_dbus_name( ) + "(" + e.args[0] + ")") except Exception, e: error = e continue if error is not None : raise error def __invoke( self, dt, t, func, *args, **kwargs ): ## set maximum dbus timeout... kwargs['timeout'] = 0x7fffffff / 1000.0 try: result = func(*args,**kwargs) except dbus.DBusException, e: if e.get_dbus_name() == 'org.freedesktop.DBus.Error.Disconnected' : raise RuntimeError('DBus daemon has died....') elif e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown' : raise RuntimeError('DBus Viewer service has exited....') else: raise RuntimeError('Unexpected DBus problem: ' + e.get_dbus_name( ) + "(" + e.args[0] + ")") if type(result) == dbus.Dictionary and result.has_key('*error*') : raise RuntimeError(str(result['*error*'])) elif type(result) != dt : raise RuntimeError(str(result)) return t(result) def panel( self, paneltype="viewer" ) : if type(paneltype) != str or (paneltype != "viewer" and paneltype != "clean"): if not (paneltype.endswith('.rstr') and os.path.isfile(paneltype)): raise Exception, "the only valid panel types are 'viewer' and 'clean' or path to restore file" if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Int32, int, self.__state['proxy'].panel, paneltype ) def load( self, path, displaytype="raster", panel=0, scaling=0 ): if type(path) != str or type(displaytype) != str or \ (type(scaling) != float and type(scaling) != int) : raise Exception, "load() takes two strings; only the first arg is required..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Int32, int, self.__state['proxy'].load, path, displaytype, panel, float(scaling) ) def close( self, panel=0 ): if type(panel) != int : raise Exception, "close() takes one optional integer..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].close, panel ) def popup( self, what, panel=0 ): if type(what) != str or type(panel) != int : raise Exception, "popup() takes a string followed by one optional integer..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].popup, what, panel ) def freeze( self, panel=0 ): if type(panel) != int : raise Exception, "freeze() takes only a panel id..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].freeze, panel ) def unfreeze( self, panel=0 ): if type(panel) != int : raise Exception, "unfreeze() takes only a panel id..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].unfreeze, panel ) def restore( self, path, panel=0 ): if type(path) != str or type(panel) != int: raise Exception, "restore() takes a string and an integer; only the first arg is required..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Int32, int, self.__state['proxy'].restore, path, panel ) def cwd( self, new_path='' ): if type(new_path) != str: raise Exception, "cwd() takes a single (optional) string..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.String, str, self.__state['proxy'].cwd, new_path ) def output( self, device, devicetype='file', panel=0, scale=1.0, dpi=300, format="jpg", \ orientation="portrait", media="letter" ): if type(device) != str or type(panel) != int or type(scale) != float or \ type(dpi) != int or type(format) != str or type(orientation) != str or \ type( media ) != str: raise Exception, "output() takes (str,int,float,int,str,str,str); only the first is required..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].output, device, devicetype, panel, scale, dpi, format, orientation, media ) def axes( self, x='', y='', z='', panel=0 ): if type(x) != str or type(y) != str or type(z) != str or type(panel) != int : raise Exception, "axes() takes one to three strings and an optional panel id..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].axes, x, y, z, panel ) def datarange( self, range, data=0 ): if type(range) != list or type(data) != int or \ all( map( lambda x: type(x) == int or type(x) == float, range ) ) == False: raise Exception, "datarange() takes (numeric list,int)..." if len(range) != 2 or range[0] > range[1] : raise Exception, "range should be [ min, max ]..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].datarange, map( lambda(x): float(x), range ), data ) def contourlevels( self, levels=[], baselevel=2147483648.0, unitlevel=2147483648.0, data=0 ): if type(levels) != list or type(data) != int or \ all( map( lambda x: type(x) == int or type(x) == float, levels ) ) == False: raise Exception, "contorlevels() takes (numeric list,int)..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].contourlevels, dbus.Array(map( lambda(x): float(x), levels),signature="d"), baselevel, unitlevel, data ) def contourcolor( self, color="foreground", data=0 ): if type(color) != str or type(data) != int: raise Exception, "contorcolor() takes color name and data id..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].contourcolor, color, data ) def contourthickness( self, thickness=0.0, data=0 ): if type(thickness) != float or type(data) != int: raise Exception, "contourthickness() takes a float representing the thickness and data id..." if thickness < 0 or thickness > 5: raise Exception, "the thickness supplied to contourthickness() should between 0 and 5" if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].contourthickness, thickness, data ) def colormap( self, map, data_or_panel=0 ): if type(map) != str or type(data_or_panel) != int : raise Exception, "colormap() takes a colormap name and an optional panel or data id..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].colormap, map, data_or_panel ) def colorwedge( self, show, data_or_panel=0 ): if type(show) != bool or type(data_or_panel) != int : raise Exception, "colorwedge() takes a boolean and an optional panel or data id..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].colorwedge, show, data_or_panel ) def channel( self, num=-1, panel=0 ): if type(num) != int or type(panel) != int: raise Exception, "frame() takes (int,int); each argument is optional..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Int32, int, self.__state['proxy'].channel, num, panel ) def zoom( self, level=None, blc=[], trc=[], coordinates="pixel", region="", panel=0 ): if (type(level) != int and level != None) or \ type(blc) != list or type(trc) != list or type(panel) != int or \ type(coordinates) != str or (type(region) != str and type(region) != dict) : raise Exception, "zoom() takes (int|None,list,list,str,int); each argument is optional..." if self.__state['proxy'] == None: self.__connect( ) if (type(region) == str and os.path.isfile( region )) or \ type(region) == dict : reg = region if type(region) == str : try: reg = self.__rgm.fromfiletorecord( region ) except: raise Exception, "region file (" + str(region) + ") exists but is not a valid region file" if type(reg) != dict : raise Exception, "invalid regions or failed to load region" ( _blc, _trc, _coord ) = self.__extract_region_box( reg ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].zoom, _blc, _trc, _coord, panel ) elif len(blc) == 2 and len(trc) == 2 and \ all( map( lambda x,y: (type(x) == int or type(x) == float) and (type(y) == int or type(y) == float), blc, trc ) ) == True: if coordinates != "pixel" and coordinates != "world": raise Exception, "zoom() coordinates must be either world or pixel" return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].zoom, map( lambda(x): float(x), blc ), map( lambda(x): float(x), trc ), coordinates, panel ) elif level != None: return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].zoom, level, panel ) else: raise Exception, "must supply either blc and trc or a level for zoom" def hide( self, panel=0 ): if type(panel) != int: raise Exception, "hide() takes a single (int) panel identifier ..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].hide, panel ) def show( self, panel=0 ): if type(panel) != int: raise Exception, "show() takes a single (int) panel identifier ..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].show, panel ) def fileinfo( self, path ): if type(path) != str: raise Exception, "fileinfo() takes a single path..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Dictionary, dict, self.__state['proxy'].fileinfo, path ) def keyinfo( self, key ): if type(key) != int: raise Exception, "keyinfo() takes a single int..." if self.__state['proxy'] == None: self.__connect( ) return self.__invoke( dbus.Array, lambda x: map(str,x), self.__state['proxy'].keyinfo, key ) def done( self ): if self.__state['proxy'] == None: self.__connect( ) result = self.__invoke( dbus.Boolean, bool, self.__state['proxy'].done ) self.__state['proxy'] = None self.__state['launched'] = False return result def __extract_region_box( self, reg ): if reg.has_key( 'regions' ) : if type(reg['regions']) != dict or not reg['regions'].has_key( '*1' ) : raise Exception, "invalid region, has 'regions' field but wrong format" reg=reg['regions']['*1'] if not reg.has_key('trc') or not reg.has_key('blc'): raise Exception, "region must have a 'blc' and 'trc' field" blc_r = reg['blc'] trc_r = reg['trc'] if type(blc_r) != dict or type(trc_r) != dict : raise Exception, "region blc/trc of wrong type" blc_k = blc_r.keys( ) trc_k = trc_r.keys( ) if len(blc_k) < 2 or len(trc_k) < 2: raise Exception, "degenerate region" blc_k.sort( ) trc_k.sort( ) if type(blc_r[blc_k[0]]) != dict or type(blc_r[blc_k[1]]) != dict or \ type(trc_r[trc_k[0]]) != dict or type(trc_r[trc_k[1]]) != dict : raise Exception, "invalid blc/trc in region" if not blc_r[blc_k[0]].has_key('value') or not blc_r[blc_k[1]].has_key('value') or \ not trc_r[trc_k[0]].has_key('value') or not trc_r[trc_k[1]].has_key('value'): raise Exception, "invalid shape for blc/trc in region" if (type(blc_r[blc_k[0]]['value']) != float and type(blc_r[blc_k[0]]['value']) != int) or \ (type(blc_r[blc_k[1]]['value']) != float and type(blc_r[blc_k[1]]['value']) != int) or \ (type(trc_r[trc_k[0]]['value']) != float and type(trc_r[trc_k[0]]['value']) != int) or \ (type(trc_r[trc_k[0]]['value']) != float and type(trc_r[trc_k[0]]['value']) != int) : raise Exception, "invalid type for blc/trc value in region" blc = [ float(blc_r[blc_k[0]]['value']), float(blc_r[blc_k[1]]['value']) ] trc = [ float(trc_r[trc_k[0]]['value']), float(trc_r[trc_k[1]]['value']) ] coord = "pixel" if reg.has_key('name') and reg['name'] == "WCBox": coord = "world" return ( blc, trc, coord )