argos.core.session

Session layer — device ownership + acquisition orchestration (WS5).

DeviceSession owns the Alpaca device handles, discovery, the pollers and the Stellarium server; the acquisition engine owns the camera ownership state machine and the capture/solve/photometry workers. Both are UI-thread QObjects; the ImagingPage is a view over them.

class argos.core.session.CameraCapabilities(gain_min: int, gain_max: int, exposure_min: float, exposure_max: float, offset_min: int | None, offset_max: int | None, offset: int | None, max_bin: int, binning: int)[source]

Bases: object

Driver-derived camera limits + optional parameters, read at connect.

Sourced from the fields argos.core.alpaca.camera.Camera.connect() populates. offset/offset_min/offset_max are None when the driver doesn’t support offset; max_bin is 1 when binning is fixed.

binning: int
exposure_max: float
exposure_min: float
gain_max: int
gain_min: int
max_bin: int
offset: int | None
offset_max: int | None
offset_min: int | None
class argos.core.session.FilterWheelState(names: tuple[str, ...], position: int, position_name: str)[source]

Bases: object

Filter wheel snapshot at connect: slot names + current position.

names: tuple[str, ...]
position: int
position_name: str
class argos.core.session.FocuserState(name: str, position: int, max_step: int, max_increment: int, absolute: bool)[source]

Bases: object

Focuser snapshot at connect: capabilities + current position.

absolute: bool
max_increment: int
max_step: int
name: str
position: int
class argos.core.session.LiveFrame(preview: ndarray, full: ndarray, start: datetime | None, end: datetime | None, decimated: bool)[source]

Bases: object

One acquired frame, as published by the AcquisitionEngine.

preview is the (possibly stride-decimated) display array; full is always the full-resolution readout. decimated is True when the two differ — plate-solve/photometry must skip such frames (their plate scale is wrong). start/end are the exposure UTC timestamps (None for sequence frames, which are timestamped by their worker).

decimated: bool
end: datetime | None
full: ndarray
preview: ndarray
start: datetime | None
class argos.core.session.PhotometryPoint(key: str, name: str, jd: float, mag: float, mag_err: float, saturated: bool, role: str = 'target', relative_flux: float | None = None, relative_flux_err: float | None = None)[source]

Bases: object

One differential-photometry light-curve point for a measured star.

jd: float
key: str
mag: float
mag_err: float
name: str
relative_flux: float | None = None
relative_flux_err: float | None = None
role: str = 'target'
saturated: bool

Typed signal payloads for the session layer.

Frozen dataclasses carried by DeviceSession / AcquisitionEngine signals, replacing the loose pyqtSignal(object, ...) tuples the ImagingPage used internally. Modeled on argos.workers.preview_processor.ProcessedFrame.

class argos.core.session.types.CameraCapabilities(gain_min: int, gain_max: int, exposure_min: float, exposure_max: float, offset_min: int | None, offset_max: int | None, offset: int | None, max_bin: int, binning: int)[source]

Bases: object

Driver-derived camera limits + optional parameters, read at connect.

Sourced from the fields argos.core.alpaca.camera.Camera.connect() populates. offset/offset_min/offset_max are None when the driver doesn’t support offset; max_bin is 1 when binning is fixed.

binning: int
exposure_max: float
exposure_min: float
gain_max: int
gain_min: int
max_bin: int
offset: int | None
offset_max: int | None
offset_min: int | None
class argos.core.session.types.FilterWheelState(names: tuple[str, ...], position: int, position_name: str)[source]

Bases: object

Filter wheel snapshot at connect: slot names + current position.

names: tuple[str, ...]
position: int
position_name: str
class argos.core.session.types.FocuserState(name: str, position: int, max_step: int, max_increment: int, absolute: bool)[source]

Bases: object

Focuser snapshot at connect: capabilities + current position.

absolute: bool
max_increment: int
max_step: int
name: str
position: int
class argos.core.session.types.LiveFrame(preview: ndarray, full: ndarray, start: datetime | None, end: datetime | None, decimated: bool)[source]

Bases: object

One acquired frame, as published by the AcquisitionEngine.

preview is the (possibly stride-decimated) display array; full is always the full-resolution readout. decimated is True when the two differ — plate-solve/photometry must skip such frames (their plate scale is wrong). start/end are the exposure UTC timestamps (None for sequence frames, which are timestamped by their worker).

decimated: bool
end: datetime | None
full: ndarray
preview: ndarray
start: datetime | None
class argos.core.session.types.PhotometryPoint(key: str, name: str, jd: float, mag: float, mag_err: float, saturated: bool, role: str = 'target', relative_flux: float | None = None, relative_flux_err: float | None = None)[source]

Bases: object

One differential-photometry light-curve point for a measured star.

jd: float
key: str
mag: float
mag_err: float
name: str
relative_flux: float | None = None
relative_flux_err: float | None = None
role: str = 'target'
saturated: bool

DeviceSession — owns the Alpaca device handles and their pollers.

Extracted from the ImagingPage (WS5): the session is a plain QObject living on the UI thread (never moveToThread it — every worker→UI connection relies on the receiver’s thread affinity for queued delivery). It owns:

  • the four device handles (Telescope, Camera, Focuser, FilterWheel) and their connect/disconnect lifecycles;

  • Alpaca UDP discovery;

  • the mount-position poller and both temperature pollers;

  • the pointing intent (target_radec, set on goto) and the cached last_position;

  • the Stellarium telescope-control server worker (fed the mount position directly — no Shell-level signal plumbing).

