curryer.correction.config ========================= .. py:module:: curryer.correction.config .. autoapi-nested-parse:: Configuration models and enumerations for the geolocation correction pipeline. This module defines the data structures that represent the complete configuration for a correction analysis run, including: - ``ParameterType`` – enum of the three parameter application strategies - ``ParameterSpec`` – sampling specification for a single correction parameter - ``ParameterConfig`` – assembles a parameter's type, kernel file, and sampling spec - ``GeolocationConfig`` – SPICE kernel paths and instrument settings - ``NetCDFParameterMetadata`` / ``NetCDFConfig`` – NetCDF output metadata (re-exported from io_config) - ``GeolocationSetup`` – durable, mission-specific setup (built once, reused across sweeps) - ``Sweep`` – the lightweight, frequently-varied parameter experiment - ``OutputConfig`` – output settings (NetCDF metadata + filename) - ``KernelContext``, ``CalibrationData``, ``ImageMatchingContext`` – lightweight NamedTuples used to pass state between pipeline helper functions - ``load_setup_from_json`` / ``load_sweep_from_json`` / ``load_config_files`` – build the ``setup`` / ``sweep`` / ``output`` models from a JSON file All mission-specific values (kernel filenames, parameter ranges, instrument names) live in mission configuration modules (e.g. ``tests/test_correction/clarreo_config.py``) and are injected via ``GeolocationSetup`` / ``Sweep``. All config objects are ``pydantic.BaseModel`` subclasses which provide: - Automatic type validation and clear ``ValidationError`` messages on construction - Free JSON serialization via ``model_dump_json()`` / ``model_validate_json()`` - IDE autocomplete on every field Parameter configuration — three cooperating classes ---------------------------------------------------- These three classes each capture a distinct, orthogonal concern: ``ParameterType`` (enum) *How the value is applied to the pipeline.* Each member maps to a different pipeline code path: - ``CONSTANT_KERNEL`` — replace the kernel value with the sampled value - ``OFFSET_KERNEL`` — shift the existing kernel value by the sampled offset - ``OFFSET_TIME`` — shift the input timestamps by the sampled offset ``ParameterType`` belongs on :class:`ParameterConfig` (not on :class:`ParameterSpec`) because it describes application mechanics, not sampling statistics. ``ParameterSpec`` *How to draw samples.* Holds the statistical description of the search space: the nominal ``current_value``, the ``bounds`` that clip samples, the ``sigma`` for normal-distribution sampling, physical ``units``, and optional ``field`` / ``coordinate_frames`` hints consumed by kernel-creation routines. ``ParameterSpec`` is intentionally agnostic about how any drawn value will be applied — that is ``ParameterType``'s job. ``ParameterConfig`` *Assembles the three concerns.* A single ``ParameterConfig`` answers: "vary *this* kernel file (``config_file``), applied as *this kind* of change (``ptype``), drawing samples according to *this spec* (``spec``)." ``ParameterConfig`` is also the right place for cross-field validation (e.g. confirming that ``data.field`` is provided whenever ``ptype`` requires it). Typical construction:: ParameterConfig( ptype=ParameterType.OFFSET_TIME, config_file=None, # time offsets need no kernel file spec=ParameterSpec( current_value=0.0, bounds=[-50.0, 50.0], sigma=10.0, units="milliseconds", field="time_ugps", # required for OFFSET_KERNEL / OFFSET_TIME ), ) Attributes ---------- .. autoapisummary:: curryer.correction.config.logger Classes ------- .. autoapisummary:: curryer.correction.config.KernelContext curryer.correction.config.CalibrationData curryer.correction.config.ImageMatchingContext curryer.correction.config.DataConfig curryer.correction.config.ParameterType curryer.correction.config.SearchStrategy curryer.correction.config.ParameterSpec curryer.correction.config.ParameterConfig curryer.correction.config.GeolocationConfig curryer.correction.config.RequirementsConfig curryer.correction.config.PSFSamplingConfig curryer.correction.config.SearchConfig curryer.correction.config.RegridConfig curryer.correction.config.CalibrationFiles curryer.correction.config.GeolocationSetup curryer.correction.config.Sweep curryer.correction.config.OutputConfig curryer.correction.config.CorrectionInput Functions --------- .. autoapisummary:: curryer.correction.config._read_config_json curryer.correction.config.load_setup_from_json curryer.correction.config.load_sweep_from_json curryer.correction.config.load_config_files Module Contents --------------- .. py:data:: logger .. py:class:: KernelContext Bases: :py:obj:`NamedTuple` Context for SPICE kernel loading during geolocation. .. py:attribute:: mkrn :type: curryer.meta.MetaKernel .. py:attribute:: dynamic_kernels :type: list[pathlib.Path] .. py:attribute:: param_kernels :type: list[pathlib.Path] .. py:class:: CalibrationData Bases: :py:obj:`NamedTuple` Pre-loaded calibration data for image matching. .. py:attribute:: los_vectors :type: numpy.ndarray | None .. py:attribute:: optical_psfs :type: list | None .. py:class:: ImageMatchingContext Bases: :py:obj:`NamedTuple` Context data needed for image matching operations. .. py:attribute:: gcp_pairs :type: list[tuple] .. py:attribute:: params :type: list[tuple] .. py:attribute:: pair_idx :type: int .. py:attribute:: sci_key :type: str .. py:class:: DataConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Configuration for config-driven internal data loading. Replaces mission-specific loader callables with a declarative specification of how files should be read. The pipeline reads telemetry and science data directly from the provided file paths using pandas/xarray, applying the ``time_scale_factor`` to convert the science time column to uGPS. .. attribute:: file_format File format for both telemetry and science data files. ``"csv"`` uses :func:`pandas.read_csv`; ``"netcdf"`` converts via :func:`xarray.open_dataset`; ``"hdf5"`` uses :func:`pandas.read_hdf`. .. attribute:: time_scale_factor Multiply science timestamps by this factor to obtain uGPS (microseconds since GPS epoch). For example, ``1e6`` converts GPS seconds to uGPS; ``1.0`` means the file already contains uGPS. The time column name is taken from :attr:`GeolocationConfig.time_field` (single source of truth). .. attribute:: position_columns Explicit column name mappings for telemetry spacecraft-position data, e.g. ``["sc_pos_x", "sc_pos_y", "sc_pos_z"]``. ``None`` means use mission defaults from the geolocation configuration. .. py:attribute:: file_format :type: Literal['csv', 'netcdf', 'hdf5'] :value: 'csv' .. py:attribute:: time_scale_factor :type: float :value: 1.0 .. py:attribute:: position_columns :type: list[str] | None :value: None .. py:class:: ParameterType Bases: :py:obj:`str`, :py:obj:`enum.Enum` Parameter types used in the correction configuration. Specifies how a parameter is applied during geolocation analysis: whether as a constant kernel value, a kernel offset, or a time offset. String-valued so JSON configs use readable names (``"OFFSET_TIME"``). .. attribute:: CONSTANT_KERNEL Set a specific kernel value (e.g., fixed rotation angles). .. attribute:: OFFSET_KERNEL Modify input kernel data by an offset. .. attribute:: OFFSET_TIME Modify input timetags by an offset. .. py:attribute:: CONSTANT_KERNEL :value: 'CONSTANT_KERNEL' .. py:attribute:: OFFSET_KERNEL :value: 'OFFSET_KERNEL' .. py:attribute:: OFFSET_TIME :value: 'OFFSET_TIME' .. py:class:: SearchStrategy Bases: :py:obj:`str`, :py:obj:`enum.Enum` Strategy used to generate parameter sets during correction analysis. .. attribute:: RANDOM Monte Carlo random walk (current default). Each iteration draws an independent sample from a normal distribution centered on the parameter's ``current_value`` with the specified ``sigma``, clipped to ``bounds``. Requires ``seed`` and ``n_iterations`` on :class:`Sweep`. .. attribute:: GRID_SEARCH Deterministic cartesian-product sweep. For every parameter, ``grid_points_per_param`` evenly-spaced values are generated across the full ``bounds`` offset range and the cartesian product of all per-parameter grids is enumerated. ``n_iterations`` is ignored. .. attribute:: SINGLE_OFFSET Deterministic single-parameter sweep. Each parameter is varied independently across ``n_iterations`` evenly-spaced values (spanning its ``bounds`` offset range) while all other parameters are held at their nominal ``current_value``. Total parameter sets produced: ``len(parameters) × n_iterations``. .. py:attribute:: RANDOM :value: 'random' .. py:attribute:: GRID_SEARCH :value: 'grid' .. py:attribute:: SINGLE_OFFSET :value: 'single' .. py:class:: ParameterSpec(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Typed sampling specification for a single correction parameter. Strict by design (``extra="forbid"``): unknown fields raise a ``ValidationError`` so typos surface immediately. Mission-specific extras that the pipeline does not interpret go in :attr:`metadata`. .. attribute:: current_value Baseline parameter value(s). A scalar for OFFSET_KERNEL/OFFSET_TIME and a 3-element list ``[roll, pitch, yaw]`` for CONSTANT_KERNEL. .. attribute:: bounds ``[min, max]`` offset limits (same units as ``sigma``). .. attribute:: sigma Standard deviation for normal-distribution sampling. ``None`` means the parameter is held fixed at ``current_value``. .. attribute:: units Physical units string, e.g. ``"arcseconds"`` or ``"milliseconds"``. .. attribute:: distribution Sampling distribution name. Stored for documentation purposes; the current implementation always uses a normal distribution. .. attribute:: field Telemetry / science DataFrame column that this parameter modifies (required for ``OFFSET_KERNEL`` and ``OFFSET_TIME``). .. attribute:: transformation_type Optional hint consumed by kernel-creation routines (e.g. ``"dcm_rotation"`` or ``"angle_bias"``). .. attribute:: coordinate_frames Optional list of SPICE frame names affected by this parameter. .. attribute:: metadata Free-form mission-specific extras not interpreted by the pipeline (e.g. a display ``"name"``, provenance, calibration date). .. py:attribute:: model_config Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. .. py:attribute:: current_value :type: float | list[float] :value: 0.0 .. py:attribute:: bounds :type: list[float] :value: None .. py:attribute:: sigma :type: float | None :value: None .. py:attribute:: units :type: str | None :value: None .. py:attribute:: distribution :type: str :value: 'normal' .. py:attribute:: field :type: str | None :value: None .. py:attribute:: transformation_type :type: str | None :value: None .. py:attribute:: coordinate_frames :type: list[str] | None :value: None .. py:attribute:: metadata :type: dict[str, Any] :value: None .. py:class:: ParameterConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` A single parameter to vary during correction analysis. .. attribute:: ptype How this parameter is applied (constant kernel, offset kernel, or time offset). .. attribute:: config_file Path to the SPICE kernel JSON template, or ``None`` for time offsets that require no kernel file. .. attribute:: spec Sampling specification. Accepts a plain ``dict`` or ``None`` on construction (Pydantic coerces both to :class:`ParameterSpec` automatically; ``None`` becomes an empty ``ParameterSpec()``). .. py:attribute:: ptype :type: ParameterType .. py:attribute:: config_file :type: pathlib.Path | None :value: None .. py:attribute:: spec :type: ParameterSpec :value: None .. py:method:: _coerce_none_spec(values: Any) -> Any :classmethod: Convert ``spec=None`` to an empty ``ParameterSpec`` (backward compat). .. py:class:: GeolocationConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` SPICE kernel paths and instrument settings for geolocation. .. attribute:: meta_kernel_file Path to the mission meta-kernel JSON file. .. attribute:: generic_kernel_dir Directory containing generic/shared SPICE kernels. .. attribute:: dynamic_kernels Kernels regenerated from telemetry each run (SC-SPK, SC-CK, etc.) but *not* altered by parameter variations. .. attribute:: instrument_name SPICE instrument name (e.g. ``"CPRS_HYSICS"``). .. attribute:: time_field Column name in the science DataFrame that holds uGPS timestamps. .. attribute:: minimum_correlation Optional image-matching quality filter threshold (0.0–1.0). .. py:attribute:: meta_kernel_file :type: pathlib.Path .. py:attribute:: generic_kernel_dir :type: pathlib.Path .. py:attribute:: dynamic_kernels :type: list[pathlib.Path] :value: None .. py:attribute:: instrument_name :type: str .. py:attribute:: time_field :type: str .. py:attribute:: minimum_correlation :type: float | None :value: None .. py:class:: RequirementsConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Verification requirements / thresholds. Held as the required ``requirements`` field on :class:`GeolocationSetup` and consumed by :func:`~curryer.correction.verification.verify` and the correction verdict. .. attribute:: performance_threshold_m Per-measurement nadir-equivalent error limit in meters (must be > 0). A measurement *passes* when its error is **below** this value. :type: float .. attribute:: performance_spec_percent Minimum fraction of measurements (0–100) that must pass for the overall verification to be considered successful. :type: float .. py:attribute:: performance_threshold_m :type: float :value: None .. py:attribute:: performance_spec_percent :type: float :value: None .. py:class:: PSFSamplingConfig Configuration for PSF sampling during image matching. Default values are calibrated for Landsat 30 m GCPs, which are the standard reference for GCP-based geolocation verification. Override all fields for instruments with different ground resolution or motion characteristics. :param gcp_step_m: Ground control point step size in meters. Default is 30.0. :type gcp_step_m: float, optional :param motion_convolution_step_m: Step size for spacecraft motion convolution in meters. If ``None`` (default), derived as ``gcp_step_m / 20.0`` in ``__post_init__``. :type motion_convolution_step_m: float or None, optional :param psf_lat_sample_dist_deg: PSF sample distance in the latitude direction in degrees. Default approximately 2.7 m at the equator (Landsat calibration). :type psf_lat_sample_dist_deg: float, optional :param psf_lon_sample_dist_deg: PSF sample distance in the longitude direction in degrees. Default approximately 2.7 m at the equator (Landsat calibration). :type psf_lon_sample_dist_deg: float, optional .. py:attribute:: gcp_step_m :type: float :value: 30.0 .. py:attribute:: motion_convolution_step_m :type: float | None :value: None .. py:attribute:: psf_lat_sample_dist_deg :type: float :value: 2.4397105613972e-05 .. py:attribute:: psf_lon_sample_dist_deg :type: float :value: 2.8737038710207e-05 .. py:method:: __post_init__() -> None .. py:class:: SearchConfig Configuration for the image-matching correlation search grid. Default values are tuned for Landsat 30 m GCPs. Override all fields for instruments with different ground resolution. :param grid_size: Number of grid points per axis in the correlation search grid. Default 44 (Landsat-tuned). :type grid_size: int, optional :param grid_span_km: Half-width of the search grid in kilometers. Default 11.0. :type grid_span_km: float, optional :param reduction_factor: Multiplicative reduction applied to grid spacing each iteration. Default 0.8. :type reduction_factor: float, optional :param spacing_limit_m: Minimum grid spacing in meters; search stops when reached. Default 10.0 (Landsat-tuned). :type spacing_limit_m: float, optional .. py:attribute:: grid_size :type: int :value: 44 .. py:attribute:: grid_span_km :type: float :value: 11.0 .. py:attribute:: reduction_factor :type: float :value: 0.8 .. py:attribute:: spacing_limit_m :type: float :value: 10.0 .. py:class:: RegridConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Configuration for GCP chip regridding. Specifies output grid parameters for transforming irregular geodetic grids to regular latitude/longitude grids. ECEF → geodetic conversion always uses the WGS84 ellipsoid, which is the only ellipsoid supported by ``curryer.compute.spatial.ecef_to_geodetic``. :param output_grid_size: Desired output grid dimensions as (nrows, ncols). Mutually exclusive with ``output_resolution_deg``. :type output_grid_size: tuple[int, int], optional :param output_resolution_deg: Desired output resolution as (dlat, dlon) in degrees. Mutually exclusive with ``output_grid_size``. Required when ``output_bounds`` is set. :type output_resolution_deg: tuple[float, float], optional :param output_bounds: Explicit output grid bounds as (minlon, maxlon, minlat, maxlat) in degrees. Requires ``output_resolution_deg``. :type output_bounds: tuple[float, float, float, float], optional :param conservative_bounds: If True, shrink bounds to ensure all output points lie within the input irregular grid (avoids edge extrapolation). :type conservative_bounds: bool, default=True :param interpolation_method: Interpolation method; one of ``"bilinear"`` or ``"nearest"``. :type interpolation_method: str, default="bilinear" :param fill_value: Value assigned to output points that fall outside the input grid. :type fill_value: float, default=NaN .. py:attribute:: output_grid_size :type: tuple[int, int] | None :value: None .. py:attribute:: output_resolution_deg :type: tuple[float, float] | None :value: None .. py:attribute:: output_bounds :type: tuple[float, float, float, float] | None :value: None .. py:attribute:: conservative_bounds :type: bool :value: True .. py:attribute:: interpolation_method :type: str :value: 'bilinear' .. py:attribute:: fill_value :type: float .. py:method:: validate_method(v: str) -> str :classmethod: Validate interpolation method name. .. py:method:: validate_grid_size(v: tuple[int, int] | None) -> tuple[int, int] | None :classmethod: Validate that grid size has at least 2 rows and 2 columns. .. py:method:: validate_resolution(v: tuple[float, float] | None) -> tuple[float, float] | None :classmethod: Validate that resolution values are positive. .. py:method:: validate_bounds(v: tuple[float, float, float, float] | None) -> tuple[float, float, float, float] | None :classmethod: Validate that bounds are properly ordered. .. py:method:: validate_grid_spec() -> RegridConfig Validate that grid specification options are mutually consistent. .. py:class:: CalibrationFiles(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Direct paths to instrument calibration inputs. Both fields are optional and interim: real line-of-sight vectors and spacecraft geometry will be SPICE-derived from telemetry rather than loaded from files, so nothing in the pipeline *requires* these. .. attribute:: los_vectors_file Per-detector line-of-sight unit vectors (instrument frame). .. attribute:: psf_file Optical point-spread-function calibration. .. py:attribute:: los_vectors_file :type: pathlib.Path | None :value: None .. py:attribute:: psf_file :type: pathlib.Path | None :value: None .. py:class:: GeolocationSetup(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Durable, mission-specific setup for geolocation correction/verification. Built once per mission and reused across many :class:`Sweep` runs. Holds everything that does *not* change when you vary which parameters are swept: SPICE kernels and instrument identity (:class:`GeolocationConfig`), the pass/fail :class:`RequirementsConfig`, how input data is read (:class:`DataConfig`), static instrument calibration (:class:`CalibrationFiles`), the science-Dataset variable names, and an optional custom image-matching implementation. .. attribute:: geo SPICE kernels, instrument name, and science time field. .. attribute:: requirements Pass/fail thresholds used by verification and the correction verdict. .. attribute:: data_config How telemetry/science files are read. ``None`` uses CSV defaults. .. attribute:: calibration Optional direct calibration file paths. ``None`` when geometry is supplied another way (e.g. SPICE-derived). .. attribute:: spacecraft_position_name, boresight_name, transformation_matrix_name Variable names for the spacecraft-state fields in the image-matching ``xr.Dataset`` (mission-configurable; generic defaults). .. attribute:: image_matching_func Optional custom image-matching callable. ``None`` uses the built-in :func:`~curryer.correction.verification.image_matching`. Excluded from JSON serialisation because callables are not serialisable. .. py:attribute:: model_config Configuration for the model, should be a dictionary conforming to [`ConfigDict`][pydantic.config.ConfigDict]. .. py:attribute:: geo :type: GeolocationConfig .. py:attribute:: requirements :type: RequirementsConfig .. py:attribute:: data_config :type: DataConfig | None :value: None .. py:attribute:: calibration :type: CalibrationFiles | None :value: None .. py:attribute:: spacecraft_position_name :type: str :value: 'sc_position' .. py:attribute:: boresight_name :type: str :value: 'boresight' .. py:attribute:: transformation_matrix_name :type: str :value: 't_inst2ref' .. py:attribute:: image_matching_func :type: collections.abc.Callable | None :value: None .. py:class:: Sweep(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` A parameter-variation experiment run against a :class:`GeolocationSetup`. Lightweight and cheap to copy, so a setup can be held fixed while rapidly trying parameter variations. .. attribute:: parameters The parameters to vary (at least one). .. attribute:: search_strategy How parameter sets are generated (RANDOM / GRID_SEARCH / SINGLE_OFFSET). .. attribute:: n_iterations Iterations for RANDOM and values-per-parameter for SINGLE_OFFSET; ignored by GRID_SEARCH. .. attribute:: seed Random seed for reproducible RANDOM sweeps. .. attribute:: grid_points_per_param Evenly-spaced points per parameter for GRID_SEARCH. .. attribute:: max_grid_sets Safety cap on total GRID_SEARCH parameter sets. .. py:attribute:: parameters :type: list[ParameterConfig] :value: None .. py:attribute:: search_strategy :type: SearchStrategy .. py:attribute:: n_iterations :type: int :value: None .. py:attribute:: seed :type: int | None :value: None .. py:attribute:: grid_points_per_param :type: int :value: None .. py:attribute:: max_grid_sets :type: int :value: None .. py:method:: _validate_search_strategy() -> Sweep Ensure strategy-specific settings are consistent. .. py:method:: with_strategy(strategy: SearchStrategy | str, **sweep_changes: Any) -> Sweep Return a copy of this sweep using a different search strategy. Additional sweep-level fields (``n_iterations``, ``seed``, ``grid_points_per_param``, ``max_grid_sets``) may be overridden via keyword arguments. The result is re-validated, so typos and strategy-inconsistent settings fail eagerly. .. rubric:: Examples >>> grid = sweep.with_strategy("grid", grid_points_per_param=5) >>> repro = sweep.with_strategy(SearchStrategy.RANDOM, seed=7, n_iterations=200) .. py:method:: update_param(selector: int | str, **spec_changes: Any) -> Sweep Return a copy of this sweep with one parameter's :class:`ParameterSpec` changed. *selector* is either an integer index into :attr:`parameters`, or a string matched against each parameter's ``spec.field`` or its ``config_file`` stem. The changed spec is re-validated against :class:`ParameterSpec` (which is ``extra="forbid"``), so out-of-spec values or unknown field names raise immediately rather than being silently swallowed. .. rubric:: Examples >>> wider = sweep.update_param("hps.az_ang_nonlin", bounds=[-100.0, 100.0]) >>> tighter = sweep.update_param(0, sigma=5.0) .. py:method:: _resolve_param_index(selector: int | str) -> int Resolve *selector* (index, ``spec.field``, or ``config_file`` stem) to an index. .. py:class:: OutputConfig(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` Output settings for a correction run. .. attribute:: netcdf NetCDF structure/metadata config. ``None`` is auto-populated by :func:`~curryer.correction.pipeline.run_correction` from the setup's performance threshold. .. attribute:: output_filename Output NetCDF filename. ``None`` falls back to the default in :meth:`get_output_filename`. .. py:attribute:: netcdf :type: curryer.correction.io_config.NetCDFConfig | None :value: None .. py:attribute:: output_filename :type: str | None :value: None .. py:method:: get_output_filename(default: str = 'correction_results.nc') -> str Return :attr:`output_filename` if set, otherwise *default*. .. py:class:: CorrectionInput(/, **data: Any) Bases: :py:obj:`pydantic.BaseModel` A single input set for the correction loop. Replaces the positional tuple ``(telemetry_path, science_path, gcp_path)`` with named fields for clarity and IDE autocomplete. The reader for each file is chosen by :attr:`DataConfig.file_format`, so the inputs are format-agnostic. The first-class real-data path is a NetCDF image observation (radiance as the science variable) carrying telemetry, metadata, and science times; ``.mat`` files are interim test scaffolding. :param telemetry_file: Telemetry observation file (NetCDF for real data; CSV/HDF5 also read). :type telemetry_file: Path :param science_file: Science/timing observation file (NetCDF for real data; CSV/HDF5 also read). :type science_file: Path :param gcp_file: GCP reference-image file (NetCDF or ``.mat``). :type gcp_file: Path .. rubric:: Examples >>> from curryer.correction import CorrectionInput >>> inputs = [ ... CorrectionInput( ... telemetry_file="data/obs_20240317.nc", ... science_file="data/obs_20240317.nc", ... gcp_file="gcps/landsat_chip_001.nc", ... ) ... ] .. py:attribute:: telemetry_file :type: pathlib.Path .. py:attribute:: science_file :type: pathlib.Path .. py:attribute:: gcp_file :type: pathlib.Path .. py:function:: _read_config_json(config_path: pathlib.Path) -> dict Read and parse a JSON config file, raising clear errors on failure. .. py:function:: load_setup_from_json(config_path: pathlib.Path) -> GeolocationSetup Load a :class:`GeolocationSetup` from the ``"setup"`` section of a JSON file. .. py:function:: load_sweep_from_json(config_path: pathlib.Path) -> Sweep Load a :class:`Sweep` from the ``"sweep"`` section of a JSON file. .. py:function:: load_config_files(config_path: pathlib.Path) -> tuple[GeolocationSetup, Sweep, OutputConfig] Load ``(GeolocationSetup, Sweep, OutputConfig)`` from one JSON file. The file has three top-level sections — ``"setup"``, ``"sweep"``, and an optional ``"output"`` — each validated directly against its model. The ``"sweep".parameters`` entries mirror :class:`ParameterConfig` (``ptype`` / ``config_file`` / ``spec``); rotation frames are authored as a single ``CONSTANT_KERNEL`` parameter with ``spec.current_value = [roll, pitch, yaw]``.