To install, simply download the repository into any directory you'd like, and make sure that you have all of the requirements from the requirements.txt file installed as well. Python >= 3.8 is required. The code has been optimized for a Windows environment and there has been no testing done on other OSs.
You can also use:
git clone https://github.com/Kakon24/omegalambda
Also make sure you install the module by running pip install .
from your omegalambda
directory.
This will allow you to import it from anywhere and run the CLI from anywhere. We currently don't have any unit tests to run (possibly a future goal), since the scale of the project is relatively small
and easy to manage, but you are free to inspect the code to see its inner workings. The weather module can also be run independently since
it does not depend on any hardware connections.
Please note that since the code requires Python >= 3.8, you may wish to make a separate environment for this module, whether that be a conda environment or a Python virtual environment.
The config
module contains basic configuration files for the code in the .json
format. Each
configuration parameter is explained in the docstring for said parameter's software object, and also later
in the relevant sections of this readme. The parameters_config
file is for general code configurations,
logging
is for the logging module, and fw_config
is for the filter wheel positions.
The logger
module is pretty self-explanatory, it handles the logging messages and log file writing of the code.
We use a rotating file handler, keeping backups of the previous 9 logs before overwriting the oldest ones.
The logger can be initiated by this simple example:
from omegalambda.logger.logger import Logger
log = Logger(r'C:\Users\[username]\omegalambda\omegalambda\config\logging.json')
And that's it! A log file will now be automatically generated until the logger is stopped by calling Logger.stop()
.
The main/common/IO
directory includes the main file reader object (Reader
) that converts these .json
files into Python dictionaries. These are then fed to the ObjectReader
which sorts them into the appropriate
datatype to be deserialzed. One of these objects is the Config
class in config_reader.py
.
It has the following attributes:
cooler_setpoint : INT, FLOAT, optional
Setpoint in C when running camera cooler. Our default is -30 C.
cooler_idle_setpoint : INT, FLOAT, optional
Setpoint in C when not running camera cooler. Our default is +5 C.
cooler_settle_time : INT, optional
Time in minutes given for the cooler to settle to its setpoint. Our default is 5-10 minutes.
maximum_jog : INT, optional
Maximum distance in arcseconds to be used for the telescope jog function. Our default is 1800 arcseconds.
site_latitude : FLOAT, optional
Latitude at the telescope location. Our default is +38.828 degrees.
site_longitude : FLOAT, optional
Longitude at the telescope location. Our default is -77.305 degrees.
site_altitude : FLOAT, optional
Altitude above sea level at the telescope location. Our default is 154 meters.
humidity_limit : INT, optional
Limit for humidity while observing. Our default is 85%.
wind_limit : INT, optional
Limit for wind speed in mph while observing. Our default is 20 mph.
weather_freq : INT, optional
Frequency of weather checks in minutes. Our default is 10 minutes.
cloud_cover_limit : FLOAT, optional
Limit for percentage of sky around Fairfax to be covered by clouds before closing up.
Our default is 75%.
cloud_saturation_limit: FLOAT, optional
Minimum pixel value that represents a clouds in the satellite image. Our default is 100.
rain_percent_limit: FLOAT, optional
Limit for the percentage of rain present in 1/4 of the field surveyed before shutting down (two tiles
out of the four must pass this threshold). Our default is 5%.
user_agent : STR, optional
Internet user agent for connections, specifically to weather.com. Our default is Mozilla/5.0.
cloud_satellite : STR, optional
Which satellite to use to check for cloud cover. Currently only supports goes-16. Our default is goes-16.
weather_api_key : STR, optional
The api key to search for in weather.com's api. Sometimes changes and needs an update. Should be a regex
search string.
min_reopen_time : INT or FLOAT, optional
Minimum wait time to reopen (in minutes) after a weather check has gone off. Our default is 30 minutes.
plate_scale : FLOAT, optional
CCD camera conversion factor between pixels and arcseconds, in arcseconds/pixel. Our default is
0.350 arcseconds/pixel.
saturation : INT, optional
CCD camera saturation limit for exposure in counts. This is more like the exposure linearity limit, after
which you'd prefer not to have targets pass. Our default is 25,000 counts.
focus_exposure_multiplier : FLOAT, optional
Multiplier for exposure times on focusing images. The multiplier is applied to the exposure time for the
current ticket. Our default is 0.33.
initial_focus_delta : INT, optional
Initial number of steps the focuser will move for each adjustment. Our default is 15 steps.
focus_temperature_constant : FLOAT, optional
Relationship between focuser steps and degrees Fahrenheit, in steps/degF. Our default is 2 steps/degF.
focus_iterations : INT, optional
The total number of exposures to take at the beginning of the night while focusing. Our default is 11.
focus_adjust_frequency : FLOAT or INT, optional
How often the focus will adjust over the course of the night, in minutes. Our default is 15 minutes.
focus_max_distance : INT, optional
Maximum distance away from the initial focus position that the focuser can move. Our default is 100 steps.
guiding_threshold : FLOAT, optional
How far to let a star drift, in arcseconds, before making a guiding correction. Our default is 10
arcseconds.
guider_ra_dampening : FLOAT, optional
Dampening coefficient for guider telescope corrections on the RA axis. Our default is 0.75.
guider_dec_dampening : FLOAT, optional
Dampening coefficient for guider telescope corrections on the Dec axis. Our default is 0.5.
guider_max_move : FLOAT, optional
The maximum distance in arcseconds that the guider can make adjustments for. Our default is 30 arcseconds.
guider_angle : FLOAT, optional
The clocking angle of the CCD camera's x and y axes against the RA and Dec axes of the telescope, in
degrees. This is defined as the angle between the +x axis and the +RA axis, by rotating counterclockwise
in the reference frame where RA increases to the left and Dec increases upwards.
(i.e. counterclockwise angles in this frame are positive, while clockwise angles are negative).
0.0 degrees corresponds to alignment between +x/+y and +RA/+Dec. Our default is 180 degrees.
guider_flip_y : BOOL, optional
This supports guider axes configurations that are mirrored with respect to a simple guider angle flip.
If True, this will flip the y axis of the guider.
This would correspond to configurations that, at a 0 degree guider angle, would have +x aligned with +RA
while +y is aligned with -Dec. Or similarly if the guider angle is 180 degrees, +x aligns with -RA while
+y aligns with +Dec. Our default is False.
data_directory : STR, optional
Where images and other data are saved on the computer. Our default is
H:/Observatory Files/Observing Sessions/2020_Data.
calibration_time : STR, optional
If darks and flats should be taken at the start or end of a given observing session. Can be str "start"
or "end." If "start", it will take darks and flats for ALL observing tickets at the start of the night.
If "end", it will take darks and flats for all FINISHED tickets at the end of the night.
Our default is "end".
calibration_num : INT, optional
The number of darks and flats that should be taken per target. Note that there will be one set of flats
with this number of exposures, but two sets of darks, each with this number of exposures: one to match
the flat exposure time and the other to match the science exposure time. Our default is 10.
As you can see, this object contains general configuration parameters that affect nearly every aspect of how
the code runs. The only methods associated with this object are the serialized()
and deserialized()
methods,
the latter of which is a staticmethod used to convert json.loads
objects into Config
objects, and
the former of which is used to convert a Config
object back into a Python dictionary. These two methods
are common throughout the FilterWheel
and ObservationTicket
objects as well.
The main/common/datatype
folder contains
the software implementations of some of the other input datatypes. Among these are the FilterWheel
type, with the
following attributes:
position_1 - position_8 : str
Each position will be the name/label of the filter in said position (i.e. "r" for red filter).
For readability, each position is labeled by a str "position_X" rather than just an int X.
and the ObservationTicket
type. Observation Tickets are the method by which our code is instructed to
observe a certain target on a given night. As such, an observation ticket object contains all the necessary
information about a target for the code to collect data on it. ObservationTicket
objects have the following
attributes:
name : str, optional
Name of intended target, ex: TOI1234.01 . The default is None.
ra : float, str, optional
Right ascension of target object. The default is None.
dec : float, str, optional
Declination of target object. The default is None.
start_time : str, optional
Start time of the observations. The default is None.
end_time : str, optional
End time of the observations. The default is None.
_filter : str or list, optional
List of filters that will be used during observing session. Must be one of the following:
"uv", "b", "v", "r", "ir", or "Ha". The default is None.
num : int, optional
Number of exposures. The default is None.
exp_time : float or list, optional
Exposure time of each image in seconds. List order must match the order of filters.
The default is None.
self_guide : bool, optional
If True, self-guiding module will activate, keeping the telescope
pointed steady at the same target with minor adjustments. The default is None.
guide : bool, optional
Currently unused. The default is None.
cycle_filter : bool, optional
If True, filters will be cycled through after each exposure, if False will take
the num of specific images in one filter before moving to the next filter.
The default is None.
ObservationTicket
objects have an additional method associated with them called check_ticket()
.
This is used to validate that all parameters of the ticket are in the proper format, can be read by the code,
and have valid values. This generally should not be an issue if one utilizes the built-in observation ticket
creator widget, located in observation_tickets/
. This GUI is created using tkinter
and allows for easy
creation of observation tickets.
import omegalambda as om
# Read the config file in via the json reader
reader = om.Reader('C:\Users\[username]\omegalambda\omegalambda\'
+ 'config\parameters_config.json')
# Sort the object into the correct type (in this case, Config)
obj_reader = om.ObjectReader(reader)
# The global object is automatically created! Now you can instantiate any hardware class.
HOWEVER, it is not necessary to undergo this procedure, as it is done automatically upon importing the OmegaLambda package. If you wish to read the config file from a different directory from the default, you will need to perform this step with your preferred directory. The filter wheel configuration file may be read in in the exact same manner.
The Hardware
class itself overwrite threading.Thread
's __init__
and run
methods to create a
queue system for placing methods on the queue list for a hardware object.
A concrete example will be provided for the Camera
module.
import omegalambda as om
# Initialize the camera object (does not connect to the hardware yet)
camera = om.Camera()
# Start the camera thread (actually calls camera.run). When the thread is started, it will
# automatically try connecting to the hardware.
camera.start()
# If the hardware connection was successfully established, you should get a logging
# message saying so. Then, you can put methods on the camera queue by using onThread():
camera.onThread(camera.expose, 15, 2)
# You can then disconnect from the hardware and stop the thread
camera.onThread(camera.disconnect)
camera.onThread(camera.stop)
It reads images from the camera and calculates the FWHM of the brightest stars to determine the focus quality. It then moves the focus to a new position and makes another measurement. After ~10 of these measurements, it fits the data to a parabola (with a positive quadratic term) and moves the focus to the minimum of the parabola (that is, if the fit succeeded...it doesn't always).
The gradual focusing uses a linear temperature-dependence model to determine how much to move the focus over the course of the night.
The FocusProcedures
object thus requires the Focus
, Camera
, and Conditions
objects
as input parameters.
It reads in images from the camera and determines where the stars are. It then waits for the next image and calculates the displacement of the stars between images, then instructs the telescope to move back to correct the displacement.
The Guider
object thus requires the Camera
and Telescope
objects as input parameters.
It reads in the target's exposure times/filters and gathers the necessary calibration images while turning on the flatfield lamp for flats and turning it off for darks. The calibration module assumes that the dome is closed when it has been called, as it does not force the dome to close itself.
The Calibration
object thus requires the Camera
and FlatLamp
objects as input parameters, as
well as the image directories for each target.
An example of how the code would be started using the obesrvation run module looks like this:
# Read in your observation ticket
reader = Reader('observation_ticket_example.json')
object_reader = ObjectReader(reader)
# Prepare the observation ticket and image write directory
observation_request_list = [object_reader.ticket]
folder = 'C:\Useres\[username]\example_image_directory\'
# Instantiate the observation run object
run_object = ObservationRun(observation_request_list, folder, shutdown_toggle=True,
calibration_toggle=True, focus_toggle=True)
# Call the observe() function to begin
run_object.observe()
If you simply run python -m omegalambda -h
, you will receive a message showing you the available functions
(of which there is only one):
usage: __main__.py [-h] {run} ...
Telescope automation code
positional arguments:
{run}
run Start an observation run
optional arguments:
-h, --help show this help message and exit
And if you run python -m omegalambda run -h
, you will see all of the optional arguments for the run
function:
usage: __main__.py run [-h] [--data PATH] [--config PATH] [--filter PATH] [--logger PATH]
[--noshutdown] [--nocalibration] [--nofocus]
obs_tickets [obs_tickets ...]
positional arguments:
obs_tickets Paths to each observation ticket, or 1 path to a directory with
observation tickets.
optional arguments:
-h, --help show this help message and exit
--data PATH, -d PATH Manual save path for CCD image files.
--config PATH, -c PATH
Manual file path to the general config json file.
--filter PATH, -f PATH
Manual file path to the filter wheel config json file.
--logger PATH, -l PATH
Manual file path to the logging config json file.
--noshutdown, -ns Use this option if you do not want to shutdown after running
the tickets. Note this will also stop the program from taking
any darks and flats if the calibration time is set to end.
--nocalibration, -nc Use this option if you do not want to take any darks and flats.
--nofocus, -nf Use this option if you do not want to perform the automatic
focus procedure at thebeginning of the night. Continuous
focusing will still be enabled.
So, a few examples. Say you want to run your observation ticket with all of the defaults. You would simply do
python -m omegalambda run '/path/to/observation/ticket.json'
.
If you have multiple tickets, just add them on:
python -m omegalambda run '/path/to/observation/ticket1.json' '/path/to/observation/ticket2.json'
.
However, if you already took dark and flat images beforehand, you would instead want to run
python -m omegalambda run -nc '/path/to/observation/ticket'
.
Or, if you are already confident about the focus of your target, or want to focus manually:
python -m omegalambda run -nf '/path/to/observation/ticket'
.
Finally, if you'd like to continue using the telescope after the observing sequence:
python -m omegalambda run -ns '/path/to/observation/ticket'
.
These arguments can be stacked of course, so if you are in a situation where all 3 of the above apply:
python -m omegalambda run -nf -ns -nc '/path/to/observation/ticket'
.