=============== Python Bindings =============== The ``cal-py`` crate provides a thin PyO3 surface for calling the calibration engine from Python. It contains **zero logic** — all work is dispatched to cal-core and cal-io. Module: ``_cal_rs`` ------------------- Build with maturin: .. code-block:: bash cd crates/cal-py maturin develop # debug maturin build --release # release wheel Exported Functions ------------------ .. list-table:: :header-rows: 1 :widths: 30 70 * - Function - Description * - ``version()`` - Returns the crate version string * - ``list_scans(store_path)`` - List scan numbers in an L0 Zarr store * - ``calibrate_store(input, output, atm, config?, foeff?, gain_image?)`` - Calibrate all scans in a store * - ``calibrate_scan_py(input, output, atm, scan, config?, foeff?, gain_image?)`` - Calibrate a single scan (Dask-friendly) * - ``convert_fits_folder(fits_folder, zarr_path, skip?)`` * - Convert FITS folder to Zarr (* requires ``fits-ingest``) Configuration Dict ------------------ The ``config`` parameter accepts a Python dict: .. code-block:: python config = { "obs_mode": "otf", # auto, totalpower, otf "pwv": 1.2, # fixed PWV in mm (omit to fit) "physics": "kalibrate_compat", # exact or kalibrate_compat "clip_tsys": 200.0, # bad channel threshold (K) "clip_counts": 0.01, # min hot-cold ratio "eta_fss": 1.0, # forward scattering efficiency "eta_mb": 1.0, # main beam efficiency "n_threads": 4, # Rayon thread count } Dask Integration ---------------- For distributed processing, use ``calibrate_scan_py`` as a Dask delayed function: .. code-block:: python import dask tasks = [ dask.delayed(_cal_rs.calibrate_scan_py)( input_path, output_path, atm_table_path, scan_num ) for scan_num in scan_numbers ] results = dask.compute(*tasks) Each call processes one scan independently, making this suitable for ``dask.distributed`` or multiprocessing. Design: Zero Logic in cal-py ------------------------------ ``cal-py`` is intentionally thin: - No calibration math - No data manipulation - No error recovery logic - All work dispatched to ``cal-core`` and ``cal-io`` - Multi-pixel orchestration (slice → calibrate → merge) is the only structured logic, and it delegates each step This keeps the Python surface stable while the Rust internals evolve.