Tuesday, May 24, 2011

Automated Astrophotography with Python - Part 1c

CCD TEMPERATURE CONTROL
A feature of most serious astronomical CCD cameras is the ability to set and regulate the temperature of the CCD chip via use of thermo-electric cooling (TEC). To this point, the 'cCamera' class has not addressed this feature. The listing show below now includes new constants, a class attribute, and three new methods that add TEC control to the script. (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\\"
SETTLE_TIME = 120  # minimum time for stable temp at set point 
SETTLE_MAX  = 480  # maximum time for temp to stabilize
ERROR = True
NOERROR = False

##------------------------------------------------------------------------------
## Class: cCamera
##------------------------------------------------------------------------------
class cCamera:
    def __init__(self):
        self.__CCDTemp = 'Skip' # CCD Temperature (Skip = don't change temp)

        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 setCCDTemp(self,strtemp):
        if strtemp.upper() == 'SKIP':
            print "No CCD Cooling Specified"
            self.__CCDTemp = -99
            return NOERROR
        if strtemp.endswith("C"):
            try:
                flttemp = float(strtemp[:-1])
            except:
                print "ERROR: Specified CCD Temperature - Invalid format"
                return ERROR
        else:
            try:
                flttemp = float(strtemp)
            except:
                print "ERROR: Specified CCD Temperature - Invalid format"
                return ERROR
        self.__CCDTemp = flttemp
        return NOERROR
        
    def gotoCCDTemp(self):
        if self.__CCDTemp > -90:
            # set the CCD temperature set-point
            self.__CAMERA.TemperatureSetpoint = self.__CCDTemp
            print "CCD temperature setpoint: %0.2fC" % self.__CCDTemp
            # make sure the cooler is on, just in case
            if not self.__CAMERA.CoolerOn:
                print "Turning CCD cooler on"
                self.__CAMERA.CoolerOn = True
            print "Waiting for CCD temperature to stabilize"
            started = time.time()
            cnt = 0
            # Check CCD temp to stabilize
            while cnt < SETTLE_TIME and (time.time() - started) < SETTLE_MAX:
                currentTemp = self.__CAMERA.Temperature
                if (currentTemp < self.__CCDTemp - 0.5 or
                    currentTemp > self.__CCDTemp + 0.5):
                    cnt = 0
                time.sleep(1)
                cnt += 1
            if cnt == SETTLE_TIME and (time.time() - started) < SETTLE_MAX:
                print "CCD Temperature Stable at %0.2fC" % self.__CAMERA.Temperature
                try:
                    power = self.__CAMERA.CoolerPower
                    print "CCD Cooler Power: %d%%" % power
                except:
                    print "CCD cooler power could not be read"
                    return NOERROR
                return NOERROR
            else:
                print "CCD Temperature Did Not Stabilize"
                return ERROR
        else:
            print "Skipping temp stabilization."
            return NOERROR

    def warmCCD(self):
        if self.__CAMERA.CoolerOn:
            print "Starting to gradually warm CCD temperature to ambient"
            power = 100
            setTemp = self.__CAMERA.Temperature
            while power > 3:
                setTemp = setTemp + 5.0
                self.__CAMERA.TemperatureSetpoint = setTemp
                print "CCD temperature setpoint: %0.2fC" % setTemp
                print "Waiting 2.5 minutes for temperature to rise"
                time.sleep(150)
                print "CCD Cooler Temp : %0.2fC" % self.__CAMERA.Temperature
                try:
                    power = self.__CAMERA.CoolerPower
                    print "CCD Cooler Power: %d%%" % power
                except:
                    print "CCD cooler power could not be read"
                    power = 0
            print "CCD warming complete. Turning CCD cooler off"
            self.__CAMERA.CoolerOn = False
        else:
            print "CCD Cooler is off. CCD warming not necessary."

    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
##
The first new method is called 'setCCDTemp' and takes a string (ex. '-15.5C') as its argument. This method first checks to see if the string is 'skip' (meaning don't change the currently set TEC regulated temperature) and if it is, sets the new class attribute '__CCDTemp' to -99. If the string is not 'skip', it strips off the trailing 'C' (if it exists) and then attempts to convert the string to a float. If successful, '__CCDTemp' is set to the float value, and if not successful, the method ends with an invalid format error message and returns an error flag back to the calling routine.

The second new method, 'gotoCCDTemp', sets the MaxIm DL property 'TemperatureSetpoint' to the desired temperature and sets the 'CoolerOn' property to TRUE to activate the TEC. The remainder of the method reads the CCD temperature and looks for it to be stable (setpoint temp plus or minus 0.5C) for a two minute period (SETTLE_TIME) before reading the TEC power and continuing. The method also imposes a maximum of 8 minutes (SETTLE_MAX) for the temperature to become stable before returning an error flag back to the calling routine.

The third method is a simple CCD warming routine that raises the chip's temperature by 5C, waits 150 seconds, then reads the TEC power. If the TEC power is above 3%, the temperature setpoint is raised another 5C and another 2.5 minute wait is initiated. This procedure repeats until the TEC power falls below 3% at which point the 'CoolerOn' property is set to FALSE. (The cooler is also turned off if the method is unable to read the TEC power - as is the case with the MaxIm DL autoguider simulator.) NOTE: This method is probably not really needed since the risk of damaging the CCD by thermal shock from turning off the cooler without warming is minimal or nonexistent.

cCAMERA UNIT TEST
The following listing shows the unit test code for this version of the 'cCamera' class. The script sets the simulator camera up for full-frame mode with 1x1 binning. It then sets a CCD temperature and takes three images then sets a different CCD temperature and takes three more images. The test script then sets the CCD temp to 'Skip' and takes more images to verify that the temperature doesn't change before warming the CCD back to ambient.
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 1x1
    testCamera.setBinning(1)

    # Set CCD Temperature
    testCamera.setCCDTemp('-20C')
    # Goto CCD Temperature
    testCamera.gotoCCDTemp()
 
    # Take 3 images
    for i in range(3):
        # Expose filter slot 1 (Green) for 10 seconds
        testCamera.exposeLight(10,1,'m51_G')
    
    # Set CCD Temperature
    testCamera.setCCDTemp('-15C')
    # Goto CCD Temperature
    testCamera.gotoCCDTemp()
 
    # Take 3 images
    for i in range(3):
        # Expose filter slot 1 (Green) for 10 seconds
        testCamera.exposeLight(10,1,'m51_G')
    
    # Set CCD Temperature
    testCamera.setCCDTemp('skip')
    # Goto CCD Temperature
    testCamera.gotoCCDTemp()
 
    # Take 3 images
    for i in range(3):
        # Expose filter slot 1 (Green) for 10 seconds
        testCamera.exposeLight(10,1,'m51_G')
    
    # Warm the CCD to ambient
    testCamera.warmCCD()
RUNNING THE SCRIPT
Testing this version of the 'cCamera' class follows the same procedure described in Part 1a of this series. The new listing for 'cCamera' can be downloaded from cCamera_1c.zip. Experiment with the script by running it multiple times and by changing the CCD temperature, 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 autoguide tracking during imaging. 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