Wednesday, June 15, 2011

Automated Astrophotography with Python - Part 4

USING AN INPUT TEXT FILE
In the previous post there was much user interaction required to choose all the possible options for setting up a simple imaging session. In this post, I'll show how I use an input text file that eliminates the need to make all the selections at run-time. Here is a sample of a typical input text file:
#
# Object Command Arguments: [0] = 'OBJECT'
#                           [1] = Object Name
Object m34
#
# Guidestar Command Arguments: [0] = 'GUIDESTAR'
#                              [1] = AUTO -or- MANUAL Guide Star Selection
#                              [2] = Exposure Length in sec. (Auto mode only)
Guidestar Auto 1.0
#
# Exposure Command Arguments: [0] = 'LIGHT'
#                             [1] = Filter (L,R,G,B,HA)
#                             [2] = Binning (Positive Integer)
#                             [3] = Exposure Length (Positive Float)
#                             [4] = No. of Images (Positive Integer)
#            
#
Light L 1 10.25 3
# Light B 2 584 1
# Light G 2 30 2
# Light R 2 357 1
# Other Parameters Command Arguments: [0] = 'OTHERS'
#                                     [1] = CCD Temperature (ex. '-20.0C' or 'Skip')
#                                     [2] = Warm CCD After Imaging? (Y/N)
#                                     [3] = Slew to Safe Position After 
#                                           Imaging? (Y/N)
Others -20C Y Y
A new class called 'cParser' has been created to parse the text file and strip out commands and the parameters associated with each command. The code listing of the 'cParser' class follows:
import sys
import string

INPUTPATH = "c:\\Imaging_Scripts\\input_files\\"
COMMANDS = ('OBJECT','LIGHT','OTHERS','GUIDESTAR')

ERROR = True
NOERROR = False

##------------------------------------------------------------------------------
## Class: cParser
##------------------------------------------------------------------------------
class cParser:
    def __init__(self):
        self.__cmdArgument = []

    def missing(self,filename):
        try:
            f = open(INPUTPATH + filename)
            f.close()
            return NOERROR
        except IOError:
            return ERROR

    def parseFile(self,filename):
        if not self.missing(filename):
            f = open(INPUTPATH + filename, "r")
            while True:
                setup = []
                text = f.readline()
                if text == "":
                    f.close()
                    break
                if text[0] == "#":
                    continue
                line = text.split(" ")
                if line[0].upper() not in COMMANDS:
                    print " ERROR: Invalid Command - %s" % line[0].upper()
                    f.close()
                    return None
                else:
                    for i in range(len(line)):
                        line[i] = line[i].replace('\n','')
                        setup.append(line[i].upper())
                self.__cmdArgument.append(setup)
            return self.__cmdArgument
        else:
            print " Error: Input file %s not found" % filename
            return None

##
## END OF 'cParser' Class
##
As shown in the listing, the expected path to the input text files is defined by the constant "INPUTPATH" and indicates I've chosen to create a sub-directory off of the main directory that holds all my Python classes and scripts. The next line defines a constant called 'COMMANDS' that is a tuple of all valid commands that the parser recognizes. The heart of the 'cParser' class is contained in the 'parseFile()' method. This method reads through the input file line-by-line while ignoring any lines that begins with a '#' character. The contents of a line containing a valid command is parsed using the 'split' string function where the splitting character is a space. The command and parameters associated with that command are built into a list which is then appended to the class attribute list '__cmdArgument'. This process repeats for each command that is found until a line is read containing an empty string. The empty string is considered an end-of-file character and terminates the parsing operation. At this point the contents of the class attribute '__cmdArgument' is passed back to the calling routine thus returning a list that contains lists of commands and parameters.

The following code is a listing of the unit test for this module. It simply prompts the user to enter a filename for the input file then parses the file and prints out lists of commands and parameters found in the file.
if __name__ == '__main__':

    # instantiate the class
    cmdFile = cParser()

    # prompt for the filename of the input command file
    print
    filename = raw_input("Enter input command filename: ")
    if not cmdFile.missing(filename):
        cmds = cmdFile.parseFile(filename)
    else:
        print "ERROR: Input command file not found"
        raise EnvironmentError, 'Halting program'
    print
    for cmdsAndParams in cmds:
        print cmdsAndParams
    print
The following is a listing of the script that demonstrates how to make use of the input text file.
import cMount_4
import cCamera_4
import cParser_4

filterDictionary = {'R':0,'G':1,'B':2,'L':3,'HA':4}

# create an instance of the mount and camera class
testMount = cMount_4.cMount()
testCamera = cCamera_4.cCamera()
cmdFile = cParser_4.cParser()

# get imaging location
print
print "The Sky6 location is %s" % testMount.getImagingLocation()

# prompt for the filename of the input command file
print
filename = raw_input("Enter input command filename: ")
if not cmdFile.missing(filename):
    cmds = cmdFile.parseFile(filename)
else:
    print "ERROR: Input command file not found"
    raise EnvironmentError, 'Halting program'

