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:

cd crates/cal-py
maturin develop              # debug
maturin build --release      # release wheel

Exported Functions#

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:

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:

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.