Source: SPACE.com: All about our solar system, outer space and exploration
A blog mainly about astronomy, astrophotography, computers, computer programming, and amateur radio. Any general questions or comments regarding the contents of this blog may be directed to me at jon@wt5l.com. For specific questions related to a particular post, please leave a comment.
Saturday, March 31, 2012
Apollo 11 Rocket Engine Recovery (Infographic)
Wednesday, November 30, 2011
NGC 6960 - Veil Nebula (Western Segment)
Note 1: East is toward top and North is toward right side for all images. Complete image details are available by clicking here.
Note 2: [Source: O'Meara, Stephen James. "The Caldwell Objects". Cambridge: University of Cambridge and Sky Publishing, 2002. Print.]
Saturday, November 26, 2011
Spectacular Pleiades
Note 1: East is toward top and North is toward right side for all images. Complete image details are available by clicking here.
Note 2: [Source: O'Meara, Stephen James. "The Messier Objects". Cambridge: University of Cambridge and Sky Publishing, 1998. Print.]
Saturday, October 29, 2011
NGC 663 and Companions
Note: East is toward top and North is toward right side for all images. Also, complete image details are available by clicking here.
Thursday, October 6, 2011
Comet Garradd - Sept. 29, 2011
Monday, September 5, 2011
Sending Text Messages with Python
The key to sending text messages is contained in this List of SMS Gateways that contains e-mail addresses for each of the major cell phone carriers. Basically, all you need to do is send an e-mail message to the domain that applies to your cell phone carrier with your phone's ten-digit number as the address. As the Wikipedia page explains, you can only send simple text messages to the SMS gateway. The following Python code listing shows how to send the e-mail message programmatically. Obviously, you will need to know the outgoing e-mail domain, your user name and password for accessing your personal e-mail service provider to make the code work.
The listing shown below is coded for my particular service carrier (AT&T Wireless). To personalize the code for your particular setup, just modify the five constants defined prior to the class declaration. The source file corresponding to this listing can be downloaded from cTexting.zip.
from smtplib import SMTP
import datetime
import socket
import traceback
SMTP_DOMAIN = r'<OUTGOING EMAIL DOMAIN>'
SMTP_USER = r'<USER NAME>'
SMTP_PASS = r'<PASSWORD>'
FROM_ADDR = r'AstroPhoto Update <anyone@gmail.com>'
TO_ADDR = r'<TEN-DIGIT CELL NUMBER>@txt.att.net'
socket.setdefaulttimeout(10)
##--------------------------------------------------------------------------------
## Class: cTextMsg
##------------------------------------------------------------------------------
class cTextMsg:
__objectName = ''
def __init__(self):
pass
def set_objectName(self, value):
self.__objectName = value
return
def get_objectName(self):
return self.__objectName
def sendSMSMessage(self,message):
smtp = SMTP()
smtp.set_debuglevel(0)
subject = self.get_objectName()
date = datetime.datetime.now().strftime('%d/%m/%Y %H:%M')
msg = 'From: %s\nTo: %s\nSubject: %s\nDate: %s\n\n%s' % \
(FROM_ADDR,TO_ADDR,subject,date,message)
try:
smtp.connect(SMTP_DOMAIN,0)
smtp.login(SMTP_USER,SMTP_PASS)
smtp.sendmail(FROM_ADDR,TO_ADDR,msg)
print "SMS Message Sent Successfully"
smtp.quit()
except socket.timeout:
print "ERROR: Timeout sending SMS Message"
except:
print "ERROR: Unable to send SMS Message"
print '!'*60
print traceback.format_exc()
##
## END OF 'cTextMsg' Class
##
if __name__ == '__main__':
email_msg = cTextMsg()
email_msg.set_objectName('M31')
print 'Object Name: ' + email_msg.get_objectName()
email_msg.sendSMSMessage('This is a sample message from an imaging session.')
Saturday, September 3, 2011
Vertical Antenna Tilt Mount
Due to the obvious deterioration of the antenna from lack of maintenance and from this area's highly corrosive seaside location, I've decided to remove the antenna from its second floor mount and to re-mount it in a more convenient location in my back yard. A main requirement of the this new mount was to provide a means to easily raise and lower the antenna when needed for maintenance and when adverse weather conditions threaten. Below is a photo of the new tilt ground-mount base for the GAP antenna that I've come up with.
The base is made up of two 8-foot long treated 2"x4"s that are sunk 3 feet into the ground. A 42" deep by 18" diameter hole was dug out, filled with 6" of rock then poured with concrete around the 2"x4"s. Three sets of 6" long carriage bolts were installed between the 2"x4"s below ground level to give the concrete something to grab onto other than the treated wood.
Above the ground, the 2"x4" were joined with a 4-foot piece of treated 2"x6". A 6-foot piece of schedule-40 1" galvanized water pipe (1.32" OD) is mounted inside the channel formed by the 2"x4"s and 2"x6" (backside of photo on the left).
The pivot-point for the tilt mechanism is drilled through the galvanized pipe near the top end of the pipe. The pipe swings on a piece of 3/32" stainless steel threaded rod secured with two nuts of the outside of the 2"x4"s. Another set of nuts is installed on either side of the pipe to prevent the pipe from wobbling when it swings in and out of the channel between the 2"x4"s. Refer to the photo to the right.
In the normal, upright position, the galvanized pipe is secured to the base mount using 3 stainless steel 1/4" U-bolts pushed through holes in the 2"x6". Two of the three U-bolts are visible in the photo to the left.
For the antenna to be lowered, the three U-bolts are removed and the antenna will be either walked or winched down to a horizontal position. (I'm considering installation of a hand-winching mechanism.) The top of the 2"x6" will prevent the antenna from swinging lower than a nearly-horizontal position. Refer to the photo of the unloaded swing pipe in the photo to the right.
The antenna will be mounted onto the top portion of the 1" galvanized pipe using a stainless steel mounting plate and U-bolts provided with the GAP antenna. The 5'x5' wire hoop at the base of the antenna will be installed after the antenna is raised into its vertical position. Likewise, the hoop will have to be removed prior to lowering the antenna into its horizontal position. By elevating the base of the antenna about 6' off the ground, the hoop will be positioned approximately 8' off the the ground and well above the heads of anyone walking in the yard.
Tuesday, August 30, 2011
LoTW Application Complete!
My latest Logbook of the World (LoTW) application was submitted on July 19, 2011 and completed on August 29, 2011. In this application, I applied for 93 LoTW QSO credits, 20 paper QSO credits, and one DXCC award (10M). The following table contains my current DXCC status. Complete details regarding all confirmed DXCC entities and additional information regarding the DXCC awards program can be found by clicking here.
| Mixed | 157 Confirmed | 157 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| CW | 127 Confirmed | 127 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| Phone | 131 Confirmed | 131 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| Digital | 78 Confirmed | 78 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 80m | 20 Confirmed | 20 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 40m | 92 Confirmed | 92 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 30m | 5 Confirmed | 5 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 20m | 117 Confirmed | 117 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 17m | 3 Confirmed | 3 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 15m | 129 Confirmed | 129 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 12m | 6 Confirmed | 6 Awarded | 0 Pending | (0 LotW, 0 Paper) |
| 10m | 104 Confirmed | 104 Awarded | 0 Pending | (0 LotW, 0 Paper) |
Friday, August 12, 2011
Starts With A Bang! and Messier 29
One of my favorite science blogs is Starts With A Bang! by Ethan Siegel. Ethan is a theoretical astrophysicist at the Lewis and Clark College in Portland, Oregon. I hadn't checked in on his blog in a few weeks so I spent some time today reading through recent postings and ran across a post from August 2nd entitled "If I had a Telescope this Summer...". Reading through the excellent post on summer objects to view, I was pleasantly surprised to see he used my photo from last fall of the open cluster Messier 29 in Cygnus (see below). Now it really is my favorite blog! (Click here for a larger version of the photo.)
Sunday, July 17, 2011
Updated DXCC Status
The following table contains my current DXCC status. Complete details regarding all confirmed DXCC entities and additional information regarding the DXCC awards program can be found by clicking here.
| Mixed | 157 Confirmed | 135 Awarded | 22 Pending | (18 LotW, 4 Card) |
| CW | 127 Confirmed | 100 Awarded | 27 Pending | (24 LotW, 3 Card) |
| Phone | 131 Confirmed | 118 Awarded | 13 Pending | (8 LotW, 5 Card) |
| RTTY | 78 Confirmed | 66 Awarded | 12 Pending | (12 LotW, 0 Card) |
| 80m | 20 Confirmed | 18 Awarded | 2 Pending | (2 LotW, 0 Card) |
| 40m | 92 Confirmed | 70 Awarded | 22 Pending | (21 LotW, 1 Card) |
| 20m | 117 Confirmed | 102 Awarded | 15 Pending | (14 LotW, 1 Card) |
| 15m | 129 Confirmed | 112 Awarded | 17 Pending | (15 LotW, 2 Card) |
| 10m | 104 Confirmed | 93 Awarded | 11 Pending | (7 LotW, 4 Card) |
Sunday, July 10, 2011
Automated Astrophotography with Python - Part 7
Achieving great focus is undoubtedly one of the most critical operations for any astronomical imaging application. Using the free application, FocusMax, this step can be reduced to a simple, repeatable, and highly effective operation. Fortunately, FocusMax provides all of the necessary interfaces to quickly automate this important step. The following listing shows a new class named 'cFocuser'. This class is responsible for controlling all aspects of the focus operation. The listing for the 'cFocuser' class is shown below:
import time
import pythoncom
import win32com.client
FOCUSMAXTIME = 150
LASTFOCHFD = -1
ERROR = True
NOERROR = False
##------------------------------------------------------------------------------
## Class: cFocuser
##------------------------------------------------------------------------------
class cFocuser:
def __init__(self,printlog):
printlog.log(0,"Connecting to FocusMax...")
self.__FM = win32com.client.Dispatch("FocusMax.FocusControl")
def checkFocusStar(self,printlog):
printlog.log(0,"Checking focus by measuring HFD...")
HFD = []
count = 0
# take 7 HFD measurements, sort the list, report the median value
while count < 7:
busy = self.__FM.SingleExposeAsyncStatus
self.__FM.SingleExposeAsync()
# wait for measurement to end
startTime = time.time()
busy = -1
while busy == -1 and (time.time() - startTime) < FOCUSMAXTIME:
time.sleep(1)
busy = self.__FM.SingleExposeAsyncStatus
if self.__FM.SingleExposeAsyncStatus != 1:
printlog.log(0,"HFD Measurement failed or timed out")
return -1
result = self.__FM.HalfFluxDiameter
flux = self.__FM.TotalFlux
time.sleep(0.5)
printlog.log(1,"HFD Measurement: %0.2f Total Flux: %d" %
(result,flux))
if flux > 20000:
HFD.append(result)
count = count + 1
HFD.sort()
j = len(HFD)
if not j%2:
median = (HFD[(j/2)-1]+HFD[j/2])/2.0
else:
median = HFD[j/2]
focuserPos = self.__FM.Position
printlog.log(0,"Median HFD = %0.2f at Pos = %d" % (median,focuserPos))
return median
def focusStar(self,printlog):
global LASTFOCHFD
printlog.log(0,"Starting FocusMax Focus method...")
try:
busy = self.__FM.FocusAsyncStatus
start = self.__FM.FocusAsync()
except pythoncom.com_error, (hr, msg, exc, arg):
printlog.log(0,"ERROR: %s" % exc[2])
return ERROR
else:
if start:
# wait for focus method to end
startTime = time.time()
busy = -1
while busy == -1 and (time.time() - startTime) < FOCUSMAXTIME:
time.sleep(1)
busy = self.__FM.FocusAsyncStatus
if self.__FM.FocusAsyncStatus != 1:
printlog.log(0,"Focus method failed or timed out.")
return ERROR
printlog.log(0,"FocusMax Focus method complete...")
tgtHFD = self.__FM.HalfFluxDiameter
if tgtHFD > 0.5:
median = self.checkFocusStar(printlog)
if median == -1:
return ERROR
elif median < 0.5 or median > 4.0:
printlog.log(0,"ERROR: Focus results out of range.")
return ERROR
else:
LASTFOCHFD = median
return NOERROR
else:
printlog.log(0,"ERROR: Focus method failed.")
return ERROR
else:
printlog.log(0,"ERROR: FocusMax Focus method failed to start.")
return ERROR
##
## END OF 'cFocuser' Class
##The constructor for 'cFocuser' is called whenever a new object of the class is instantiated and is used to create a new object (__FM) that is bound to the FocusMax object (FocusControl) required by this class. Following the constructor, the 'checkFocusStar()' method performs a check of focus quality by invoking the 'SingleExposeAsync()' method. This method is called multiple times to take a series of measurement of a suitable focus star that has previously been centered in the field of view. After waiting for the exposure to complete, the method checks the properties that holds the measurement's HFD and total flux (brightness). The measurements are written to the screen and to the log file and the HFD value is appended to a list data structure. After seven measurements are collected, the HFD list is sorted and the median value is extracted and returned to the method's calling routine.The other method in this class is named 'focusStar()' and performs the actual focus operation (nearly equivalent to clicking on the 'Focus' button on FocusMax's front panel). This method invokes the FocusMax 'FocusAsync()' method to kick off the focus operation. The method then polls the 'FocusAsyncStatus' property to determine when the focus operation is complete. If the focus star's HFD is a reasonable value, the 'checkFocusStar()' method is called to determine the median of seven consecutive HFD measurements. The median value is then assigned to a global variable, 'LASTFOCHFD'.
UNIT TESTING
Unlike unit tests for modules presented up to this point, the unit test for this class can not be simulated. Therefore, actual stars must be available to adequately verify the operation of this module. Due to poor weather and equipment failures I have been unable to generate and verify a unit test for the 'cFocuser' class. As soon as I am able, I will update this post with a unit test.
WHAT'S NEXT?
In the next post, I will demonstrate the method that I use to automatically select focus stars. This method will work with previous modules and result in a means to select a suitable focus star near the object to image, slew to the star, focus and determine median HFD, then slew to the main object to begin imaging. This procedure, along with periodic slews back to the focus star to check and ,if necessary, re-focus, is the main sequence of events that I use for my astronomical imaging script.
Tuesday, June 28, 2011
M21 - Open Cluster
The open cluster M21 is a loose collection of about 50 stars located less than one degree northeast of the Trifid nebula (M20) in Sagittarius. The cluster lies approximately 4000 light years away and has a diameter of about 15 arc-minutes. Together, M20 and M21 make up an asterism known as "Webb's Cross" with M21 at the northern end and M20 at the southern end of the cross. M21 was first observed by Charles Messier in 1764. [Source: O'Meara, Stephen James. "The Messier Objects". Cambridge: University of Cambridge and Sky Publishing, 1998. Print.]
The data for this LRGB photo was captured between 3 June and 5 June 2011. All images were taken at the Chiefland Astronomy Village in Chiefland, Florida. Complete details regarding this image can be found by clicking on the image above or by clicking here.
Monday, June 27, 2011
M20 - Trifid Nebula
The Trifid Nebula is a large, bright nebula and open cluster located in the constellation Sagittarius. It consists of four bright lobes separated by dark lanes of obscuring matter. As seen in the photo above and in the full-frame photo found here, M20 is located in a densely populated region of the Milky Way. At a magnitude of 5.9 and a distance of approximately 5000 light-years, the Trifid was first observed by Charles Messier in 1764. [Source: O'Meara, Stephen James. "The Messier Objects". Cambridge: University of Cambridge and Sky Publishing, 1998. Print.]
The data for this LRGB photo was captured between 3 June and 5 June 2011. All images were taken at the Chiefland Astronomy Village in Chiefland, Florida. Complete details regarding this image can be found by clicking on the image above or by clicking here.
Sunday, June 26, 2011
Automated Astrophotography with Python - Part 6
One of the most important features of any automated imaging script includes the ability to determine exactly where the imaging system is pointing at any given time. This is accomplished by astrometric plate solves that correlates positions of stars in an image with predetermined, known star positions. The combination of CCDSoft and the Sky6 (or MaxIm DL Pro and Pinpoint) provide a software path to implement this feature. For my astro-imaging purposes, I've chosen a more convoluted path to achieving plate solves. Specifically, I acquire my plate solve images using MaxIm DL (which pulls approximate RA and Dec coordinates of the image's center from the Sky6 via an ASCOM driver), then load the image into CCDSoft to perform the actual plate solve operation.
In order to capture the initial image to be plate solved it is necessary to add some code to the 'cCamera' class. A new class constant that specifies the save directory of the plate solve images is added at the top of the module:
PLATESOLVE_PATH = r"c:\\imaging scripts\\plate_solve_images\\"Also, two new methods are added to the 'cCamera' class. These new methods are shown below:
def exposePlateSolve(self,length,filterSlot,name,printlog):
self.__CAMERA.Expose(length,1,filterSlot)
while not self.__CAMERA.ImageReady:
time.sleep(0.5)
# save exposure into plate solve folder
printlog.log(0,"Saving plate solve image...")
self.__CAMERA.SaveImage(PLATESOLVE_PATH + name + "_platesolve.fit")
def setQuarterSubFrame(self):
self.__CAMERA.StartX = self.CAMERA.CameraXSize / 4
self.__CAMERA.StartY = self.CAMERA.CameraYSize / 4
self.__CAMERA.NumX = self.CAMERA.CameraXSize / 2
self.__CAMERA.NumY = self.CAMERA.CameraYSize / 2
The method 'exposePlateSolve()' is used to capture the image, generate a filename, and save the image to the directory specified by PLATESOLVE_PATH. (Typically, all my plate solve images are unbinned, 10-second exposures taken through the luminance filter.) Since plate solves using CCDSoft and the Sky6 do not execute reliably on large images, I always use sub-framing to reduce the image size to one-fourth the area of a full-size frame. The 'setQuarterSubFrame()' method is responsible for setting up the reduced sub-frame so that the center of the sub-frame and the center of the full-size frame coincide.The next listing shows a new class named 'cPlateSolver'. This class is responsible for initiating the plate solve operation through CCDSoft. CCDSoft, in turn, calls on the Sky6 to assist with the matching of stars in the database with the stars in the plate solve image. The listing for 'cPlateSolver' is shown below:
import pythoncom
import win32com.client
IMAGESCALE = 1.808
PLATESOLVE_PATH = r"c:\\Imaging_Scripts\\plate_solve_images\\"
class cPlateSolver:
def __init__(self,printlog):
printlog.log(0,"Connecting to CCDSoft...")
self.__IMAGE = win32com.client.Dispatch("CCDSoft.Image")
def insertWCS(self,name,targetRA,targetDec,mount,printlog):
plateSolveResults = []
# open platesolve.fit in CCDSoft and perform plate solve
self.__IMAGE.Path = PLATESOLVE_PATH + name + "_platesolve.fit"
self.__IMAGE.Open()
# set image scale in CCDSoft
self.__IMAGE.ScaleInArcsecondsPerPixel = IMAGESCALE
try:
self.__IMAGE.InsertWCS()
except pythoncom.com_error, (hr, msg, exc, arg):
errorstr = exc[2]
errorstr = errorstr.replace('\n',' ')
printlog.log(0,"Plate Solve %s" % errorstr)
self.__IMAGE.Close()
plateSolveResults = [0.0,0.0,-1,-1]
else:
# if no errors, report results
printlog.log(0,"Plate Solve Results:")
printlog.log(1,"Image Scale: %0.3f asp" % IMAGESCALE)
printlog.log(1,"North Angle: %0.2f deg" % self.__IMAGE.NorthAngle)
# get 2000 Epoch RA and Dec of center of plate solved image
RADec = self.__IMAGE.XYToRADec(self.__IMAGE.Width/2.0,
self.__IMAGE.Height/2.0)
plateSolveResults.append(RADec[0])
plateSolveResults.append(RADec[1])
J2000RA = mount.getAngleToDMS(RADec[0])
printlog.log(1,"Solved RA (J2000) : %02dh %02dm %0.2fs" % \
(J2000RA[0],J2000RA[1],J2000RA[2]))
J2000DEC = mount.getAngleToDMS(RADec[1])
printlog.log(1,"Solved DEC (J2000): %02d deg %02d' %0.2f\"" % \
(J2000DEC[0],J2000DEC[1],J2000DEC[2]))
# Compute angular separation and position angle from
# plate solved location to target location
angSeparation = mount.getAngularSeparation(targetRA,targetDec,
RADec[0],RADec[1])
positionAngle = mount.getPositionAngle(targetRA,targetDec,
RADec[0],RADec[1])
angSeparation = angSeparation * 3600
plateSolveResults.append(angSeparation)
plateSolveResults.append(positionAngle)
printlog.log(1,"Pointing error = %0.2f arcsec at %0.2f deg" % \
(angSeparation,positionAngle))
self.__IMAGE.Close()
return plateSolveResults
def execPlateSolve(self,camera,mount,name,targetRA,targetDec,printlog,
mode="Image"):
# set binning to unbinned
printlog.log(0,"Setting imager bin mode to 1x1")
camera.setBinning(1,printlog)
# set up camera for quarter-size subframe in center
camera.setQuarterSubFrame()
# take 10 second exposure
printlog.log(0,"Exposing 10 second image for Plate Solve...")
if mode.upper() == "IMAGE":
camera.exposePlateSolve(10.0,0,PLATESOLVE_PATH+name,printlog)
else:
camera.exposePlateSolve(10.0,0,PLATESOLVE_PATH+name+'_test',
printlog)
#insert WCS
printlog.log(0,"Performing plate solve with CCDSoft and The Sky6...")
return self.insertWCS(name,targetRA,targetDec,mount,printlog)
##
## END OF 'cPlateSolver' Class
##
Two constants are declared and initialized at the top of the listing. 'IMAGESCALE' contains the arc-seconds per pixel resolution of the image to be solved and 'PLATESOLVE_PATH' is the path to the directory where the image has been stored. The class constructor creates an instance of the 'Image' object of CCDSoft.The plate solve operation is initiated by a call to the 'execPlateSolve()' method of 'cPlateSolver'. This method sets up the binning and sub-framing of the plate solve image then calls the 'exposePlateSolveImage()' method of the 'camera' instance of the 'cCamera' object. The image is then exposed and saved under a name that is generated using the 'name' argument of the method (the save filename is modified if the mode is determined to be something other than 'Image'). After the plate solve image is exposed and saved, the plate solve operation is performed by a call to the 'insertWCS()' method.
The 'insertWCS()' method is fairly simple. After opening the plate solve image in CCDSoft and setting the 'ScaleInArcsecondsPerPixel' attribute, the 'insertWCS()' method of CCDSoft's 'Image' object is called from inside a try...except...else construct. If the plate solve fails, an informative error message is printed to the screen and log file. Otherwise, the RA and Dec coordinates of the plate solve image's center is determined using the 'XYToRADec()' method of CCDSoft's 'Image' object. The RA and Declination are reformatted, written to screen and log file, and appended to the 'plateSolveResults' list data structure. Additionally, the separation (in arc-seconds) and position angle from the solved coordinates to the intended coordinates are found, written to screen and log file, and also appended to the 'plateSolveResults' list. This is is then returned to the calling routine ('execPlateSolve()') which, in turn, returns the list to its calling routine.
The unit test for this class demonstrates use of the methods in the 'cPlateSolver' class and is shown below:
if __name__ == '__main__':
import cLogger_6
import cCamera_6
import cMount_6
# set up path and name of the logfile
cCameraLogFile = r"C:\Imaging_Scripts\logfiles\cplatesolver.log"
# instantiate the class with the path and name
printer = cLogger_6.cLogger(cCameraLogFile)
printer.logHeader('Unit Test cPlateSolver Class')
# Create an instance of the cCamera class
testCamera = cCamera_6.cCamera(printer)
# Create an instance of the cMount class
testMount = cMount_6.cMount(printer)
# Create an instance of the cPlateSolver class
testPlateSolve = cPlateSolver(printer)
objects = ['M92','M107']
for obj in objects:
printer.log(0,"")
if not testMount.findObject(obj):
printer.log(0,"%s located" % (obj,))
coords = testMount.getCoordinates()
targetRA = coords['RA J2000']
targetDec = coords['DEC J2000']
DMScoords = testMount.getAngleToDMS(targetRA)
printer.log(0,"%s RA (J2000) = %02.0fh %02.0fm %02.0fs" % \
(obj,DMScoords[0],DMScoords[1],DMScoords[2]))
DMScoords = testMount.getAngleToDMS(targetDec)
printer.log(0,"%s Dec (J2000) = %02.0fd %02.0f' %02.0f\"" % \
(obj,DMScoords[0],DMScoords[1],DMScoords[2]))
psResults = testPlateSolve.execPlateSolve(testCamera,testMount,
obj,targetRA,targetDec,
printer,'test')
else:
printer.log(0,"%s could not be found." % obj)
printer.logFooter()
printer.blank()
In the unit test shown above, after importing the 'cLogger', 'cCamera', and 'cMount' modules, an instance of the class contained in each module is created. An instance of the 'cPlateSolver' class is then created and two object names are placed in a list. Next, a loop is entered that iterates through the object list. The first step of the loop is to find the object in the Sky6's database and, if found, extract the object's RA and Declination coordinates which are used as the target or intended coordinates. The 'execPlateSolve()' method of the 'cPlateSolver' object is then called with mode set to something other than 'Image'. This causes the plate solve image's name to be mangled by inserting '_test' in the middle of the file name. This is so as to not overwrite the actual plate solve image that is used to generate a successful plate solve after image acquisition. The process is then repeated for each object in the list.TESTING THE CLASSES
Like in previous posts, before testing classes in this section, 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 'cPlateSolver' module can then be executed from the IDE in the normal manner. The source files described in this chapter can be downloaded from chapter_6.zip. Note: the .fit files included in the .zip must be placed in the same directory that the plate solve images are saved to.
As an additional exercise, edit the unit test for the 'cPlateSolver' module and completely remove the 'test' argument in the call to the 'execPlateSolve()' method then run the module. This will cause the plate solve image's name to NOT be mangled and will thus overwrite the real plate solve image included in the .zip file (so, you should make a copy of the actual .fit image prior to this test.). After the plate solve image is taken, the image generated from the simulator will be used for a plate solve. Obviously, this will fail but the manner of the failure is instructive. If the error message returned is 'Plate Solve Error: command failed. Error code = 206 (0xce).' this most likely means the RA and Declination coordinates of the center of the image were not placed into the FITS header of the plate solve image (look for keywords 'OBJCTRA' and 'OBJCTDEC'). Conversely, if the error message is 'Plate Solve Image link failed because there are not enough stars in the image. Try adjusting the image's background and range. Error code = 651 (0x28b).', this indicates that the image just can't be solved (and this is the expected behavior).
WHAT'S NEXT?
In the next post, I will present a new class that demonstrates the use of 'FocusMax' using MaxIm DL Pro. The module will include a unit test which can't be executed from in a simulator mode but must have real stars, real images, and a properly configured 'FocusMax' to execute successfully.
CLICK HERE FOR NEXT POST IN THIS SERIES
Tuesday, June 21, 2011
M5 - Globular Cluster Now In Color
As described in a previous post found here, the high-resolution (unbinned) luminance data for this showcase globular cluster was captured in early May 2011 from my favorite dark site at the Chiefland Astronomy Village. On my return trip to CAV in June 2011, I collected the low-resolution (binned 2x2) red, green, and blue data. Normally, I adjust the exposure times for each filter per results of manual G2V calibration that I performed when I first acquired the SBIG ST-8300M camera and then use 1/1/1 weighting when color combining. This time I used the same exposure (5-minutes) for each sub-frame for each filter then used the free application eXcalibrator to determine the RGB weighting for color combine. In short, it worked great! eXcalibrator found 13 G2V stars in the image field and from these determined a RGB weighting of 1.000/1.128/1.301 (very close to the results of my manual G2V calibration).
The color balance of the image above has not been manually adjusted from the initial color combine. Complete details regarding this image and more color images of M5 at various resolutions can be found by clicking on the image above or by clicking here.
Saturday, June 18, 2011
Automated Astrophotography with Python - Part 5
In previous posts all status messages have been sent only to the standard output (screen) and disappear when Python is closed. In this post I'll describe how to direct status messages to also appear in a permanent log file. In order to accomplish this, it is necessary to generate a new class named 'cLogger'. The listing for the 'cLogger' class is show below:
import time
##----------------------------------------------------------------------------
## Class: cLogger
##----------------------------------------------------------------------------
class cLogger:
def __init__(self,filename):
self.__filename = filename
self.__level = 0
self.__echo = True
def log(self,level,value,echo=True):
self.__level = level
self.__echo = echo
# echo to terminal with leading space if echo = True (default)
if self.__echo:
print " " + value
# build the filename
iDateTime = time.localtime()
sDateStr = time.strftime('_%Y%m%d.log', iDateTime)
# prepend spaces depending on log level (each additional level has two
# spaces before text, for indenting)
for i in range(level):
value = " " + value
# build the full screen line including timestamp
prefix = time.strftime("%H:%M:%S ", time.localtime(time.time()))
if value.endswith("\n"):
value = value[:-1]
value = prefix + value
# write all entries to detailed log file
try:
f = open (self.__filename[:-4] + sDateStr, "a")
f.write(value)
f.write("\n")
f.close()
except:
print "Log: UNABLE TO WRITE TO LOG FILE:"
print "Filename:", self.__filename[:-4] + sDateStr
print "Log line:", value
def blank(self,echo=True):
self.__echo = echo
if self.__echo:
print " "
# build the filename
iDateTime = time.localtime()
sDateStr = time.strftime('_%Y%m%d.log', iDateTime)
# write all entries to detailed log file
try:
f = open (self.__filename[:-4] + sDateStr, "a")
f.write("\n")
f.close()
except:
print "Log: UNABLE TO WRITE TO LOG FILE:"
print "Filename:", self.__filename[:-4] + sDateStr
def logHeader(self,strTitle):
self.log(0,"")
strVal = strTitle + " Log File"
self.log(0,strVal,False)
self.log(0,"",False)
self.log(0,"*** START OF SCRIPT ***")
def logFooter(self):
self.log(0,"Script complete!")
self.log(0,"*** END OF SCRIPT ***")
self.log(0,"")
##
## END OF 'cLogger' Class
##
The constructor for 'cClass' is a short method that initializes class attributes. (Note: when an instance of the class is created, a complete path and filename must be provided to the class.) The 'log()' method is the routine that writes a status message to the logfile. The contents of the argument 'value' is the string text message that is logged. The 'level' argument determines the indentation level of the message and 'echo' determines whether the status message is also sent to the standard output (screen). The filename that is passed into the class is used as the base of the actual log filename. After the dot and three letter extension (ex. '.log') is removed, the remainder of the filename is appended with the year, month, day, and '.log' extension. Also, the text status message is prefixed with the local hour, minute, and second. The new filename is then opened in 'append' mode and the prefixed text message is written to the file with a carriage return character added to the end of the line. The file is then immediately closed to prevent inadvertent writes.The 'blank()' method serves only to write a blank line (with no time-stamp on the line) to the log file. This method is used to separate logging of individual runs of a script. The final two methods, 'logHeader()' and 'logFooter()', write out pre-defined header and footer messages to the log file.
The unit test for this class demonstrates use of all methods in the 'cLogger' class and is shown below:
if __name__ == '__main__':
# set up path and name of the logfile
cLoggerLogFile = r"C:\Imaging_Scripts\logfiles\clogger.log"
# instantiate the class with the path and name
printer = cLogger(cLoggerLogFile)
# print to logfile and to screen
printer.logHeader('Unit Test for cLogger Class')
printer.log(0,"Line 1 of status: no indentation")
printer.log(1,"Line 2 of status: 2 spaces of indentation")
printer.log(2,"Line 3 of status: 4 spaces of indentation")
printer.log(3,"Line 4 of status: 6 spaces of indentation")
# print to logfile only
printer.log(2,"Line 5 of status: 4 spaces of indentation",False)
# continue printing to logfile and to screen
printer.log(1,"Line 6 of status: 2 spaces of indentation")
printer.log(0,"Line 7 of status: no indentation")
printer.logFooter()
printer.blank()
LOGGING FROM WITHIN CLASSESIn order for status messages generated within classes to be logged, it is necessary to modify those classes to include calls to 'cLogger'. For example, shown below is the listing of affected methods in the 'cMount' class. The major change is that a 'cLogger' object is now passed as an argument into the methods that contain status message print statements. Then, in those arguments, the 'print' statement is changed to call the 'log()' method of the passed-in 'cLogger' object. Note that any messages that you wish to only show up on the screen should be left as normal Python 'print' statements. (Changes necessary to implement logging is shown in light cyan text.)
##------------------------------------------------------------------------------
## Class: cMount
##------------------------------------------------------------------------------
class cMount:
def __init__(self,printlog):
self.__info = {}
self.__objectName = ''
printlog.log(0,"Connecting to The Sky6...")
self.__SKYCHART = win32com.client.Dispatch("TheSky6.StarChart")
self.__SKYINFO = win32com.client.Dispatch("TheSky6.ObjectInformation")
self.__UTIL = win32com.client.Dispatch("TheSky6.Utils")
self.__MOUNT = win32com.client.Dispatch("TheSky6.RASCOMTele")
def slewToObject(self,obj,printlog,delay=2.0):
if not self.findObject(obj):
coords = self.getCoordinates()
printlog.log(0,"Slewing telescope to %s..." % obj)
JnowRA = self.__UTIL.ConvertAngleToDMS(coords['RA Now'])
printlog.log(0,"JNow RA : %02dh %02dm %0.2fs" % (JnowRA[0],
JnowRA[1],
JnowRA[2]))
JnowDEC = self.__UTIL.ConvertAngleToDMS(coords['DEC Now'])
printlog.log(0,"JNow DEC: %02d deg %02d' %0.2f\"" % (JnowDEC[0],
JnowDEC[1],
JnowDEC[2]))
try:
self.__MOUNT.SlewToRaDec(coords['RA Now'],coords['DEC Now'],obj)
except:
printlog.log(0,"ERROR: During slew to Object")
return ERROR
else:
printlog.log(0,"Done slewing!")
# delay (default = 2.0 seconds)
time.sleep(delay)
return NOERROR
else:
printlog.log(0,"%s could not be found." % obj)
return ERROR
def slewToAzAlt(self,azimuth,altitude,name,printlog,delay=2.0):
printlog.log(0,"Slewing to Azimuth: %0.1f and Altitude: %0.1f..." % \
(azimuth,altitude))
try:
self.__MOUNT.SlewToAzAlt(azimuth,altitude,name)
except:
printlog.log(0,"ERROR: During slew to altitude/azimuth position")
return ERROR
else:
printlog.log(0,"Done slewing!")
# delay (default = 2.0 seconds)
time.sleep(delay)
return NOERROR
Additionally, the unit test for 'cMount' must be modified to implement the data logging feature. Here's the listing for the 'cMount' class unit test:if __name__ == "__main__":
import cLogger_5
# set up path and name of the logfile
cMountLogFile = r"C:\Imaging_Scripts\logfiles\cmount.log"
# instantiate the class with the path and name
printer = cLogger_5.cLogger(cMountLogFile)
printer.logHeader('Unit Test for cMount Class')
# create an instance of the cMount object
testMount = cMount(printer)
# prompt for name of object to locate
print
obj = raw_input(" Enter first object to slew to: ")
# test slewToObject()
testMount.slewToObject(obj,printer)
# prompt for name of object to locate
print
obj = raw_input(" Enter second object to slew to: ")
# test slewToObject()
testMount.slewToObject(obj,printer)
# prompt for name of object for sync
print
obj = raw_input(" Enter object to sync to: ")
if not testMount.findObject(obj):
coords = testMount.getCoordinates()
# test syncToObject()
printer.log(0,"Syncing control system to %s" % obj)
testMount.syncToObject(coords['RA Now'],coords['DEC Now'],obj)
else:
printer.log(0,"%s could not be found." % obj)
# prompt for azimuth and altitude for slew
print
azimuth = raw_input(" Enter azimuth for slew: ")
azimuth = float(azimuth)
altitude = raw_input(" Enter altitude for slew: ")
altitude = float(altitude)
# test slewToAzAlt()
testMount.slewToAzAlt(azimuth,altitude,"park",printer)
printer.logFooter()
printer.blank()In the unit test shown above, after importing the 'cLogger' module, an instance of the class named 'printer' is created. This object is then used in calls to methods of the 'cMount' class and to generate status messages for the unit test itself. The class listing and unit test for the 'cCamera' class has been similarly modified and included in the download files for this chapter.TESTING THE CLASSES
Like in previous posts, before testing classes in this section, 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 'cLogger', 'cMount', and 'cCamera' classes can then be executed from the IDE in the normal manner. The source files described in this chapter can be downloaded from chapter_5.zip. Execute each unit test individually then inspect the generated log files to see that they are written as expected. Next, run each unit test again to verify that the log file is appended with new status messages for each run of the unit test.
WHAT'S NEXT?
In the next post, I will present a new class that implements plate solving using the Sky6 and CCDSoft. The class and unit test will also include a provision to perform a simulated test of the plate solve operation.
CLICK HERE FOR NEXT POST IN THIS SERIES
Wednesday, June 15, 2011
Automated Astrophotography with Python - Part 4
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 YA 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
Saturday, June 11, 2011
Automated Astrophotography with Python - Part 3
In this post, I'll demonstrate how to use the classes (cCamera and cMount) developed in previous posts to generate a practical script that slews to a given object then starts imaging with or without autoguiding. In order for the classes and scripts to work together correctly, there are a few initial steps to perform.
SETUP
The first step is to set up a local directory that will contain source code for classes and scripts. This directory can be set up anywhere on your hard drive but I prefer to locate this directory just off of the root directory of my C: drive. On my computer I have set up a directory named 'c:\imaging_scripts'. The classes and script that go with with this post (chapter_3.zip) should be copied to this directory. The second step is to set up Python so that it knows to look in this new directory. This is done my setting an environment variable called 'PYTHONPATH' with the path to the directory. These are the steps to set up 'PYTHONPATH' on a Windows 7 machine (setup on a Windows XP computer should be very similar):
- Click 'Start' button
- Right click on 'Computer' then click on 'Properties'
- Click on 'Advanced system settings'
- Click on the 'Environment Variables...' button
- Under 'User variables for...' click the 'New...' button
- In the 'Variable name:' field enter 'PYTHONPATH'
- in the 'Variable value:' field enter 'C:\Python27;C:\imaging_scripts' or whatever name you used for your directory
- Click the 'OK' button then reboot your computer
- Test the path setup by loading the Python IDE. At the '>>>' prompt type 'import cMount_3'. If the IDE comes back with another '>>>' prompt without throwing an exception, the path is set up correctly.
A listing of the complete script that calls both the 'cMount' and 'cCamera' classes is given below:
import cMount_3
import cCamera_3
filterDictionary = {'R':0,'G':1,'B':2,'L':3,'HA':4}
# create an instance of the mount and camera class
testMount = cMount_3.cMount()
testCamera = cCamera_3.cCamera()
# get imaging location
print
print "The Sky6 location is %s" % testMount.getImagingLocation()
# prompt for CCD temperature (type 'skip' if no change to existing temp)
print
temp = raw_input("Enter CCD temperature (ex. '-20C','-5','skip'): ")
testCamera.setCCDTemp(temp)
testCamera.gotoCCDTemp()
# Test 1 - Slew and image with no autoguiding
# prompt for name of object to locate
print
obj = raw_input("Enter name of object to image: ")
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
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])
# prompt for length of exposure
exp = raw_input("Enter length of exposure: ")
try:
exp = float(exp)
except:
print "ERROR: Invalid exposure length..."
raise EnvironmentError, 'Halting program'
# prompt for binning of image
binning = raw_input("Enter binning for exposure (1/2/3): ")
if binning in ('1','2','3'):
try:
binmode = int(binning)
except:
print "ERROR: Invalid binning..."
raise EnvironmentError, 'Halting program'
# prompt for number of exposures
noImages = raw_input("Enter the number of exposures: ")
try:
noImages = int(noImages)
except:
print "ERROR: Invalid number of images..."
raise EnvironmentError, 'Halting program'
# prompt for filter
filterName = raw_input("Enter filter for exposures (L/R/G/B/Ha): ")
if not filterName.upper() in ('R','G','B','L','HA'):
print "ERROR: Invalid filter designation..."
raise EnvironmentError, 'Halting program'
# begin test
print
if not testMount.slewToObject(obj):
testCamera.setBinning(binmode)
testCamera.setFullFrame()
fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)
for i in range(noImages):
testCamera.exposeLight(exp,filterDictionary[filterName.upper()],
fileName)
else:
print "ERROR: Object not found or error slewing to object"
raise EnvironmentError, 'Halting program'
# Test 2 - Slew and image with autoguiding and auto guide star selection
# prompt for name of object to locate
print
obj = raw_input("Enter name of object to image: ")
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
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])
# prompt for length of exposure
exp = raw_input("Enter length of exposure: ")
try:
exp = float(exp)
except:
print "ERROR: Invalid exposure length..."
raise EnvironmentError, 'Halting program'
# prompt for binning of image
binning = raw_input("Enter binning for exposure (1/2/3): ")
if binning in ('1','2','3'):
try:
binmode = int(binning)
except:
print "ERROR: Invalid binning..."
raise EnvironmentError, 'Halting program'
# prompt for number of exposures
noImages = raw_input("Enter the number of exposures: ")
try:
noImages = int(noImages)
except:
print "ERROR: Invalid number of images..."
raise EnvironmentError, 'Halting program'
# prompt for filter
filterName = raw_input("Enter filter for exposures (L/R/G/B/Ha): ")
if not filterName.upper() in ('R','G','B','L','HA'):
print "ERROR: Invalid filter designation..."
raise EnvironmentError, 'Halting program'
#begin test
print
if not testMount.slewToObject(obj):
# Start camera autoguiding with auto guide star select
# Guide exposure = 2.0 second
# Reset guide star positions for next test
testCamera.resetGuideStar()
if testCamera.autoGuide(True,2.0):
testCamera.stopAutoGuide()
# Make sure autoguider is running
if testCamera.checkGuiderRunning():
testCamera.setBinning(binmode)
testCamera.setFullFrame()
fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)
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 "ERROR: Object not found or error slewing to object"
raise EnvironmentError, 'Halting program'
# Test 3 - Slew and image with autoguiding and manual guide star selection
# prompt for name of object to locate
print
obj = raw_input("Enter name of object to image: ")
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
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])
# prompt for length of exposure
exp = raw_input("Enter length of exposure: ")
try:
exp = float(exp)
except:
print "ERROR: Invalid exposure length..."
raise EnvironmentError, 'Halting program'
# prompt for binning of image
binning = raw_input("Enter binning for exposure (1/2/3): ")
if binning in ('1','2','3'):
try:
binmode = int(binning)
except:
print "ERROR: Invalid binning..."
raise EnvironmentError, 'Halting program'
# prompt for number of exposures
noImages = raw_input("Enter the number of exposures: ")
try:
noImages = int(noImages)
except:
print "ERROR: Invalid number of images..."
raise EnvironmentError, 'Halting program'
# prompt for filter
filterName = raw_input("Enter filter for exposures (L/R/G/B/Ha): ")
if not filterName.upper() in ('R','G','B','L','HA'):
print "ERROR: Invalid filter designation..."
raise EnvironmentError, 'Halting program'
# begin test
print
if not testMount.slewToObject(obj):
# Start camera autoguiding with manual guide star select
# Reset guide star positions for next test
testCamera.resetGuideStar()
if testCamera.autoGuide(False,0.0):
testCamera.stopAutoGuide()
# Make sure autoguider is running
if testCamera.checkGuiderRunning():
testCamera.setBinning(binmode)
testCamera.setFullFrame()
fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)
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 "ERROR: Object not found or error slewing to object"
raise EnvironmentError, 'Halting program'
# warm the CCD to ambient
print
testCamera.warmCCD()
print
print "Script Complete!"
The first two lines of the script contain the import statements that reference the filenames (less the .py extension) containing the 'cMount' and 'cCamera' classes. At this point, instances of the two class are created and a dictionary is set up that indexes a letter or string representing the filter to the filter wheel slot number. Next, the current Sky6 location is printed then the user is prompted to enter a CCD temperature (enter 'skip' if no temperature change is desired). After setting the temperature and waiting for it to stabilize, the script begins the first of three tests.The first test begins by prompting the user for the name of an object. If the object is located in the Sky6 database, the data related to the object (side of meridian, current date/time, rise/transit/set times) are printed to screen. The user is then prompted to enter the length of the exposure, the binning, the number of exposures, and the filter to be used. The testing then begins by commanding a slew to the object then the images are collected per user inputs with no autoguiding enabled. For the second test, the first test is repeated similarly but this time the images are collected with autoguiding and auto guide star selection (2.0 second guide exposures) enabled. The third test is identical to the first two tests except that autoguiding is enable with manual guide star selection.
RUNNING THE SCRIPT
Before executing this script, the Sky6 simulator must be manually connected to the telescope control system using the same procedure as described in the previous post. 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_3.zip. Experiment with the script by running it multiple times and selecting different objects for The Sky6 to locate, slew to, and image.
WHAT'S NEXT?
One problem with this script is the repetitive and error-prone requirement to have the user manually enter object name, binning, exposure length, etc. for each test. In the next post, I will develop a new class that will allow this information to be read from an input text file. This will be a necessary feature to facilitate full automation of the imaging process.
CLICK HERE FOR NEXT POST IN THIS SERIES
Thursday, June 9, 2011
Automated Astrophotography with Python - Part 2c
The last features to add to the 'cMount' class are the ability to control pointing of the telescope and to synchronize the control system to a particular point in the sky. All these functions are found in the RASCOMTele class of the Sky6. As in previous posts, instead of showing the complete 'cMount' class listing, here I show only the changes to the listing presented in the previous post. The first listing shows the one new line that must be added to the class constructor ('__init__' method) to create an instance of the Sky6 RASCOMTele class. Here is the listing for that change:
self.__MOUNT = win32com.client.Dispatch("TheSky6.RASCOMTele")
The next listing shows the three new methods added to 'cMount' that call methods from the Sky6 RASCOMTele class:def slewToObject(self,obj,delay=2.0):
if not self.findObject(obj):
coords = self.getCoordinates()
print "Slewing telescope to %s..." % obj
JnowRA = self.__UTIL.ConvertAngleToDMS(coords['RA Now'])
print "JNow RA : %02dh %02dm %0.2fs" % (JnowRA[0],JnowRA[1],
JnowRA[2])
JnowDEC = self.__UTIL.ConvertAngleToDMS(coords['DEC Now'])
print "JNow DEC: %02d deg %02d' %0.2f\"" % (JnowDEC[0],JnowDEC[1],
JnowDEC[2])
try:
self.__MOUNT.SlewToRaDec(coords['RA Now'],coords['DEC Now'],obj)
except:
print "ERROR: During slew to Object"
return ERROR
else:
print "Done slewing!"
# delay (default = 2.0 seconds)
time.sleep(delay)
return NOERROR
else:
print "%s could not be found." % obj
return ERROR
def slewToAzAlt(self,azimuth,altitude,name,delay=2.0):
print "Slewing to Azimuth: %0.1f and Altitude: %0.1f..." % (azimuth,
altitude)
try:
self.__MOUNT.SlewToAzAlt(azimuth,altitude,name)
except:
print "ERROR: During slew to altitude/azimuth position"
return ERROR
else:
print "Done slewing!"
# delay (default = 2.0 seconds)
time.sleep(delay)
return NOERROR
def syncToObject(self,RA,Dec,Obj):
self.__MOUNT.Sync(RA,Dec,Obj)The first new method is 'slewToObject()'. This method attempts to locate the object in the Sky6's database. If successful, the current epoch equatorial coordinates are extracted and printed to screen. The method then calls the 'RASCOMTele' method 'SlewToRaDec()' in a try, except, else block to trap any exceptions. After the slew completes, a delay (default of 2 sec) is entered before the method returns to the calling routine. Similarly, the 'slewToAzAlt()' method calls the 'RASCOMTele' method 'SlewToAzAlt()' in a try, except, else block again to trap any exceptions. The final new method is 'syncToObject()' and calls the 'Sync()' method of 'RASCOMTele' to synchronize the control system to the specified coordinates.cMOUNT UNIT TEST
The following listing shows the unit test code for this version of the 'cMount' class. After creating an instance of the class, the user is prompted to enter the name of an object. If a valid object name is entered, the simulator is commanded to slew to the object. This test is then repeated for a second object. Next, the user is prompted to enter the name of another object. This object is then located in the database and the telescope control system is synced to the location of that object. (The telescope pointing "bull's eye" jumps to the location of the object.) Finally, the user is prompted to enter azimuth and altitude values. Assuming valid values have been entered, the simulator will slew to the location specified by the entered azimuth and altitude.
if __name__ == "__main__":
# create an instance of the cMount object
testMount = cMount()
# prompt for name of object to locate
print
obj = raw_input("Enter first object to slew to: ")
# test slewToObject()
testMount.slewToObject(obj)
# prompt for name of object to locate
print
obj = raw_input("Enter second object to slew to: ")
# test slewToObject()
testMount.slewToObject(obj)
# prompt for name of object for sync
print
obj = raw_input("Enter object to sync to: ")
if not testMount.findObject(obj):
coords = testMount.getCoordinates()
# test syncToObject()
print "Syncing control system to %s" % obj
testMount.syncToObject(coords['RA Now'],coords['DEC Now'],obj)
else:
print "%s could not be found." % obj
# prompt for azimuth and altitude for slew
print
azimuth = raw_input("Enter azimuth for slew: ")
azimuth = float(azimuth)
altitude = raw_input("Enter altitude for slew: ")
altitude = float(altitude)
# test slewToAzAlt()
testMount.slewToAzAlt(azimuth,altitude,"park")
RUNNING THE SCRIPTIn order to test this version of the 'cMount' class you must first load the Sky6 and manually connect the telescope control system to the simulator. This is done by clicking on the 'Telescope' pull-down menu tab then selecting the 'Setup...' option. In the 'Telescope Setup' dialog box, select 'Simulator' in the selection box under 'Name:'. Click on the 'Close' button then re-click the 'Telescope' pull-down menu tab then select the 'Server Settings...' option. In the 'Server Settings' dialog box, make sure the 'Allow sync' and 'Allow goto' boxes are checked then click the 'OK' button. (Note: the changes to the 'Telescope Setup' and 'Server Settings' dialog boxes only need to be made the first time the simulator is used.) The connection to the simulator is then made by clicking on the 'Telescope' pull-down menu tab then hovering over the 'Link' option. Move the mouse pointer over to and click on 'Establish' to make the connection. (You can also just click on the green telescope icon on the menu bar.) After a moment, the white bull's eye will appear that indicates the current location of the scope control system. The python script can now be executed from the IDE in the normal manner. The source listing for 'cMount' can be downloaded from cMount_2c.zip. Experiment with the script by running it multiple times and selecting different objects for The Sky6 to locate, slew to, and sync to. (When testing is complete, click on the red telescope icon the menu bar to terminate the connection.)
WHAT'S NEXT?
This post completes discussion of the 'cMount' class (although additional methods may be added to the class in later posts). In the next post, I will create a script that creates an instance of the 'cMount' class and an instance of the 'cCamera' class to demonstrate how these classes can be used in combination to generate practical scripts for slewing and imaging of objects.
CLICK HERE FOR NEXT POST IN THIS SERIES
Wednesday, June 8, 2011
Automated Astrophotography with Python - Part 2b
The next major functionality to add to the 'cMount' class is the ability to perform various calculations related to equatorial coordinates. All these functions are found in the UTILS class of the Sky6. As in previous posts, instead of showing the complete 'cMount' class listing, here I show only the changes to the listing presented in the previous post. The first listing shows the one new line that must be added to the class constructor ('__init__' method) to create an instance of the Sky6 UTILS class. Here is the listing for that change:
self.__UTIL = win32com.client.Dispatch("TheSky6.Utils")The next listing shows the six new methods added to 'cMount' that call methods from the Sky6 UTILS class:
def getAngularSeparation(self,RA1,Dec1,RA2,Dec2):
"""Returns results of "ComputeAngularSeparation" method of UTILS"""
return self.__UTIL.ComputeAngularSeparation(RA1,Dec1,RA2,Dec2)
def getPositionAngle(self,RA1,Dec1,RA2,Dec2):
"""Returns results of "ComputePositionAngle" method of UTILS"""
return self.__UTIL.ComputePositionAngle(RA1,Dec1,RA2,Dec2)
def getAngleToDMS(self,angle):
"""Returns results of "ConvertAngleToDMS" method of UTILS"""
return self.__UTIL.ConvertAngleToDMS(angle)
def get2000ToNow(self,RA,Dec):
"""Returns results of "Precess2000ToNow" method of UTILS"""
return self.__UTIL.Precess2000ToNow(RA,Dec)
def getNowTo2000(self,RA,Dec):
"""Returns results of "PrecessNowTo2000" method of UTILS"""
return self.__UTIL.PrecessNowTo2000(RA,Dec)
def getRADecToAzAlt(self,RA,Dec):
"""Returns results of "ConvertRADecToAzAlt" method of UTILS"""
return self.__UTIL.ConvertRADecToAzAlt(RA,Dec)The first two methods are used to compute the angular separation (in degrees) between two equatorial coordinates and to compute the position angle (measured counter-clockwise from true North) from one coordinate to the other. The method 'getAngleToDMS()' is used to compute the degrees, minutes, and seconds from an angle expressed as a float value. The next two methods are used to precess an equatorial coordinate between the current epoch and the year 2000 epoch or vice-versa. Finally, the 'getRADecToAzAlt()' method is used to generate altitude and azimuth for the Sky6's current time and location from an equatorial coordinate. Many more methods are available in the UTILS class and descriptions can be found in the Sky6's help section under 'Scripted Operation'.cMOUNT UNIT TEST
The following listing shows the unit test code for this version of the 'cMount' class. After creating an instance of the class, the test attempts to locate 'Pollux' in the database. If successful, the equatorial coordinates for the star are found and the RA and Dec for both the current epoch and for the year 2000 epoch are printed out in Hours or Degrees, minutes, and seconds. This operation is repeated for 'Castor' then the separation and position angle between the two stars is found and printed to screen. Next, the object 'M95' is located and the the RA and DEC (epoch 2000) is found and printed to screen. The current epoch coordinates is then found and printed using the 'get2000ToNow()' method. The object's altitude and azimuth is then computed and printed out. As a final test the object 'M65' is located and the the RA and DEC (current epoch) is found and printed. The year 2000 epoch coordinates is then found and printed using the 'getNowTo2000()' method. The object's altitude and azimuth is then computed and printed out.
if __name__ == "__main__":
# create an instance of the cMount object
testMount = cMount()
# test getAngleToDMS()
obj1 = 'Pollux'
if not testMount.findObject(obj1):
coords1 = testMount.getCoordinates()
print
DMScoords = testMount.getAngleToDMS(coords1['RA Now'])
print "%s RA (current epoch) = %02.0fh %02.0fm %03.1fs" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords1['DEC Now'])
print "%s Dec (current epoch) = %02.0fd %02.0f' %03.1f\"" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords1['RA J2000'])
print "%s RA (epoch 2000) = %02.0fh %02.0fm %03.1fs" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords1['DEC J2000'])
print "%s Dec (epoch 2000) = %02.0fd %02.0f' %03.1f\"" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
else:
print "%s could not be found." % obj1
obj2 = 'Castor'
if not testMount.findObject(obj2):
coords2 = testMount.getCoordinates()
print
DMScoords = testMount.getAngleToDMS(coords2['RA Now'])
print "%s RA (current epoch) = %02.0fh %02.0fm %03.1fs" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords2['DEC Now'])
print "%s Dec (current epoch) = %02.0fd %02.0f' %03.1f\"" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords2['RA J2000'])
print "%s RA (epoch 2000) = %02.0fh %02.0fm %03.1fs" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords2['DEC J2000'])
print "%s Dec (epoch 2000) = %02.0fd %02.0f' %03.1f\"" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
else:
print "%s could not be found." % obj2
# test getAngularSeparation()
separation = testMount.getAngularSeparation(coords1['RA Now'],
coords1['DEC Now'],coords2['RA Now'],coords2['DEC Now'])
separation = testMount.getAngleToDMS(separation)
print
print "The separation between %s and %s is %02.0f deg %02.0f min" % \
(obj1,obj2,separation[0],separation[1])
# test getPositionAngle()
angle = testMount.getPositionAngle(coords2['RA Now'],coords2['DEC Now'],
coords1['RA Now'],coords1['DEC Now'])
angle = testMount.getAngleToDMS(angle)
print
print "The postion angle from %s to %s is %02.0f deg %02.0f min" % \
(obj1,obj2,angle[0],angle[1])
obj1 = 'm95'
if not testMount.findObject(obj1):
# test get2000ToNow()
coords1 = testMount.getCoordinates()
print
DMScoords = testMount.getAngleToDMS(coords1['RA J2000'])
print "%s RA (epoch 2000) = %02.0fh %02.0fm %02.0fs" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords1['DEC J2000'])
print "%s Dec (epoch 2000) = %02.0fd %02.0f' %02.0f\"" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
coords2 = testMount.get2000ToNow(coords1['RA J2000'],coords1['DEC J2000'])
DMScoords = testMount.getAngleToDMS(coords2[0])
print "%s RA (current epoch) = %02.0fh %02.0fm %02.0fs" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords2[1])
print "%s Dec (current epoch) = %02.0fd %02.0f' %02.0f\"" % (obj1,
DMScoords[0],DMScoords[1],DMScoords[2])
# test getRADecToAzAlt()
print
azAlt = testMount.getRADecToAzAlt(coords1['RA J2000'],
coords1['DEC J2000'])
azimuth = testMount.getAngleToDMS(azAlt[0])
print "%s Azimuth = %02.0fd %02.0fm %02.0fs" % (obj1,
azimuth[0],azimuth[1],azimuth[2])
altitude = testMount.getAngleToDMS(azAlt[1])
print "%s Altitude = %02.0fd %02.0fm %02.0fs" % (obj1,
altitude[0],altitude[1],altitude[2])
print
else:
print "%s could not be found." % obj1
obj2 = 'm65'
if not testMount.findObject(obj2):
# test getNowTo2000()
coords1 = testMount.getCoordinates()
print
DMScoords = testMount.getAngleToDMS(coords1['RA Now'])
print "%s RA (current epoch) = %02.0fh %02.0fm %02.0fs" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords1['DEC Now'])
print "%s Dec (current epoch) = %02.0fd %02.0f' %02.0f\"" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
coords2 = testMount.getNowTo2000(coords1['RA Now'],coords1['DEC Now'])
DMScoords = testMount.getAngleToDMS(coords2[0])
print "%s RA (epoch 2000) = %02.0fh %02.0fm %02.0fs" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
DMScoords = testMount.getAngleToDMS(coords2[1])
print "%s Dec (epoch 2000) = %02.0fd %02.0f' %02.0f\"" % (obj2,
DMScoords[0],DMScoords[1],DMScoords[2])
# test getRADecToAzAlt()
print
azAlt = testMount.getRADecToAzAlt(coords2[0],coords2[1])
azimuth = testMount.getAngleToDMS(azAlt[0])
print "%s Azimuth = %02.0fd %02.0fm %02.0fs" % (obj2,
azimuth[0],azimuth[1],azimuth[2])
altitude = testMount.getAngleToDMS(azAlt[1])
print "%s Altitude = %02.0fd %02.0fm %02.0fs" % (obj2,
altitude[0],altitude[1],altitude[2])
print
else:
print "%s could not be found." % obj2
RUNNING THE SCRIPTAs before, testing this version of the 'cMount' class is simply a matter of loading the source code into the Python IDE and running the module by hitting the F5 key. If the Sky6 is not already running, it will be loaded before the unit test executes. The source listing for 'cMount' can be downloaded from cMount_2b.zip. Experiment with the script by running it multiple times and selecting different objects for The Sky6 to locate and display information about.
WHAT'S NEXT?
In the next post I will complete the 'cMount' class by introducing methods that allow the script to control pointing of the telescope and syncing the the scope with a particular point in the sky. I will also create a new unit test listing to verify correct operation of the features added to this class.
CLICK HERE FOR NEXT POST IN THIS SERIES