autoGuide = False
warmCCD = False
slewSafePosition = False
autoGuideStarSelect = False
print
if len(cmds) > 0:
    for stringcmd in cmds:
        if stringcmd[0] == 'OBJECT':
            obj = stringcmd[1]
            print "Object:     %s" % obj
        if stringcmd[0] == 'LIGHT':
            filterName = stringcmd[1]
            binning = stringcmd[2]
            exp = float(stringcmd[3])
            noImages = int(stringcmd[4])
            print "Filter:     %s" % filterName
            print "Binning:    %s" % binning
            print "Exposure:   %02.2f" % exp
            print "No. Images: %d" % noImages
        if stringcmd[0] == 'OTHERS':
            temp = stringcmd[1]
            print "CCD Temp:   %s" % temp
            if stringcmd[2] == 'Y':
                print "Warm CCD?:  Yes"
                warmCCD = True
            else:
                print "Warm CCD?:  No"
            if stringcmd[3] == 'Y':
                print "Slew to Safe Position?: Yes"
                slewSafePosition = True
            else:
                print "Slew to Safe Position?: No"
        if stringcmd[0] == 'GUIDESTAR':
            autoGuide = True
            if stringcmd[1] == 'AUTO':
                autoGuideStarSelect = True
                guideExp = float(stringcmd[2])

print
testCamera.setCCDTemp(temp)
testCamera.gotoCCDTemp()

print
if not testMount.findObject(obj):
    if testMount.checkObjectInWest():
        print "%s is west of the meridian" % obj
    else:
        print "%s is east of the meridian" % obj

    print
    times = testMount.getTimes()
    print "System Date = %s" % times['Date Now']
    print "System Time = %s" % times['Time Now']
    if times.has_key('Rise time'):
        hms = testMount.getAngleToDMS(times['Rise time'])
        print "%s rise time    = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],
                                                          hms[2])
    if times.has_key('Transit time'):
        hms = testMount.getAngleToDMS(times['Transit time'])
        print "%s transit time = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],
                                                          hms[2])
    if times.has_key('Set time'):
        hms = testMount.getAngleToDMS(times['Set time'])
        print "%s set time     = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],
                                                          hms[2])
else:
    print "ERROR: Object not found"
    raise EnvironmentError, 'Halting program'

#begin test
print
if not testMount.slewToObject(obj):
    # Reset guide star positions for next test
    testCamera.resetGuideStar()
    print
    testCamera.setBinning(int(binning))
    testCamera.setFullFrame()
    fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)
    if autoGuide:
        if autoGuideStarSelect:
            if testCamera.autoGuide(True,guideExp):
                testCamera.stopAutoGuide()
        else:
            if testCamera.autoGuide(False,0.0):
                testCamera.stopAutoGuide()
        # Make sure autoguider is running
        if testCamera.checkGuiderRunning():
            print
            for i in range(noImages):
                testCamera.exposeLight(exp,filterDictionary[filterName.upper()],
                                       fileName)
            # Stop autoguider after all images complete
            testCamera.stopAutoGuide()    
        else:
            print "ERROR - Autoguider not running as expected"
    else:
        print
        for i in range(noImages):
            testCamera.exposeLight(exp,filterDictionary[filterName.upper()],
                                   fileName)
else:
    print "ERROR: During slew to object"
    raise EnvironmentError, 'Halting program'

# warm the CCD to ambient
if warmCCD:
    print
    testCamera.warmCCD()

# slew to safe position
if slewSafePosition:
    print
    testMount.slewToAzAlt(90.0,75.0,"safe")

print
print "Script Complete!"
After importing the 'cCamera', 'cMount', and 'cParser' modules, each class is instantiated. The user is then prompted for the name of the input text file, its existence is verified by the 'missing()' method, and the file is parsed by the 'parseFile()' method. The next block of code strips out the parameters for each command and associates these parameters with local variables to be used throughout the remainder of the script. The next step is to set the CCD temperature and to wait for the CCD temperature to stabilize (unless 'skip' is specified). After finding the object to image in the Sky6 database, the scope is commanded to slew to the object. If the 'GUIDESTAR' command has been specified, autoguiding will then start with either automatic or manual guide star selection. Finally, after tracking errors have converged (if applicable), the images will be taken using the parameters found with the 'LIGHT' command. After all images are collected, the CCD temperature will be warmed back to ambient if the appropriate parameter is set in the 'OTHERS' command. Likewise, if the appropriate parameter is set, the scope will be commanded to slew to a hard-coded safe position (azimuth of 90 degrees, altitude of 75 degrees).

RUNNING THE SCRIPT
Like in the previous post, before executing this script the Sky6 simulator must be manually connected to the telescope control system using the usual procedure. Also, the imaging and guide cameras in MaxIm DL should be first connected to the simulated cameras. The python script can then be executed from the IDE in the normal manner. The source listing for files used by this script can be downloaded from chapter_4.zip. Experiment with the script by running it multiple times by editing and saving the input text file to select different commands and parameters. (Note: Leave off (or comment out using '#' character) the 'GUIDESTAR' command to perform imaging with no autoguiding.)

WHAT'S NEXT?
One deficiency with this script is all status messages only go to the screen. Ideally, it would be good to also echo status of the imaging session to a log file for later analysis. In the next post, I will present a new class that handles data logging with time-stamps on each message. Implementing this capability will also require some changes to the 'cCamera' and 'cMount' class modules.

CLICK HERE FOR NEXT POST IN THIS SERIES

No comments:

Post a Comment