9    Various utilities for use in VisAOScript 
   15 import sys, os, time, calendar, glob, math, copy, pyfits, shutil
 
   18 def get_visao_filename(prefix,  t):
 
   20       Gets the standard VisAO filename (without the leading base name) for a time t. 
   22              prefix   =  prefix of the filename 
   23              t        = the time in seconds since the epoch. 
   24       Returns:  a string containing the VisAO filename time portion. 
   27    t_usec = 
int((t - math.floor(t))*1e6)
 
   29    res = 
"%s_%04i%02i%02i%02i%02i%02i%06i" % (prefix, gmt.tm_year, gmt.tm_mon, gmt.tm_mday, gmt.tm_hour, gmt.tm_min, gmt.tm_sec, t_usec)
 
   33 def get_visao_filename_now(prefix):
 
   37    sd = get_visao_filename(prefix, time.time())
 
   41 def visao_wait(waitt):
 
   45    t = time.mktime(time.localtime())
 
   46    while(time.mktime(time.localtime()) - t < waitt):
 
   52       Return a sequence containing the names of the VisAO bandpasses 
   54    filters=[
"r'", "i'", "z'", "Ys"]
 
   57 def xDither(scale = 1.0):
 
   59       Return a the x offsets for a standard 5 point dither pattern (starting from 0) 
   61       scale = multiplicative scale to multiply by, default is 1.0 
   63    dx = [-.5*scale, 1.*scale, 0.*scale, -1.*scale]
 
   67 def yDither(scale = 1.0):
 
   69       Return the y offsets for a standard 5 point dither pattern (starting from 0) 
   71       scale = multiplicative scale to multiply by, default is 1.0 
   73    dy = [.5*scale, 0.*scale, -1.*scale, 0.*scale]
 
   79       Say something using the MagAO sound server as the VisAO personality. 
   81       txt = the words to say 
   83    cmd = 
"echo vicki %s | nc zorro.lco.cl 50000" % (txt) 
 
   86 def visao_script_complete():
 
   87    visao_say(
"viz ay oh  script complete")
 
   89 def visao_script_error():
 
   90    visao_say(
"viz ay oh script airor")
 
   92 def visao_script_stopped():
 
   93    visao_say(
"viz ay oh script stopped")
 
   97       Issue an audible alert to the terminal. 
   99       n = the number of alerts to issue with a 2 second wait between. 
  109 def parseSysLog(fname):
 
  117      tv_sec = float(elem[0])
 
  118      tv_used = float(elem[1])
 
  120      fsec = tv_sec + tv_used/1e6
 
  133 def timestampFileName(fname, prefix="V47_"):
 
  135   yr = 
int(fname[off:off+4])
 
  136   mo = 
int(fname[off+4:off+6])
 
  137   d = 
int(fname[off+6:off+8])
 
  138   hr = 
int(fname[off+8:off+10])
 
  139   mn = 
int(fname[off+10:off+12])
 
  140   s = 
int(fname[off+12:off+14])
 
  141   usec = 
int(fname[off+14:off+20])
 
  143   tv = calendar.timegm([yr, mo, d, hr, mn, float(s)+float(usec)/1e6, 
'', 
'' , 
''])
 
  148 def getFileTimes(dir, prefix="V47_", ext=".fits"):
 
  150    if(dir[len(dir)-1] != 
'/'):
 
  153    fnames = glob.glob(dir+prefix+
"*"+ext)
 
  158       fn = dfn[len(dir):len(dfn)]
 
  159       ft = timestampFileName(fn, prefix)
 
  161          joetimes = getJoeTimes(dfn)
 
  165       res.append([dfn, fn, ft, joetimes[0], joetimes[1]])
 
  167    return sorted(res,  key=
lambda times: times[2])   
 
  169 def getOneFileTime(dir, prefix="V47_", ext=".fits"):
 
  171    if(dir[len(dir)-1] != 
'/'):
 
  174    fnames = glob.glob(dir+prefix+
"*"+ext)
 
  175    fnames.sort(key=os.path.getmtime)
 
  183    fn = dfn[len(dir):len(dfn)]
 
  184    ft = timestampFileName(fn, prefix)
 
  186       joetimes = getJoeTimes(dfn)
 
  190    res.append([dfn, fn, ft, joetimes[0], joetimes[1]])
 
  192    return sorted(res,  key=
lambda times: times[2])   
 
  194 def chooseLogFile(ftime, loglist):
 
  196    for i 
