lunar_toolkit.workspace

DuckDB workspace (<work_dir>/lunar.duckdb): image footprints, stereo overlaps and bundle-adjustment statistics.

WorkspaceError

Bases: RuntimeError

The workspace cannot do what was asked (e.g. no spatial extension).

Workspace(path, *, memory_limit='1GB', threads=None, read_only=False, spatial=True)

A DuckDB database of images, stereo pairs and bundle-adjustment results.

Parameters:
  • path (Path or str) –

    Database file (":memory:" for a scratch one); a directory means <path>/lunar.duckdb. Parent directories are created.

  • memory_limit (str, default: '1GB' ) –

    DuckDB memory_limit.

  • threads (int, default: None ) –

    DuckDB threads (default: DuckDB's, all cores).

  • read_only (bool, default: False ) –

    Open an existing database read-only (no schema changes).

  • spatial (bool, default: True ) –

    Load the spatial extension (INSTALL spatial once needs network; it is cached in ~/.duckdb). Without it the bundle-adjustment tables and views work, while add_observations and pairs raise WorkspaceError.

Notes

Tables: images (observation columns plus footprint, a GEOMETRY in metres in the polar stereographic projection of hemi, PS_PROJ4), pairs (the last pairs result), ba_runs, ba_residual_stats, ba_pixel_residuals, ba_match_offsets, ba_points, ba_convergence, ba_camera_offsets (by run) and rejection_params. Views: ba_image_stats (per image before / after), ba_run_summary and image_rejections (Bertone et al. 2026 rules with rejection_params); the table macro image_rejections_at(max_offset_p85, max_large_residual_fraction, large_residual_px, min_matches) takes other thresholds.

Example

::

with Workspace("work/malapert") as ws:
    ws.add_observations(obs)
    pairs = ws.pairs(aoi={"point": (-85.9, 0.0), "radius_km": 5})
    ws.load_bundle_adjust("work/malapert/ba/run")
    ws.query("SELECT * FROM image_rejections")

query(sql, params=None)

Run SQL; returns the result as a DataFrame (empty for statements without one).

tables()

name, type (table / view) and rows of the workspace's tables and views.

export(table, path)

Write a table or view to Parquet (.parquet) or CSV (anything else).

GEOMETRY columns are written as WKT in CSV and as WKB (GeoParquet) in Parquet.

add_observations(df, *, rings=None)

Insert or replace (by product_id) observation rows in images.

Parameters:
Returns:
  • int –

    Rows written. footprint is the footprint polygon in polar stereographic metres (lunar_toolkit.geometry.footprint_geometry with ps_projection of hemi, the hemisphere of the image centre; edges densified along great circles every GREAT_CIRCLE_STEP_DEG), footprint_km2 its area corrected for the projection's scale, cap_lat / cap_lon the centre of its vertices, obs_key the observation (NAC LE / RE halves share one). idx numbers rows in insertion order.

add_index_rows(df, dataset='edr')

add_observations of CumIndex rows (footprints from FP_WKT, corners or FP_*).

pairs(aoi=None, *, convergence=(10.0, 60.0), ideal_convergence=(15.0, 45.0), max_incidence_diff=20.0, max_azimuth_diff=30.0, min_overlap=0.1, min_aoi_coverage=0.0, max_resolution_ratio=None, same_instrument=True, require_geometry=False, min_time_sep_s=60.0, weights=None, product_ids=None, table='pairs')

Rank stereo pairs of images with a SQL spatial self-join.

Parameters are those of lunar_toolkit.stereo.find_stereo_pairs, plus product_ids (only these images) and table (where the result is stored; None keeps it out of the database).

Returns:
  • DataFrame –

    lunar_toolkit.stereo.PAIR_COLUMNS; index_1 / index_2 are images.idx.

Notes

Candidate pairs come from a bounding-box spatial join (ST_Intersects_Extent, R-tree built by DuckDB's spatial join operator) of the footprints of each hemisphere, clipped to the AOI; then the attribute filters, overlap area / fraction (ST_Area of ST_Intersection in metres, divided by the projection's areal scale at the centroid), AOI coverage, incidence / azimuth / emission / resolution / time differences and the overlap centroid are SQL. The convergence angle and b_h are then computed for the candidates with numpy (lunar_toolkit.stereo.convergence_angles: sub-spacecraft point + altitude, pushbroom-corrected), exactly as find_stereo_pairs, and the score is lunar_toolkit.stereo.score_pairs. Footprint edges are great circles in both (densified here), so overlap areas and fractions agree with find_stereo_pairs to ~1e-3 relative.

set_rejection_params(config=None, **overrides)

Thresholds of the image_rejections view, from config (default SfsConfig).

Keyword overrides: max_offset_p85, max_large_residual_fraction, large_residual_px, min_matches (None disables the first two rules).

load_bundle_adjust(prefix, run=None)

Load ASP bundle_adjust outputs <prefix>-* as run run (replacing it).

run defaults to the prefix's directory name (ba, ba_refine). Reads, when present: -initial / -final_residuals_stats.txt and _raw_pixels.txt (ba_residual_stats, ba_pixel_residuals), -mapproj_match_offset_stats.txt (ba_match_offsets; -1 becomes NULL), -initial / -final_residuals_pointmap.csv (ba_points, with polar stereographic x / y), -convergence_angles.txt (ba_convergence) and -camera_offsets.txt (ba_camera_offsets).

Returns:
  • dict –

    Rows loaded per table (empty when no report exists).

runs()

Loaded bundle-adjustment runs.

rejections(run)

image_rejections of run: image file name -> reason.

ba_report(run)

Per-image table of run in lunar_toolkit.sfs_report.read_ba_reports form, or None.

convergence(run)

ba_convergence of run as left, right, p25, p50, p75, matches.

point_residuals(run, stage='final')

mean_residual_px of the run / stage residual point map.

summary()

The ba_run_summary view.

workspace_path(work_dir)

<work_dir>/lunar.duckdb, or work_dir itself when it names a .duckdb file.

spatial_available()

Whether DuckDB's spatial extension loads (installing it when needed).

find_stereo_pairs_duckdb(obs_df, aoi=None, *, workspace=None, **kwargs)

lunar_toolkit.stereo.find_stereo_pairs through Workspace.pairs.

The observations are written to images of workspace (default: a scratch in-memory one) and index_1 / index_2 are mapped back to obs_df row labels; keyword arguments are those of find_stereo_pairs.

flag_images_sql(residual_stats, residual_norms, offsets, **thresholds)

image_rejections of parsed final-stage reports (see lunar_toolkit.sfs.flag_images).