casa  $Rev:20696$
 All Classes Namespaces Files Functions Variables
simutil.py
Go to the documentation of this file.
00001 # geodesy and pointing and other helper functions that are useful
00002 # to be available outside of the simdata task
00003 import casac
00004 import os
00005 import shutil
00006 import commands
00007 #import pdb
00008 # all I really need is casalog, but how to get it:?
00009 from taskinit import *
00010 import pylab as pl
00011 
00012 # qa doesn't hold state.
00013 #qatool = casac.homefinder.find_home_by_name('quantaHome')
00014 #qa = qatool.create()
00015 
00016 im, cb, ms, tb, fg, me, ia, po, sm, cl, cs, rg, sl, dc, vp, msmd, fi, fn = gentools()
00017 
00018 class compositenumber:
00019     def __init__(self, maxval=100):
00020         self.generate(maxval)
00021     def generate(self,maxval):
00022         n2 = int(log(float(maxval))/log(2.0) + 1) +1
00023         n3 = int(log(float(maxval))/log(3.0) + 1) +1
00024         n5 = int(log(float(maxval))/log(5.0) + 1) +1
00025         itsnumbers=pl.zeros(n2*n3*n5)
00026         n = 0
00027         for i2 in range(n2):
00028             for i3 in range(n3):
00029                 for i5 in range(n5):
00030                     composite=( 2.**i2 * 3.**i3 * 5.**i5 )
00031                     itsnumbers[n] = composite
00032                     #print i2,i3,i5,composite
00033                     n=n+1
00034         itsnumbers.sort()
00035         maxi=0
00036         while maxi<(n2*n3*n5) and itsnumbers[maxi]<=maxval: maxi=maxi+1
00037         self.itsnumbers=pl.int64(itsnumbers[0:maxi])
00038     def list(self):
00039         print self.itsnumbers
00040     def nextlarger(self,x):
00041         if x>max(self.itsnumbers): self.generate(2*x)
00042         xi=0
00043         n=self.itsnumbers.__len__()
00044         while xi<n and self.itsnumbers[xi]<x: xi=xi+1
00045         return self.itsnumbers[xi]
00046 
00047 
00048 
00049 
00050 
00051 class simutil:
00052     def __init__(self, direction="",
00053                  centerfreq=qa.quantity("245GHz"),
00054                  bandwidth=qa.quantity("1GHz"),
00055                  totaltime=qa.quantity("0h"),
00056                  verbose=False):
00057         self.direction=direction
00058         self.verbose=verbose
00059         self.centerfreq=centerfreq
00060         self.bandwidth=bandwidth
00061         self.totaltime=totaltime
00062         self.pmulti=0  # rows, cols, currsubplot
00063 
00064 
00065     def newfig(self,multi=0,show=True):  # new graphics window/file
00066         if show:
00067             pl.ion() # creates a fig if ness
00068         else:
00069             pl.ioff() 
00070         pl.clf()
00071 
00072         if multi!=0:
00073             if type(multi)!=type([]):
00074                 self.msg("internal error setting multi-panel figure with multi="+str(multi),priority="warn")
00075             if len(multi)!=3:
00076                 self.msg("internal error setting multi-panel figure with multi="+str(multi),priority="warn")
00077             self.pmulti=multi
00078             pl.subplot(multi[0],multi[1],multi[2])
00079             pl.subplots_adjust(left=0.05,right=0.98,bottom=0.09,top=0.95,hspace=0.2,wspace=0.2)
00080 
00081 
00082     ###########################################################
00083 
00084     def nextfig(self): # advance subwindow
00085         ax=pl.gca()
00086         l=ax.get_xticklabels()
00087         pl.setp(l,fontsize="x-small")
00088         l=ax.get_yticklabels()
00089         pl.setp(l,fontsize="x-small")
00090         if self.pmulti!=0:
00091             self.pmulti[2] += 1
00092             multi=self.pmulti
00093             if multi[2] <= multi[0]*multi[1]:
00094                 pl.subplot(multi[0],multi[1],multi[2])
00095         # consider pl.draw() here - may be slow
00096 
00097     ###########################################################
00098 
00099     def endfig(self,show=True,filename=""): # set margins to smaller, save to file if required        
00100         ax=pl.gca()
00101         l=ax.get_xticklabels()
00102         pl.setp(l,fontsize="x-small")
00103         l=ax.get_yticklabels()
00104         pl.setp(l,fontsize="x-small")
00105         if show:
00106             pl.draw()
00107         if len(filename)>0:
00108             pl.savefig(filename)
00109         pl.ioff()
00110         
00111 
00112         
00113     ###########################################################
00114 
00115     def msg(self, s, origin=None, priority=None):
00116         # ansi color codes:
00117         # Foreground colors
00118         # 30    Black
00119         # 31    Red
00120         # 32    Green
00121         # 33    Yellow
00122         # 34    Blue
00123         # 35    Magenta
00124         # 36    Cyan
00125         # 37    White
00126         toterm=False
00127         if priority==None:
00128             clr="\x1b[32m"
00129             priority="INFO"
00130         else:
00131             priority=priority.upper()
00132             if priority=="WARN":
00133                 clr="\x1b[35m"                
00134                 toterm=True
00135                 priority="INFO" # otherwise casalog will spew to term also.
00136             else:
00137                 if priority=="ERROR":
00138                     clr="\x1b[31m"
00139                     toterm=False  # casalog spews severe to term already
00140                 else:
00141                     if not (priority=="DEBUG" or priority[:-1]=="DEBUG"):
00142                         priority="INFO"
00143         bw="\x1b[0m"
00144         if origin==None:
00145             origin="simutil"            
00146         if toterm:
00147             print clr+"["+origin+"] "+bw+s
00148         if priority=="ERROR":
00149             #return False
00150             raise Exception, s
00151         else:
00152             casalog.post(s,priority=priority,origin=origin)
00153 
00154 
00155 
00156     ###########################################################
00157 
00158     def isquantity(self,s,halt=True):
00159         if type(s)!=type([]):
00160             t=[s]
00161         else:
00162             t=s
00163         for t0 in t:
00164             if not (len(t0)>0 and qa.isquantity(t0)):
00165                 if halt:
00166                     self.msg("can't interpret '"+str(t0)+"' as a CASA quantity",priority="error")
00167                 return False
00168         return True
00169 
00170     ###########################################################
00171 
00172     def isdirection(self,s,halt=True):
00173         if type(s)==type([]):
00174             t=s[0]
00175         else:
00176             t=s
00177         try:
00178             x=self.direction_splitter(s)
00179             y=me.direction(x[0],x[1],x[2])
00180         except:
00181             if halt:
00182                 self.msg("can't interpret '"+str(s)+"' as a direction",priority="error")
00183             return False
00184         if not me.measure(y,y['refer']):
00185             if halt:
00186                 self.msg("can't interpret '"+str(s)+"' as a direction",priority="error")
00187             return False
00188         return True
00189 
00190     ###########################################################
00191 
00192     def ismstp(self,s,halt=False):
00193         try:
00194             istp = False
00195             # check if the ms is tp data or not.
00196             tb.open(s+'/ANTENNA')
00197             antname = tb.getcol('NAME')
00198             tb.close()
00199             if antname[0].find('TP') > -1:
00200                 istp = True
00201             elif len(antname) == 1:
00202                 istp = True
00203             else:
00204                 # need complete testing of UVW
00205                 tb.open(s)
00206                 uvw = tb.getcol("UVW")
00207                 tb.close()
00208                 if uvw.all() == 0.:
00209                     istp = True 
00210         except:
00211             if halt:
00212                 self.msg("can't understand the file '"+str(s)+"'",priority="error")
00213             return False
00214         if not istp: 
00215             if halt:
00216                 self.msg("input file '"+str(s)+"' is not a totalpower ms",priority="error")
00217             return False
00218         return True
00219         
00220 
00221 
00222     ###########################################################
00223     # plot an image (optionally), and calculate its statistics
00224 
00225     def statim(self,image,plot=True,incell=None,disprange=None,bar=True,showstats=True):
00226         pix=self.cellsize(image)  # cell positive by convention
00227         pixarea=abs(qa.convert(pix[0],'arcsec')['value']*
00228                     qa.convert(pix[1],'arcsec')['value'])
00229         ia.open(image)       
00230         imunit=ia.brightnessunit()
00231         if imunit == 'Jy/beam':
00232             bm=ia.restoringbeam()
00233             if len(bm)>0:
00234                 toJyarcsec=1./pixarea
00235             else:
00236                 toJyarcsec=1.
00237             toJypix=toJyarcsec*pixarea
00238         elif imunit == 'Jy/pixel':
00239             toJyarcsec=1./pixarea
00240             toJypix=1.
00241         else:
00242             self.msg("%s: unknown units" % image,origin="statim")
00243             toJyarcsec=1.
00244             toJypix=1.
00245         stats=ia.statistics(robust=True,verbose=False,list=False)
00246         im_min=stats['min']*toJypix
00247         plarr=pl.zeros(1)
00248         badim=False
00249         if type(im_min)==type([]) or type(im_min)==type(plarr):
00250             if len(im_min)<1: 
00251                 badim=True
00252                 im_min=0.
00253         im_max=stats['max']*toJypix
00254         if type(im_max)==type([]) or type(im_max)==type(plarr):
00255             if len(im_max)<1: 
00256                 badim=True
00257                 im_max=1.
00258         imsize=ia.shape()[0:2]
00259         reg1=rg.box([0,0],[imsize[0]*.25,imsize[1]*.25])
00260         stats=ia.statistics(region=reg1,verbose=False,list=False)
00261         im_rms=stats['rms']*toJypix
00262         if type(im_rms)==type([]) or type(im_rms)==type(plarr):
00263             if len(im_rms)==0: 
00264                 badim=True
00265                 im_rms=0.
00266         data_array=ia.getchunk([-1,-1,1,1],[-1,-1,1,1],[1],[],True,True,False)
00267         data_array=pl.array(data_array)
00268         tdata_array=pl.transpose(data_array)
00269         ttrans_array=tdata_array.tolist()
00270         ttrans_array.reverse()
00271         if (plot):
00272             pixsize=[qa.convert(pix[0],'arcsec')['value'],qa.convert(pix[1],'arcsec')['value']]
00273             xextent=imsize[0]*abs(pixsize[0])*0.5
00274             yextent=imsize[1]*abs(pixsize[1])*0.5
00275             if self.verbose: 
00276                 self.msg("plotting %fx%f\" im with %fx%f\" pix" % 
00277                          (xextent,yextent,pixsize[0],pixsize[1]),origin="statim")
00278             xextent=[xextent,-xextent]
00279             yextent=[-yextent,yextent]
00280         # remove top .5% of pixels:
00281         nbin=200
00282         if badim:
00283             highvalue=im_max
00284             lowvalue=im_min
00285         else:
00286             imhist=ia.histograms(cumu=True,nbins=nbin,list=False)#['histout']
00287             ii=0
00288             lowcounts=imhist['counts'][ii]
00289             while imhist['counts'][ii]<0.005*lowcounts and ii<nbin: 
00290                 ii=ii+1
00291             lowvalue=imhist['values'][ii]
00292             ii=nbin-1
00293             highcounts=imhist['counts'][ii]
00294             while imhist['counts'][ii]>0.995*highcounts and ii>0 and 0.995*highcounts>lowcounts: 
00295                 ii=ii-1
00296             highvalue=imhist['values'][ii]
00297         if disprange != None:
00298             if type(disprange)==type([]):
00299                 if len(disprange)>0:
00300                     highvalue=disprange[-1]
00301                     if len(disprange)>1:
00302                         lowvalue=disprange[0]
00303                         if len(disprange)>2:
00304                             throw("internal error disprange="+str(disprange)+" has too many elements")
00305                 else:  # if passed an empty list [], return low.high
00306                     disprange.append(lowvalue)
00307                     disprange.append(highvalue)
00308             else:
00309                 highvalue=disprange  # assume if scalar passed its the max
00310 
00311         if plot:
00312             img=pl.imshow(ttrans_array,interpolation='bilinear',cmap=pl.cm.jet,extent=xextent+yextent,vmax=highvalue,vmin=lowvalue)            
00313             ax=pl.gca()
00314             #l=ax.get_xticklabels()
00315             #pl.setp(l,fontsize="x-small")
00316             #l=ax.get_yticklabels()
00317             #pl.setp(l,fontsize="x-small")
00318             foo=image.split("/")
00319             if len(foo)==1:
00320                 imagestrip=image
00321             else:
00322                 imagestrip=foo[1]
00323             pl.title(imagestrip,fontsize="x-small")
00324             if showstats:
00325                 pl.text(0.05,0.95,"min=%7.1e\nmax=%7.1e\nRMS=%7.1e\n%s" % (im_min,im_max,im_rms,imunit),transform = ax.transAxes,bbox=dict(facecolor='white', alpha=0.7),size="x-small",verticalalignment="top")
00326             if bar:
00327                 cb=pl.colorbar(pad=0)
00328                 cl = pl.getp(cb.ax,'yticklabels')
00329                 pl.setp(cl,fontsize='x-small')
00330         ia.done()
00331         return im_min,im_max,im_rms,imunit
00332 
00333 
00334 
00335 
00336 
00337 
00338 
00339     ###########################################################
00340 
00341     def calc_pointings2(self, spacing, size, maptype="hex", direction=None, relmargin=0.5, beam=0.):   
00342         """
00343         If direction is a list, simply returns direction and the number of
00344         pointings in it.
00345         
00346         Otherwise, returns a hexagonally packed list of pointings separated by
00347         spacing and fitting inside an area specified by direction and mapsize, 
00348         as well as the number of pointings.  The hexagonal packing starts with a
00349         horizontal row centered on direction, and the other rows alternate
00350         being horizontally offset by a half spacing.  
00351         """
00352         # make size 2-dimensional and ensure it is quantity
00353         if type(size) != type([]):
00354             size=[size,size]
00355         if len(size) <2:
00356             size=[size[0],size[0]]
00357         self.isquantity(size)
00358 
00359         # parse and check direction
00360         if direction==None:
00361             # if no direction is specified, use the object's direction
00362             direction=self.direction
00363         else:
00364             # if one is specified, use it to set the object's direction
00365             self.direction=direction
00366         self.isdirection(direction)
00367 
00368         # direction is always a list of strings (defined by .xml)
00369         if type(direction)==type([]):
00370             if len(direction) > 1:                
00371                 if self.verbose: self.msg("you are inputing the precise pointings in 'direction' - if you want to calculate a mosaic, give a single direction string and set maptype",priority="warn")
00372                 return direction
00373             else: direction=direction[0]
00374 
00375 
00376         # haveing eliminated other options, we need to calculate:
00377         epoch, centx, centy = self.direction_splitter()
00378 
00379         shorttype=str.upper(maptype[0:3])
00380 #        if not shorttype=="HEX":
00381 #            self.msg("can't calculate map of maptype "+maptype,priority="error")
00382         if shorttype == "HEX": 
00383             # this is hexagonal grid - Kana will add other types here
00384             self.isquantity(spacing)
00385             spacing  = qa.quantity(spacing)
00386             yspacing = qa.mul(0.866025404, spacing)
00387             
00388             xsize=qa.quantity(size[0])
00389             ysize=qa.quantity(size[1])
00390 
00391             nrows = 1+ int(pl.floor(qa.convert(qa.div(ysize, yspacing), '')['value']
00392                                     - 2.309401077 * relmargin))
00393 
00394             availcols = 1 + qa.convert(qa.div(xsize, spacing),
00395                                        '')['value'] - 2.0 * relmargin
00396             ncols = int(pl.floor(availcols))
00397 
00398             # By making the even rows shifted spacing/2 ahead, and possibly shorter,
00399             # the top and bottom rows (nrows odd), are guaranteed to be short.
00400             if availcols - ncols >= 0.5 and nrows>1:                            # O O O
00401                 evencols = ncols                                    #  O O O
00402                 ncolstomin = 0.5 * (ncols - 0.5)
00403             else:
00404                 evencols = ncols - 1                                #  O O 
00405                 ncolstomin = 0.5 * (ncols - 1)                      # O O O
00406             pointings = []
00407 
00408             # Start from the top because in the Southern hemisphere it sets first.
00409             y = qa.add(centy, qa.mul(0.5 * (nrows - 1), yspacing))
00410             for row in xrange(0, nrows):         # xrange stops early.
00411                 xspacing = qa.mul(1.0 / pl.cos(qa.convert(y, 'rad')['value']),spacing)
00412                 ystr = qa.formxxx(y, format='dms',prec=5)
00413                 
00414                 if row % 2:                             # Odd
00415                     xmin = qa.sub(centx, qa.mul(ncolstomin, xspacing))
00416                     stopcolp1 = ncols
00417                 else:                                   # Even (including 0)
00418                     xmin = qa.sub(centx, qa.mul(ncolstomin - 0.5,
00419                                                 xspacing))
00420                     stopcolp1 = evencols
00421                 for col in xrange(0, stopcolp1):        # xrange stops early.
00422                     x = qa.formxxx(qa.add(xmin, qa.mul(col, xspacing)),
00423                                    format='hms',prec=5)
00424                     pointings.append("%s%s %s" % (epoch, x, ystr))
00425                 y = qa.sub(y, yspacing)
00426         elif shorttype =="SQU":
00427             # lattice gridding
00428             self.isquantity(spacing)
00429             spacing  = qa.quantity(spacing)
00430             yspacing = spacing
00431     
00432             xsize=qa.quantity(size[0])
00433             ysize=qa.quantity(size[1])
00434 
00435             nrows = 1+ int(pl.floor(qa.convert(qa.div(ysize, yspacing), '')['value']
00436                                     - 2.0 * relmargin))
00437 
00438             availcols = 1 + qa.convert(qa.div(xsize, spacing), '')['value'] \
00439                         - 2.0 * relmargin
00440             ncols = int(pl.floor(availcols))
00441 
00442 
00443             ncolstomin = 0.5 * (ncols - 1)                           # O O O O
00444             pointings = []                                           # O O O O
00445 
00446             # Start from the top because in the Southern hemisphere it sets first.
00447             y = qa.add(centy, qa.mul(0.5 * (nrows - 1), yspacing))
00448             for row in xrange(0, nrows):         # xrange stops early.
00449                 xspacing = qa.mul(1.0 / pl.cos(qa.convert(y, 'rad')['value']),spacing)
00450                 ystr = qa.formxxx(y, format='dms',prec=5)
00451 
00452                 xmin = qa.sub(centx, qa.mul(ncolstomin, xspacing))
00453                 stopcolp1 = ncols
00454         
00455                 for col in xrange(0, stopcolp1):        # xrange stops early.
00456                     x = qa.formxxx(qa.add(xmin, qa.mul(col, xspacing)),
00457                                    format='hms',prec=5)
00458                     pointings.append("%s%s %s" % (epoch, x, ystr))
00459                 y = qa.sub(y, yspacing)
00460         if shorttype == "ALM": 
00461             # OT algorithm
00462             self.isquantity(spacing)
00463             spacing  = qa.quantity(spacing)
00464             xsize = qa.quantity(size[0])
00465             ysize = qa.quantity(size[1])
00466 
00467             spacing_asec=qa.convert(spacing,'arcsec')['value']
00468             xsize_asec=qa.convert(xsize,'arcsec')['value']
00469             ysize_asec=qa.convert(ysize,'arcsec')['value']
00470             angle = 0. # deg
00471 
00472             if str.upper(maptype[0:8]) == 'ALMA2012':
00473                 x,y = self.getTrianglePoints(xsize_asec, ysize_asec, angle, spacing_asec)
00474             else:
00475                 if beam<=0: 
00476                     beam=spacing_asec*1.2*sqrt(3) # ASSUMES Nyquist and arcsec
00477                 x,y = self.getTriangularTiling(xsize_asec, ysize_asec, angle, spacing_asec, beam)
00478 
00479             pointings = []
00480             nx=len(x)
00481             for i in range(nx):
00482             # Start from the top because in the Southern hemisphere it sets first.
00483                 y1=qa.add(centy, str(y[nx-i-1])+"arcsec")
00484                 ycos=pl.cos(qa.convert(y1,"rad")['value'])
00485                 ystr = qa.formxxx(y1, format='dms',prec=5)
00486                 xstr = qa.formxxx(qa.add(centx, str(x[nx-i-1]/ycos)+"arcsec"), format='hms',prec=5)
00487                 pointings.append("%s%s %s" % (epoch, xstr, ystr))
00488             
00489 
00490         # if could not fit any pointings, then return single pointing
00491         if(len(pointings)==0):
00492             pointings.append(direction)
00493 
00494         self.msg("using %i generated pointing(s)" % len(pointings))
00495         self.pointings=pointings
00496         return pointings
00497 
00498 
00499 
00500 
00501 
00502 
00503 
00504 
00505 
00506 
00507     ###########################################################
00508 
00509     def read_pointings(self, filename):
00510         """
00511         read pointing list from file containing epoch, ra, dec,
00512         and scan time (optional,in sec).
00513         Parameter:
00514              filename:  (str) the name of input file
00515        
00516         The input file (ASCII) should contain at least 3 fields separated
00517         by a space which specify positions with epoch, ra and dec (in dms
00518         or hms).
00519         The optional field, time, shoud be a list of decimal numbers
00520         which specifies integration time at each position in second.
00521         The lines which start with '#' is ignored and can be used
00522         as comment lines. 
00523         
00524         Example of an input file:
00525         #Epoch     RA          DEC      TIME(optional)
00526         J2000 23h59m28.10 -019d52m12.35 10.0
00527         J2000 23h59m32.35 -019d52m12.35 10.0
00528         J2000 23h59m36.61 -019d52m12.35 60.0
00529         
00530         """
00531         f=open(filename)
00532         line= '  '
00533         time=[]
00534         pointings=[]
00535 
00536         # add option of different epoch in a header line like read_antenna?
00537 
00538         while (len(line)>0):
00539             try: 
00540                 line=f.readline()
00541                 if not line.startswith('#'):
00542                 ### ignoring line that has less than 3 elements
00543                      if(len(line.split()) >2):
00544                         splitline=line.split()
00545                         epoch=splitline[0]
00546                         ra0=splitline[1]
00547                         de0=splitline[2]
00548                         if len(splitline)>3:
00549                             time.append(float(splitline[3]))
00550                         else:
00551                             time.append(0.)
00552                         xstr = qa.formxxx(qa.quantity(ra0), format='hms',prec=5)
00553                         ystr = qa.formxxx(qa.quantity(de0), format='dms',prec=5)
00554                         pointings.append("%s %s %s" % (epoch,xstr,ystr))
00555             except:
00556                 break
00557         f.close()
00558 
00559         # need an error check here if zero valid pointings were read
00560         if len(pointings) < 1:
00561             s="No valid lines found in pointing file"
00562             self.msg(s,priority="error")
00563         self.msg("read in %i pointing(s) from file" % len(pointings))
00564         self.pointings=pointings
00565         #self.direction=pointings
00566                 
00567         return len(pointings), pointings, time
00568 
00569     
00570 
00571 
00572 
00573 
00574 
00575     ###########################################################
00576 
00577     def write_pointings(self, filename,pointings,time=1.):
00578         """
00579         write pointing list to file containing epoch, ra, dec,
00580         and scan time (optional,in sec).
00581         
00582         Example of an output file:
00583         #Epoch     RA          DEC      TIME(optional)
00584         J2000 23h59m28.10 -019d52m12.35 10.0
00585         J2000 23h59m32.35 -019d52m12.35 10.0
00586         J2000 23h59m36.61 -019d52m12.35 60.0
00587         
00588         """
00589         f=open(filename,"write")
00590         f.write('#Epoch     RA          DEC      TIME[sec]\n')
00591         if type(pointings)!=type([]):
00592             pointings=[pointings]
00593         npos=len(pointings)
00594         if type(time)!=type([]):
00595             time=[time]
00596         if len(time)==1:
00597             time=list(time[0] for x in range(npos))
00598 
00599         for i in range(npos):
00600             #epoch,ra,dec=direction_splitter(pointings[i])
00601             #xstr = qa.formxxx(qa.quantity(ra), format='hms')
00602             #ystr = qa.formxxx(qa.quantity(dec), format='dms')
00603             #line = "%s %s %s" % (epoch,xstr,ystr)
00604             #self.isdirection(line)  # extra check
00605             #f.write(line+"  "+str(time[i])+"\n")
00606             f.write(pointings[i]+"  "+str(time[i])+"\n")
00607 
00608         f.close()
00609         return 
00610     
00611 
00612 
00613     ###########################################################
00614 
00615     def average_direction(self, directions=None):
00616         # RI TODO make deal with list of measures as well as list of strings
00617         """
00618         Returns the average of directions as a string, and relative offsets
00619         """
00620         if directions==None:
00621             directions=self.direction
00622         epoch0, x, y = self.direction_splitter(directions[0])
00623         i = 1
00624         avgx = 0.0
00625         avgy = 0.0
00626         for drn in directions:
00627             epoch, x, y = self.direction_splitter(drn)
00628             # in principle direction_splitter returns directions in degrees,
00629             # but can we be sure?
00630             x=qa.convert(x,'deg')
00631             y=qa.convert(y,'deg')
00632             x = x['value']
00633             y = y['value']
00634             if epoch != epoch0:                     # Paranoia
00635                 print "[simutil] WARN: precession not handled by average_direction()"
00636             x = self.wrapang(x, avgx, 360.0)
00637             avgx += (x - avgx) / i
00638             avgy += (y - avgy) / i
00639             i += 1
00640         offsets=pl.zeros([2,i-1])
00641         i=0
00642         cosdec=pl.cos(avgy*pl.pi/180.)
00643         for drn in directions:
00644             epoch, x, y = self.direction_splitter(drn)
00645             x=qa.convert(x,'deg')
00646             y=qa.convert(y,'deg')
00647             x = x['value']
00648             y = y['value']
00649             x = self.wrapang(x, avgx, 360.0)
00650             offsets[:,i]=[(x-avgx)*cosdec,y-avgy]  # apply cosdec to make offsets on sky
00651             i+=1
00652         avgx = qa.toangle('%17.12fdeg' % avgx)
00653         avgy = qa.toangle('%17.12fdeg' % avgy)
00654         avgx = qa.formxxx(avgx, format='hms',prec=5)
00655         avgy = qa.formxxx(avgy, format='dms',prec=5)
00656         return "%s%s %s" % (epoch0, avgx, avgy), offsets
00657 
00658 
00659 
00660     ###########################################################
00661 
00662     def median_direction(self, directions=None):
00663         # RI TODO make deal with list of measures as well as list of strings
00664         """
00665         Returns the median of directions as a string, and relative offsets
00666         """
00667         if directions==None:
00668             directions=self.direction
00669         epoch0, x, y = self.direction_splitter(directions[0])
00670         i = 1
00671         avgx = 0.0
00672         avgy = 0.0
00673         xx=[]
00674         yy=[]
00675         for drn in directions:            
00676             epoch, x, y = self.direction_splitter(drn)
00677             # in principle direction_splitter returns directions in degrees,
00678             # but can we be sure?
00679             x=qa.convert(x,'deg')
00680             y=qa.convert(y,'deg')
00681             x = x['value']
00682             y = y['value']
00683             if epoch != epoch0:                     # Paranoia
00684                 print "[simutil] WARN: precession not handled by average_direction()"
00685             x = self.wrapang(x, avgx, 360.0)
00686             xx.append(x)
00687             yy.append(y)
00688             i += 1
00689         avgx = pl.median(xx)
00690         avgy = pl.median(yy)
00691         offsets=pl.zeros([2,i-1])
00692         i=0
00693         cosdec=pl.cos(avgy*pl.pi/180.)
00694         for drn in directions:
00695             epoch, x, y = self.direction_splitter(drn)
00696             x=qa.convert(x,'deg')
00697             y=qa.convert(y,'deg')
00698             x = x['value']
00699             y = y['value']
00700             x = self.wrapang(x, avgx, 360.0)
00701             offsets[:,i]=[(x-avgx)*cosdec,y-avgy]  # apply cosdec to make offsets on sky
00702             i+=1
00703         avgx = qa.toangle('%17.12fdeg' % avgx)
00704         avgy = qa.toangle('%17.12fdeg' % avgy)
00705         avgx = qa.formxxx(avgx, format='hms',prec=5)
00706         avgy = qa.formxxx(avgy, format='dms',prec=5)
00707         return "%s%s %s" % (epoch0, avgx, avgy), offsets
00708 
00709 
00710 
00711     ###########################################################
00712 
00713     def direction_splitter(self, direction=None):
00714         """
00715         Given a direction, return its epoch, x, and y parts.  Epoch will be ''
00716         if absent, or '%s ' % epoch if present.  x and y will be angle qa's in
00717         degrees.
00718         """
00719         import re
00720         if direction == None:
00721             direction=self.direction
00722         if type(direction) == type([]):
00723             direction=self.average_direction(direction)[0]
00724         dirl = direction.split()
00725         if len(dirl) == 3:
00726             epoch = dirl[0] + ' '
00727         else:
00728             epoch = ''
00729         # x, y = map(qa.toangle, dirl[-2:])
00730         x=qa.toangle(dirl[1])
00731         # qa is stupid when it comes to dms vs hms, and assumes hms with colons and dms for periods.  
00732         decstr=dirl[2]
00733         # parse with regex to get three numbers and reinstall them as dms
00734         q=re.compile('([+-]?\d+).(\d+).(\d+\.?\d*)')
00735         qq=q.match(decstr)
00736         z=qq.groups()
00737         decstr=z[0]+'d'
00738         if len(z)>1:
00739             decstr=decstr+z[1]+'m'
00740         if len(z)>2:
00741             decstr=decstr+z[2]+'s'
00742         y=qa.toangle(decstr)
00743 
00744         return epoch, qa.convert(x, 'deg'), qa.convert(y, 'deg')
00745 
00746 
00747     ###########################################################
00748 
00749     def dir_s2m(self, direction=None):
00750         """
00751         Given a direction as a string 'refcode lon lat', return it as qa measure.
00752         """
00753         if direction == None:
00754             direction=self.direction
00755         if type(direction) == type([]):
00756             direction=self.average_direction(direction)[0]            
00757         dirl = direction.split()
00758         if len(dirl) == 3:
00759             refcode = dirl[0] + ' '
00760         else:
00761             refcode = 'J2000'
00762             if self.verbose: self.msg("assuming J2000 for "+direction,origin="simutil.s2m")
00763         x, y = map(qa.quantity, dirl[-2:])
00764         if x['unit'] == '': x['unit']='deg'
00765         if y['unit'] == '': y['unit']='deg'
00766         return me.direction(refcode,qa.toangle(x),qa.toangle(y))
00767 
00768 
00769     ###########################################################
00770 
00771     def dir_m2s(self, dir):
00772         """
00773         Given a direction as a measure, return it as astring 'refcode lon lat'.
00774         """
00775         if dir['type'] != 'direction':
00776             self.msg("converting direction measure",priority="error",origin="simutil.m2s")
00777             return False
00778         ystr = qa.formxxx(dir['m1'], format='dms',prec=5)
00779         xstr = qa.formxxx(dir['m0'], format='hms',prec=5)
00780         return "%s %s %s" % (dir['refer'], xstr, ystr)
00781 
00782     ###########################################################
00783 
00784     def wrapang(self, ang, target, period = 360.0):
00785         """
00786         Returns ang wrapped so that it is within +-period/2 of target.
00787         """
00788         dang       = ang - target
00789         period     = pl.absolute(period)
00790         halfperiod = 0.5 * period
00791         if pl.absolute(dang) > halfperiod:
00792             nwraps = pl.floor(0.5 + float(dang) / period)
00793             ang -= nwraps * period
00794         return ang
00795     
00796 
00797 
00798 
00799 
00800 
00801 
00802 
00803 
00804 
00805     ###########################################################
00806     #========================== tsys ==========================
00807 
00808     def noisetemp(self, telescope=None, freq=None,
00809                   diam=None, epsilon=None):
00810 
00811         """
00812         Noise temperature and efficiencies for several telescopes:
00813               ALMA, ACA, EVLA, VLA, and SMA
00814         Input: telescope name, frequency as a quantity string "300GHz", 
00815                dish diameter (optional - knows diameters for arrays above)
00816                epsilon = rms surface accuracy in microns (also optional - 
00817                    this method contains the spec values for each telescope)
00818         Output: eta_p phase efficieny (from Ruze formula), 
00819                 eta_s spill (main beam) efficiency,
00820                 eta_b geometrical blockage efficiency,
00821                 eta_t taper efficiency,
00822                 eta_q correlator efficiency including quantization,
00823                 t_rx  reciever temperature.
00824         antenna efficiency = eta_p * eta_s * eta_b * eta_t
00825         Notes: VLA correlator efficiency includes waveguide loss
00826                EVLA correlator efficiency is probably optimistic at 0.88
00827         """
00828 
00829         if telescope==None: telescope=self.telescopename
00830         telescope=str.upper(telescope)
00831         
00832         obs =['ALMA','ACA','EVLA','VLA','SMA']
00833         d   =[ 12.   ,7.,   25.  , 25.  , 6. ]
00834         ds  =[ 0.75,  0.75, 0.364, 0.364,0.35] # subreflector size for ACA?
00835         eps =[ 25.,   20.,  300,   300  ,15. ] # antenna surface accuracy
00836         
00837         cq  =[ 0.845, 0.845,  0.88,  0.79, 0.86] # correlator eff
00838         # ALMA includes quantization eff of 0.96    
00839         # VLA includes additional waveguide loss from correlator loss of 0.809
00840         # EVLA is probably optimistic
00841 
00842         # things hardcoded in ALMA etimecalculator
00843         # t_ground=270.
00844         # t_cmb=2.73
00845         # eta_q*eta_corr = 0.88*.961
00846         # eta_ap = 0.72*eta_ruze
00847         
00848         if obs.count(telescope)>0:
00849             iobs=obs.index(telescope)
00850         else:
00851             if self.verbose: self.msg("I don't know much about "+telescope+" so I'm going to use ALMA specs")
00852             iobs=0 # ALMA is the default ;)
00853             
00854         if diam==None: diam=d[iobs]
00855         diam_subreflector=ds[iobs]
00856         if self.verbose: self.msg("subreflector diameter="+str(diam_subreflector),origin="noisetemp")
00857 
00858         # blockage efficiency.    
00859         eta_b = 1.-(diam_subreflector/diam)**2
00860 
00861         # spillover efficiency.    
00862         eta_s = 0.95 # these are ALMA values
00863         # taper efficiency.    
00864         #eta_t = 0.86 # these are ALMA values
00865         eta_t = 0.819 # 20100914 OT value
00866         eta_t = 0.72
00867 
00868         # Ruze phase efficiency.    
00869         if epsilon==None: epsilon = eps[iobs] # microns RMS
00870         if freq==None:
00871             freq_ghz=qa.convert(qa.quantity(self.centerfreq),'GHz')
00872             bw_ghz=qa.convert(qa.quantity(self.bandwidth),'GHz')
00873         else:            
00874             freq_ghz=qa.convert(qa.quantity(freq),'GHz')
00875         eta_p = pl.exp(-(4.0*3.1415926535*epsilon*freq_ghz.get("value")/2.99792458e5)**2)
00876         if self.verbose: self.msg("ruze phase efficiency for surface accuracy of "+str(epsilon)+"um = " + str(eta_p) + " at "+str(freq),origin="noisetemp")
00877 
00878         # antenna efficiency
00879         # eta_a = eta_p*eta_s*eta_b*eta_t
00880 
00881         # correlator quantization efficiency.    
00882         eta_q = cq[iobs]
00883 
00884         # Receiver radiation temperature in K.         
00885         if telescope=='ALMA' or telescope=='ACA':
00886             # ALMA-40.00.00.00-001-A-SPE.pdf
00887             # http://www.eso.org/sci/facilities/alma/system/frontend/
00888 
00889             # limits instead of centers, go to higher band in gaps
00890             f0=[31.3,45,84,116,163,211,275,373,500,720]
00891             # cycle 1 OT values 7/12
00892             t0=[ 17, 30, 45, 51, 65, 55, 75, 196, 100, 230]
00893 
00894             flim=[31.3,950]
00895             if self.verbose: self.msg("using ALMA/ACA Rx specs",origin="noisetemp")
00896         else:
00897             if telescope=='EVLA':
00898                 # 201009114 from rick perley:
00899                 # f0=[1.5,3,6,10,15,23,33,45]
00900                 t0=[10.,15,12,15,10,12,15,28]
00901                 # limits
00902                 f0=[1,2,4,8,12,18,26.5,40,50]
00903 
00904                 flim=[0.8,50]
00905                 if self.verbose: self.msg("using EVLA Rx specs",origin="noisetemp")
00906             else:
00907                 if telescope=='VLA':
00908                     # http://www.vla.nrao.edu/genpub/overview/
00909                     # f0=[0.0735,0.32, 1.5, 4.75, 8.4, 14.9, 23, 45 ]
00910                     # t0=[5000,  165,  56,  44,   34,  110, 110, 110]
00911                     # exclude P band for now
00912                     # f0=[0.32, 1.5, 4.75, 8.4, 14.9, 23, 45 ]
00913                     # limits
00914                     # http://www.vla.nrao.edu/genpub/overview/
00915                     f0=[0.30,0.34,1.73,5,8.8,15.4,24,50]
00916                     t0=[165,  56,  44,   34,  110, 110, 110]
00917                     flim=[0.305,50]
00918                     if self.verbose: self.msg("using old VLA Rx specs",origin="noisetemp")                    
00919                 else:
00920                     if telescope=='SMA':
00921                         # f0=[212.,310.,383.,660.]
00922                         # limits
00923                         f0=[180,250,320,420,720]
00924                         t0=[67,  116, 134, 500]
00925                         flim=[180.,720]
00926                     else:
00927                         self.msg("I don't know about the "+telescope+" receivers, using 200K",priority="warn",origin="noisetemp")
00928                         f0=[10,900]
00929                         t0=[200,200]
00930                         flim=[0,5000]
00931 
00932 
00933         obsfreq=freq_ghz.get("value")        
00934         # z=pl.where(abs(obsfreq-pl.array(f0)) == min(abs(obsfreq-pl.array(f0))))
00935         # t_rx=t0[z[0]]
00936         z=0
00937         while(f0[z]<obsfreq and z<len(t0)):
00938             z+=1
00939         t_rx=t0[z-1]
00940         
00941         if obsfreq<flim[0]:
00942             self.msg("observing freqency is lower than expected for "+telescope,priority="warn",origin="noise")
00943             self.msg("proceeding with extrapolated receiver temp="+str(t_rx),priority="warn",origin="noise")
00944         if obsfreq>flim[1]:
00945             self.msg("observing freqency is higher than expected for "+telescope,priority="warn",origin="noise")
00946             self.msg("proceeding with extrapolated receiver temp="+str(t_rx),priority="warn",origin="noise")
00947         if obsfreq<=flim[1] and obsfreq>=flim[0]:
00948             self.msg("interpolated receiver temp="+str(t_rx),origin="noise")
00949 
00950         return eta_p, eta_s, eta_b, eta_t, eta_q, t_rx
00951     
00952     
00953 
00954 
00955 
00956 
00957     def sensitivity(self, freq, bandwidth, etime, elevation, pwv=None,
00958                    telescope=None, diam=None, nant=None,
00959                    antennalist=None,
00960                    doimnoise=None,
00961                    integration=None,debug=None,
00962                    method="tsys-atm",tau0=None,t_sky=None):
00963         
00964         import glob
00965         tmpname="tmp"+str(os.getpid())
00966         i=0
00967         while i<10 and len(glob.glob(tmpname+"*"))>0:
00968             tmpname="tmp"+str(os.getpid())+str(i)
00969             i=i+1
00970         if i>=9:
00971             xx=glob.glob(tmpname+"*")
00972             for k in range(len(xx)):
00973                 if os.path.isdir(xx[k]):
00974                     cu.removetable(xx[k])
00975                 else:
00976                     os.remove(xx[k])
00977  
00978         msfile=tmpname+".ms"
00979         sm.open(msfile)
00980 
00981         rxtype=0 # 2SB
00982         if antennalist==None:
00983             if telescope==None:
00984                 self.msg("Telescope name has not been set.",priority="error")
00985                 return False 
00986             if diam==None:
00987                 self.msg("Antenna diameter has not been set.",priority="error")
00988                 return False
00989             if nant==None:
00990                 self.msg("Number of antennas has not been set.",priority="error")
00991                 return False
00992                
00993             posobs=me.observatory(telescope)
00994             obs=me.measure(posobs,'WGS84')
00995             obslat=qa.convert(obs['m1'],'deg')['value']
00996             obslon=qa.convert(obs['m0'],'deg')['value']
00997             obsalt=qa.convert(obs['m2'],'m')['value']
00998             stnx,stny,stnz = self.locxyz2itrf(obslat,obslon,0,0,obsalt)
00999             antnames="A00"
01000 
01001         else:
01002             if str.upper(antennalist[0:4])=="ALMA":
01003                 tail=antennalist[5:]
01004                 if self.isquantity(tail,halt=False):
01005                     resl=qa.convert(tail,"arcsec")['value']
01006                     repodir=os.getenv("CASAPATH").split(' ')[0]+"/data/alma/simmos/"
01007                     if os.path.exists(repodir):
01008                         confnum=(2.867-pl.log10(resl*1000*qa.convert(freq,"GHz")['value']/672.))/0.0721
01009                         confnum=max(1,min(28,confnum))
01010                         conf=str(int(round(confnum)))
01011                         if len(conf)<2: conf='0'+conf
01012                         antennalist=repodir+"alma.out"+conf+".cfg"
01013                         self.msg("converted resolution to antennalist "+antennalist)
01014 
01015             if os.path.exists(antennalist):
01016                 stnx, stny, stnz, stnd, padnames, nant, telescope = self.readantenna(antennalist)
01017             else:
01018                 self.msg("antennalist "+antennalist+" not found",priority="error")
01019                 return False
01020 
01021             # RI TODO average antenna instead of first?
01022             diam = stnd[0]
01023             antnames=padnames
01024 
01025             posobs=me.observatory(telescope)
01026             obs=me.measure(posobs,'WGS84')
01027             #obslat=qa.convert(obs['m1'],'deg')['value']
01028             #obslon=qa.convert(obs['m0'],'deg')['value']
01029             #obsalt=qa.convert(obs['m2'],'m')['value']
01030 
01031  
01032         if (telescope==None or diam==None):
01033             self.msg("Telescope name and antenna diameter have not been set.",priority="error")
01034             return False
01035 
01036         # copied from task_simdata:
01037 
01038         self.setcfg(sm, telescope, stnx, stny, stnz, diam,
01039                     padnames, posobs)
01040                 
01041         model_nchan=1
01042         # RI TODO isquantity checks
01043         model_width=qa.quantity(bandwidth) # note: ATM uses band center
01044 
01045         # start is center of first channel.  for nch=1, that equals center
01046         model_start=qa.quantity(freq)
01047 
01048         stokes, feeds = self.polsettings(telescope)
01049         sm.setspwindow(spwname="band1", freq=qa.tos(model_start), 
01050                        deltafreq=qa.tos(model_width), 
01051                        freqresolution=qa.tos(model_width), 
01052                        nchannels=model_nchan, stokes=stokes)
01053         sm.setfeed(mode=feeds, pol=[''])
01054 
01055         sm.setlimits(shadowlimit=0.01, elevationlimit='10deg')
01056         sm.setauto(0.0)
01057 
01058         obslat=qa.convert(obs['m1'],'deg')
01059         dec=qa.add(obslat, qa.add(qa.quantity("90deg"),qa.mul(elevation,-1)))
01060 
01061         sm.setfield(sourcename="src1", 
01062                     sourcedirection="J2000 00:00:00.00 "+qa.angle(dec)[0],
01063                     calcode="OBJ", distance='0m')
01064         reftime = me.epoch('TAI', "2012/01/01/00:00:00")
01065         if integration==None:
01066             integration=qa.mul(etime,0.01)        
01067         self.msg("observing for "+qa.tos(etime)+" with integration="+qa.tos(integration))
01068         sm.settimes(integrationtime=integration, usehourangle=True, 
01069                     referencetime=reftime)
01070 
01071         sm.observe(sourcename="src1", spwname="band1",
01072                    starttime=qa.quantity(0, "s"),
01073                    stoptime=qa.quantity(etime));
01074         
01075         sm.setdata()
01076         sm.setvp()
01077         
01078         eta_p, eta_s, eta_b, eta_t, eta_q, t_rx = self.noisetemp(telescope=telescope,freq=freq)
01079         eta_a = eta_p * eta_s * eta_b * eta_t
01080         if self.verbose: 
01081             self.msg('antenna efficiency    = ' + str(eta_a),origin="noise")
01082             self.msg('spillover efficiency  = ' + str(eta_s),origin="noise")
01083             self.msg('correlator efficiency = ' + str(eta_q),origin="noise")
01084         
01085         if pwv==None:
01086             # RI TODO choose based on freq octile
01087             pwv=2.0
01088 
01089         # things hardcoded in ALMA etimecalculator, & defaults in simulator.xml
01090         t_ground=270.
01091         t_cmb=2.725
01092         # eta_q = 0.88
01093         # eta_a = 0.95*0.8*eta_s
01094 
01095         if telescope=='ALMA' and (qa.convert(freq,"GHz")['value'])>600.:
01096             rxtype=1 # DSB
01097         
01098         if method=="tsys-atm":
01099             sm.setnoise(spillefficiency=eta_s,correfficiency=eta_q,
01100                         antefficiency=eta_a,trx=t_rx,
01101                         tground=t_ground,tcmb=t_cmb,pwv=str(pwv)+"mm",
01102                         mode="tsys-atm",table=tmpname,rxtype=rxtype)
01103         else:
01104             if method=="tsys-manual":
01105                 if not t_sky:
01106                     t_sky=200.
01107                     self.msg("Warning: Sky brightness temp not set, using 200K",origin="noise",priority="warn")
01108                 sm.setnoise(spillefficiency=eta_s,correfficiency=eta_q,
01109                             antefficiency=eta_a,trx=t_rx,tatmos=t_sky,
01110                             tground=t_ground,tcmb=t_cmb,tau=tau0,
01111                             mode="tsys-manual",table=tmpname,rxtype=rxtype)
01112             else:
01113                 self.msg("Unknown calculation method "+method,priority="error")
01114                 return False
01115         
01116         if doimnoise:
01117             sm.corrupt()
01118 
01119         sm.done()
01120 
01121         
01122         if doimnoise:
01123             cellsize=qa.quantity(6.e3/250./qa.convert(model_start,"GHz")["value"],"arcsec")  # need better cell determination - 250m?!
01124             cellsize=[cellsize,cellsize]
01125             # very light clean - its an empty image!
01126             self.imclean(tmpname+".ms",tmpname,
01127                        "csclean",cellsize,[128,128],
01128                        "J2000 00:00:00.00 "+qa.angle(dec)[0],
01129                        100,"0.01mJy","natural",[],"I")
01130 
01131             ia.open(tmpname+".image")
01132             stats= ia.statistics(robust=True, verbose=False,list=False)
01133             ia.done()
01134             imnoise=(stats["rms"][0])
01135         else:
01136             imnoise=0.
01137 
01138         nint = qa.convert(etime,'s')['value'] / qa.convert(integration,'s')['value'] 
01139         nbase= 0.5*nant*(nant-1)
01140                 
01141         if os.path.exists(tmpname+".T.cal"):
01142             tb.open(tmpname+".T.cal")
01143             gain=tb.getcol("CPARAM")
01144             # RI TODO average instead of first?
01145             tb.done()
01146             # gain is per ANT so square for per baseline;  
01147             # pick a gain from about the middle of the track
01148             noiseperbase=1./(gain[0][0][0.5*nint*nant].real)**2
01149         else:
01150             noiseperbase=0.
01151 
01152         theoreticalnoise=noiseperbase/pl.sqrt(nint*nbase*2) # assume 2-poln
01153         
01154         if debug==None:
01155             xx=glob.glob(tmpname+"*")
01156             for k in range(len(xx)):
01157                 if os.path.isdir(xx[k]):
01158                     cu.removetable(xx[k])
01159                 else:
01160                     os.remove(xx[k])
01161 
01162         if doimnoise:
01163             return theoreticalnoise , imnoise
01164         else:
01165             return theoreticalnoise 
01166 
01167 
01168     def setcfg(self, mysm, telescope, x, y, z, diam,
01169                padnames, posobs, mounttype=None):
01170         """
01171         Sets the antenna positions for the mysm sm instance, which should have
01172         already opened an MS, given
01173         telescope - telescope name
01174         x         - array of X positions, i.e. stnx from readantenna
01175         y         - array of Y positions, i.e. stny from readantenna
01176         z         - array of Z positions, i.e. stnz from readantenna
01177         diam      - numpy.array of antenna diameters, i.e. from readantenna
01178         padnames  - list of pad names
01179         posobs    - The observatory position as a measure.
01180 
01181         Optional:
01182         mounttype   (Defaults to a guess based on telescope.)
01183 
01184         Returns the mounttype that it uses.
01185 
01186         Closes mysm and throws a ValueError if it can't set the configuration.
01187         """
01188         if not mounttype:
01189             mounttype = 'alt-az'
01190             if telescope.upper() in ('ASKAP',  # Effectively, if not BIZARRE.
01191                                      'DRAO',
01192                                      'WSRT'):
01193                 mounttype = 'EQUATORIAL'
01194             elif telescope.upper() in ('LOFAR', 'LWA', 'MOST'):
01195                 # And other long wavelength arrays...like that one in WA that
01196                 # has 3 orthogonal feeds so they can go to the horizon.
01197                 #
01198                 # The SKA should go here too once people accept
01199                 # that it will have to be correlated as stations.
01200                 mounttype = 'BIZARRE'  # Ideally it would be 'FIXED' or
01201                                        # 'GROUND'.
01202 
01203         if not mysm.setconfig(telescopename=telescope, x=x, y=y, z=z,
01204                               dishdiameter=diam.tolist(), 
01205                               mount=[mounttype], padname=padnames,
01206                               coordsystem='global', referencelocation=posobs):
01207             mysm.close()
01208             raise ValueError("Error setting the configuration")
01209         return mounttype
01210 
01211 
01212 
01213 
01214     ###########################################################
01215     #===================== ephemeris ==========================
01216 
01217 
01218     def ephemeris(self, date, usehourangle=True, direction=None, telescope=None, ms=None):
01219 
01220         if direction==None: direction=self.direction
01221         if telescope==None: telescope=self.telescopename
01222 
01223         import pdb
01224         
01225         # right now, simdata centers at the transit;  when that changes,
01226         # or when that becomes optional, then that option needs to be
01227         # stored in the simutil object and used here, to either
01228         # center the plot at transit or not.
01229         #
01230         # direction="J2000 18h00m00.03s -45d59m59.6s"
01231         # refdate="2012/06/21/03:25:00"
01232 
01233         # useHourAngle_p means simulate at transit
01234         # TODO: put in reftime parameter, parse 2012/05/21, 2012/05/21/transit,
01235         # and 2012/05/21/22:05:00 separately.
01236         
01237         ds=self.direction_splitter(direction)  # if list, returns average
01238         src=me.direction(ds[0],ds[1],ds[2])
01239     
01240         me.done()
01241         me.doframe(me.observatory(telescope))
01242     
01243         time=me.epoch('TAI',date)
01244         me.doframe(time)
01245 
01246         # what is HA of source at refdate? 
01247         offset_ha=qa.convert((me.measure(src,'hadec'))['m0'],'h')
01248         peak=me.epoch("TAI",qa.add(date,qa.mul(-1,offset_ha)))
01249         peaktime_float=peak['m0']['value']
01250         if usehourangle:
01251             # offset the reftime to be at transit:
01252             time=peak
01253             me.doframe(time)
01254                         
01255         reftime_float=time['m0']['value']
01256         reftime_floor=pl.floor(time['m0']['value'])
01257         refdate_str=qa.time(qa.totime(str(reftime_floor)+'d'),form='dmy')[0]
01258 
01259         timeinc='15min'  # for plotting
01260         timeinc=qa.convert(qa.time(timeinc)[0],'d')['value']
01261         ntime=int(1./timeinc)
01262 
01263         # check for circumpolar:
01264         rset = me.riseset(src)
01265         rise = rset['rise']
01266         if rise == 'above':
01267             rise = time
01268             rise['m0']['value'] = rise['m0']['value'] - 0.5
01269             settime = time
01270             settime['m0']['value'] = settime['m0']['value'] + 0.5
01271         elif rise == 'below':
01272             raise ValueError(direction + ' is not visible from ' + telescope)
01273         else:
01274             settime = rset['set']
01275             rise = me.measure(rise['utc'],'tai')
01276             settime = me.measure(settime['utc'],'tai')
01277 
01278         # where to start plotting?
01279         offset=-0.5
01280         if settime < time: offset-=0.5
01281         if rise > time: offset+=0.5
01282         time['m0']['value']+=offset
01283 
01284         times=[]
01285         az=[]
01286         el=[]
01287     
01288         for i in range(ntime):
01289             times.append(time['m0']['value'])
01290             me.doframe(time)
01291             azel=me.measure(src,'azel')
01292             az.append(qa.convert(azel['m0'],'deg')['value'])
01293             el.append(qa.convert(azel['m1'],'deg')['value'])
01294             time['m0']['value']+=timeinc
01295     
01296 #        self.msg(" ref="+date,origin='ephemeris')
01297         self.msg("rise="+qa.time(rise['m0'],form='dmy')[0],origin='ephemeris')
01298         self.msg(" set="+qa.time(settime['m0'],form='dmy')[0],origin='ephemeris')
01299     
01300         pl.plot((pl.array(times)-reftime_floor)*24,el)
01301 #        peak=(rise['m0']['value']+settime['m0']['value'])/2        
01302 #        self.msg("peak="+qa.time('%fd' % peak,form='dmy'),origin='ephemeris')
01303         self.msg("peak="+qa.time('%fd' % reftime_float,form='dmy')[0],origin='ephemeris')
01304 
01305         relpeak=peaktime_float-reftime_floor
01306         pl.plot(pl.array([1,1])*24*relpeak,[0,90])
01307 
01308         # if theres an ms, figure out the entire range of observation
01309         if ms:
01310             tb.open(ms+"/OBSERVATION")
01311             timerange=tb.getcol("TIME_RANGE")
01312             tb.done()
01313             obsstart=min(timerange.flat)
01314             obsend=max(timerange.flat)
01315             relstart=me.epoch("UTC",str(obsstart)+"s")['m0']['value']-reftime_floor
01316             relend=me.epoch("UTC",str(obsend)+"s")['m0']['value']-reftime_floor
01317             pl.plot([relstart*24,relend*24],[89,89],'r',linewidth=3)
01318         else:
01319             if self.totaltime>0:
01320                 etimeh=qa.convert(self.totaltime,'h')['value']
01321                 pl.plot(pl.array([-0.5,0.5])*etimeh+relpeak*24,[80,80],'r')
01322 
01323         pl.xlabel("hours relative to "+refdate_str,fontsize='x-small')
01324         pl.ylabel("elevation",fontsize='x-small')
01325         ax=pl.gca()
01326         l=ax.get_xticklabels()
01327         pl.setp(l,fontsize="x-small")
01328         l=ax.get_yticklabels()
01329         pl.setp(l,fontsize="x-small")
01330 
01331 
01332         pl.ylim([0,90])
01333         pl.xlim(pl.array([-12,12])+24*(reftime_float-reftime_floor))
01334 
01335 
01336 
01337 
01338 
01339 
01340 
01341 
01342 
01343 
01344 
01345 
01346 
01347 
01348 
01349 
01350 
01351 
01352 
01353     ###########################################################
01354     #==========================================================
01355     
01356     def readantenna(self, antab=None):
01357     ###Helper function to read 4 columns text antenna table X, Y, Z, Diam
01358         f=open(antab)
01359         self.msg("Reading antenna positions from '%s'" % antab,origin="readantenna")
01360         line= '  '
01361         inx=[]
01362         iny=[]
01363         inz=[]
01364         ind=[]
01365         id=[] # pad id
01366         nant=0
01367         line='    '
01368         params={}
01369         while (len(line)>0):
01370             try: 
01371                 line=f.readline()
01372                 if line.startswith('#'):
01373                     line=line[1:]
01374                     paramlist=line.split('=')
01375                 ### if the line is a parameter line like coordsys=utms, then it stores that
01376                     if (paramlist.__len__() == 2):
01377                         if params==None:
01378                             params={paramlist[0].strip():paramlist[1].strip()}
01379                         else:
01380                             params[paramlist[0].strip()]=paramlist[1].strip()
01381                 else:
01382                 ### ignoring line that has less than 4 elements
01383                 ### all coord systems should have x,y,z,diam,id, where xyz varies
01384                     #print line.split()
01385                     if(len(line.split()) >3):
01386                         splitline=line.split()
01387                         inx.append(float(splitline[0]))
01388                         iny.append(float(splitline[1]))
01389                         inz.append(float(splitline[2]))
01390                         ind.append(float(splitline[3]))
01391                         if len(splitline)>4:
01392                             id.append(splitline[4])
01393                         else:
01394                             id.append('A%02d'%nant)                            
01395                         nant+=1                 
01396             except:
01397                 break
01398         f.close()
01399 
01400         if not params.has_key("observatory"):
01401             self.msg("Must specify observatory in antenna file",origin="readantenna",priority="error")
01402             return -1
01403         else:
01404             self.telescopename=params["observatory"]
01405             if self.verbose:
01406                 self.msg("Using observatory= %s" % params["observatory"],origin="readantenna")
01407 
01408         if not params.has_key("coordsys"):
01409             self.msg("Must specify coordinate system #coorsys=XYZ|UTM|GEO in antenna file",origin="readantenna",priority="error")
01410             return -1
01411         else:
01412             self.coordsys=params["coordsys"]
01413 
01414         if (params["coordsys"].upper()=="XYZ"):
01415         ### earth-centered XYZ i.e. ITRF in casa
01416             stnx=inx
01417             stny=iny
01418             stnz=inz
01419         else:
01420             stnx=[]
01421             stny=[]
01422             stnz=[]
01423             if (params["coordsys"].upper()=="UTM"):
01424         ### expect easting, northing, elevation in m
01425                 self.msg("Antenna locations in UTM; will read from file easting, northing, elevation in m",origin="readantenna") 
01426                 if params.has_key("zone"):
01427                     zone=params["zone"]
01428                 else:
01429                     self.msg("You must specify zone=NN in your antenna file",origin="readantenna",priority="error")
01430                     return -1
01431                 if params.has_key("datum"):
01432                     datum=params["datum"]
01433                 else:
01434                     self.msg("You must specify datum in your antenna file",origin="readantenna",priority="error")
01435                     return -1
01436                 if params.has_key("hemisphere"):
01437                     nors=params["hemisphere"]
01438                     nors=nors[0].upper()
01439                 else:
01440                     self.msg("You must specify hemisphere=N|S in your antenna file",origin="readantenna",priority="error")
01441                     return -1
01442                 
01443                 vsave=self.verbose
01444                 for i in range(len(inx)):
01445                     x,y,z = self.utm2xyz(inx[i],iny[i],inz[i],int(zone),datum,nors)
01446                     if i==1: 
01447                         self.verbose=False
01448                     stnx.append(x)
01449                     stny.append(y)
01450                     stnz.append(z)
01451                 self.verbose=vsave
01452             else:
01453                 if (params["coordsys"].upper()[0:3]=="LOC"):
01454                     # I'm pretty sure Rob's function only works with lat,lon in degrees;
01455                     meobs=me.observatory(params["observatory"])
01456                     if (meobs.__len__()<=1):
01457                         self.msg("You need to add "+params["observatory"]+" to the Observatories table in your installation to proceed.",priority="error")
01458                         return False,False,False,False,False,params["observatory"]
01459                     obs=me.measure(meobs,'WGS84')
01460                     obslat=qa.convert(obs['m1'],'deg')['value']
01461                     obslon=qa.convert(obs['m0'],'deg')['value']
01462                     obsalt=qa.convert(obs['m2'],'m')['value']
01463                     if self.verbose:
01464                         self.msg("converting local tangent plane coordinates to ITRF using observatory position = %d %d " % (obslat,obslon))
01465                         #foo=self.getdatum(datum,verbose=True)
01466                     for i in range(len(inx)):
01467                         x,y,z = self.locxyz2itrf(obslat,obslon,inx[i],iny[i],inz[i]+obsalt)
01468                         stnx.append(x)
01469                         stny.append(y)
01470                         stnz.append(z)                
01471                 else:
01472                     if (params["coordsys"].upper()[0:3]=="GEO"):
01473                         if params.has_key("datum"):
01474                             datum=params["datum"]
01475                         else:
01476                             self.msg("You must specify zone=NN in your antenna file",origin="readantenna",priority="error")
01477                             return -1
01478                         if (datum.upper() != "WGS84"):
01479                             self.msg("Unfortunately I only can deal with WGS84 right now",origin="readantenna",priority="error")
01480                             return -1
01481                         self.msg("geodetic coordinates not implemented yet",priority="error")
01482                     
01483         return (stnx, stny, stnz, pl.array(ind), id, nant, params["observatory"])
01484 
01485 
01486 
01487 
01488 
01489 
01490 
01491 
01492 
01493 
01494 
01495 
01496 
01497 
01498 
01499 
01500 
01501 
01502 
01503     ###########################################################
01504     #==================== geodesy =============================
01505 
01506 
01507     def tmgeod(self,n,e,eps,cm,fe,sf,so,r,v0,v2,v4,v6,fn,er,esq):
01508         """
01509         Transverse Mercator Projection
01510         conversion of grid coords n,e to geodetic coords
01511         revised subroutine of t. vincenty  feb. 25, 1985
01512         converted from Fortran R Indebetouw Jan 2009
01513         ********** symbols and definitions ***********************
01514         latitude positive north, longitude positive west.
01515         all angles are in radian measure.
01516         
01517         input:
01518         n,e       are northing and easting coordinates respectively
01519         er        is the semi-major axis of the ellipsoid
01520         esq       is the square of the 1st eccentricity
01521         cm        is the central meridian of the projection zone
01522         fe        is the false easting value at the cm
01523         eps       is the square of the 2nd eccentricity
01524         sf        is the scale factor at the cm
01525         so        is the meridional distance (times the sf) from the
01526         equator to southernmost parallel of lat. for the zone
01527         r         is the radius of the rectifying sphere
01528         u0,u2,u4,u6,v0,v2,v4,v6 are precomputed constants for
01529         determination of meridianal dist. from latitude
01530         output:
01531         lat,lon   are lat. and long. respectively
01532         conv      is convergence
01533         kp        point scale factor
01534         
01535         the formula used in this subroutine gives geodetic accuracy
01536         within zones of 7 degrees in east-west extent.  within state
01537         transverse mercator projection zones, several minor terms of
01538         the equations may be omitted (see a separate ngs publication).
01539         if programmed in full, the subroutine can be used for
01540         computations in surveys extending over two zones.        
01541         """
01542         
01543         om=(n-fn+so)/(r*sf)  # (northing - flag_north + distance_from_equator)
01544         cosom=pl.cos(om)
01545         foot=om+pl.sin(om)*cosom*(v0+v2*cosom*cosom+v4*cosom**4+v6*cosom**6)
01546         sinf=pl.sin(foot)
01547         cosf=pl.cos(foot)
01548         tn=sinf/cosf
01549         ts=tn*tn
01550         ets=eps*cosf*cosf
01551         rn=er*sf/pl.sqrt(1.-esq*sinf*sinf)
01552         q=(e-fe)/rn
01553         qs=q*q
01554         b2=-tn*(1.+ets)/2.
01555         b4=-(5.+3.*ts+ets*(1.-9.*ts)-4.*ets*ets)/12.
01556         b6=(61.+45.*ts*(2.+ts)+ets*(46.-252.*ts-60.*ts*ts))/360.
01557         b1=1.
01558         b3=-(1.+ts+ts+ets)/6.
01559         b5=(5.+ts*(28.+24.*ts)+ets*(6.+8.*ts))/120.
01560         b7=-(61.+662.*ts+1320.*ts*ts+720.*ts**3)/5040.
01561         lat=foot+b2*qs*(1.+qs*(b4+b6*qs))
01562         l=b1*q*(1.+qs*(b3+qs*(b5+b7*qs)))
01563         lon=-l/cosf+cm
01564         
01565         # compute scale factor
01566         fi=lat
01567         lam = lon
01568         sinfi=pl.sin(fi)
01569         cosfi=pl.cos(fi)
01570         l1=(lam-cm)*cosfi
01571         ls=l1*l1
01572         
01573         tn=sinfi/cosfi
01574         ts=tn*tn
01575         
01576         # convergence
01577         c1=-tn
01578         c3=(1.+3.*ets+2.*ets**2)/3.
01579         c5=(2.-ts)/15.
01580         conv=c1*l1*(1.+ls*(c3+c5*ls))
01581         
01582         # point scale factor
01583         f2=(1.+ets)/2.
01584         f4=(5.-4.*ts+ets*( 9.-24.*ts))/12.
01585         kp=sf*(1.+f2*ls*(1.+f4*ls))
01586         
01587         return lat,lon,conv,kp
01588 
01589 
01590 
01591 
01592     def tconpc(self,sf,orlim,er,esq,rf):
01593         
01594         """
01595         transverse mercator projection               ***
01596         conversion of grid coords to geodetic coords
01597         revised subroutine of t. vincenty  feb. 25, 1985
01598         converted from fortran r. indebetouw jan 2009
01599         ********** symbols and definitions ***********************
01600         input:
01601         rf is the reciprocal flattening of ellipsoid
01602         esq = e squared (eccentricity?)    
01603         er is the semi-major axis for grs-80
01604         sf is the scale factor at the cm
01605         orlim is the southernmost parallel of latitude for which the
01606         northing coord is zero at the cm
01607         
01608         output:
01609         eps
01610         so is the meridional distance (times the sf) from the
01611         equator to southernmost parallel of lat. for the zone
01612         r is the radius of the rectifying sphere
01613         u0,u2,u4,u6,v0,v2,v4,v6 are precomputed constants for
01614         determination of meridional dist. from latitude
01615         **********************************************************
01616         """
01617         
01618         f=1./rf
01619         eps=esq/(1.-esq)
01620         pr=(1.-f)*er
01621         en=(er-pr)/(er+pr)
01622         en2=en*en
01623         en3=en*en*en
01624         en4=en2*en2
01625         
01626         c2=-3.*en/2.+9.*en3/16.
01627         c4=15.*en2/16.-15.*en4/32.
01628         c6=-35.*en3/48.
01629         c8=315.*en4/512.
01630         u0=2.*(c2-2.*c4+3.*c6-4.*c8)
01631         u2=8.*(c4-4.*c6+10.*c8)
01632         u4=32.*(c6-6.*c8)
01633         u6=128.*c8
01634         
01635         c2=3.*en/2.-27.*en3/32.
01636         c4=21.*en2/16.-55.*en4/32.
01637         c6=151.*en3/96.
01638         c8=1097.*en4/512.
01639         v0=2.*(c2-2.*c4+3.*c6-4.*c8)
01640         v2=8.*(c4-4.*c6+10.*c8)
01641         v4=32.*(c6-6.*c8)
01642         v6=128.*c8
01643         
01644         r=er*(1.-en)*(1.-en*en)*(1.+2.25*en*en+(225./64.)*en4)
01645         cosor=pl.cos(orlim)
01646         omo=orlim+pl.sin(orlim)*cosor*(u0+u2*cosor*cosor+u4*cosor**4+u6*cosor**6)
01647         so=sf*r*omo
01648         
01649         return eps,r,so,v0,v2,v4,v6
01650     
01651     
01652     
01653     
01654     
01655     def getdatum(self,datumcode,verbose=False):
01656         """
01657         local datums and ellipsoids;
01658         take local earth-centered xyz coordinates, add dx,dy,dz to get wgs84 earth-centered
01659         """
01660         
01661         # set equatorial radius and inverse flattening
01662         
01663         ellipsoids={'AW':[6377563.396,299.3249647  ,'Airy 1830'                     ],
01664                     'AM':[6377340.189,299.3249647  ,'Modified Airy'                 ],
01665                     'AN':[6378160.0  ,298.25       ,'Australian National'           ],
01666                     'BR':[6377397.155,299.1528128  ,'Bessel 1841'                   ],
01667                     'CC':[6378206.4  ,294.9786982  ,'Clarke 1866'                   ],
01668                     'CD':[6378249.145,293.465      ,'Clarke 1880'                   ],
01669                     'EA':[6377276.345,300.8017     ,'Everest (India 1830)'          ],
01670                     'RF':[6378137.0  ,298.257222101,'Geodetic Reference System 1980'],
01671                     'HE':[6378200.0  ,298.30       ,'Helmert 1906'                  ],
01672                     'HO':[6378270.0  ,297.00       ,'Hough 1960'                    ],
01673                     'IN':[6378388.0  ,297.00       ,'International 1924'            ],
01674                     'SA':[6378160.0  ,298.25       ,'South American 1969'           ],
01675                     'WD':[6378135.0  ,298.26       ,'World Geodetic System 1972'    ],
01676                     'WE':[6378137.0  ,298.257223563,'World Geodetic System 1984'    ]}
01677         
01678         datums={
01679             'AGD66' :[-133, -48, 148,'AN','Australian Geodetic Datum 1966'], 
01680             'AGD84' :[-134, -48, 149,'AN','Australian Geodetic Datum 1984'], 
01681             'ASTRO' :[-104,-129, 239,'IN','Camp Area Astro (Antarctica)'  ], 
01682             'CAPE'  :[-136,-108,-292,'CD','CAPE (South Africa)'           ], 
01683             'ED50'  :[ -87, -98,-121,'IN','European 1950'                 ], 
01684             'ED79'  :[ -86, -98,-119,'IN','European 1979'                 ], 
01685             'GRB36' :[ 375,-111, 431,'AW','Great Britain 1936'            ], 
01686             'HAWAII':[  89,-279,-183,'IN','Hawaiian Hawaii (Old)'         ],
01687             'KAUAI' :[  45,-290,-172,'IN','Hawaiian Kauai (Old)'          ],
01688             'MAUI'  :[  65,-290,-190,'IN','Hawaiian Maui (Old)'           ],
01689             'OAHU'  :[  56,-284,-181,'IN','Hawaiian Oahu (Old)'           ],
01690             'INDIA' :[ 289, 734, 257,'EA','Indian'                        ],
01691             'NAD83' :[   0,   0,   0,'RF','N. American 1983'              ],
01692             'CANADA':[ -10, 158, 187,'CC','N. American Canada 1927'       ], 
01693             'ALASKA':[  -5, 135, 172,'CC','N. American Alaska 1927'       ],
01694             'NAD27' :[  -8, 160, 176,'CC','N. American Conus 1927'        ],
01695             'CARIBB':[  -7, 152, 178,'CC','N. American Caribbean'         ],
01696             'MEXICO':[ -12, 130, 190,'CC','N. American Mexico'            ],
01697             'CAMER' :[   0, 125, 194,'CC','N. American Central America'   ],
01698             'SAM56' :[-288, 175,-376,'IN','South American (Provisional1956)'],
01699             'SAM69' :[ -57, 1  , -41,'SA','South American 1969'           ],
01700             'CAMPO' :[-148, 136,  90,'IN','S. American Campo Inchauspe (Argentina)'],
01701             'WGS72' :[   0, 0  , 4.5,'WD','World Geodetic System - 72'    ],
01702             'WGS84' :[   0, 0  ,   0,'WE','World Geodetic System - 84'    ]}
01703         
01704         if not datums.has_key(datumcode):
01705             self.msg("unknown datum %s" % datumcode,priority="error")
01706             return -1
01707         
01708         datum=datums[datumcode]
01709         ellipsoid=datum[3]
01710         
01711         if not ellipsoids.has_key(ellipsoid):
01712             self.msg("unknown ellipsoid %s" % ellipsoid,priority="error")
01713             return -1
01714         
01715         if verbose:
01716             self.msg("Using %s datum with %s ellipsoid" % (datum[4],ellipsoids[ellipsoid][2]),origin="getdatum")
01717         return datum[0],datum[1],datum[2],ellipsoids[ellipsoid][0],ellipsoids[ellipsoid][1]
01718     
01719     
01720 
01721 
01722 
01723     def utm2long(self,east,north,zone,datum,nors):
01724         """
01725         this program converts universal transverse meractor coordinates to gps
01726         converted from fortran by r. indebetouw jan 2009.
01727         ri also added other datums and ellipsoids in a helper function
01728         
01729         header from original UTMS fortran program:
01730         *     this program was originally written by e. carlson
01731         *     subroutines tmgrid, tconst, tmgeod, tconpc,
01732         *     were written by t. vincenty, ngs, in july 1984 .
01733         *     the orginal program was written in september of 1988.
01734         *
01735         *     this program was updated on febuary 16, 1989.  the update was
01736         *     having the option of saving and *81* record file.
01737         *
01738         *
01739         *     this program was updated on april 3, 1990.  the following update
01740         *     were made:
01741         *      1. change from just a choice of nad27 of nad83 reference
01742         *         ellipsoids to; clarke 1866, grs80/wgs84, international, and
01743         *         allow for user defined other.
01744         *      2. allow use of latitudes in southern hemisphere and longitudes
01745         *         up to 360 degrees west.
01746         *
01747         *     this program was updated on december 1, 1993. the following update
01748         *     was made:
01749         *      1. the northings would compute the right latitude in the southern
01750         *         hemishpere.
01751         *      2. the computed latitude on longidutes would be either  in e or w.
01752         *
01753         *****************************************************************     *
01754         *                  disclaimer                                         *
01755         *                                                                     *
01756         *   this program and supporting information is furnished by the       *
01757         * government of the united states of america, and is accepted and     *
01758         * used by the recipient with the understanding that the united states *
01759         * government makes no warranties, express or implied, concerning the  *
01760         * accuracy, completeness, reliability, or suitability of this         *
01761         * program, of its constituent parts, or of any supporting data.       *
01762         *                                                                     *
01763         *   the government of the united states of america shall be under no  *
01764         * liability whatsoever resulting from any use of this program.  this  *
01765         * program should not be relied upon as the sole basis for solving a   *
01766         * problem whose incorrect solution could result in injury to person   *
01767         * or property.                                                        *
01768         *                                                                     *
01769         *   this program is property of the government of the united states   *
01770         * of america.  therefore, the recipient further agrees not to assert  *
01771         * proprietary rights therein and not to represent this program to     *
01772         * anyone as being other than a government program.                    *
01773         *                                                                     *
01774         ***********************************************************************
01775         
01776         this is the driver program to compute latitudes and longitudes from
01777         the utms for each zone
01778         
01779         input:
01780         northing, easting
01781         zone, datum
01782         nors=N/S
01783         
01784         determined according to datum:
01785         er = equatorial radius of the ellipsoid (semi-major axis)
01786         rf = reciprocal of flatting of the ellipsod
01787         f  =
01788         esq= e squared
01789         
01790         calculated according to longitude and zone:
01791         rad = radian conversion factor
01792         cm = central meridian ( computed using the longitude)
01793         sf = scale factor of central meridian ( always .9996 for utm)
01794         orlim = southernmost parallel of latitude ( always zero for utm)
01795         r, a, b, c, u, v, w = ellipsoid constants used for computing
01796         meridional distance from latitude
01797         so = meridional distance (multiplied by scale factor )
01798         from the equator to the southernmost parallel of latitude
01799         ( always zero for utm)
01800         
01801         """
01802         
01803         rad=180./pl.pi
01804         
01805         offx,offy,offz,er,rf = self.getdatum(datum,verbose=self.verbose)
01806 
01807         f=1./rf
01808         esq=(2*f-f*f)
01809         
01810         # find the central meridian if the zone number is less than 30
01811         
01812         if zone < 30 :   # ie W long - this code uses W=positive lon
01813             iz=zone
01814             icm=(183-(6*iz))
01815             cm=float(icm)/rad
01816             ucm=(icm+3)/rad
01817             lcm=(icm-3)/rad
01818         else:
01819             iz=zone
01820             icm=(543 - (6*iz))
01821             cm= float(icm)/rad
01822             ucm=(icm+3)/rad
01823             lcm=(icm-3)/rad
01824             
01825         tol=(5./60.)/rad
01826         
01827         if nors == 'S':
01828             fn= 10000000.
01829         else:
01830             fn=0.
01831     
01832         fe=500000.0
01833         sf=0.9996
01834         orlim=0.0
01835         
01836         found=0
01837 
01838         # precompute parameters for this zone:
01839         eps,r,so,v0,v2,v4,v6 = self.tconpc(sf,orlim,er,esq,rf)
01840         
01841         # compute the latitudes and longitudes:
01842         lat,lon,conv,kp = self.tmgeod(north,east,eps,cm,fe,sf,so,r,v0,v2,v4,v6,fn,er,esq)
01843         
01844         # do the test to see if the longitude is within 5 minutes
01845         # of the boundaries for the zone and if so      compute the
01846         # north and easting for the adjacent zone
01847         
01848         # if abs(ucm-lam) < tol:
01849         #     cm=float(icm+6)/rad
01850         #     iz=iz-1
01851         #     if iz == 0:
01852         #         iz=60
01853         #     found=found+1
01854         #     lat,lon,conv,kp = tmgeod(n,e,eps,cm,fe,sf,so,r,v0,v2,v4,v6,fn,er,esq)
01855         # 
01856         # if abs(lcm-lam) < tol:
01857         #     cm=float(icm-6)/rad
01858         #     iz=iz+1
01859         #     if iz == 61:
01860         #         iz=1
01861         #     lat,lon,conv,kp = tmgeod(n,e,eps,cm,fe,sf,so,r,v0,v2,v4,v6,fn,er,esq)
01862         #     found=found+1
01863         
01864         # *** convert to more usual convention of negative lon = W
01865         lon=-lon
01866 
01867         if self.verbose:
01868             self.msg(" longitude, latitude = %s %s; conv,kp = %f,%f" % (qa.angle(qa.quantity(lon,"rad"),prec=8)[0],qa.angle(qa.quantity(lat,"rad"),prec=8)[0],conv,kp),origin="utm2long")
01869         
01870         return lon,lat
01871 
01872 
01873 
01874     
01875     def long2xyz(self,long,lat,elevation,datum):
01876         
01877         dx,dy,dz,er,rf = self.getdatum(datum,verbose=False)
01878 
01879         f=1./rf
01880         esq=2*f-f**2    
01881         nu=er/(1.-esq*(pl.sin(lat))**2)
01882         
01883         x=(nu+elevation)*pl.cos(lat)*pl.cos(long) +dx
01884         y=(nu+elevation)*pl.cos(lat)*pl.sin(long) +dy
01885         z=((1.-esq)*nu+elevation)*pl.sin(lat)  +dz
01886         
01887         return x,y,z
01888     
01889     
01890     def xyz2long(self,x,y,z,datum):
01891         
01892         dx,dy,dz,er,rf = self.getdatum(datum,verbose=False)
01893         
01894         f=1./rf
01895 
01896         b= ((x-dx)**2 + (y-dy)**2) / er**2
01897         c= (z-dx)**2 / er**2
01898 
01899         esq=2*f-f**2 # (a2-b2)/a2
01900 
01901         a0=c*(1-esq)
01902         a1=2*a0
01903         efth=esq**2
01904         a2=a0+b-efth
01905         a3=-2.*efth
01906         a4=-efth
01907 
01908         b0=4.*a0
01909         b1=3.*a1
01910         b2=2.*a2
01911         b3=a3
01912 
01913         # refine/calculate esq
01914         nlqk=esq
01915         for i in range(3):
01916             nlqks = nlqk * nlqk
01917             nlqkc = nlqk * nlqks
01918             nlf = a0*nlqks*nlqks + a1*nlqkc + a2*nlqks + a3*nlqk + a4
01919             nlfprm = b0*nlqkc + b1*nlqks + b2*nlqk + b3
01920             nlqk = nlqk - (nlf / nlfprm)
01921 
01922         y0 = (1.+nlqk)*(z-dz)
01923         x0 = pl.sqrt((x-dx)**2 + (y-dy)**2)
01924         lat=pl.arctan2(y0,x0)
01925         lon=pl.arctan2(y-dy,x-dx)
01926         #print x-dx,y-dy,z-dz,x0,y0
01927                 
01928         return lon,lat
01929 
01930 
01931     def utm2xyz(self,easting,northing,elevation,zone,datum,nors):
01932 
01933         lon,lat = self.utm2long(easting,northing,zone,datum,nors)
01934         x,y,z = self.long2xyz(lon,lat,elevation,datum)
01935 
01936         return x,y,z
01937 
01938 
01939 
01940     def locxyz2itrf(self, lat, longitude, locx=0.0, locy=0.0, locz=0.0):
01941         """
01942         Returns the nominal ITRF (X, Y, Z) coordinates (m) for a point at "local"
01943         (x, y, z) (m) measured at geodetic latitude lat and longitude longitude
01944         (degrees).  The ITRF frame used is not the official ITRF, just a right
01945         handed Cartesian system with X going through 0 latitude and 0 longitude,
01946         and Z going through the north pole.  The "local" (x, y, z) are measured
01947         relative to the closest point to (lat, longitude) on the WGS84 reference
01948         ellipsoid, with z normal to the ellipsoid and y pointing north.
01949         """
01950         # from Rob Reid;  need to generalize to use any datum...
01951         phi, lmbda = map(pl.radians, (lat, longitude))
01952         sphi = pl.sin(phi)
01953         a = 6378137.0      # WGS84 equatorial semimajor axis
01954         b = 6356752.3142   # WGS84 polar semimajor axis
01955         ae = pl.arccos(b / a)
01956         N = a / pl.sqrt(1.0 - (pl.sin(ae) * sphi)**2)
01957     
01958         # Now you see the connection between the Old Ones and Antarctica...
01959         Nploczcphimlocysphi = (N + locz) * pl.cos(phi) - locy * sphi
01960     
01961         clmb = pl.cos(lmbda)
01962         slmb = pl.sin(lmbda)
01963     
01964         x = Nploczcphimlocysphi * clmb - locx * slmb
01965         y = Nploczcphimlocysphi * slmb + locx * clmb
01966         z = (N * (b / a)**2 + locz) * sphi + locy * pl.cos(phi)
01967     
01968         return x, y, z
01969 
01970 
01971 
01972 
01973     def itrf2loc(self, x,y,z, cx,cy,cz):
01974         """
01975         itrf xyz and COFA cx,cy,cz -> latlon WGS84
01976         """
01977         clon,clat = self.xyz2long(cx,cy,cz,'WGS84')
01978         ccoslon=pl.cos(clon)
01979         csinlon=pl.sin(clon)        
01980         csinlat=pl.sin(clat)
01981         n=x.__len__()
01982         lat=pl.zeros(n)
01983         lon=pl.zeros(n)
01984 
01985         # do like MsPlotConvert
01986         for i in range(n):
01987             # translate w/o rotating:
01988             xtrans=x[i]-cx
01989             ytrans=y[i]-cy
01990             ztrans=z[i]-cz
01991             # rotate
01992             lat[i] = (-csinlon*xtrans) + (ccoslon*ytrans)
01993             lon[i] = (-csinlat*ccoslon*xtrans) - (csinlat*csinlon*ytrans) + ztrans
01994                 
01995         return lat,lon
01996 
01997 
01998 
01999 
02000 
02001 
02002 
02003 
02004 
02005 
02006 
02007 
02008 
02009 
02010     ###########################################################
02011 
02012     def plotants(self,x,y,z,d,name):
02013         # given globals
02014         
02015         #stnx, stny, stnz, stnd, nant, telescopename = util.readantenna(antennalist)
02016         cx=pl.mean(x)
02017         cy=pl.mean(y)
02018         cz=pl.mean(z)
02019         lat,lon = self.itrf2loc(x,y,z,cx,cy,cz)
02020         n=lat.__len__()
02021         
02022         dolam=0
02023         # TODO convert to klam: (d too)
02024         ###
02025                 
02026         rg=max(lat)-min(lat)
02027         r2=max(lon)-min(lon)
02028         if r2>rg:
02029             rg=r2
02030         if max(d)>0.01*rg:
02031             pl.plot(lat,lon,',')            
02032             #print max(d),ra
02033             for i in range(n):
02034                 pl.gca().add_patch(pl.Circle((lat[i],lon[i]),radius=0.5*d[i],fc="#dddd66"))
02035                 if n<10:
02036                     pl.text(lat[i],lon[i],name[i],horizontalalignment='center',verticalalignment='center')
02037         else:
02038             pl.plot(lat,lon,'o',c="#dddd66")
02039             if n<10: 
02040                 for i in range(n):
02041                     pl.text(lat[i],lon[i],name[i],horizontalalignment='center',fontsize=8)
02042 
02043         pl.axis('equal')
02044         #if dolam:
02045         #    pl.xlabel("kilolamda")
02046         #    pl.ylabel("kilolamda")
02047 
02048 
02049 
02050 
02051 
02052 
02053 
02054 
02055 
02056 
02057 
02058 
02059 
02060 
02061 
02062 
02063 
02064 
02065 
02066     ######################################################
02067     # helper function to get the pixel size from an image  (positive increments)
02068 
02069     def cellsize(self,image):
02070         ia.open(image)
02071         mycs=ia.coordsys()
02072         ia.done()
02073         increments=mycs.increment(type="direction")['numeric']
02074         cellx=qa.quantity(abs(increments[0]),mycs.units(type="direction")[0])
02075         celly=qa.quantity(abs(increments[1]),mycs.units(type="direction")[1])
02076         xform=mycs.lineartransform(type="direction")
02077         offdiag=max(abs(xform[0,1]),abs(xform[1,0]))
02078         if offdiag > 1e-4:
02079             self.msg("Your image is rotated with respect to Lat/Lon.  I can't cope with that yet",priority="error")
02080         cellx=qa.mul(cellx,abs(xform[0,0]))
02081         celly=qa.mul(celly,abs(xform[1,1]))
02082         return [qa.tos(cellx),qa.tos(celly)]
02083 
02084     ######################################################
02085     # helper function to get the spectral size from an image
02086 
02087     def spectral(self,image):
02088         ia.open(image)
02089         cs=ia.coordsys()
02090         sh=ia.shape()
02091         ia.done()
02092         spc=cs.findcoordinate("spectral")
02093         if not spc[0]: return (0,0)
02094         model_width=str(cs.increment(type="spectral")['numeric'][0])+cs.units(type="spectral")[0]
02095         model_nchan=sh[spc['pixel']]
02096         return model_nchan,model_width
02097 
02098     ######################################################
02099 
02100     def is4d(self, image):
02101         ia.open(image)
02102         s=ia.shape()
02103         if len(s)!=4: return False
02104         cs=ia.coordsys()
02105         ia.done()
02106         dir=cs.findcoordinate("direction")
02107         spc=cs.findcoordinate("spectral")
02108         stk=cs.findcoordinate("stokes")
02109         if not (dir[0] and spc[0] and stk[0]): return False
02110         if dir[1].__len__() != 2: return False
02111         if spc[1].__len__() != 1: return False
02112         if stk[1].__len__() != 1: return False
02113         # they have to be in the correct order too
02114         if stk[1]!=2: return False
02115         if spc[1]!=3: return False
02116         if dir[1][0]!=0: return False
02117         if dir[1][1]!=1: return False
02118         cs.done()
02119         return True
02120 
02121     ##################################################################
02122     # fit modelimage into a 4 coordinate image defined by the parameters
02123     
02124     # TODO spectral extrapolation and regridding using innchan ****
02125 
02126     def modifymodel(self, inimage, outimage, 
02127                 inbright,indirection,incell,incenter,inwidth,innchan,
02128                 flatimage=False):  # if nonzero, create mom -1 image 
02129 
02130         # new ia tool
02131         in_ia=ia.newimagefromfile(inimage)            
02132         in_shape=in_ia.shape()
02133         in_csys=in_ia.coordsys()
02134 
02135         # pull data first, since ia.stats doesn't work w/o a CS:
02136         if outimage!=inimage:
02137             if self.verbose: self.msg("rearranging input data (may take some time for large cubes)")
02138             arr=in_ia.getchunk()
02139         else:
02140             # TODO move rearrange to inside ia tool, and at least don't do this:
02141             arr=pl.zeros(in_shape)
02142         axmap=[-1,-1,-1,-1]
02143         axassigned=[-1,-1,-1,-1]
02144 
02145 
02146 
02147         # brightness scaling 
02148         if (inbright=="unchanged") or (inbright==""):
02149             scalefactor=1.
02150         else:
02151             if self.isquantity(inbright,halt=False):
02152                 qinb=qa.quantity(inbright)
02153                 if qinb['unit']!='':
02154                     # qa doesn't deal universally well with pixels and beams
02155                     # so this may fail:
02156                     try:
02157                         inb=qa.convert(qinb,"Jy/pixel")['value']
02158                     except:
02159                         inb=qinb['value']
02160                         self.msg("assuming inbright="+str(inbright)+" means "+str(inb)+" Jy/pixel",priority="warn")
02161                     inbright=inb
02162             scalefactor=float(inbright)/pl.nanmax(arr)
02163 
02164         # check shape characteristics of the input;
02165         # add degenerate axes as neeed:
02166 
02167         in_dir=in_csys.findcoordinate("direction")
02168         in_spc=in_csys.findcoordinate("spectral")
02169         in_stk=in_csys.findcoordinate("stokes")
02170 
02171 
02172         # first check number of pixel axes and raise to 4 if required
02173         in_nax=arr.shape.__len__()
02174         if in_nax<2:
02175             self.msg("Your input model has fewer than 2 dimensions.  Can't proceed",priority="error")
02176             return False
02177         if in_nax==2:            
02178             arr=arr.reshape([arr.shape[0],arr.shape[1],1])
02179             in_shape=arr.shape
02180             in_nax=in_shape.__len__() # which should be 3
02181         if in_nax==3:
02182             arr=arr.reshape([arr.shape[0],arr.shape[1],arr.shape[2],1])
02183             in_shape=arr.shape
02184             in_nax=in_shape.__len__() # which should be 4
02185         if in_nax>4:
02186             self.msg("model image has more than 4 dimensions.  Not sure how to proceed",priority="error")
02187             return False
02188 
02189 
02190         # make incell a list if not already
02191         if type(incell) == type([]):
02192             incell =  map(qa.convert,incell,['arcsec','arcsec'])
02193         else:
02194             incell = qa.abs(qa.convert(incell,'arcsec'))
02195             # incell[0]<0 for RA increasing left
02196             incell = [qa.mul(incell,-1),incell]
02197         # later, we can test validity with qa.compare()
02198 
02199 
02200         # now parse coordsys:
02201         model_refdir=""
02202         model_cell=""
02203         # look for direction coordinate, with two pixel axes:
02204         if in_dir[0]:
02205             in_ndir = in_dir[1].__len__() 
02206             if in_ndir != 2:
02207                 self.msg("Mal-formed direction coordinates in modelimage. Discarding and using first two pixel axes for RA and Dec.")
02208                 axmap[0]=0 # direction in first two pixel axes
02209                 axmap[1]=1
02210                 axassigned[0]=0  # coordinate corresponding to first 2 pixel axes
02211                 axassigned[1]=0
02212             else:
02213                 # we've found direction axes, and may change their increments and direction or not.
02214                 dirax=in_dir[1]
02215                 axmap[0]=dirax[0]
02216                 axmap[1]=dirax[1]
02217                 axassigned[dirax[0]]=0
02218                 axassigned[dirax[1]]=0
02219                 if self.verbose: self.msg("Direction coordinate (%i,%i) parsed" % (axmap[0],axmap[1]),origin="setup model")
02220             # move model_refdir to center of image
02221             model_refpix=[0.5*in_shape[axmap[0]],0.5*in_shape[axmap[1]]]
02222             ra = in_ia.toworld(model_refpix,'q')['quantity']['*'+str(axmap[0]+1)]
02223             dec = in_ia.toworld(model_refpix,'q')['quantity']['*'+str(axmap[1]+1)]
02224             model_refdir= in_csys.referencecode(type="direction",list=False)[0]+" "+qa.formxxx(ra,format='hms',prec=5)+" "+qa.formxxx(dec,format='dms',prec=5)
02225             model_projection=in_csys.projection()['type']
02226             model_projpars=in_csys.projection()['parameters']
02227 
02228             # cell size from image
02229             increments=in_csys.increment(type="direction")['numeric']
02230             incellx=qa.quantity(increments[0],in_csys.units(type="direction")[0])
02231             incelly=qa.quantity(increments[1],in_csys.units(type="direction")[1])
02232             xform=in_csys.lineartransform(type="direction")
02233             offdiag=max(abs(xform[0,1]),abs(xform[1,0]))
02234             if offdiag > 1e-4:
02235                 self.msg("Your image is rotated with respect to Lat/Lon.  I can't cope with that yet",priority="error")
02236             incellx=qa.mul(incellx,xform[0,0])
02237             incelly=qa.mul(incelly,xform[1,1])
02238 
02239             # preserve sign in case input image is backwards (RA increasing right)
02240             model_cell = [qa.convert(incellx,'arcsec'),qa.convert(incelly,'arcsec')]
02241 
02242         else: # no valid direction coordinate
02243             axmap[0]=0 # assign direction to first two pixel axes
02244             axmap[1]=1
02245             axassigned[0]=0  # assign coordinate corresponding to first 2 pixel axes
02246             axassigned[1]=0
02247 
02248 
02249         # try to parse direction using splitter function and override model_refdir 
02250         if type(indirection)==type([]):
02251             if len(indirection) > 1:
02252                 self.msg("error parsing indirection "+str(indirection)+" -- should be a single direction string")
02253                 return False
02254             else:
02255                 indirection=indirection[0]
02256         if self.isdirection(indirection,halt=False):
02257             # lon/lat = ra/dec if J2000, =glon/glat if galactic
02258             epoch, lon, lat = self.direction_splitter(indirection)
02259 
02260             model_refdir=epoch+qa.formxxx(lon,format='hms',prec=5)+" "+qa.formxxx(lat,format='dms',prec=5)
02261             model_refpix=[0.5*in_shape[axmap[0]],0.5*in_shape[axmap[1]]]
02262             model_projection="SIN" # for indirection we default to SIN.
02263             model_projpars=pl.array([0.,0])
02264             if self.verbose: self.msg("setting model image direction to indirection = "+model_refdir)
02265         else:
02266             # indirection is not set - is there a direction in the model already?
02267             if not self.isdirection(model_refdir,halt=False):
02268                 self.msg("Cannot determine reference direction in model image.  Valid 'indirection' parameter must be provided.",priority="error")
02269                 return False
02270         
02271 
02272         # override model_cell?
02273         cell_replaced=False
02274         if self.isquantity(incell[0],halt=False):
02275             if qa.compare(incell[0],"1arcsec"): 
02276                 model_cell=incell
02277                 cell_replaced=True
02278                 if self.verbose: self.msg("replacing existing model cell size with incell")
02279         valid_modcell=False
02280         if not cell_replaced:
02281             if self.isquantity(model_cell[0],halt=False):
02282                 if qa.compare(model_cell[0],"1arcsec"):
02283                     valid_modcell=True
02284             if not valid_modcell:
02285                 self.msg("Unable to determine model cell size.  Valid 'incell' parameter must be provided.",priority="error")
02286                 return False
02287 
02288 
02289      
02290         if self.verbose:
02291             self.msg("model image shape="+str(in_shape),origin="setup model")
02292             self.msg("model pixel = %8.2e x %8.2e arcsec" % (model_cell[0]['value'],model_cell[1]['value']),origin="setup model")
02293 
02294 
02295 
02296 
02297 
02298 
02299 
02300         # we've now found or assigned two direction axes, and changed direction and cell if required
02301         # next, work on spectral axis:
02302 
02303         model_center=""
02304         model_width=""
02305         # look for a spectral axis:
02306         if in_spc[0]:
02307             #if type(in_spc[1]) == type(1) :
02308             #    # should not come here after SWIG switch over
02309             #    foo=in_spc[1]
02310             #else:
02311             foo=in_spc[1][0]
02312             if in_spc[1].__len__() > 1:
02313                 self.msg("you seem to have two spectral axes",priority="warn")
02314             model_nchan=arr.shape[foo]
02315             axmap[3]=foo
02316             axassigned[foo]=3
02317             model_restfreq=in_csys.restfrequency()
02318             in_startpix=in_csys.referencepixel(type="spectral")['numeric'][0]
02319             model_width=in_csys.increment(type="spectral")['numeric'][0]
02320             model_start=in_csys.referencevalue(type="spectral")['numeric'][0]-in_startpix*model_width
02321             # this maybe can be done more accurately - for nonregular
02322             # grids it may trip things up
02323             # start is center of first channel.  for nch=1, that equals center
02324             model_center=model_start+0.5*(model_nchan-1)*model_width
02325             model_width=str(model_width)+in_csys.units(type="spectral")[0]
02326             model_start=str(model_start)+in_csys.units(type="spectral")[0]
02327             model_center=str(model_center)+in_csys.units(type="spectral")[0]
02328             add_spectral_coord=False
02329             if self.verbose: self.msg("Spectral Coordinate %i parsed" % axmap[3],origin="setup model")                
02330         else:
02331             # need to add one to the coordsys 
02332             add_spectral_coord=True 
02333 
02334 
02335         # override incenter?
02336         center_replaced=False
02337         if self.isquantity(incenter,halt=False):
02338             if qa.compare(incenter,"1Hz"): 
02339                 if (qa.quantity(incenter))['value']>=0:
02340                     model_center=incenter
02341                     model_restfreq=model_center
02342                     center_replaced=True
02343                     if self.verbose: self.msg("setting central frequency to "+incenter)
02344         valid_modcenter=False
02345         if not center_replaced:
02346             if self.isquantity(model_center,halt=False):
02347                 if qa.compare(model_center,"1Hz"):
02348                     valid_modcenter=True
02349             if not valid_modcenter:
02350                 self.msg("Unable to determine model frequency.  Valid 'incenter' parameter must be provided.",priority="error")
02351                 return False
02352 
02353         # override inwidth?
02354         width_replaced=False
02355         if self.isquantity(inwidth,halt=False):
02356             if qa.compare(inwidth,"1Hz"): 
02357                 if (qa.quantity(inwidth))['value']>=0:
02358                     model_width=inwidth
02359                     width_replaced=True
02360                     if self.verbose: self.msg("setting channel width to "+inwidth)
02361         valid_modwidth=False
02362         if not width_replaced:
02363             if self.isquantity(model_width,halt=False):
02364                 if qa.compare(model_width,"1Hz"):
02365                     valid_modwidth=True
02366             if not valid_modwidth:
02367                 self.msg("Unable to determine model channel or bandwidth.  Valid 'inwidth' parameter must be provided.",priority="error")
02368                 return False
02369 
02370 
02371 
02372 
02373 
02374         model_stokes=""
02375         # look for a stokes axis
02376         if in_stk[0]:
02377             model_stokes=in_csys.stokes()
02378             foo=model_stokes[0]
02379             out_nstk=model_stokes.__len__()
02380             for i in range(out_nstk-1):
02381                 foo=foo+model_stokes[i+1]
02382             model_stokes=foo
02383             #if type(in_stk[1]) == type(1):
02384             #    # should not come here after SWIG switch over
02385             #    foo=in_stk[1]
02386             #else:
02387             foo=in_stk[1][0]
02388             if in_stk[1].__len__() > 1:
02389                 self.msg("you seem to have two stokes axes",priority="warn")                
02390             axmap[2]=foo
02391             axassigned[foo]=2
02392             if in_shape[foo]>4:
02393                 self.msg("you appear to have more than 4 Stokes components - please edit your header and/or parameters",priority="error")
02394                 return False                        
02395             add_stokes_coord=False
02396             if self.verbose: self.msg("Stokes Coordinate %i parsed" % axmap[2],origin="setup model")
02397         else:
02398             # need to add one to the coordsys 
02399             add_stokes_coord=True 
02400 
02401 
02402 
02403         if add_spectral_coord:                        
02404             # find first unused axis - probably at end, but just in case its not:
02405             i=0
02406             extra_axis=-1
02407             while extra_axis<0 and i<4:
02408                 if axassigned[i]<0: extra_axis=i
02409                 i+=1
02410             if extra_axis<0:                    
02411                 self.msg("I can't find an unused axis to make Spectral [%i %i %i %i] " % (axassigned[0],axassigned[1],axassigned[2],axassigned[3]),priority="error",origin="setup model")
02412                 return False
02413 
02414             axmap[3]=extra_axis
02415             axassigned[extra_axis]=3
02416             model_nchan=arr.shape[extra_axis]
02417 
02418 
02419         if add_stokes_coord:
02420             # find first unused axis - probably at end, but just in case its not:
02421             i=0
02422             extra_axis=-1
02423             while extra_axis<0 and i<4:
02424                 if axassigned[i]<0: extra_axis=i
02425                 i+=1
02426             if extra_axis<0:                    
02427                 self.msg("I can't find an unused axis to make Stokes [%i %i %i %i] " % (axassigned[0],axassigned[1],axassigned[2],axassigned[3]),priority="error",origin="setup model")
02428                 return False
02429             axmap[2]=extra_axis
02430             axassigned[extra_axis]=2                            
02431 
02432             if arr.shape[extra_axis]>4:
02433                 self.msg("you have %i Stokes parameters in your potential Stokes axis %i.  something is wrong." % (arr.shape[extra_axis],extra_axis),priority="error")
02434                 return False
02435             if self.verbose: self.msg("Adding Stokes Coordinate",origin="setup model")
02436             if arr.shape[extra_axis]==4:                    
02437                 model_stokes="IQUV"
02438             if arr.shape[extra_axis]==3:                    
02439                 model_stokes="IQV"
02440                 self.msg("setting IQV Stokes parameters from the 4th axis of you model.  If that's not what you want, then edit the header",origin="setup model",priority="warn")
02441             if arr.shape[extra_axis]==2:                    
02442                 model_stokes="IQ"
02443                 self.msg("setting IQ Stokes parameters from the 4th axis of you model.  If that's not what you want, then edit the header",origin="setup model",priority="warn")
02444             if arr.shape[extra_axis]<=1:                    
02445                 model_stokes="I"
02446 
02447 
02448 
02449 
02450         # ========================================
02451         # now we should have 4 assigned pixel axes, and model_cell, model_refdir, model_nchan, 
02452         # model_stokes all set 
02453         out_nstk=len(model_stokes)
02454         if self.verbose:
02455             self.msg("axis map for model image = %i %i %i %i" %
02456                      (axmap[0],axmap[1],axmap[2],axmap[3]),origin="setup model")
02457 
02458         modelshape=[in_shape[axmap[0]], in_shape[axmap[1]],out_nstk,model_nchan]
02459         if outimage!=inimage:
02460             ia.fromshape(outimage,modelshape,overwrite=True)
02461             modelcsys=ia.coordsys()        
02462         else:
02463             modelcsys=in_ia.coordsys()        
02464         modelcsys.setunits(['rad','rad','','Hz'])
02465         modelcsys.setincrement([qa.convert(model_cell[0],modelcsys.units()[0])['value'],    # already -1
02466                                 qa.convert(model_cell[1],modelcsys.units()[1])['value']],
02467                                 type="direction")
02468 
02469         dirm=self.dir_s2m(model_refdir)
02470         lonq=dirm['m0'] 
02471         latq=dirm['m1'] 
02472         modelcsys.setreferencecode(dirm['refer'],type="direction")
02473         if len(model_projpars)>0:
02474             modelcsys.setprojection(parameters=model_projpars.tolist(),type=model_projection)
02475         else:
02476             modelcsys.setprojection(type=model_projection)
02477         modelcsys.setreferencevalue(
02478             [qa.convert(lonq,modelcsys.units()[0])['value'],
02479              qa.convert(latq,modelcsys.units()[1])['value']],
02480             type="direction")
02481         modelcsys.setreferencepixel(model_refpix,"direction")
02482         if self.verbose: 
02483             self.msg("sky model image direction = "+model_refdir)
02484             self.msg("sky model image increment = "+str(model_cell))
02485 
02486         modelcsys.setspectral(refcode="LSRK",restfreq=model_restfreq)
02487         modelcsys.setreferencevalue(qa.convert(model_center,modelcsys.units()[3])['value'],type="spectral")
02488 #        modelcsys.setreferencepixel(0.5*model_nchan,type="spectral") # default is middle chan
02489         modelcsys.setreferencepixel(0.5*(model_nchan-1),type="spectral") # but not half-pixel
02490         modelcsys.setincrement(qa.convert(model_width,modelcsys.units()[3])['value'],type="spectral")
02491 
02492 
02493         # first assure that the csys has the expected order 
02494         expected=['Direction', 'Direction', 'Stokes', 'Spectral']
02495         if modelcsys.axiscoordinatetypes() != expected:
02496             self.msg("internal error with coordinate axis order created by Imager",priority="error")
02497             self.msg(modelcsys.axiscoordinatetypes().__str__(),priority="error")
02498             return False
02499 
02500         # more checks:
02501         foo=pl.array(modelshape)
02502         if not (pl.array(arr.shape) == pl.array(foo.take(axmap).tolist())).all():
02503             self.msg("internal error: I'm confused about the shape if your model data cube",priority="error")
02504             self.msg("have "+foo.take(axmap).__str__()+", want "+in_shape.__str__(),priority="error")
02505             return False
02506 
02507         if outimage!=inimage:
02508             ia.setcoordsys(modelcsys.torecord())
02509             ia.done()
02510             ia.open(outimage)
02511         
02512         # now rearrange the pixel axes if ness.
02513         for ax in range(4):
02514             if axmap[ax] != ax:
02515                 if self.verbose: self.msg("swapping input axes %i with %i" % (ax,axmap[ax]),origin="setup model")
02516                 arr=arr.swapaxes(ax,axmap[ax])                        
02517                 tmp=axmap[ax]
02518                 axmap[ax]=ax
02519                 axmap[tmp]=tmp                
02520 
02521         
02522         # there's got to be a better way to remove NaNs:
02523         if outimage!=inimage:
02524             for i0 in range(arr.shape[0]):
02525                 for i1 in range(arr.shape[1]):
02526                     for i2 in range(arr.shape[2]):
02527                         for i3 in range(arr.shape[3]):
02528                             foo=arr[i0,i1,i2,i3]
02529                             if foo!=foo: arr[i0,i1,i2,i3]=0.0
02530 
02531         if self.verbose and outimage!=inimage:
02532             self.msg("model array minmax= %e %e" % (arr.min(),arr.max()),origin="setup model")        
02533             self.msg("scaling model brightness by a factor of %f" % scalefactor,origin="setup model")
02534             self.msg("image channel width = %8.2e GHz" % qa.convert(model_width,'GHz')['value'],origin="setup model")
02535             if arr.nbytes > 5e7:
02536                 self.msg("your model is large - predicting visibilities may take a while.",priority="warn")
02537 
02538         if outimage!=inimage:
02539             ia.putchunk(arr*scalefactor)
02540             ia.setbrightnessunit("Jy/pixel")
02541             ia.close()
02542         in_ia.close()
02543         # outimage should now have correct Coordsys and shape
02544 
02545         # make a moment 0 image
02546         if flatimage and outimage!=inimage:
02547             self.flatimage(outimage,verbose=self.verbose)
02548 
02549         # returning to the outside world we'll return a positive cell:
02550         model_cell=[qa.abs(model_cell[0]),qa.abs(model_cell[1])]
02551         model_size=[qa.mul(modelshape[0],model_cell[0]),
02552                     qa.mul(modelshape[1],model_cell[1])]
02553 
02554 
02555         return model_refdir,model_cell,model_size,model_nchan,model_center,model_width,model_stokes
02556 
02557 
02558 
02559 
02560 
02561     ##################################################################
02562     # image/clean subtask
02563 
02564     def imclean(self,mstoimage,imagename,
02565                 cleanmode,cell,imsize,imcenter,niter,threshold,weighting,
02566                 outertaper,stokes,sourcefieldlist="",modelimage="",mask=[]):
02567         from clean import clean
02568 
02569         # determine channelization from (first) ms:
02570         if type(mstoimage)==type([]):
02571             ms0=mstoimage[0]
02572             if len(mstoimage)==1:
02573                 mstoimage=mstoimage[0]
02574         else:
02575             ms0=mstoimage
02576         
02577         tb.open(ms0+"/SPECTRAL_WINDOW")
02578         if tb.nrows() > 1:
02579             self.msg("determining output cube parameters from FIRST of several SPW in MS "+ms0)
02580         freq=tb.getvarcol("CHAN_FREQ")['r1']
02581         nchan=freq.size
02582         tb.done()
02583 
02584         if nchan==1:
02585             chanmode="mfs"
02586         else:
02587             chanmode="channel"
02588         
02589         psfmode="clark"
02590         ftmachine="ft"
02591 
02592         if cleanmode=="csclean":
02593             imagermode='csclean'
02594         #if cleanmode=="clark":
02595         #    imagermode=""
02596         if cleanmode=="mosaic":
02597             imagermode="mosaic"
02598             ftmachine="mosaic" 
02599 
02600         # in 3.4 clean doesn't accept just any imsize
02601         from cleanhelper import cleanhelper
02602         optsize=[0,0]
02603         optsize[0]=cleanhelper.getOptimumSize(imsize[0])
02604         nksize=len(imsize)
02605         if nksize==1: # imsize can be a single element or array
02606             optsize[1]=optsize[0]
02607         else:
02608             optsize[1]=cleanhelper.getOptimumSize(imsize[1])
02609         if((optsize[0] != imsize[0]) or (nksize!=1 and optsize[1] != imsize[1])):
02610             self.msg(str(imsize)+' is not an acceptable imagesize, will use '+str(optsize)+" instead",priority="warn")
02611             imsize=optsize
02612                 
02613         # print clean inputs no matter what, so user can use them.
02614         # and write a clean.last file
02615         cleanlast=open(imagename+".clean.last","write")
02616         cleanlast.write('taskname            = "clean"\n')
02617 
02618         self.msg("clean inputs:")        
02619         if type(mstoimage)==type([]):
02620             cleanstr="clean(vis="+str(mstoimage)+",imagename='"+imagename+"'"
02621             cleanlast.write('vis                 = '+str(mstoimage)+'\n')
02622         else:
02623             cleanstr="clean(vis='"+str(mstoimage)+"',imagename='"+imagename+"'"
02624             cleanlast.write('vis                 = "'+str(mstoimage)+'"\n')
02625         cleanlast.write('imagename           = "'+imagename+'"\n')
02626         cleanlast.write('outlierfile         = ""\n')
02627         cleanlast.write('field               = "'+sourcefieldlist+'"\n')
02628         cleanlast.write('spw                 = ""\n')
02629         cleanlast.write('selectdata          = False\n')
02630         cleanlast.write('timerange           = ""\n')
02631         cleanlast.write('uvrange             = ""\n')
02632         cleanlast.write('antenna             = ""\n')
02633         cleanlast.write('scan                = ""\n')
02634         if nchan>1:
02635             cleanstr=cleanstr+",mode='"+chanmode+"',nchan="+str(nchan)
02636             cleanlast.write('mode                = "'+chanmode+'"\n')
02637             cleanlast.write('nchan               = "'+str(nchan)+'"\n')
02638         else:
02639             cleanlast.write('mode                = "mfs"\n')
02640             cleanlast.write('nchan               = -1\n')
02641         cleanlast.write('gridmode                = ""\n')
02642         cleanlast.write('wprojplanes             = 1\n')
02643         cleanlast.write('facets                  = 1\n')
02644         cleanlast.write('cfcache                 = "cfcache.dir"\n')
02645         cleanlast.write('painc                   = 360.0\n')
02646         cleanlast.write('epjtable                = ""\n')
02647         #cleanstr=cleanstr+",interpolation='nearest'"  # default change 20100518
02648         cleanlast.write('interpolation           = "linear"\n')
02649         cleanstr=cleanstr+",niter="+str(niter)
02650         cleanlast.write('niter                   = '+str(niter)+'\n')
02651         cleanlast.write('gain                    = 0.1\n')
02652         cleanstr=cleanstr+",threshold='"+str(threshold)+"'"
02653         cleanlast.write('threshold               = "'+str(threshold)+'"\n')
02654         cleanstr=cleanstr+",psfmode='"+psfmode+"'"
02655         cleanlast.write('psfmode                 = "'+psfmode+'"\n')
02656         if imagermode != "":
02657             cleanstr=cleanstr+",imagermode='"+imagermode+"'"
02658         cleanlast.write('imagermode              = "'+imagermode+'"\n')
02659         cleanstr=cleanstr+",ftmachine='"+ftmachine+"'"
02660         cleanlast.write('ftmachine               = "'+ftmachine+'"\n')
02661         cleanlast.write('mosweight               = False\n')
02662         cleanlast.write('scaletype               = "SAULT"\n')
02663         cleanlast.write('multiscale              = []\n')
02664         cleanlast.write('negcomponent            = -1\n')
02665         cleanlast.write('smallscalebias          = 0.6\n')
02666         cleanlast.write('interactive             = False\n')
02667         if type(mask)==type(" "):
02668             cleanlast.write('mask                    = "'+mask+'"\n')
02669             cleanstr=cleanstr+",mask='"+mask+"'"
02670         else:
02671             cleanlast.write('mask                    = '+str(mask)+'\n')
02672             cleanstr=cleanstr+",mask="+str(mask)
02673         cleanlast.write('start                   = 0\n')
02674         cleanlast.write('width                   = 1\n')
02675         cleanlast.write('outframe                = ""\n')
02676         cleanlast.write('veltype                 = "radio"\n')
02677         cleanstr=cleanstr+",imsize="+str(imsize)+",cell="+str(map(qa.tos,cell))+",phasecenter='"+str(imcenter)+"'"
02678         cleanlast.write('imsize                  = '+str(imsize)+'\n');
02679         cleanlast.write('cell                    = '+str(map(qa.tos,cell))+'\n');
02680         cleanlast.write('phasecenter             = "'+str(imcenter)+'"\n');
02681         cleanlast.write('restfreq                = ""\n');
02682         if stokes != "I":
02683             cleanstr=cleanstr+",stokes='"+stokes+"'"
02684         cleanlast.write('stokes                  = "'+stokes+'"\n');
02685         cleanlast.write('weighting               = "'+weighting+'"\n');
02686         cleanstr=cleanstr+",weighting='"+weighting+"'"
02687         if weighting == "briggs":
02688             cleanstr=cleanstr+",robust=0.5"
02689             cleanlast.write('robust                  = 0.5\n');
02690             robust=0.5
02691         else:
02692             cleanlast.write('robust                  = 0.0\n');
02693             robust=0.
02694             
02695         taper=False
02696         if len(outertaper) >0:            
02697             taper=True
02698             if type(outertaper) == type([]):
02699                 if len(outertaper[0])==0:
02700                     taper=False
02701         if taper:
02702             uvtaper=True
02703             cleanlast.write('uvtaper                 = True\n');
02704             cleanlast.write('outertaper              = "'+str(outertaper)+'"\n');
02705             cleanstr=cleanstr+",uvtaper=True,outertaper="+str(outertaper)+",innertaper=[]"
02706         else:
02707             uvtaper=False            
02708             cleanlast.write('uvtaper                 = False\n');
02709             cleanlast.write('outertaper              = []\n');
02710             cleanstr=cleanstr+",uvtaper=False"
02711         cleanlast.write('innertaper              = []\n');
02712         if os.path.exists(modelimage):
02713             cleanstr=cleanstr+",modelimage='"+str(modelimage)+"'"
02714             cleanlast.write('modelimage              = "'+str(modelimage)+'"\n');
02715         else:
02716             cleanlast.write('modelimage              = ""\n');
02717         cleanlast.write("restoringbeam           = ['']\n");
02718         cleanlast.write("pbcor                   = True\n");
02719         cleanlast.write("minpb                   = 0.2\n");
02720         cleanlast.write("calready                = True\n");
02721         cleanlast.write('noise                   = ""\n');
02722         cleanlast.write('npixels                 = 0\n');
02723         cleanlast.write('npercycle               = 100\n');
02724         cleanlast.write('cyclefactor             = 1.5\n');
02725         cleanlast.write('cyclespeedup            = -1\n');
02726         cleanlast.write('nterms                  = 1\n');
02727         cleanlast.write('reffreq                 = ""\n');
02728         cleanlast.write('chaniter                = False\n');
02729         cleanstr=cleanstr+")"
02730         if self.verbose:
02731             self.msg(cleanstr,priority="warn")
02732         else:
02733             self.msg(cleanstr)
02734         cleanlast.write("#"+cleanstr+"\n")
02735         cleanlast.close()
02736         
02737         clean(vis=mstoimage, imagename=imagename, mode=chanmode, 
02738               niter=niter, threshold=threshold, selectdata=False, nchan=nchan,
02739               psfmode=psfmode, imagermode=imagermode, ftmachine=ftmachine, 
02740               imsize=imsize, cell=map(qa.tos,cell), phasecenter=imcenter,
02741               stokes=stokes, weighting=weighting, robust=robust,
02742               uvtaper=uvtaper,outertaper=outertaper, pbcor=True, mask=mask,
02743               modelimage=modelimage)
02744 
02745         del freq,nchan # something is holding onto the ms in table cache
02746 
02747 
02748 
02749 
02750 
02751 
02752 
02753     ###################################################
02754 
02755     def flatimage(self,image,complist="",verbose=False):
02756         # flat output 
02757         ia.open(image)
02758         imsize=ia.shape()
02759         imcsys=ia.coordsys()
02760         ia.done()
02761         spectax=imcsys.findcoordinate('spectral')[1]
02762         nchan=imsize[spectax]
02763         stokesax=imcsys.findcoordinate('stokes')[1]
02764         nstokes=imsize[stokesax]
02765 
02766         flat=image+".flat"
02767         if nchan>1:
02768             if verbose: self.msg("creating moment zero image "+flat,origin="analysis")
02769             ia.open(image)
02770             flat_ia = ia.moments(moments=[-1],outfile=flat,overwrite=True)
02771             ia.done()
02772             flat_ia.close()
02773             del flat_ia
02774         else:
02775             if verbose: self.msg("removing degenerate image axes in "+flat,origin="analysis")
02776             # just remove degenerate axes from image
02777             flat_ia = ia.newimagefromimage(infile=image,outfile=flat,dropdeg=True,overwrite=True)
02778             flat_ia.close()
02779 
02780             # seems no way to just drop the spectral and keep the stokes. 
02781             if nstokes<=1:
02782                 os.rename(flat,flat+".tmp")
02783                 ia.open(flat+".tmp")
02784                 flat_ia = ia.adddegaxes(outfile=flat,stokes='I',overwrite=True)
02785                 ia.done()
02786                 flat_ia.close()
02787                 shutil.rmtree(flat+".tmp")
02788             del flat_ia
02789 
02790         if nstokes>1:
02791             os.rename(flat,flat+".tmp")
02792             po.open(flat+".tmp")
02793             foo=po.stokesi(outfile=flat,stokes='I')
02794             foo.done()
02795             po.done()
02796             shutil.rmtree(flat+".tmp")
02797 
02798         imcsys.done()
02799         del imcsys
02800 
02801         # add components 
02802         if len(complist)>0:
02803             ia.open(flat)
02804             if not os.path.exists(complist):
02805                 self.msg("sky component list "+str(complist)+" not found in flatimage",priority="error")
02806             cl.open(complist)
02807             ia.modify(cl.torecord(),subtract=False) 
02808             cl.done()
02809             ia.done()
02810 
02811 
02812 
02813 
02814     ###################################################
02815 
02816     def convimage(self,modelflat,outflat,complist=""):
02817         # regrid flat input to flat output shape and convolve
02818         modelregrid = modelflat+".regrid"
02819         # get outflatcoordsys from outflat
02820         ia.open(outflat)
02821         outflatcs=ia.coordsys()
02822         outflatshape=ia.shape()
02823         # and beam TODO is beam the same in flat as a cube?
02824         beam=ia.restoringbeam()
02825         ia.done()            
02826 
02827         ia.open(modelflat)
02828         modelflatcs=ia.coordsys()
02829         modelflatshape=ia.shape()
02830         tmpxx=ia.regrid(outfile=modelregrid+'.tmp', overwrite=True,
02831                   csys=outflatcs.torecord(),shape=outflatshape)
02832         # im.regrid assumes a surface brightness, or more accurately doesnt
02833         # pay attention to units at all, so we now have to scale 
02834         # by the pixel size to have the right values in jy/pixel, 
02835 
02836         # get pixel size from model image coordsys
02837         tmpxx.done()
02838         increments=outflatcs.increment(type="direction")['numeric']
02839         incellx=qa.quantity(abs(increments[0]),outflatcs.units(type="direction")[0])
02840         incelly=qa.quantity(abs(increments[1]),outflatcs.units(type="direction")[1])
02841         xform=outflatcs.lineartransform(type="direction")
02842         offdiag=max(abs(xform[0,1]),abs(xform[1,0]))
02843         if offdiag > 1e-4:
02844             self.msg("Your image is rotated with respect to Lat/Lon.  I can't cope with that yet",priority="error")
02845         incellx=qa.mul(incellx,abs(xform[0,0]))
02846         incelly=qa.mul(incelly,abs(xform[1,1]))
02847         model_cell = [qa.convert(incellx,'arcsec'),qa.convert(incelly,'arcsec')]
02848 
02849         # and from outflat (the clean image)
02850         increments=outflatcs.increment(type="direction")['numeric']
02851         incellx=qa.quantity(abs(increments[0]),outflatcs.units(type="direction")[0])
02852         incelly=qa.quantity(abs(increments[1]),outflatcs.units(type="direction")[1])
02853         xform=outflatcs.lineartransform(type="direction")
02854         offdiag=max(abs(xform[0,1]),abs(xform[1,0]))
02855         if offdiag > 1e-4:
02856             self.msg("Your image is rotated with respect to Lat/Lon.  I can't cope with that yet",priority="error")
02857         incellx=qa.mul(incellx,abs(xform[0,0]))
02858         incelly=qa.mul(incelly,abs(xform[1,1]))
02859         cell = [qa.convert(incellx,'arcsec'),qa.convert(incelly,'arcsec')]
02860         
02861         # image scaling
02862         factor  = (qa.convert(cell[0],"arcsec")['value'])
02863         factor *= (qa.convert(cell[1],"arcsec")['value']) 
02864         factor /= (qa.convert(model_cell[0],"arcsec")['value']) 
02865         factor /= (qa.convert(model_cell[1],"arcsec")['value'])         
02866         imrr = ia.imagecalc(modelregrid, 
02867                             "'%s'*%g" % (modelregrid+'.tmp',factor), 
02868                             overwrite = True)
02869         shutil.rmtree(modelregrid+".tmp")
02870         if self.verbose:
02871             self.msg("scaling model by pixel area ratio %g" % factor)
02872 
02873         # add unresolved components in Jy/pix
02874         # don't do this if you've already done it in flatimage()!
02875         if (os.path.exists(complist)):            
02876             cl.open(complist)
02877             imrr.modify(cl.torecord(),subtract=False)
02878             cl.done()
02879             
02880         imrr.done()    
02881         ia.done()
02882         del imrr
02883 
02884         # Convolve model with beam.
02885         convolved = modelregrid + '.conv'
02886         ia.open(modelregrid)
02887         ia.setbrightnessunit("Jy/pixel")
02888         tmpcnv=ia.convolve2d(convolved,major=beam['major'],minor=beam['minor'],
02889                       pa=beam['positionangle'],overwrite=True)
02890         ia.done()
02891         
02892         #tmpcnv.open(convolved)
02893         tmpcnv.setbrightnessunit("Jy/beam")
02894         tmpcnv.setrestoringbeam(beam=beam)
02895         tmpcnv.done()
02896 
02897 
02898     def bandname(self, freq):
02899         """
02900         Given freq in GHz, return the silly and in some cases deliberately
02901         confusing band name that some people insist on using.
02902         """
02903         # TODO: add a trivia argument to optionally provide the historical
02904         #       radar band info from Wikipedia.
02905         band = ''
02906         if freq > 90:   # Use the ALMA system.
02907             band = 'band%.0f' % (0.01 * freq)   # () are mandatory!
02908         # Now switch to radar band names.  Above 40 GHz different groups used
02909         # different names.  Wikipedia uses ones from Baytron, a now defunct company
02910         # that made test equipment.
02911         elif freq > 75:    # used as a visual sensor for experimental autonomous vehicles
02912             band = 'W' 
02913         elif freq > 50:    # Very strongly absorbed by atmospheric O2,
02914             band = 'V'     # which resonates at 60 GHz.
02915         elif freq >= 40:
02916             band = 'Q'
02917         elif freq >= 26.5: # mapping, short range, airport surveillance;
02918             band = 'Ka'    # frequency just above K band (hence 'a')
02919                            # Photo radar operates at 34.300 +/- 0.100 GHz.
02920         elif freq >= 18:
02921             band = 'K'     # from German kurz, meaning 'short'; limited use due to
02922                            # absorption by water vapor, so Ku and Ka were used
02923                            # instead for surveillance. Used for detecting clouds
02924                            # and at 24.150 +/- 0.100 GHz for speeding motorists.
02925         elif freq >= 12:
02926             band = 'U'     # or Ku
02927         elif freq >= 8:    # Missile guidance, weather, medium-resolution mapping and ground
02928             band = 'X'     # surveillance; in the USA the narrow range 10.525 GHz +/- 25 MHz
02929                            # is used for airport radar and short range tracking.  Named X
02930                            # band because the frequency was a secret during WW2.
02931         elif freq >= 4:
02932             band = 'C'     # Satellite transponders; a compromise (hence 'C') between X
02933                            # and S bands; weather; long range tracking
02934         elif freq >= 2:
02935             band = 'S'     # Moderate range surveillance, air traffic control,
02936                            # long-range weather; 'S' for 'short'
02937         elif freq >= 1:
02938             band = 'L'     # Long range air traffic control and surveillance; 'L' for 'long'
02939         elif freq >= 0.3:
02940             band = 'UHF'
02941         else:
02942             band = 'P'     # for 'previous', applied retrospectively to early radar systems
02943                            # Includes HF and VHF.  Worse, it leaves no space for me
02944                            # to put in rock band easter eggs.
02945         return band
02946 
02947 
02948     def polsettings(self, telescope, poltype=None, listall=False):
02949         """
02950         Returns stokes parameters (for use as stokes in sm.setspwindow)
02951         and feed type (for use as mode in sm.setfeed).
02952 
02953         If poltype is not specified or recognized, a guess is made using
02954         telescope.  Defaults to 'XX YY', 'perfect X Y'
02955 
02956         If listall is True, return the options instead.
02957         """
02958         psets = {'circular': ('RR LL', 'perfect R L'),
02959                  'linear':   ('XX YY', 'perfect X Y'),
02960                  'RR':       ('RR',    'perfect R L')}
02961         if listall:
02962             return psets
02963         if poltype not in psets:
02964             poltype = 'linear'
02965             for circ in ('VLA', 'DRAO'):       # Done this way to
02966                 if circ in telescope.upper():  # catch EVLA.
02967                     poltype = 'circular'
02968         return psets[poltype]
02969 
02970 #######################################
02971 # ALMA calcpointings
02972         
02973     def applyRotate(self, x, y, tcos, tsin):     
02974         return tcos*x-tsin*y, tsin*x+tcos*y
02975     
02976     def isEven(self, num):
02977         return (num % 2) == 0
02978 
02979     # this was the only algorithm in Cycle 0 - for Cycle 1 it was 
02980     # recast as BaseTriangularTiling.getPointings(), the width>height 
02981     # case statement was removed, and BaseTriangularTiling was supplemented by
02982     # ShiftTriangularTiling.getPointings()
02983     def getTrianglePoints(self, width, height, angle, spacing):        
02984         tcos = pl.cos(angle*pl.pi/180)
02985         tsin = pl.sin(angle*pl.pi/180)
02986 
02987         xx=[]
02988         yy=[]
02989 
02990         if (width >= height):
02991             wSpacing = spacing
02992             hSpacing = (pl.sqrt(3.) / 2) * spacing
02993       
02994             nwEven = int(pl.floor((width / 2) / wSpacing))
02995             nwOdd  = int(pl.floor((width / 2) / wSpacing + 0.5))
02996             nh     = int(pl.floor((height / 2) / hSpacing))
02997 
02998             for ih in pl.array(range(nh*2+1))-nh:
02999                 if (self.isEven(ih)):
03000                     for iw in pl.array(range(nwEven*2+1))-nwEven:
03001                         x,y = self.applyRotate(iw*wSpacing, ih*hSpacing, tcos, tsin)
03002                         xx.append(x)
03003                         yy.append(y)          
03004                 else:
03005                     for iw in pl.array(range(nwOdd*2+1))-nwOdd:
03006                         x,y = self.applyRotate((iw+0.5)*wSpacing, ih*hSpacing, tcos, tsin)
03007                         xx.append(x)
03008                         yy.append(y)
03009         else:
03010             wSpacing = (pl.sqrt(3) / 2) * spacing
03011             hSpacing = spacing
03012       
03013             nw     = int(pl.floor((width / 2) / wSpacing))
03014             nhEven = int(pl.floor((height / 2) / hSpacing))
03015             nhOdd  = int(pl.floor((height / 2) / hSpacing + 0.5))
03016 
03017             for iw in pl.array(range(nw*2+1))-nw:                
03018                 if (self.isEven(iw)):
03019                     for ih in pl.array(range(nhEven*2+1))-nhEven:
03020                         x,y = self.applyRotate(iw*wSpacing, ih*hSpacing, tcos, tsin)
03021                         xx.append(x)
03022                         yy.append(y)          
03023                 else:                    
03024                     for ih in pl.array(range(nhOdd*2+1))-nhOdd:
03025                         x,y = self.applyRotate(iw*wSpacing, (ih+0.5)*hSpacing, tcos, tsin)
03026                         xx.append(x)
03027                         yy.append(y)          
03028         return xx,yy
03029 
03030 
03031 
03032     def getTriangularTiling(self, longlength, latlength, angle, spacing, pb):
03033 
03034         # OT if isLandscape, shortside=Latlength
03035         # else isLandscape=false, shortside=Longlength
03036 
03037         if longlength > latlength: # OT isLandscape=True (OT uses >= )
03038             width=longlength # arcsec
03039             height=latlength # arcsec
03040             shortside=height
03041         else:
03042             width=latlength
03043             height=longlength
03044             shortside=height
03045             angle=angle+90
03046         
03047         # which tiling? Base or Shifted (OT getTiling)
03048         vSpacing = (pl.sqrt(3) / 2) * spacing
03049         n = pl.ceil(shortside / vSpacing)
03050 
03051         if n % 2 == 0:
03052             return self.getShiftTriangularTiling(width, height, angle, spacing, pb)
03053         else:
03054             return self.getBaseTriangularTiling(width, height, angle, spacing, pb)
03055 
03056     def needsFiller(self, length, spacing, bs, npoints):
03057         if length > spacing * (npoints - 1) + bs:
03058             return 1
03059         else:
03060             return 0
03061 
03062     def getBaseTriangularTiling(self, width, height, angle, spacing, pb):
03063         tcos = pl.cos(angle*pl.pi/180)
03064         tsin = pl.sin(angle*pl.pi/180)
03065 
03066         xx=[]
03067         yy=[]
03068 
03069         wSpacing = spacing
03070         hSpacing = (pl.sqrt(3.) / 2) * spacing
03071       
03072         nwEven = int(pl.floor((width / 2) / wSpacing))
03073         nwEven += self.needsFiller(width, wSpacing, pb, nwEven*2+1)
03074 
03075         nwOdd  = int(pl.floor((width / 2) / wSpacing + 0.5))
03076         nwOdd += self.needsFiller(width, wSpacing, pb, nwOdd*2)
03077 
03078         nh     = int(pl.floor((height / 2) / hSpacing))
03079         nh += self.needsFiller(height, hSpacing, pb, nh*2+1)
03080 
03081         for ih in pl.array(range(nh*2+1))-nh:
03082             if (self.isEven(ih)):
03083                 for iw in pl.array(range(nwEven*2+1))-nwEven:
03084                     x,y = self.applyRotate(iw*wSpacing, ih*hSpacing, tcos, tsin)
03085                     xx.append(x)
03086                     yy.append(-y) # will require additional testing @ angle>0
03087             else:
03088                 for iw in pl.array(range(nwOdd*2))-nwOdd:
03089                     x,y = self.applyRotate((iw+0.5)*wSpacing, ih*hSpacing, tcos, tsin)
03090                     xx.append(x)
03091                     yy.append(-y)
03092         
03093         return xx,yy
03094 
03095 
03096 
03097 
03098     def getShiftTriangularTiling(self, width, height, angle, spacing, pb):
03099         tcos = pl.cos(angle*pl.pi/180)
03100         tsin = pl.sin(angle*pl.pi/180)
03101 
03102         xx=[]
03103         yy=[]
03104 
03105         wSpacing = spacing
03106         hSpacing = (pl.sqrt(3.) / 2) * spacing
03107       
03108         nwEven = int(pl.floor((width / 2) / wSpacing + 0.5))
03109         nwEven += self.needsFiller(width, wSpacing, pb, nwEven*2)
03110 
03111         nwOdd  = int(pl.floor((width / 2) / wSpacing ))
03112         nwOdd += self.needsFiller(width, wSpacing, pb, nwOdd*2+1)
03113 
03114         nh     = int(pl.floor((height - hSpacing) / 2 / hSpacing +1))
03115         nh += self.needsFiller(height, hSpacing, pb, nh*2)
03116 
03117         for ih in pl.array(range(nh*2))-nh:
03118             if (self.isEven(ih)):
03119                 for iw in pl.array(range(nwEven*2))-nwEven:
03120                     x,y = self.applyRotate((iw+0.5)*wSpacing, (ih+0.5)*hSpacing, tcos, tsin)
03121                     xx.append(x)
03122                     yy.append(-y)          
03123             else:
03124                 for iw in pl.array(range(nwOdd*2+1))-nwOdd:
03125                     x,y = self.applyRotate(iw*wSpacing, (ih+0.5)*hSpacing, tcos, tsin)
03126                     xx.append(x)
03127                     yy.append(-y)
03128         
03129         return xx,yy