camera_orientation_from_sun_candidates — GEOCAM sun op

• Data kinds: keypoints × signal × signal → table

• Call: import fullseye as fs; fs.ledger.camera_orientation_from_sun_candidates(candidates, frame_index, unix_times, lat_deg, lon_deg, shape, K=None, hfov_range_deg=(25.0, 120.0), tol_px=20.0, roll_max_deg=12.0, pitch_range_deg=(-40.0, 0.0), min_dt=1800.0, max_pairs=600, seed=0) (to call the implementation directly, import geocam; geocam.camera_orientation_from_sun_candidates(candidates, frame_index, unix_times, lat_deg, lon_deg, shape, K=None, hfov_range_deg=(25.0, 120.0), tol_px=20.0, roll_max_deg=12.0, pitch_range_deg=(-40.0, 0.0), min_dt=1800.0, max_pairs=600, seed=0); from the registry, opsgeocam.get("camera_orientation_from_sun_candidates"))

Usage

From several candidate "bright blobs" per frame (a mix of the sun, white cars, signs and text), selects with RANSAC the 1 track that moves as the time predicts, and determines (yaw, pitch, roll) and the focal length simultaneously → table. With a fixed camera only the sun moves at the sun's speed, so the sun is decided by its motion, not by its appearance (on Fintraffic weather cameras, an appearance gate gave 24/24 false detections, 2026-09-21).

Hypothesis = 1 candidate each from 2 frames with time difference ≥ min_dt → the same Wahba 2-point solution as camera_orientation_from_sun. Hypotheses that do not satisfy the prior knowledge of road cameras (|roll| ≤ roll_max_deg, pitch in pitch_range_deg, horizontal field of view in hfov_range_deg) are discarded -- with 4 degrees of freedom (rotation 3 + focal length) and many candidates, a chance set of 3 points can pass an unphysical pose. Votes = number of frames with a candidate within tol_px of the predicted position (times below the horizon do not vote). Real bloom centres wobble by 5 to 13 px (clouds, exposure), hence the default of 20 px. With the inliers of the best hypothesis the focal length is optimised in 1 dimension and the rotation is re-fitted on all points (2 times).

The sun's reflection on a wet road surface also moves at the sun's speed (a mirror image), so motion alone cannot tell them apart. The reflection lies in the lower part of the image (the road surface), so fitting it as the sun gives a pose where "the camera faces upward" (pitch > 0) -- the default upper limit 0 of pitch_range_deg is the gate for that (road cameras do not face upward). Widen it for cameras that face upward. An independent cross-check (elevation of the lane vanishing point ≈ 0) is also recommended.

If K is passed, it is used (the focal length is not searched). With K=None the principal point is the image centre and fx = fy = f is searched over the range of hfov_range_deg -- public cameras normally have no intrinsic parameters.

Args:

candidates: (N, 2) (u, v). Candidates of all frames stacked (output of sun_bloom_fit or sun_pixel_position).

frame_index: (N,) which frame each candidate belongs to (an index into unix_times, integer values).

unix_times: (F,) UNIX seconds (UTC) of each frame.

lat_deg, lon_deg: camera position.

shape: (H, W).

K: (fx, fy, cx, cy) or None.

Returns:

table: yaw_deg / pitch_deg / roll_deg / f_px / hfov_deg / K (4,) / inlier (N,, 1 = accepted) /

n_inliers / n_frames / n_frames_with_candidates / residual_deg (angular residual RMS of accepted points) /

max_residual_deg / residual_px / loo_px (mean leave-1-out prediction error) / loo_max_px / span_h

(time span of accepted points) / n_hypotheses (number of hypotheses that passed the prior) / at_prior_bound (1 = the answer is stuck at the edge of the prior: do not trust it).

Raises:

ValueError: candidates in fewer than 2 frames, no hypothesis passes the prior, fewer than 3 inliers.

Detailed usage guide

• geocam family guide

References (sample data, literature)

• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).

• Operator provenance and references — the sources of the research/methods this op family came from.

• The canonical algorithm (author, year) and its uses are named in the family usage guide above.

Runnable examples (verified samples that actually call this op)

• poc_public_camera_heading_real — py -3.11 examples/poc_public_camera_heading_real.py

Ops the type connects to (they accept table as input)

render_skyline_view · camera_orientation_from_skyline

Same category (sun)

sun_position · sun_pixel_position · camera_orientation_from_sun · sun_bloom_fit


*Provenance: geocam.py — GEOCAM operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*

© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.