The UI consumes typed signals (capabilities_ready carries a frozen CameraCapabilities, …); device use (exposures, sweeps, sequences) belongs to the AcquisitionEngine, which coordinates with the session through the camera_will_disconnect / focuser_will_disconnect hooks.

AcquisitionEngine — camera ownership + capture/solve/photometry workers.

Extracted from the ImagingPage (WS5). A UI-thread QObject (never moveToThread it) that owns everything that uses the devices:

  • the CameraService ownership state machine (WS3) and its preview hooks;

  • the LivePreview / Sequence / Autofocus workers;

  • the AstrometryController (live plate-solve lifecycle) and the VSX/VSP catalog cache;

  • the persistent target set and the live photometry state (light curves, per-target CSVs, FITS saves).

The ImagingPage is a view: it forwards dock intents into these methods and renders the typed signals (frame_ready carries a frozen LiveFrame, photometry_point a PhotometryPoint, …). Capture parameters are read through injected providers (set_providers()) so the engine never touches a widget.

Coordination with the DeviceSession happens through its camera_will_disconnect / focuser_will_disconnect hooks — the engine stops using a device before the session drops the handle.

Read a finished Argos session for the offline Review workspace.

The reader is deliberately Qt-free: a session is useful after a crash, on a different computer, or before any hardware is connected. It consumes the durable session.json truth and only reads FITS headers for telemetry that is not part of that log (currently CCD temperature).

class argos.core.session.review.ReviewFrame(filename: 'str', image_type: 'str', filter_name: 'str', exposure_s: 'float', gain: 'int', timestamp: 'datetime | None', hfd: 'float | None' = None, fwhm: 'float | None' = None, star_count: 'int | None' = None, sky_adu: 'float | None' = None, peak_adu: 'float | None' = None, eccentricity: 'float | None' = None, ccd_temp: 'float | None' = None)[source]

Bases: object

ccd_temp: float | None = None
eccentricity: float | None = None
exposure_s: float
filename: str
filter_name: str
fwhm: float | None = None
gain: int
hfd: float | None = None
image_type: str
peak_adu: float | None = None
sky_adu: float | None = None
star_count: int | None = None
timestamp: datetime | None
class argos.core.session.review.ReviewedSession(root: 'Path', object_name: 'str', started_utc: 'str', software: 'str', observer: 'str', frames: 'list[ReviewFrame]' = <factory>, metadata: 'dict[str, Any]'=<factory>, comparison_quality: 'dict[str, Any] | None'=None, warnings: 'list[str]' = <factory>)[source]

Bases: object

comparison_quality: dict[str, Any] | None = None
property filter_counts: Counter[str]
frame_path(frame: ReviewFrame) → Path | None[source]

Find the stored FITS for a logged frame without modifying the session.

frames: list[ReviewFrame]
property image_type_counts: Counter[str]
property light_frames: list[ReviewFrame]
metadata: dict[str, Any]
metric_samples() → list[tuple[float, ReviewFrame]][source]

Return light-frame elapsed seconds and frames with a valid timestamp.

nearest_light_frame(jd_utc: float) → ReviewFrame | None[source]

Match a preview-photometry JD to its closest recorded light frame.

object_name: str
observer: str
readiness_issues() → list[str][source]
root: Path
software: str
started_utc: str
warnings: list[str]
exception argos.core.session.review.SessionReviewError[source]

Bases: ValueError

The selected folder does not contain a readable Argos session.

argos.core.session.review.load_session(path: Path | str, *, read_temperature: bool = True) → ReviewedSession[source]

Load a session folder or its session.json file.

Bad individual frame rows are skipped and reported so a partial night can still be reviewed. This function never writes or alters the session.

argos.core.session.review.load_session_curves(review: ReviewedSession) → dict[str, LightCurve][source]

Load all per-star curves with their scientific roles and identities.

Per-star CSVs deliberately contain only measurement columns so they remain simple inputs for external tools. Their filename and the session’s targets.json therefore provide the missing role/identity at Review time. Older Review incorrectly treated every such file as a target, making calibration stars appear in the source-light-curve panel.

Session flight recorder — machine-readable per-frame diagnostics (JSONL).

One line of JSON per record, one file per run, next to the session outputs. Postprod loads it with pandas.read_json(path, lines=True) and can audit what Argos measured and decided on every frame: each comparison star’s raw behaviour, the ensemble zero-point health, the aperture tracker’s state, and sparse session events. The human-oriented log tells the story; this file is the evidence.

Record shape: {"t": iso8601-utc, "kind": ..., ["frame": n,] **fields}. Kinds in use: star, ensemble, tracking, frame, event (see notes/photometry_hardening_plan.md §P11).

Failure-safe by design: the recorder must never take a session down. Any write error disables it with a single warning; every method is a no-op when disabled. Qt-free.

class argos.core.session.diagnostics.SessionDiagnostics(path: Path | str, enabled: bool = True)[source]

Bases: object

Append-only JSONL writer for one run (live session or batch re-run).

The file is opened lazily on the first record so an enabled-but-unused recorder leaves nothing behind. It is an opt-in local diagnostic file and has no network transport. enabled=False (config diagnostics.enabled) makes every call a no-op.

close() → None[source]
property enabled: bool
property path: Path
record(kind: str, frame: int | None = None, **fields: Any) → None[source]

Write one record; drops None-valued fields, rounds floats.

star(frame: int, star, phot, x: float, y: float) → None[source]

One measured star on one frame — target, comparison or check.

star is a TargetStar, phot an AperturePhot or None (aperture off-frame). Recording every comparison’s raw instrumental behaviour is the point: a drifting or clouded comp is invisible in the calibrated target curve but obvious here.