astra.action_configs#
Action configuration dataclasses for observatory operations.
- Key capabilities:
Define structured configurations for various observatory actions
Validate required fields and types for action parameters
Provide defaults from observatory configuration
Support dictionary-like access to action configuration fields
Functions
|
Return how many hours of ephemeris an action needs. |
|
Return the declared default of an action config field. |
|
Return the fields of an action config that belong in an action value. |
Classes
Configuration for automated autofocus calibration field selection. |
|
|
Perform an autofocus sweep to determine the optimal focus position. |
Base class for action configurations. |
|
|
Calibrate guiding parameters using timed guide pulses. |
|
Capture a sequence of calibration images (bias/dark). |
Close the observatory safely. |
|
Complete FITS headers after exposures finish. |
|
Configuration for the |
|
|
Capture a sequence of sky flats as the sky brightness evolves. |
|
Capture a sequence of light frames. |
Open the observatory for observations. |
|
|
Aid building a telescope pointing model. |
|
- astra.action_configs.ephemeris_window_hours(start_time: Time, end_time: Time) float[source]#
Return how many hours of ephemeris an action needs.
The window covers the action itself and a margin past its end.
- Parameters:
start_time – Start of the action.
end_time – End of the action.
- Returns:
float – Length of the ephemeris window in hours.
- astra.action_configs.public_fields(config) list[source]#
Return the fields of an action config that belong in an action value.
Class variables such as FIELD_DESCRIPTIONS and EXAMPLE_SCHEDULE, private attributes, and fields that are not constructor arguments are left out.
- Parameters:
config – An action config class or instance.
- Returns:
list – The dataclass fields that a user can set in an action value.
- astra.action_configs.field_default(config, name: str)[source]#
Return the declared default of an action config field.
- Parameters:
config – An action config class or instance.
name (str) – Name of the field.
- Returns:
The default value, or None if the field has no default.
- class astra.action_configs.BaseActionConfig[source]#
Bases:
objectBase class for action configurations.
This class serves as a base for specific action configurations, providing validation and dictionary-like access to its fields. It supports type validation, required fields, and merging with default values, centralizing common functionality for all action configurations and ensuring that the action values passed by the user are valid.
Examples
>>> from astra.action_configs import AutofocusConfig >>> autofocus_config = AutofocusConfig(exptime=3.0) >>> exptime in autofocus_config True >>> autofocus_config['exptime'] 3.0 >>> autofocus_config.get('not_available')
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- classmethod from_dict(config_dict: dict, default_dict: dict = {}, logger=None)[source]#
Create an instance from a dictionary, merging with defaults.
- classmethod defaults_from_observatory_config(device_name: str, device_type: str = 'Camera', observatory_config: object | None = None) dict[source]#
Retrieve default values for this action from the observatory configuration.
Returns a dict suitable for passing as default_dict into from_dict.
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- get(key: str, default=None)[source]#
Get attribute value by key with optional default.
- Parameters:
key – Attribute name to retrieve.
default – Value to return if attribute is not found.
- Returns:
Attribute value or default if not found.
- validate_filters(filterwheel_names: dict[str, list[str]]) None[source]#
Validate that filter(s) exist in the available filterwheels.
- Parameters:
filterwheel_names – Dict mapping filterwheel device names to lists of filter names. e.g., {“fw1”: [“Clear”, “Red”, “Green”, “Blue”]}
- Raises:
ValueError – If a filter is specified but doesn’t exist in any filterwheel.
- validate_subframe() None[source]#
Validate subframe parameters.
- Raises:
ValueError – If subframe parameters are invalid.
- validate_visibility(start_time: Time, end_time: Time, observatory_location: EarthLocation, min_altitude: float = 0.0, nonsidereal_supported: bool | None = None)[source]#
Validate that the target is visible during the scheduled observation window.
Checks target visibility at the beginning, middle, and end of the planned observation to ensure the target remains observable throughout.
Only implemented for object actions; override in subclasses as needed.
- has_subframe() bool[source]#
Check if subframing is enabled.
- Returns:
True if subframe_width and subframe_height are specified, False otherwise.
- classmethod merge_config_dicts(config_dict: dict, default_dict: dict) dict[source]#
Merge default_dict and config_dict, keeping only keys in dataclass.
- to_jsonable() dict[source]#
Return this config as a JSON-serializable action value.
Only the fields a user can set are included. A nested config marked with
flattenmetadata is merged into the parent dict, because that is the layout from_dict reads back.- Returns:
dict – The action value for this config.
- class astra.action_configs.OpenActionConfig[source]#
Bases:
BaseActionConfigOpen the observatory for observations.
- Steps:
Opens dome shutter
Unparks telescope
Cools camera
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'open', 'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.CloseActionConfig[source]#
Bases:
BaseActionConfigClose the observatory safely.
- Steps:
Stop any active guiding operations
Stop telescope slewing and tracking
Park the telescope
Park the dome and close shutter
Cools camera
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'close', 'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.CompleteHeadersActionConfig[source]#
Bases:
BaseActionConfigComplete FITS headers after exposures finish.
Uses paired device polled data to fill in FITS header fields that were unavailable at exposure time. Automatically executed at the end of every schedule.
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'complete_headers', 'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.CoolCameraActionConfig[source]#
Bases:
BaseActionConfigConfiguration for the
cool_cameraschedule action.Activates the camera cooler and sets the target temperature with specified tolerance and timeout from observatory configuration.
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'cool_camera', 'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.ObjectActionConfig(object: str, exptime: float, ra: float | None = None, dec: float | None = None, alt: float | None = None, az: float | None = None, lookup_name: str | None = None, tle: str | None = None, filter: str | None = None, focus_shift: float | None = None, focus_position: float | None = None, n: int | None = None, guiding: bool = False, pointing: bool = False, bin: int = 1, dir: str | None = None, execute_parallel: bool = False, disable_telescope_movement: bool = False, reset_guiding_reference: bool = True, subframe_width: int | None = None, subframe_height: int | None = None, subframe_center_x: float = 0.5, subframe_center_y: float = 0.5, nonsidereal_recenter_interval: int = 0, nonsidereal_start_lead_time_seconds: float = 0.0, nonsidereal_rate_update_interval: float | None = None, metadata: dict[str, ~typing.Any]=<factory>)[source]#
Bases:
BaseActionConfigCapture a sequence of light frames.
- Workflow:
- Pre-sequence setup (pointing, filters, focus, binning, sub-framing, headers)
Observatory opens if not already done by a prior action if coordinates specified
Capture exposures in succession
Perform pointing correction if
pointing=trueStart autoguiding if
guiding=trueStop exposures, guiding, and tracking at completion
- Non-sidereal tracking:
Differential tracking is enabled implicitly: whenever
lookup_nameis given in place of a fixedra/decand resolves to a moving body, the sequence is tracked non-sidereally. Names resolved against Astropy’s built-in ephemeris (the planets, the Moon and the Sun) or against JPL Horizons (asteroids and comets) are treated as moving, while names resolved as stars or deep-sky objects are tracked sidereally.nonsidereal_recenter_intervalgoverns only how often the mount re-slews once tracking is under way. Autoguiding is incompatible with non-sidereal tracking and is disabled automatically. For Earth-orbiting objects, supplytleand setlookup_nameto “TLE”.The mount must report the ASCOM capabilities
CanSetRightAscensionRateandCanSetDeclinationRate. Wherelookup_nameresolves to a moving body and no telescope in the observatory reports both, the schedule is rejected as it is loaded rather than run sidereally.Schedule example for tracking Saturn:
{ "device_name": "camera_name", "action_type": "object", "action_value": { "object": "Saturn", "lookup_name": "saturn", "exptime": 30, "filter": "Clear", "nonsidereal_recenter_interval": 300, }, "start_time":"2025-01-01 00:00:00.000", "end_time":"2025-01-01 01:00:00.000", }
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'alt': 'Altitude coordinate when issuing Alt/Az pointings.', 'az': 'Azimuth coordinate when issuing Alt/Az pointings.', 'bin': 'Camera binning factor.', 'dec': 'Declination to slew to', 'dir': 'Base directory path for saving images.', 'disable_telescope_movement': 'Prevent any telescope motion during the sequence.', 'execute_parallel': 'Execute action in parallel mode when supported.', 'exptime': 'Exposure time per frame in seconds.', 'filter': 'Filter name to load before imaging.', 'focus_position': 'Absolute focus position override.', 'focus_shift': 'Focus offset relative to the stored best focus.', 'guiding': 'Start autoguiding with Donuts before imaging. Should be False for solar system objects using non-sidereal tracking, as the star field drifts relative to the guide reference.', 'lookup_name': "Instead of specifying ra/dec or alt/az, use SIMBAD/Astropy to look up coordinates for celestial body to observe (e.g., 'mars', 'M31').", 'metadata': 'Arbitrary user-defined key/value pairs that are not otherwise interpreted by Astra.Values ca be written to the FITS header via a `device_type=action_metadata` row in the FITS header configuration CSV, where `device_command` names the metadata key.', 'n': 'Number of exposures in the sequence. If not specified, defaults to infinite exposures until end_time.', 'nonsidereal_rate_update_interval': 'Minimum interval in seconds between differential rate commands sent to the mount. Astra will not issue a new rate more often than this, however rapidly the ephemeris changes. Lengthen it for mounts that stutter when a rate is applied; shorten it for targets whose rate changes over seconds, such as satellites in low orbit. Defaults to 10 seconds.', 'nonsidereal_recenter_interval': "Interval in seconds at which the mount re-slews to the target's current ephemeris position, refreshing the tracking rates as it does so. Setting it to 0 suppresses the re-slews and leaves the differential rates to work alone, which does not disable non-sidereal tracking. Has no effect on fixed targets.", 'nonsidereal_start_lead_time_seconds': 'Lead time in seconds for the initial slew, for targets too fast to slew to directly. The mount is sent to the position the target will occupy at start_time plus this value, idles until that moment, and only then begins exposing. Set it to at least the slew and settling time. The default of 0 slews straight at the target, which is accurate to well under an arcsecond for a planet or comet; only satellites, which cross degrees during a slew, need a lead time.', 'object': 'Target name.', 'pointing': 'Perform pointing correction with twirl before imaging.', 'ra': 'Right Ascension to slew to', 'reset_guiding_reference': 'Acquire a fresh guiding reference frame at the start.', 'subframe_center_x': 'Horizontal location of the subframe center (0=left, 1=right).', 'subframe_center_y': 'Vertical location of the subframe center (0=top, 1=bottom).', 'subframe_height': 'Height of the requested subframe in binned pixels.', 'subframe_width': 'Width of the requested subframe in binned pixels.', 'tle': "Two-line element set for an Earth-orbiting object, given as the two element lines separated by a newline. Set lookup_name to 'TLE' when this is supplied. Requires a mount that can set differential tracking rates."}#
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'object', 'action_value': {'dec': -5.39111, 'exptime': 60.0, 'filter': 'V', 'guiding': True, 'metadata': {'id': '47026', 'requested_by_user': 'user@mail.com'}, 'n': 3, 'object': 'M42', 'pointing': True, 'ra': 83.82208}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- validate_visibility(start_time: Time, end_time: Time, observatory_location: EarthLocation, min_altitude: float = 0.0, nonsidereal_supported: bool | None = None) None[source]#
Validate that the target is visible during the scheduled observation window.
Samples the target’s altitude across the window, starting at the exact start time and ending at the exact end time, and reports every sample below the limit. A moving target is followed through its ephemeris, so the altitude is that of the body itself at each moment, not of its start-time position.
- Parameters:
start_time – Observation start time as astropy Time object
end_time – Observation end time as astropy Time object
observatory_location – Observatory location as EarthLocation object
min_altitude – Minimum altitude in degrees for target to be considered visible (default: 0°)
nonsidereal_supported – Whether any telescope in the observatory can accept differential tracking rates (ASCOM CanSetRightAscensionRate and CanSetDeclinationRate).
Nonemeans unknown – no devices are connected – and leaves non-sidereal resolution enabled.
- Raises:
ValueError – If the target is below the minimum altitude at any sampled time, if lookup_name resolves to a moving body that no mount can track, or if lookup_name resolves to a moving body while fixed coordinates are also given.
Note
If RA/Dec are not provided, attempts to resolve them from ‘lookup_name’ or ‘alt’/’az’ parameters using the start time.
- class astra.action_configs.CalibrationActionConfig(exptime: List[float] = <factory>, n: List[int] = <factory>, filter: str | None = None, dir: str | None = None, bin: int = 1, execute_parallel: bool = False, subframe_width: int | None = None, subframe_height: int | None = None, subframe_center_x: float = 0.5, subframe_center_y: float = 0.5, metadata: dict[str, ~typing.Any]=<factory>)[source]#
Bases:
BaseActionConfigCapture a sequence of calibration images (bias/dark).
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'bin': 'Camera binning factor.', 'dir': 'Base directory path for saving images.', 'execute_parallel': 'Execute action in parallel mode when supported.', 'exptime': 'Exposure times (seconds) to iterate.', 'filter': 'Filter name to load before imaging.', 'metadata': 'Arbitrary user-defined key/value pairs, exposed to FITS headers via `device_type=action_metadata` rows in the FITS header configuration CSV.', 'n': 'Exposure counts aligned with each exposure time.', 'subframe_center_x': 'Horizontal subframe center (0=left, 1=right).', 'subframe_center_y': 'Vertical subframe center (0=top, 1=bottom).', 'subframe_height': 'Height of the requested subframe in binned pixels.', 'subframe_width': 'Width of the requested subframe in binned pixels.'}#
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'calibration', 'action_value': {'exptime': [0.0, 5.0, 30.0], 'n': [10, 5, 3]}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.FlatsActionConfig(filter: List[str] = <factory>, n: List[int] = <factory>, dir: str | None = None, bin: int = 1, execute_parallel: bool = False, disable_telescope_movement: bool = False, subframe_width: int | None = None, subframe_height: int | None = None, subframe_center_x: float = 0.5, subframe_center_y: float = 0.5)[source]#
Bases:
BaseActionConfigCapture a sequence of sky flats as the sky brightness evolves.
- Steps:
Wait for Sun altitude between -1° and -12°
- Point to a near-uniform patch of sky opposite the Sun
Opens observatory if not already done by a prior action
Capture exposures and re-position between frames
Iterate through requested filters while auto-adjusting exposure times
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'bin': 'Camera binning factor.', 'dir': 'Base directory path for saving images.', 'disable_telescope_movement': 'Prevent telescope motion during the sequence.', 'execute_parallel': 'Execute action in parallel mode when supported.', 'filter': 'Filters to iterate while capturing flats.', 'metadata': 'Arbitrary user-defined key/value pairs, exposed to FITS headers via `device_type=action_metadata` rows in the FITS header configuration CSV.', 'n': 'Number of flats to capture per filter.', 'subframe_center_x': 'Horizontal subframe center (0=left, 1=right).', 'subframe_center_y': 'Vertical subframe center (0=top, 1=bottom).', 'subframe_height': 'Height of the requested subframe in binned pixels.', 'subframe_width': 'Width of the requested subframe in binned pixels.'}#
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'flats', 'action_value': {'filter': ['V', 'R'], 'n': [10, 10]}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.CalibrateGuidingActionConfig(filter: str | None = None, pulse_time: int = 5000, exptime: float = 1.0, settle_time: float = 1.0, number_of_cycles: int = 10, focus_shift: float | None = None, focus_position: float | None = None, bin: int = 1, subframe_width: int | None = None, subframe_height: int | None = None, subframe_center_x: float = 0.5, subframe_center_y: float = 0.5)[source]#
Bases:
BaseActionConfigCalibrate guiding parameters using timed guide pulses.
- Steps:
- Slews telescope to RA = LST - 1 hour, Dec = 0° at the start of sequence
Opens observatory if not already done by a prior action
Issues a series of guide pulses in each cardinal direction with specified duration and settling time
Captures exposures after each pulse and measures star shifts to determine pixel-to-time scales and camera orientation relative to mount axes
Averages results over specified number of cycles
Saves calibration parameters in the observatory configuration for use in guiding
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'bin': 'Camera binning factor.', 'exptime': 'Exposure time for calibration images.', 'filter': 'Filter to use during calibration.', 'focus_position': 'Absolute focus position override.', 'focus_shift': 'Focus offset relative to best focus.', 'number_of_cycles': 'How many calibration cycles to take average over.', 'pulse_time': 'Duration of guide pulses in milliseconds.', 'settle_time': 'Wait time after pulses before exposing.', 'subframe_center_x': 'Horizontal subframe center (0=left, 1=right).', 'subframe_center_y': 'Vertical subframe center (0=top, 1=bottom).', 'subframe_height': 'Height of the requested subframe in binned pixels.', 'subframe_width': 'Width of the requested subframe in binned pixels.'}#
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'calibrate_guiding', 'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.PointingModelActionConfig(n: int = 50, exptime: float = 3.0, dark_subtraction: bool = False, object: str = 'Pointing Model', use_local_db: bool = False, filter: str | None = None, focus_shift: float | None = None, focus_position: float | None = None, bin: int = 1, dir: str | None = None, subframe_width: int | None = None, subframe_height: int | None = None, subframe_center_x: float = 0.5, subframe_center_y: float = 0.5)[source]#
Bases:
BaseActionConfigAid building a telescope pointing model. Astra itself does not build or maintain a pointing model.
Captures a spiral of points from zenith down to 30° altitude while avoiding positions within 20° of the Moon.
Plate solves each pointing and sends SyncToCoordinates commands to the mount. The receipt of these commands can be used to build a pointing model in the mount control software. The action can be configured to use the local star catalog for plate solving to speed up the process if the online Gaia catalog is unavailable or slow.
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'bin': 'Camera binning factor.', 'dark_subtraction': 'Enable dark subtraction using previously taken calibration frames of same exposure time in the same date folder.', 'dir': 'Directory path for saving images.', 'exptime': 'Exposure time for each pointing image.', 'filter': 'Filter to use for exposures.', 'focus_position': 'Absolute focus position override.', 'focus_shift': 'Focus offset relative to best focus.', 'n': 'Number of points to include in the model.', 'object': 'Descriptive label for the pointing run.', 'subframe_center_x': 'Horizontal subframe center (0=left, 1=right).', 'subframe_center_y': 'Vertical subframe center (0=top, 1=bottom).', 'subframe_height': 'Height of the requested subframe in binned pixels.', 'subframe_width': 'Width of the requested subframe in binned pixels.', 'use_local_db': 'Use local star catalog database for plate solving (faster).'}#
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'pointing_model', 'action_value': {}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- validate()[source]#
Validate required fields and types of all fields.
- Raises:
ValueError – If required fields are missing.
TypeError – If any field has an incorrect type.
- class astra.action_configs.SelectionMethod(value)[source]#
Bases:
Enum- SINGLE = 'single'#
- MAXIMAL = 'maximal'#
- ANY = 'any'#
- classmethod from_string(key: str, logger=None) SelectionMethod[source]#
- class astra.action_configs.AutofocusCalibrationFieldConfig(maximal_zenith_angle: float | int | Angle | None = None, airmass_threshold: float = 1.01, g_mag_range: List[float | int] = <factory>, j_mag_range: List[float | int] = <factory>, fov_height: float | int = 0, fov_width: float | int = 0, selection_method: SelectionMethod | str = 'single', use_gaia: bool = True, observation_time: Time | None = None, maximal_number_of_stars: int = 100000, ra: float | int | None = None, dec: float | int | None = None, _coordinates: SkyCoord | None = None)[source]#
Bases:
BaseActionConfigConfiguration for automated autofocus calibration field selection.
- selection_method: SelectionMethod | str = 'single'#
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'airmass_threshold': 'Highest acceptable airmass for autofocus candidates.', 'dec': 'Fixed Declination used to bypass automatic selection.', 'fov_height': 'Height of the field of view in degrees.', 'fov_width': 'Width of the field of view in degrees.', 'g_mag_range': 'Inclusive Gaia G magnitude range to consider.', 'j_mag_range': 'Inclusive 2MASS J magnitude range to consider.', 'maximal_number_of_stars': 'Maximum number of stars to query or consider.', 'maximal_zenith_angle': 'Maximum zenith angle allowed when selecting autofocus fields.', 'observation_time': 'Observation time used when evaluating constraints.', 'ra': 'Fixed Right Ascension used to bypass automatic selection.', 'selection_method': 'Strategy for selecting stars (single, maximal, any).', 'use_gaia': 'Whether to rely on Gaia catalog sources.'}#
- classmethod from_dict(config_dict: dict, logger=None, default_dict: dict = {}) AutofocusCalibrationFieldConfig[source]#
Create an instance from a dictionary, merging with defaults.
- class astra.action_configs.AutofocusConfig(exptime: float | int = 3.0, filter: str | None = None, bin: int = 1, reduce_exposure_time: bool = False, search_range: List[int] | int | None = None, search_range_is_relative: bool = False, n_steps: List[int] = <factory>, n_exposures: List[int] | int = <factory>, decrease_search_range: bool = True, star_find_threshold: float | int = 5.0, fwhm: int | None = None, percent_to_cut: int = 60, focus_measure_operator: str = 'HFR', save: bool = True, extremum_estimator: str = 'LOWESS', extremum_estimator_kwargs: dict[str, ~typing.Any]=<factory>, secondary_focus_measure_operators: List[str] = <factory>, calibration_field: AutofocusCalibrationFieldConfig = <factory>, save_path: Path | None = None, subframe_width: int | None = None, subframe_height: int | None = None, subframe_center_x: float = 0.5, subframe_center_y: float = 0.5)[source]#
Bases:
BaseActionConfigPerform an autofocus sweep to determine the optimal focus position.
- Steps:
- Select a suitable autofocus field (or use provided coordinates)
Opens observatory if not already done by a prior action
Move the telescope if needed
Capture images at different focus positions
Measure star sharpness in each image
Fit a curve to determine optimal focus
Save plots/results and save the best focus position in the observatory configuration
Note
Coarse searches (for example fft, normalized_variance) use non-parametric focus measures that characterise overall frame sharpness and are well suited for very broad search ranges where stars appear as large, defocused “donuts”. Analytic response-function autofocusers (for example HFR/StarSize), which fit a V-curve to measured star sizes, are better for fine-tuning near the focus peak but can give incorrect results if applied over an excessively large range because the assumed response model may not fit across the whole span.
- calibration_field: AutofocusCalibrationFieldConfig#
- FIELD_DESCRIPTIONS: ClassVar[dict[str, str]] = {'bin': 'Camera binning factor.', 'decrease_search_range': 'Reduce the search range after each sweep.', 'exptime': 'Exposure time for focus frames in seconds.', 'extremum_estimator': 'Curve-fitting method used to determine the minimum (LOWESS, medianfilter, spline, rbf).', 'extremum_estimator_kwargs': 'Additional keyword overrides for the extremum estimator.', 'filter': 'Filter to use during autofocus procedure.', 'focus_measure_operator': 'Focus metric to optimize (e.g., hfr, gauss, tenengrad, fft, normalized_variance).', 'focus_measure_operator_note': "Prefer non-parametric metrics (e.g., 'fft', 'normalized_variance') for coarse/broad searches; use analytic measures (e.g., 'HFR') for fine tuning near the focus peak.", 'fwhm': 'DAOStarFinder FWHM of the Gaussian kernel in pixels. If not set, derived from the camera/telescope plate scale assuming ~2" seeing.', 'n_exposures': 'Number of exposures at each focus position or an array specifying exposures for each sweep. If an integer is given, the same number of exposures is used for each sweep. If an array is given, the length of the array must match the number of sweeps. ', 'n_steps': 'Number of steps for each sweep.', 'percent_to_cut': 'Percentage of worst-performing focus samples to drop when shrinking the range.', 'reduce_exposure_time': 'Automatically shorten exposures to prevent saturation.', 'save': 'Persist the optimal focus position back into observatory configuration.', 'save_path': 'Directory override for saving autofocus results.', 'search_range': 'Range of focus positions to search. Accepts a single width or explicit bounds.', 'search_range_is_relative': 'Interpret search_range relative to the current focus position.', 'secondary_focus_measure_operators': 'Additional focus metrics to compute for diagnostics.', 'star_find_threshold': 'DAOStarFinder threshold for star detection.', 'subframe_center_x': 'Horizontal subframe center (0=left, 1=right).', 'subframe_center_y': 'Vertical subframe center (0=top, 1=bottom).', 'subframe_height': 'Height of the requested subframe in binned pixels.', 'subframe_width': 'Width of the requested subframe in binned pixels.'}#
- EXAMPLE_SCHEDULE: ClassVar[dict] = {'action_type': 'autofocus', 'action_value': {'exptime': 1.0, 'filter': 'V', 'focus_measure_operator': 'HFR', 'n_exposures': [1, 1], 'n_steps': [30, 20], 'search_range': 1000, 'search_range_is_relative': True}, 'device_name': 'camera_name', 'end_time': '2025-02-01 00:00:00.000', 'start_time': '2025-01-01 00:00:00.000'}#
- classmethod from_dict(config_dict: dict, logger=None, default_dict: dict = {}) AutofocusConfig[source]#
Create an instance from a dictionary, merging with defaults.