Use analyze_camera_frame as the boundary between the ToupTek camera layer and
the SmartTScope star/capture logic.
The function is intentionally a one-trick pony:
- It accepts camera facts and one already-captured image frame.
- It returns star findings, temporal confidence, and next-frame capture advice.
- It does not call the ToupTek SDK.
- It does not read FITS files.
- It does not communicate with OnStep.
This keeps the detector testable on stored FITS frames while allowing the same code to run on the Raspberry Pi with live camera frames.
from smarttscope_live_analysis import analyze_camera_frame
findings = analyze_camera_frame(
camera_info,
frame,
previous_star_state=state,
)
state = findings["state"]Signature:
def analyze_camera_frame(
camera_info: Mapping[str, Any],
frame: np.ndarray,
*,
previous_star_state: Mapping[str, Any] | None = None,
) -> dict[str, Any]:camera_info is a plain mapping with the current camera mode and settings. The
detector currently consumes exposure_s, gain, and offset directly, and the
remaining fields are preserved in the returned state for the caller.
Recommended fields:
camera_info = {
"camera": "ToupTek IMX678M",
"sensor": "IMX678M",
"exposure_s": 100.0,
"gain": 101,
"offset": 150,
"bit_depth": 12,
"raw_mode": True,
"conversion_gain": "HCG",
"binning": "1x1",
"black_level_auto_adjust": False,
"auto_exposure_enabled": False,
"temperature_c": None,
}frame must be a 2D NumPy array as delivered by the camera layer. Keep it in
the native camera dtype, normally uint16; the detector samples statistics
without requiring the caller to scale the image.
previous_star_state must be None for the first frame in a run. For later
frames, pass back the exact findings["state"] returned by the previous call.
The function returns a dictionary with these main sections:
{
"frame_index": 3,
"camera_info": {...},
"single_frame": {
"stars_found": 291,
"image_quality": "stars_saturated",
"notes": [...],
"focus_warning": None,
"sources": [...],
},
"temporal": {
"persistent_star_candidate": {"count": 42, "tracks": [...]},
"uncertain_temporal_source": {"count": 5, "tracks": [...]},
"transient_artifact_candidate": {"count": 77, "tracks": [...]},
},
"first_to_last_shift": {
"dx": 0,
"dy": 0,
"count": 40,
"top_bins": [...],
},
"recommendation": {
"recommended_exposure_s": 50,
"recommended_gain": 100,
"recommended_offset": 150,
"actions": [...],
},
"state": {...},
}Use single_frame for immediate feedback, but prefer temporal once at least
three adjacent frames have been analyzed. Persistent sources are the best input
for star count, focus, guiding checks, and capture decisions. Transient sources
are likely hot pixels, gain artifacts, cosmic hits, or tiny noise spikes.
The ToupTek adapter should own all SDK calls. The detector only receives the resulting frame and the SDK readbacks.
from smarttscope_live_analysis import analyze_camera_frame
def run_capture_loop(camera):
state = None
while True:
frame = camera.capture_frame()
camera_info = {
"camera": camera.model_name,
"exposure_s": camera.exposure_us / 1_000_000,
"gain": camera.analog_gain,
"offset": camera.black_level,
"bit_depth": camera.bit_depth,
"raw_mode": camera.raw_mode,
"conversion_gain": camera.conversion_gain,
"binning": camera.binning,
"black_level_auto_adjust": camera.black_level_auto_adjust,
"auto_exposure_enabled": camera.auto_exposure_enabled,
}
findings = analyze_camera_frame(
camera_info,
frame,
previous_star_state=state,
)
state = findings["state"]
apply_recommendation(camera, findings["recommendation"])
publish_star_feedback(findings)Do not call this directly from a native SDK callback if that callback must return quickly. Put the frame into a worker queue and run analysis in a Python worker thread or process.
The recommendation is deliberately conservative because ToupTek gain, offset, exposure time, bit depth, RAW mode, and conversion gain are not guaranteed to be linear or independent.
Rules for the integration layer:
- If SmartTScope controls exposure/gain/offset, disable SDK auto exposure or constrain it explicitly with the SDK auto-exposure range and policy APIs.
- Around normal deep-sky operation, prefer unity-like gain near
100. - Adjust exposure before moving far away from unity-like gain.
- Keep offset stable unless the detector reports clipping or a calibration probe shows a black-level problem.
- At very high gain values, return to a unity-like probe frame instead of scaling gain linearly.
- Do not use transient temporal sources for exposure, gain, or offset decisions.
The caller owns the actual SDK writes. This function only describes the next
step in recommendation["actions"] and the recommended numeric values.
Keep passing findings["state"] while frames are part of the same analysis
run. Reset the state to None when the image geometry or source field changes.
Reset on:
- new target or slew
- ROI change
- binning change
- pixel format or bit-depth change
- conversion gain mode change
- meridian flip
- large focus move
- intentional dither when the displacement is larger than the temporal matching tolerance
Small guiding jitter of about plus/minus one pixel should remain in the same state. The temporal classifier uses that stability to separate real stars from one-frame artifacts.
Exposure, gain, and offset changes may keep the state if the image geometry and field stay the same, but after a large setting change it is safer to reset or mark the next few frames as a new probe sequence.
FITS support is not a runtime dependency. Development tools may install Astropy and feed stored frames through the same function:
from astropy.io import fits
from smarttscope_live_analysis import analyze_camera_frame
state = None
for path in fits_paths:
frame = fits.getdata(path)
findings = analyze_camera_frame(
{"exposure_s": 100, "gain": 101, "offset": 150},
frame,
previous_star_state=state,
)
state = findings["state"]For the Raspberry Pi 5, keep the integration simple:
- Feed one frame at a time.
- Keep frames in native integer dtype.
- Keep only the returned rolling
state, not image frames. - Use temporal persistent stars for decisions when multiple frames exist.
- Use single-frame findings only as early feedback before temporal confidence is available.
No AI model, plate solving, network call, or cloud dependency is required for this detector.