in range(len(loglist)-1):
 
  197       if(loglist[i][2] <= ftime 
and loglist[i+1][2] >= ftime):
 
  202 def getVisAOExpTimes(ftime, rotime, exptime):
 
  204   expend = ftime-rotime
 
  205   expst  = ftime-rotime-exptime
 
  207   return [expst, expend]
 
  209 def getLogEntries(ftime1, ftime2, loglist):
 
  215    ist = chooseLogFile(ftime1, loglist)
 
  220    iend = chooseLogFile(ftime2, loglist)
 
  223    if(iend < len(loglist) - 1):
 
  228    entries = parseSysLog(loglist[ist][0])
 
  230    for i 
in range(ist+1, iend):
 
  231       entries.extend(parseSysLog(loglist[i][0]))
 
  236 def getCCD47MinExp(pxrt,window, bin):
 
  238       Get the minimum exposure time (also the readout time) for the VisAO CCD47 image. 
  240          pxrt = the pixel rate (2500,250, or 80) 
  241          window = the window size (1024, 512,256, 64, or 32) 
  242          bin = the binning (1,2,16) 
  244          minexptime = the minimum exposure time 
  251             minexptime = 1./3.528
 
  253             minexptime = 1./0.440
 
  255             minexptime = 1./0.144
 
  258             minexptime = 1./6.703
 
  260             minexptime = 1./1.487
 
  262             minexptime = 1./0.535
 
  265             minexptime = 1./1.772
 
  268             minexptime = 1./31.283
 
  271             minexptime = 1./42.779
 
  275             minexptime = 1./0.551
 
  282       print 'getCCD47MinExp: Read out time not found for parameters' 
  288 def getJoeTimes(fname):
 
  290       Get the exposure time and readout time for a VisAO CCD47 image. 
  296    hdu = pyfits.open(fname)
 
  298    exptime = float(hdu[0].header[
"EXPTIME"]) 
 
  299    pxrt = float(hdu[0].header[
"V47PIXRT"])
 
  300    window = float(hdu[0].header[
"V47WINDX"])
 
  301    bin = float(hdu[0].header[
"V47BINX"])
 
  340       print 'Read out time not found for %s' % fname
 
  345    return [rotime, exptime]
 
  347 def getLoopStat(datadir, aosysdir, force = 0):
 
  349       Calculates loop status and avg WFE during each image in a directory. 
  350       Loop status is closed iff the loop was closed during the entire exposure.  If exposure time is less than 1 second, the nearest time is used. 
  351       A check of WFE is first made for inf and spuriously large values, which are interpolated. Avg WFE is calculated as the average of the 1 second averages recorded in the system logs. 
  352       If exptime is shorter than 1 second, the nearest WFE entry is used. Std dev of WFE is calculated as the average of the 1 second averages recorded in the system logs. 
  354       Writes a data file to the datadir with the wfe, and a file with the second by second system status. 
  357          datadir = directory where the images are located 
  358          aosysdir = directory where the ao system logs are located 
  359          force = perform update even if the file has already been updated. 
  362          list of [full_path, file_name, loop_status, wfe_avg, std_avg] 
  366    datalistone = getOneFileTime(datadir) 
 
  368    if len(datalistone) < 1:
 
  369       print 'No VisAO fits files found in %s' % datadir
 
  370       return ([
'-1', 
'-1', 
'-1', 
'-1', 
'-1'])
 
  372    if checkLoopStat(datalistone) == 1 
