casa  $Rev:20696$
 All Classes Namespaces Files Functions Variables
viewertool.py
Go to the documentation of this file.
00001 import os
00002 import re
00003 import sys
00004 import time
00005 import base64
00006 import string
00007 import inspect
00008 from taskinit import casac       ### needed for regionmanager
00009 
00010 try:
00011     import dbus
00012     try:
00013         bus = dbus.SessionBus( )
00014         have_dbus_module = True
00015     except:
00016         print "warning: dbus is not properly configured, viewer scripting will not be available"
00017         have_dbus_module = False
00018         bus = None
00019 except:
00020     print "warning: dbus is not available, viewer scripting will not be available"
00021     have_dbus_module = False
00022     bus = None
00023 
00024 
00025 def dbus_connection( ):
00026     return bus
00027 
00028 
00029 def seqselect(test, list):  
00030     """ 
00031     Select the elements from a sequence that 
00032     satisfy the given test function 
00033     - compare The test function should have following 
00034     signature def test(item): and must return a boolean 
00035     - list The List from which element need to be selected 
00036     """  
00037     selected = [ ]
00038     for item in list:  
00039         if test(item) == True:  
00040             selected.append(item)
00041     return selected  
00042 
00043 
00044 class viewertool(object):
00045     "manage task engines"
00046 
00047     __t = string.maketrans("abcdefghijklmnopqrstuvwxyz0123456789/*:%$#@!&()~+,.:;{}[]|\\\"'^","abcdefghijklmnopqrstuvwxyz0123456789__________________________")
00048     __rgm = casac.regionmanager()
00049 
00050     ###
00051     ### 'use_existing' defaults to false because:
00052     ###    o  for linux a new dbus session daemon is started for each casapy
00053     ###    o  for osx it would result in multiple casapy sessions using the same viewer
00054     ###    o  for casa.py included into python it makes sense to avoid a new viewer
00055     ###             appearing (and sticking around) for each include
00056     ###
00057     def __init__( self, with_gui=True, pre_launch=False, use_existing=False ):
00058 
00059         if type(with_gui) != bool:
00060             raise Exception, "the 'with_gui' parameter must be a boolean"
00061 
00062         self.__state = { }
00063         self.__state['proxy'] = None
00064         self.__state['gui'] = with_gui
00065         self.__state['launched'] = False
00066         self.__state['dbus name'] = None
00067 
00068         if type(with_gui) == bool and with_gui == False:
00069             basename = "vtoolng"
00070         else:
00071             basename = "vtool"
00072 
00073         ## for viewer used from plain python, see if a viewer is already available on dbus first...
00074         bus = dbus_connection( )
00075         if bus != None and type(use_existing) == bool and use_existing == True :
00076             candidates = seqselect(lambda x: x.startswith('edu.nrao.casa.%s_' % (basename)),map(str,bus.list_names( )))
00077             if len( candidates ) > 0 :
00078                 candidate = candidates[0]
00079                 p = re.compile('[^\.]+')
00080                 segments = p.findall(candidate)
00081                 if len(segments) == 4 :
00082                     self.__state['dbus name'] = segments[3]
00083                     self.__state['launched'] = True
00084 
00085         if self.__state['dbus name'] == None:
00086             self.__state['dbus name'] = "%s_%s" % (basename, (base64.b64encode(os.urandom(16))).translate(self.__t,'='))
00087 
00088         if pre_launch:
00089             self.__launch( )
00090 
00091 
00092     def __launch( self ):
00093 
00094         ## if we've already launched the viewer
00095         if type(self.__state['launched']) == bool and self.__state['launched'] == True:
00096             return
00097 
00098         if dbus_connection( ) == None:
00099             raise Exception, "dbus is not available; cannot script the viewer"
00100 
00101         a=inspect.stack()
00102         stacklevel=0
00103         for k in range(len(a)):
00104             if a[k][1] == "<string>" or (string.find(a[k][1], 'ipython console') > 0 or string.find(a[k][1],"casapy.py") > 0):
00105                       stacklevel=k
00106 
00107         myf=sys._getframe(stacklevel).f_globals
00108 
00109         viewer_path = None
00110         if type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == dict and myf['casa'].has_key('helpers') \
00111                 and type(myf['casa']['helpers']) == dict and myf['casa']['helpers'].has_key('viewer'):
00112             viewer_path = myf['casa']['helpers']['viewer']   #### set in casapy.py
00113             if len(os.path.dirname(viewer_path)) == 0:
00114                 for dir in os.getenv('PATH').split(':') :
00115                     dd = dir + os.sep + viewer_path
00116                     if os.path.exists(dd) and os.access(dd,os.X_OK) :
00117                         viewer_path = dd
00118                         break
00119             args = [ viewer_path, "--casapy" ]
00120         else:
00121             for exe in ['casaviewer']:
00122                 for dir in os.getenv('PATH').split(':') :
00123                     dd = dir + os.sep + exe
00124                     if os.path.exists(dd) and os.access(dd,os.X_OK) :
00125                         viewer_path = dd
00126                         break
00127                 if viewer_path is not None:
00128                     break
00129             args = [ viewer_path ]
00130 
00131         if viewer_path == None or not os.access(viewer_path,os.X_OK):
00132             raise RuntimeError("cannot find casa viewer executable")
00133 
00134         if self.__state['gui']:
00135             args += [ '--server=' + self.__state['dbus name'] ]
00136         else:
00137             args += [ '--nogui=' + self.__state['dbus name'] ]
00138 
00139         if type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == dict and myf['casa'].has_key('files') \
00140                 and type(myf['casa']['files']) == dict and myf['casa']['files'].has_key('logfile'):
00141             args += [ '--casalogfile=' + myf['casa']['files']['logfile'] ]
00142 
00143         if  type(myf) == dict and myf.has_key('casa') and type(myf['casa']) == dict and myf['casa'].has_key('flags') \
00144                  and type(myf['casa']['flags']) == dict and myf['casa']['flags'].has_key('--rcdir'):
00145             args += [ "--rcdir=" + myf['casa']['flags']['--rcdir'] ]
00146 
00147         if (os.uname()[0]=='Darwin'):
00148                 vwrpid=os.spawnvp( os.P_NOWAIT, viewer_path, args )
00149         elif (os.uname()[0]=='Linux'):
00150                 vwrpid=os.spawnlp( os.P_NOWAIT, viewer_path, *args )
00151         else:
00152                 raise Exception,'unrecognized operating system'
00153 
00154         self.__state['launched'] = True
00155 
00156 
00157     def __connect( self ):
00158         if not self.__state['launched']:
00159             self.__launch( )
00160 
00161         if not self.__state['launched']:
00162             raise Exception, 'launch failed'
00163 
00164         error = None
00165         for i in range(1,500):
00166             time.sleep(0.1)
00167             try:
00168                 self.__state['proxy'] = bus.get_object( "edu.nrao.casa." + self.__state['dbus name'], "/casa/" + self.__state['dbus name'] )
00169                 if self.__state['proxy'] == None:
00170                     time.sleep(0.25)
00171                     continue
00172                 error = None
00173                 break
00174             except dbus.DBusException, e:
00175                 if e.get_dbus_name() == 'org.freedesktop.DBus.Error.Disconnected' :
00176                     raise RuntimeError('DBus daemon has died...')
00177                 elif e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown' :
00178                     error = RuntimeError('DBus Viewer service failed to start...')
00179                     continue
00180                 else:
00181                     raise RuntimeError('Unexpected DBus problem: ' + e.get_dbus_name( ) + "(" + e.args[0] + ")")
00182             except Exception, e:
00183                 error = e
00184                 continue
00185 
00186         if error is not None :
00187             raise error
00188 
00189     def __invoke( self, dt, t, func, *args, **kwargs ):
00190         ## set maximum dbus timeout...
00191         kwargs['timeout'] = 0x7fffffff / 1000.0
00192         try:
00193             result = func(*args,**kwargs)
00194         except dbus.DBusException, e:
00195             if e.get_dbus_name() == 'org.freedesktop.DBus.Error.Disconnected' :
00196                 raise RuntimeError('DBus daemon has died....')
00197             elif e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown' :
00198                 raise RuntimeError('DBus Viewer service has exited....')
00199             else:
00200                 raise RuntimeError('Unexpected DBus problem: ' + e.get_dbus_name( ) + "(" + e.args[0] + ")")
00201 
00202         if type(result) == dbus.Dictionary and result.has_key('*error*') :
00203             raise RuntimeError(str(result['*error*']))
00204         elif type(result) != dt :
00205             raise RuntimeError(str(result))
00206 
00207         return t(result)
00208 
00209     def panel( self, paneltype="viewer" ) :
00210         if type(paneltype) != str or (paneltype != "viewer" and paneltype != "clean"):
00211             raise Exception, "the only valid panel types are 'viewer' and 'clean'"
00212         if self.__state['proxy'] == None:
00213             self.__connect( )
00214 
00215         return self.__invoke( dbus.Int32, int, self.__state['proxy'].panel, paneltype )
00216 
00217     def load( self, path, displaytype="raster", panel=0, scaling=0 ):
00218         if type(path) != str or type(displaytype) != str or \
00219                (type(scaling) != float and type(scaling) != int) :
00220             raise Exception, "load() takes two strings; only the first arg is required..."
00221 
00222         if self.__state['proxy'] == None:
00223             self.__connect( )
00224 
00225         return self.__invoke( dbus.Int32, int, self.__state['proxy'].load, path, displaytype, panel, float(scaling) )
00226 
00227     def close( self, panel=0 ):
00228         if type(panel) != int :
00229             raise Exception, "close() takes one optional integer..."
00230 
00231         if self.__state['proxy'] == None:
00232             self.__connect( )
00233 
00234         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].close, panel )
00235 
00236     def popup( self, what, panel=0 ):
00237         if type(what) != str or type(panel) != int :
00238             raise Exception, "popup() takes a string followed by one optional integer..."
00239 
00240         if self.__state['proxy'] == None:
00241             self.__connect( )
00242 
00243         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].popup, what, panel )
00244 
00245     def freeze( self, panel=0 ):
00246         if type(panel) != int :
00247             raise Exception, "freeze() takes only a panel id..."
00248 
00249         if self.__state['proxy'] == None:
00250             self.__connect( )
00251 
00252         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].freeze, panel )
00253 
00254     def unfreeze( self, panel=0 ):
00255         if type(panel) != int :
00256             raise Exception, "unfreeze() takes only a panel id..."
00257 
00258         if self.__state['proxy'] == None:
00259             self.__connect( )
00260 
00261         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].unfreeze, panel )
00262 
00263 
00264     def restore( self, path, panel=0 ):
00265         if type(path) != str or type(panel) != int:
00266             raise Exception, "restore() takes a string and an integer; only the first arg is required..."
00267 
00268         if self.__state['proxy'] == None:
00269             self.__connect( )
00270 
00271         return self.__invoke( dbus.Int32, int, self.__state['proxy'].restore, path, panel )
00272 
00273     def cwd( self, new_path='' ):
00274         if type(new_path) != str:
00275             raise Exception, "cwd() takes a single (optional) string..."
00276 
00277         if self.__state['proxy'] == None:
00278             self.__connect( )
00279 
00280         return self.__invoke( dbus.String, str, self.__state['proxy'].cwd, new_path )
00281 
00282     def output( self, device, devicetype='file', panel=0, scale=1.0, dpi=300, format="jpg", \
00283                     orientation="portrait", media="letter" ):
00284         if type(device) != str or type(panel) != int or type(scale) != float or \
00285                 type(dpi) != int or type(format) != str or type(orientation) != str or \
00286                 type( media ) != str:
00287             raise Exception, "output() takes (str,int,float,int,str,str,str); only the first is required..."
00288 
00289         if self.__state['proxy'] == None:
00290             self.__connect( )
00291 
00292         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].output, device, devicetype, panel, scale, dpi, format, orientation, media )
00293 
00294     def axes( self, x='', y='', z='', panel=0 ):
00295         if type(x) != str or type(y) != str or type(z) != str or type(panel) != int :
00296             raise Exception, "axes() takes one to three strings and an optional panel id..."
00297 
00298         if self.__state['proxy'] == None:
00299             self.__connect( )
00300 
00301         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].axes, x, y, z, panel )
00302 
00303     def datarange( self, range, data=0 ):
00304         if type(range) != list or type(data) != int or \
00305            all( map( lambda x: type(x) == int or type(x) == float, range ) ) == False:
00306             raise Exception, "datarange() takes (numeric list,int)..."
00307         if len(range) != 2 or range[0] > range[1] :
00308             raise Exception, "range should be [ min, max ]..."
00309 
00310         if self.__state['proxy'] == None:
00311             self.__connect( )
00312 
00313         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].datarange, map( lambda(x): float(x), range ), data )
00314     
00315     def contourlevels( self, levels=[], baselevel=2147483648.0, unitlevel=2147483648.0, data=0 ):
00316         if type(levels) != list or type(data) != int or \
00317            all( map( lambda x: type(x) == int or type(x) == float, levels ) ) == False:
00318             raise Exception, "contorlevels() takes (numeric list,int)..."
00319 
00320         if self.__state['proxy'] == None:
00321             self.__connect( )
00322 
00323         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].contourlevels, dbus.Array(map( lambda(x): float(x), levels),signature="d"), baselevel, unitlevel, data )
00324 
00325     def colormap( self, map, data_or_panel=0 ):
00326         if type(map) != str or type(data_or_panel) != int :
00327             raise Exception, "colormap() takes a colormap name and an optional panel or data id..."
00328 
00329         if self.__state['proxy'] == None:
00330             self.__connect( )
00331 
00332         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].colormap, map, data_or_panel )
00333     
00334 
00335     def colorwedge( self, show, data_or_panel=0 ):
00336         if type(show) != bool or type(data_or_panel) != int :
00337             raise Exception, "colorwedge() takes a boolean and an optional panel or data id..."
00338 
00339         if self.__state['proxy'] == None:
00340             self.__connect( )
00341 
00342         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].colorwedge, show, data_or_panel )
00343     
00344 
00345     def channel( self, num=-1, panel=0 ):
00346         if type(num) != int or type(panel) != int:
00347             raise Exception, "frame() takes (int,int); each argument is optional..."
00348 
00349         if self.__state['proxy'] == None:
00350             self.__connect( )
00351 
00352         return self.__invoke( dbus.Int32, int, self.__state['proxy'].channel, num, panel )
00353 
00354     def zoom( self, level=None, blc=[], trc=[], coordinates="pixel", region="", panel=0 ):
00355         if (type(level) != int and level != None) or \
00356                type(blc) != list or type(trc) != list or type(panel) != int or \
00357                type(coordinates) != str or (type(region) != str and type(region) != dict) :
00358             raise Exception, "zoom() takes (int|None,list,list,str,int); each argument is optional..."
00359 
00360         if self.__state['proxy'] == None:
00361             self.__connect( )
00362 
00363         if (type(region) == str and os.path.isfile( region )) or \
00364                type(region) == dict :
00365             reg = region
00366             if type(region) == str :
00367                 try:
00368                     reg = self.__rgm.fromfiletorecord( region )
00369                 except:
00370                     raise Exception, "region file (" + str(region) + ") exists but is not a valid region file"
00371 
00372             if type(reg) != dict :
00373                 raise Exception, "invalid regions or failed to load region"
00374 
00375             ( _blc, _trc, _coord ) = self.__extract_region_box( reg )
00376 
00377             return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].zoom, _blc, _trc, _coord, panel )
00378             
00379         elif len(blc) == 2 and len(trc) == 2 and \
00380                all( map( lambda x,y: (type(x) == int or type(x) == float) and (type(y) == int or type(y) == float), blc, trc ) ) == True:
00381             if coordinates != "pixel" and coordinates != "world":
00382                 raise Exception, "zoom() coordinates must be either world or pixel"
00383             return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].zoom, map( lambda(x): float(x), blc ), map( lambda(x): float(x), trc ), coordinates, panel )
00384         elif level != None:
00385             return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].zoom, level, panel )
00386         else:
00387             raise Exception, "must supply either blc and trc or a level for zoom"
00388 
00389 
00390 
00391     def hide( self, panel=0 ):
00392         if type(panel) != int:
00393             raise Exception, "hide() takes a single (int) panel identifier ..."
00394 
00395         if self.__state['proxy'] == None:
00396             self.__connect( )
00397 
00398         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].hide, panel )
00399 
00400     def show( self, panel=0 ):
00401         if type(panel) != int:
00402             raise Exception, "show() takes a single (int) panel identifier ..."
00403 
00404         if self.__state['proxy'] == None:
00405             self.__connect( )
00406 
00407         return self.__invoke( dbus.Boolean, bool, self.__state['proxy'].show, panel )
00408 
00409     def fileinfo( self, path ):
00410         if type(path) != str:
00411             raise Exception, "fileinfo() takes a single path..."
00412 
00413         if self.__state['proxy'] == None:
00414             self.__connect( )
00415 
00416         return self.__invoke( dbus.Dictionary, dict, self.__state['proxy'].fileinfo, path )
00417 
00418     def keyinfo( self, key ):
00419         if type(key) != int:
00420             raise Exception, "keyinfo() takes a single int..."
00421 
00422         if self.__state['proxy'] == None:
00423             self.__connect( )
00424 
00425         return self.__invoke( dbus.Array, lambda x: map(str,x), self.__state['proxy'].keyinfo, key )
00426 
00427     def done( self ):
00428 
00429         if self.__state['proxy'] == None:
00430             self.__connect( )
00431 
00432         result = self.__invoke( dbus.Boolean, bool, self.__state['proxy'].done )
00433         self.__state['proxy'] = None
00434         self.__state['launched'] = False
00435         return result
00436 
00437     def __extract_region_box( self, reg ):
00438 
00439         if reg.has_key( 'regions' ) :
00440             if type(reg['regions']) != dict or not reg['regions'].has_key( '*1' ) :
00441                 raise Exception, "invalid region, has 'regions' field but wrong format"
00442             reg=reg['regions']['*1']
00443 
00444         if not reg.has_key('trc') or not reg.has_key('blc'):
00445             raise Exception, "region must have a 'blc' and 'trc' field"
00446 
00447         blc_r = reg['blc']
00448         trc_r = reg['trc']
00449 
00450         if type(blc_r) != dict or type(trc_r) != dict :
00451             raise Exception, "region blc/trc of wrong type"
00452 
00453         blc_k = blc_r.keys( )
00454         trc_k = trc_r.keys( )
00455 
00456         if len(blc_k) < 2 or len(trc_k) < 2:
00457             raise Exception, "degenerate region"
00458 
00459         blc_k.sort( )
00460         trc_k.sort( )
00461 
00462         if type(blc_r[blc_k[0]]) != dict or type(blc_r[blc_k[1]]) != dict or \
00463                type(trc_r[trc_k[0]]) != dict or type(trc_r[trc_k[1]]) != dict :
00464             raise Exception, "invalid blc/trc in region"
00465 
00466         if not blc_r[blc_k[0]].has_key('value') or not blc_r[blc_k[1]].has_key('value') or \
00467                not trc_r[trc_k[0]].has_key('value') or not trc_r[trc_k[1]].has_key('value'):
00468             raise Exception, "invalid shape for blc/trc in region"
00469 
00470         if (type(blc_r[blc_k[0]]['value']) != float and type(blc_r[blc_k[0]]['value']) != int) or \
00471                (type(blc_r[blc_k[1]]['value']) != float and type(blc_r[blc_k[1]]['value']) != int) or \
00472                (type(trc_r[trc_k[0]]['value']) != float and type(trc_r[trc_k[0]]['value']) != int) or \
00473                (type(trc_r[trc_k[0]]['value']) != float and type(trc_r[trc_k[0]]['value']) != int) :
00474             raise Exception, "invalid type for blc/trc value in region"
00475 
00476         blc = [ float(blc_r[blc_k[0]]['value']), float(blc_r[blc_k[1]]['value']) ]
00477         trc = [ float(trc_r[trc_k[0]]['value']), float(trc_r[trc_k[1]]['value']) ]
00478 
00479         coord = "pixel"
00480         if reg.has_key('name') and reg['name'] == "WCBox":
00481             coord = "world"
00482 
00483         return ( blc, trc, coord )