#!/usr/bin/python '''A GUI to select target stars for observation with NPOI, their calibrators, and format the output appropriately for an observing list file. ''' import os import sys import re import math import pdb import matplotlib matplotlib.use('TkAgg') from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg from matplotlib.figure import Figure from matplotlib.pylab import * from Tkinter import * import tkMessageBox import tkFileDialog import tkSimpleDialog ''' Assuming we did an install as a non-su with the --prefix=$PWD we need to make sure obsprep finds the correct lib, site-packages, .so files etc associated with obsprep. The user should have set the environment variable for us. Check this and continue on if we can. ''' obsprepDir=os.getenv('OBSPREPDIR') if obsprepDir == None: raise EnvironmentError('*** environment variable OBSPREPDIR not defined!') # get python version versionTuple=sys.version_info versionString=str(versionTuple[0])+'.'+str(versionTuple[1]) sys.path.insert(1,obsprepDir+'/pnovas-2.0.1b/lib64/python'+versionString+'/site-packages') sys.path.insert(2,obsprepDir+'/npoi') sys.path.insert(3,obsprepDir+'/lib64/python'+versionString+'/site-packages') # to find npoic.so! import novas import npoic import npoi.tktools as tktools import npoi.astro as astro import npoi.catalogs obsprepVersion='1.5.9.5' def bessj1(x): '''Return the J1 function for x.''' ax = abs(x) if ax < 8.: y = x * x ans1 = x * (72362614232.0 + y * (-7895059235.0 + y * (242396853.1 + \ y * (-2972611.439 + y * (15704.48260 + y * (-30.16036606)))))) ans2 = 144725228442.0 + y * (2300535178.0 + y * (18583304.74 + \ y * (99447.43394 + y * (376.9991397 + y * 1.0)))) ans = ans1 / ans2 else: z = 8.0 / ax y = z * z xx = ax - 2.356194491 ans1 = 1.0 + y * (0.183105e-2 + y * (-0.3516396496e-4 + y * (0.2457520174e-5 + y * (-0.240337019e-6)))) ans2 = 0.04687499995 + y * (-0.2002690873e-3 + y * (0.8449199096e-5 + y * (-0.88228987e-6 + y * 0.105787412e-6))) ans = sqrt(0.636619772/ax) * (cos(xx) * ans1 - z * sin(xx) * ans2) if x < 0.: ans = -ans return ans def diskVis2(s, diameter): '''Return the visibility squared for the specified diameter and s. ARGS: s (double): sqrt(u^2 + v^2) (lambda) diameter (double): diameter of the disk (mas) ''' x = pi * s * diameter * pi / (180. * 3600000.) v = 2 * bessj1(x) / x return v * v def binaryVis2(u, v, d1, d2, separation, r, pa): '''Return the visibility squared for a binary. ARGS: u (double): u (lambda) v (double): v (lambda) d1 (double): Diameter of primary star (mas) d2 (double) : Diameter of secondary star (mas) separation (double): Separation between the stars (mas) r (double): Brightness ratio (10 ** (-delta_mag/2.5), mag) pa (double): Binary position angle (deg) ''' s = sqrt(u * u + v * v) theta = pa * pi / 180. - math.atan(u / v) v12 = diskVis2(s, d1) v22 = diskVis2(s, d2) v2 = (v12 + r * r * v22 + 2 * r * sqrt(v12) * sqrt(v22) * \ cos(2 * pi * s * separation * (pi / (180.*3600000.)) * cos(theta))) \ / ((1 + r) * (1 + r)) return v2 def selectWavelengthsFile(program,specsInUse): '''Function to eventually select the wavelengths file. ''' if program.startswith('H-Alpha'): if specsInUse == [1,2]: waveFile = '2006-10-19.S1.S2.wavelengths' if specsInUse == [2,3]: waveFile = '2007-01-20.S2.S3.wavelengths' if specsInUse == [1,3]: waveFile = '2006-10-19.S1.S3.wavelengths' else: if specsInUse == [1,2]: waveFile = '2007-11-06.S1.S2.wavelengths' if specsInUse == [2,3]: waveFile = '2008-07-30.S2.S3.wavelengths' if specsInUse == [1,3]: waveFile = '2008-01-15.S1.S3.wavelengths' return waveFile class Siderostat(object): '''A single siderostat.''' def __init__(self, name, feedAz, feedEl, sidAz, sidEl, axisOff, zeroAz, mirrorOff, zeroEl, minAz, maxAz, minEl, maxEl): '''Initialize the siderostat configuration. ARGS: name (string): Siderostat name feedAz (double): ? feedEl (double): ? sidAz (double): ? sidEl (double): ? axisOff (double): ? zeroAz (double): ? mirrorOff (double): ? zeroEl (double): ? minAz(double): ? maxAz (double): ? minEl(double): ? maxEl (double): ? ''' self.name = name self.feedAz = feedAz self.feedEl = feedEl self.sidAz = sidAz self.sidEl = sidEl self.axisOff = axisOff self.zeroAz = zeroAz self.mirrorOff = mirrorOff self.zeroEl = zeroEl self.minAz = minAz self.maxAz = maxAz self.minEl = minEl self.maxEl = maxEl def azEl(self, ha, dec): '''Return the (azimuth,elevation), in degrees, for the siderostat when pointing in the specified direction. ARGS: ha (double): Hour angle (apparent, hourss) dec (double): Declination (apparent, degrees) ''' results = npoic.siderostat(ha, dec, self.feedAz, self.feedEl, self.sidAz, self.sidEl, self.axisOff, self.zeroAz, self.mirrorOff, self.zeroEl) return results def observable(self, ha, dec): '''Return 1 if the specified coordinates are within the azimuth/elevation limits of the siderostat, else return 0. ARGS: ha (double): Hour angle (apparent, hourss) dec (double): Declination (apparent, degrees) ''' results = self.azEl(ha, dec) az = results[0] el = results[1] if az < self.minAz or az > self.maxAz: return False if el < self.minEl or el > self.maxEl: return False return True class Station(object): '''A single observing station.''' def __init__(self, name, delayConstant, x, y, z): '''Initialize the station configuration. Positions are relative to the array zero point. ARGS: name (string): Station name delayConstant (double): Delay constant (m) x (double): X (east) position (m). y (double): Y (north) position (m). z (double): Elevation (m). ''' self.name = name self.delayConstant = delayConstant self.x = x self.y = y self.z = z def rotate(self, ha, dec): '''Rotate the coordinates of the station into the stellar meridian coordinate system. ARGS: ha (double): Hour angle (hour) dec (double): Declination (deg) RETURNS: (r,i,polarDelay), all in meters ''' site = astro.npoi() # Convert station coordinates in local horizon coordinates to # equatorial coordinates. Note that the X-axis is along the local # medidian, NOT the Greenwich meridian as used in VLBI. This # corresponds to the X,Y,Z system used by Thompson, Moran, and Swenson, # "Interferometry and Synthesis in Radio Astonomy", page 86 # (1st edition). # # That is, Z points towards the north pole along the earth's # rotational axis, X along the projection of the local meridian # on the equator, increasing as you go from the earth's axis to # the local point on the earth's surface, and Y points east. sinLat = sin(site.lat * novas.DEG2RAD) cosLat = cos(site.lat * novas.DEG2RAD) x = self.z * cosLat - self.y * sinLat y = self.x z = self.y * cosLat + self.z * sinLat # Next rotate in the equatorial plane to make x point along the # stellar meridian. Also, scale by cos(dec) to give the vector whose # projection onto the x axis is the oscillating component of the delay. sinDec = sin(dec * novas.DEG2RAD) cosDec = cos(dec * novas.DEG2RAD) sinHA = sin(ha * 15. * novas.DEG2RAD) cosHA = cos(ha * 15. * novas.DEG2RAD) r = cosDec * (x * cosHA - y * sinHA) i = cosDec * (x * sinHA + y * cosHA) # The polar delay is the constant portion of the delay. It is the # delay which would be observed if only the component of the baseline # oriented along the rotation pole of the earth were present. # # NOTE: The sign convention used by JTA for the delay constant is # that it equals the path length from the array element through the # feed system to the equal phase point in the beam combiner. As such # it is always a positive number and must be sbtracted from B dot S # to give the additional delay that must be introduced by the delay # line. polarDelay = z * sinDec - self.delayConstant; return (r,i,polarDelay) class NPOI(object): '''The entire NPOI telescope array.''' def __init__(self, stations, zdMax, mode): '''Initialize the configuration of the NPOI telescope array. ARGS: stations (tuple): List of station names we are using zdMax (float): Maximum zenith distance (deg) mode (String): Observing mode ''' # Initialize a NOVAS Location structure for the NPOI site self.site = astro.npoi() self.timezone = 7 # Time zone (hours) # Zenith distance limits self.zdMin = 0 # Minimum observable zenith distance (deg) self.zdMax = zdMax # Maximum observable zenith distance (deg) # Initialize the stations # Numbers are constant term, x, y, z (meters). self.stations = { 'E2':Station('E2', 98.9058940, 5.6128295, -1.7790461, 0.0216787), 'E3':Station('E3', 102.2439356, 9.0576822, -3.2913456, 0.0044113), 'N3':Station('N3', 101.7033231, 0.4368091, 7.6926878, 0.0082141), 'AC':Station('AC',103.8867830, -0.4866225, -5.6261545, 0.0095949), 'AE':Station('AE',121.8798810, 17.0027065,-12.8829861, 0.0092909), 'AW':Station('AW',118.0181631,-20.3769427,-15.4831804, 0.0081096), 'W7':Station('W7',147.3002440,-44.1908188,-32.9754257,-0.0706610), 'AN':Station('AN',110.7434252, -0.6132186, 17.2268933, 0.0017019), 'E4':Station('E4',106.377800, 12.787536, -4.946864, 0.005366), 'E6':Station('E6',128.6662106, 32.8518190,-13.7440581, 0.0124705 ), 'E7':Station('E7',150.5044844, 52.833348, -22.523315, 0.00), 'W4':Station('W4',103.7627099,-9.1187655, -7.2228962, 0.0146863), 'W6':Station('W6',125.5560, -26.79, -20.20, 0.00), 'E10':Station('E10',346.8408, 232.606, -101.452, 0.00), 'W10':Station('W10',336.5804, -196.735, -145.010, 0.00), } # 'E4':Station('E4',106.377800, 12.787536, -4.946864, 0.005366), # 'E7':Station('E7',149.9281, 52.73, -22.48, 0.00), # 'W4':Station('W4',103.6561, -9.14, -7.23, 0.00), # 'W6':Station('W6',125.5560, -26.79, -20.20, 0.00) self.centerOfTank = 17.5 # Center of the tank (m) # Initialize the siderostats if mode == 'Astrometry': self.siderostats = { 'AC':Siderostat('1', 86.57800, 20.073000, 80.888000, 89.736000, 0.0, 278.095001, 0.0, 49.201000, -30.45, 34.20, -33.43, 26.98), 'AE':Siderostat('2', 86.23000, 20.389000, 84.887001, 89.785004, 0.0, 273.606995, 0.0, 50.013000, -42.84, 39.85, -36.17, 27.21), 'AW':Siderostat('3', 86.79000, 20.148001, 88.589996, 89.790001, 0.0, 269.424988, 0.0, 50.214001, -31.00, 35.70, -36.06, 27.00), 'AN':Siderostat('4', 86.81400, 20.230000, 99.125999, 89.773003, 0.0, 260.230988, 0.0, 49.980999, -43.47, 46.53, -33.28, 26.34) } else: self.siderostats = { 'AC':Siderostat('1', 84.43900, 20.096000, 259.895000,90.258000, 0.0, 99.084000, 0.0, 49.185000, -30.45, 34.20, -33.43, 26.98), 'AE':Siderostat('2', 84.17400, 20.271000, 90.081000,89.768000, 0.0, 268.411000, 0.0, 50.002000, -42.84, 39.85, -36.17, 27.21), 'AW':Siderostat('3', 84.55300, 20.287000, 268.742000,90.208000, 0.0, 449.274000, 0.0, 50.224000, -31.00, 35.70, -36.06, 27.00), 'AN':Siderostat('4', 84.41800, 20.150000, 73.954002,89.740997, 0.0, 285.368988, 0.0, 49.270000, -43.47, 46.53, -33.28, 26.34), 'W7':Siderostat('5', 84.40400, 22.114000, 57.258000,89.727000, 0.0, -59.83800, 0.0, 70.016000, -88.6, 85.6, -69.2, 18.4), 'E2':Siderostat('6', 84.59200, 20.446000, 58.695000,89.781000, 0.0, -61.78900, 0.0, 64.096000, -91.2, 91.1, -61.6, 18.2), 'E3':Siderostat('2', 84.59200, 20.446000, 58.695000,89.781000, 0.0, -61.78900, 0.0, 64.096000, -42.84, 39.85, -36.17, 27.21), 'E6':Siderostat('6', 84.72900, 20.406000, 248.403000,90.217000, 0.0, -251.8300, 0.0, 64.129000, -91.12, 91.19, -61.6, 18.2), 'E4':Siderostat('8', 88.46000, 18.500000, 85.441000,89.829000, 0.0, 272.58000, 0.0, 49.088000, -91.2, 91.1, -61.6, 18.2), 'E7':Siderostat('7', 88.46000, 18.500000, 85.441000,89.829000, 0.0, 272.58000, 0.0, 49.088000, -91.19, 91.12, -61.61, 18.15), 'E10':Siderostat('11', 88.46000, 18.500000, 85.441000,89.829000, 0.0, 272.58000, 0.0, 49.088000, -91.2, 91.1, -61.6, 18.2), 'W4':Siderostat('5', 84.40400, 22.114000, 57.258000,89.727000, 0.0, -59.83800, 0.0, 70.016000, -31.0, 35.7, -36.06, 27.0), 'W6':Siderostat('10', 84.40400, 22.114000, 57.258000,89.727000, 0.0, -59.83800, 0.0, 70.016000, -88.6, 85.6, -69.2, 18.4), 'W10':Siderostat('12', 84.40400, 22.114000, 57.258000,89.727000, 0.0, -59.83800, 0.0, 70.016000, -88.63, 85.61, -69.2, 18.4), 'N3':Siderostat('4', 84.59200, 20.446000, 58.695000,89.781000, 0.0, -61.78900, 0.0, 64.096000, -91.2, 91.1, -61.6, 18.2), } # Which stations are we currently using for station in stations: if station not in self.stations: raise ValueError, 'illegal station specified: %s' % station self.stationsInUse = stations def observable(self, ha, dec): '''Are these coordinates observable with the current set of stations. ARGS: ha (double): Hour angle (apparent, hours) dec (double): Declination (apparent, degrees) RETURNS: True if coordinates are observable, False if not. ''' # Check zenith distance limits zd = astro.zenithDistance(ha, dec, self.site) if zd < self.zdMin or zd > self.zdMax: return False # Convert station coordinates to stellar sytem stellarCoords = [self.stations[station].rotate(ha,dec) \ for station in self.stationsInUse] r = [x[0] for x in stellarCoords] i = [x[1] for x in stellarCoords] d = [x[2] for x in stellarCoords] # Translate coordinates to minimize max delay rate and acceleration rZero = (min(r) + max(r)) / 2. iZero = (min(i) + max(i)) / 2. r = [x - rZero for x in r] i = [x - iZero for x in i] # Minimize the delay, and subtract off the center of tank value. delay = [x[0] + x[1] for x in zip(d,r)] dZero = (min(delay) + max(delay)) / 2. - self.centerOfTank d = [x - dZero for x in d] # Calculate the positions of the carts in the delay lines, in m pos = [x[0] + x[1] for x in zip(d,r)] # Return False if the position of the carts for one or more used # stations is not within the tanks. if min(pos) < 0 or max(pos) > self.centerOfTank * 2: return False # Passes delay test. Now test whether the star is within the # siderostats' limits. for station in self.stationsInUse: sid = self.siderostats[station] if self.siderostats[station].observable(ha,dec) == False: return False # Passes all tests. Its observable. return True def riseSet(self, star, mjd): '''Return the local rise and set times for the specified star on the specified date. ARGS: star (Star): Star to observe. mjd (int): UT1 Modified Julian Date (within 1 sec of UTC); should be an integer RETURNS: (rise,set), both in local civil time (hours) ''' assert isinstance(mjd,int) # Get the apparent place of the star cat = star.novas() coords = astro.apparentStar(cat, mjd) assert coords[0] == 0 ra = coords[1] # hours dec = coords[2] # Bound our search by the rise and set time determined by zenith # distance only. times = astro.riseSet(ra,dec,self.site,self.timezone,mjd,self.zdMax) if times == None: times = astro.twilight(self.site,self.timezone,mjd,zd=96.) zdRise = times[0] zdSet = times[1] # Now simply search every 5 minutes rise = None set = None hour = zdRise while hour < zdSet: # Update the apparent place of the star mjd2 = mjd + (self.timezone + hour) / 24. coords = astro.apparentStar(cat, mjd2) assert coords[0] == 0 ha = astro.inRange(astro.last(mjd2, self.site) - coords[1],-12,12) dec = coords[2] # Are these coordinates observable? up = self.observable(ha, dec) if rise == None and up: rise = astro.inRange(hour,0,24) elif rise != None and not up: set = astro.inRange(hour,0,24) break # Increment the time by 5 minutes hour += 5. / 60. if rise != None and set == None: set = astro.inRange(zdSet,0,24) # Return the rise and set time return (rise,set) class Target(object): '''A program target star. This is for use mainly with the GUI.''' def __init__(self,star): '''Create a new target star. ARGS: star (catalogs.Star): The target star. ''' self.star = star # The target star self.calibrators = [] # List of possible calibrators. # Tuple(calibrator name,separation (deg)) self.theCalibrator = None # Name of the chosen calibrator to use self.expTime = 30 # Exposure time (seconds) self.rise = None # Rise time (UTC, hours) self.set = None # Set time (UTC, hours) self.beams = {1:False, 2:False, 3:False, 4:False, 5:False, 6:False} # List of beams used for this star def obsFileWrite(self, fd=None): '''Append an entry for this star to the specified observing input file. ARGS: fd (file descriptor): file descriptor for the observing file to append to; if None, write to stdout ''' # Write to stdout if no file descriptor specified if fd == None: fd = sys.stdout # Print entry for the star fd.write('%-10s' % self.star.npoiName()) if self.star.constellation != None: fd.write(' %10s' % self.star.constellation.upper()) elif self.star.common != None: fd.write(' %10s' % self.star.common.upper()) elif self.star.variablestar != None: fd.write(' %10s' % self.star.variablestar.upper()) else: fd.write(' %10s' % '') if self.star.status == None: fd.write(' .') elif self.star.status == 'C': fd.write(' .') else: fd.write(' %1s' % self.star.status) fd.write(' %4.1f' % self.star.v) if self.star.diameter == None: fd.write(' ???') else: fd.write(' %4.1f' % self.star.diameter) fd.write(' %3d %2s ____ %7.4f %7.3f | ' % \ (self.expTime, self.star.spectralType[0:2], self.star.skyRa / 15., self.star.skyDec)) if self.rise != None: riseHr = int(self.rise) riseMin = int((self.rise - riseHr) * 60.) setHr = int(self.set) setMin = int((self.set - setHr) * 60.) fd.write('%02.0fh %02.0fm %02.0fh %02.0fm\n' % \ (riseHr, riseMin, setHr, setMin)) else: fd.write(' ??? ??? ??? ???\n') def guiTuple(self): beamStr = {} for beam in xrange(1,7): if self.beams[beam]: beamStr[beam] = 'X' else: beamStr[beam] = ' ' if self.star.diameter == None: self.star.diameter = 1.0 return (self.star.npoiName(), '%3s' % self.star.spectralType.lstrip()[0:2], \ '%5.1f' % self.star.v, '%5.1f' % (self.star.raNow / 15.), '%6.1f' % self.star.decNow, '%4.1f' % self.star.diameter, '%1s' % beamStr[1], '%1s' % beamStr[2], '%1s' % beamStr[3], '%1s' % beamStr[4], '%1s' % beamStr[5], '%1s' % beamStr[6]) def guiTupleAdd(self): beamStr = {} for beam in xrange(1,7): if self.beams[beam]: beamStr[beam] = 'X' else: beamStr[beam] = ' ' if self.star.diameter == None: self.star.diameter = 1.0 tkMessageBox.showinfo('INFO','Diameter unknown, set to 1 mas') return (self.star.npoiName(), '%3s' % self.star.spectralType.lstrip()[0:2], \ '%5.1f' % self.star.v, '%5.1f' % (self.star.raNow / 15.), '%6.1f' % self.star.decNow, '%4.1f' % self.star.diameter, '%1s' % beamStr[1], '%1s' % beamStr[2], '%1s' % beamStr[3], '%1s' % beamStr[4], '%1s' % beamStr[5], '%1s' % beamStr[6]) class Calibrator(object): '''A calibrator star. This is for use mainly with the GUI.''' def __init__(self,star): '''Create a new calibrator star. ARGS: star (catalogs.Star): The calibrator star. ''' self.star = star self.targets = {} # Target objects for which this star is a # potential calibrator. # Key = target name, value = separation (deg) self.expTime = 30 # Exposure time (seconds) self.rise = None # Rise time (UTC, hours) self.set = None # Set time (UTC, hours) self.beams = {1:False, 2:False, 3:False, 4:False, 5:False, 6:False} # List of beams used for this star def obsFileWrite(self, fd=None): '''Append an entry for this star to the specified observing input file. ARGS: fd (file descriptor): file descriptor for the observing file to append to; if None, write to stdout ''' # Write to stdout if no file descriptor specified if fd == None: fd = sys.stdout # Print entry for the star fd.write('%-10s' % self.star.npoiName()) if self.star.constellation != None: fd.write(' %10s' % self.star.constellation.upper()) else: fd.write(' %10s' % '') if self.star.status == None: fd.write(' ?') else: fd.write(' %1s' % self.star.status) fd.write(' %4.1f' % self.star.v) if self.star.diameter == None: fd.write(' ???') else: fd.write(' %4.1f' % self.star.diameter) fd.write(' %3d %2s ____ %7.4f %7.3f | ' % \ (self.expTime, self.star.spectralType[0:2], self.star.skyRa / 15., self.star.skyDec)) if self.rise != None: riseHr = int(self.rise) riseMin = int((self.rise - riseHr) * 60.) setHr = int(self.set) setMin = int((self.set - setHr) * 60.) fd.write('%02.0fh %02.0fm %02.0fh %02.0fm\n' % \ (riseHr, riseMin, setHr, setMin)) else: fd.write(' ??? ??? ??? ???\n') def guiTuple(self): results = map(None, self.targets.values(), self.targets.keys()) results.sort() s = '' for target in results: star = '%s(%.1fdeg)' % (target[1], target[0]) if len(s) > 0: s = '%s ' % s s = '%s%-16s' % (s,star) if self.star.diameter == None: return (self.star.npoiName(), '%3s' % self.star.spectralType.lstrip()[0:2], \ '%5.1f' % self.star.v, '%4.1f' % 1.0, '%5.1f' % (self.star.raNow / 15.), '%6.1f' % self.star.decNow, '%4d' % self.expTime,s) if self.star.diameter != None: return (self.star.npoiName(), '%3s' % self.star.spectralType.lstrip()[0:2], \ '%5.1f' % self.star.v, '%4.1f' % self.star.diameter, '%5.1f' % (self.star.raNow / 15.), '%6.1f' % self.star.decNow,s) #'%4d' % self.expTime,s) class Gui(object): '''A GUI to prepare an observation list for NPOI.''' def __init__(self,fileName=None): '''Start up a new GUI. Reads the star catalog.''' # Keep track of the last star selected in the star plot self.lastStar = None # Variables to keep track of the targets and calibrators self.site = astro.npoi() self.targets = {} self.calibrators = {} # The various plotting windows self.visPlots = {} self.uvPlots = {} self.starPlotWindow = None self.uptimePlotWindow = None self.coveragePlotWindow = None # Combinations of spectrometers versus beams self.beams = {} self.beams[1] = [2,3,5,6] self.beams[2] = [1,3,4,6] self.beams[3] = [1,2,4,5] self.spectrometers = {} for beam in xrange(1,7): self.spectrometers[beam] = [] for spec in xrange(1,4): if beam in self.beams[spec]: self.spectrometers[beam].append(spec) # Read the target catalog #@@@ print "Looking for the catalog from line 619" #catalogPrefix = '/home/scr/bzavala/software/ObsPrep/noiPlan' #catalogFile = '%s/local/npoi/catalog' % catalogPrefix #@@@ sys.exec_prefix catalogFile = obsprepDir+'/local/npoi/catalog' #@@@ print "catalogFile is ",catalogFile if not os.path.exists(catalogFile): catalogFile = 'catalog' if not os.path.exists(catalogFile): raise ValueErr, "couldn't find star catalog '%s' in either '%s/local/npoi' or '.'" % (catalogFile, sys.exec_prefix) self.catalog = npoi.catalogs.StarCatalog.load(catalogFile) # Here's the root frame self.root = Tk() self.root.title('ObsPrep-'+obsprepVersion) # self.root.maxsize(1000,700) #@@@ This fixes maxsize, but cuts off GUI in Observing sequence. # determine screen dimensions, do we need to shrink the GUI? First set three heights # that I can modify. I assume any display is at least 725 in height. If not I will have to # shrink more stuff or add a scrollbar. summaryHeight = 3 goalHeight = 5 obsSeqHeight = 8 screenWidth = self.root.winfo_screenwidth() screenHeight = self.root.winfo_screenheight() if screenHeight < 1000: summaryHeight = 1 goalHeight = 1 obsSeqHeight = 1 # Comments stuff frame #self.bigFrame = Frame(borderwidth=2,relief=RAISED) #self.commentsFrame = Frame(self.bigFrame) self.commentsFrame = Frame(borderwidth=2,relief=RAISED) self.program = StringVar() Label(self.commentsFrame,text='Program:').grid(row=0,column=0,sticky=E) self.programButtons=Frame(self.commentsFrame) self.imaging = Radiobutton(self.programButtons,variable=self.program, value='Imaging',text='Imaging', command=self.displayCalibrators) self.astrometry = Radiobutton(self.programButtons, variable=self.program, value='Astrometry',text='Astrometry', command=self.hideCalibrators) self.halphaWith = Radiobutton(self.programButtons, variable=self.program, value='H-Alpha (with H-Alpha filter)', text='H-Alpha (with H-Alpha filter)', command=self.displayCalibrators) self.halphaWithout = Radiobutton(self.programButtons, variable=self.program, value='H-Alpha (no H-Alpha filter)', text='H-Alpha (no H-Alpha filter)', command=self.displayCalibrators) self.imaging.select() self.imaging.pack(side=LEFT) self.astrometry.pack(side=LEFT) self.halphaWith.pack(side=LEFT) self.halphaWithout.pack(side=LEFT) self.programButtons.grid(row=0,column=1,sticky=W) Label(self.commentsFrame,text='PIs:').grid(row=1,column=0,sticky=E) self.pis = StringVar() self.pisBox = Entry(self.commentsFrame,textvariable=self.pis,width=60) self.pisBox.grid(row=1,column=1,sticky=W) Label(self.commentsFrame,text='Observing UT Date:').grid(row=2, column=0, sticky=E) self.date = tktools.DateEntry(self.commentsFrame,ut=True, command=lambda x=True: self.updateConfig(dateOnly=x)) self.date.grid(row=2,column=1,sticky=W) Label(self.commentsFrame,text='Summary:').grid(row=3,column=0,sticky=E) self.summary = Text(self.commentsFrame,height=summaryHeight,width=69) self.summary.grid(row=3,column=1,sticky=W) Label(self.commentsFrame,text='Goal:').grid(row=4,column=0,sticky=E) self.goal = Text(self.commentsFrame,height=goalHeight,width=69) self.goal.grid(row=4,column=1,sticky=NE) self.commentsFrame.pack(fill=X) #self.commentsFrame.pack(side=LEFT,anchor=W) #self.bigFrame.pack(side=TOP,fill=X) # Target and calibrator star lists frames self.starsPanel = Frame(borderwidth=2,relief=RAISED) self.starsFrame = Frame(self.starsPanel) #fixedFont = ('Courier') fixedFont = ('Fixed 12') self.targetLabel = Label(self.starsFrame,text='Targets') self.targetLabel.grid(row=0,column=0) self.targetList = tktools.MultiListbox(self.starsFrame, (('Name',10,'Star name'), ('Spec',3,'Spectral Classification'), ('V',5,'Johnson V magnitude'), ('RA',5,'Right Ascension (hours)'), ('Dec',6,'Declination (degrees)'), ('D',4,'Diameter (mas)'), ('1',1,'Beam 1 used?'), ('2',1,'Beam 2 used?'), ('3',1,'Beam 3 used?'), ('4',1,'Beam 4 used?'), ('5',1,'Beam 5 used?'), ('6',1,'Beam 6 used?')), font=fixedFont) self.targetList.grid(row=1,column=0) self.calibratorLabel = Label(self.starsFrame,text='Calibrators') self.calibratorLabel.grid(row=0,column=1) self.calibratorList = tktools.MultiListbox(self.starsFrame, (('Sep',5,'Angular separation from the target star (degrees)'), ('Name',10,'Star name'), ('Spec',3,'Spectral Classification'), ('V',5,'Johnson V magnitude'), ('D',5,'Diameter (mas)'), ('RA',5,'Right Ascension (hours)'), ('Dec',6,'Declination (degrees)'), ('Targets',48,'All target stars for which this is a potential calibrator')), font=fixedFont) self.calibratorList.grid(row=1,column=1) self.starsFrame.pack() # Target function buttons self.targetButtonsFrame = Frame(self.starsPanel) self.newStar = StringVar() Label(self.targetButtonsFrame,text='Target Functions: Add:').pack(side=LEFT) self.addBox = Entry(self.targetButtonsFrame,textvariable=self.newStar, width=20) self.addBox.pack(side=LEFT) self.delButton = Button(self.targetButtonsFrame,text='Delete') self.delButton.pack(side=LEFT) self.uvButton = Button(self.targetButtonsFrame,text='UV') self.uvButton.pack(side=LEFT) self.visButton = Menubutton(self.targetButtonsFrame,width=10, relief=RAISED,text='Visibility') self.visButton.menu = Menu(self.visButton, tearoff=0) self.visButton.menu.add_command(label='Disk', command=lambda m='Disk': self.visPlot(mode=m)) self.visButton.menu.add_command(label='Binary', command=lambda m='Binary': self.visPlot(mode=m)) self.visButton['menu'] = self.visButton.menu self.visButton.pack(side=LEFT) self.upButton = Button(self.targetButtonsFrame,text='Up') self.upButton.pack(side=LEFT) self.downButton = Button(self.targetButtonsFrame,text='Down') self.downButton.pack(side=LEFT) self.blankButton = Button(self.targetButtonsFrame,text='Blank') self.blankButton.pack(side=LEFT) Label(self.targetButtonsFrame,text=' Beams:').pack(side=LEFT) self.beamButton = {} for beam in xrange(1,7): self.beamButton[beam] = Button(self.targetButtonsFrame, \ text=str(beam)) self.targetButtonsFrame.pack() self.starsPanel.pack(fill=X) # Setup frame self.setupFrame = Frame(borderwidth=2, relief=RAISED) self.station = {} self.referenceStation = IntVar() Label(self.setupFrame,text="Stations:").grid(row=1,column=1,sticky=E) Label(self.setupFrame,text="Reference Station:").\ grid(row=2,column=1,sticky=E) for i in range(1,7): Label(self.setupFrame,text=str(i)).grid(row=0,column=i+1) self.station[i] = tktools.PickList(self.setupFrame,10,'None', [],self.updateStationMenus) self.station[i].grid(row=1,column=i+1) Radiobutton(self.setupFrame,command=self.cleanupBaselines, variable=self.referenceStation, value=i).grid(row=2,column=i+1) Label(self.setupFrame, text='Spectrometers:').grid(row=4,column=1,sticky=E) self.spectrometer = {} for i in range(1,4): self.spectrometer[i] = IntVar() Checkbutton(self.setupFrame,text=str(i), variable=self.spectrometer[i], command=self.checkSpectrometers).grid(row=4,column=i+1) Label(self.setupFrame,text='Tracking Baselines:').grid(row=5,column=1, sticky=E) self.baseline = {} for i in range(1,6): self.baseline[i] = tktools.PickList(self.setupFrame,10,'None',[], postcommand=self.updateBaselineMenus) self.baseline[i].grid(row=5,column=i+1) Label(self.setupFrame, text='Spectrometer Apertures:').grid(row=6,column=1,sticky=E) self.spectrometerApertures = tktools.PickList(self.setupFrame,10, '35 mm', ['20 mm','25 mm','35 mm']) self.spectrometerApertures.grid(row=6,column=2) Label(self.setupFrame, text='NAT Quad Apertures:').grid(row=6,column=4,sticky=E, columnspan=2) self.natApertures = tktools.PickList(self.setupFrame,10,'Open', ['20 mm','25 mm','30 mm','Open']) self.natApertures.grid(row=6,column=6) Label(self.setupFrame, text='Maximum zenith distance (deg):').grid(row=7,column=1, sticky=E) self.zdMax = tktools.PickList(self.setupFrame, 10, '60', [{'label':i, 'value':i, 'command':self.updateConfig} \ for i in xrange(50,71)]) self.zdMax.grid(row=7,column=2) # Label(self.setupFrame,text='Wavelengths file:').\ # grid(row=7,column=3,sticky=E) # self.waveFile = Text(self.setupFrame,height=1,width=20) # self.waveFile.grid(row=7,column=4,sticky=W,columnspan=6) Label(self.setupFrame,text='Observing Sequence:').\ grid(row=8,column=1,sticky=E) self.sequence = Text(self.setupFrame,height=obsSeqHeight,width=77) self.sequence.grid(row=8,column=2,sticky=W,columnspan=6) Label(self.setupFrame,text='Photometric Scans:').\ grid(row=9,column=1, sticky=E) self.scans = Text(self.setupFrame,height=2,width=77) self.scans.grid(row=9,column=2,sticky=W,columnspan=6) self.kBox = Text(self.setupFrame,relief=FLAT,width=20,borderwidth=20, state=DISABLED) self.kBox.grid(row=0,column=8,rowspan=10) self.kBox.tag_config('active') self.kBox.tag_config('inactive',foreground='red') self.setupFrame.pack(fill=X) # Master buttons self.buttonsFrame = Frame(borderwidth=2,relief=RAISED) self.saveButton = Button(self.buttonsFrame,text='Save') self.saveButton.pack(side=LEFT) self.readButton = Button(self.buttonsFrame,text='Read') self.readButton.pack(side=LEFT) self.starPlotButton = Button(self.buttonsFrame,text='Star Plot') self.starPlotButton.pack(side=LEFT) self.uptimeButton = Button(self.buttonsFrame,text='Uptime Plot') self.uptimeButton.pack(side=LEFT) self.coveragePlotButton = \ Button(self.buttonsFrame,text='Coverage Plot') self.coveragePlotButton.pack(side=LEFT) self.helpButton = Button(self.buttonsFrame,text='Help') self.helpButton.pack(side=LEFT) self.quitButton = Button(self.buttonsFrame,text='Quit') self.quitButton.pack(side=LEFT) self.buttonsFrame.pack(fill=X) # Bindings self.addBox.bind('', self.targetAdd) self.targetList.bind('', self.calibratorsShow) #@@@ self.targetList.labelBind('Exp', '', #@@@ self.targetExposureTimeSet) self.calibratorList.bind('', self.calibratorSelect) #@@@ self.calibratorList.labelBind('Exp', '', #@@@ self.calibratorExposureTimeSet) self.calibratorList.bind('', self.calibratorSelectAll) self.targetList.bind('', self.nextTarget) self.calibratorList.bind('', self.nextTarget) self.delButton.config(command=self.targetDelete) self.uvButton.config(command=self.uvPlot) self.upButton.config(command=self.targetUp) self.downButton.config(command=self.targetDown) self.blankButton.config(command=self.blankAdd) self.beamButton[1].config(command=self.beam1Toggle) self.beamButton[2].config(command=self.beam2Toggle) self.beamButton[3].config(command=self.beam3Toggle) self.beamButton[4].config(command=self.beam4Toggle) self.beamButton[5].config(command=self.beam5Toggle) self.beamButton[6].config(command=self.beam6Toggle) self.saveButton.config(command=self.save) self.readButton.config(command=self.read) self.helpButton.config(command=self.help) self.uptimeButton.config(command=self.uptimePlot) self.starPlotButton.config(command=self.starPlot) self.coveragePlotButton.config(command=self.coveragePlot) self.quitButton.config(command=self.root.quit) self.pisBox.focus_set() self.coveragePlotButton.bind('', lambda t=True: self.coveragePlot(text=t)) # Start it up if fileName != None: self.read(fileName) self.start() def checkSpectrometers(self): '''Check instrument configuration consistency if a spectrometer was added/deleted. This consists in checking for redundant frequencies if a spectrometer was added, and checking the baselines if a spectrometer was deleted. ''' # Check for redundant frequencies for spec in xrange(1,4): if self.spectrometer[spec].get() == 0: continue if self.spectrometerRedundantFrequencies(spec): self.spectrometer[spec].set(0) tkMessageBox.showerror('ERROR', "Can't add spectrometer %d as it would have redundant frequencies" % spec) # Check baselines self.cleanupBaselines() self.updateConfig() def displayCalibrators(self): '''Display both target and calibrator lists.''' self.calibratorLabel.grid(row=0,column=1) self.calibratorList.grid(row=1,column=1) self.updateConfig(dateOnly=True) def hideCalibrators(self): '''Display just the target lists.''' self.calibratorLabel.grid_remove() self.calibratorList.grid_remove() self.updateConfig(dateOnly=True) if self.starPlotWindow != None: self.killStarWindow() def cleanupBaselines(self): '''Set baselines to None if they lost one of their stations or their spectrometer or their reference station.''' referenceBeam = self.referenceStation.get() for i in range(1,6): baseline = self.baseline[i].get() if baseline == 'None': continue expr = re.compile('([1-6])-([1-6])\(([1-3])\)') result = expr.match(baseline) assert result != None if int(result.group(1)) != referenceBeam or \ self.station[int(result.group(1))].get() \ == 'None' or \ self.station[int(result.group(2))].get() \ == 'None' or \ self.spectrometer[int(result.group(3))].get() == 0: self.baseline[i].variable.set('None') def changeStations(self): '''If a station was changed, or even looked at, check that the baselines are still valid and change the stations to use for all target and calibrator stars to the current configuration.''' # Change beams to use for each target and calibrator star to the # current configuration. beams = {} for beam in xrange(1,7): self.beamButton[beam].pack_forget() if self.station[beam].get() == 'None': beams[beam] = False else: beams[beam] = True self.beamButton[beam].pack(side=LEFT) for targetName in self.targets.keys(): self.targets[targetName].beams = beams.copy() for calName in self.calibrators.keys(): self.calibrators[calName].beams = beams.copy() self.targetList.select_clear(0,END) for idx in xrange(0,len(self.targetList.get(0,END))): starName = self.targetList.get(idx)[0] self.targetList.delete(idx,idx) if starName == '': self.targetList.insert(idx,('','','','','','','','','','','','')) else: self.targetList.insert(idx,self.targets[starName].guiTuple()) self.calibratorList.delete(0,END) # Check baselines self.cleanupBaselines() # Update plots self.updateConfig() def displayK(self): '''Display the k values for the current configuration.''' self.kBox.config(state=NORMAL) self.kBox.delete('1.0',END) self.kBox.insert(END, 'Spec Baseline k\n') self.kBox.insert(END, '-----------------\n') for spec in xrange(1,4): if self.spectrometer[spec].get() == 1: tag = 'active' else: tag = 'inactive' for beam1 in self.beams[spec]: station1 = self.station[beam1].get().split() stat1 = station1[0] if stat1 == 'None': continue stroke1 = int(station1[1]) for beam2 in self.beams[spec]: if beam2 <= beam1: continue station2 = self.station[beam2].get().split() stat2 = station2[0] if stat2 == 'None': continue stroke2 = int(station2[1]) k = abs(stroke2 - stroke1) / 1000 baseline = '%s-%s' % (stat1, stat2) self.kBox.insert(END, '%-4d %-8s %d\n' % \ (spec,baseline,k), tag) self.kBox.config(state=DISABLED) def updateStationMenus(self): '''Update the station menus, excluding any previously selected stations and any strokes that would introduce redundant frequencies.''' # List of stations which can be on a given beam possibleStations = {} possibleStations[1] = ('AN', 'E2','E3','E4','E5','E6','E7','E8','E9','E10', 'N1','N2','N3','N4','N5','N6','N7','N8','N9', 'N10') possibleStations[2] = ('AC','AN', 'E2','E3','E4','E5','E6','E7','E8','E9', 'N1','N2','N3','N4','N5','N6','N7','N8','N9') possibleStations[3] = ('AE','AN', 'E2','E3','E4','E5','E6','E7','E8','E9', 'N1','N2','N3','N4','N5','N6','N7','N8') possibleStations[4] = ('AW','AN', 'W2','W3','W4','W5','W6','W7','W8', 'N1','N2','N3','N4','N5','N6','N7','N8','N9', 'N10') possibleStations[5] = ('AW','AN', 'W2','W3','W4','W5','W6','W7','W8','W9','W10', 'N1','N2','N3','N4','N5','N6','N7','N8','N9') possibleStations[6] = ('AW','AN', 'W2','W3','W4','W5','W6','W7','W8','W9', 'N1','N2','N3','N4','N5','N6','N7','N8') strokes = (-4000, -3000, -2000, -1000, 0, 1000, 2000, 3000, 4000) stations = ['AC','AE','AW','AN','E3','E6','E7','E10','W4','W7','W10','N3'] usedBeams = [beam for beam in xrange(1,7) \ if self.station[beam].get() != 'None'] usedStations = [self.station[beam].get().split()[0] for beam \ in usedBeams] for beam in range(1,7): thisStation = self.station[beam].get().split()[0] unusedStations = [station for station in stations \ if (station not in usedStations or \ station == thisStation) and \ station in possibleStations[beam]] beamStrokes = [stroke for stroke in strokes \ if not self.beamRedundantFrequencies(beam,stroke)] entries = [] entries.append({'label':'None', 'command':self.changeStations}) for station in unusedStations: if len(beamStrokes) == 0: entries.append({'label':'%s (no non-redundant strokes)' \ % station, 'state':DISABLED}) else: bsEntries = [] for stroke in beamStrokes: kStr = 'k=' for otherStation,otherStroke in \ [self.station[b].get().split() \ for b in usedBeams if b != beam]: k = abs(int(otherStroke) - stroke) / 1000 if len(kStr) > 2: kStr += ',' kStr += '%d[%s]' % (k,otherStation) bsEntries.append({'label':'%d (%s)' % (stroke,kStr), 'value':'%s %d' % (station,stroke), 'command':self.changeStations}) entries.append({'label':station, 'submenu':bsEntries}) self.station[beam].menuSet(entries) def beamRedundantFrequencies(self,beam,stroke): '''Check whether adding the specified stroke to the specified beam would add a redundant frequency in one of the spectrometers. It also checks so that k=0 is not allowed. This was discovered while preparing an obslist for 2012-01-06 when a k=0 n a tracking baseline made it into the obslist and makeFringeconConfig complained. Returns True if it would introduce redundant or a 0 frequencies, else False. ARGS: beam (int): The beam (1-6) to add a station to. stroke(int): The stroke to use for the new beam. ''' assert 1 <= beam <= 6 # For each spectrometer this beam goes into ... for spec in self.spectrometers[beam]: # Skip if this spectrometer is not being used if self.spectrometer[spec].get() == 0: continue # Check for redundant frequencies in this spectrometer. strokes = [] frequencies = [] for otherBeam in self.beams[spec]: if otherBeam == beam: thisStroke = stroke else: station = self.station[otherBeam].get() if station == 'None': continue thisStroke = int(station.split()[1]) for otherStroke in strokes: freq = thisStroke - otherStroke if freq < 0: freq = -freq if freq in frequencies: return True if freq == 0: return True frequencies.append(freq) strokes.append(thisStroke) return False def spectrometerRedundantFrequencies(self,spectrometer): '''Check whether adding the specified spectrometer would add a redundant frequency. Returns True if it would introduce redundant frequencies, else False. ARGS: spectrometer (int): The spectrometer (1-3) to add. ''' assert 1 <= spectrometer <= 3 # Check for redundant frequencies in this spectrometer. strokes = [] frequencies = [] for beam in self.beams[spectrometer]: station = self.station[beam].get() if station == 'None': continue stroke = int(station.split()[1]) for otherStroke in strokes: freq = stroke - otherStroke if freq < 0: freq = -freq if freq in frequencies: return True frequencies.append(freq) strokes.append(stroke) return False def updateBaselineMenus(self): '''Update the tracking baseline menus based on the currently selected stations and spectrometers.''' values = [] values.append('None') entries = [] entries.append({'label':'None'}) referenceBeam = self.referenceStation.get() if referenceBeam > 0: referenceSpectrometers = self.spectrometers[referenceBeam] for beam in range(1,7): if beam == referenceBeam: continue if self.station[beam].get() == 'None': continue value = '%d-%d' % (referenceBeam,beam) used = False for base in range(1,6): if self.baseline[base].get()[0:3] == value: used = True break if used: continue for spec in referenceSpectrometers: if self.spectrometer[spec].get() == 0: continue if beam not in self.beams[spec]: continue values.append('%s(%d)' % (value,spec)) entries.append({'label':'%s(%d)' % (value,spec)}) for i in range(1,6): self.baseline[i].menuSet(entries) def spectrometersForBeam(self,beam): '''Return a list of the spectrometers which the specified beam is configured to go through (not the list of all potential spectrometers which it can go through).''' specs = [] for i in xrange(1,6): baseline = self.baseline[i].get() if baseline == 'None': continue expr = re.compile('([1-6])-([1-6])\(([1-3])\)') result = expr.match(baseline) assert result != None if int(result.group(1)) == beam or int(result.group(2)) == beam: spec = int(result.group(3)) if spec not in specs: specs.append(int(result.group(3))) return specs def photometricScans(self): '''Return a list of a minimal set of incoherent scans. Returns a list of lists, where the sublists are each beam to observe in that scan. Combos needs to be a list because it will contain lists OF lists. It will list beams for photometric scans which can include 2 beams. It may look like this: combos = [[1,2],[3],[4]] which says a phot sequence has 3 scans: one with 2 beams and 2 with only one beam. ''' ### BUG FIX FOR VERSION 1.3.5 # Original JAM version did not give correct photometric scan sequence # when a tracking baseline appeared on two spectrometers. Once a # specific spectrometer was selected the other identical baseline on # second spectrometer was forgotten about in spectrometersForBeam. # I changed the specs = after combo = [i] so that all specs not just # spectrometers used for tracking were checked for photometric scans. # I also make use of spectrometers used to help deicde phot scan # sequence. specsUsed = [] for i in range(1,4): if self.spectrometer[i].get() == 1: specsUsed.append(i) used = {} for i in xrange(1,7): used[i] = False combos = [] for i in xrange(1,7): comboCount=1 # keep track of combined phot scans if self.station[i].get() == 'None': continue if used[i]: continue used[i] = True combo = [i] specs = self.spectrometers[i] if specs == specsUsed: combos.append(combo) continue for j in xrange(i+1,7): if comboCount == 2: continue # no more than 2 SIDs in a phot scan if self.station[j].get() == 'None': continue if used[j]: continue for spec in self.spectrometers[j]: if spec in specs: if spec in specsUsed: continue else: combo.append(j) used[j] = True comboCount=comboCount+1 combos.append(combo) return combos def textBoxWrite(self, fd, textbox, header): '''Write out a text box to a file. ARGS: fd (file descriptor): the file to write to textbox (Text): TK text box header1: header at beginning of first line header2: header at beginning of other lines ''' # Create the headers header1 = '! %s ' % header header2 = '! ' for i in xrange(0,len(header)): header2 = header2 + ' ' headerLen = len(header1) # Break the text into paragraphs. Check for length of # string in relevant textbox str = textbox.get('1.0', END).rstrip().expandtabs() textbox_len = len(str) paragraphs = [] paragraph = [] for line in str.splitlines(): line = line.rstrip() if line == '': if len(paragraph) == 0 and len(paragraphs) == 0: continue if len(paragraph) > 0: paragraphs.append(paragraph) paragraph = [] paragraph.append(line) paragraphs.append(paragraph) paragraph = [] else: paragraph.append(line) if len(paragraph) > 0: paragraphs.append(paragraph) # Remove blank paragraphs at the end idx = len(paragraphs)-1 while idx >= 0: if paragraphs[idx][0] == '': paragraphs = paragraphs[0:idx-1] else: break # Process each paragraph separately # If a textbox is blank this for loop is not executed. for paragraph in paragraphs: # Check that all lines fit within 80 chars, including header bad = False for line in paragraph: if len(line) + headerLen > 80: bad = True break # Print if bad: # Run-on lines present. Reformat. nMax = 80 - headerLen fd.write('%s' % header1) nCurrent = 0 words = ' '.join(paragraph).split() for word in words: if nCurrent == 0: fd.write('%s' % word) nCurrent = len(word) elif len(word) + 1 + nCurrent > nMax: fd.write('\n%s%s' % (header2, word)) nCurrent = len(word) else: fd.write(' %s' % word) nCurrent += len(word) + 1 fd.write('\n') else: # No run-on lines. Print as entered. first = True for line in paragraph: if first: fd.write('%s%s\n' % (header1, line)) first = False else: fd.write('%s%s\n' % (header2, line)) header1 = header2 if header1 != header2: fd.write('%s\n' % header1) # Test for non-zero length of stuff in textbox. This removes an extra # comment line when photometric sequence instructions are blank if textbox_len != 0: fd.write('!\n') def read(self,fileName=None): '''Initialize the GUI by reading an observing file.''' # Get the file name, and open it if fileName == None: fileName = tkFileDialog.askopenfilename(title='Read file') if len(fileName) == 0: return fd = open(fileName, 'r') # Delete previous targets and calibrators self.targets = {} self.calibrators = {} self.targetList.delete(0,END) self.calibratorList.delete(0,END) # Close all open plots self.killAllWindows() # Process line by line calibrator = None last = None for line in fd: line = line.replace('\r','') previous = last last = None if line[0] != '!': # A star line if line == '\n': if previous == 'star': # Adding a blank line to the target list self.calibratorList.delete(0,END) self.targetList.insert(END, ('','','','','','','','','','','','')) self.targetList.select_clear(0,END) idx = len(self.targetList.get(0,END))-1 self.targetList.select_set(idx) self.targetList.see(idx) last = 'star' else: continue last = 'star' tokens = line.strip('\n').split() if len(tokens) < 14 or len(tokens) > 18: continue starName = tokens[0] if len(tokens) == 16: status = tokens[3] expTime = int(tokens[6]) elif len(tokens) == 17: status = tokens[4] expTime = int(tokens[7]) elif len(tokens) == 14: status = tokens[1] expTime = int(tokens[4]) else: status = tokens[2] expTime = int(tokens[5]) if status == 'C' and self.program.get() != 'Astrometry': # For calibrators, just save their name to use for all # targets which appear after them calibrator = starName calExpTime = expTime else: # Target star. Create the new star. star = self.catalog.get(starName) if star == None: tkMessageBox.showerror('ERROR', "Star '%s' not in the catalog" \ % starName) continue if starName in self.targets: print 'get ready for warning' import pdb pdb.set_trace() tkMessageBox.showerror('ERROR', "Star '%s' already a target" % \ starName) continue target = Target(star) target.expTime = expTime # Update the target list, and clear the calibrator list. self.calibratorList.delete(0,END) self.targets[starName] = target self.targetList.insert(END, target.guiTuple()) # Find the calibrators for this target #@@@ Include a test for Astrometry, to avoid execution. #@@@ print 'self.program.get() returns : ',self.program.get() if self.program.get() != 'Astrometry': if calibrator != None: target.theCalibrator = calibrator calibrators = star.findCalibrators(self.catalog) for cal in calibrators: calName = cal[0].npoiName() target.calibrators.append((calName,cal[1])) if self.calibrators.has_key(calName) == False: self.calibrators[calName] = Calibrator(cal[0]) if calName == calibrator: self.calibrators[calName].expTime = calExpTime self.calibrators[calName].targets[starName] = cal[1] # Select the new star and display its calibrators self.targetList.select_clear(0,END) idx = len(self.targetList.get(0,END))-1 self.targetList.select_set(idx) self.targetList.see(idx) if self.program.get() != 'Astrometry': self.calibratorsShow() continue # Comment line. First handle things started by a single token. if line.startswith('! Star') and last == None: last = 'star' continue line = line.rstrip('\n ').expandtabs() tokens = line.strip('\n').split(None,2) if len(tokens) == 3: if tokens[1] == 'Program:': if tokens[2] == 'H-Alpha': self.program.set('H-Alpha (no H-Alpha filter)') else: self.program.set(tokens[2]) if tokens[2] == 'Astrometry': self.hideCalibrators() else: self.displayCalibrators() continue elif tokens[1] == 'PIs:': self.pis.set(tokens[2]) continue elif tokens[1] == 'Date:': expr = re.compile('(\d\d\d\d)-(\d\d)(\d\d)') result = expr.match(tokens[2]) assert result != None self.date.set('%s-%s-%s' % (result.group(1), result.group(2), result.group(3))) continue elif tokens[1] == 'Summary:': self.summary.delete('1.0',END) self.summary.insert(END,'%s\n' % tokens[2]) last = tokens[1] continue elif tokens[1] == 'Goal:': self.goal.delete('1.0',END) self.goal.insert(END,'%s\n' % tokens[2]) last = tokens[1] continue elif tokens[1] == 'Reference_station:': expr = re.compile('([^(]+)\(([\d])\)') result = expr.match(tokens[2]) assert result != None self.referenceStation.set(int(result.group(2))) continue elif tokens[1] == 'Spectrometers:': tokens = line.strip('\n').split(None,3) self.spectrometer[int(tokens[2])].set(1) self.spectrometer[int(tokens[3])].set(1) continue #elif tokens[1] == 'Spectrometer_wavelengths:': # self.waveFile.delete('1.0',END) # self.waveFile.insert(END,'%s' % tokens[2]) # continue elif tokens[1] == 'OBSERVING' and tokens[2] == 'SEQUENCE:': self.sequence.delete('1.0',END) last = tokens[2] continue elif tokens[1] == 'PHOTOMETRIC' and \ tokens[2] == 'SCANS:': self.scans.delete('1.0',END) last = tokens[2] continue # Now things started by two tokens. tokens = line.strip('\n').split(None,2) #@@@print "first run through tokens is",tokens #@@@for token in tokens: #@@@if token == 'Stations:': #@@@print "Found Stations" #@@@print "token length is :",len(tokens) #@@@print "entire token is :",tokens #@@@continue if len(tokens) == 3: if tokens[1] == 'Stations:': beams = {} stations = tokens[2].split() assert len(stations) == 6 beam = 1 expr = re.compile('([^(]+)\(([\d])\)') for station in stations: result = expr.match(station) if result == None: assert station == 'X' else: assert int(result.group(2)) == beam station = result.group(1) if station == 'X': beams[beam] = 'None' else: beams[beam] = station beam += 1 continue elif tokens[1] == 'Stroke_amplitudes:': stations = tokens[2].split() assert len(stations) == 6 beam = 1 for station in stations: if station == 'X': assert beams[beam] == 'None' self.station[beam].variable.set('None') else: self.station[beam].variable.set('%s %s' % \ (beams[beam], station)) beam += 1 continue #elif tokens[1] == 'Spectrometers:': # specs = tokens[2].split() # print "spectrometer tokens line 1537 are :",specs # for spec in specs: # self.spectrometer[int(spec)].set(1) #continue elif tokens[1] == 'Spectrometer_apertures:': self.spectrometerApertures.variable.set(tokens[2]) continue elif tokens[1] == 'NAT_quad_apertures:': self.natApertures.variable.set(tokens[2]) continue elif tokens[1] == 'H-Alpha_filters:': if tokens[2] == 'Yes': self.program.set('H-Alpha (with H-Alpha filter)') self.displayCalibrators() continue elif tokens[1] == 'Tracking_baselines:': baselines = tokens[2].split() assert len(baselines) == 5 idx = 1 for baseline in baselines: if baseline == 'X': self.baseline[idx].variable.set('None') else: self.baseline[idx].variable.set(baseline) idx += 1 continue # Handle continuation of a text box if line.startswith('!---------------------------------------'): continue elif previous == 'Summary:': str = line[11:] self.summary.insert(END,'%s\n' % str) last = previous continue elif previous == 'Goal:': str = line[11:] self.goal.insert(END,'%s\n' % str) last = previous continue elif previous == 'SEQUENCE:': str = line[3:] expr = re.compile('(Dwell_time:)|(All scans should be)|(Do photometric scans as)') result = expr.match(str) # print 'result is :',result if result == None: if len(str) > 0 or self.sequence.index(CURRENT) > '1.0': # print "self.sequence.index(CURRENT) is :",self.sequence.index(CURRENT) # print "string is :",str self.sequence.insert(END,'%s\n' % str) last = previous continue elif previous == 'SCANS:': autoIdx = line.find('The sequence will require,') if autoIdx > -1: str = line[3:autoIdx].rstrip() if len(str) > 0: self.scans.insert(END,'%s\n' % str) previous = None else: str = line[3:] if len(str) > 0 or self.scans.index(CURRENT) != '1.0': self.scans.insert(END,'%s\n' % str) last = previous # Close the file fd.close() # Update open plots, as well as stations for each star self.changeStations() def save(self,*ignore): '''Write out the observing file, as well as an uptime plot with the same root but an ".eps" extension.''' # Test instrument setup only if at least one station is specified nBeams = len([i for i in xrange(1,7) \ if self.station[i].get() != 'None']) if nBeams > 0: # Error if only one station specified if nBeams < 2: tkMessageBox.showerror('ERROR', 'Too few stations specified') return # Error if no reference station specified referenceStation = self.referenceStation.get() if referenceStation == 0 or \ self.station[referenceStation].get() == 'None': tkMessageBox.showerror('ERROR', 'No reference station specified') return # Error if no spectrometers specified if self.spectrometer[1].get() == 0 and \ self.spectrometer[2].get() == 0 and \ self.spectrometer[3].get() == 0: tkMessageBox.showerror('ERROR','No spectrometers specified') return # Error if incomplete set of tracking baselines specified nBaselines = len([i for i in xrange(1,6) \ if self.baseline[i].get() != 'None']) if nBaselines != nBeams - 1: tkMessageBox.showerror('ERROR', 'Too few baselines specified') return # Compile the list of calibrators and targets in the order they are # to be observed. Verify that each target has a calibrator (in # imaging and H-alpha mode only). imaging = self.program.get() != 'Astrometry' calibrators = [] stars = [] stations = {} missing = '' for star in self.targetList.get(0,END): target = star[0] if target == '': stars.append(None) else: currentStations = [self.station[beam].get().split()[0] \ for beam in \ self.targets[target].beams.keys() \ if self.targets[target].beams[beam]] if imaging: calName = self.targets[target].theCalibrator if calName == None: missing = missing + ' %s' % target elif calName not in calibrators: stars.append(self.calibrators[calName]) stations[self.calibrators[calName]] = currentStations calibrators.append(calName) stars.append(self.targets[target]) stations[self.targets[target]] = currentStations if missing != '': tkMessageBox.showwarning('WARNING', 'Targets%s are missing calibrators' \ % missing) # Get the file name, and open it fileName = tkFileDialog.asksaveasfilename(title='Save As') if len(fileName) == 0: return fd = open(fileName, 'w') # Suspend cursor self.watchCursor() # Print header delimiter = '!-------------------------------------------------------------------------------\n' fd.write('! Observing_list_for: %s\n' % self.date.get()) fd.write(delimiter) if self.program.get().startswith('H-Alpha'): fd.write('! Program: H-Alpha\n') else: fd.write('! Program: %s\n' % self.program.get()) fd.write('! PIs: %s\n' % self.pis.get()) fd.write('! Date: %4d-%02d%02d\n' % (self.date.year(), self.date.month(), self.date.day())) fd.write('!\n'); self.textBoxWrite(fd,self.summary,'Summary:') self.textBoxWrite(fd,self.goal,'Goal: ') fd.write(delimiter) fd.write('! Star Name Type m_V D Int Sp. PI RA (BG) Dec | Rise (UT) Set\n') # Configure the stations usedStations = [self.station[beam].get().split()[0] \ for beam in xrange(1,7) \ if self.station[beam].get() != 'None'] n = NPOI(usedStations, float(self.zdMax.variable.get()), self.program.get()) # Get the MJD jd = novas.Cal2JD(self.date.year(), self.date.month(), self.date.day(), n.timezone) mjd = int(astro.jd2mjd(jd)) # Write out the targets and calibrators. for star in stars: if star == None: fd.write('\n') continue n.stationsInUse = stations[star] rise,set = n.riseSet(star.star,int(mjd)) if rise != None: star.rise = astro.inRange(rise + n.timezone, 0, 24) else: star.rise = None if set != None: star.set = astro.inRange(set + n.timezone, 0, 24) else: star.set = None star.obsFileWrite(fd) # Write out the configuration information fd.write(delimiter) fd.write('!\n') fd.write('! SETUP:\n') fd.write('!\n') fd.write('! Stations:') for i in range(1,7): if self.station[i].get() == 'None': fd.write(' X') else: fd.write(' %s' % self.station[i].get().split()[0]) fd.write('(%d)' % i) fd.write('\n') refIdx = self.referenceStation.get() if refIdx == 0: fd.write('! Reference_station: ?(?)\n') else: fd.write('! Reference_station: %s(%d)\n' % \ (self.station[refIdx].get().split()[0], refIdx)) fd.write('!\n') fd.write('! Stroke_amplitudes:') for i in range(1,7): if self.station[i].get() == 'None': fd.write(' X') else: fd.write(' %s' % self.station[i].get().split()[1]) fd.write('\n!\n') fd.write('! Tracking_baselines:') for i in range(1,6): if self.baseline[i].get() == 'None': fd.write(' X') else: fd.write(' %s' % self.baseline[i].get()) fd.write('\n!\n') fd.write('! Spectrometers:') specsInUse=[] for i in range(1,4): if self.spectrometer[i].get() == 1: fd.write(' %d' % i) specsInUse.append(i) fd.write('\n') fd.write('! Software_spectrometers:') for i in range(1,4): if self.spectrometer[i].get() == 1: fd.write(' %d' % i) fd.write('\n') if len(specsInUse) != 2: errorString='Select two spectrographs, then try again.' tkMessageBox.showerror('specsInUse ERROR',errorString) return waveFile = selectWavelengthsFile(self.program.get(),specsInUse) # Next line is what wrote the wavbelengths file entered by the # user within the GUI # self.textBoxWrite(fd,waveFile,' Spectrometer_wavelengths:') fd.write('! Spectrometer_wavelengths: %s\n' % waveFile) fd.write('! Spectrometer_apertures: %s\n' % self.spectrometerApertures.get()) fd.write('! NAT_quad_apertures: %s\n!\n' % self.natApertures.get()) fd.write('! Laser_rejection_filters: ') if self.program.get() == 'Astrometry': fd.write('Yes\n') else: fd.write('No\n') # fd.write('!\n') fd.write('! H-Alpha_filters: ') if self.program.get() == 'H-Alpha (with H-Alpha filter)': fd.write('Yes\n') else: fd.write('No\n') fd.write('!\n') fd.write('! OBSERVING SEQUENCE:\n!\n') fd.write('! Dwell_time: 180 seconds\n!\n') fd.write('! All scans should be 30 seconds integration.\n!\n') fd.write('! Do photometric scans as noted below in the next section.\n!\n') self.textBoxWrite(fd,self.sequence,'') fd.write('! PHOTOMETRIC SCANS:\n!\n') self.textBoxWrite(fd,self.scans,'') if self.program.get() != 'Astrometry': fd.write('! The sequence will require, in some order, photometric scans with only\n') fd.write('! light from the following beams:\n!\n') idx = 1 for scan in self.photometricScans(): fd.write('! %d)' % idx) first = True for beam in scan: if first: first = False else: fd.write(' and') fd.write(' %s(%d)' % \ (self.station[beam].get().split()[0], beam)) fd.write('\n') idx += 1 fd.write('!\n') # End by writing the obsprep version number fd.write('! Prepared with obsprep version '+obsprepVersion+'\n') fd.write(delimiter) if fd != sys.stdout: fd.close() # Restore the cursor self.defaultCursor() # Write out the uptime plot if fileName != '': path = os.path.splitext(fileName) self.uptimePlot('%s.eps' % path[0]) def targetAdd(self,*ignore): '''Add a single target star, whose name is specified in the "Add" entry box.''' # If entry block is blank, add a blank line if self.newStar.get() == '': self.calibratorList.delete(0,END) self.targetList.insert(END, ('','','','','','','','','','','','')) self.targetList.select_clear(0,END) self.targetList.select_set(END) self.targetList.see(END) return # Suspend cursor input self.watchCursor() # Fetch the star star = self.catalog.get(self.newStar.get()) if star == None: self.defaultCursor() tkMessageBox.showerror('ERROR', "Star '%s' not in the catalog" % \ self.newStar.get()) return # Error if this star is already a target starName = star.npoiName() if starName in self.targets: self.defaultCursor() tkMessageBox.showerror('ERROR', "Star '%s' already a target" % \ self.newStar.get()) return # Create the new target target = Target(star) # Initialize it being observed with the complete set of configured # stations. for beam in xrange(1,7): if self.station[beam].get() == 'None': target.beams[beam] = False else: target.beams[beam] = True # Got one. Update the target list, and clear the calibrator list. self.calibratorList.delete(0,END) self.targets[starName] = target self.targetList.insert(END, target.guiTupleAdd()) # Find the calibrators for this target calibrators = star.findCalibrators(self.catalog) for cal in calibrators: calName = cal[0].npoiName() target.calibrators.append((calName,cal[1])) if self.calibrators.has_key(calName) == False: self.calibrators[calName] = Calibrator(cal[0]) self.calibrators[calName].beams = target.beams.copy() self.calibrators[calName].targets[starName] = cal[1] # Select the new star and display its calibrators self.targetList.select_clear(0,END) idx = len(self.targetList.get(0,END))-1 self.targetList.select_set(idx) self.targetList.see(idx) self.calibratorsShow() self.updateStars() # Restore cursor input self.addBox.delete(0,END) self.defaultCursor() def targetSelectionGet(self): '''Get the single selected star from the list of target stars. Returns (starName,idx) for the selection, or (None,None) if a single star has not been selected.''' indices = [ int(x) for x in self.targetList.curselection() ] if len(indices) != 1: tkMessageBox.showerror('ERROR', 'No single selected target star') return(None,None) idx = indices[0] return(self.targetList.get(idx)[0],idx) def calibratorsShow(self,*ignore): '''Show the calibrators for the selected target star, sorted by increasing distance. variable s in for loop below contains calibrator information. Allowing diameter to be displayed in planning GUI required changing function guiTuple in class Calibrator to record diameter information.''' # Get the target selected by the cursor indices = [ int(x) for x in self.targetList.curselection() ] if len(indices) != 1: return idx = indices[0] targetName = self.targetList.get(idx)[0] # Update the calibrators list for this star self.calibratorList.delete(0,END) if targetName == '': return target = self.targets[targetName] it = -1 idx = 0 for star in target.calibrators: # check for targetName and skip if it is in calibrator list if targetName == star[0]: continue s = ('%4.1f' % star[1],) + self.calibrators[star[0]].guiTuple() self.calibratorList.insert(END,s) if target.theCalibrator == star[0]: it = idx idx += 1 if it > -1: self.calibratorList.select_set(it) self.calibratorList.see(it) def targetDelete(self,*ignore): '''Delete the selected target star from the observing list.''' # Get the target selected by the cursor starName,idx = self.targetSelectionGet() if idx == None: return # Remove it from the target list self.targetList.delete(idx,idx) self.calibratorList.delete(0,END) if starName == '': return # Remove it from the list of stars for each calibrator for star in self.targets[starName].calibrators: del self.calibrators[star[0]].targets[starName] # Remote it from the targets del self.targets[starName] # Remove any calibrators which no longer have targets dead = [star for star in self.calibrators \ if len(self.calibrators[star].targets) == 0] for star in dead: del self.calibrators[star] # Update the necessary plots if self.uvPlots.has_key(starName): self.uvPlots[starName].destroy() del self.uvPlots[starName] if self.visPlots.has_key(starName): self.visPlots[starName].destroy() del self.visPlots[starName] self.updateStars() def beamToggle(self,beam): '''Toggle whether the specified beam is used for the selected target. ARGS: beam (int): the beam to toggle ''' # Get the target selected by the cursor starName,idx = self.targetSelectionGet() if idx == None or starName == '': return # Toggle whether the specified beam is used when observing this target self.targets[starName].beams[beam] = not self.targets[starName].beams[beam] # Update its display self.targetList.delete(idx,idx) self.targetList.insert(idx,self.targets[starName].guiTuple()) self.targetList.select_set(idx) self.targetList.see(idx) self.updateStarConfig(starName) def beam1Toggle(self,*ignore): '''Toggle whether the first beam is used for the selected target.''' self.beamToggle(1) def beam2Toggle(self,*ignore): '''Toggle whether the second beam is used for the selected target.''' self.beamToggle(2) def beam3Toggle(self,*ignore): '''Toggle whether the third beam is used for the selected target.''' self.beamToggle(3) def beam4Toggle(self,*ignore): '''Toggle whether the fourth beam is used for the selected target.''' self.beamToggle(4) def beam5Toggle(self,*ignore): '''Toggle whether the fifth beam is used for the selected target.''' self.beamToggle(5) def beam6Toggle(self,*ignore): '''Toggle whether the sixth beam is used for the selected target.''' self.beamToggle(6) def targetExposureTimeSet(self,*ignore): '''Set the exposure time for the selected target.''' # Get the target selected by the cursor starName,idx = self.targetSelectionGet() if idx == None or starName == '': return # Prompt for its new exposure time, and set it expTime = 30 if expTime == None: return self.targets[starName].expTime = expTime self.targetList.delete(idx,idx) self.targetList.insert(idx,self.targets[starName].guiTuple()) self.targetList.select_set(idx) self.targetList.see(idx) def uptimePlot(self,fileName=None): '''Create an uptime plot.''' # Creating a new plot window? if fileName != None: newWindow = False elif self.uptimePlotWindow == None: newWindow = True else: newWindow = False # Configure the stations usedStations = [self.station[beam].get().split()[0] \ for beam in xrange(1,7) \ if self.station[beam].get() != 'None'] if len(usedStations) == 0 and newWindow: tkMessageBox.showerror('ERROR', 'No stations specified') return n = NPOI(usedStations, float(self.zdMax.variable.get()), self.program.get()) # Suspend cursor input self.watchCursor() # Compile the list of calibrators and targets in the order they are # to be observed. imaging = self.program.get() != 'Astrometry' calibrators = [] calibrators.append(None) stars = [] stations = {} for star in self.targetList.get(0,END): target = star[0] if target == '': continue currentStations = [self.station[beam].get().split()[0] \ for beam in self.targets[target].beams.keys() \ if self.targets[target].beams[beam]] if imaging: calName = self.targets[target].theCalibrator if calName not in calibrators: stars.append(self.calibrators[calName]) stations[self.calibrators[calName]] = currentStations calibrators.append(calName) stars.append(self.targets[target]) stations[self.targets[target]] = currentStations nStars = len(stars) # Plot them jd = novas.Cal2JD(self.date.year(), self.date.month(), self.date.day(), n.timezone) mjd = astro.jd2mjd(jd) ix = 0. args = [] x = [] y = [] fontdict = [] label = [] # For each star ... for star in stars: # Plot lines on y-axis using order of star in observing list ix += 1 # Get uptime for just the subset of stations used with this star n.stationsInUse = stations[star] rise,set = n.riseSet(star.star,int(mjd)) if rise != None: # Different line styles for targets and calibrators if isinstance(star,Target): subSymbol = 'b:' fullSymbol = 'b-' meridianSymbol = 'b+' else: subSymbol = 'r:' fullSymbol = 'r--' meridianSymbol = 'r+' # First plot uptime for the full set of stations n.stationsInUse = usedStations frise,fset = n.riseSet(star.star,int(mjd)) if frise != None: frise = astro.inRange(frise + n.timezone, 0, 24) fset = astro.inRange(fset + n.timezone, 0, 24) if frise > fset: frise -= 24 if frise > 14: frise -= 24 fset -= 24 args.append([frise,fset]) args.append([ix,ix]) args.append(fullSymbol) # Now plot additional observable time that might be available # using just the subset of stations used by this star. mjdRise = mjd + astro.inRange(rise,-12,12) / 24. mjdSet = mjd + astro.inRange(set,-12,12) / 24. haRise = astro.inRange(astro.last(mjdRise, self.site) - \ star.star.ra/15., -12, 12) haSet = astro.inRange(astro.last(mjdSet, self.site) - \ star.star.ra/15., -12, 12) rise = astro.inRange(rise + n.timezone, 0, 24) set = astro.inRange(set + n.timezone, 0, 24) if rise > set: rise -= 24 if rise > 14: rise -= 24 set -= 24 if frise == None: args.append([rise,set]) args.append([ix,ix]) args.append(subSymbol) else: if rise < frise: args.append([rise,frise]) args.append([ix,ix]) args.append(subSymbol) if set > fset: args.append([fset,set]) args.append([ix,ix]) args.append(subSymbol) # Mark median crossing with a plus if haRise < 0 and haSet > 0: meridian = rise + (set - rise) * (0. - haRise) / \ (haSet - haRise) args.append([meridian]) args.append([ix]) args.append(meridianSymbol) # Label the line if rise > 2: xpos = rise - 0.1 font = {'fontsize':'x-small','verticalalignment':'center', 'horizontalalignment':'right'} else: xpos = set + 0.1 font = {'fontsize':'x-small','verticalalignment':'center', 'horizontalalignment':'left'} x.append(xpos) y.append(ix) fontdict.append(font) label.append(star.star.npoiName()) if len(stations[star]) < len(usedStations): stationsLabel = '(' for station in stations[star]: stationsLabel = '%s%s,' % (stationsLabel, station) stationsLabel = '%s)' % stationsLabel[0:-1] x.append(xpos) y.append(ix - 0.3) fontdict.append(font) label.append(stationsLabel) else: font = {'fontsize':'x-small','verticalalignment':'center', 'horizontalalignment':'left'} x.append(0.3) y.append(ix) fontdict.append(font) label.append(star.star.npoiName()+' (inaccessible)') if fileName != None: matplotlib.rcParams['backend'] = 'PS' #backEnd=matplotlib.get_backend() #print 'The matplotlib backend is '+backEnd+'\n'#@@@ #matplotlib.use('PS') figure(1000) else: backEnd=matplotlib.get_backend() if backEnd != 'TkAgg': matplotlib.use('TkAgg') if newWindow: w = Toplevel() w.protocol('WM_DELETE_WINDOW', self.killUptimeWindow) w.wm_title('Uptime Plot') w.figFrame = tktools.EmbeddedFigure(w) w.figFrame.pack() self.uptimePlotWindow = w else: w = self.uptimePlotWindow figure(w.figFrame.figureNumber) clf() plot(*args) # Plot dawn and dusk zdCivilTwilight = 96. jd = novas.Cal2JD(self.date.year(), self.date.month(), self.date.day(), n.timezone) mjd = astro.jd2mjd(jd) dusk,dawn = astro.twilight(astro.npoi(), n.timezone, int(mjd), zdCivilTwilight) dusk = (dusk + n.timezone) % 24 dawn = dawn + n.timezone plot([dusk,dusk], [0,nStars+1], 'k--') plot([dawn,dawn], [0,nStars+1], 'k--') # Label each line for i in xrange(0,len(x)): text(x[i],y[i],label[i],fontdict=fontdict[i]) axis([0,14,0,nStars+2]) xlabel('UT (hrs)') ylabel('Star List #') # Plot title str = '%s (' % self.date.get() first = True for i in xrange(1,7): station = self.station[i].get() if station != 'None': if first: first = False else: str = '%s, ' % str str = '%s%s' % (str, station.split()[0]) str = '%s)' % str title(str) # Plot legend targetLine = matplotlib.lines.Line2D([],[],color='b') calibratorLine = matplotlib.lines.Line2D([],[],linestyle='--', color='r') legend((targetLine,calibratorLine),('Target','Calibrator'),loc=2) # Display it if fileName == None: w.figFrame.show() else: savefig(fileName) # Restore cursor input self.defaultCursor() def watchCursor(self): '''Set cursor in all top-level windows to a watch.''' # Root self.root.config(cursor='watch') self.root.update_idletasks() # Visibility plots for p in self.visPlots.values(): p.config(cursor='watch') p.update_idletasks() # UV plots for p in self.uvPlots.values(): p.config(cursor='watch') p.update_idletasks() # Uptime plot if self.uptimePlotWindow != None: self.uptimePlotWindow.config(cursor='watch') self.uptimePlotWindow.update_idletasks() # Coverage plot if self.coveragePlotWindow != None: self.coveragePlotWindow.config(cursor='watch') self.coveragePlotWindow.update_idletasks() # Star plot if self.starPlotWindow != None: self.starPlotWindow.config(cursor='watch') self.starPlotWindow.update_idletasks() def defaultCursor(self): '''Set cursor in all top-level windows to its default.''' # Root self.root.config(cursor='') self.root.update_idletasks() # Visibility plots for p in self.visPlots.values(): p.config(cursor='') p.update_idletasks() # UV plots for p in self.uvPlots.values(): p.config(cursor='') p.update_idletasks() # Uptime plot if self.uptimePlotWindow != None: self.uptimePlotWindow.config(cursor='') self.uptimePlotWindow.update_idletasks() # Coverage plot if self.coveragePlotWindow != None: self.coveragePlotWindow.config(cursor='') self.coveragePlotWindow.update_idletasks() # Star plot if self.starPlotWindow != None: self.starPlotWindow.config(cursor='') self.starPlotWindow.update_idletasks() def killUVWindow(self, window): '''Kill a UV or visibility plot window, cleaning up the bookkeeping first. ARGS: window: window to kipp ''' if window.plotType == 'visibility': del self.visPlots[window.starName] window.destroy() else: del self.uvPlots[window.starName] window.destroy() def killUptimeWindow(self): '''Kill the uptime plot window, cleaning up the bookkeeping first.''' self.uptimePlotWindow.destroy() self.uptimePlotWindow = None def killCoverageWindow(self): '''Kill the coverage plot window, cleaning up the bookkeeping first.''' self.coveragePlotWindow.destroy() self.coveragePlotWindow = None def killStarWindow(self): '''Kill the star plot window, cleaning up the bookkeeping first.''' self.starPlotWindow.destroy() self.starPlotWindow = None def killAllWindows(self): '''Kill all open windows.''' # Visibility plots for star in self.visPlots.keys(): self.visPlots[star].destroy() del self.visPlots[star] # UV plots for star in self.uvPlots.keys(): self.uvPlots[star].destroy() del self.uvPlots[star] # Uptime plot if self.uptimePlotWindow != None: self.killUptimeWindow() # Coverage plot if self.coveragePlotWindow != None: self.killCoverageWindow() # Star plot if self.starPlotWindow != None: self.killStarWindow() def uvPlot(self,fileName=None): '''Plot the UV coverage for the selected star.''' # Get the target selected by the cursor starName,idx = self.targetSelectionGet() if idx == None or starName == '': return # Plot it if self.uvPlots.has_key(starName): return self.uvReplot(starName, mode='UV') def visPlot(self,mode='Disk',fileName=None): '''Plot the visibility for the selected star.''' # Get the target selected by the cursor starName,idx = self.targetSelectionGet() if idx == None or starName == '': return # Plot it if self.visPlots.has_key(starName): if self.visPlots[starName].mode == mode: return self.visPlots[starName].destroy() del self.visPlots[starName] self.uvReplot(starName, mode=mode) def uvReplot(self, master, mode='', fileName=None): '''Plot the UV coverage or visibility for the selected star. ARGS: master (Event/Toplevel/String): The window, or event in that window, which controls this plot. If a string, then open a new plot for the star specified by that string. mode (String): Plotting mode (UV, Disk, or Binary). Used for new windows only. visibility (Boolean): True->plot visibility, False->plot uv coverage fileName (String): File to send plot to. If None, then plot to the GUI. ''' # Get the plotting window that we're replotting if isinstance(master,Event): newWindow = False w = master.widget.master.master starName = w.starName mode = w.mode resetHaBoth = False resetHaEnd = master.widget.resetHaEnd elif isinstance(master,Toplevel): newWindow = False w = master starName = w.starName mode = w.mode resetHaBoth = True else: assert mode == 'UV' or mode == 'Disk' or mode == 'Binary' newWindow = True starName = master # Get the star assert starName != None try: star = self.targets[starName] except KeyError: if not newWindow: w.destroy() return # Get the star parameters from the entry boxes if not newWindow: if mode == 'Disk': try: diameter = w.diameterValue.get() except ValueError: tkMessageBox.showerror('ERROR', 'Diameter must be a floating point number, in mas') return elif mode == 'Binary': try: diameter = w.diameterValue.get() except ValueError: tkMessageBox.showerror('ERROR', 'Primary diameter must be a floating point number, in mas') return try: secDiameter = w.secDiameterValue.get() except ValueError: tkMessageBox.showerror('ERROR', 'Secondary diameter must be a floating point number, in mas') return try: separation = w.separationValue.get() except ValueError: tkMessageBox.showerror('ERROR', 'Separation must be a floating point number, in mas') return try: dmag = w.dmagValue.get() except ValueError: tkMessageBox.showerror('ERROR', 'Delta mag must be a floating point number, in mag') return try: pa = w.paValue.get() except ValueError: tkMessageBox.showerror('ERROR', 'Position angle must be a floating point number, in deg') return # Configure the stations used by this star usedStations = [self.station[beam].get().split()[0] \ for beam in self.targets[starName].beams.keys() \ if self.targets[starName].beams[beam]] if len(usedStations) == 0 and newWindow: tkMessageBox.showerror('ERROR', 'No stations specified') return n = NPOI(usedStations, float(self.zdMax.variable.get()), self.program.get()) # Suspend cursor input. self.watchCursor() # The channel wavelengths if self.program.get().startswith('H-Alpha'): wavelengths = [656.3e-9] else: wavelengths = [849.4e-9, 820.9e-9, 793.9e-9, 768.3e-9, 744.2e-9, 722.9e-9, 701.5e-9, 683.1e-9, 664.6e-9, 648.9e-9, 617.7e-9, 603.5e-9, 590.7e-9, 577.9e-9, 566.5e-9, 556.6e-9] # print 'Got the wavelengths.' # Find the baseline pairs pairs = [] usedBeams = [beam for beam in xrange(1,7) if star.beams[beam]] for beam in usedBeams: for beam2 in [b for b in usedBeams if b > beam]: for spec in [s for s in xrange(1,4) \ if self.spectrometer[s].get()]: if beam in self.beams[spec] and beam2 in self.beams[spec]: pairs.append((self.station[beam].get().split()[0], self.station[beam2].get().split()[0])) break # Get local rise and set time for the star jd = novas.Cal2JD(self.date.year(), self.date.month(), self.date.day(), n.timezone) mjd = astro.jd2mjd(jd) rise,set = n.riseSet(star.star,int(mjd)) if rise == None and newWindow: self.defaultCursor() tkMessageBox.showerror('ERROR', 'Star never observable') return # Bound rise and set time to within twilight times zdCivilTwilight = 96. dusk,dawn = astro.twilight(self.site,n.timezone,int(mjd), zd=zdCivilTwilight) dusk = astro.inRange(dusk,-12,12) dawn = astro.inRange(dawn,-12,12) rise = astro.inRange(rise,-12,12) set = astro.inRange(set,-12,12) if rise >= dusk and rise <= dawn: if set > rise: set = min([set,dawn]) else: set = dawn elif set >= dusk and set <= dawn: rise = dusk elif (rise < dusk or rise > set) and set > dawn: rise = dusk set = dawn elif newWindow: self.defaultCursor() tkMessageBox.showerror('ERROR', 'Star never observable') return else: rise = None set = None if not rise == None: mjdRise = mjd + rise / 24. mjdSet = mjd + set / 24. haRise = astro.inRange(astro.last(mjdRise, self.site) - \ star.star.ra/15., -12, 12) haSet = astro.inRange(astro.last(mjdSet, self.site) - \ star.star.ra/15., -12, 12) if newWindow: # Create new window if necessary w = Toplevel() w.protocol('WM_DELETE_WINDOW', lambda x=w:self.killUVWindow(x)) w.mode = mode w.starName = starName w.figFrame = tktools.EmbeddedFigure(w,figsize=(8,8)) if self.root.winfo_screenheight() < 815: w.figFrame = tktools.EmbeddedFigure(w,figsize=(6,6)) w.figFrame.config(borderwidth=2,relief=RAISED) w.figFrame.pack(side=TOP, fill=BOTH, expand=1) w.controls = Frame(w,borderwidth=2,relief=RAISED) if mode != 'Binary': Label(w.controls, text=' HA Start (hrs):').pack(side=LEFT) w.haStart = Scale(w.controls,orient=HORIZONTAL,from_=haRise, to=haSet,resolution=0.01) w.haStart.set(haRise) w.haStart.resetHaEnd = True w.haStart.pack(side=LEFT) Label(w.controls, text=' HA End (hrs):').pack(side=LEFT) w.haEnd = Scale(w.controls,orient=HORIZONTAL,from_=haRise, to=haSet, resolution=0.01) w.haEnd.set(haSet) w.haEnd.resetHaEnd = False w.haEnd.pack(side=LEFT) w.haStart.bind('', self.uvReplot) w.haStart.bind('', self.uvReplot) w.haEnd.bind('', self.uvReplot) w.haEnd.bind('', self.uvReplot) if mode == 'Disk': w.wm_title('%s Visibility' % starName) w.plotType = 'visibility' self.visPlots[starName] = w w.diameterValue = DoubleVar() w.diameterLabel = Label(w.controls, text=" Diameter (mas):") w.diameterLabel.pack(side=LEFT) w.diameterEntry = Entry(w.controls, textvariable=w.diameterValue, width=10) w.diameterEntry.pack(side=LEFT) w.diameterEntry.bind('', self.uvReplot) w.diameterEntry.resetHaEnd = False # Set the diameter to the star's diameter; if None, then 1 mas diameter = self.targets[starName].star.diameter if diameter == None: diameter = 1. w.diameterValue.set(diameter) elif mode == 'Binary': w.wm_title('%s Visibility' % starName) w.plotType = 'visibility' self.visPlots[starName] = w Label(w.controls, text=' HA (hrs):').grid(row=0, column=0, sticky=E) w.haStart = Scale(w.controls,orient=HORIZONTAL,from_=haRise, to=haSet,resolution=0.01) w.haStart.set(haRise) w.haStart.resetHaEnd = True w.haStart.grid(row=0,column=1,sticky=W) w.haStart.bind('', self.uvReplot) w.haStart.bind('', self.uvReplot) w.diameterValue = DoubleVar() w.diameterLabel = Label(w.controls, text=" Primary Diameter (mas):") w.diameterLabel.grid(row=1,column=0,sticky=E) w.diameterEntry = Entry(w.controls, textvariable=w.diameterValue, width=10) w.diameterEntry.grid(row=1,column=1,sticky=W) w.diameterEntry.bind('', self.uvReplot) # Set the diameter to the star's diameter; if None, then 1 mas # Set the binary diameter to 1 mas. diameter = self.targets[starName].star.diameter if diameter == None: diameter = 1. w.diameterValue.set(diameter) w.secDiameterValue = DoubleVar() w.secDiameterLabel = Label(w.controls, text=" Secondary Diameter (mas):") w.secDiameterLabel.grid(row=2,column=0,sticky=E) w.secDiameterEntry = Entry(w.controls, textvariable=w.secDiameterValue, width=10) w.secDiameterEntry.grid(row=2,column=1,sticky=W) w.secDiameterEntry.bind('', self.uvReplot) secDiameter = 1. w.secDiameterValue.set(secDiameter) w.separationValue = DoubleVar() w.separationLabel = Label(w.controls, text=" Separation (mas):") w.separationLabel.grid(row=0,column=2,sticky=E) w.separationEntry = Entry(w.controls, textvariable=w.separationValue, width=10) w.separationEntry.grid(row=0,column=3,sticky=W) w.separationEntry.bind('', self.uvReplot) separation = 0 w.separationValue.set(separation) w.paValue = DoubleVar() w.paLabel = Label(w.controls, text=" Position Angle (deg):") w.paLabel.grid(row=1,column=2,sticky=E) w.paEntry = Entry(w.controls, textvariable=w.paValue, width=10) w.paEntry.grid(row=1,column=3,sticky=W) w.paEntry.bind('', self.uvReplot) pa = 0 w.paValue.set(pa) w.dmagValue = DoubleVar() w.dmagLabel = Label(w.controls, text=' Delta mag (mag):') w.dmagLabel.grid(row=2,column=2,sticky=E) w.dmagEntry = Entry(w.controls, textvariable=w.dmagValue, width=10) w.dmagEntry.grid(row=2,column=3,sticky=W) w.dmagEntry.bind('', self.uvReplot) dmag = 0 w.dmagValue.set(dmag) w.diameterEntry.resetHaEnd = False w.secDiameterEntry.resetHaEnd = False w.separationEntry.resetHaEnd = False w.paEntry.resetHaEnd = False w.dmagEntry.resetHaEnd = False elif mode == 'UV': w.wm_title('%s UV Coverage' % starName) w.plotType = 'UV' self.uvPlots[starName] = w w.controls.pack(side=BOTTOM,fill=X) # Initially plot entire ha range haStart = haRise if mode == 'Binary': haEnd = haStart else: haEnd = haSet else: # Not a new window, but check to see if we need to readjust slider # values for ha start and end if resetHaBoth: haStart = haRise w.haStart.config(from_=haRise, to=haSet) w.haStart.set(haRise) if mode == 'Binary': haEnd = haStart else: haEnd = haSet w.haEnd.config(from_=haRise, to=haSet) w.haEnd.set(haSet) else: haStart = w.haStart.get() if mode == 'Binary': haEnd = haStart else: if resetHaEnd: haEnd = haStart w.haEnd.config(from_=haStart) w.haEnd.set(haStart) else: haEnd = w.haEnd.get() # Plot it if fileName == None: backEnd = matplotlib.get_backend() if backEnd != 'TkAgg': matplotlib.use('TkAgg') else: matplotlib.use('PS') figure(w.figFrame.figureNumber) clf() axes([0.1, 0.1, 0.8, 0.8]) ax = axes() # Plot the full visibility curve as a dotted line if mode == 'Disk': s = arange(0.01,2,0.01) * 180. * 3600000. / (pi * diameter) v2 = [diskVis2(ss, diameter) for ss in s] plot(s/1.e6, v2, 'k:') text(0.5,0.8,r'$\theta = %.2f\ \rm{mas}$' % diameter, fontsize=20, transform = ax.transAxes, horizontalalignment='center') # Plot data as a curve with minute intervals. If only plotting a # single hour angle, plot it as a plus sign. lines = [] labels = [] has = [] if not rise == None: sinLat = sin(self.site.lat * novas.DEG2RAD) cosLat = cos(self.site.lat * novas.DEG2RAD) sinDec = sin(star.star.dec * novas.DEG2RAD) cosDec = cos(star.star.dec * novas.DEG2RAD) ha = arange(haStart,haEnd+0.000001,1./60.) has.append(ha) if len(ha) > 1 and haStart <= 0 and haEnd >= 0: has.append(arange(0,0.1,1)) colors = ['b', 'g', 'r', 'c', 'm', 'y'] colorIdx = 0 for pair in pairs: color = colors[colorIdx % len(colors)] colorIdx += 1 dx = n.stations[pair[0]].x - n.stations[pair[1]].x dy = n.stations[pair[0]].y - n.stations[pair[1]].y dz = n.stations[pair[0]].z - n.stations[pair[1]].z lines.append(matplotlib.lines.Line2D([],[],linestyle='-', color=color)) labels.append('%s-%s (%.1f m)' % (pair[0], pair[1], sqrt(dx*dx + dy*dy + dz*dz))) for ha in has: if len(ha) == 1: marker = color+'+' else: marker = color+'-' if mode == 'Binary': svec = [] for wave in wavelengths: x = dx / wave y = dy / wave z = dz / wave u = -y * sinLat * sin(ha * 15. * novas.DEG2RAD) + \ z * cosLat * sin(ha * 15. * novas.DEG2RAD) + x * \ cos(ha * 15. * novas.DEG2RAD) v = y * sinLat * sinDec * cos(ha*15.*novas.DEG2RAD) - \ z * cosLat * sinDec * cos(ha*15.*novas.DEG2RAD) + \ x * sinDec * sin(ha * 15. * novas.DEG2RAD) + \ y * cosLat * cosDec + z * sinLat * cosDec if mode == 'Disk': s = sqrt(u * u + v * v) v2 = [diskVis2(ss, diameter) for ss in s] plot(s/1.e6, v2, marker) elif mode == 'Binary': r = 10 ** (-dmag / 2.5) v2 = [binaryVis2(x[0], x[1], diameter, secDiameter, separation, r, pa) for x in zip(u,v)] s = sqrt(u * u + v * v) plot(s/1.e6, v2, marker) svec.extend(s) elif mode == 'UV': u = u / 1.e6 v = v / 1.e6 plot(u,v,marker) # Same plots for the reverse baselines u = -u v = -v plot(u,v,marker) if mode == 'Binary': vmin = sqrt(min(svec) ** 2 / (1 + (u[0]/v[0]) ** 2)) vmax = sqrt(max(svec) ** 2 / (1 + (u[0]/v[0]) ** 2)) if vmax - vmin < 10: vvec = arange(vmin, vmax, 10) else: vvec = arange(vmin, vmax, (vmax-vmin)/100.) uvec = vvec * u / v v2 = [binaryVis2(x[0], x[1], diameter, secDiameter, separation, r, pa) for x in zip(uvec, vvec)] s = sqrt(uvec * uvec + vvec * vvec) plot(s/1.e6, v2, color+':') legend(lines, labels, loc=0) # Axis labels if mode == 'Disk' or mode == 'Binary': xlabel(r'$\rm{s}\ (\rm{M}\lambda)$') ylabel(r'$V^2$') elif mode == 'UV': limits = axis() maxVal = max(abs(limits[0]), abs(limits[1]), abs(limits[2]), abs(limits[3])) maxVal *= 1.2 axis([maxVal, -maxVal, -maxVal, maxVal]) xlabel(r'$\rm{u}\ (\rm{M}\lambda)$') ylabel(r'$\rm{v}\ (\rm{M}\lambda)$') # Plot title str = '%s: %s' % (starName, self.date.get()) if len(has[0]) == 1: str = '%s: HA = %.2f' % (str, haStart) else: str = '%s: %.2f < HA < %.2f' % (str, haStart, haEnd) title(str) # Display it if fileName == None: w.figFrame.show() else: savefig(fileName) # Restore cursor input self.defaultCursor() def coveragePlot(self, fileName=None, text=False): '''Create a sky coverage plot. ARGS: master (Event/Toplevel/String): The window, or event in that window, which controls this plot. If a string, then open a new plot for the star specified by that string. fileName (String): File to send plot to. If None, then plot to the GUI. text (Boolean): Put out text file, rather than plot ''' # Creating a new plot window? if fileName != None: newWindow = False elif self.coveragePlotWindow == None: newWindow = True else: newWindow = False # Configure the stations usedStations = [self.station[beam].get().split()[0] \ for beam in xrange(1,7) \ if self.station[beam].get() != 'None'] if len(usedStations) == 0 and newWindow: tkMessageBox.showerror('ERROR', 'No stations specified') return n = NPOI(usedStations, float(self.zdMax.variable.get()), self.program.get()) # If not plotting, get name of output text file if text: if fileName == None: fileName = tkFileDialog.asksaveasfilename(title='Save As') if len(fileName) == 0: return fd = open(fileName, 'w') # Suspend cursor input self.watchCursor() # Get plotting points color = {} color[0] = 100 color[1] = 200 decMin = -55 haMax = 7 coords = [(ha10/10.,dec,n.observable(ha10/10.,dec)) \ for ha10 in xrange(-haMax*10,haMax*10,1) \ for dec in xrange(decMin,90,1)] if text: # If not plotting, just print visibility values to a file and exit for x in coords: fd.write('%5.1f %5.1f %1d\n' % (x[0], x[1], x[2])) fd.close() self.defaultCursor() return horizon = [(astro.hourAngle(90.,dec,self.site),dec) \ for dec in xrange(decMin,90,1)] # Plot them if fileName != None: matplotlib.use('PS') figure(1000) else: backEnd = matplotlib.get_backend() if backEnd != 'TkAgg': matplotlib.use('TkAgg') if newWindow: w = Toplevel() w.protocol('WM_DELETE_WINDOW', self.killCoverageWindow) w.wm_title('Coverage Plot') w.figFrame = tktools.EmbeddedFigure(w) w.figFrame.pack() self.coveragePlotWindow = w else: w = self.coveragePlotWindow figure(w.figFrame.figureNumber) clf() ha = [x[0] for x in coords if x[2] == 1] dec = [x[1] for x in coords if x[2] == 1] if len(ha) == 0 or len(dec) == 0: errorString='No sky coverage with chosen configuration.' errorString=errorString+' Try increasing ZD or another configuration' tkMessageBox.showerror('COVERAGE PLOT ERROR',errorString) return scatter(ha, dec, c='y') ha = [x[0] for x in horizon if x[0] != None] dec = [x[1] for x in horizon if x[0] != None] plot(ha,dec,'b',[-x for x in ha],dec,'b') axis([-haMax,haMax,decMin,90]) xlabel('HA (hrs)') ylabel(r'$\delta\ \rm{(deg)}$') # Plot title first = True str = '' for i in xrange(1,7): station = self.station[i].get() if station != 'None': if first: first = False else: str = '%s, ' % str str = '%s%s' % (str, station.split()[0]) str = '%s: Max Zenith Distance = %d deg' % (str, int(self.zdMax.variable.get())) title(str) # Display it if fileName == None: w.figFrame.show() else: savefig(fileName) # Restore cursor input self.defaultCursor() def updateStarConfig(self,starName): '''Update any open plot windows for changed configuration for a single star. The configuration for that star only has changed. ARGS starName (String): Star whose configuration has changed. ''' # Redo visibility plot if self.visPlots.has_key(starName): self.uvReplot(self.visPlots[starName]) # Redo uv plot if self.uvPlots.has_key(starName): self.uvReplot(self.uvPlots[starName]) # Redo uptime plot if self.uptimePlotWindow != None: self.uptimePlot() def updateConfig(self,dateOnly=False): '''Update any open plot windows for changed configuration. ARGS dateOnly (Bool): Only the date changed. ''' # Redo visibility plots for p in self.visPlots.values(): self.uvReplot(p) # Redo uv plots for p in self.uvPlots.values(): self.uvReplot(p) # Redo uptime plot if self.uptimePlotWindow != None: self.uptimePlot() # Redo coverage plot and k display if not dateOnly: if self.coveragePlotWindow != None: self.coveragePlot() self.displayK() def updateStars(self): '''Update the plots of all stars (uptime and star plots).''' if self.uptimePlotWindow != None: self.uptimePlot() if self.starPlotWindow != None: self.starPlot() def starPlot(self,fileName=None): '''Create a star plot.''' # Creating a new plot window? if fileName != None: newWindow = False elif self.starPlotWindow == None: newWindow = True else: newWindow = False # Return if no stars to plot if len(self.targets) == 0 and newWindow: tkMessageBox.showerror('ERROR', 'No stars to plot') return # Suspend cursor input self.watchCursor() # Set up the plotting window if fileName != None: matplotlib.use('PS') figure(1000) else: backEnd = matplotlib.get_backend() if backEnd != 'TkAgg': matplotlib.use('TkAgg') if newWindow: w = Toplevel() w.protocol('WM_DELETE_WINDOW', self.killStarWindow) w.wm_title('Star Plot') w.figFrame = tktools.EmbeddedFigure(w) w.figFrame.canvas.get_tk_widget().config(cursor='trek') w.figFrame.pack() self.starPlotWindow = w self.starPlotCanvas = get_current_fig_manager().canvas w.figFrame.canvas.get_tk_widget().bind('', self.starPlotSelectPair) w.figFrame.canvas.get_tk_widget().bind('', self.starPlotSelectCalibrator) w.figFrame.canvas.get_tk_widget().bind('', self.starPlotInfo) else: w = self.starPlotWindow figure(w.figFrame.figureNumber) clf() # Plot the targets ra = [self.targets[target].star.ra/15. \ for target in self.targets] dec = [self.targets[target].star.dec \ for target in self.targets] self.starPlotFrame = subplot(111) plot(ra,dec,'bo') # Determine if a target+calibrator pair crosses the 0/24 boundary fixUp = True hi = [] lo = [] for target in self.targets: targetRa = self.targets[target].star.ra/15. for calibrator in self.targets[target].calibrators: calibratorName = calibrator[0] calRa = self.calibrators[calibratorName].star.ra/15. if calRa > 21 and targetRa < 3: if calibratorName in hi: fixUp = False break else: lo.append(calibratorName) elif calRa < 3 and targetRa > 21: if calibratorName in lo: fixUp = False break else: hi.append(calibratorName) elif calRa < 3: lo.append(calibratorName) elif calRa > 21: hi.append(calibratorName) if not fixUp: break # Plot the calibrators, and the lines connecting them to the targets ra = [] dec = [] args = [] for target in self.targets: targetRa = self.targets[target].star.ra/15. targetDec = self.targets[target].star.dec theCalibrator = self.targets[target].theCalibrator for calibrator in self.targets[target].calibrators: calibratorName = calibrator[0] calRa = self.calibrators[calibratorName].star.ra/15. calDec = self.calibrators[calibratorName].star.dec if fixUp: if calRa < 3 and targetRa > 21: calRa += 24 elif calRa > 21 and targetRa < 3: calRa -= 24 if calibratorName == theCalibrator: lineType = 'k-' else: lineType = 'k--' args.append([targetRa,calRa]) args.append([targetDec,calDec]) args.append(lineType) ra.append(calRa) dec.append(calDec) plot(ra,dec,'ro') plot(*args) xlabel(r'$\alpha\ \rm{(hr)}$') ylabel(r'$\delta\ \rm{(deg)}$') # Plot title title(self.date.get()) # Show legend selectedLine = matplotlib.lines.Line2D([],[],linestyle='-',color='k') potentialLine = matplotlib.lines.Line2D([],[],linestyle='--',color='k') legend((selectedLine,potentialLine),('Selected Calibrator','Potential Calibrator'),loc=0) # Display it if fileName == None: w.figFrame.show() else: # Save to a postscript file savefig(fileName) # Restore cursor input self.defaultCursor() def starPlotInfo(self,event): '''Print info on the selected star from the star plot.''' # get the x and y coords, flip y from top to bottom height = self.starPlotCanvas.figure.bbox.height() x, y = event.x, height-event.y # Do nothing if click is outside plot window if not self.starPlotFrame.in_axes(x, y): return # Transform to ra,dec. # TransData transforms data coords to display coords. Use the # inverse method to transform back ra, dec = self.starPlotFrame.transData.inverse_xy_tup( (x,y) ) ra = astro.inRange(ra, 0, 24) # Find the closest target or calibrator star stars = [self.targets[star] for star in self.targets] + \ [self.calibrators[star] for star in self.calibrators] stars = [(star.star.separation(ra*15.,dec),star) for star in stars] if len(stars) == 0: return stars.sort() star = stars[0][1] # Print the info if star.star.constellation != None: d = '%s (%s)' % (star.star.npoiName(), star.star.constellation) else: d = '%s' % star.star.npoiName() d = '%s\nV: %.2f\nSpectralType: %s' % \ (d,star.star.v, star.star.spectralType) if star.star.diameter == None: d = '%s\nDiameter: ???\nStatus: ???' % (d) else: d = '%s\nDiameter: %.2f mas\nStatus: %s' % \ (d,star.star.diameter, star.star.status) d = '%s\nra: %.3f hr\ndec: %.3f deg' % \ (d,star.star.ra/15.,star.star.dec) tkMessageBox.showinfo('Star Description',d) def starPlotSelectPair(self,event): '''Select a target/calibrator pair from the star plot.''' # get the x and y coords, flip y from top to bottom height = self.starPlotCanvas.figure.bbox.height() x, y = event.x, height-event.y # Do nothing if click is outside plot window if not self.starPlotFrame.in_axes(x, y): return # Transform to ra,dec. # TransData transforms data coords to display coords. Use the # inverse method to transform back ra, dec = self.starPlotFrame.transData.inverse_xy_tup( (x,y) ) ra = astro.inRange(ra, 0, 24) # Find the closest target or calibrator star stars = [self.targets[star] for star in self.targets] + \ [self.calibrators[star] for star in self.calibrators] stars = [(star.star.separation(ra*15.,dec),star) for star in stars] if len(stars) == 0: return stars.sort() star = stars[0][1] # Handle the event if self.lastStar == None: # First star clicked on. Just remember it. self.lastStar = star elif star.star.npoiName() == self.lastStar.star.npoiName(): # Clicked on same star twice. Nothing to do. pass elif type(star) == type(self.lastStar): # Click on same type of star as previous star. Just remember it. self.lastStar = star else: if isinstance(star,Target): target = star calibrator = self.lastStar else: target = self.lastStar calibrator = star calName = calibrator.star.npoiName() if target.theCalibrator == calName: # They're already linked up. Nothing to do. pass elif calName not in \ [calibrator[0] for calibrator in \ self.targets[target.star.npoiName()].calibrators]: # Bad pair selected tkMessageBox.showerror('ERROR', 'The selected calibrator is not valid for the selected target') else: # Match up the target/calibrator pair and replot target.theCalibrator = calName self.calibratorsShow() self.updateStars() self.lastStar = None def starPlotSelectCalibrator(self,event): '''Select a calibrator to use as calibrator for all possible targets from the star plot.''' # get the x and y coords, flip y from top to bottom height = self.starPlotCanvas.figure.bbox.height() x, y = event.x, height-event.y # Do nothing if click is outside plot window if not self.starPlotFrame.in_axes(x, y): return # Transform to ra,dec. # TransData transforms data coords to display coords. Use the # inverse method to transform back ra, dec = self.starPlotFrame.transData.inverse_xy_tup( (x,y) ) ra = astro.inRange(ra, 0, 24) # Find the closest target or calibrator star stars = [self.targets[star] for star in self.targets] + \ [self.calibrators[star] for star in self.calibrators] stars = [(star.star.separation(ra*15.,dec),star) for star in stars] if len(stars) == 0: return stars.sort() star = stars[0][1] # Error if star is not a calibrator if not isinstance(star,Calibrator): tkMessageBox.showerror('ERROR', 'Must select a calibrator with the middle biutton') return # Set this star as calibrator for all target for which it is a # potential calibrator. calName = star.star.npoiName() for target in self.calibrators[calName].targets.keys(): self.targets[target].theCalibrator = calName self.calibratorsShow() self.updateStars() self.lastStar = None def help(self,*ignore): '''Display a help window.''' helpText = '''OBSERVING PREPARATION TOOL FOR NPOI FIRST PANEL: Program Description All controls are self-explanatory. SECOND PANEL: Program and Calibrating Star Selection The left hand box lists the program stars, in the order they are to be observed. Use the left button to select a target star; the potential calibrating stars for the selected program star will then be displayed in the right hand box, in order of increasing distance from the target star (as specified in the "Sep" column, in degrees). Use the left mouse button to select the calibrating star for the selected target star. A double click will choose that calibrating star as the calibrator for all target stars for which it may be used (as listed at the end of the calibrating stars entry). Clicking on a column label performs the following: Single left button: sort by that column, in ascending order Double left button: sort by that column, in descending order Single middle button: show a description of that column Single right button: edit the value of that column for the selected star; this works only for "Exp" (exposure time) To change the exposure time of a target/calibrating star, first select the relevant star with the left mouse button, then click the right mouse button on the "Exp" column label; you will be prompted for the new exposure time. To advance to the next target star in the list, click the right mouse button in either the target or calibrating star lists; the next target star will be selected and its calibrating stars displayed. To add a target star, enter its name (any name resolvable by SIMBAD will work, as well as the NPOI-preferred but incorrect FKV and BSC designations) in the "Add:" box and hit a carriage return while the cursor is in the box. If no name is entered, then a blank line is added to the end of the target list. To add a blank line anywhere in the target list, select a star and click the "Blank" button; a blank line will be added after the selected star. To plot the u-v plane coverage for a target star, select it with the left mouse button and then click on the "UV" button. The u-v plane coverage will be plotted for each channel and each baseline pair for the entire uptime of the star. The scale bars may be used to narrow the hour angle range plotted. Changing the starting HA will select a snapshot at just that hour angle. Changing the ending HA will plot the range from the previously selected starting hour angle through the newly selected ending hour angle. When a range of hour angles is plotted, plus signs mark the time of meridian crossing. To plot the visibility for a target star, select it with the left mouse button and then click on the "Visibility" button. Select either a disk for a single star model or Binary for a binary star model. The visibility will be plotted for each channel and each baseline pair for the entire uptime of the star. The scale bars may be used to narrow the hour angle range plotted. Changing the starting HA will select a snapshot at just that hour angle. Changing the ending HA will plot the range from the previously selected starting hour angle through the newly selected ending hour angle. When a range of hour angles is plotted, plus signs mark the time of meridian crossing. To delete a target star from the list, select it with the left mouse button and then click on the "Delete" button. To move a target star up or down one space in the target list, select it with the left mouse button and then click either the "Up" or "Down" buttons. To toggle whether a beam is used for a specific target star (an X in the display indicates that it is used), select the target star with the left mouse button and then click the beam you wish to toggle. THIRD PANEL: Instrument Configuration Most controls are self-explanatory. When selecting a station for each beam, the menu first offers the available stations, which themselves are submenus from which to select the stroke (in mm) to use for that station. Note that the choice of available tracking baselines is determined by the current selection of stations, the reference station, and the spectrometers. If no tracking baselines are listed as available, confirm that you have selected an adequate set of stations and spectrometers, as well as a reference station. FOURTH PANEL: Control Buttons Save: Save the current information to an observing file. You will be prompted for the name of the file to save to. An uptime plot will also be created, with the same file name except the extension will be changed to ".eps". Read: Read in an existing observing file, which you may then change (you may also do this by specifying the name of the observing file as the sole argument to the program when it is started). Star Plot: Display interactive plot of targets and calibrators, from which one may select calibrators for targets. Uptime Plot: Display the uptime plot. Plus signs mark the time of meridian crossing. Dusk and dawn are for nautical twilight. Coverage Plot: Display sky coverage for selected NPOI configuration with a left mouse button click. Coverage is plotted in declination and hour angle units and the blue line represents the horizon. A right mouse button click will save the coverage data to a text file. Output columns are HA (decimal hours), Dec. (decimal degrees) and a plotting flag (0=nothing, 1=plot a symbol). Help: Display this help. Quit: Quit the program without saving the current information. STAR PLOT: Target stars are plotted as blue circles, calibrator stars as red circles. Dashed lines connect target stars with their potential calibrators; if the calibrator star is selected as the calibrator for a target, then they are connected with a solid line. Left button: Click first on a target star, then a calibrator star (or vice versa), to choose that calibrator as the calibrator for that target. Middle button: Click on a calibrator star to choose it as the calibrator star for all associated targets. Right button: Click on any star to get information on that star. CATALOGS: It may be necessary to rebuild the NPOI target catalog. If so cd to the npoi-1.*/npoi directory start python and >>> import catalogs >>> catalogs.StarCatalog.build() #builds the catalog Now save a binary version of the catalog: >>> catalogs.StarCatalog.dump(catalogs.StarCatalog.build(),filename) Or save an ascii versio of it. catalogs.StarCatalog.dumpDict(catalogs.StarCatalog.build(),filename) If a new binary is saved copy it to the binary catalog file which python installed. ''' help = Toplevel() help.title('ObsPrep Help') frame = Frame(help) s = Scrollbar(frame) t = Text(frame,height=40,width=80) s.focus_set() s.pack(side=RIGHT,fill=Y) t.pack(side=LEFT,fill=Y) s.config(command=t.yview) t.config(yscrollcommand=s.set) t.insert(END,helpText) t.config(state=DISABLED) frame.pack() quit = Button(help,text='Quit') quit.config(command=help.destroy) quit.pack() def calibratorExposureTimeSet(self,*ignore): '''Set the exposure time for the selected calibrator.''' # Get the target selected by the cursor indices = [ int(x) for x in self.calibratorList.curselection() ] if len(indices) != 1: tkMessageBox.showerror('ERROR', 'No single selected calibrator star') return idx = indices[0] starName = self.calibratorList.get(idx)[1] # Prompt for its new exposure time, and set it expTime = 30 if expTime == None: return self.calibrators[starName].expTime = expTime self.calibratorsShow() def calibratorSelect(self,*ignore): '''Use the selected calibrator as the calibrator for that target.''' indices = [ int(x) for x in self.calibratorList.curselection() ] if len(indices) != 1: tkMessageBox.showerror('ERROR', 'No single selected calibrator') return calibrator = self.calibratorList.get(indices[0])[1] targetName,idx = self.targetSelectionGet() if idx == None: return self.targets[targetName].theCalibrator = calibrator self.updateStars() def calibratorSelectAll(self,*ignore): '''Use the selected calibrator as the calibrator for all targets that have it as a potential calibrator.''' indices = [ int(x) for x in self.calibratorList.curselection() ] if len(indices) != 1: tkMessageBox.showerror('ERROR', 'No single selected calibrator') return entry = self.calibratorList.get(indices[0]) calibrator = entry[1] expr = re.compile('\([^)]+\)') targets = expr.sub('', entry[7]).split() for target in targets: self.targets[target].theCalibrator = calibrator self.updateStars() def nextTarget(self,*ignore): '''Advance to the next star in the target list.''' starName,idx = self.targetSelectionGet() if idx == None: return self.calibratorList.delete(0,END) self.targetList.select_clear(idx) if idx < len(self.targetList.get(0,END)) - 1: idx += 1 else: idx = 0 self.targetList.select_set(idx) self.targetList.see(idx) self.calibratorsShow() def targetUp(self,*ignore): '''Move the selected target up in the list.''' starName,idx = self.targetSelectionGet() if idx == None: return if idx == 0: return str = self.targetList.get(idx) self.targetList.select_clear(idx) self.targetList.delete(idx,idx) idx -= 1 self.targetList.insert(idx,str) self.targetList.select_set(idx) self.targetList.see(idx) # Redo uptime plot if self.uptimePlotWindow != None: self.uptimePlot() def targetDown(self,*ignore): '''Move the selected target down in the list.''' starName,idx = self.targetSelectionGet() if idx == None: return if idx == len(self.targetList.get(0,END))-1: return str = self.targetList.get(idx) self.targetList.select_clear(idx) self.targetList.delete(idx,idx) idx += 1 self.targetList.insert(idx,str) self.targetList.select_set(idx) self.targetList.see(idx) # Redo uptime plot if self.uptimePlotWindow != None: self.uptimePlot() def blankAdd(self,*ignore): '''Add a blank line to the separator list below the selected star, to allow grouping of stars.''' # Find the selected star starName,idx = self.targetSelectionGet() if idx == None: return # Got one. Add a blank line below it. idx += 1 self.calibratorList.delete(0,END) self.targetList.insert(idx, ('','','','','','','','','','','','')) self.targetList.select_clear(0,END) self.targetList.select_set(idx) self.targetList.see(idx) # Redo uptime plot if self.uptimePlotWindow != None: self.uptimePlot() def start(self,*ignore): '''Start the GUI.''' self.root.mainloop() def usage(status, msg=''): "Error message and usage" print main.__doc__ if msg: print '-- ERROR: %s' % msg sys.exit(status) def main(argv): '''Start the observing planning tool GUI. Usage: obsprep [filename] Options: [filename] : initialize with this existing observing file ''' import getopt # Parse command line try: optlist, args = getopt.getopt(argv[1:],'') except getopt.error, e: usage(1,__doc__,e) # No optional arguments allowed for o,a in optlist: usage(1) if len(args) > 1: usage(1) # Do it if len(args) == 1: g = Gui(args[0]) else: g = Gui() if __name__=='__main__': main(sys.argv)