and force == 0:
 
  373       return ([
'-2', 
'-2', 
'-2', 
'-2', 
'-2'])
 
  376    datalist = getFileTimes(datadir) 
 
  380    loglist = getFileTimes(aosysdir,
'aosys_', 
'*.txt') 
 
  384    if len(datalist) < 1:
 
  385      print 'No visao fits files found' 
  386      return ([
'-1', 
'-1', 
'-1', 
'-1', 
'-1'])
 
  388    print 'Getting system logs:' 
  389    print ' Directory: %s' % datadir
 
  390    print '   Start time: %f' % datalist[0][2]
 
  391    print '     End time: %f' % datalist[len(datalist)-1][2]
 
  392    print ' Elapsed time: %f' % (float(datalist[len(datalist)-1][2]) - float(datalist[0][2]))
 
  396    rawentries = getLogEntries(datalist[0][2]-datalist[0][3]-datalist[0][4], datalist[len(datalist)-1][2], loglist)
 
  399    entries = copy.deepcopy(rawentries)
 
  401    print 'Starting WFE interpolation' 
  403    for i 
in range(1,len(entries)-1):
 
  404       if entries[i][2] == 
'inf' or float(entries[i][2]) > 1e5:
 
  408             if(entries[i-q][0] != 
'inf' and float(entries[i-q][2] < 1e5)):
 
  414          t0 = float(entries[i-n][0])
 
  415          wfe0 = float(entries[i-n][2])
 
  416          std0 = float(entries[i-n][3])
 
  418          t1 = float(entries[i][0])
 
  420          if(entries[i+1][0] != 
'inf' and float(entries[i+1][2] < 1e5)):
 
  428            if(entries[i+q][0] != 
'inf' and float(entries[i+q][2] < 1e5)):
 
  431            if i+q > len(entries)-1:
 
  432             n = len(entries)-1 - i   
 
  435          t2 = float(entries[i+n][0])
 
  436          wfe2 = float(entries[i+n][2])
 
  437          std2 = float(entries[i+n][2])
 
  439          wfe1 = wfe0 + (wfe2-wfe0)/(t2-t0)*(t1-t0)
 
  440          std1 = std0 + (std2-std0)/(t2-t0)*(t1-t0)
 
  442          entries[i][2] = str(wfe1)
 
  443          entries[i][3] = str(std1)
 
  446    f = file(datadir+
'/wfe.txt', 
'w')
 
  447    for i 
in range(len(rawentries)):
 
  448       f.write(
"%s %s %s %s %s %s \n" % (rawentries[i][0], rawentries[i][1], rawentries[i][2], rawentries[i][3], entries[i][2], entries[i][3]))
 
  452    for i 
in range(len(datalist)):
 
  454       joetime = getJoeTimes(datalist[i][0])
 
  456       etimes = getVisAOExpTimes(datalist[i][2], joetime[0], joetime[1])
 
  458       if len(entries) <= 0:
 
  459          print 'No AOSYS entries' 
  460          return  ([
'-1', 
'-1', 
'-1', 
'-1', 
'-1'])
 
  464       while(float(entries[j][0]) <= etimes[0]):
 
  466         if j >= len(entries): 
break 
  478         while(float(entries[k][0]) <= etimes[1] 
and k < len(entries)-1):
 
  480           if(
int(entries[k][1]) != 1): loopst = 0
 
  481           wfeavg = wfeavg + float(entries[k][2])
 
  482           stdavg = stdavg + float(entries[k][3])
 
  484           if k >= len(entries): 
break 
  486         if(k < len(entries)-1):
 
  489             if(
int(entries[j][1]) != 1 
or int(entries[j+1][1]) != 1): loopst = 0
 
  490             wfeavg = 0.5*(float(entries[j][2]) + float(entries[j][2]))
 
  491             if(j < len(entries)-1):
 
  492               stdavg = 0.5*(float(entries[j][3]) + float(entries[j+1][3]))
 
  496             wfeavg = wfeavg/(k-j)
 
  497             stdavg = stdavg/(k-j)
 
  499       statent.append([datalist[i][0], datalist[i][1], loopst, wfeavg, stdavg])
 
  501    f = file(datadir+
'/status.txt', 
'w')
 
  502    for i 
