|
|
Automation on a Budget - Part 2: Software

Last time I described the hardware that I have assembled for imaging. Briefly, I have a Celestron C9.25 at f/10 on a Losmandy/Gemini G-11 mount and SBIG ST-402 imager, and use an AT-66 f/6 refractor as a guide scope with an Atik 16ic camera as the guider. There are two major difficulties with my setup: 1) the field of view of my imager on the sky, at a focal length of 2350mm, is rather small, and that makes it difficult to use astrometric software to “solve” images and determine their exact locations, and 2) my mount is not consistent enough to always point exactly where I tell it to, making it hard for the astrometric software to work, since it needs to be close to the intended location, especially with a small field of view. The fact that I don’t have a pier makes this even harder.
Purchased Software
I use two commercial software programs that are very important to my setup:
- MaxIm DL Pro ($499) for camera exposure control and guiding.
- Pinpoint astrometry software ($149) to solve images for exact current location and allow iterative refinement of the position in the sky. Note that while MaxIm includes a simple version of Pinpoint, the full version of Pinpoint is needed to support automation scripting.
In addition, I use several other free software packages, such as FocusMax and some libraries and catalogs freely available online. I’ll detail these later. The various software components interact with each other using a software interface called COM, which stands for Common Object Model. Many, but not all, software programs have a COM interface, even programs like Microsoft Word or Excel. Using this interface, I canwrite a script to, for example, command the telescope to slew to a specified location or tell the camera to take an image. There are many different ways to write such a script, and many different languages available to do this. (There are even commercial packages like ACP or CCD Commander that will handle the automation themselves.) The most common language used for writing scripts is called VB Script; it is a variant of the BASIC computer programming language; another language commonly used for scripting is JavaScript.
Here is a simple example of what a script looks like, using VB Script, to tell MaxIm to start a 10 second exposure, wait until it completes the exposure, and then save the result in a file:
set camera = CreateObject("MaxIm.Camera")
camera.Expose 10,1
Do While Not camera.ImageReady
'wait
camera.SaveImage "MyTestImage.fit"
set camera = Nothing
For various reasons, I decided to write my automation script in a language called Python. The reason I chose to write my own software rather than using something commercially available is partly a matter of the cost of the alternative software packages, but mostly because of the hardware that I use. If I had a high-end mount that always positioned targets precisely and a megapixel camera that saw a large part of the sky for astrometry solving, my software requirements would be straightforward and something like ACP would work great. But my lower-end equipment does not provide these features, so I needed to write custom software to work around the limitations.
Next I’ll describe the parts of my automation software.
Algorithm for positioning scope
In my opinion, the most important aspect of automated imaging is positioning the telescope precisely on the desired target. After all, a great photo with precise tracking of an empty star field will not be very rewarding. The target must be in the camera’s field of view and preferably centered, or the rest of the imaging automation will not have the desired results..
As I mentioned above, my mount will not consistently position a target in the small field of my camera without help. I use the commercial software program Pinpoint to find where the telescope is currently pointing, and iteratively correct its position until it is pointing at the desired target with the desired precision. The complication is that Pinpoint needs to know roughly where it is already looking before it can “solve” an image, and this is hard to do with a small field of view. My solution is to use my guide scope and its large field of view, almost one-half degree across, and first solve that. My mount has no problem positioning well enough for this to work consistently, and then with the guider solution I can get close enough for Pinpoint to solve the main imager for precise centering of the target.
The algorithm to accomplish all of this has several components, so I’ll build it up in parts:
“Basic Solve”
- Take an image with a camera (guider or imager) and save it in a file (Pinpoint needs the image in a file in order to work with it)
- ‘Solve’ the image file with Pinpoint. Pinpoint uses the expected scope location coordinates and image scale to look up stars in a catalog (the Hubble Guide Star Catalog (GSC) is often used here), and match up the stars in the image against the catalog. ‘Solving’ the image means it was able to figure out exactly how the image maps to the sky, including the actual coordinates of the center of the image, usually to a fraction of an arcsecond.
- If the ‘solve’ fails, try doubling the image exposure to try to get more image stars and repeat the ‘solve’. The more stars in an image, preferably at least a few dozen, the better this works.
After a “Basic Solve,” I know where the scope is pointing.. It may not be exactly where I want it, but I can use the solution to adjust the scope’s position.
“Solve Correction”
- Execute “Basic Solve” above
- ‘Sync’ the mount; this tells the mount to use the solved location as its actual current location, rather than the pre-solved location. This doesn’t move the mount; instead, it changes its internal state, similar to pressing the reset button on a car’s odometer.
- Tell the mount to GOTO the original coordinates. This step moves the scope to correct the error in the initial slew, positioning the mount more precisely.
If my initial location is not very close to the desired location, the movement here to correct the position may not be as good as desired. Think of it this way: the closer we are to the desired location from the initial slew, the smaller the correction movement is, and the more likely it is to be precise. To account for this, I rerun the “Solve Correction” if my solved position is more than two arcminutes from the desired location. The threshold of two arcminutes is somewhat arbitrary but works for my scope.
Repeated Solve
- Execute “Solve Correction” steps
- If difference of Solved Position and Desired Position > Two Arcminutes, then repeat.
In practice, I have a limit counter on how many times it can repeat here; I can specify that the software should accept a poor position and continue, or have it abort this target and move on to another one.
Because my main imager has such a small field of view on the sky – a combination of the small chip and the long focal length of the imaging scope – I use my guider for the first “Basic Solve” step. However, since the guide scope and main imaging scope do not remain precisely aligned, I repeat all of this again using the main imager:
Full Solve
After a “Full Solve” step, my imager is precisely centered on the desired coordinates. The only downside of increased precision is the extra time it takes to execute these steps. Solving with my guide camera is relatively quick; a 10 second exposure is sufficient with the f/6 guide scope to capture lots of stars. However, for the main imager at f/10 I typically need more than a minute to get enough stars even when binning the image. Also, with exposures this long, trailing can occur if not guided, so my script automatically guides during the long imager exposure.
For my setup, as a rule, it takes about 5 minutes per target for a “Full Solve”, from the initial command to slew to a target, to the point where the target is centered and ready for imaging.
If I don’t need the target precisely centered, such as when I’m running FocusMax on a star, I’ll skip the “Repeated Basic Solve” for the imager; this cuts the time down to roughly 2 minutes per target, including the initial slew time.
I also want to point out that the solution by Pinpoint can take a variable amount of time, depending on the density of the star field and the number of imaged stars. I’m often lucky enough that the solve takes less than one second, but it can take much longer. Pinpoint will sometimes report that it cannot solve an image, but it may also continue trying until stopped. I use 60 seconds as the cutoff for solving a guider image, and 90 seconds for solving a main imager exposure. From my testing, if an image hasn’t solved in this amount of time, it probably won’t solve at all. I’m willing to accept a long time here for the attempt because I’d much rather have a solution than not know where the scope is actually pointing.
If the “Repeated Solve” of the imager camera fails, I’ll often proceed with imaging anyway, because I might get lucky and have the image centered well-enough. Some fields won’t solve for me because they have too many stars (M13) or have a non-stellar object filling the image (M31).
If the “Repeated Solve” of the guider fails, I usually won’t proceed with imaging the current target. This can happen if I misestimated how high in the sky the target would be when imaging occurs and I’m actually pointing at a tree; or there could be a cloud blocking the object. But there is a chance that the next target might be better-placed, or in a part of the sky without clouds, so my script will skip the current target and try the next one when I can’t solve the guider image of a target.
To use this class, create an instance and give it a value, and then later reference it as desired. Here is a code fragment where I set the catalog (J2000) position, and then tell the mount, which requires JNow coordinates, to slew to it:
pos = Position() #create an instance of the class
pos.setJ2000(18.8933, 33.033, ”M57”) #initialize w/ J2000 coords
MOUNT.SlewToCoordinates(pos.RA_JNow(), pos.Dec_JNow())
Specifying a Target
There are several ways that I can tell my software where I want to image. I can provide specific RA/Dec coordinates, or I can give it a catalog name to look up automatically to find the coordinates. In the future I plan to add support for calculating asteroid or comet locations from orbital elements, using the KEPLER orbit engine software available from the nice people who wrote ASCOM, especially Bob Denny.
Since transcribing RA/Dec coordinates can be tedious and error-prone, I typically use a catalog name to indicate a target. The catalog that I use is the MiniSAC Deep Sky Object Database, also available from the ASCOM site. This contains over 70,000 objects, and there is a simple procedure that will add any additional objects to it if desired. I will go over the topic of target selection in detail in the last article of this series.
Focusing
Focusing is done using the free software FocusMax. It works extremely well, and also supports ASCOM so it can easily be controlled by automation software.
One difficulty that I have noticed in running FocusMax with my equipment is that it occasionally converges on the wrong focus location, resulting in obviously unfocused images. (This might be happening when poor seeing confuses the algorithm looking at the out-of-focus star images.) I deal with this by having my automation software run FocusMax more than once, and require that I get two results in a row that are close to each other.
Focusing works best with a star between magnitude 4 and 6. To automate this, I used TheSky6 and its Data Wizard feature to extract a list and save the data as a text file. The list contains 1200 stars in this brightness range all over the sky visible at my location. When my automation software starts, it reads the focus star file into memory for quick access. When I want to choose a focus star, I specify a target, such as M57, and search for the closest focus star in the list to this location.
Technically, I take a subset of stars within one hour RA of the target, and then calculate the coordinate difference between each star and the desired location, and choose the smallest difference found. Rather than using spherical trigonometry, I just calculate
distance = abs(RAstar – RAtarget) + abs(DECstar – DECtarget)
My software has to deal with the possibility that FocusMax reports a failure when trying to focus. This could happen from something as simple as a moment of turbulence that confused it, or a missing star due to a catalog error, or a passing cloud. After suffering from poor focus in some of my automated imaging sessions, I decided that I need a successful focus regardless of the effort. Therefore I will repeat the attempt a few times on a star, but if I just cannot focus on it my software will mark that star as bad to prevent it from being used again, and then find another star and try focusing on that.
The focusing attempts continue until success, or until the sun reaches an altitude where the sky will be too bright to continue. I would rather spend all night focusing than having spent a night taking out-of-focus images.
(An enhancement that I am currently working on here is to raise an alarm if I get several focus failures in a row. This might indicate that the sky has clouded up, and it could be a good idea for me to cover up my telescope rather than risk having it rained on.)
As temperature changes during the night, the focus position in most telescopes can shift. So even if I focus precisely early in the evening, I’ll probably need to refocus later on. I have a couple of features to handle this. First, I measured the response of my telescope’s focus to changing temperature, and try to estimate what the correct focus should be based on the current temperature. This is the same approach the Robofocus software uses, although mine is controlled from my script so the focuser only moves when I am between image exposures. I found this does not work well for me; there is a large temperature drop early in the evening before it becomes linear, so I’ve stopped using this. Instead, I would just insert a Focus command every couple of hours into my target script for that night.
Lately, I’m letting the temperature change (as reported by Robofocus) determine when to refocus. The approach is:
After each exposure finishes:
- Compare Robofocus temperature to value from last refocus
- If temperature is different and more than 1 hour since last refocus:
- Pause the current target imaging.
- Take a guider image of current field, solve it; save this value.
- Locate closest focus star to current target, GOTO this star
- Run FocusMax on this star.
- Slew back to the location of the previous field, using guider images
- Reset temperature and time variables for next time
- Resume imaging
Although the guider and imager are not necessarily precisely aligned, this technique uses the fact that any difference will probably stay constant during the movement to a nearby focus star and return, so I can precisely reposition on a target without having to take the much longer time needed for exposing a Pinpoint image with the main camera.
Logging
The script outputs several different log files to help me understand what took place during the session. For example, every time I run the autofocus software, I record the results in a focus log file. The reason I write several different logs is to keep similar data together to make it easier to review for patterns. One of my favorite logs is a simple one that lists the objects actually imaged during the night, and the total exposure on each one. Here is just a part of a recent summary log, showing the filter, binning, exposure (seconds) and number of exposures taken.
Target F b Exp cnt Obs-Date
------------- -- - --- --- ----------
HCG 1 ,L ,1,300, 1,2009-09-11
IC1340 ,L ,1,300, 12,2009-09-11
IC1584 ,L ,1,300, 1,2009-09-11
IC1656 ,L ,1,300, 1,2009-09-11
NGC7538 ,B ,2,300, 2,2009-09-11
NGC7538 ,G ,2,300, 2,2009-09-11
NGC7538 ,L ,1,300, 4,2009-09-11
NGC7538 ,R ,2,300, 2,2009-09-11
NGC818 ,L ,1,300, 1,2009-09-11
…
I accumulate all of these daily reports into a history file that I periodically summarize to tell me the total exposure on each target, as well as on which dates I imaged it. Once I have sufficient exposures on a target, I can then stack the different sessions and do further processing on the result.
Software failures during operation
Like any software program, my automation script can sometimes encounter a problem and shut down. Since I run mostly while I sleep, I stand the chance of losing most of the night if I’m not aware of the problem and then resolve and restart my software.
Like many programming environments, Python supports an “exception” mechanism where one part of a program can “raise” or “throw” an exception, and another part can “catch” it and take some action. Exceptions can come either from a programming error, such as a misspelled variable name, or can be explicitly written into my code if it detects a major problem, such as being unable to plate solve several fields in a row, which might indicate that clouds have moved in and there is a danger of rain.
I have essentially all of my automation logic wrapped in an exception handler, and when an exception occurs, the handler plays an annoying beep over and over again until I get up and shut it off. I don’t enjoy being woken up, but as long as I’m careful with software changes this doesn’t happen very often.
Also, using a hardware solution for monitoring the weather would probably be better, but I’m doing this on a budget after all.
Dealing with trees (the kind that grow in my yard — and yes, this is about software):
A short aside here. My observing location has a terrible horizon. I cannot image south of approximately 20 degrees north declination, so there are many nice objects, including most of the ecliptic, that I cannot reach.
Therefore, I decided to concentrate on the part of the sky that I can see, and to make a list of potential targets I could go after in detail. From my experience, targets smaller than 1 arcminute won’t show much detail in my images so I concentrate on larger targets. Also, going fainter than 14th magnitude starts to bring in a huge number of small galaxies, so I decided to limit my initial list to 14th magnitude and 1 arcminute or larger. Using the DataWizard feature in TheSky6, I extracted a list of objects meeting those criteria including being north of declination +20.
I have approximately 1700 objects in my list.
In order to determine which ones will make for interesting targets to try imaging in detail, I decided to do an initial survey of all 1700 targets, taking a single 5 minute exposures of each one. I started doing this in September 2008 but I didn’t fully automate it until the following February; I’ve done over 1400 of the targets so far. Since my positioning logic (described above) takes about 5 minutes per target and I expose for another 5 minutes, I’m spending 10 minutes per target. That’s 6 an hour. If I can average 6 hours a night, I could complete the entire survey in fewer than 50 nights. At this point, I’m mostly limited by waiting for the sun to move away from the Winter constellations so that I can complete that area.
Five minutes of exposure isn’t enough to make a pretty picture, but it is enough to give an indication of what a target looks like. Below is an example of one of my survey images, a single 300 second exposure of the planetary nebula NGC 6894 in Cygnus. Only dark and flat calibration was applied to this image. I wasn’t familiar with this planetary nebula before; it was just one of the 1700 names I identified using TheSky6 that fit my criteria. I think this nebula looks interesting and I will definitely follow up with deeper exposures.
At first I was manually editing the target list for each night’s session, but I didn’t always estimate correctly what time I’d get to a certain object during the night. Sometimes a target was still behind a tree, and I’d waste the exposure.
My solution was to let the software decide which targets were best placed for imaging at any given moment. Here is my solution:
- Read in my master target list into my program, with objects sorted in RA. Ignore any entries that I marked as previously imaged so I don’t repeat them.
- Get the current sidereal time. Add 1 hour to it so I have a location slightly east of the meridian.
- Search my list of targets for the first unimaged object with an RA larger than the test value.
- When the object is found, calculate the current altitude/azimuth of that target’s coordinates for the current time. (This uses the NOVAS-COM Vector Astrometry Engine.) I’ve previously measured my local horizon so I know the approximate altitude/azimuth to compare with the target’s value, to decide if it is visible to me, or behind a tree. If it is visible, image it.
- If the target found is too far east (below my local horizon), I then check the western sky to find the highest target not imaged yet there.
- If no targets are current available, I then move to stage 2. For the targets I’ve already imaged, I’ve previously noted which ones looked interesting when I reviewed the survey images (such as NGC 6894 above). Stage 2 target selection reads from this list of previously imaged objects that I have marked as wanting to image in detail. My software finds one high in the sky using the same algorithm as used with the survey target selection. At present, when a Stage 2 target is selected, I spend an hour taking either luminance images of this target, or a set of LRGB images that add up to an hour if the object appears bright and large enough that I think it might show interesting color. This will be repeated automatically over multiple nights to build up a very long total exposure.
So now when I image, unless there is some specific target I’m interested in that night, I just start my program in full automation mode and let it pick the targets for that night. I currently have over 300 objects identified for Stage 2 imaging, and I’m adding more all the time.
I haven’t processed much of the Stage 2 data yet because I’ve been concentrating on getting the automation software working. But now that it is running fairly well, I can transition to processing the raw images.
Here is another example of a target I identified in my survey that I wasn’t aware of before: NGC 3198 in Ursa Major. Working on just the single 300 second survey image in Photoshop, the galaxy shows some nice detail.
Notice that the galaxy is off-center in this image. I’m finding some entries in the MiniSAC catalog are off slightly from what I see in my images. An additional project I’m currently doing is using Pinpoint to independently measure the coordinates for each of my survey images, as long as the object has a distinct center such as planetary nebulae and most galaxies. When completed, I’ll make the corrected locations available to any MiniSAC users interested. These corrections will also insure that my Stage 2 imaging will be properly centered in all cases.
I’ve actually only touched on the complexity of actually operating my automation software so far. Next time, in the final installment of this series, I’ll go over operational techniques I’ve developed to get the best results I can from my setup, including examples of the command files I use to run my automation software for an imaging session.
Coordinates: JNow vs J2000
By necessity, I have to work with two different coordinate systems at the same time: all catalog entries are in J2000, while commands to the telescope and its reported position are in JNow (the equinox of the current date). The two coordinate systems have values within a few arcminutes of each other, but even as few as 5 arcminutes can make the difference between a centered target and one on the edge of the frame in a camera with a field of view of 10 arcminutes. Therefore the difference in coordinate systems is important, but I initially had trouble in my code, keeping track of which coordinates were which type when I stored a location in a variable and later referenced it. I came up with a coding technique that greatly helped me here. (Feel free to skip the rest of this section if you don’t like reading software).
I created a “class” object that I called Position that represented some position in the sky, regardless of what coordinate system was used. After all, M57 doesn’t care what coordinates I use to reference it; it’s still going to be the same place in the sky regardless.
For Position, I created “getter” and “setter” methods, and I can “get” or “set” a Position instance using either JNow or J2000. The “get” of a value in a specific coordinate system is independent of which system was used to “set” it. During a “set” method call I convert the supplied coordinates into the other coordinates, and save both types.
Below is part of the Python implementation of my Position class. The real work is done in the “setter” methods where precession between coordinate systems is handled by calls to the NOVAS library:
class Position:
def __init__(self):
self.__RAJ2000 = None
self.__DecJ2000 = None
self.__RAJNow = None
self.__DecJNow = None
self.posName = "n/a" #optional name of object this coord is for
self.isValid = Falsedef setJ2000(self,RAJ2000,DecJ2000,name=None):
self.__RAJ2000 = RAJ2000
self.__DecJ2000 = DecJ2000
self.__RAJNow, self.__DecJNow = PrecessJ2000ToLocal(RAJ2000,DecJ2000)
self.posName = name
self.isValid = Truedef setJNow(self,RAJNow,DecJNow,name=None):
self.__RAJNow = RAJNow
self.__DecJNow = DecJNow
self.__RAJ2000, self.__DecJ2000 = PrecessLocalToJ2000(RAJNow,DecJNow)
self.posName = name
self.isValid = Truedef getJ2000(self):
return(self.__RAJ2000, self.__DecJ2000) #returns a ‘tuple’def getJNow(self):
return(self.__RAJNow, self.__DecJNow) #returns a ‘tuple’#sometimes need these values separately
def RA_J2000(self):
return self.__RAJ2000
def Dec_J2000(self):
return self.__DecJ2000
def RA_JNow(self):
return self.__RAJNow
def Dec_JNow(self):
return self.__DecJNowdef PrecessJ2000ToLocal(dJ2000RA, dJ2000Dec):
# inputs: dJ2000RA, dJ2000Dec decimal values in J2000
# outputs: tuple: (dLocalRA, dLocalDec) decimal values in Local Epoch
objv = win32com.client.Dispatch("NOVAS.Star")
objv.RightAscension = dJ2000RA
objv.Declination = dJ2000Dec
#objv.DeltaT handled by Novas#lat/long are only going to matter for close solar system objects,
# but this is required for the conversion function
site = win32com.client.Dispatch("NOVAS.Site")site.Latitude = 42
site.Longitude = -87
site.Height = 190
site.Temperature = 10#Get julian date for current moment in time
t = time.gmtime( time.time() )
epoch = jd( t[0], t[1], t[2], t[3], t[4], t[5])doRefraction = True #won't matter much, but might as well use it
tvec = objv.GetTopocentricPosition(epoch, site, doRefraction)
dLocalRA = tvec.RightAscension
dLocalDec = tvec.Declination
tup = (dLocalRA, dLocalDec) #return multiple values in a tuple
del objv #close the library objects opened above
del site
return tupdef PrecessLocalToJ2000(dJNowRA, dJNowDec):
#this treats the incoming coords as J2000 and precesses
#them to JNow, and calculates the difference that the precession
#caused; then reverses the sign of this difference and applies to
#incoming coords, with the idea that the precession should be
#roughly symmetric
# diff = JNow - J2000
# J2000 = JNow - diff
tup = PrecessJ2000ToLocal(dJNowRA,dJNowDec)
#yes, give it JNow coords!
delta_RA = dJNowRA - tup[0] #reverse the direction of the correction
delta_Dec = dJNowDec - tup[1]
dJ2000RA = dJNowRA + delta_RA
dJ2000Dec = dJNowDec + delta_Dec
return (dJ2000RA,dJ2000Dec)def jd(yy, mm, dd, hr, mn, sec): #calculate Julian Date
if yy < 0: yy = yy + 1
hr = hr + (float(mn) / 60) + float(sec)/3600
ggg = 1
if yy <= 1585: ggg = 0
JD = -1 * (7 * (((mm + 9) // 12) + yy) // 4)
s = 1
if (mm - 9) < 0: s = -1
a = abs(mm - 9)
j1 = math.floor(yy + s * (a // 7))
j1 = -1 * (((j1 // 100) + 1) * 3 // 4)
JD = JD + (275 * mm // 9) + dd + (ggg * j1)
JD = JD + 1721027 + 2 * ggg + 367 * yy - 0.5
JD = JD + float(hr)/24
JD = round(JD, 5)
return JD
Public articles
- SBIG STX Beta Report
- Automation on a Budget - Part 3: Operation
- Automation on a Budget - Part 2: Software
- Object list for August/September 2009
- Object list for June/July 2009
- Tips and Tricks: Photographing the Perseid Meteor Shower by Fred Bruenjes
- Automation on a Budget - Part 1: Hardware
- 2009 Camera Buyer's Guide
- Astrophoto Live Chat
- Bareket Observatory Outreach
- AstroPhoto Insight Membership Options
- 2008 NEAIC/NEAF Recap
- NEAIC & NEAF 2008 Pictures and Videos
- Reader Images from the Flickr AstroPhoto Insight Group
- Testimonials




