Release Notes


0.4.0 (2026-04-26) — API cleanup, unit fixes, and footprint improvements

This release removes redundant CLI options, fixes unit-conversion bugs in the NGL adapter and the CSV-to-raster preprocessor, and improves the raster-footprint algorithm. No new processing features are introduced.

Removed

  • ``FittingConfig.method`` — the fitting algorithm is no longer stored in the configuration dataclass. It is controlled solely by the --fitting / -fi CLI flag (default least_squares). This removes a redundancy that could silently override the CLI value.

  • ``SpatialConfig.hull_method`` and ``–hull_method`` / ``-hm`` CLI flag — the hull algorithm is now selected automatically based on raster shape, with no user choice required.

  • ``–inverse_los`` / ``-iv`` CLI flag — superseded by the more explicit --los_convention / -lc flag (ENU or NEU).

Fixed

  • ``tools/reference.py`` — duplicate WAVELENGTH_MM constant (55.465763) replaced with an import of SENTINEL1_WAVELENGTH_MM from config.py. Ensures a single authoritative source for the Sentinel-1 wavelength.

  • ``gnss/ngl.py`` — ve / vn / vu / se / sn / su columns in the station DataFrame were left in m/yr (native NGL MIDAS units) after the LOS projection step. They are now multiplied by 1000 in _normalise() so that the DataFrame conforms to the canonical STATION_COLUMNS schema (all velocities in mm/yr). The metadata JSON output now also reports these fields in mm/yr, consistent with the schema comment.

  • ``tools/csv2raster.py`` — displacement time-series columns (DTs) were written in cm instead of radians. The same cm → rad conversion (4π / λ × 0.01) applied to the mean velocity band is now also applied to all time-series columns.

  • ``tools/geoprocessing.py`` — hard-coded wavelength 55.465763 mm replaced with SENTINEL1_WAVELENGTH_MM imported from config.py.

Changed

  • ``geo/spatial.py`` — raster-to-polygon footprint function rewritten. Replaces the former _complex_hull_from_raster / _simple_hull_from_raster pair with a single algorithm: OR-pooling subsampling to at most 600 × 600 pixels, 5 × 5 binary closing for exterior smoothing, and size-based selective hole filling (interior holes smaller than 5 % of the image area are filled; border-connected regions such as open sea are never touched). Handles both compact and complex (multi-island, coastal) shapes without user intervention.

  • ``doc/testing.rst`` — Case 3 station exclusion list updated with a full rationale table. The optimised command produces RMS residuals of 0.23 mm/yr (down from 1.1 mm/yr).

  • Package version — src/__init__.py updated to 0.4.0.


0.3.0 (2026-04-18) — Robustness, weighting, and output quality

This release focuses on scientific correctness of the GNSS weighting scheme, robustness of the EPOS API connector against server-side format changes, and a major reduction in pipeline verbosity. The public CLI is backward-compatible.

Added

  • ``–plate`` / ``-pl`` CLI argument — overrides the tectonic plate code (CFG.gnss.plate) at runtime, allowing different ITRF plate-motion corrections without editing config.py. Propagated end-to-end through runner.py, correction.py, referencing.py, factory.py, and EPOSjson.py.

Changed

  • Fisher-information GNSS station weighting — replaced the previous heuristic linear combination with a statistically principled formulation:

    \[w_i \propto \frac{N_i}{\sigma_i^2}\]

    where \(N_i\) is the number of GNSS observation days and \(\sigma_i\) is the projected LOS velocity uncertainty (mm/yr). Weights are normalised to unit mean. This automatically down-weights stations with short or imprecise time series without requiring manual tuning of per-criterion coefficients. Dead helper functions _minmax() and normalize() removed from tools/geodetic.py.

  • ``EPOSjson.py`` — tqdm progress bar — the outer station-download loop is now wrapped with a tqdm progress bar displaying live counters (added / cached / skipped). The former per-station "Dictionaries added." INFO log is replaced by a single summary line after the loop: [EPOS] Metadata fetch complete — N new, M cached, K skipped (no data).

  • ``EPOSjson.py`` — verbose log demotion — the following messages are demoted from WARNING to DEBUG (they are expected during normal operation and do not require user attention):

    • “No availability data for station …”

    • “Unexpected velocity/time-series response for …”

    • “Empty ‘results’ in velocity response …”

    • “Empty time series for …”

    • “Could not build metadata record for …”

  • Cache log demotion — tools/caching.py: "Cached '…'" messages demoted from INFO to DEBUG.

  • NumPy RuntimeWarning suppression — warnings.filterwarnings("ignore", category=RuntimeWarning, module="numpy") added at module level in EPOSjson.py to suppress expected "Mean of empty slice" and "invalid value encountered in divide" messages during LOS projection and outlier filtering.

  • SSL warning suppression — urllib3.disable_warnings(InsecureRequestWarning) moved to module level in EPOSjson.py to suppress per-request SSL warnings when connecting to the EPOS server (self-signed certificate; verify=False is intentional).

  • ``tools/timeseries.py`` — unsupported time-series format now raises NotImplementedError with a clear message instead of silently returning None.

