Monday, May 23, 2011

Automated Astrophotography with Python - Part 1b

SAVING IMAGES
The script of the 'cCamera' class presented in Part 1a of this series suffered from one major problem - it only provided for exposing and downloading an image but did not provide for saving the image to storage media. The script shown below addresses that deficiency. (Python code added to this version of the script is shown in light cyan text; existing code is shown in yellow text.)
import time
import os
import win32com.client

LIGHT_PATH = r"c:\\astro_images\\"
ERROR = True
NOERROR = False

##------------------------------------------------------------------------------
## Class: cCamera
##------------------------------------------------------------------------------
class cCamera:
    def __init__(self):
        print "Connecting to MaxIm DL..."
        self.__CAMERA = win32com.client.Dispatch("MaxIm.CCDCamera")
        self.__CAMERA.DisableAutoShutdown = True
        try:
            self.__CAMERA.LinkEnabled = True
        except:
            print "... cannot connect to camera"
            print "--> Is camera hardware attached?"
            print "--> Is some other application already using camera hardware?"
            raise EnvironmentError, 'Halting program'
        if not self.__CAMERA.LinkEnabled:
            print "... camera link DID NOT TURN ON; CANNOT CONTINUE"
            raise EnvironmentError, 'Halting program'

    def generateFilename(self,path,baseName):
        # path is the path to where the file will be saved
        baseName.replace(':', '_')      # colons become underscores
        baseName.replace(' ', '_')      # blanks become underscores
        baseName.replace('\\', '_')     # backslash becomes underscore
        # make sure the base filename has an '_' at the end
        if not baseName.endswith("_"):
            baseName = baseName + "_"
        # add 1 to use next available number
        seqMax = self.getSequenceNumber(path,baseName) 
        seqNext = seqMax + 1
        filename = "%s%s%05d.fit" % (path,baseName,seqNext)
        return filename

    def getSequenceNumber(self,path,baseName):
        # get a list of files in the image directory
        col = os.listdir(path)
        # Loop over these filenames and see if any match the basename
        retValue = 0
        for name in col:
            front = name[0:-9]
            back = name[-9:]
            if front == baseName:
                # baseName match found, now get sequence number for this file
                seqString = name[-9:-4]  # get last 5 chars of name (seq number)
                try:
                    seqInt = int(seqString)
                    if seqInt > retValue:
                        retValue = seqInt    # store greatest sequence number
                except:
                    pass
        return retValue

    def exposeLight(self,length,filterSlot,name):
        print "Exposing light frame..."
        self.__CAMERA.Expose(length,1,filterSlot)
        while not self.__CAMERA.ImageReady:
            time.sleep(1)
        print "Light frame exposure and download complete!"
        # save image
        filename = self.generateFilename(LIGHT_PATH,name)
        print "Saving light image -> %s" % filename
        self.__CAMERA.SaveImage(filename)

    def setFullFrame(self):
        self.__CAMERA.SetFullFrame()
        print "Camera set to full-frame mode"
        
    def setBinning(self,binmode):
        tup = (1,2,3)
        if binmode in tup:
            self.__CAMERA.BinX = binmode
            self.__CAMERA.BinY = binmode
            print "Camera binning set to %dx%d" % (binmode,binmode)
            return NOERROR
        else:
            print "ERROR: Invalid binning specified"
            return ERROR

##
##    END OF 'cCamera' Class
##
As you can see, two new methods have been added to the script. The 'generateFilename' script takes as arguments a path and a base name that usually should include the object name, filter, and binning used during the exposure (ex. m106_R_2x2). The first thing this method does is to take the 'baseName' parameter and strip out any characters that should not be in a filename and replace them with the '_' (underscore) character. It also appends an underscore character to 'baseName'. This method then calls the other new method, 'getSequenceNumber', and provides it with the path and new name arguments. The 'getSequenceNumber' method then uses the 'listdir' method of the 'os' object (hence, the new 'import os' at the top of the listing) to generate a list of all files in the directory specified by 'path'. The method then iterates through the list of directory files looking for one with a matching base name. If one is found, it checks the last five characters before '.fit' and tries to convert these characters into an integer. If successful, it returns the highest integer it can find back to the calling method. The 'generateFilename' method then uses this number to assign the next image with a similar base name the next number in the sequence.

Additional new code has been added to the existing 'exposeLight' method. This new code makes the call to the 'generateFilename' method, prints a message to the screen showing the path and filename of the saved image, then calls the MaxIm DL 'SaveImage' method to actually transfer the image to storage.

cCAMERA UNIT TEST
The following listing shows the unit test code for this version of the 'cCamera' class. The main difference is the requirement for the new 'name' parameter in the 'exposeLight' method.
if __name__ == "__main__":

    # Create an instance of the cCamera class
    testCamera = cCamera()

    # Setup MaxIm DL to take a full frame image 
    testCamera.setFullFrame()
    # Setup binning for 2x2
    if not testCamera.setBinning(2):
        for i in range(4):
            # Expose filter slot 0 (Red) for 15 seconds
            testCamera.exposeLight(15,0,'m51_R_2x2')
    else:
        print "Images not taken due to previous error"
RUNNING THE SCRIPT
Testing this version of the 'cCamera' class follows the same procedure described in Part 1a of this series. Note: Remember that the path specified by the constant 'LIGHT_PATH' must exist prior to running the script. The new listing for 'cCamera' can be downloaded from cCamera_1b.zip. Experiment with the script by running it multiple times and by changing the binning, filter, exposure length, or file base name in the unit test section to verify the script operates as expected.

WHAT'S NEXT?
In the next post I will continue to expand the 'cCamera' class by introducing more methods and properties that allow for control of the camera's thermo-electric cooler (TEC). I will also expand the unit test listing to verify correct operation of the features added to this class.

CLICK HERE FOR NEXT POST IN THIS SERIES

No comments:

Post a Comment