in range(len(statent)):
 
  503       f.write(
"%s %i %f %f\n" % (statent[i][1], statent[i][2], statent[i][3], statent[i][4]))
 
  507 def updateLoopStat(statlist):
 
  513       hdu = pyfits.open(im[0], 
'update')
 
  516          hdu[0].header.update(
'AOLOOPST', 
"CLOSED",comment=
"AO loop status during exposure") 
 
  519          hdu[0].header.update(
'AOLOOPST', 
"OPEN",comment=
"AO loop status during exposure") 
 
  521       hdu[0].header.update(
'AVGWFE', im[3],comment=
'Avg WFE (nm rms phase)')    
 
  522       hdu[0].header.update(
'STDWFE', im[4],comment=
'Std Dev of WFE (nm rms phase)')
 
  524       hdu[0].header.update(
'HISTORY',  
'VisAO system status update applied %s' % time.asctime(time.localtime(time.time())))
 
  528 def checkLoopStat(statlist):
 
  531    if len(statlist) <= 0:
 
  535    hdu = pyfits.open(im[0], 
'update')
 
  537    if hdu[0].header.get(
'AOLOOPST') != 
'NOT PROCESSED':
 
  543 def getGoodIms(dir, movedir):
 
  545      run this after headers updated 
  547   if(dir[len(dir)-1] != 
'/'):
 
  550   if(movedir[len(movedir)-1] != 
'/'):
 
  551      movedir = movedir + 
'/' 
  553   fnames = glob.glob(dir+
"V47_*.fits")
 
  562      hdu = pyfits.open(dfn)
 
  563      fn = dfn[len(dir):len(dfn)]
 
  565      if (hdu[0].header[
"AOLOOPST"] == 
'OPEN' and hdu[0].header[
"VIMTYPE"] == 
'SCIENCE'):
 
  569      if (hdu[0].header[
"AOLOOPST"] == 
'CLOSED' and hdu[0].header[
"VIMTYPE"] == 
'SCIENCE'):
 
  571         good.append([dfn, fn])
 
  573      if (hdu[0].header[
"VIMTYPE"] == 
'DARK'):
 
  575         good.append([dfn, fn])
 
  583     newf = movedir + f[1]
 
  584     shutil.copyfile(f[0], newf)
 
  587 def getDarks(dir, movedir):
 
  589      run this after headers updated 
  591   if(dir[len(dir)-1] != 
'/'):
 
  594   if(movedir[len(movedir)-1] != 
'/'):
 
  595      movedir = movedir + 
'/' 
  597   fnames = glob.glob(dir+
"V47_*.fits")
 
  606      hdu = pyfits.open(dfn)
 
  607      fn = dfn[len(dir):len(dfn)]
 
  609      if (hdu[0].header[
"VIMTYPE"] == 
'DARK'):
 
  611         good.append([dfn, fn])
 
  619     newf = movedir + f[1]
 
  620     shutil.copyfile(f[0], newf)
 
  624      Returns a list of the standard VisAO filters 
  626    filters=[
"r'", "i'", "z'", "Ys"]
 
  629 def xDither(scale = 1.0):
 
  631      Returns a list of x offsets for a gimbal dither.  Units are mm. 
  633      scale = total width of the pattern in mm 
  635    dx = [-.5*scale, 1.*scale, 0.*scale, -1.*scale]
 
  639 def yDither(scale = 1.0):
 
  641      Returns a list of y offsets for a gimbal dither.  Units are mm. 
  643      scale = total width of the pattern in mm 
  645    dy = [.5*scale, 0.*scale, -1.*scale, 0.*scale]
 
  650    cmd = 
"echo vicki %s | nc zorro.lco.cl 50000" % (txt) 
 
  653 def visao_script_complete():
 
  654    visao_say(
"viz ay oh  script complete")
 
  656 def visao_script_error():
 
  657    visao_say(
"viz ay oh script airor")
 
  659 def visao_script_stopped():
 
  660    visao_say(
"viz ay oh script stopped")