Fixed

  • EPOS API: list-wrapped scalar values — the EPOS REST API began wrapping scalar velocity components (de/dt, dn/dt, du/dt, sed, snd, sud) in single-element lists. The new _scalar() helper in EPOSjson.py handles both value and [value] transparently.

  • EPOS API: JSON-string time-series response — for some stations the time-series endpoint returns a plain JSON string instead of a dict. The isinstance(ts_payload, dict) guard in EPOSjson.py prevents the resulting TypeError: string indices must be integers.

  • ``correction.py`` — grid_df[["nbr_days"]] = 0 (double brackets causing “Cannot set a DataFrame with multiple columns to the single column nbr_days”) corrected to grid_df["nbr_days"] = 0.

  • ``reference.py`` — multiple scientific and structural fixes:

    • Renamed delta_days → delta_years (variable represented years, not days).

    • Pinned reference epoch with idxmin() instead of iloc[0] to correctly anchor to the globally earliest acquisition.

    • Endpoint velocity replaced by scipy.stats.linregress on the model trajectory, providing a statistically robust velocity estimate.

    • Antenna offset correction now applied only to *_data columns, not *_model columns.

    • IRLS prior weights added: w_prior = 1 / sigma² (previously all stations were equally weighted in the referencing fit).

    • Fixed return pd.DataFrame() → return None, None to match the declared return type.

    • Added close() and __del__ methods to InsarReferencer to properly release the open rasterio dataset.


0.2.0 (2025-04-17) — Modular architecture refactoring

This release replaces the original monolithic src/tools/ package with a clean, package-oriented architecture. The public command-line interface is unchanged.

Added

  • src/config.py: all user-tunable parameters as frozen dataclasses (ProcessingConfig, IOConfig, FittingConfig, GNSSConfig, SpatialConfig). Replaces tools/parameters.py.

  • src/schema.py: shared data contracts — STATION_COLUMNS, FitResult, failed_fit, validate_station_df, normalise_station_df, empty_station_df.

  • src/fitting/: modular plane-fitting package.

    • fitting/weighting.py — Huber weights, Tukey weights, MAD scale estimate, prior-weight computation from GNSS precision and coverage.

    • fitting/irls.py — IRLS with Huber/Tukey robust reweighting, k-fold cross-validation, full metric suite (MSE, MAE, R², WRSS, σ²).

    • fitting/ransac.py — RANSAC with weighted OLS re-fit on inliers.

    • fitting/__init__.py — unified fit_plane(X, y, method=…) dispatcher.

  • src/geo/: geodetic and raster geometry package.

    • geo/geodetic.py — WGS-84 LLH ↔ ECEF conversion, CRS/projection selection, Voronoi-based outlier removal.

    • geo/raster.py — windowed raster sampling, LOS projection of NEU velocity vectors, band-interleave rewriting.

    • geo/spatial.py — convex/concave hull computation, Ordinary Kriging fallback grid.

  • src/gnss/: GNSS data-source adapters following the GNSSSource abstract base class (gnss/base.py).

    • gnss/epos.py, gnss/ngl.py — provider-specific adapters.

    • gnss/csv.py — user-supplied CSV velocity file reader.

    • gnss/factory.py — runtime source selection and LOS projection.

    • gnss/stations.py — station filtering, weighting, and spatial distribution assessment.

  • src/insar/: InSAR product ingestion (insar/reader.py) and raster correction (insar/corrector.py).

  • src/ioutil/metadata.py: .meta sidecar file writer.

  • src/pipeline/: orchestration layer.

    • pipeline/runner.py — input validation, session model, workflow dispatch.

    • pipeline/correction.py — velocity-field correction workflow.

    • pipeline/referencing.py — time-series referencing workflow.

  • src/utils/: cross-cutting utilities.

    • utils/cache.py — rewritten LRU on-disk file cache with clean public API (get, set, limit, size).

    • utils/logging_setup.py — structured logging initialisation.

    • utils/validation.py — CLI-argument validators.

  • src/viz/plots.py: diagnostic figures (scatter plots, station maps, residual histogram, fitted-plane contour, metrics summary).

  • src/rringg.py: rewritten as a thin CLI wrapper (~150 lines). All processing logic moved into pipeline/.

  • --log-level and --log-file CLI arguments.

  • tests/conftest.py: shared pytest path fixture.

  • tests/test_schema.py, tests/test_fitting.py, tests/test_geo_geodetic.py, tests/test_utils.py: 99 unit tests covering the new packages (all passing).

Changed

  • src/rringg.py reduced from ~1 050 lines to ~270 lines.

  • All tools.* imports in new modules migrated to thematic package equivalents (geo.*, gnss.*, fitting.*, etc.).

  • utils/cache.py re-implemented from scratch; retains the same LRU behaviour as the original tools/caching.py but with improved error reporting.

  • Python 3.9 compatibility restored: from __future__ import annotations added to all modules using X | Y union syntax.

  • Documentation fully updated to reflect new architecture.

Fixed

  • Typo velocity_raser_path → velocity_raster_path throughout.

  • Attribute name mismatches between SpatialConfig and pipeline/correction.py (x_offset → grid_x_offset_m, etc.).

  • Wrong call signature: station_weighting(df) replaced by compute_prior_weights(df).

  • normalize (non-existent in geo.geodetic) replaced by normalise_weights from fitting.weighting.

  • RANSAC cross_validate keyword argument incompatibility with the installed scikit-learn version.


0.0.1 (2024-01-13) — Initial release

First public release of RRINGG.