def plotSpec(self, filename=None, plotother=False): """ Determine the current state of the widget entries and plot the spectrum accordingly. """ # Should I use the default name? if plotother: # No - put filename to None filename = None else: # Yes - read it from the entry filename = self.lastplotname # Now, if no filename is defined yet, open a dialog to ask for a file to be # plotted if not filename: filename = tkFileDialog.askopenfilename(parent=self, initialdir=config.options["outpath"].value) if filename: self.lastplotname = filename else: return # Not yet implemented : # LOOP OVER RANGES - WHEN THERE ARE SEVERAL RANGES DEFINED - DO WE WANT THIS? # Check that the file to plot (still) exists and then ask the task manager # to execute the 'plotspec' task. if os.path.exists(filename): taskManager.runTask(tasks.plotspec, filename, usebiggles=self.usebiggles.get()) else: messageLog.put_warning('Could not open file for plotting - Skipped (%s)' % filename)
def plotSpec(self, filename=None, plotother=False): """ Determine the current state of the widget entries and plot the spectrum accordingly. """ # Should I use the default name? if plotother: # No - put filename to None filename = None else: # Yes - read it from the entry filename = self.lastplotname # Now, if no filename is defined yet, open a dialog to ask for a file to be # plotted if not filename: filename = tkFileDialog.askopenfilename( parent=self, initialdir=config.options["outpath"].value) if filename: self.lastplotname = filename else: return # Not yet implemented : # LOOP OVER RANGES - WHEN THERE ARE SEVERAL RANGES DEFINED - DO WE WANT THIS? # Check that the file to plot (still) exists and then ask the task manager # to execute the 'plotspec' task. if os.path.exists(filename): taskManager.runTask(tasks.plotspec, filename, usebiggles=self.usebiggles.get()) else: messageLog.put_warning( 'Could not open file for plotting - Skipped (%s)' % filename)
def getwavedef1D(frame): """ Extract and return the wavelength definition vector [ordernumber, zero wavelength offset, wavelength step] using the FITS header keywords of a 1-dimensional spectrum. """ # Read and store the appropriate headers try: npixels = frame[0].header['NAXIS1'] zerowave = frame[0].header['CRVAL1'] refpix = frame[0].header['CRPIX1'] wstep = frame[0].header['CDELT1'] except KeyError: # Error when reading headers! messageLog.put_warning('Could not find FITS headers defining the wavelength solution') return None # Determine starting (reference) wavelength woffset = zerowave + (refpix - 1.0) * wstep # And create the wavelength vector wavearray = numpy.zeros((1, 4), dtype=numpy.float64) wavearray[0, :] = [0, 0, woffset, wstep] # Return the vector to the caller return wavearray
def helcorr(frame): # NB: This routine assumes that the file containes the 'DATE-AVG' keyword, # which is currently only added by the MEF post-processing scripts # # To be checked : which epoch are RA and DEC given in, and which role # does EQUINOX play in this? """ Determine the heliocentric velocity correction from the frame headers """ # Read the appropriate headers try: dateavg = frame[0].header['DATE-AVG'] except KeyError: # Error when reading headers! messageLog.put_warning('Could not find DATE-AVG header: skipping heliocentric velocity calculation') return try: ra = float(frame[0].header['RA']) dec = float(frame[0].header['DEC']) epoch = float(frame[0].header['EQUINOX']) except KeyError: # Error when reading headers! messageLog.put_warning('Could not read object coordinates: skipping heliocentric velocity calculation') return # Example of header card : # DATE-AVG= '2003-12-13T16:52:12.0' / Midpoint of observation # Extract useful data from header year = int(dateavg[0:4]) month = int(dateavg[5:7]) day = int(dateavg[8:10]) ut = dateavg[11:] # Obtain the heliocentric correction from IRAF vhelio = irafWrappers.rvcorrect(year, month, day, ut, ra / 15.0, dec, epoch) # Write result to frame header frame[0].header.update('VHELIO', vhelio) frame[0].header.add_history('Added header card for heliocentric velocity') messageLog.put('Added header card VHELIO with heliocentric velocity', 5)
def fixheaders(frame): # Make sure header cards GAIN and RDNOISE are defined # Gain = 1.0 and RDNOISE = 0.0 are the only feasible assumptions one can # make if nothing else is known. data = pyfits.open(frame, 'update') try: gain = data[0].header['GAIN'] except KeyError: data[0].header.update('GAIN', 1.0) messageLog.put_warning('Could not find GAIN header: assumed GAIN = 1.0') data[0].header.add_history('Inserted missing GAIN header card, assumed GAIN = 1.0') try: rdnoise = data[0].header['RDNOISE'] except KeyError: data[0].header.update('RDNOISE', 0.0) messageLog.put_warning('Could not find RDNOISE header: assumed RDNOISE = 0.0') data[0].header.add_history('Inserted missing RDNOISE header card, assumed RDNOISE = 0.0') data.close()
def removefiles(*patterns): """ Remove files with UNIX-like filename pattern matching. No questions asked, so be careful! """ # 'patterns' is an array or tuple of filename patterns. Can, of course, # also be inidividual file names. # Loop over patterns for pattern in patterns: # Split off the directory path (dirname, filepattern) = os.path.split(pattern) # Get a list of all files in the directory try: filelist = os.listdir(dirname) except OSError, errstr: # Succeed even if the directory was not there (and put warning in log) messageLog.put_warning(errstr, 9) return # Check each file in the directory list for file in filelist: # And see if it's name matches the pattern if fnmatch.fnmatch(file, filepattern): # If yes, (try to) remove it from the system messageLog.put('Removing file : "%s"' % file, 7) try: os.remove(os.path.join(dirname, file)) except OSError, errstr: # Succeed even if there were no files (and put warning in log) messageLog.put_warning(errstr, 9)
def removefiles(*patterns): """ Remove files with UNIX-like filename pattern matching. No questions asked, so be careful! """ # 'patterns' is an array or tuple of filename patterns. Can, of course, # also be inidividual file names. # Loop over patterns for pattern in patterns: # Split off the directory path (dirname, filepattern) = os.path.split(pattern) # Get a list of all files in the directory try: filelist = os.listdir(dirname) except OSError, errstr: # Succeed even if the directory was not there (and put warning in log) messageLog.put_warning(errstr, 9) return # Check each file in the directory list for file in filelist: # And see if it's name matches the pattern if fnmatch.fnmatch(file, filepattern): # If yes, (try to) remove it from the system messageLog.put('Removing file : "%s"' % file, 7) try: os.remove(os.path.join(dirname, file)) except OSError, errstr: # Succeed even if there were no files (and put warning in log) messageLog.put_warning(errstr, 9)
def save(outfile=None, optionlist=[], ignore_options=[]): "Save the current values of options to disk" # Only proceed if outfile is defined if outfile: # Create an object that will only contain the elements that are # supposed to be written to disk outconfig = configobj.ConfigObj(options['default_savefile'].value) # If optionlist is defined, only select the listed items if optionlist: keys = optionlist # Otherwise, select all else: keys = outconfig['main'].keys() # If ignore_options was defined, modify the keylist if ignore_options: for ignore_key in ignore_options: if ignore_key in keys: keys.remove(ignore_key) # Loop over options for key in keys: try: outvalue = options[key].value except KeyError: messageLog.put_warning("Could not write value for %s - continuing" % key) return # Process arrays of files if options[key].type in ('filelist', 'fitsfilelist'): # Cast to array of strings if type(outvalue) != type([]): outvalue = [outvalue] # And attach directory name is necessary filelist = outvalue outvalue = [] for filename in filelist: if os.path.dirname(filename) == options[key].indir.value : filename = os.path.basename(filename) outvalue.append(filename) if options[key].type in ('fitsfile', 'outfitsfile'): if os.path.dirname(outvalue) == options[key].indir.value : outvalue = os.path.basename(outvalue) # Copy the options into the output object try: outconfig['main'][key] = outvalue messageLog.put("Wrote value for %s to %s" % (key, outfile), 9) except KeyError: messageLog.put_error("Cannot write configuration option '%s'." % key) try: # Now, open the output file in write mode filehandle = open(outfile, mode='w') # Write the data outconfig.write(filehandle) # Close the output file filehandle.close() except Exception, errstr: messageLog.put_error("Could not save config to %s : %s" % (outfile, errstr)) # Inform user messageLog.put("Wrote configuration to %s" % outfile, 5)
def findNewFiles(self): """ Find files that are not yet listed in the queue of unprocessed files, and match these with the filename filter pattern """ # Retrieve the current value of the filename pattern from the widget pattern = config.options['filename_filter'].value # Retrieve the current value of the data input directory inpath = config.options['inpath'].value # Read the directory contents contents = [os.path.join(inpath, file) for file in os.listdir(inpath)] # Check the obtained list of files agains the existing directory list # Remove files that already existed in the directory list # ORDER of if-statements is important! iterlist = copy.copy(self.dirlist) for file in iterlist: if file not in contents: # Hmm... a strange situation. Apparently a file listed in self.dirlist # DISappeared from the directory. Adjust the lists accordingly messageLog.put_warning( 'File %s disappeared from directory - updating lists' % file, 5) self.dirlist.remove(file) # Do NOT swap the following two statements! # Is this file already in the list of processed files? if file in self.reducedfiles: self.reducedfiles.remove(file) # Is this file already in the list of unprocessed files? if file in self.newfiles: self.newfiles.remove(file) if file in contents: # Normal situation, all files in self.dirlist are also in the current # directory listing. Remove these files one-by-one from the list, so that # the remaining files are those that are the new files in the input # directory (this time). contents.remove(file) # Now loop over the remaining files for file in contents: # And append these to 'self.dirlist' for future reference self.dirlist.append(file) # Only look at the filename (disregard from directory path) basename = os.path.basename(file) # Make sure that (1) the 'file' is not a directory entry, (2) that it # matches the filename filter pattern, and (3) that it not yet in # the unprocessed or processed list. if ((not os.path.isdir(file)) and (fnmatch.fnmatch(basename, pattern)) and (file not in self.reducedfiles) and (file not in self.newfiles)): messageLog.put('Appending file to the queue', 9) # Only then, append the file to the list of files to process. self.newfiles.append(file) # Finally, update queue sizes on screen self.updateQueueSize()
def flipframe(frame, direction=0, extension=0): # Retrieved the x and y coordinate vectors (xvec, yvec) = WCSvector(frame, extension) # MMM... MAYBE NOT TRUE... # Comment : flipping the xvec and yvec below is not really # necessary, because the xnewmin and ynewmin will still be the # lowest pixel number in the (flipped) xvec and yvec. However, # exchanging the xvec and yvec is _very_ relevant. # NB: xaxis is axis nr 1, and yaxis is axis nr 0 flippeddata = frame[extension].data # Rotate 'n' times 90 degrees # x -> x, y -> y if direction == 0: flippeddata = flippeddata # x -> -y, y -> x elif direction == 1: flippeddata = _flipaxis(flippeddata, axis=1) flippeddata.transpose() yvec = _flipaxis(yvec, axis=0) # x -> -x, y -> -y elif direction == 2: flippeddata = _flipaxis(flippeddata, axis=1) flippeddata = _flipaxis(flippeddata, axis=0) xvec = _flipaxis(xvec, axis=0) yvec = _flipaxis(yvec, axis=0) # x -> y, y -> -x elif direction == 3: flippeddata = _flipaxis(flippeddata, axis=0) flippeddata.transpose() xvecnew = yvec yvec = _flipaxis(xvec, axis=0) xvec = xvecnew del(xvecnew) # Rotate 'n' times 90 degrees, and transpose # x -> y, y -> x elif direction == 4: flippeddata.transpose() xvecnew = yvec yvec = xvec xvec = xvecnew del(xvecnew) # x -> -x, y -> y elif direction == 5: flippeddata = _flipaxis(flippeddata, axis=1) xvec = _flipaxis(xvec, axis=0) # x -> -y, y -> -x elif direction == 6: flippeddata = _flipaxis(flippeddata, axis=1) flippeddata = _flipaxis(flippeddata, axis=0) flippeddata.transpose() # x -> x, y -> -y elif direction == 7: flippeddata = _flipaxis(flippeddata, axis=0) yvec = _flipaxis(yvec, axis=0) else: messageLog.put_warning('Frame orientation parameter out of range - ignored') # Determine 'new' vector minima and stepsize xnewbin = abs(xvec[2] - xvec[1]) ynewbin = abs(yvec[2] - yvec[1]) xnewmin = xvec.min() ynewmin = yvec.min() # Update header values accordingly frame[extension].header.update('CRVAL1', xnewmin) frame[extension].header.update('CRPIX1', 1) frame[extension].header.update('CDELT1', xnewbin) frame[extension].header.update('CTYPE1', 'PIXEL') frame[extension].header.update('CRVAL2', ynewmin) frame[extension].header.update('CRPIX2', 1) frame[extension].header.update('CDELT2', ynewbin) frame[extension].header.update('CTPYE2', 'PIXEL') # Store the rotated and flipped data in fits object frame[extension].data = flippeddata # And adjust the image headers to reflect the data frame[extension].update_header()
def run(): """ Main program entry, initializing and starting the main window and managing its 'mainloop' """ # Get and check command-line options # # NB: The -c (enable-calib) option is now deprecated # try: opts, args = getopt.getopt(sys.argv[1:], "cs", ["calib", "scrollbar"]) except getopt.GetoptError: # print help information and exit: print "Unknown command line parameter. Allowed optional parameters are : " #print "-c [file] Load calibration file at startup" print "-s / --scrollbar Add vertical scrollbar is screen does not fit" sys.exit(2) #enablecalib = False hasscrollbar = False for option, parameter in opts: if option in ("-c", "--calib"): print "The -c option is deprecated. You can now always access the calibs submenu." # enablecalib = True if option in ("-s", "--scrollbar"): hasscrollbar = True # Display a welcome message in the message log messageLog.put('=' * 65) messageLog.put("Welcome to FIEStool - the automatic data processing tool") messageLog.put("for the FIES spectrograph at the Nordic Optical Telescope") messageLog.put("Copyright (c) 2005 NOTSA; All Rights Reserved") messageLog.put("This is version %s (%s), written by %s" % (_version, _date, _author)) messageLog.put('=' * 65) messageLog.put("Right-click on any item to obtain instantaneous help") messageLog.put('=' * 65) # And tell the user what warnings and errors look like messageLog.put_warning("This is a sample warning") messageLog.put_error("This is a sample error") messageLog.put('=' * 65) # Now, check if a default config file exists; if yes, load it. try: config.load(config.options['default_savefile'].value) except IOError: messageLog.put_warning('No default configuration file found!') pass # Now, try to load the autoLoader config file. try: config.load_raw(config.options['default_autoload_savefile'].value) except IOError: messageLog.put_warning( 'No default autoloader configuration file found!') pass # Create the absolute top-level root window. Destroying this kills 'everything'! root = Tk() # Make the root window variable in width and height root.rowconfigure(0, weight=1) root.columnconfigure(0, weight=1) # Give it a title root.title('FIEStool - Data reduction for the FIES spectrograph') # Initialize the main window, with 'root' as its parent main = mainWindow(root, hasscrollbar) if hasscrollbar: # This puts the focus right, and will give the canvas child real sizes root.deiconify() root.focus_set() root.grab_set() root.wait_visibility() # Show the canvas main.canvas.grid(row=0, column=0, sticky=N + S + E + W) # Determine width and heigth of the contained frame canvaschild = main.canvas.children.values()[0] neededwidth = canvaschild.winfo_width() neededheight = canvaschild.winfo_height() maxheight = main.canvas.winfo_screenheight() maxwidth = main.canvas.winfo_screenwidth() neededheight = min(maxheight, neededheight) neededwidth = min(maxwidth, neededwidth) # Set the canvas size and enable scrolling main.canvas.config(width=neededwidth, height=neededheight) main.canvas.config(scrollregion=main.canvas.bbox("all")) else: # Display the main window main.grid(sticky=N + E + S + W) # Catch the 'delete window' signal that may be triggered by the window # manager if the user clicks on its 'close' button root.protocol("WM_DELETE_WINDOW", main.really_quit) # Start the mainloop. The program executing pauses here, only to execute events # generated by the main window (which in essence is everything) ############### main.mainloop() ############### # Apparently an event triggered an exit out of the mainloop (see main.reallyquit) messageLog.put("Destroying widgets") # Destroy the root window and all its children root.destroy() # This message will never arrive, though... messageLog.put("Program finished - EXIT FIEStool") # Return to caller (main) return
def run(): """ Main program entry, initializing and starting the main window and managing its 'mainloop' """ # Get and check command-line options # # NB: The -c (enable-calib) option is now deprecated # try: opts, args = getopt.getopt(sys.argv[1:], "cs", ["calib", "scrollbar"]) except getopt.GetoptError: # print help information and exit: print "Unknown command line parameter. Allowed optional parameters are : " #print "-c [file] Load calibration file at startup" print "-s / --scrollbar Add vertical scrollbar is screen does not fit" sys.exit(2) #enablecalib = False hasscrollbar = False for option, parameter in opts: if option in ("-c", "--calib"): print "The -c option is deprecated. You can now always access the calibs submenu." # enablecalib = True if option in ("-s", "--scrollbar"): hasscrollbar = True # Display a welcome message in the message log messageLog.put('='*65) messageLog.put("Welcome to FIEStool - the automatic data processing tool") messageLog.put("for the FIES spectrograph at the Nordic Optical Telescope") messageLog.put("Copyright (c) 2005 NOTSA; All Rights Reserved") messageLog.put("This is version %s (%s), written by %s" % (_version, _date, _author)) messageLog.put('='*65) messageLog.put("Right-click on any item to obtain instantaneous help") messageLog.put('='*65) # And tell the user what warnings and errors look like messageLog.put_warning("This is a sample warning") messageLog.put_error("This is a sample error") messageLog.put('='*65) # Now, check if a default config file exists; if yes, load it. try: config.load(config.options['default_savefile'].value) except IOError: messageLog.put_warning('No default configuration file found!') pass # Now, try to load the autoLoader config file. try: config.load_raw(config.options['default_autoload_savefile'].value) except IOError: messageLog.put_warning('No default autoloader configuration file found!') pass # Create the absolute top-level root window. Destroying this kills 'everything'! root = Tk() # Make the root window variable in width and height root.rowconfigure(0, weight=1) root.columnconfigure(0, weight=1) # Give it a title root.title('FIEStool - Data reduction for the FIES spectrograph') # Initialize the main window, with 'root' as its parent main = mainWindow(root, hasscrollbar) if hasscrollbar: # This puts the focus right, and will give the canvas child real sizes root.deiconify() root.focus_set() root.grab_set() root.wait_visibility() # Show the canvas main.canvas.grid(row=0, column=0, sticky=N+S+E+W) # Determine width and heigth of the contained frame canvaschild = main.canvas.children.values()[0] neededwidth = canvaschild.winfo_width() neededheight = canvaschild.winfo_height() maxheight = main.canvas.winfo_screenheight() maxwidth = main.canvas.winfo_screenwidth() neededheight = min(maxheight, neededheight) neededwidth = min(maxwidth, neededwidth) # Set the canvas size and enable scrolling main.canvas.config(width=neededwidth, height=neededheight) main.canvas.config(scrollregion=main.canvas.bbox("all")) else: # Display the main window main.grid(sticky=N+E+S+W) # Catch the 'delete window' signal that may be triggered by the window # manager if the user clicks on its 'close' button root.protocol("WM_DELETE_WINDOW", main.really_quit) # Start the mainloop. The program executing pauses here, only to execute events # generated by the main window (which in essence is everything) ############### main.mainloop() ############### # Apparently an event triggered an exit out of the mainloop (see main.reallyquit) messageLog.put("Destroying widgets") # Destroy the root window and all its children root.destroy() # This message will never arrive, though... messageLog.put("Program finished - EXIT FIEStool") # Return to caller (main) return
def getwavedef2D(frame): """ Extract and return the wavelength definition vector [ordernumber, zero wavelength offset, wavelength step] using the FITS header keywords of a 2-dimensional frame, following the IRAF definition. It is a rather dirty routine. """ # Make sure I can find the appropriate headers if not (frame[0].header.get('WAT0_001', None)): messageLog.put_warning('Could not find FITS headers defining the wavelength solution') return None # Get the number of pixels per order and the number of orders from the headers npixels = frame[0].header['NAXIS1'] norders = frame[0].header['NAXIS2'] # Create an (empty) output array wavearray = numpy.zeros((norders, 4), dtype=numpy.float64) # Get all the FITS header keywords in an array fullhlist = frame[0].header.items() wavedef = "" # And loop over the headers to extract any header that contains (part of) # the wavelength definition, and store these in one long string object for name, value in fullhlist: # Only 68 characters can be part of FITS header value if name[0:4] == 'WAT2': wavedef = wavedef + value.ljust(68) # Now it gets very IRAF-specific! Bin the first 16 characters (garbage) wavedef = wavedef[16:] # and split the string object into smaller pieces, one for each order wavedef = wavedef.split('spec') # Bin the first entry (also garbage) del(wavedef[0]) # Check that we got the correct number of order definitions from the headers if len(wavedef) != norders: messageLog.put_warning('No wavelength solution found', 5) return None # Initialize order number counter linecounter = 0 # Get rid of " and = characters in the string and isolate the parameter values for line in wavedef: line = line.replace('"', '') line = line.replace('=', '') line = line.split() # According to IRAF's definition the following parameters can be extracted # from the line number = int(line[1]) orderno = int(line[2]) woffset = float(line[4]) wstep = float(line[5]) # Store the order number, zero pixel wavelength and wavelength step in the # output array wavearray[linecounter, :] = [number, orderno, woffset, wstep] linecounter = linecounter + 1 # If an incompatible format was used to define the order locations, complain! if int(line[3]) != 0: messageLog.put_error('Error in interpreting spectrum definition') messageLog.put_warning('Dispersion solution format seems to be non-linear. Trying to make ') messageLog.put_warning('the best of it. Wavelength scale may be terribly wrong.') # Finally, return the result to the caller return wavearray