<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5037516298520460978</id><updated>2012-02-16T15:14:37.105-05:00</updated><category term='Spaceflight'/><category term='Python'/><category term='Scripting'/><category term='DXCC'/><category term='Amateur Radio'/><category term='Messier'/><category term='Astrophotography'/><category term='Comets'/><title type='text'>WT5L</title><subtitle type='html'>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.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>30</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8362146746236091474</id><published>2011-11-30T16:12:00.001-05:00</published><updated>2011-11-30T20:33:10.611-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>NGC 6960 - Veil Nebula (Western Segment)</title><content type='html'>In the constellation Cygnus (The Swan) an unknown supergiant star exploded some 15,000 years ago and briefly appeared as a spectacular Type II supernova.  The remnant of that ancient blast is now visible as a very large circle of nebulosity known as the Cygnus Loop.  More popularly known as the Veil Nebula (aka Filamentary Nebula, Bridal Veil Nebula, or Network Nebula), this circle of star material spans some 3 degrees or six full-Moon-diameters of the night sky. The segment on the western side of the loop is cataloged as NGC 6960 or Caldwell 33 and is the subject of my recent image shown below:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/ngc6960-10292011.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="523" width="700" src="http://astro.wt5l.com/astro_images_content/ngc6960-10292011_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;This portion of the Veil Nebula is also known as "The Witches Broom" and is dominated by the bright, magnitude 4.2 foreground star known as 52 Cygni.  NGC 6960 runs in a roughly north-south line slightly east of 52 Cygni.  A cropped, full resolution image of this western segment of the Veil is shown below:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/ngc6960-10292011_cropped.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="271" width="700" src="http://astro.wt5l.com/astro_images_content/ngc6960-10292011_cropped.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Note 1:&lt;/b&gt; East is toward top and North is toward right side for all images.  Complete image details are available by clicking &lt;a href="http://astro.wt5l.com/ngc6960_01.php"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Note 2:&lt;/b&gt; [Source: O'Meara, Stephen James. &lt;i&gt;"The Caldwell Objects".&lt;/i&gt; Cambridge: University of Cambridge and Sky Publishing, 2002. Print.]&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8362146746236091474?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8362146746236091474/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/11/ngc-6960-veil-nebula-western-segment.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8362146746236091474'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8362146746236091474'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/11/ngc-6960-veil-nebula-western-segment.html' title='NGC 6960 - Veil Nebula (Western Segment)'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8601718661091579618</id><published>2011-11-26T18:36:00.000-05:00</published><updated>2011-11-26T18:36:18.137-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>Spectacular Pleiades</title><content type='html'>On fall and winter evenings, the northern sky's most spectacular open cluster rides high in the constellation Taurus the Bull.  The 70-million-year-old cluster contains some 100 stars in an area 14 light years in diameter with ten stars brighter than 6th magnitude.  Photographs show the entire area surrounded by ice-blue nebulosity that reflects the light of the hot, young stars.  Debate continues whether the nebulosity is associated with the stars or whether the cluster is just moving through an area of nebulosity.  Either way, the cluster/nebulosity combination makes for a magnificent wide-field photograph.  Here is a reduced version of the Pleiades:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/m45_10302011.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="526" width="700" src="http://astro.wt5l.com/astro_images_content/m45_10302011_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;A map of the Pleiades rotated to match all images is shown below:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/m45_map.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="708" width="500" src="http://astro.wt5l.com/astro_images_content/m45_map.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;The brightest area of nebulosity surrounds the star Merope (23 Taurii) and is known as the Merope Nebula (IC349).  Just to the south of Merope (left) is another area of nebulosity cataloged as NGC 1435 and also known as Tempel's Nebula. A close-up image of the area around Merope is shown below: &lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/m45_merope_10302011.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="434" width="700" src="http://astro.wt5l.com/astro_images_content/m45_merope_10302011.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;Another bright area of nebulosity surrounds the Maia (20 Taurii) and is known as the Maia Nebula (NGC 1432).  A close-up image of the area around Maia is shown below:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/m45_maia_10302011.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="465" width="700" src="http://astro.wt5l.com/astro_images_content/m45_maia_10302011.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;b&gt;Note 1:&lt;/b&gt; East is toward top and North is toward right side for all images.  Complete image details are available by clicking &lt;a href="http://astro.wt5l.com/m45_02.php"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;Note 2:&lt;/b&gt; [Source: O'Meara, Stephen James. &lt;i&gt;"The Messier Objects".&lt;/i&gt; Cambridge: University of Cambridge and Sky Publishing, 1998. Print.]&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8601718661091579618?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8601718661091579618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/11/spectacular-pleiades.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8601718661091579618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8601718661091579618'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/11/spectacular-pleiades.html' title='Spectacular Pleiades'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-7122528327973505182</id><published>2011-10-29T08:39:00.005-04:00</published><updated>2011-11-01T06:43:59.341-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>NGC 663 and Companions</title><content type='html'>Among the many open cluster in the region where the Milky Way cuts through Cassiopeia is a tight grouping of three clusters dominated by NGC 663.  With a diameter of 15 arc-minutes and a magnitude of 7.1, NGC 663 is also cataloged as Caldwell 10 and is often described as the 'The Horseshoe Cluster'. Here is a close-up image of NGC 663:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/ngc663_10022011_cropped.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="468" width="700" src="http://astro.wt5l.com/astro_images_content/ngc663_10022011_cropped.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;About one-half degree to the southwest of NGC 663 is another fainter and smaller cluster cataloged as NGC 659.  This cluster is about 6 arcmin in diameter and shines at magnitude 7.9.  Here is a close-up image of NGC 659:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/ngc659_10022011_cropped.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="418" width="555" src="http://astro.wt5l.com/astro_images_content/ngc659_10022011_cropped.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;Also in the same field of view, about 43 arc-minutes to the northwest of NGC 663 is still another small cluster designated as NGC 654.  This cluster is also about 6 arc-minutes in diameter but slightly brighter than NGC 659 at magnitude 6.5.  Here is a close-up image of NGC 654:&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/ngc654_10022011_cropped.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="420" width="603" src="http://astro.wt5l.com/astro_images_content/ngc654_10022011_cropped.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;The complete field-of-view containing the three clusters (labeled) is shown in the following image.  &lt;b&gt;Note:&lt;/b&gt; The bright blue-white star to the southwest (below and left) of NGC 659 is the multiple star 44 CAS.  This 5.8 magnitude star has a magnitude 9.6 companion 66 arc-seconds away and a 9.2 magnitude companion 140 arc-seconds away. &lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/ngc663_10022011.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="528" width="700" src="http://astro.wt5l.com/astro_images_content/ngc663_10022011_annot.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger, unlabeled version)&lt;/div&gt;&lt;p&gt;&lt;b&gt;Note:&lt;/b&gt; East is toward top and North is toward right side for all images.  Also, complete image details are available by clicking &lt;a href="http://astro.wt5l.com/ngc663_01.php"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-7122528327973505182?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/7122528327973505182/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/10/ngc-663-and-companions.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7122528327973505182'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7122528327973505182'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/10/ngc-663-and-companions.html' title='NGC 663 and Companions'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-1726721803179832003</id><published>2011-10-06T10:35:00.003-04:00</published><updated>2011-10-06T21:23:07.219-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Comets'/><title type='text'>Comet Garradd - Sept. 29, 2011</title><content type='html'>Comet C/2009 P1 (Garradd) is a fairly bright and very well placed object for North American astronomers from now through Spring of 2012.  As comets go it is a large object but never comes closer to the Sun than Mars' average distance.  Thus it will be visible for an unusually long time but will never sport much of a tail.  Below is an image of Garradd taken on the evening of September 29, 2011 from the Chiefland Astronomy Village in Chiefland, Florida.  It is composed of multiple 2-minute exposures through LRGB filters.&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/Garradd_09302011.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px"border="0" height="511" width="700" src="http://astro.wt5l.com/astro_images_content/Garradd_09302011.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to view larger version)&lt;/div&gt;&lt;p&gt;&lt;/p&gt;Here is also a very short video of the motion of Garradd over the 90-minute period that I spent imaging the object (10 frames, each frame approximately ten minutes apart).&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;iframe width="420" height="315" src="http://www.youtube.com/embed/J6zu4i0QbTM" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-1726721803179832003?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/1726721803179832003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/10/comet-garradd-sept-29-2011.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/1726721803179832003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/1726721803179832003'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/10/comet-garradd-sept-29-2011.html' title='Comet Garradd - Sept. 29, 2011'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://img.youtube.com/vi/J6zu4i0QbTM/default.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-7480921383364186949</id><published>2011-09-05T16:35:00.000-04:00</published><updated>2011-09-05T16:35:25.528-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Sending Text Messages with Python</title><content type='html'>One of the neat things I like to do with my Astro-Imaging scripts is to have the ability to send periodic status messages to my cell phone.  With this capability, if I am away from my imaging computer, I can always know how imaging is progressing. Also, I can have my script send any error messages that crop up so that I may immediately return to my imaging setup if needed.&lt;br /&gt;&lt;br /&gt;The key to sending text messages is contained in this &lt;a href="http://en.wikipedia.org/wiki/List_of_SMS_gateways"&gt;List of SMS Gateways&lt;/a&gt; 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.&lt;br /&gt;&lt;br /&gt;The listing shown below is coded for my particular service carrier (AT&amp;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 &lt;a href="http://astro.wt5l.com/blogspot/cTexting.zip"&gt;cTexting.zip&lt;/a&gt;.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;from smtplib import SMTP&lt;br /&gt;import datetime&lt;br /&gt;import socket&lt;br /&gt;import traceback&lt;br /&gt;&lt;br /&gt;SMTP_DOMAIN = r'&amp;lt;OUTGOING EMAIL DOMAIN&amp;gt;'&lt;br /&gt;SMTP_USER   = r'&amp;lt;USER NAME&amp;gt;'&lt;br /&gt;SMTP_PASS   = r'&amp;lt;PASSWORD&amp;gt;'&lt;br /&gt;FROM_ADDR   = r'AstroPhoto Update &amp;lt;anyone@gmail.com&amp;gt;'&lt;br /&gt;TO_ADDR     = r'&amp;lt;TEN-DIGIT CELL NUMBER&amp;gt;@txt.att.net'&lt;br /&gt;&lt;br /&gt;socket.setdefaulttimeout(10)&lt;br /&gt;&lt;br /&gt;##--------------------------------------------------------------------------------&lt;br /&gt;## Class: cTextMsg&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cTextMsg:&lt;br /&gt;    __objectName = ''&lt;br /&gt;&lt;br /&gt;    def __init__(self):&lt;br /&gt;        pass&lt;br /&gt;        &lt;br /&gt;    def set_objectName(self, value):&lt;br /&gt;        self.__objectName = value&lt;br /&gt;        return&lt;br /&gt;    &lt;br /&gt;    def get_objectName(self):&lt;br /&gt;        return self.__objectName&lt;br /&gt;&lt;br /&gt;    def sendSMSMessage(self,message):&lt;br /&gt;        smtp = SMTP()&lt;br /&gt;        smtp.set_debuglevel(0)&lt;br /&gt;        &lt;br /&gt;        subject = self.get_objectName()&lt;br /&gt;        date = datetime.datetime.now().strftime('%d/%m/%Y %H:%M')&lt;br /&gt;&lt;br /&gt;        msg = 'From: %s\nTo: %s\nSubject: %s\nDate: %s\n\n%s' % \&lt;br /&gt;                (FROM_ADDR,TO_ADDR,subject,date,message)&lt;br /&gt;        &lt;br /&gt;        try:&lt;br /&gt;            smtp.connect(SMTP_DOMAIN,0)&lt;br /&gt;            smtp.login(SMTP_USER,SMTP_PASS)&lt;br /&gt;            smtp.sendmail(FROM_ADDR,TO_ADDR,msg)&lt;br /&gt;            print "SMS Message Sent Successfully"&lt;br /&gt;            smtp.quit()&lt;br /&gt;        except socket.timeout:&lt;br /&gt;            print "ERROR: Timeout sending SMS Message"&lt;br /&gt;        except:&lt;br /&gt;            print "ERROR: Unable to send SMS Message"&lt;br /&gt;            print '!'*60&lt;br /&gt;            print traceback.format_exc()&lt;br /&gt;        &lt;br /&gt;##&lt;br /&gt;##    END OF 'cTextMsg' Class&lt;br /&gt;##&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;    &lt;br /&gt;    email_msg = cTextMsg()&lt;br /&gt;    email_msg.set_objectName('M31')&lt;br /&gt;    print 'Object Name: ' + email_msg.get_objectName()&lt;br /&gt;    email_msg.sendSMSMessage('This is a sample message from an imaging session.')&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-7480921383364186949?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/7480921383364186949/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/09/sending-text-messages-with-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7480921383364186949'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7480921383364186949'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/09/sending-text-messages-with-python.html' title='Sending Text Messages with Python'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-4235642024295782252</id><published>2011-09-03T17:24:00.031-04:00</published><updated>2011-09-04T12:31:20.915-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Amateur Radio'/><title type='text'>Vertical Antenna Tilt Mount</title><content type='html'>For the past 10+ years (which covers almost the entire span of my amateur radio career) my main antenna has been a GAP Titan DX 8-band vertical antenna.  This versatile antenna weighs in at around 25 lbs. and has a height of about 25 feet.  For most of this time the antenna has been mounted on one corner of a second floor wooden deck and was very difficult to raise and lower.  In fact, the antenna was only lowered once, in September 2004, when our area was hit by two hurricanes in a three-week period.  Consequently, I have not been able to easily check the condition of the antenna and perform some needed periodic maintenance.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-_TTMLboO3is/TmKNGexh3aI/AAAAAAAAAAg/y9AmifeDsHI/s1600/tilt_mount01.JPG" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="400" width="212" src="http://3.bp.blogspot.com/-_TTMLboO3is/TmKNGexh3aI/AAAAAAAAAAg/y9AmifeDsHI/s400/tilt_mount01.JPG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-T6eZoUa0KqI/TmKURF83o-I/AAAAAAAAAAo/s0MwEZ5hyWY/s1600/tilt_mount02.JPG" imageanchor="1" style="clear:right; float:right; margin-left:1em; margin-bottom:1em"&gt;&lt;img border="0" height="400" width="217" src="http://4.bp.blogspot.com/-T6eZoUa0KqI/TmKURF83o-I/AAAAAAAAAAo/s0MwEZ5hyWY/s400/tilt_mount02.JPG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;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. &lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-LZo54ew4dL0/TmKX1kFOlRI/AAAAAAAAAAw/7andcWo_xKo/s1600/tilt_mount03.JPG" imageanchor="1" style="clear:left; float:left;margin-right:1em; margin-bottom:1em"&gt;&lt;img border="0" height="400" width="176" src="http://1.bp.blogspot.com/-LZo54ew4dL0/TmKX1kFOlRI/AAAAAAAAAAw/7andcWo_xKo/s400/tilt_mount03.JPG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-t9nNWisplyU/TmKY_9fhW7I/AAAAAAAAABA/krHUdv3B-cM/s1600/tilt_mount04.JPG" imageanchor="1" style="clear:right; float:right; margin-left:1em; margin-bottom:1em"&gt;&lt;img border="0" height="333" width="400" src="http://1.bp.blogspot.com/-t9nNWisplyU/TmKY_9fhW7I/AAAAAAAAABA/krHUdv3B-cM/s400/tilt_mount04.JPG" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-4235642024295782252?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/4235642024295782252/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/09/vertical-antenna-tilt-mount.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4235642024295782252'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4235642024295782252'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/09/vertical-antenna-tilt-mount.html' title='Vertical Antenna Tilt Mount'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-_TTMLboO3is/TmKNGexh3aI/AAAAAAAAAAg/y9AmifeDsHI/s72-c/tilt_mount01.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-3305860307656551601</id><published>2011-08-30T19:06:00.000-04:00</published><updated>2011-08-30T19:06:29.650-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DXCC'/><category scheme='http://www.blogger.com/atom/ns#' term='Amateur Radio'/><title type='text'>LoTW Application Complete!</title><content type='html'>&lt;p&gt;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 &lt;a href="http://radio.wt5l.com/dxcc.php"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;table style="padding-left: 30px" width=75% border="0px"&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;Mixed&lt;/span&gt;&lt;/td&gt; &lt;td&gt;157 Confirmed&lt;/td&gt; &lt;td&gt;157 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;CW&lt;/span&gt;&lt;/td&gt; &lt;td&gt;127 Confirmed&lt;/td&gt; &lt;td&gt;127 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;Phone&lt;/span&gt;&lt;/td&gt; &lt;td&gt;131 Confirmed&lt;/td&gt; &lt;td&gt;131 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;Digital&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&amp;nbsp;78 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;78 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;80m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&amp;nbsp;20 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;20 Awarded&lt;/td&gt; &lt;td&gt;&amp;nbsp;0 Pending&lt;/td&gt; &lt;td&gt;&amp;nbsp;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;40m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&amp;nbsp;92 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;92 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;30m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&amp;nbsp;&amp;nbsp;5 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;&amp;nbsp;5 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;20m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;117 Confirmed&lt;/td&gt; &lt;td&gt;117 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;17m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&amp;nbsp;&amp;nbsp;3 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;&amp;nbsp;3 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;15m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;129 Confirmed&lt;/td&gt; &lt;td&gt;129 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;12m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;&amp;nbsp;&amp;nbsp;6 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;&amp;nbsp;6 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td&gt;&lt;span style="text-decoration: underline;"&gt;10m&lt;/span&gt;&lt;/td&gt; &lt;td&gt;104 Confirmed&lt;/td&gt; &lt;td&gt;&amp;nbsp;104 Awarded&lt;/td&gt; &lt;td&gt;0 Pending&lt;/td&gt; &lt;td&gt;&amp;nbsp;(0 LotW, 0 Paper)&lt;/td&gt; &lt;/tr&gt;&lt;/table&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-3305860307656551601?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/3305860307656551601/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/08/lotw-application-complete.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/3305860307656551601'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/3305860307656551601'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/08/lotw-application-complete.html' title='LoTW Application Complete!'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8383901166392469797</id><published>2011-08-12T16:27:00.006-04:00</published><updated>2011-08-12T16:31:17.833-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>Starts With A Bang! and Messier 29</title><content type='html'>&lt;p&gt;One of my favorite science blogs is &lt;a href="http://scienceblogs.com/startswithabang/"&gt;Starts With A Bang!&lt;/a&gt; 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 &lt;a href="http://scienceblogs.com/startswithabang/2011/08/if_i_had_a_telescope_this_summ.php"&gt;"If I had a Telescope this Summer..."&lt;/a&gt;.  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 &lt;a href="http://astro.wt5l.com/astro_images_content/m29_09112010.jpg"&gt;here&lt;/a&gt; for a larger version of the photo.)&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/astro_images_content/m29_09112010_red.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img style="border: 3px solid #0e0e0e; padding: 2px" border="0" height="435" width="629" src="http://astro.wt5l.com/astro_images_content/m29_09112010_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8383901166392469797?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8383901166392469797/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/08/starts-with-bang-and-messier-29.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8383901166392469797'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8383901166392469797'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/08/starts-with-bang-and-messier-29.html' title='Starts With A Bang! and Messier 29'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-7951690166183854974</id><published>2011-07-17T15:01:00.000-04:00</published><updated>2011-07-17T15:01:19.784-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='DXCC'/><category scheme='http://www.blogger.com/atom/ns#' term='Amateur Radio'/><title type='text'>Updated DXCC Status</title><content type='html'>&lt;p&gt;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 &lt;a href="http://radio.wt5l.com/dxcc.php"&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;table style="padding-left: 30px" width=75% border="0px"&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;Mixed&lt;/span&gt;&lt;/td&gt;&lt;td&gt;157 Confirmed&lt;/td&gt;&lt;td&gt;135 Awarded&lt;/td&gt;&lt;td&gt;22 Pending&lt;/td&gt;&lt;td&gt;(18 LotW, 4 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;CW&lt;/span&gt;&lt;/td&gt;&lt;td&gt;127 Confirmed&lt;/td&gt;&lt;td&gt;100 Awarded&lt;/td&gt;&lt;td&gt;27 Pending&lt;/td&gt;&lt;td&gt;(24 LotW, 3 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;Phone&lt;/span&gt;&lt;/td&gt;&lt;td&gt;131 Confirmed&lt;/td&gt;&lt;td&gt;118 Awarded&lt;/td&gt;&lt;td&gt;13 Pending&lt;/td&gt;&lt;td&gt;&amp;nbsp;(8 LotW, 5 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;RTTY&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&amp;nbsp;78 Confirmed&lt;/td&gt;&lt;td&gt;&amp;nbsp;66 Awarded&lt;/td&gt;&lt;td&gt;12 Pending&lt;/td&gt;&lt;td&gt;(12 LotW, 0 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;80m&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&amp;nbsp;20 Confirmed&lt;/td&gt;&lt;td&gt;&amp;nbsp;18 Awarded&lt;/td&gt;&lt;td&gt;&amp;nbsp;2 Pending&lt;/td&gt;&lt;td&gt;&amp;nbsp;(2 LotW, 0 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;40m&lt;/span&gt;&lt;/td&gt;&lt;td&gt;&amp;nbsp;92 Confirmed&lt;/td&gt;&lt;td&gt;&amp;nbsp;70 Awarded&lt;/td&gt;&lt;td&gt;22 Pending&lt;/td&gt;&lt;td&gt;(21 LotW, 1 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;20m&lt;/span&gt;&lt;/td&gt;&lt;td&gt;117 Confirmed&lt;/td&gt;&lt;td&gt;102 Awarded&lt;/td&gt;&lt;td&gt;15 Pending&lt;/td&gt;&lt;td&gt;(14 LotW, 1 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;15m&lt;/span&gt;&lt;/td&gt;&lt;td&gt;129 Confirmed&lt;/td&gt;&lt;td&gt;112 Awarded&lt;/td&gt;&lt;td&gt;17 Pending&lt;/td&gt;&lt;td&gt;(15 LotW, 2 Card)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;span style="text-decoration: underline;"&gt;10m&lt;/span&gt;&lt;/td&gt;&lt;td&gt;104 Confirmed&lt;/td&gt;&lt;td&gt;&amp;nbsp;93 Awarded&lt;/td&gt;&lt;td&gt;11 Pending&lt;/td&gt;&lt;td&gt;&amp;nbsp;(7 LotW, 4 Card)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-7951690166183854974?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/7951690166183854974/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/07/updated-dxcc-status.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7951690166183854974'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7951690166183854974'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/07/updated-dxcc-status.html' title='Updated DXCC Status'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-2246213174505228874</id><published>2011-07-10T14:20:00.002-04:00</published><updated>2011-07-10T16:17:08.187-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 7</title><content type='html'>&lt;b&gt;FOCUSING WITH FOCUSMAX&lt;/b&gt;&lt;br /&gt;Achieving great focus is undoubtedly one of the most critical operations for any astronomical imaging application.  Using the free application, &lt;a href="http://users.bsdwebsolutions.com/~larryweber/"&gt;FocusMax&lt;/a&gt;, 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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import time&lt;br /&gt;import pythoncom&lt;br /&gt;import win32com.client&lt;br /&gt;&lt;br /&gt;FOCUSMAXTIME = 150&lt;br /&gt;LASTFOCHFD = -1&lt;br /&gt;&lt;br /&gt;ERROR   = True&lt;br /&gt;NOERROR = False&lt;br /&gt;&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cFocuser&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cFocuser:&lt;br /&gt;    def __init__(self,printlog):&lt;br /&gt;        printlog.log(0,"Connecting to FocusMax...")&lt;br /&gt;        self.__FM = win32com.client.Dispatch("FocusMax.FocusControl")&lt;br /&gt;&lt;br /&gt;    def checkFocusStar(self,printlog):&lt;br /&gt;        printlog.log(0,"Checking focus by measuring HFD...")&lt;br /&gt;        HFD = []&lt;br /&gt;        count = 0&lt;br /&gt;        # take 7 HFD measurements, sort the list, report the median value&lt;br /&gt;        while count &lt; 7:&lt;br /&gt;            busy = self.__FM.SingleExposeAsyncStatus        &lt;br /&gt;            self.__FM.SingleExposeAsync()&lt;br /&gt;            # wait for measurement to end&lt;br /&gt;            startTime = time.time()&lt;br /&gt;            busy = -1&lt;br /&gt;            while busy == -1 and (time.time() - startTime) &lt; FOCUSMAXTIME:&lt;br /&gt;                time.sleep(1)&lt;br /&gt;                busy = self.__FM.SingleExposeAsyncStatus&lt;br /&gt;            if self.__FM.SingleExposeAsyncStatus != 1:&lt;br /&gt;                printlog.log(0,"HFD Measurement failed or timed out")&lt;br /&gt;                return -1        &lt;br /&gt;            result = self.__FM.HalfFluxDiameter&lt;br /&gt;            flux = self.__FM.TotalFlux&lt;br /&gt;            time.sleep(0.5)&lt;br /&gt;            printlog.log(1,"HFD Measurement: %0.2f  Total Flux: %d" %&lt;br /&gt;                         (result,flux))&lt;br /&gt;            if flux &gt; 20000:&lt;br /&gt;                HFD.append(result)&lt;br /&gt;                count = count + 1&lt;br /&gt;        HFD.sort()&lt;br /&gt;        j = len(HFD)&lt;br /&gt;        if not j%2:&lt;br /&gt;            median = (HFD[(j/2)-1]+HFD[j/2])/2.0&lt;br /&gt;        else:&lt;br /&gt;            median = HFD[j/2]&lt;br /&gt;        focuserPos = self.__FM.Position&lt;br /&gt;        printlog.log(0,"Median HFD = %0.2f at Pos = %d" % (median,focuserPos))&lt;br /&gt;        return median&lt;br /&gt;&lt;br /&gt;    def focusStar(self,printlog):&lt;br /&gt;        global LASTFOCHFD&lt;br /&gt;        printlog.log(0,"Starting FocusMax Focus method...")&lt;br /&gt;        try:&lt;br /&gt;            busy = self.__FM.FocusAsyncStatus        &lt;br /&gt;            start = self.__FM.FocusAsync()&lt;br /&gt;        except pythoncom.com_error, (hr, msg, exc, arg):&lt;br /&gt;            printlog.log(0,"ERROR: %s" % exc[2])&lt;br /&gt;            return ERROR&lt;br /&gt;        else:&lt;br /&gt;            if start:&lt;br /&gt;                # wait for focus method to end&lt;br /&gt;                startTime = time.time()&lt;br /&gt;                busy = -1&lt;br /&gt;                while busy == -1 and (time.time() - startTime) &lt; FOCUSMAXTIME:&lt;br /&gt;                    time.sleep(1)&lt;br /&gt;                    busy = self.__FM.FocusAsyncStatus&lt;br /&gt;                if self.__FM.FocusAsyncStatus != 1:&lt;br /&gt;                    printlog.log(0,"Focus method failed or timed out.")&lt;br /&gt;                    return ERROR        &lt;br /&gt;                printlog.log(0,"FocusMax Focus method complete...")&lt;br /&gt;                tgtHFD = self.__FM.HalfFluxDiameter&lt;br /&gt;                if tgtHFD &gt; 0.5:&lt;br /&gt;                    median = self.checkFocusStar(printlog)&lt;br /&gt;                    if median == -1:&lt;br /&gt;                        return ERROR&lt;br /&gt;                    elif median &lt; 0.5 or median &gt; 4.0:&lt;br /&gt;                        printlog.log(0,"ERROR: Focus results out of range.")&lt;br /&gt;                        return ERROR&lt;br /&gt;                    else:&lt;br /&gt;                        LASTFOCHFD = median&lt;br /&gt;                        return NOERROR&lt;br /&gt;                else:&lt;br /&gt;                    printlog.log(0,"ERROR: Focus method failed.")&lt;br /&gt;                    return ERROR&lt;br /&gt;            else:&lt;br /&gt;                printlog.log(0,"ERROR: FocusMax Focus method failed to start.")&lt;br /&gt;                return ERROR&lt;br /&gt;&lt;br /&gt;##&lt;br /&gt;## END OF 'cFocuser' Class&lt;br /&gt;##&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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'.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;UNIT TESTING&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-2246213174505228874?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/2246213174505228874/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/07/automated-astrophotography-with-python.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2246213174505228874'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2246213174505228874'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/07/automated-astrophotography-with-python.html' title='Automated Astrophotography with Python - Part 7'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8214029986258774718</id><published>2011-06-28T08:30:00.024-04:00</published><updated>2011-06-28T09:52:42.183-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>M21 - Open Cluster</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center"&gt;&lt;a href="http://astro.wt5l.com/m21_01.php" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px" border="0" height="435" width="629" src="http://astro.wt5l.com/astro_images_content/m20m21_06052011_m21_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to go to info page)&lt;/div&gt;&lt;br /&gt;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. &lt;i&gt;"The Messier Objects".&lt;/i&gt; Cambridge: University of Cambridge and Sky Publishing, 1998. Print.]&lt;br /&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/m21_01.php"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8214029986258774718?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8214029986258774718/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/m21-open-cluster.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8214029986258774718'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8214029986258774718'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/m21-open-cluster.html' title='M21 - Open Cluster'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-446263621195687308</id><published>2011-06-27T08:05:00.005-04:00</published><updated>2011-06-28T09:54:35.239-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>M20 - Trifid Nebula</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/m20_01.php" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img style="border: 3px solid #0e10ff; padding: 2px" border="0" height="435" width="629" src="http://astro.wt5l.com/astro_images_content/m20m21_06052011_m20_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to go to info page)&lt;/div&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/astro_images_content/m20m21_06052011.jpg"&gt;here&lt;/a&gt;, 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. &lt;i&gt;"The Messier Objects".&lt;/i&gt; Cambridge: University of Cambridge and Sky Publishing, 1998. Print.]&lt;br /&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/m20_01.php"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-446263621195687308?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/446263621195687308/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/m20-trifid-nebula.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/446263621195687308'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/446263621195687308'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/m20-trifid-nebula.html' title='M20 - Trifid Nebula'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-2518789960306527243</id><published>2011-06-26T15:15:00.003-04:00</published><updated>2011-07-11T08:05:42.421-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 6</title><content type='html'>&lt;b&gt;PLATE SOLVING WITH CCDSOFT AND THE SKY6&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;PLATESOLVE_PATH = r"c:\\imaging scripts\\plate_solve_images\\"&lt;/pre&gt;Also, two new methods are added to the 'cCamera' class. These new methods are shown below:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;def exposePlateSolve(self,length,filterSlot,name,printlog):&lt;br /&gt;    self.__CAMERA.Expose(length,1,filterSlot)&lt;br /&gt;    while not self.__CAMERA.ImageReady:&lt;br /&gt;        time.sleep(0.5)&lt;br /&gt;    # save exposure into plate solve folder&lt;br /&gt;    printlog.log(0,"Saving plate solve image...")&lt;br /&gt;    self.__CAMERA.SaveImage(PLATESOLVE_PATH + name + "_platesolve.fit")&lt;br /&gt;&lt;br /&gt;def setQuarterSubFrame(self):&lt;br /&gt;    self.__CAMERA.StartX = self.CAMERA.CameraXSize / 4&lt;br /&gt;    self.__CAMERA.StartY = self.CAMERA.CameraYSize / 4&lt;br /&gt;    self.__CAMERA.NumX = self.CAMERA.CameraXSize / 2&lt;br /&gt;    self.__CAMERA.NumY = self.CAMERA.CameraYSize / 2&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import pythoncom&lt;br /&gt;import win32com.client&lt;br /&gt;&lt;br /&gt;IMAGESCALE = 1.808&lt;br /&gt;PLATESOLVE_PATH = r"c:\\Imaging_Scripts\\plate_solve_images\\"&lt;br /&gt;&lt;br /&gt;class cPlateSolver:&lt;br /&gt;    def __init__(self,printlog):&lt;br /&gt;        printlog.log(0,"Connecting to CCDSoft...")&lt;br /&gt;        self.__IMAGE = win32com.client.Dispatch("CCDSoft.Image")&lt;br /&gt;&lt;br /&gt;    def insertWCS(self,name,targetRA,targetDec,mount,printlog):&lt;br /&gt;        plateSolveResults = []&lt;br /&gt;        # open platesolve.fit in CCDSoft and perform plate solve&lt;br /&gt;        self.__IMAGE.Path = PLATESOLVE_PATH + name + "_platesolve.fit"&lt;br /&gt;        self.__IMAGE.Open()&lt;br /&gt;        # set image scale in CCDSoft&lt;br /&gt;        self.__IMAGE.ScaleInArcsecondsPerPixel = IMAGESCALE&lt;br /&gt;        try:&lt;br /&gt;            self.__IMAGE.InsertWCS()&lt;br /&gt;        except pythoncom.com_error, (hr, msg, exc, arg):&lt;br /&gt;            errorstr = exc[2]&lt;br /&gt;            errorstr = errorstr.replace('\n',' ')&lt;br /&gt;            printlog.log(0,"Plate Solve %s" % errorstr)&lt;br /&gt;            self.__IMAGE.Close()&lt;br /&gt;            plateSolveResults = [0.0,0.0,-1,-1]&lt;br /&gt;        else:&lt;br /&gt;            # if no errors, report results&lt;br /&gt;            printlog.log(0,"Plate Solve Results:")&lt;br /&gt;            printlog.log(1,"Image Scale: %0.3f asp" % IMAGESCALE)&lt;br /&gt;            printlog.log(1,"North Angle: %0.2f deg" % self.__IMAGE.NorthAngle)&lt;br /&gt;            # get 2000 Epoch RA and Dec of center of plate solved image&lt;br /&gt;            RADec = self.__IMAGE.XYToRADec(self.__IMAGE.Width/2.0,&lt;br /&gt;                                         self.__IMAGE.Height/2.0)&lt;br /&gt;            plateSolveResults.append(RADec[0])&lt;br /&gt;            plateSolveResults.append(RADec[1])&lt;br /&gt;            J2000RA = mount.getAngleToDMS(RADec[0])&lt;br /&gt;            printlog.log(1,"Solved RA (J2000) : %02dh %02dm %0.2fs" % \&lt;br /&gt;                         (J2000RA[0],J2000RA[1],J2000RA[2]))&lt;br /&gt;            J2000DEC = mount.getAngleToDMS(RADec[1])&lt;br /&gt;            printlog.log(1,"Solved DEC (J2000): %02d deg %02d' %0.2f\"" % \&lt;br /&gt;                         (J2000DEC[0],J2000DEC[1],J2000DEC[2]))&lt;br /&gt;            # Compute angular separation and position angle from&lt;br /&gt;            # plate solved location to target location&lt;br /&gt;            angSeparation = mount.getAngularSeparation(targetRA,targetDec,&lt;br /&gt;                                                       RADec[0],RADec[1])&lt;br /&gt;            positionAngle = mount.getPositionAngle(targetRA,targetDec,&lt;br /&gt;                                                   RADec[0],RADec[1])&lt;br /&gt;            angSeparation = angSeparation * 3600&lt;br /&gt;            plateSolveResults.append(angSeparation)&lt;br /&gt;            plateSolveResults.append(positionAngle)&lt;br /&gt;            printlog.log(1,"Pointing error = %0.2f arcsec at %0.2f deg" % \&lt;br /&gt;                         (angSeparation,positionAngle))&lt;br /&gt;            self.__IMAGE.Close()&lt;br /&gt;        return plateSolveResults        &lt;br /&gt;&lt;br /&gt;    def execPlateSolve(self,camera,mount,name,targetRA,targetDec,printlog,&lt;br /&gt;                       mode="Image"):&lt;br /&gt;        # set binning to unbinned&lt;br /&gt;        printlog.log(0,"Setting imager bin mode to 1x1")&lt;br /&gt;        camera.setBinning(1,printlog)&lt;br /&gt;        # set up camera for quarter-size subframe in center&lt;br /&gt;        camera.setQuarterSubFrame()&lt;br /&gt;        # take 10 second exposure&lt;br /&gt;        printlog.log(0,"Exposing 10 second image for Plate Solve...")&lt;br /&gt;        if mode.upper() == "IMAGE":&lt;br /&gt;            camera.exposePlateSolve(10.0,0,PLATESOLVE_PATH+name,printlog)&lt;br /&gt;        else:&lt;br /&gt;            camera.exposePlateSolve(10.0,0,PLATESOLVE_PATH+name+'_test',&lt;br /&gt;                                    printlog)&lt;br /&gt;        #insert WCS&lt;br /&gt;        printlog.log(0,"Performing plate solve with CCDSoft and The Sky6...")&lt;br /&gt;        return self.insertWCS(name,targetRA,targetDec,mount,printlog)&lt;br /&gt;            &lt;br /&gt;##&lt;br /&gt;## END OF 'cPlateSolver' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;The unit test for this class demonstrates use of the methods in the 'cPlateSolver' class and is shown below:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == '__main__':&lt;br /&gt;&lt;br /&gt;    import cLogger_6&lt;br /&gt;    import cCamera_6&lt;br /&gt;    import cMount_6&lt;br /&gt;&lt;br /&gt;    # set up path and name of the logfile&lt;br /&gt;    cCameraLogFile = r"C:\Imaging_Scripts\logfiles\cplatesolver.log"&lt;br /&gt;&lt;br /&gt;    # instantiate the class with the path and name&lt;br /&gt;    printer = cLogger_6.cLogger(cCameraLogFile)&lt;br /&gt;    printer.logHeader('Unit Test cPlateSolver Class')&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cCamera class&lt;br /&gt;    testCamera = cCamera_6.cCamera(printer)&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cMount class&lt;br /&gt;    testMount = cMount_6.cMount(printer)&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cPlateSolver class&lt;br /&gt;    testPlateSolve = cPlateSolver(printer)&lt;br /&gt;&lt;br /&gt;    objects = ['M92','M107']&lt;br /&gt;    for obj in objects:&lt;br /&gt;        printer.log(0,"")&lt;br /&gt;        if not testMount.findObject(obj):&lt;br /&gt;            printer.log(0,"%s located" % (obj,))&lt;br /&gt;            coords = testMount.getCoordinates()&lt;br /&gt;            targetRA = coords['RA J2000']&lt;br /&gt;            targetDec = coords['DEC J2000']&lt;br /&gt;            DMScoords = testMount.getAngleToDMS(targetRA)&lt;br /&gt;            printer.log(0,"%s RA  (J2000) = %02.0fh %02.0fm %02.0fs" % \&lt;br /&gt;                        (obj,DMScoords[0],DMScoords[1],DMScoords[2]))&lt;br /&gt;            DMScoords = testMount.getAngleToDMS(targetDec)&lt;br /&gt;            printer.log(0,"%s Dec (J2000) = %02.0fd %02.0f' %02.0f\"" % \&lt;br /&gt;                        (obj,DMScoords[0],DMScoords[1],DMScoords[2]))&lt;br /&gt;            psResults = testPlateSolve.execPlateSolve(testCamera,testMount,&lt;br /&gt;                                                      obj,targetRA,targetDec,&lt;br /&gt;                                                      printer,'test')&lt;br /&gt;        else:&lt;br /&gt;            printer.log(0,"%s could not be found." % obj)&lt;br /&gt;&lt;br /&gt;    printer.logFooter()&lt;br /&gt;    printer.blank()&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;TESTING THE CLASSES&lt;/b&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/blogspot/chapter_6.zip"&gt;chapter_6.zip&lt;/a&gt;. Note: the .fit files included in the .zip must be placed in the same directory that the plate solve images are saved to.&lt;br /&gt;&lt;br /&gt;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).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/07/automated-astrophotography-with-python.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-2518789960306527243?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/2518789960306527243/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_26.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2518789960306527243'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2518789960306527243'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_26.html' title='Automated Astrophotography with Python - Part 6'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-6568387022858321738</id><published>2011-06-21T11:00:00.000-04:00</published><updated>2011-06-21T11:00:03.500-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>M5 - Globular Cluster Now In Color</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/m5_02.php" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="435" width="629" src="http://astro.wt5l.com/astro_images_content/m5_06042011_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to go to info page)&lt;/div&gt;&lt;br /&gt;As described in a previous post found &lt;a href="http://wt5l.blogspot.com/2011/05/m5-globular-cluster.html"&gt;here&lt;/a&gt;, 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 &lt;a href="http://bf-astro.com/eXcalibrator/excalibrator.htm"&gt;eXcalibrator&lt;/a&gt; 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).&lt;br /&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/m5_02.php"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-6568387022858321738?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/6568387022858321738/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/m5-globular-cluster-now-in-color.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/6568387022858321738'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/6568387022858321738'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/m5-globular-cluster-now-in-color.html' title='M5 - Globular Cluster Now In Color'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-3131275369100657669</id><published>2011-06-18T13:35:00.001-04:00</published><updated>2011-07-11T08:07:29.736-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 5</title><content type='html'>&lt;b&gt;LOGGING TO A TEXT FILE&lt;/b&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import time&lt;br /&gt;&lt;br /&gt;##----------------------------------------------------------------------------&lt;br /&gt;## Class: cLogger&lt;br /&gt;##----------------------------------------------------------------------------&lt;br /&gt;class cLogger:&lt;br /&gt;    def __init__(self,filename):&lt;br /&gt;        self.__filename = filename&lt;br /&gt;        self.__level = 0&lt;br /&gt;        self.__echo = True&lt;br /&gt;&lt;br /&gt;    def log(self,level,value,echo=True):&lt;br /&gt;        self.__level = level&lt;br /&gt;        self.__echo = echo&lt;br /&gt;        &lt;br /&gt;        # echo to terminal with leading space if echo = True (default)&lt;br /&gt;        if self.__echo:&lt;br /&gt;            print " " + value&lt;br /&gt;        # build the filename&lt;br /&gt;        iDateTime = time.localtime()&lt;br /&gt;        sDateStr = time.strftime('_%Y%m%d.log', iDateTime)&lt;br /&gt;        # prepend spaces depending on log level (each additional level has two&lt;br /&gt;        # spaces before text, for indenting)&lt;br /&gt;        for i in range(level):&lt;br /&gt;            value = "   " + value&lt;br /&gt;        # build the full screen line including timestamp&lt;br /&gt;        prefix = time.strftime("%H:%M:%S ", time.localtime(time.time()))&lt;br /&gt;        if value.endswith("\n"):&lt;br /&gt;            value = value[:-1]&lt;br /&gt;        value = prefix + value&lt;br /&gt;        # write all entries to detailed log file&lt;br /&gt;        try:&lt;br /&gt;            f = open (self.__filename[:-4] + sDateStr, "a")&lt;br /&gt;            f.write(value)&lt;br /&gt;            f.write("\n")&lt;br /&gt;            f.close()&lt;br /&gt;        except:&lt;br /&gt;            print "Log: UNABLE TO WRITE TO LOG FILE:"&lt;br /&gt;            print "Filename:", self.__filename[:-4] + sDateStr&lt;br /&gt;            print "Log line:", value        &lt;br /&gt;&lt;br /&gt;    def blank(self,echo=True):&lt;br /&gt;        self.__echo = echo&lt;br /&gt;        if self.__echo:&lt;br /&gt;            print " "&lt;br /&gt;        # build the filename&lt;br /&gt;        iDateTime = time.localtime()&lt;br /&gt;        sDateStr = time.strftime('_%Y%m%d.log', iDateTime)&lt;br /&gt;        # write all entries to detailed log file&lt;br /&gt;        try:&lt;br /&gt;            f = open (self.__filename[:-4] + sDateStr, "a")&lt;br /&gt;            f.write("\n")&lt;br /&gt;            f.close()&lt;br /&gt;        except:&lt;br /&gt;            print "Log: UNABLE TO WRITE TO LOG FILE:"&lt;br /&gt;            print "Filename:", self.__filename[:-4] + sDateStr&lt;br /&gt;&lt;br /&gt;    def logHeader(self,strTitle):&lt;br /&gt;        self.log(0,"")&lt;br /&gt;        strVal = strTitle + " Log File"&lt;br /&gt;        self.log(0,strVal,False)&lt;br /&gt;        self.log(0,"",False)&lt;br /&gt;        self.log(0,"*** START OF SCRIPT ***")&lt;br /&gt;&lt;br /&gt;    def logFooter(self):&lt;br /&gt;        self.log(0,"Script complete!")&lt;br /&gt;        self.log(0,"*** END OF SCRIPT ***")&lt;br /&gt;        self.log(0,"")&lt;br /&gt;&lt;br /&gt;##&lt;br /&gt;## END OF 'cLogger' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;The unit test for this class demonstrates use of all methods in the 'cLogger' class and is shown below:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == '__main__':&lt;br /&gt;&lt;br /&gt;    # set up path and name of the logfile&lt;br /&gt;    cLoggerLogFile = r"C:\Imaging_Scripts\logfiles\clogger.log"&lt;br /&gt;&lt;br /&gt;    # instantiate the class with the path and name&lt;br /&gt;    printer = cLogger(cLoggerLogFile)&lt;br /&gt;    &lt;br /&gt;    # print to logfile and to screen&lt;br /&gt;    printer.logHeader('Unit Test for cLogger Class')&lt;br /&gt;    printer.log(0,"Line 1 of status: no indentation")&lt;br /&gt;    printer.log(1,"Line 2 of status: 2 spaces of indentation")&lt;br /&gt;    printer.log(2,"Line 3 of status: 4 spaces of indentation")&lt;br /&gt;    printer.log(3,"Line 4 of status: 6 spaces of indentation")&lt;br /&gt;    # print to logfile only&lt;br /&gt;    printer.log(2,"Line 5 of status: 4 spaces of indentation",False)&lt;br /&gt;    # continue printing to logfile and to screen&lt;br /&gt;    printer.log(1,"Line 6 of status: 2 spaces of indentation")&lt;br /&gt;    printer.log(0,"Line 7 of status: no indentation")&lt;br /&gt;    printer.logFooter()&lt;br /&gt;    printer.blank()&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;LOGGING FROM WITHIN CLASSES&lt;/b&gt;&lt;br /&gt;In 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.)&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cMount&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cMount:&lt;br /&gt;    def __init__(self,&lt;span style="color: lightcyan;"&gt;printlog&lt;/span&gt;):&lt;br /&gt;        self.__info = {}&lt;br /&gt;        self.__objectName = ''&lt;br /&gt;        &lt;span style="color: lightcyan;"&gt;printlog.log(0,"Connecting to The Sky6...")&lt;/span&gt;&lt;br /&gt;        self.__SKYCHART = win32com.client.Dispatch("TheSky6.StarChart")&lt;br /&gt;        self.__SKYINFO = win32com.client.Dispatch("TheSky6.ObjectInformation")&lt;br /&gt;        self.__UTIL = win32com.client.Dispatch("TheSky6.Utils")&lt;br /&gt;        self.__MOUNT = win32com.client.Dispatch("TheSky6.RASCOMTele")&lt;br /&gt;&lt;br /&gt;    def slewToObject(self,obj,&lt;span style="color: lightcyan;"&gt;printlog&lt;/span&gt;,delay=2.0):&lt;br /&gt;        if not self.findObject(obj):&lt;br /&gt;            coords = self.getCoordinates()&lt;br /&gt;            &lt;span style="color: lightcyan;"&gt;printlog.log(0,"Slewing telescope to %s..." % obj)&lt;/span&gt;&lt;br /&gt;            JnowRA = self.__UTIL.ConvertAngleToDMS(coords['RA Now'])&lt;br /&gt;            &lt;span style="color: lightcyan;"&gt;printlog.log(0,"JNow RA : %02dh %02dm %0.2fs" % (JnowRA[0],&lt;br /&gt;                                                             JnowRA[1],&lt;br /&gt;                                                             JnowRA[2]))&lt;/span&gt;&lt;br /&gt;            JnowDEC = self.__UTIL.ConvertAngleToDMS(coords['DEC Now'])&lt;br /&gt;            &lt;span style="color: lightcyan;"&gt;printlog.log(0,"JNow DEC: %02d deg %02d' %0.2f\"" % (JnowDEC[0],&lt;br /&gt;                                                                 JnowDEC[1],&lt;br /&gt;                                                                 JnowDEC[2]))&lt;/span&gt;&lt;br /&gt;            try:&lt;br /&gt;                self.__MOUNT.SlewToRaDec(coords['RA Now'],coords['DEC Now'],obj)&lt;br /&gt;            except:&lt;br /&gt;                &lt;span style="color: lightcyan;"&gt;printlog.log(0,"ERROR: During slew to Object")&lt;/span&gt;&lt;br /&gt;                return ERROR&lt;br /&gt;            else:&lt;br /&gt;                &lt;span style="color: lightcyan;"&gt;printlog.log(0,"Done slewing!")&lt;/span&gt;&lt;br /&gt;                # delay (default = 2.0 seconds)&lt;br /&gt;                time.sleep(delay)&lt;br /&gt;                return NOERROR&lt;br /&gt;        else:&lt;br /&gt;            &lt;span style="color: lightcyan;"&gt;printlog.log(0,"%s could not be found." % obj)&lt;/span&gt;&lt;br /&gt;            return ERROR&lt;br /&gt;&lt;br /&gt;    def slewToAzAlt(self,azimuth,altitude,name,&lt;span style="color: lightcyan;"&gt;printlog&lt;/span&gt;,delay=2.0):&lt;br /&gt;        &lt;span style="color: lightcyan;"&gt;printlog.log(0,"Slewing to Azimuth: %0.1f and Altitude: %0.1f..." % \&lt;br /&gt;                     (azimuth,altitude))&lt;/span&gt;&lt;br /&gt;        try:&lt;br /&gt;            self.__MOUNT.SlewToAzAlt(azimuth,altitude,name)&lt;br /&gt;        except:&lt;br /&gt;            &lt;span style="color: lightcyan;"&gt;printlog.log(0,"ERROR: During slew to altitude/azimuth position")&lt;/span&gt;&lt;br /&gt;            return ERROR&lt;br /&gt;        else:&lt;br /&gt;            &lt;span style="color: lightcyan;"&gt;printlog.log(0,"Done slewing!")&lt;/span&gt;&lt;br /&gt;            # delay (default = 2.0 seconds)&lt;br /&gt;            time.sleep(delay)&lt;br /&gt;            return NOERROR&lt;br /&gt;&lt;/pre&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;    &lt;br /&gt;    import cLogger_5&lt;br /&gt;&lt;br /&gt;    # set up path and name of the logfile&lt;br /&gt;    cMountLogFile = r"C:\Imaging_Scripts\logfiles\cmount.log"&lt;br /&gt;&lt;br /&gt;    # instantiate the class with the path and name&lt;br /&gt;    printer = cLogger_5.cLogger(cMountLogFile)&lt;br /&gt;    printer.logHeader('Unit Test for cMount Class')&lt;br /&gt;&lt;br /&gt;    # create an instance of the cMount object&lt;br /&gt;    testMount = cMount(printer)&lt;br /&gt;&lt;br /&gt;    # prompt for name of object to locate&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input(" Enter first object to slew to: ")&lt;br /&gt;    # test slewToObject()&lt;br /&gt;    testMount.slewToObject(obj,printer)&lt;br /&gt;&lt;br /&gt;    # prompt for name of object to locate&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input(" Enter second object to slew to: ")&lt;br /&gt;    # test slewToObject()&lt;br /&gt;    testMount.slewToObject(obj,printer)&lt;br /&gt;&lt;br /&gt;    # prompt for name of object for sync&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input(" Enter object to sync to: ")&lt;br /&gt;    if not testMount.findObject(obj):&lt;br /&gt;        coords = testMount.getCoordinates()&lt;br /&gt;        # test syncToObject()&lt;br /&gt;        printer.log(0,"Syncing control system to %s" % obj)&lt;br /&gt;        testMount.syncToObject(coords['RA Now'],coords['DEC Now'],obj)&lt;br /&gt;    else:&lt;br /&gt;        printer.log(0,"%s could not be found." % obj)&lt;br /&gt;&lt;br /&gt;    # prompt for azimuth and altitude for slew&lt;br /&gt;    print&lt;br /&gt;    azimuth = raw_input(" Enter azimuth for slew: ")&lt;br /&gt;    azimuth = float(azimuth)&lt;br /&gt;    altitude = raw_input(" Enter altitude for slew: ")&lt;br /&gt;    altitude = float(altitude)&lt;br /&gt;&lt;br /&gt;    # test slewToAzAlt()&lt;br /&gt;    testMount.slewToAzAlt(azimuth,altitude,"park",printer)&lt;br /&gt;&lt;br /&gt;    printer.logFooter()&lt;br /&gt;    printer.blank()&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;TESTING THE CLASSES&lt;/b&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/blogspot/chapter_5.zip"&gt;chapter_5.zip&lt;/a&gt;. 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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_26.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-3131275369100657669?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/3131275369100657669/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_18.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/3131275369100657669'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/3131275369100657669'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_18.html' title='Automated Astrophotography with Python - Part 5'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-5773850467214672955</id><published>2011-06-15T12:20:00.009-04:00</published><updated>2011-06-22T06:32:10.451-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 4</title><content type='html'>&lt;b&gt;USING AN INPUT TEXT FILE&lt;/b&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;#&lt;br /&gt;# Object Command Arguments: [0] = 'OBJECT'&lt;br /&gt;#                           [1] = Object Name&lt;br /&gt;Object m34&lt;br /&gt;#&lt;br /&gt;# Guidestar Command Arguments: [0] = 'GUIDESTAR'&lt;br /&gt;#                              [1] = AUTO -or- MANUAL Guide Star Selection&lt;br /&gt;#                              [2] = Exposure Length in sec. (Auto mode only)&lt;br /&gt;Guidestar Auto 1.0&lt;br /&gt;#&lt;br /&gt;# Exposure Command Arguments: [0] = 'LIGHT'&lt;br /&gt;#                             [1] = Filter (L,R,G,B,HA)&lt;br /&gt;#                             [2] = Binning (Positive Integer)&lt;br /&gt;#                             [3] = Exposure Length (Positive Float)&lt;br /&gt;#                             [4] = No. of Images (Positive Integer)&lt;br /&gt;#            &lt;br /&gt;#&lt;br /&gt;Light L 1 10.25 3&lt;br /&gt;# Light B 2 584 1&lt;br /&gt;# Light G 2 30 2&lt;br /&gt;# Light R 2 357 1&lt;br /&gt;# Other Parameters Command Arguments: [0] = 'OTHERS'&lt;br /&gt;#                                     [1] = CCD Temperature (ex. '-20.0C' or 'Skip')&lt;br /&gt;#                                     [2] = Warm CCD After Imaging? (Y/N)&lt;br /&gt;#                                     [3] = Slew to Safe Position After &lt;br /&gt;#                                           Imaging? (Y/N)&lt;br /&gt;Others -20C Y Y&lt;br /&gt;&lt;/pre&gt;A new class called 'cParser' has been created to parse the text file and strip out commands and the parameters associated with each command. The code listing of the 'cParser' class follows:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import sys&lt;br /&gt;import string&lt;br /&gt;&lt;br /&gt;INPUTPATH = "c:\\Imaging_Scripts\\input_files\\"&lt;br /&gt;COMMANDS = ('OBJECT','LIGHT','OTHERS','GUIDESTAR')&lt;br /&gt;&lt;br /&gt;ERROR = True&lt;br /&gt;NOERROR = False&lt;br /&gt;&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cParser&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cParser:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self.__cmdArgument = []&lt;br /&gt;&lt;br /&gt;    def missing(self,filename):&lt;br /&gt;        try:&lt;br /&gt;            f = open(INPUTPATH + filename)&lt;br /&gt;            f.close()&lt;br /&gt;            return NOERROR&lt;br /&gt;        except IOError:&lt;br /&gt;            return ERROR&lt;br /&gt;&lt;br /&gt;    def parseFile(self,filename):&lt;br /&gt;        if not self.missing(filename):&lt;br /&gt;            f = open(INPUTPATH + filename, "r")&lt;br /&gt;            while True:&lt;br /&gt;                setup = []&lt;br /&gt;                text = f.readline()&lt;br /&gt;                if text == "":&lt;br /&gt;                    f.close()&lt;br /&gt;                    break&lt;br /&gt;                if text[0] == "#":&lt;br /&gt;                    continue&lt;br /&gt;                line = text.split(" ")&lt;br /&gt;                if line[0].upper() not in COMMANDS:&lt;br /&gt;                    print " ERROR: Invalid Command - %s" % line[0].upper()&lt;br /&gt;                    f.close()&lt;br /&gt;                    return None&lt;br /&gt;                else:&lt;br /&gt;                    for i in range(len(line)):&lt;br /&gt;                        line[i] = line[i].replace('\n','')&lt;br /&gt;                        setup.append(line[i].upper())&lt;br /&gt;                self.__cmdArgument.append(setup)&lt;br /&gt;            return self.__cmdArgument&lt;br /&gt;        else:&lt;br /&gt;            print " Error: Input file %s not found" % filename&lt;br /&gt;            return None&lt;br /&gt;&lt;br /&gt;##&lt;br /&gt;## END OF 'cParser' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == '__main__':&lt;br /&gt;&lt;br /&gt;    # instantiate the class&lt;br /&gt;    cmdFile = cParser()&lt;br /&gt;&lt;br /&gt;    # prompt for the filename of the input command file&lt;br /&gt;    print&lt;br /&gt;    filename = raw_input("Enter input command filename: ")&lt;br /&gt;    if not cmdFile.missing(filename):&lt;br /&gt;        cmds = cmdFile.parseFile(filename)&lt;br /&gt;    else:&lt;br /&gt;        print "ERROR: Input command file not found"&lt;br /&gt;        raise EnvironmentError, 'Halting program'&lt;br /&gt;    print&lt;br /&gt;    for cmdsAndParams in cmds:&lt;br /&gt;        print cmdsAndParams&lt;br /&gt;    print&lt;br /&gt;&lt;/pre&gt;The following is a listing of the script that demonstrates how to make use of the input text file.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import cMount_4&lt;br /&gt;import cCamera_4&lt;br /&gt;import cParser_4&lt;br /&gt;&lt;br /&gt;filterDictionary = {'R':0,'G':1,'B':2,'L':3,'HA':4}&lt;br /&gt;&lt;br /&gt;# create an instance of the mount and camera class&lt;br /&gt;testMount = cMount_4.cMount()&lt;br /&gt;testCamera = cCamera_4.cCamera()&lt;br /&gt;cmdFile = cParser_4.cParser()&lt;br /&gt;&lt;br /&gt;# get imaging location&lt;br /&gt;print&lt;br /&gt;print "The Sky6 location is %s" % testMount.getImagingLocation()&lt;br /&gt;&lt;br /&gt;# prompt for the filename of the input command file&lt;br /&gt;print&lt;br /&gt;filename = raw_input("Enter input command filename: ")&lt;br /&gt;if not cmdFile.missing(filename):&lt;br /&gt;    cmds = cmdFile.parseFile(filename)&lt;br /&gt;else:&lt;br /&gt;    print "ERROR: Input command file not found"&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;autoGuide = False&lt;br /&gt;warmCCD = False&lt;br /&gt;slewSafePosition = False&lt;br /&gt;autoGuideStarSelect = False&lt;br /&gt;print&lt;br /&gt;if len(cmds) &gt; 0:&lt;br /&gt;    for stringcmd in cmds:&lt;br /&gt;        if stringcmd[0] == 'OBJECT':&lt;br /&gt;            obj = stringcmd[1]&lt;br /&gt;            print "Object:     %s" % obj&lt;br /&gt;        if stringcmd[0] == 'LIGHT':&lt;br /&gt;            filterName = stringcmd[1]&lt;br /&gt;            binning = stringcmd[2]&lt;br /&gt;            exp = float(stringcmd[3])&lt;br /&gt;            noImages = int(stringcmd[4])&lt;br /&gt;            print "Filter:     %s" % filterName&lt;br /&gt;            print "Binning:    %s" % binning&lt;br /&gt;            print "Exposure:   %02.2f" % exp&lt;br /&gt;            print "No. Images: %d" % noImages&lt;br /&gt;        if stringcmd[0] == 'OTHERS':&lt;br /&gt;            temp = stringcmd[1]&lt;br /&gt;            print "CCD Temp:   %s" % temp&lt;br /&gt;            if stringcmd[2] == 'Y':&lt;br /&gt;                print "Warm CCD?:  Yes"&lt;br /&gt;                warmCCD = True&lt;br /&gt;            else:&lt;br /&gt;                print "Warm CCD?:  No"&lt;br /&gt;            if stringcmd[3] == 'Y':&lt;br /&gt;                print "Slew to Safe Position?: Yes"&lt;br /&gt;                slewSafePosition = True&lt;br /&gt;            else:&lt;br /&gt;                print "Slew to Safe Position?: No"&lt;br /&gt;        if stringcmd[0] == 'GUIDESTAR':&lt;br /&gt;            autoGuide = True&lt;br /&gt;            if stringcmd[1] == 'AUTO':&lt;br /&gt;                autoGuideStarSelect = True&lt;br /&gt;                guideExp = float(stringcmd[2])&lt;br /&gt;&lt;br /&gt;print&lt;br /&gt;testCamera.setCCDTemp(temp)&lt;br /&gt;testCamera.gotoCCDTemp()&lt;br /&gt;&lt;br /&gt;print&lt;br /&gt;if not testMount.findObject(obj):&lt;br /&gt;    if testMount.checkObjectInWest():&lt;br /&gt;        print "%s is west of the meridian" % obj&lt;br /&gt;    else:&lt;br /&gt;        print "%s is east of the meridian" % obj&lt;br /&gt;&lt;br /&gt;    print&lt;br /&gt;    times = testMount.getTimes()&lt;br /&gt;    print "System Date = %s" % times['Date Now']&lt;br /&gt;    print "System Time = %s" % times['Time Now']&lt;br /&gt;    if times.has_key('Rise time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Rise time'])&lt;br /&gt;        print "%s rise time    = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Transit time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Transit time'])&lt;br /&gt;        print "%s transit time = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Set time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Set time'])&lt;br /&gt;        print "%s set time     = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;else:&lt;br /&gt;    print "ERROR: Object not found"&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;#begin test&lt;br /&gt;print&lt;br /&gt;if not testMount.slewToObject(obj):&lt;br /&gt;    # Reset guide star positions for next test&lt;br /&gt;    testCamera.resetGuideStar()&lt;br /&gt;    print&lt;br /&gt;    testCamera.setBinning(int(binning))&lt;br /&gt;    testCamera.setFullFrame()&lt;br /&gt;    fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)&lt;br /&gt;    if autoGuide:&lt;br /&gt;        if autoGuideStarSelect:&lt;br /&gt;            if testCamera.autoGuide(True,guideExp):&lt;br /&gt;                testCamera.stopAutoGuide()&lt;br /&gt;        else:&lt;br /&gt;            if testCamera.autoGuide(False,0.0):&lt;br /&gt;                testCamera.stopAutoGuide()&lt;br /&gt;        # Make sure autoguider is running&lt;br /&gt;        if testCamera.checkGuiderRunning():&lt;br /&gt;            print&lt;br /&gt;            for i in range(noImages):&lt;br /&gt;                testCamera.exposeLight(exp,filterDictionary[filterName.upper()],&lt;br /&gt;                                       fileName)&lt;br /&gt;            # Stop autoguider after all images complete&lt;br /&gt;            testCamera.stopAutoGuide()    &lt;br /&gt;        else:&lt;br /&gt;            print "ERROR - Autoguider not running as expected"&lt;br /&gt;    else:&lt;br /&gt;        print&lt;br /&gt;        for i in range(noImages):&lt;br /&gt;            testCamera.exposeLight(exp,filterDictionary[filterName.upper()],&lt;br /&gt;                                   fileName)&lt;br /&gt;else:&lt;br /&gt;    print "ERROR: During slew to object"&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# warm the CCD to ambient&lt;br /&gt;if warmCCD:&lt;br /&gt;    print&lt;br /&gt;    testCamera.warmCCD()&lt;br /&gt;&lt;br /&gt;# slew to safe position&lt;br /&gt;if slewSafePosition:&lt;br /&gt;    print&lt;br /&gt;    testMount.slewToAzAlt(90.0,75.0,"safe")&lt;br /&gt;&lt;br /&gt;print&lt;br /&gt;print "Script Complete!"&lt;br /&gt;&lt;/pre&gt;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).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/blogspot/chapter_4.zip"&gt;chapter_4.zip&lt;/a&gt;. 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.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_18.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-5773850467214672955?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/5773850467214672955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_15.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/5773850467214672955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/5773850467214672955'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_15.html' title='Automated Astrophotography with Python - Part 4'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-4013413590697056190</id><published>2011-06-11T20:40:00.005-04:00</published><updated>2011-06-17T17:14:48.043-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 3</title><content type='html'>&lt;b&gt;USING CLASSES&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;SETUP&lt;/b&gt;&lt;br /&gt;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 (&lt;a href="http://astro.wt5l.com/blogspot/chapter_3.zip"&gt;chapter_3.zip&lt;/a&gt;) 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): &lt;br /&gt;&lt;ol&gt;&lt;li&gt;Click 'Start' button&lt;/li&gt;&lt;li&gt;Right click on 'Computer' then click on 'Properties'&lt;/li&gt;&lt;li&gt;Click on 'Advanced system settings'&lt;/li&gt;&lt;li&gt;Click on the 'Environment Variables...' button&lt;/li&gt;&lt;li&gt;Under 'User variables for...' click the 'New...' button&lt;/li&gt;&lt;li&gt;In the 'Variable name:' field enter 'PYTHONPATH'&lt;/li&gt;&lt;li&gt;in the 'Variable value:' field enter 'C:\Python27;C:\imaging_scripts' or whatever name you used for your directory&lt;/li&gt;&lt;li&gt;Click the 'OK' button then reboot your computer&lt;/li&gt;&lt;li&gt;Test the path setup by loading the Python IDE. At the '&gt;&gt;&gt;' prompt type 'import cMount_3'. If the IDE comes back with another '&gt;&gt;&gt;' prompt without throwing an exception, the path is set up correctly.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;A listing of the complete script that calls both the 'cMount' and 'cCamera' classes is given below:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import cMount_3&lt;br /&gt;import cCamera_3&lt;br /&gt;&lt;br /&gt;filterDictionary = {'R':0,'G':1,'B':2,'L':3,'HA':4}&lt;br /&gt;&lt;br /&gt;# create an instance of the mount and camera class&lt;br /&gt;testMount = cMount_3.cMount()&lt;br /&gt;testCamera = cCamera_3.cCamera()&lt;br /&gt;&lt;br /&gt;# get imaging location&lt;br /&gt;print&lt;br /&gt;print "The Sky6 location is %s" % testMount.getImagingLocation()&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;# prompt for CCD temperature (type 'skip' if no change to existing temp)&lt;br /&gt;print&lt;br /&gt;temp = raw_input("Enter CCD temperature (ex. '-20C','-5','skip'): ")&lt;br /&gt;testCamera.setCCDTemp(temp)&lt;br /&gt;testCamera.gotoCCDTemp()&lt;br /&gt;&lt;br /&gt;# Test 1 - Slew and image with no autoguiding&lt;br /&gt;# prompt for name of object to locate&lt;br /&gt;print&lt;br /&gt;obj = raw_input("Enter name of object to image: ")&lt;br /&gt;if not testMount.findObject(obj):&lt;br /&gt;    if testMount.checkObjectInWest():&lt;br /&gt;        print "%s is west of the meridian" % obj&lt;br /&gt;    else:&lt;br /&gt;        print "%s is east of the meridian" % obj&lt;br /&gt;&lt;br /&gt;    times = testMount.getTimes()&lt;br /&gt;    print "System Date = %s" % times['Date Now']&lt;br /&gt;    print "System Time = %s" % times['Time Now']&lt;br /&gt;    if times.has_key('Rise time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Rise time'])&lt;br /&gt;        print "%s rise time    = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Transit time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Transit time'])&lt;br /&gt;        print "%s transit time = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Set time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Set time'])&lt;br /&gt;        print "%s set time     = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;&lt;br /&gt;# prompt for length of exposure&lt;br /&gt;exp = raw_input("Enter length of exposure: ")&lt;br /&gt;try:&lt;br /&gt;    exp = float(exp)&lt;br /&gt;except:&lt;br /&gt;    print "ERROR: Invalid exposure length..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for binning of image&lt;br /&gt;binning = raw_input("Enter binning for exposure (1/2/3): ")&lt;br /&gt;if binning in ('1','2','3'):&lt;br /&gt;    try:&lt;br /&gt;        binmode = int(binning)&lt;br /&gt;    except:&lt;br /&gt;        print "ERROR: Invalid binning..."&lt;br /&gt;        raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for number of exposures&lt;br /&gt;noImages = raw_input("Enter the number of exposures: ")&lt;br /&gt;try:&lt;br /&gt;    noImages = int(noImages)&lt;br /&gt;except:&lt;br /&gt;    print "ERROR: Invalid number of images..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for filter&lt;br /&gt;filterName = raw_input("Enter filter for exposures (L/R/G/B/Ha): ")&lt;br /&gt;if not filterName.upper() in ('R','G','B','L','HA'):&lt;br /&gt;    print "ERROR: Invalid filter designation..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# begin test&lt;br /&gt;print&lt;br /&gt;if not testMount.slewToObject(obj):&lt;br /&gt;    testCamera.setBinning(binmode)&lt;br /&gt;    testCamera.setFullFrame()&lt;br /&gt;    fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)&lt;br /&gt;    for i in range(noImages):&lt;br /&gt;        testCamera.exposeLight(exp,filterDictionary[filterName.upper()],&lt;br /&gt;                               fileName)&lt;br /&gt;else:&lt;br /&gt;    print "ERROR: Object not found or error slewing to object"&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;# Test 2 - Slew and image with autoguiding and auto guide star selection&lt;br /&gt;# prompt for name of object to locate&lt;br /&gt;print&lt;br /&gt;obj = raw_input("Enter name of object to image: ")&lt;br /&gt;if not testMount.findObject(obj):&lt;br /&gt;    if testMount.checkObjectInWest():&lt;br /&gt;        print "%s is west of the meridian" % obj&lt;br /&gt;    else:&lt;br /&gt;        print "%s is east of the meridian" % obj&lt;br /&gt;&lt;br /&gt;    times = testMount.getTimes()&lt;br /&gt;    print "System Date = %s" % times['Date Now']&lt;br /&gt;    print "System Time = %s" % times['Time Now']&lt;br /&gt;    if times.has_key('Rise time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Rise time'])&lt;br /&gt;        print "%s rise time    = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Transit time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Transit time'])&lt;br /&gt;        print "%s transit time = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Set time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Set time'])&lt;br /&gt;        print "%s set time     = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;&lt;br /&gt;# prompt for length of exposure&lt;br /&gt;exp = raw_input("Enter length of exposure: ")&lt;br /&gt;try:&lt;br /&gt;    exp = float(exp)&lt;br /&gt;except:&lt;br /&gt;    print "ERROR: Invalid exposure length..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for binning of image&lt;br /&gt;binning = raw_input("Enter binning for exposure (1/2/3): ")&lt;br /&gt;if binning in ('1','2','3'):&lt;br /&gt;    try:&lt;br /&gt;        binmode = int(binning)&lt;br /&gt;    except:&lt;br /&gt;        print "ERROR: Invalid binning..."&lt;br /&gt;        raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for number of exposures&lt;br /&gt;noImages = raw_input("Enter the number of exposures: ")&lt;br /&gt;try:&lt;br /&gt;    noImages = int(noImages)&lt;br /&gt;except:&lt;br /&gt;    print "ERROR: Invalid number of images..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for filter&lt;br /&gt;filterName = raw_input("Enter filter for exposures (L/R/G/B/Ha): ")&lt;br /&gt;if not filterName.upper() in ('R','G','B','L','HA'):&lt;br /&gt;    print "ERROR: Invalid filter designation..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;#begin test&lt;br /&gt;print&lt;br /&gt;if not testMount.slewToObject(obj):&lt;br /&gt;    # Start camera autoguiding with auto guide star select&lt;br /&gt;    # Guide exposure = 2.0 second&lt;br /&gt;    # Reset guide star positions for next test&lt;br /&gt;    testCamera.resetGuideStar()&lt;br /&gt;    if testCamera.autoGuide(True,2.0):&lt;br /&gt;        testCamera.stopAutoGuide()&lt;br /&gt;    # Make sure autoguider is running&lt;br /&gt;    if testCamera.checkGuiderRunning():&lt;br /&gt;        testCamera.setBinning(binmode)&lt;br /&gt;        testCamera.setFullFrame()&lt;br /&gt;        fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)&lt;br /&gt;        for i in range(noImages):&lt;br /&gt;            testCamera.exposeLight(exp,filterDictionary[filterName.upper()],&lt;br /&gt;                                   fileName)&lt;br /&gt;        # Stop autoguider after all images complete&lt;br /&gt;        testCamera.stopAutoGuide()    &lt;br /&gt;    else:&lt;br /&gt;        print "ERROR - Autoguider not running as expected"&lt;br /&gt;else:&lt;br /&gt;    print "ERROR: Object not found or error slewing to object"&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# Test 3 - Slew and image with autoguiding and manual guide star selection&lt;br /&gt;# prompt for name of object to locate&lt;br /&gt;print&lt;br /&gt;obj = raw_input("Enter name of object to image: ")&lt;br /&gt;if not testMount.findObject(obj):&lt;br /&gt;    if testMount.checkObjectInWest():&lt;br /&gt;        print "%s is west of the meridian" % obj&lt;br /&gt;    else:&lt;br /&gt;        print "%s is east of the meridian" % obj&lt;br /&gt;&lt;br /&gt;    times = testMount.getTimes()&lt;br /&gt;    print "System Date = %s" % times['Date Now']&lt;br /&gt;    print "System Time = %s" % times['Time Now']&lt;br /&gt;    if times.has_key('Rise time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Rise time'])&lt;br /&gt;        print "%s rise time    = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Transit time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Transit time'])&lt;br /&gt;        print "%s transit time = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;    if times.has_key('Set time'):&lt;br /&gt;        hms = testMount.getAngleToDMS(times['Set time'])&lt;br /&gt;        print "%s set time     = %02.0f:%02.0f:%02.0f" % (obj,hms[0],hms[1],&lt;br /&gt;                                                          hms[2])&lt;br /&gt;        &lt;br /&gt;# prompt for length of exposure&lt;br /&gt;exp = raw_input("Enter length of exposure: ")&lt;br /&gt;try:&lt;br /&gt;    exp = float(exp)&lt;br /&gt;except:&lt;br /&gt;    print "ERROR: Invalid exposure length..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for binning of image&lt;br /&gt;binning = raw_input("Enter binning for exposure (1/2/3): ")&lt;br /&gt;if binning in ('1','2','3'):&lt;br /&gt;    try:&lt;br /&gt;        binmode = int(binning)&lt;br /&gt;    except:&lt;br /&gt;        print "ERROR: Invalid binning..."&lt;br /&gt;        raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for number of exposures&lt;br /&gt;noImages = raw_input("Enter the number of exposures: ")&lt;br /&gt;try:&lt;br /&gt;    noImages = int(noImages)&lt;br /&gt;except:&lt;br /&gt;    print "ERROR: Invalid number of images..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# prompt for filter&lt;br /&gt;filterName = raw_input("Enter filter for exposures (L/R/G/B/Ha): ")&lt;br /&gt;if not filterName.upper() in ('R','G','B','L','HA'):&lt;br /&gt;    print "ERROR: Invalid filter designation..."&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# begin test&lt;br /&gt;print&lt;br /&gt;if not testMount.slewToObject(obj):&lt;br /&gt;    # Start camera autoguiding with manual guide star select&lt;br /&gt;    # Reset guide star positions for next test&lt;br /&gt;    testCamera.resetGuideStar()&lt;br /&gt;    if testCamera.autoGuide(False,0.0):&lt;br /&gt;        testCamera.stopAutoGuide()&lt;br /&gt;    # Make sure autoguider is running&lt;br /&gt;    if testCamera.checkGuiderRunning():&lt;br /&gt;        testCamera.setBinning(binmode)&lt;br /&gt;        testCamera.setFullFrame()&lt;br /&gt;        fileName = "%s_%s_%sx%s" % (obj,filterName,binning,binning)&lt;br /&gt;        for i in range(noImages):&lt;br /&gt;            testCamera.exposeLight(exp,filterDictionary[filterName.upper()],&lt;br /&gt;                                   fileName)&lt;br /&gt;        # Stop autoguider after all images complete&lt;br /&gt;        testCamera.stopAutoGuide()    &lt;br /&gt;    else:&lt;br /&gt;        print "ERROR - Autoguider not running as expected"&lt;br /&gt;else:&lt;br /&gt;    print "ERROR: Object not found or error slewing to object"&lt;br /&gt;    raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;# warm the CCD to ambient&lt;br /&gt;print&lt;br /&gt;testCamera.warmCCD()&lt;br /&gt;&lt;br /&gt;print&lt;br /&gt;print "Script Complete!"&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/blogspot/chapter_3.zip"&gt;chapter_3.zip&lt;/a&gt;. Experiment with the script by running it multiple times and selecting different objects for The Sky6 to locate, slew to, and image.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_15.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-4013413590697056190?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/4013413590697056190/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_11.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4013413590697056190'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4013413590697056190'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_11.html' title='Automated Astrophotography with Python - Part 3'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-2810935337241516308</id><published>2011-06-09T19:49:00.003-04:00</published><updated>2011-06-17T16:39:58.657-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 2c</title><content type='html'>&lt;b&gt;TELESCOPE CONTROL&lt;/b&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;self.__MOUNT = win32com.client.Dispatch("TheSky6.RASCOMTele")&lt;br /&gt;&lt;/pre&gt;The next listing shows the three new methods added to 'cMount' that call methods from the Sky6 RASCOMTele class:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;def slewToObject(self,obj,delay=2.0):&lt;br /&gt;    if not self.findObject(obj):&lt;br /&gt;        coords = self.getCoordinates()&lt;br /&gt;        print "Slewing telescope to %s..." % obj&lt;br /&gt;        JnowRA = self.__UTIL.ConvertAngleToDMS(coords['RA Now'])&lt;br /&gt;        print "JNow RA : %02dh %02dm %0.2fs" % (JnowRA[0],JnowRA[1],&lt;br /&gt;                                                JnowRA[2])&lt;br /&gt;        JnowDEC = self.__UTIL.ConvertAngleToDMS(coords['DEC Now'])&lt;br /&gt;        print "JNow DEC: %02d deg %02d' %0.2f\"" % (JnowDEC[0],JnowDEC[1],&lt;br /&gt;                                                    JnowDEC[2])&lt;br /&gt;        try:&lt;br /&gt;            self.__MOUNT.SlewToRaDec(coords['RA Now'],coords['DEC Now'],obj)&lt;br /&gt;        except:&lt;br /&gt;            print "ERROR: During slew to Object"&lt;br /&gt;            return ERROR&lt;br /&gt;        else:&lt;br /&gt;            print "Done slewing!"&lt;br /&gt;            # delay (default = 2.0 seconds)&lt;br /&gt;            time.sleep(delay)&lt;br /&gt;            return NOERROR&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj&lt;br /&gt;        return ERROR&lt;br /&gt;    &lt;br /&gt;&lt;br /&gt;def slewToAzAlt(self,azimuth,altitude,name,delay=2.0):&lt;br /&gt;    print "Slewing to Azimuth: %0.1f and Altitude: %0.1f..." % (azimuth,&lt;br /&gt;                                                                altitude)&lt;br /&gt;    try:&lt;br /&gt;        self.__MOUNT.SlewToAzAlt(azimuth,altitude,name)&lt;br /&gt;    except:&lt;br /&gt;        print "ERROR: During slew to altitude/azimuth position"&lt;br /&gt;        return ERROR&lt;br /&gt;    else:&lt;br /&gt;        print "Done slewing!"&lt;br /&gt;        # delay (default = 2.0 seconds)&lt;br /&gt;        time.sleep(delay)&lt;br /&gt;        return NOERROR&lt;br /&gt;&lt;br /&gt;def syncToObject(self,RA,Dec,Obj):&lt;br /&gt;    self.__MOUNT.Sync(RA,Dec,Obj)&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cMOUNT UNIT TEST&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;    &lt;br /&gt;    # create an instance of the cMount object&lt;br /&gt;    testMount = cMount()&lt;br /&gt;&lt;br /&gt;    # prompt for name of object to locate&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input("Enter first object to slew to: ")&lt;br /&gt;    # test slewToObject()&lt;br /&gt;    testMount.slewToObject(obj)&lt;br /&gt;&lt;br /&gt;    # prompt for name of object to locate&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input("Enter second object to slew to: ")&lt;br /&gt;    # test slewToObject()&lt;br /&gt;    testMount.slewToObject(obj)&lt;br /&gt;&lt;br /&gt;    # prompt for name of object for sync&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input("Enter object to sync to: ")&lt;br /&gt;    if not testMount.findObject(obj):&lt;br /&gt;        coords = testMount.getCoordinates()&lt;br /&gt;        # test syncToObject()&lt;br /&gt;        print "Syncing control system to %s" % obj&lt;br /&gt;        testMount.syncToObject(coords['RA Now'],coords['DEC Now'],obj)&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj&lt;br /&gt;&lt;br /&gt;    # prompt for azimuth and altitude for slew&lt;br /&gt;    print&lt;br /&gt;    azimuth = raw_input("Enter azimuth for slew: ")&lt;br /&gt;    azimuth = float(azimuth)&lt;br /&gt;    altitude = raw_input("Enter altitude for slew: ")&lt;br /&gt;    altitude = float(altitude)&lt;br /&gt;&lt;br /&gt;    # test slewToAzAlt()&lt;br /&gt;    testMount.slewToAzAlt(azimuth,altitude,"park")&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;In 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 &lt;a href="http://astro.wt5l.com/blogspot/cMount_2c.zip"&gt;cMount_2c.zip&lt;/a&gt;. 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.)&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_11.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-2810935337241516308?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/2810935337241516308/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_09.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2810935337241516308'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2810935337241516308'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_09.html' title='Automated Astrophotography with Python - Part 2c'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8634822553961788586</id><published>2011-06-08T10:00:00.004-04:00</published><updated>2011-06-17T16:37:49.620-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 2b</title><content type='html'>&lt;b&gt;THE SKY6 UTILITIES&lt;/b&gt;&lt;br /&gt;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:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;self.__UTIL = win32com.client.Dispatch("TheSky6.Utils")&lt;/pre&gt;&lt;br /&gt;The next listing shows the six new methods added to 'cMount' that call methods from the Sky6 UTILS class:&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;def getAngularSeparation(self,RA1,Dec1,RA2,Dec2):&lt;br /&gt;    """Returns results of "ComputeAngularSeparation" method of UTILS"""&lt;br /&gt;    return self.__UTIL.ComputeAngularSeparation(RA1,Dec1,RA2,Dec2)&lt;br /&gt;    &lt;br /&gt;def getPositionAngle(self,RA1,Dec1,RA2,Dec2):&lt;br /&gt;    """Returns results of "ComputePositionAngle" method of UTILS"""&lt;br /&gt;    return self.__UTIL.ComputePositionAngle(RA1,Dec1,RA2,Dec2)&lt;br /&gt;&lt;br /&gt;def getAngleToDMS(self,angle):&lt;br /&gt;    """Returns results of "ConvertAngleToDMS" method of UTILS"""&lt;br /&gt;    return self.__UTIL.ConvertAngleToDMS(angle)&lt;br /&gt;&lt;br /&gt;def get2000ToNow(self,RA,Dec):&lt;br /&gt;    """Returns results of "Precess2000ToNow" method of UTILS"""&lt;br /&gt;    return self.__UTIL.Precess2000ToNow(RA,Dec)&lt;br /&gt;&lt;br /&gt;def getNowTo2000(self,RA,Dec):&lt;br /&gt;    """Returns results of "PrecessNowTo2000" method of UTILS"""&lt;br /&gt;    return self.__UTIL.PrecessNowTo2000(RA,Dec)&lt;br /&gt;&lt;br /&gt;def getRADecToAzAlt(self,RA,Dec):&lt;br /&gt;    """Returns results of "ConvertRADecToAzAlt" method of UTILS"""&lt;br /&gt;    return self.__UTIL.ConvertRADecToAzAlt(RA,Dec)&lt;/pre&gt;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'.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cMOUNT UNIT TEST&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;    &lt;br /&gt;    # create an instance of the cMount object&lt;br /&gt;    testMount = cMount()&lt;br /&gt;&lt;br /&gt;    # test getAngleToDMS()&lt;br /&gt;    obj1 = 'Pollux'&lt;br /&gt;    if not testMount.findObject(obj1):&lt;br /&gt;        coords1 = testMount.getCoordinates()&lt;br /&gt;        print&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['RA Now'])&lt;br /&gt;        print "%s RA  (current epoch) = %02.0fh %02.0fm %03.1fs" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['DEC Now'])&lt;br /&gt;        print "%s Dec (current epoch) = %02.0fd %02.0f' %03.1f\"" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['RA J2000'])&lt;br /&gt;        print "%s RA     (epoch 2000) = %02.0fh %02.0fm %03.1fs" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['DEC J2000'])&lt;br /&gt;        print "%s Dec    (epoch 2000) = %02.0fd %02.0f' %03.1f\"" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj1&lt;br /&gt;&lt;br /&gt;    obj2 = 'Castor'&lt;br /&gt;    if not testMount.findObject(obj2):&lt;br /&gt;        coords2 = testMount.getCoordinates()&lt;br /&gt;        print&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2['RA Now'])&lt;br /&gt;        print "%s RA  (current epoch) = %02.0fh %02.0fm %03.1fs" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2['DEC Now'])&lt;br /&gt;        print "%s Dec (current epoch) = %02.0fd %02.0f' %03.1f\"" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2['RA J2000'])&lt;br /&gt;        print "%s RA     (epoch 2000) = %02.0fh %02.0fm %03.1fs" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2['DEC J2000'])&lt;br /&gt;        print "%s Dec    (epoch 2000) = %02.0fd %02.0f' %03.1f\"" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj2&lt;br /&gt;&lt;br /&gt;    # test getAngularSeparation()&lt;br /&gt;    separation = testMount.getAngularSeparation(coords1['RA Now'],&lt;br /&gt;                    coords1['DEC Now'],coords2['RA Now'],coords2['DEC Now'])&lt;br /&gt;    separation = testMount.getAngleToDMS(separation)&lt;br /&gt;    print&lt;br /&gt;    print "The separation between %s and %s is %02.0f deg %02.0f min" % \&lt;br /&gt;          (obj1,obj2,separation[0],separation[1])&lt;br /&gt;&lt;br /&gt;    # test getPositionAngle()&lt;br /&gt;    angle = testMount.getPositionAngle(coords2['RA Now'],coords2['DEC Now'],&lt;br /&gt;                                       coords1['RA Now'],coords1['DEC Now'])&lt;br /&gt;    angle = testMount.getAngleToDMS(angle)&lt;br /&gt;    print&lt;br /&gt;    print "The postion angle from %s to %s is %02.0f deg %02.0f min" % \&lt;br /&gt;          (obj1,obj2,angle[0],angle[1])&lt;br /&gt;&lt;br /&gt;    obj1 = 'm95'&lt;br /&gt;    if not testMount.findObject(obj1):&lt;br /&gt;        # test get2000ToNow()&lt;br /&gt;        coords1 = testMount.getCoordinates()&lt;br /&gt;        print&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['RA J2000'])&lt;br /&gt;        print "%s RA     (epoch 2000) = %02.0fh %02.0fm %02.0fs" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['DEC J2000'])&lt;br /&gt;        print "%s Dec    (epoch 2000) = %02.0fd %02.0f' %02.0f\"" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;        coords2 = testMount.get2000ToNow(coords1['RA J2000'],coords1['DEC J2000'])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2[0])&lt;br /&gt;        print "%s RA  (current epoch) = %02.0fh %02.0fm %02.0fs" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2[1])&lt;br /&gt;        print "%s Dec (current epoch) = %02.0fd %02.0f' %02.0f\"" % (obj1,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;&lt;br /&gt;        # test getRADecToAzAlt()&lt;br /&gt;        print&lt;br /&gt;        azAlt = testMount.getRADecToAzAlt(coords1['RA J2000'],&lt;br /&gt;                                          coords1['DEC J2000'])&lt;br /&gt;        azimuth = testMount.getAngleToDMS(azAlt[0])&lt;br /&gt;        print "%s Azimuth  = %02.0fd %02.0fm %02.0fs" % (obj1,&lt;br /&gt;                azimuth[0],azimuth[1],azimuth[2])&lt;br /&gt;        altitude = testMount.getAngleToDMS(azAlt[1])&lt;br /&gt;        print "%s Altitude = %02.0fd %02.0fm %02.0fs" % (obj1,&lt;br /&gt;                altitude[0],altitude[1],altitude[2])&lt;br /&gt;        print&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj1&lt;br /&gt;&lt;br /&gt;    obj2 = 'm65'&lt;br /&gt;    if not testMount.findObject(obj2):&lt;br /&gt;        # test getNowTo2000()&lt;br /&gt;        coords1 = testMount.getCoordinates()&lt;br /&gt;        print&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['RA Now'])&lt;br /&gt;        print "%s RA  (current epoch) = %02.0fh %02.0fm %02.0fs" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords1['DEC Now'])&lt;br /&gt;        print "%s Dec (current epoch) = %02.0fd %02.0f' %02.0f\"" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;        coords2 = testMount.getNowTo2000(coords1['RA Now'],coords1['DEC Now'])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2[0])&lt;br /&gt;        print "%s RA     (epoch 2000) = %02.0fh %02.0fm %02.0fs" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;        DMScoords = testMount.getAngleToDMS(coords2[1])&lt;br /&gt;        print "%s Dec    (epoch 2000) = %02.0fd %02.0f' %02.0f\"" % (obj2,&lt;br /&gt;                DMScoords[0],DMScoords[1],DMScoords[2])&lt;br /&gt;&lt;br /&gt;        # test getRADecToAzAlt()&lt;br /&gt;        print&lt;br /&gt;        azAlt = testMount.getRADecToAzAlt(coords2[0],coords2[1])&lt;br /&gt;        azimuth = testMount.getAngleToDMS(azAlt[0])&lt;br /&gt;        print "%s Azimuth  = %02.0fd %02.0fm %02.0fs" % (obj2,&lt;br /&gt;                azimuth[0],azimuth[1],azimuth[2])&lt;br /&gt;        altitude = testMount.getAngleToDMS(azAlt[1])&lt;br /&gt;        print "%s Altitude = %02.0fd %02.0fm %02.0fs" % (obj2,&lt;br /&gt;                altitude[0],altitude[1],altitude[2])&lt;br /&gt;        print&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj2&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;As 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 &lt;a href="http://astro.wt5l.com/blogspot/cMount_2b.zip"&gt;cMount_2b.zip&lt;/a&gt;. Experiment with the script by running it multiple times and selecting different objects for The Sky6 to locate and display information about.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python_09.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8634822553961788586?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8634822553961788586/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8634822553961788586'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8634822553961788586'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python.html' title='Automated Astrophotography with Python - Part 2b'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-4801525466039983775</id><published>2011-05-31T21:07:00.004-04:00</published><updated>2011-06-17T16:41:16.266-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 2a</title><content type='html'>&lt;b&gt;THE cMOUNT CLASS&lt;/b&gt;&lt;br /&gt;The next piece of code we will look at is the 'cMount' class.  This class will interact with various objects exposed by The Sky6 planetarium and telescope control software. The Python code for 'cMount' is shown below. (Note: Docstrings have been removed from this listing for brevity and readability purposes.)&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import time&lt;br /&gt;import win32com.client&lt;br /&gt;&lt;br /&gt;ERROR = True&lt;br /&gt;NOERROR = False&lt;br /&gt;&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cMount&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cMount:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        self.__info = {}&lt;br /&gt;        self.__objectName = ''&lt;br /&gt;        print "Connecting to The Sky6..."&lt;br /&gt;        self.__SKYCHART = win32com.client.Dispatch("TheSky6.StarChart")&lt;br /&gt;        self.__SKYINFO = win32com.client.Dispatch("TheSky6.ObjectInformation")&lt;br /&gt;&lt;br /&gt;    def findObject(self,objToFind):&lt;br /&gt;        self.__objectName = objToFind&lt;br /&gt;        if len(self.__objectName) &amp;gt; 0:&lt;br /&gt;            try:&lt;br /&gt;                self.__SKYCHART.Find(self.__objectName)&lt;br /&gt;                for index in range(189):&lt;br /&gt;                    skyProperty = self.__SKYINFO.Property(index)&lt;br /&gt;                    if self.__SKYINFO.PropertyApplies(index):&lt;br /&gt;                        self.__info[self.__SKYINFO.PropertyName(index)] = \&lt;br /&gt;                                    skyProperty&lt;br /&gt;                return NOERROR     &lt;br /&gt;            except:&lt;br /&gt;                return ERROR&lt;br /&gt;        &lt;br /&gt;    def getCoordinates(self):&lt;br /&gt;        raDec = {}&lt;br /&gt;        if self.__info.has_key('RA (current epoch)'):&lt;br /&gt;            raDec['RA Now'] = self.__info['RA (current epoch)']&lt;br /&gt;        if self.__info.has_key('Dec (current epoch)'):&lt;br /&gt;            raDec['DEC Now'] = self.__info['Dec (current epoch)']&lt;br /&gt;        if self.__info.has_key('RA (epoch 2000)'):&lt;br /&gt;            raDec['RA J2000'] = self.__info['RA (epoch 2000)']&lt;br /&gt;        if self.__info.has_key('Dec (epoch 2000)'):&lt;br /&gt;            raDec['DEC J2000'] = self.__info['Dec (epoch 2000)']&lt;br /&gt;        return raDec&lt;br /&gt;    &lt;br /&gt;    def getAltAzimuth(self):&lt;br /&gt;        altAzimuth = {}&lt;br /&gt;        if self.__info.has_key('Altitude'):&lt;br /&gt;            altAzimuth['Altitude'] = self.__info['Altitude']&lt;br /&gt;        if self.__info.has_key('Azimuth'):&lt;br /&gt;            altAzimuth['Azimuth'] = self.__info['Azimuth']&lt;br /&gt;        return altAzimuth&lt;br /&gt;        &lt;br /&gt;    def getTimes(self):&lt;br /&gt;        objTimes = {}&lt;br /&gt;        if self.__info.has_key('Date'):&lt;br /&gt;            objTimes['Date Now'] = self.__info['Date']&lt;br /&gt;        if self.__info.has_key('Time'):&lt;br /&gt;            objTimes['Time Now'] = self.__info['Time']&lt;br /&gt;        if self.__info.has_key('Transit time'):&lt;br /&gt;            objTimes['Transit time'] = self.__info['Transit time']&lt;br /&gt;        if self.__info.has_key('Rise time'):&lt;br /&gt;            objTimes['Rise time'] = self.__info['Rise time']&lt;br /&gt;        if self.__info.has_key('Set time'):&lt;br /&gt;            objTimes['Set time'] = self.__info['Set time']&lt;br /&gt;        return objTimes&lt;br /&gt;        &lt;br /&gt;    def getImagingLocation(self):&lt;br /&gt;        return self.__SKYCHART.DocumentProperty(63)&lt;br /&gt;&lt;br /&gt;    def checkObjectInWest(self):&lt;br /&gt;        azimuth = self.getAltAzimuth()['Azimuth']&lt;br /&gt;        if azimuth &amp;gt;= 180.00 and azimuth &amp;lt; 360.00:&lt;br /&gt;            return True&lt;br /&gt;        else:&lt;br /&gt;            return False&lt;br /&gt;&lt;br /&gt;##&lt;br /&gt;##    END OF 'cMount' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;The first two lines of the listing are the same 'import' statements that we saw in the 'cCamera' class. The listing for the 'cMount' class follows the 'import' statements and consists of seven methods.  The '__init__' method is the constructor for 'cMount' and is executed whenever an instance of the 'cMount' object is created.  This method creates a class attribute '__info' which is initialized as an empty dictionary data structure and '__objName' which will hold a string representation of the object that The Sky6 will find.  This method also creates a '__SKYCHART' object that is bound to the Sky6's 'Skychart' object and a '__SKYINFO' object that is bound to the Sky6's 'Skyinfo' object. (See help for The Sky6 under scripting for more information regarding attributes and methods relating to these two objects.) &lt;br /&gt;&lt;br /&gt;The next method is 'findObject' which takes as an argument the string representation of the object to find. After making sure the string 'objToFind' is not empty, the Sky6's 'Find' method is used to actually locate the object in its database. If the object is found, the remainder of the method loads all of the properties relating to that object into the '__info' dictionary.  The contents of this dictionary will be used by most of the remaining methods of the 'cMount' class.&lt;br /&gt;&lt;br /&gt;The next method, 'getCoordinates', strips out the RA and Declination information from the '__info' dictionary and builds a new dictionary called 'raDec' to hold this data. Likewise, the 'getAltAzimuth' and 'getTimes' methods similarly strip relevant information from the '__info' dictionary and build new dictionaries called 'altAzimuth' and 'objTimes' respectively.&lt;br /&gt;&lt;br /&gt;Finally, the 'getImagingLocation' method extracts the current location in use by the Sky6's planetarium and returns it to the calling routine. Also, the 'checkObjectInWest' method uses the 'getAltAzimuth' method to determine if the located object is in the eastern or western sky.  It returns TRUE if the object is in the western sky and FALSE if the object is in the eastern sky.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cMOUNT UNIT TEST&lt;/b&gt;&lt;br /&gt;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 an object for The Sky6 to locate. The test then prints out the current location for The Sky6 then locates the object in its database (or returns an error if it cannot be located.) Next, the script checks whether the object is in the eastern or western sky then prints out the current system time and date before printing out the object's rise, transit, and set times (or transit time only for circumpolar objects). The object's RA and Declination for the current epoch and for the year 2000 epoch are then printed. Finally, the 'getAltAzimuth' method is called and the object's current altitude and azimuth are printed to screen.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;    &lt;br /&gt;    # create an instance of the cMount object&lt;br /&gt;    testMount = cMount()&lt;br /&gt;&lt;br /&gt;    # prompt for name of object to locate&lt;br /&gt;    print&lt;br /&gt;    obj = raw_input("Enter object to find: ")&lt;br /&gt;    &lt;br /&gt;    # test getImagingLocation method&lt;br /&gt;    print&lt;br /&gt;    print "The Sky6 Location: %s" % testMount.getImagingLocation()&lt;br /&gt;&lt;br /&gt;    # test findObject method&lt;br /&gt;    print&lt;br /&gt;    if not testMount.findObject(obj):&lt;br /&gt;        # test checkObjectInWest method&lt;br /&gt;        if testMount.checkObjectInWest():&lt;br /&gt;            print "%s is west of the meridian" % obj&lt;br /&gt;        else:&lt;br /&gt;            print "%s is east of the meridian" % obj&lt;br /&gt;&lt;br /&gt;        # test getTimes function&lt;br /&gt;        times = testMount.getTimes()&lt;br /&gt;        print "System Date = %s" % times['Date Now']&lt;br /&gt;        print "System Time = %s" % times['Time Now']&lt;br /&gt;        if times.has_key('Rise time'):&lt;br /&gt;            print "%s rise time    = %0.3f" % (obj,times['Rise time'])&lt;br /&gt;        if times.has_key('Transit time'):&lt;br /&gt;            print "%s transit time = %0.3f" % (obj,times['Transit time'])&lt;br /&gt;        if times.has_key('Set time'):&lt;br /&gt;            print "%s set time     = %0.3f" % (obj,times['Set time'])&lt;br /&gt;        &lt;br /&gt;        # test getCoordinates function&lt;br /&gt;        coords = testMount.getCoordinates()&lt;br /&gt;        print&lt;br /&gt;        print "%s RA  (current epoch) = %0.3f" % (obj,coords['RA Now'])&lt;br /&gt;        print "%s Dec (current epoch) = %0.3f" % (obj,coords['DEC Now'])&lt;br /&gt;        print "%s RA  (epoch 2000)    = %0.3f" % (obj,coords['RA J2000'])&lt;br /&gt;        print "%s Dec (epoch 2000)    = %0.3f" % (obj,coords['DEC J2000'])&lt;br /&gt;        &lt;br /&gt;        # test getAltAzimuth function&lt;br /&gt;        altazi = testMount.getAltAzimuth()&lt;br /&gt;        print&lt;br /&gt;        print "%s Altitude = %0.3f" % (obj,altazi['Altitude'])&lt;br /&gt;        print "%s Azimuth  = %0.3f" % (obj,altazi['Azimuth'])&lt;br /&gt;        print&lt;br /&gt;    else:&lt;br /&gt;        print "%s could not be found." % obj&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;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 &lt;a href="http://astro.wt5l.com/blogspot/cMount_2a.zip"&gt;cMount_2a.zip&lt;/a&gt;. Experiment with the script by running it multiple times and selecting different objects for The Sky6 to locate and display information about.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;In the next post I will continue to expand the 'cMount' class by introducing more methods and properties that utilize planetarium and telescope control features of the Sky6. I will also create a new unit test listing to verify correct operation of the features added to this class.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/06/automated-astrophotography-with-python.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-4801525466039983775?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/4801525466039983775/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_31.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4801525466039983775'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4801525466039983775'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_31.html' title='Automated Astrophotography with Python - Part 2a'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-7750391120936411771</id><published>2011-05-28T06:15:00.008-04:00</published><updated>2011-06-17T16:43:14.864-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 1d</title><content type='html'>&lt;b&gt;AUTOGUIDING&lt;/b&gt;&lt;br /&gt;The last major functionality to add to the 'cCamera' class is the ability to perform autoguiding during long exposures.  Instead of showing the complete 'cCamera' class listing as I have in previous posts, here I show the new attributes and methods that must be added to the 'cCamera' class that I've built up so far.  The first listing shows the new class attributes.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;self.__guideStarXPos = 0 # x-coordinate of guide star&lt;br /&gt;self.__guideStarYPos = 0 # y-coordinate of guide star&lt;br /&gt;self.__guideExposure = 1.0 # default guide exposure in seconds&lt;br /&gt;self.__guideSettleLimit = 0.40 # max pixel error before imaging can occur&lt;br /&gt;self.__guideSettleMaxTime = 120 # max time for autoguider to settle&lt;br /&gt;&lt;/pre&gt;The first two attributes contain the x- and y-coordinates of the guide star that will be used for autoguiding and the attribute '__guideExposure' specifies the length of exposure for each guide image. The attribute '__guideSettleLimit' sets the limit which the guide errors must fall under before an image exposure will start.  (ex. In this case, guide errors for both axes must settle under 0.40 pixels before an image exposure will begin.) The '__guideSettleMaxTime' attribute holds the maximum number of seconds allowed for the guide errors to settle under '__guideSettleLimit'.&lt;br /&gt;&lt;br /&gt;The next listing shows the new methods added to the 'cCamera' class.  &lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;def autoGuide(self,autoGuideStar,exposure):&lt;br /&gt;    if autoGuideStar:&lt;br /&gt;        self.__CAMERA.GuiderAutoSelectStar = True&lt;br /&gt;        if self.__guideStarYPos == 0 or self.__guideStarYPos == 0:&lt;br /&gt;            self.__guideExposure = exposure&lt;br /&gt;            if self.exposeGuider(self.__guideExposure):&lt;br /&gt;                return ERROR&lt;br /&gt;            self.__guideStarXPos = self.__CAMERA.GuiderXStarPosition&lt;br /&gt;            self.__guideStarYPos = self.__CAMERA.GuiderYStarPosition&lt;br /&gt;            print&lt;br /&gt;            print "Guider Setup:"&lt;br /&gt;            print "Guider:                %s" % self.__CAMERA.GuiderName&lt;br /&gt;            print "Guide star selection:  Auto"&lt;br /&gt;            print "Guide star exposure:   %0.2f" % self.__guideExposure&lt;br /&gt;            print "Aggressiveness X-Axis: %0.2f" % \&lt;br /&gt;                   self.__CAMERA.GuiderAggressivenessX&lt;br /&gt;            print "Aggressiveness Y-Axis: %0.2f" % \&lt;br /&gt;                   self.__CAMERA.GuiderAggressivenessY&lt;br /&gt;            print "Max Move X-Axis:       %0.2f" % self.__CAMERA.GuiderMaxMoveX&lt;br /&gt;            print "Max Move Y-Axis:       %0.2f" % self.__CAMERA.GuiderMaxMoveY&lt;br /&gt;            print "Min Move X-Axis:       %0.2f" % self.__CAMERA.GuiderMinMoveX&lt;br /&gt;            print "Min Move Y-Axis:       %0.2f" % self.__CAMERA.GuiderMinMoveY&lt;br /&gt;    else:&lt;br /&gt;        self.__CAMERA.GuiderAutoSelectStar = False&lt;br /&gt;        ## if necessary, set up the guider for autoguiding&lt;br /&gt;        if self.__guideStarXPos == 0 or self.__guideStarYPos == 0:&lt;br /&gt;            # prompt operator to manually select a guide star&lt;br /&gt;            print&lt;br /&gt;            print " *** INPUT NEEDED ***"&lt;br /&gt;            print " 1. In MaxIm, manually expose the guide camera."&lt;br /&gt;            print " 2. Click on a guide star and enter a guide exposure value."&lt;br /&gt;            print " 3. Verify that MaxIm correctly tracks on the guide star."&lt;br /&gt;            raw_input(" 4. Press ENTER key when ready to proceed: ")&lt;br /&gt;            self.__guideStarXPos = self.__CAMERA.GuiderXStarPosition&lt;br /&gt;            self.__guideStarYPos = self.__CAMERA.GuiderYStarPosition&lt;br /&gt;            print&lt;br /&gt;            exposure = raw_input(" Enter Guide Star Exposure (sec): ")&lt;br /&gt;            try:&lt;br /&gt;                self.__guideExposure = float(exposure)&lt;br /&gt;            except:&lt;br /&gt;                print " ERROR: Invalid input. Expecting float value...try again"&lt;br /&gt;                exposure = raw_input(" Enter Guide Star Exposure: ")&lt;br /&gt;                try:&lt;br /&gt;                    self.__guideExposure = float(exposure)&lt;br /&gt;                except:&lt;br /&gt;                    print "ERROR: Invalid input for guide star exposure"&lt;br /&gt;                    return ERROR&lt;br /&gt;            print&lt;br /&gt;            print "Guider Setup:"&lt;br /&gt;            print "Guider:                %s" % self.__CAMERA.GuiderName&lt;br /&gt;            print "Guide star selection:  Manual"&lt;br /&gt;            print "Guide star exposure:   %0.2f" % self.__guideExposure&lt;br /&gt;            print "Aggressiveness X-Axis: %0.2f" % \&lt;br /&gt;                   self.__CAMERA.GuiderAggressivenessX&lt;br /&gt;            print "Aggressiveness Y-Axis: %0.2f" % \&lt;br /&gt;                   self.__CAMERA.GuiderAggressivenessY&lt;br /&gt;            print "Max Move X-Axis:       %0.2f" % self.__CAMERA.GuiderMaxMoveX&lt;br /&gt;            print "Max Move Y-Axis:       %0.2f" % self.__CAMERA.GuiderMaxMoveY&lt;br /&gt;            print "Min Move X-Axis:       %0.2f" % self.__CAMERA.GuiderMinMoveX&lt;br /&gt;            print "Min Move Y-Axis:       %0.2f" % self.__CAMERA.GuiderMinMoveY&lt;br /&gt;&lt;br /&gt;    self.__CAMERA.GuiderBinning = 1&lt;br /&gt;    self.__CAMERA.GuiderSetStarPosition(self.__guideStarXPos,&lt;br /&gt;                                        self.__guideStarYPos)&lt;br /&gt;    print "Guider Declination = %d" % self.__CAMERA.GuiderDeclination&lt;br /&gt;    print "Tracking on guide star at X = %d, Y = %d" % \&lt;br /&gt;           (self.__guideStarXPos,self.__guideStarYPos)&lt;br /&gt;    # start autoguiding&lt;br /&gt;    try:&lt;br /&gt;        guideStatus = self.__CAMERA.GuiderTrack(self.__guideExposure)&lt;br /&gt;    except:&lt;br /&gt;        print "ERROR: While attempting to start the autoguider"&lt;br /&gt;        return ERROR&lt;br /&gt;    else:&lt;br /&gt;        if guideStatus:&lt;br /&gt;            print "Start autoguiding..."&lt;br /&gt;        else:&lt;br /&gt;            print "ERROR: Autoguider did not start successfully"&lt;br /&gt;            return ERROR&lt;br /&gt;    print "Waiting for guider to settle below %0.2f px (max wait %d sec)" % \&lt;br /&gt;           (self.__guideSettleLimit,self.__guideSettleMaxTime)&lt;br /&gt;    started = time.time()&lt;br /&gt;    cnt = 0&lt;br /&gt;    while True:&lt;br /&gt;        if (time.time() - started) &amp;gt; self.__guideSettleMaxTime:&lt;br /&gt;            print "ERROR: Guider not settled within the max allowable time"&lt;br /&gt;            return ERROR&lt;br /&gt;        if self.__CAMERA.GuiderNewMeasurement:&lt;br /&gt;            recentErrorX = self.__CAMERA.GuiderXError&lt;br /&gt;            recentErrorY = self.__CAMERA.GuiderYError&lt;br /&gt;            # ignore the first reading&lt;br /&gt;            if cnt != 0:&lt;br /&gt;                print "X-Error: %7.3f  Y-Error: %7.3f" % \&lt;br /&gt;                       (recentErrorX,recentErrorY)&lt;br /&gt;                if (abs(recentErrorX) &amp;lt; self.__guideSettleLimit and&lt;br /&gt;                    abs(recentErrorY) &amp;lt; self.__guideSettleLimit):&lt;br /&gt;                    break&lt;br /&gt;        cnt += 1&lt;br /&gt;        time.sleep(0.5)&lt;br /&gt;    return NOERROR&lt;br /&gt;&lt;br /&gt;def stopAutoGuide(self):&lt;br /&gt;    try:&lt;br /&gt;        self.__CAMERA.GuiderStop()&lt;br /&gt;    except TypeError:&lt;br /&gt;        print "Stop Autoguiding..."&lt;br /&gt;        time.sleep(2)&lt;br /&gt;    except:&lt;br /&gt;        print "ERROR: Unexpected error while attempting to stop autoguider"&lt;br /&gt;&lt;br /&gt;def checkGuiderRunning(self):&lt;br /&gt;    return self.__CAMERA.GuiderRunning&lt;br /&gt;    &lt;br /&gt;def resetGuideStar(self):&lt;br /&gt;    self.__guideStarXPos = 0&lt;br /&gt;    self.__guideStarYPos = 0&lt;/pre&gt;Not surprisingly, 'autoGuide' is the new method that initiates all autoguiding operations.  This method takes a boolean argument called 'autoGuideStar' to determine if autoguiding will use automatic or manual guide star selection. Additionally, the 'exposure' argument specifies the guide exposure length to use when automatic guide star selection is chosen and is not relevant when manual guide star selection is specified. If 'True' is passed in to the 'autoGuideStar' argument, the MaxIm DL attribute 'GuiderAutoSelectStar' is set to 'True', and, if a guide star has not been previously selected, a guide image is taken, and class attributes are populated with the position of the brightest guide star that MaxIm DL finds. All guide settings are then printed to the screen. If the 'autoGuideStar' argument is passed in as 'False', the MaxIm DL attribute 'GuiderAutoSelectStar' is set to 'False', and if a guide star has not previously been identified, the user is prompted to manually take a guide exposure, click on a potential guide star, then test tracking to make sure it is suitable. After pressing the 'ENTER' key, the user is then prompted to enter a length of exposure for all guide images and then all guide settings are printed to the screen.&lt;br /&gt;&lt;br /&gt;After the guide star has been identified and set up, the guider binning is set then messages are printed to screen that show the current declination of the guider and the x- and y-coordinates of the chosen guide star. At this point, the autoguider is started with the MaxIm DL 'GuiderTrack' method inside a 'try', 'except', 'else' construct. The 'autoGuide' method then enters a loop that repeats until the guide errors fall under the pixel value defined by the class attribute '__guideSettleLimit'. If the errors do not settle within '__guideSettleMaxTime' seconds an error is returned to the calling routine. Assuming autoguiding starts okay and guide errors settle out, imaging can now begin.&lt;br /&gt;&lt;br /&gt;The other two new methods added to this class are a procedure to stop the autoguider and a procedure to check to see if the autoguider is running. The 'checkGuiderRunning' method simply returns the result of MaxIm DL's 'GuiderRunning' attribute to the calling routine.  The 'stopAutoGuide' method is complicated by a minor bug in MaxIm DL that causes a call to MaxIm's 'GuiderStop' method to always throw a 'TypeError' exception even though the autoguider is successfully stopped. This expected error is trapped in a 'except TypeError' block and any other exception generates an error statement to the screen.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cCAMERA UNIT TEST&lt;/b&gt;&lt;br /&gt;The following listing shows the unit test code for this version of the 'cCamera' class. The script sets the simulator imaging camera up for full-frame mode with 1x1 binning then sets a CCD temperature and waits for the temperature to stabilize. As a first test, the script initiates autoguiding with automatic guide star selection and uses 1-second exposures for guide images. After making sure that autoguiding is in progress, it begins taking three 10-second images through the blue filter. When all images are complete, autoguiding is stopped. After resetting the guide star's x- and y-coordinates to zero, the process is started again with auto guide star selection and with 2.5-second guide images. This time, three 20-second images through the blue filter are taken before autoguiding is stopped.  As a last test, the guide star cooridinates are zeroed out and autoguiding is started with manual guide star selection. After the user switches to MaxIm DL, takes a guide exposure, clicks on a guide star, and tests tracking for that star, autoguiding is started and three 15-second images through the blue filter are initiated. After all images are finished, autoguiding is stopped and the CCD is warmed to ambient temperature.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cCamera class&lt;br /&gt;    testCamera = cCamera()&lt;br /&gt;&lt;br /&gt;    # Setup Maxim DL to take a full frame image &lt;br /&gt;    testCamera.setFullFrame()&lt;br /&gt;    # Setup binning for 1x1&lt;br /&gt;    testCamera.setBinning(1)&lt;br /&gt;&lt;br /&gt;    # Set CCD Temperature&lt;br /&gt;    testCamera.setCCDTemp('-15C')&lt;br /&gt;    # Goto CCD Temperature&lt;br /&gt;    testCamera.gotoCCDTemp()&lt;br /&gt;    &lt;br /&gt;    # Start camera autoguiding with auto guide star select&lt;br /&gt;    # Guide exposure = 1.0 second&lt;br /&gt;    if testCamera.autoGuide(True,1.0):&lt;br /&gt;        testCamera.stopAutoGuide()&lt;br /&gt;    # Make sure autoguider is running&lt;br /&gt;    if testCamera.checkGuiderRunning():&lt;br /&gt;        # Take 3 images&lt;br /&gt;        for i in range(3):&lt;br /&gt;            # Expose filter slot 2 (Blue) for 10 seconds&lt;br /&gt;            testCamera.exposeLight(10,2,'m51_B')&lt;br /&gt;        # Stop autoguider after all images complete&lt;br /&gt;        testCamera.stopAutoGuide()    &lt;br /&gt;    else:&lt;br /&gt;        print "ERROR - Autoguider not running as expected"&lt;br /&gt;&lt;br /&gt;    # Reset guide star positions for next test&lt;br /&gt;    testCamera.resetGuideStar()&lt;br /&gt;    # Start camera autoguiding with auto guide star select&lt;br /&gt;    # Guide exposure = 2.5 seconds&lt;br /&gt;    if testCamera.autoGuide(True,2.5):&lt;br /&gt;        testCamera.stopAutoGuide()&lt;br /&gt;    # Make sure autoguider is running &lt;br /&gt;    if testCamera.checkGuiderRunning():&lt;br /&gt;        # Take 3 images&lt;br /&gt;        for i in range(3):&lt;br /&gt;            # Expose filter slot 2 (Blue) for 20 seconds&lt;br /&gt;            testCamera.exposeLight(20,2,'m51_B')&lt;br /&gt;        # Stop autoguider after all images complete&lt;br /&gt;        testCamera.stopAutoGuide()    &lt;br /&gt;    else:&lt;br /&gt;        print "ERROR - Autoguider not running as expected"&lt;br /&gt;    &lt;br /&gt;    # Reset guide star positions for next test&lt;br /&gt;    testCamera.resetGuideStar()&lt;br /&gt;    # Start camera autoguiding with manual guide star select&lt;br /&gt;    if testCamera.autoGuide(False,0):&lt;br /&gt;        testCamera.stopAutoGuide()&lt;br /&gt;    if testCamera.checkGuiderRunning():&lt;br /&gt;        # Take 3 images&lt;br /&gt;        for i in range(3):&lt;br /&gt;            # Expose filter slot 2 (Blue) for 15 seconds&lt;br /&gt;            testCamera.exposeLight(15,2,'m51_B')&lt;br /&gt;        # Stop autoguider after all images complete&lt;br /&gt;        testCamera.stopAutoGuide()    &lt;br /&gt;    else:&lt;br /&gt;        print "ERROR - Autoguider not running as expected"&lt;br /&gt;&lt;br /&gt;    # Warm the CCD to ambient&lt;br /&gt;    testCamera.warmCCD()&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;Testing this version of the 'cCamera' class using the camera simulator follows the same procedure described in &lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html"&gt;Part 1a&lt;/a&gt; of this series. The new listing for 'cCamera' can be downloaded from &lt;a href="http://astro.wt5l.com/blogspot/cCamera_1d.zip"&gt;cCamera_1d.zip&lt;/a&gt;. Experiment with the script by changing guide star selection mode (automatic or manual) and/or guide image exposure time to verify the script operates as expected.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;This is the final post that will cover the 'cCamera' class (although occasionally new attributes or methods may be added to the class, as needed). In the next post I will begin building the 'cMount' class. This class will interface with methods and attributes provided by the Sky's telescope control and planetarium classes.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_31.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-7750391120936411771?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/7750391120936411771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_28.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7750391120936411771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7750391120936411771'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_28.html' title='Automated Astrophotography with Python - Part 1d'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-7921317863005590360</id><published>2011-05-24T11:59:00.009-04:00</published><updated>2011-06-17T16:50:41.422-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 1c</title><content type='html'>&lt;b&gt;CCD TEMPERATURE CONTROL&lt;/b&gt;&lt;br /&gt;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.)&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import time&lt;br /&gt;import os&lt;br /&gt;import win32com.client&lt;br /&gt;&lt;br /&gt;LIGHT_PATH = r"c:\\astro_images\\"&lt;br /&gt;&lt;span style="color: lightcyan;"&gt;SETTLE_TIME = 120  # minimum time for stable temp at set point &lt;br /&gt;SETTLE_MAX  = 480  # maximum time for temp to stabilize&lt;/span&gt;&lt;br /&gt;ERROR = True&lt;br /&gt;NOERROR = False&lt;br /&gt;&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cCamera&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cCamera:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        &lt;span style="color: lightcyan;"&gt;self.__CCDTemp = 'Skip' # CCD Temperature (Skip = don't change temp)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;        print "Connecting to MaxIm DL..."&lt;br /&gt;        self.__CAMERA = win32com.client.Dispatch("MaxIm.CCDCamera")&lt;br /&gt;        self.__CAMERA.DisableAutoShutdown = True&lt;br /&gt;        try:&lt;br /&gt;            self.__CAMERA.LinkEnabled = True&lt;br /&gt;        except:&lt;br /&gt;            print "... cannot connect to camera"&lt;br /&gt;            print "--&gt; Is camera hardware attached?"&lt;br /&gt;            print "--&gt; Is some other application already using camera hardware?"&lt;br /&gt;            raise EnvironmentError, 'Halting program'&lt;br /&gt;        if not self.__CAMERA.LinkEnabled:&lt;br /&gt;            print "... camera link DID NOT TURN ON; CANNOT CONTINUE"&lt;br /&gt;            raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: lightcyan;"&gt;def setCCDTemp(self,strtemp):&lt;br /&gt;        if strtemp.upper() == 'SKIP':&lt;br /&gt;            print "No CCD Cooling Specified"&lt;br /&gt;            self.__CCDTemp = -99&lt;br /&gt;            return NOERROR&lt;br /&gt;        if strtemp.endswith("C"):&lt;br /&gt;            try:&lt;br /&gt;                flttemp = float(strtemp[:-1])&lt;br /&gt;            except:&lt;br /&gt;                print "ERROR: Specified CCD Temperature - Invalid format"&lt;br /&gt;                return ERROR&lt;br /&gt;        else:&lt;br /&gt;            try:&lt;br /&gt;                flttemp = float(strtemp)&lt;br /&gt;            except:&lt;br /&gt;                print "ERROR: Specified CCD Temperature - Invalid format"&lt;br /&gt;                return ERROR&lt;br /&gt;        self.__CCDTemp = flttemp&lt;br /&gt;        return NOERROR&lt;br /&gt;        &lt;br /&gt;    def gotoCCDTemp(self):&lt;br /&gt;        if self.__CCDTemp &gt; -90:&lt;br /&gt;            # set the CCD temperature set-point&lt;br /&gt;            self.__CAMERA.TemperatureSetpoint = self.__CCDTemp&lt;br /&gt;            print "CCD temperature setpoint: %0.2fC" % self.__CCDTemp&lt;br /&gt;            # make sure the cooler is on, just in case&lt;br /&gt;            if not self.__CAMERA.CoolerOn:&lt;br /&gt;                print "Turning CCD cooler on"&lt;br /&gt;                self.__CAMERA.CoolerOn = True&lt;br /&gt;            print "Waiting for CCD temperature to stabilize"&lt;br /&gt;            started = time.time()&lt;br /&gt;            cnt = 0&lt;br /&gt;            # Check CCD temp to stabilize&lt;br /&gt;            while cnt &lt; SETTLE_TIME and (time.time() - started) &lt; SETTLE_MAX:&lt;br /&gt;                currentTemp = self.__CAMERA.Temperature&lt;br /&gt;                if (currentTemp &lt; self.__CCDTemp - 0.5 or&lt;br /&gt;                    currentTemp &gt; self.__CCDTemp + 0.5):&lt;br /&gt;                    cnt = 0&lt;br /&gt;                time.sleep(1)&lt;br /&gt;                cnt += 1&lt;br /&gt;            if cnt == SETTLE_TIME and (time.time() - started) &lt; SETTLE_MAX:&lt;br /&gt;                print "CCD Temperature Stable at %0.2fC" % self.__CAMERA.Temperature&lt;br /&gt;                try:&lt;br /&gt;                    power = self.__CAMERA.CoolerPower&lt;br /&gt;                    print "CCD Cooler Power: %d%%" % power&lt;br /&gt;                except:&lt;br /&gt;                    print "CCD cooler power could not be read"&lt;br /&gt;                    return NOERROR&lt;br /&gt;                return NOERROR&lt;br /&gt;            else:&lt;br /&gt;                print "CCD Temperature Did Not Stabilize"&lt;br /&gt;                return ERROR&lt;br /&gt;        else:&lt;br /&gt;            print "Skipping temp stabilization."&lt;br /&gt;            return NOERROR&lt;br /&gt;&lt;br /&gt;    def warmCCD(self):&lt;br /&gt;        if self.__CAMERA.CoolerOn:&lt;br /&gt;            print "Starting to gradually warm CCD temperature to ambient"&lt;br /&gt;            power = 100&lt;br /&gt;            setTemp = self.__CAMERA.Temperature&lt;br /&gt;            while power &gt; 3:&lt;br /&gt;                setTemp = setTemp + 5.0&lt;br /&gt;                self.__CAMERA.TemperatureSetpoint = setTemp&lt;br /&gt;                print "CCD temperature setpoint: %0.2fC" % setTemp&lt;br /&gt;                print "Waiting 2.5 minutes for temperature to rise"&lt;br /&gt;                time.sleep(150)&lt;br /&gt;                print "CCD Cooler Temp : %0.2fC" % self.__CAMERA.Temperature&lt;br /&gt;                try:&lt;br /&gt;                    power = self.__CAMERA.CoolerPower&lt;br /&gt;                    print "CCD Cooler Power: %d%%" % power&lt;br /&gt;                except:&lt;br /&gt;                    print "CCD cooler power could not be read"&lt;br /&gt;                    power = 0&lt;br /&gt;            print "CCD warming complete. Turning CCD cooler off"&lt;br /&gt;            self.__CAMERA.CoolerOn = False&lt;br /&gt;        else:&lt;br /&gt;            print "CCD Cooler is off. CCD warming not necessary."&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    def generateFilename(self,path,baseName):&lt;br /&gt;        # path is the path to where the file will be saved&lt;br /&gt;        baseName.replace(':', '_')      # colons become underscores&lt;br /&gt;        baseName.replace(' ', '_')      # blanks become underscores&lt;br /&gt;        baseName.replace('\\', '_')     # backslash becomes underscore&lt;br /&gt;        # make sure the base filename has an '_' at the end&lt;br /&gt;        if not baseName.endswith("_"):&lt;br /&gt;            baseName = baseName + "_"&lt;br /&gt;        # add 1 to use next available number&lt;br /&gt;        seqMax = self.getSequenceNumber(path,baseName) &lt;br /&gt;        seqNext = seqMax + 1&lt;br /&gt;        filename = "%s%s%05d.fit" % (path,baseName,seqNext)&lt;br /&gt;        return filename&lt;br /&gt;&lt;br /&gt;    def getSequenceNumber(self,path,baseName):&lt;br /&gt;        # get a list of files in the image directory&lt;br /&gt;        col = os.listdir(path)&lt;br /&gt;        # Loop over these filenames and see if any match the basename&lt;br /&gt;        retValue = 0&lt;br /&gt;        for name in col:&lt;br /&gt;            front = name[0:-9]&lt;br /&gt;            back = name[-9:]&lt;br /&gt;            if front == baseName:&lt;br /&gt;                # baseName match found, now get sequence number for this file&lt;br /&gt;                seqString = name[-9:-4]  # get last 5 chars of name (seq number)&lt;br /&gt;                try:&lt;br /&gt;                    seqInt = int(seqString)&lt;br /&gt;                    if seqInt &gt; retValue:&lt;br /&gt;                        retValue = seqInt    # store greatest sequence number&lt;br /&gt;                except:&lt;br /&gt;                    pass&lt;br /&gt;        return retValue&lt;br /&gt;&lt;br /&gt;    def exposeLight(self,length,filterSlot,name):&lt;br /&gt;        print "Exposing light frame..."&lt;br /&gt;        self.__CAMERA.Expose(length,1,filterSlot)&lt;br /&gt;        while not self.__CAMERA.ImageReady:&lt;br /&gt;            time.sleep(1)&lt;br /&gt;        print "Light frame exposure and download complete!"&lt;br /&gt;        # save image&lt;br /&gt;        filename = self.generateFilename(LIGHT_PATH,name)&lt;br /&gt;        print "Saving light image -&gt; %s" % filename&lt;br /&gt;        self.__CAMERA.SaveImage(filename)&lt;br /&gt;&lt;br /&gt;    def setFullFrame(self):&lt;br /&gt;        self.__CAMERA.SetFullFrame()&lt;br /&gt;        print "Camera set to full-frame mode"&lt;br /&gt;        &lt;br /&gt;    def setBinning(self,binmode):&lt;br /&gt;        tup = (1,2,3)&lt;br /&gt;        if binmode in tup:&lt;br /&gt;            self.__CAMERA.BinX = binmode&lt;br /&gt;            self.__CAMERA.BinY = binmode&lt;br /&gt;            print "Camera binning set to %dx%d" % (binmode,binmode)&lt;br /&gt;            return NOERROR&lt;br /&gt;        else:&lt;br /&gt;            print "ERROR: Invalid binning specified"&lt;br /&gt;            return ERROR&lt;br /&gt;&lt;br /&gt;##&lt;br /&gt;##    END OF 'cCamera' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cCAMERA UNIT TEST&lt;/b&gt;&lt;br /&gt;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. &lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cCamera class&lt;br /&gt;    testCamera = cCamera()&lt;br /&gt;&lt;br /&gt;    # Setup Maxim DL to take a full frame image &lt;br /&gt;    testCamera.setFullFrame()&lt;br /&gt;    # Setup binning for 1x1&lt;br /&gt;    testCamera.setBinning(1)&lt;br /&gt;&lt;br /&gt;    # Set CCD Temperature&lt;br /&gt;    testCamera.setCCDTemp('-20C')&lt;br /&gt;    # Goto CCD Temperature&lt;br /&gt;    testCamera.gotoCCDTemp()&lt;br /&gt; &lt;br /&gt;    # Take 3 images&lt;br /&gt;    for i in range(3):&lt;br /&gt;        # Expose filter slot 1 (Green) for 10 seconds&lt;br /&gt;        testCamera.exposeLight(10,1,'m51_G')&lt;br /&gt;    &lt;br /&gt;    # Set CCD Temperature&lt;br /&gt;    testCamera.setCCDTemp('-15C')&lt;br /&gt;    # Goto CCD Temperature&lt;br /&gt;    testCamera.gotoCCDTemp()&lt;br /&gt; &lt;br /&gt;    # Take 3 images&lt;br /&gt;    for i in range(3):&lt;br /&gt;        # Expose filter slot 1 (Green) for 10 seconds&lt;br /&gt;        testCamera.exposeLight(10,1,'m51_G')&lt;br /&gt;    &lt;br /&gt;    # Set CCD Temperature&lt;br /&gt;    testCamera.setCCDTemp('skip')&lt;br /&gt;    # Goto CCD Temperature&lt;br /&gt;    testCamera.gotoCCDTemp()&lt;br /&gt; &lt;br /&gt;    # Take 3 images&lt;br /&gt;    for i in range(3):&lt;br /&gt;        # Expose filter slot 1 (Green) for 10 seconds&lt;br /&gt;        testCamera.exposeLight(10,1,'m51_G')&lt;br /&gt;    &lt;br /&gt;    # Warm the CCD to ambient&lt;br /&gt;    testCamera.warmCCD()&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;Testing this version of the 'cCamera' class follows the same procedure described in &lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html"&gt;Part 1a&lt;/a&gt; of this series. The new listing for 'cCamera' can be downloaded from &lt;a href="http://astro.wt5l.com/blogspot/cCamera_1c.zip"&gt;cCamera_1c.zip&lt;/a&gt;. 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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_28.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-7921317863005590360?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/7921317863005590360/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_24.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7921317863005590360'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7921317863005590360'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_24.html' title='Automated Astrophotography with Python - Part 1c'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-4260303081116862319</id><published>2011-05-23T06:29:00.016-04:00</published><updated>2011-06-17T16:53:19.079-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 1b</title><content type='html'>&lt;b&gt;SAVING IMAGES&lt;/b&gt;&lt;br /&gt;The script of the 'cCamera' class presented in &lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html"&gt;Part 1a&lt;/a&gt; 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.)&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import time&lt;br /&gt;&lt;span style="color: lightcyan;"&gt;import os&lt;/span&gt;&lt;br /&gt;import win32com.client&lt;br /&gt;&lt;br /&gt;&lt;span style="color: lightcyan;"&gt;LIGHT_PATH = r"c:\\astro_images\\"&lt;/span&gt;&lt;br /&gt;ERROR = True&lt;br /&gt;NOERROR = False&lt;br /&gt;&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cCamera&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cCamera:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        print "Connecting to MaxIm DL..."&lt;br /&gt;        self.__CAMERA = win32com.client.Dispatch("MaxIm.CCDCamera")&lt;br /&gt;        self.__CAMERA.DisableAutoShutdown = True&lt;br /&gt;        try:&lt;br /&gt;            self.__CAMERA.LinkEnabled = True&lt;br /&gt;        except:&lt;br /&gt;            print "... cannot connect to camera"&lt;br /&gt;            print "--&gt; Is camera hardware attached?"&lt;br /&gt;            print "--&gt; Is some other application already using camera hardware?"&lt;br /&gt;            raise EnvironmentError, 'Halting program'&lt;br /&gt;        if not self.__CAMERA.LinkEnabled:&lt;br /&gt;            print "... camera link DID NOT TURN ON; CANNOT CONTINUE"&lt;br /&gt;            raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;    &lt;span style="color: lightcyan;"&gt;def generateFilename(self,path,baseName):&lt;br /&gt;        # path is the path to where the file will be saved&lt;br /&gt;        baseName.replace(':', '_')      # colons become underscores&lt;br /&gt;        baseName.replace(' ', '_')      # blanks become underscores&lt;br /&gt;        baseName.replace('\\', '_')     # backslash becomes underscore&lt;br /&gt;        # make sure the base filename has an '_' at the end&lt;br /&gt;        if not baseName.endswith("_"):&lt;br /&gt;            baseName = baseName + "_"&lt;br /&gt;        # add 1 to use next available number&lt;br /&gt;        seqMax = self.getSequenceNumber(path,baseName) &lt;br /&gt;        seqNext = seqMax + 1&lt;br /&gt;        filename = "%s%s%05d.fit" % (path,baseName,seqNext)&lt;br /&gt;        return filename&lt;br /&gt;&lt;br /&gt;    def getSequenceNumber(self,path,baseName):&lt;br /&gt;        # get a list of files in the image directory&lt;br /&gt;        col = os.listdir(path)&lt;br /&gt;        # Loop over these filenames and see if any match the basename&lt;br /&gt;        retValue = 0&lt;br /&gt;        for name in col:&lt;br /&gt;            front = name[0:-9]&lt;br /&gt;            back = name[-9:]&lt;br /&gt;            if front == baseName:&lt;br /&gt;                # baseName match found, now get sequence number for this file&lt;br /&gt;                seqString = name[-9:-4]  # get last 5 chars of name (seq number)&lt;br /&gt;                try:&lt;br /&gt;                    seqInt = int(seqString)&lt;br /&gt;                    if seqInt &gt; retValue:&lt;br /&gt;                        retValue = seqInt    # store greatest sequence number&lt;br /&gt;                except:&lt;br /&gt;                    pass&lt;br /&gt;        return retValue&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    def exposeLight(self,length,filterSlot,name):&lt;br /&gt;        print "Exposing light frame..."&lt;br /&gt;        self.__CAMERA.Expose(length,1,filterSlot)&lt;br /&gt;        while not self.__CAMERA.ImageReady:&lt;br /&gt;            time.sleep(1)&lt;br /&gt;        print "Light frame exposure and download complete!"&lt;br /&gt;        &lt;span style="color: lightcyan;"&gt;# save image&lt;br /&gt;        filename = self.generateFilename(LIGHT_PATH,name)&lt;br /&gt;        print "Saving light image -&gt; %s" % filename&lt;br /&gt;        self.__CAMERA.SaveImage(filename)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;    def setFullFrame(self):&lt;br /&gt;        self.__CAMERA.SetFullFrame()&lt;br /&gt;        print "Camera set to full-frame mode"&lt;br /&gt;        &lt;br /&gt;    def setBinning(self,binmode):&lt;br /&gt;        tup = (1,2,3)&lt;br /&gt;        if binmode in tup:&lt;br /&gt;            self.__CAMERA.BinX = binmode&lt;br /&gt;            self.__CAMERA.BinY = binmode&lt;br /&gt;            print "Camera binning set to %dx%d" % (binmode,binmode)&lt;br /&gt;            return NOERROR&lt;br /&gt;        else:&lt;br /&gt;            print "ERROR: Invalid binning specified"&lt;br /&gt;            return ERROR&lt;br /&gt;&lt;br /&gt;##&lt;br /&gt;##    END OF 'cCamera' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;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.&lt;br /&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cCAMERA UNIT TEST&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cCamera class&lt;br /&gt;    testCamera = cCamera()&lt;br /&gt;&lt;br /&gt;    # Setup MaxIm DL to take a full frame image &lt;br /&gt;    testCamera.setFullFrame()&lt;br /&gt;    # Setup binning for 2x2&lt;br /&gt;    if not testCamera.setBinning(2):&lt;br /&gt;        for i in range(4):&lt;br /&gt;            # Expose filter slot 0 (Red) for 15 seconds&lt;br /&gt;            testCamera.exposeLight(15,0,'m51_R_2x2')&lt;br /&gt;    else:&lt;br /&gt;        print "Images not taken due to previous error"&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;Testing this version of the 'cCamera' class follows the same procedure described in &lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html"&gt;Part 1a&lt;/a&gt; of this series. &lt;b&gt;Note:&lt;/b&gt; 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 &lt;a href="http://astro.wt5l.com/blogspot/cCamera_1b.zip"&gt;cCamera_1b.zip&lt;/a&gt;. 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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;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.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_24.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-4260303081116862319?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/4260303081116862319/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_23.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4260303081116862319'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/4260303081116862319'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_23.html' title='Automated Astrophotography with Python - Part 1b'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-7109888389797885110</id><published>2011-05-22T07:48:00.009-04:00</published><updated>2011-06-17T16:55:07.327-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Part 1a</title><content type='html'>&lt;b&gt;GETTING THINGS TO WORK&lt;/b&gt;&lt;br /&gt;The very first step in this process is to verify that Python and Python Extension for Windows are properly communicating with a suitable Windows application.  In this case, we will use MaxIm DL and its built-in camera simulator.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;THE cCAMERA CLASS&lt;/b&gt;&lt;br /&gt;The first piece of code that we will look at is the 'cCamera' class.  The Python code for 'cCamera' is shown below. (Note: Docstrings have been removed from this listing for brevity and readability purposes.)&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;import time&lt;br /&gt;import win32com.client&lt;br /&gt;&lt;br /&gt;ERROR = True&lt;br /&gt;NOERROR = False&lt;br /&gt;&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;## Class: cCamera&lt;br /&gt;##------------------------------------------------------------------------------&lt;br /&gt;class cCamera:&lt;br /&gt;    def __init__(self):&lt;br /&gt;        print "Connecting to MaxIm DL..."&lt;br /&gt;        self.__CAMERA = win32com.client.Dispatch("MaxIm.CCDCamera")&lt;br /&gt;        self.__CAMERA.DisableAutoShutdown = True&lt;br /&gt;        try:&lt;br /&gt;            self.__CAMERA.LinkEnabled = True&lt;br /&gt;        except:&lt;br /&gt;            print "... cannot connect to camera"&lt;br /&gt;            print "--&gt; Is camera hardware attached?"&lt;br /&gt;            print "--&gt; Is some other application already using camera hardware?"&lt;br /&gt;            raise EnvironmentError, 'Halting program'&lt;br /&gt;        if not self.__CAMERA.LinkEnabled:&lt;br /&gt;            print "... camera link DID NOT TURN ON; CANNOT CONTINUE"&lt;br /&gt;            raise EnvironmentError, 'Halting program'&lt;br /&gt;&lt;br /&gt;    def exposeLight(self,length,filterSlot):&lt;br /&gt;        print "Exposing light frame..."&lt;br /&gt;        self.__CAMERA.Expose(length,1,filterSlot)&lt;br /&gt;        while not self.__CAMERA.ImageReady:&lt;br /&gt;            time.sleep(1)&lt;br /&gt;        print "Light frame exposure and download complete!"&lt;br /&gt;&lt;br /&gt;    def setFullFrame(self):&lt;br /&gt;        self.__CAMERA.SetFullFrame()&lt;br /&gt;        print "Camera set to full-frame mode"&lt;br /&gt;        &lt;br /&gt;    def setBinning(self,binmode):&lt;br /&gt;        tup = (1,2,3)&lt;br /&gt;        if binmode in tup:&lt;br /&gt;            self.__CAMERA.BinX = binmode&lt;br /&gt;            self.__CAMERA.BinY = binmode&lt;br /&gt;            print "Camera binning set to %dx%d" % (binmode,binmode)&lt;br /&gt;            return NOERROR&lt;br /&gt;        else:&lt;br /&gt;            print "ERROR: Invalid binning specified"&lt;br /&gt;            return ERROR&lt;br /&gt;            &lt;br /&gt;##&lt;br /&gt;##    END OF 'cCamera' Class&lt;br /&gt;##&lt;br /&gt;&lt;/pre&gt;The first two lines of the listing are 'import' statements that identify and allow use of the 'time' and 'win32com.client' library objects in the script.  The listing for the 'cCamera' class follows the 'import' statements and consists of four methods.  The '__init__' method is the constructor method for 'cCamera' and is executed whenever an instance of the cCamera object is created.  This method creates a '__CAMERA' object that is an instance of MaxIm DL's 'CCDCamera' object.  The '__CAMERA' object then can be used to access all the properties and methods found in the scripting section of MaxIm DL's help file (for CCDCamera object).  This method also uses 'try' and 'except' statements to determine if a link has been successfully established to the program. If MaxIm DL has not already been loaded and connection to the camera established manually, the script will load MaxIm DL and make the camera connection automatically. &lt;br /&gt;&lt;br /&gt;Next in the listing is the 'exposeLight' method that takes as arguments a length of exposure (float) and an integer that specifies the filter to use for the exposure (zero-based).  This method uses MaxIm DL's 'Expose' method to start the exposure and polls the 'ImageReady' parameter to determine when the exposure and image download have completed (indicated when 'ImageReady' goes True).&lt;br /&gt;&lt;br /&gt;The third method of 'cCamera' calls the MaxIm DL method 'SetFullFrame' and the last method sets the 'BinX' and 'BinY' properties.  For more details regarding 'SetFullFrame()', 'BinX', and 'BinY' refer to MaxIm DL help under 'Scripting' and under 'CCDCamera' methods or properties.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;cCAMERA UNIT TEST&lt;/b&gt;&lt;br /&gt;The listing given below is inserted onto the end of the source listing and allows unit testing the 'cCamera' class.  The first step is to create an instance of the 'cCamera' object called 'testCamera'.  In turn, the 'setFullFrame' and 'setBinning' methods of 'testCamera' are called to set MaxIm to full-frame mode and to set binning to 1x1.  If 'setBinning' finished without error, the 'exposeLight' method is called and initiates a 12.5 second exposure through the simulator's blue filter (slot 2).&lt;br /&gt;&lt;pre style="background: none repeat scroll 0% 0% rgb(23, 62, 90); color: gold; font-size: 100%;"&gt;if __name__ == "__main__":&lt;br /&gt;&lt;br /&gt;    # Create an instance of the cCamera class&lt;br /&gt;    testCamera = cCamera()&lt;br /&gt;&lt;br /&gt;    # Setup MaxIm DL to take a full frame image &lt;br /&gt;    testCamera.setFullFrame()&lt;br /&gt;    # Setup binning for 2x2&lt;br /&gt;    if not testCamera.setBinning(2):&lt;br /&gt;        # Expose filter slot 2 (Blue) for 12.5 seconds&lt;br /&gt;        testCamera.exposeLight(12.5,2)&lt;br /&gt;    else:&lt;br /&gt;        print "Image not taken due to previous error"&lt;br /&gt;&lt;/pre&gt;&lt;b&gt;RUNNING THE SCRIPT&lt;/b&gt;&lt;br /&gt;Follow the steps below to execute the script described in this post:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Download and extract the script from &lt;a href="http://astro.wt5l.com/blogspot/cCamera_1a.zip"&gt;cCamera_1a.zip&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;Start MaxIm DL and open the Camera Control window. Under 'Setup Camera' for Camera 1, choose 'Simulator' and under 'Setup Filter' for Camera 1 choose 'Simulator' also.  For Camera 2, choose 'Simulator' and under 'Setup Filter' for Camera 2 leave unselected.&lt;/li&gt;&lt;li&gt;Change focus to the IDLE window containing your script listing, click the 'Run' menu item and pull down to 'Run Module', or simply press the F5 key.&lt;/li&gt;&lt;li&gt;If everything is working correctly, you should see MaxIm DL kick off a 12.5 second exposure through the blue filter and eventually display an image from the simulated camera.  You can inspect the FITS header to verify the binning, exposure, and filter for this image.&lt;/li&gt;&lt;li&gt;Modify the parameters in the unit test section of the listing to take exposures of different lengths, through different filters, using different binnings to verify that all work as expected.&lt;/li&gt;&lt;/ol&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;In the next post I will continue to expand the 'cCamera' class by introducing methods that allow saving light images to disk. I will also expand the unit test listing to verify correct operation of the new features added to this class.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_23.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-7109888389797885110?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/7109888389797885110/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7109888389797885110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/7109888389797885110'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html' title='Automated Astrophotography with Python - Part 1a'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-2041169047379054887</id><published>2011-05-21T18:22:00.005-04:00</published><updated>2011-05-28T09:10:29.580-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Scripting'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><category scheme='http://www.blogger.com/atom/ns#' term='Python'/><title type='text'>Automated Astrophotography with Python - Introduction</title><content type='html'>&lt;b&gt;INTRODUCTION&lt;/b&gt;&lt;br /&gt;As anyone past the beginner stage of astronomical imaging quickly finds out, an executive software application that controls all aspects of the imaging process becomes a desirable tool. There are various applications available (&lt;a href="http://ccdcommander.com/"&gt;CCD Commander&lt;/a&gt;, &lt;a href="http://acp.dc3.com/index2.html"&gt;ACP&lt;/a&gt;, &lt;a href="http://www.ccdware.com/products/ccdap4/"&gt;CCD Autopilot&lt;/a&gt;) that I highly recommend and that perform very well but that also come with relatively high price tags.  (Although the price is typically a fraction of the costs tied up in equipment and other software for the average imager.)  However, an alternative to using an application that someone else has created and maintains is to create your own control software that directs the imaging process EXACTLY how you would wish it to be executed.  This introductory post is the first in a series of posts that will detail how you can easily create and maintain your own executive application at no cost (other than your time learning and programming in Python).&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHY PYTHON?&lt;/b&gt;&lt;br /&gt;Python is a agile programming language that is freely available, well supported, and easy to learn for beginners.  Python syntax is very easy to read and understand, and the language supports object-oriented programming practices.  Python is also an interpreted language which means there is no compilation process.  You simply execute the script and it runs! Most importantly, Python supports the COM (Common Object Model) architecture under which most astronomical imaging software exposes their properties and methods to the outside world.  This means that Python can seamlessly communicate with and control all major astronomical software applications like CCDSoft, MaxIm DL, the Sky, etc.  For more information regarding the question of "Why Python?" click &lt;a href="http://pythoncard.sourceforge.net/what_is_python.html"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHERE TO GET PYTHON?&lt;/b&gt;&lt;br /&gt;Python can be downloaded from &lt;a href="http://www.python.org/download/"&gt;this&lt;/a&gt; web page.  Simply choose the installer that corresponds with your operating system and follow the prompts.  (Although Python is written for Windows, Mac, and Unix platforms, you will need the Windows 32-bit or 64-bit OS installer since virtually all of the major astronomical imaging software packages you'll hook into are Windows-based.)  The installer will also install IDLE which is the Python IDE (Integrated Development Environment) which you can use to write, test, and debug your scripts.  Click &lt;a href="http://wiki.python.org/moin/BeginnersGuide/Download"&gt;here&lt;/a&gt; for a beginner's guide to downloading Python.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;NOTE:&lt;/b&gt; All scripts presented in this series have been tested using the latest stable version of Python which is 2.7.1 as of the date of this post.  The scripts should also work under the latest version of Python 3 but this claim has not been verified.  Additionally, in order to have COM support, you must also load the Python Extensions for Windows package that corresponds to the version of Python that you have chosen to use.  The latest package (Build 216 as of the date of this post) can be found &lt;a href="http://sourceforge.net/projects/pywin32/files/"&gt;here&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;HOW TO LEARN PYTHON?&lt;/b&gt;&lt;br /&gt;An on-line search of "Learning Python" or "Python tutorial" will return hundreds of on-line tutorials and PDF documents to help you get started.  Another list of learning resources can be found &lt;a href="http://wiki.python.org/moin/BeginnersGuide/Programmers"&gt;here&lt;/a&gt;.  One on-line PDF document that I found useful, even though it is somewhat dated, can be found &lt;a href="http://greenteapress.com/thinkpython/thinkCSpy.pdf"&gt;here&lt;/a&gt; and the official Python tutorial can be found &lt;a href="http://docs.python.org/tutorial/"&gt;here&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;BENEFITS OF CREATING AND RUNNING YOUR OWN SCRIPT&lt;/b&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;No Cost (other than your time)&lt;/li&gt;&lt;li&gt;Great opportunity to learn computer programming if you've never done it before or to learn a new programming language if you're already an experienced programmer&lt;/li&gt;&lt;li&gt;COMPLETE control of all aspects of the automated imaging process&lt;/li&gt;&lt;li&gt;Fine-tuned to the particular set of software that YOU use for imaging&lt;/li&gt;&lt;li&gt;You maintain the application...no waiting for someone else to get around to fixing a bug or implementing an improvement&lt;/li&gt;&lt;li&gt;Implement only the features that you want in exactly the way you want them to function&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;b&gt;MY IMAGING ENVIRONMENT&lt;/b&gt;&lt;br /&gt;The computer that I use for astronomical imaging is a 6-year old Toshiba laptop running Windows XP Home Edition.  My Python script accesses the following imaging applications: &lt;br /&gt;&lt;ul&gt;&lt;li&gt;MaxIm DL 5 (For Camera Control)&lt;/li&gt;&lt;li&gt;The Sky6 (For Mount Control)&lt;/li&gt;&lt;li&gt;FocusMax (For Automated Focusing)&lt;/li&gt;&lt;li&gt;CCDSoft (For Plate Solving)&lt;/li&gt;&lt;/ul&gt;My imaging equipment is all portable.  Since I don't have a suitable dark location near my home, I perform all image acquisition at a dark site that is approximately 200 miles away.  Consequently, I don't have any script examples for dome control, weather monitoring equipment, or any other hardware that you would typically find with a permanently mounted setup.  These features can be easily added to your Python script if required.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;MY SCRIPT&lt;/b&gt;&lt;br /&gt;The following is a very top-level description of my script's operation during acquisition of normal, light frames of astronomical objects.  I will be describing in detail how each of these steps is implemented in the course of these posts.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Read in the input text file that specifies the object to image and contains a list of images to take that includes filter, exposure, and number of frames.  (An example of an input text file can be downloaded &lt;a href="http://astro.wt5l.com/blogspot/m1_test.zip"&gt;here&lt;/a&gt;.)  Also, read in other options such as CCD temp, minimum altitude, warm CCD after all images etc. that apply to this set of images.&lt;/li&gt;&lt;li&gt;Find the object in the Sky and wait for object to cross the meridian (since I only image on the west side of the sky).  If the object has already crossed the meridian, continue immediately with the next step.&lt;/li&gt;&lt;li&gt;Locate a suitable focus star (from a preselected list of candidate stars) that is close to the object, slew to the star, plate solve, then resync and reslew if needed. Using FocusMax's focus method, auto-focus through the specified filter then check focus using FocusMax's HFD measurement method.&lt;/li&gt;&lt;li&gt;Slew to the object, plate solve, then resync and reslew if needed to reduce the pointing error.&lt;/li&gt;&lt;li&gt;Expose the guide camera, identify a guide star, begin autoguiding, then wait until guide errors fall below a specified threshold.&lt;/li&gt;&lt;li&gt;Begin acquiring images until the specified number of images have been captured or until it is time to perform the periodic focus check (required after 'x' number of images have be captured).&lt;/li&gt;&lt;li&gt;When the periodic focus check is required, slew to the same focus star used in step 3 (with plate solve, resync, and reslew if required) and check focus using FocusMax's HFD measurement method.  If the HFD has changed by a specified threshold, repeat FocusMax's focus method.  Otherwise, continue directly to the next step.&lt;/li&gt;&lt;li&gt;Slew to the object and plate solve, then resync and reslew if required.&lt;/li&gt;&lt;li&gt;Return to step 6 until all images have been acquired for the current set of images.&lt;/li&gt;&lt;li&gt;For the next set of images return to step 3.&lt;/li&gt;&lt;li&gt;When all image sets have been acquired or when the object drops below the specified altitude, end the script.  If required, warm the CCD to ambient temperature and/or slew the mount to a safe position.&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;b&gt;DISCLAIMER&lt;/b&gt;&lt;br /&gt;The scripts contained in the following posts have been written, debugged, and verified as working by me FOR MY PARTICULAR SETUP only. There are no guarantees that they will work, as is, with anyone else's setup.  Therefore, I assume no responsibility for damage to equipment, lost time, or other losses anyone may incur. Furthermore, the scripts are not guaranteed to be without bugs that I haven't found yet.  Indeed, they probably DO have bugs that I haven't found yet.  The message is: Use the information contained in these posts at your own risk.  The scripts in these posts are meant only as a STARTING POINT for your own scripts and are not meant to be used as is.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;ACKNOWLEDGMENT&lt;/b&gt;&lt;br /&gt;For some time I had been thinking about writing my own scripts even while using CCD Commander on a regular basis.  It wasn't until I ran across the series of three articles in the on-line magazine "AstroPhoto Insight" titled &lt;a href="http://www.astrophotoinsight.com/content/automation-budget-part-1-hardware"&gt;"Automated Imaging on a Budget"&lt;/a&gt; by Joe Ulowetz that I found the jump-start that I needed.  Specifically, at the end of the article, Joe graciously provided a link to allow a download of the complete Python code that controlled the automation described in his article.  By pouring through Joe's code and using it as a starting point, I was eventually able to drop CCD Commander and begin the process of writing, testing, debugging, and, eventually, using my own executive software application.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;WHAT'S NEXT?&lt;/b&gt;&lt;br /&gt;In the next post, I will begin diving into the Python code. Specifically, I'll introduce and begin describing methods contained in the Camera class.  I'll also show how these methods can be easily tested using only the camera simulator built into MaxIm DL.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;&lt;a href="http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python_22.html"&gt;CLICK HERE FOR NEXT POST IN THIS SERIES&lt;/a&gt;&lt;/b&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-2041169047379054887?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/2041169047379054887/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2041169047379054887'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/2041169047379054887'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/automated-astrophotography-with-python.html' title='Automated Astrophotography with Python - Introduction'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8593471911875856420</id><published>2011-05-20T08:24:00.004-04:00</published><updated>2011-05-20T21:41:22.373-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>M5 - Globular Cluster</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/m5_02.php" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="435" width="629" src="http://astro.wt5l.com/astro_images_content/m5_05022011_red.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to go to info page)&lt;/div&gt;&lt;br /&gt;M5 is a large, bright globular cluster located in the constellation Serpens (Caput).  This naked eye object can be found about 20 arc-minutes to the northwest of the 5th magnitude star 5 Serpentis. It's core is extremely compressed and slightly elliptical in shape.  At magnitude 5.7, M5 lies at a distance of approximately 25,000 light years and was discovered in 1702 by Gottfried Kirch.&lt;br /&gt;&lt;br /&gt;The data for this luminance-only photo was captured on 1 May and 2 May 2011.  All images were taken at the Chiefland Astronomy Village in Chiefland, Florida.  Complete details regarding this image and more images of M5 at various resolutions can be found by clicking on the image above or by clicking &lt;a href="http://astro.wt5l.com/m5_02.php"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8593471911875856420?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8593471911875856420/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/m5-globular-cluster.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8593471911875856420'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8593471911875856420'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/m5-globular-cluster.html' title='M5 - Globular Cluster'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-8639935943019131408</id><published>2011-05-19T08:43:00.005-04:00</published><updated>2011-05-20T21:41:35.778-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>M106 - Spiral Galaxy</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/m106_01.php" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="437" src="http://astro.wt5l.com/astro_images_content/m106_05022011_red.jpg" width="629" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to go to info page)&lt;/div&gt;&lt;br /&gt;The spiral galaxy M106 (NGC 4258) is a very large and bright galaxy located in the constellation Canes Venatici.  M106 has a magnitude of approximately 8.3, lies at a distance of around 22 million light years, and was first discovered by Pierre Mechain in 1781.&lt;br /&gt;&lt;br /&gt;The luminance data for the photo above was captured on 4 March 2011 and the color information was collected between 30 April and 2 May 2011.  All images were taken at the Chiefland Astronomy Village in Chiefland, Florida.  Complete details regarding this image and more images of M106 at various resolutions can be found by clicking on the image above or by clicking &lt;a href="http://astro.wt5l.com/m106_01.php"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-8639935943019131408?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/8639935943019131408/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/m106-spiral-galaxy.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8639935943019131408'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/8639935943019131408'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/m106-spiral-galaxy.html' title='M106 - Spiral Galaxy'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-1047899185551425965</id><published>2011-05-18T17:06:00.002-04:00</published><updated>2011-05-18T17:21:30.706-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Spaceflight'/><title type='text'>STS-134 Endeavour SRB Separation</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/p1011061a.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="252" src="http://astro.wt5l.com/photos/sts134/p1011061a.jpg" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to enlarge) &lt;/div&gt;&lt;br /&gt;Approximately one minute thirty seconds after launch the space shuttle SRBs (solid rocket boosters) are separated from the external fuel tank and fall back to the ocean to be recovered and reused.&amp;nbsp; The photo above has been enlarged and enhanced from &lt;a href="http://astro.wt5l.com/photos/sts134/p1011061.jpg"&gt;this&lt;/a&gt; photo to show the back-end of Endeavour, the two SRBs falling away from the external fuel tank, and the exhaust trail that extends backwards along the flight path.&amp;nbsp; Click on the image above to view a larger version of this photo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-1047899185551425965?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/1047899185551425965/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/sts-134-endeavour-srb-separation.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/1047899185551425965'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/1047899185551425965'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/sts-134-endeavour-srb-separation.html' title='STS-134 Endeavour SRB Separation'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-3856647677332962327</id><published>2011-05-18T15:40:00.003-04:00</published><updated>2011-05-18T17:29:07.293-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Spaceflight'/><title type='text'>STS-134 Launch of Space Shuttle Endeavour</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011058.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" src="http://astro.wt5l.com/photos/sts134/P1011058.jpg" style="cursor: pointer; display: block; height: 838px; margin: 0px auto 10px; text-align: center; width: 629px;" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image above to enlarge) &lt;/div&gt;&lt;br /&gt;Here's a collection of photos of the May 16, 2011 launch of Endeavour made from my house which is approximately 30 miles south of the launch pad.  Despite low clouds at the launch pad, the sky was mostly clear at my location.  Click on the links below for other photos of the climb to orbit.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011051.jpg"&gt;Photo 1&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011052.jpg"&gt;Photo 2&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011053.jpg"&gt;Photo 3&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011054.jpg"&gt;Photo 4&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011055.jpg"&gt;Photo 5&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011056.jpg"&gt;Photo 6&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011057.jpg"&gt;Photo 7&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011058.jpg"&gt;Photo 8&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011059.jpg"&gt;Photo 9&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011060.jpg"&gt;Photo 10&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011061.jpg"&gt;Photo 11&lt;/a&gt;&lt;br /&gt;&lt;a href="http://astro.wt5l.com/photos/sts134/P1011063.jpg"&gt;Photo 12&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-3856647677332962327?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/3856647677332962327/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/sts-134-launch-of-space-shuttle.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/3856647677332962327'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/3856647677332962327'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/sts-134-launch-of-space-shuttle.html' title='STS-134 Launch of Space Shuttle Endeavour'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5037516298520460978.post-9091314245170155815</id><published>2011-05-18T13:04:00.006-04:00</published><updated>2011-05-20T21:39:59.182-04:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Messier'/><category scheme='http://www.blogger.com/atom/ns#' term='Astrophotography'/><title type='text'>M63 - The Sunflower Galaxy</title><content type='html'>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://astro.wt5l.com/m63_02.php" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" src="http://astro.wt5l.com/astro_images_content/m63_05022011_red.jpg" style="cursor: pointer; display: block; height: 441px; margin: 0px auto 10px; text-align: center; width: 629px;" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;(Click image to go to info page) &lt;/div&gt;&lt;br /&gt;M63, also cataloged as NGC 5055, is a prominent spiral galaxy in the constellation Canes Venatici.  Due to its flowery appearance in photographs, it is also commonly referred to as the Sunflower galaxy.  M63 shines with the light of over 10 billion suns and sports a disk approximately 86,000 light years in diameter.  This magnitude 8.6 galaxy is located approximately 23.5 million light years away and was first discovered by Pierre Mechain in 1779.&lt;br /&gt;&lt;br /&gt;The photo above was captured between 30 April and 2 May 2011 at the Chiefland Astronomy Village in Chiefland, Florida.  Complete details regarding this image and more images of M63 at various resolutions can be found by clicking on the image above or by clicking &lt;a href="http://astro.wt5l.com/m63_02.php"&gt;here&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/5037516298520460978-9091314245170155815?l=wt5l.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://wt5l.blogspot.com/feeds/9091314245170155815/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://wt5l.blogspot.com/2011/05/m63-sunflower-galaxy.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/9091314245170155815'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5037516298520460978/posts/default/9091314245170155815'/><link rel='alternate' type='text/html' href='http://wt5l.blogspot.com/2011/05/m63-sunflower-galaxy.html' title='M63 - The Sunflower Galaxy'/><author><name>Jon Olson</name><uri>http://www.blogger.com/profile/15359434069681875383</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/-oQu-WfMQL68/TtaVX7u-aWI/AAAAAAAAABM/LeEEQJDxihM/s220/twitter_avatar.JPG'/></author><thr:total>0</thr:total></entry></feed>
