Metadata-Version: 2.4
Name: fundayao
Version: 0.2.0
Summary: Remote sensing image processing library
Author: Timo Balz
License: Unlicense
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: rasterio>=1.3
Requires-Dist: numpy>=1.24
Requires-Dist: matplotlib>=3.7
Requires-Dist: rpcfit>=0.9.9
Requires-Dist: geopandas>=1.0
Requires-Dist: scikit-learn>=1.3
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Provides-Extra: sam
Requires-Dist: segment-geospatial>=2.2; extra == "sam"
Dynamic: license-file

# fundayao

A Python library for processing remote sensing imagery, developed for
educational use in courses on the basic principles of remote sensing and its
applications.

Data from different sensors is imported into a common, self-describing format
(GeoTIFF + XML metadata), so that all processing steps work the same way
regardless of the data source. The format supports both geocoded products
(map geometry) and sensor-geometry products (image coordinates + RPC model)
and is specified in [docs/file-format.md](docs/file-format.md). Guidance for
AI coding agents working on this repository lives in [AGENTS.md](AGENTS.md).

## Features

- **Import**: Sentinel-2 L1C (SAFE), SuperView Neo-1 L1B (PAN + MUX, with
  RPCs) and WH-1 / LJ-3 L1A (PAN + MSS, with RPCs) → fundayao products (one
  GeoTIFF per resolution group + XML metadata with geometry, calibration,
  angles, provenance)
- **Radiometry**: DN → TOA reflectance/radiance, sun-elevation correction,
  DOS atmospheric correction (TOA → BOA) — each formula is an explicit,
  inspectable processing step recorded in the product provenance
- **Spectral indices**: NDVI, NDWI, MNDWI, NDBI, NDMI, SAVI, EVI — bands are
  selected from the metadata by central wavelength, not hardcoded band names,
  so the same code works across sensors
- **Band math**: custom index formulas over wavelength-addressed bands
  (e.g. `"(nir - red) / (nir + red)"`)
- **AOI subsetting**: windowed crops for geocoded products; sensor-geometry
  products get freshly re-estimated RPCs (via `rpcfit`) so the geometry model
  stays exact after cropping
- **Statistics**: per-band statistics (min/max/mean/std/percentiles), raw or
  calibrated
- **Visualization**: true-color and false-color (CIR) composites with
  percentile stretch, band/index maps with map coordinates, histograms,
  spectral profiles (reflectance vs wavelength at chosen points), band
  scatterplots (e.g. red–NIR)
- **Segmentation**: SAM-based automatic segmentation via `samgeo`
  (`segment-geospatial`), producing an L3 index mask product with optional
  unique segment IDs or a binary foreground mask, and optional vector output
- **Unsupervised classification**: classic k-means and ISODATA clustering
  (train on a pixel sample, classify the whole scene block-wise) producing
  an L3 product with uint8 class labels
- **Supervised classification**: nearest neighbour (minimum distance),
  box (parallelepiped), maximum likelihood, spectral angle mapper (SAM)
  and random forest (scikit-learn, with per-band feature importances in
  the provenance), trained from vector labels (any geopandas-readable
  format, e.g. Shapefile, GeoPackage, GeoJSON) on the grid of a chosen
  band resolution (10 m or 20 m for Sentinel-2); produces an L3 product
  whose XML records the label → class-name mapping
- **Accuracy assessment**: confusion matrix, overall/producer's/user's
  accuracy and Cohen's kappa of a supervised product against independent
  reference polygons (`fundayao accuracy`)
- **Provenance & metadata**: every processing step is recorded in the product
  XML; the radiometric state (DN / TOA / BOA / index) is explicit and checked
  by every consumer

## Installation

```sh
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
```

Requires Python ≥ 3.10. Main dependencies: rasterio, numpy, matplotlib, rpcfit,
geopandas (supervised classification training labels), scikit-learn (random
forest).
The SAM segmentation feature is optional: install it with
`pip install -e ".[sam]"` (requires `segment-geospatial`, which pulls in
PyTorch and downloads a ~2 GB model checkpoint on first use).

## Usage

### Command line

```sh
# import sensor data
fundayao import-s2 S2C_MSIL1C_....SAFE products/
fundayao import-svn1 SVN1-01_..._01/ products/
fundayao import-wh1 /path/to/WH-1/ products/

# radiometric correction chain
fundayao toa products/S2C_T36RUU_20260815_L2 products/ --quantity reflectance
fundayao dos products/S2C_T36RUU_20260815_L2_TOA products/ --percentile 1

# indices and custom formulas
fundayao index ndvi products/S2C_T36RUU_20260815_L2 products/
fundayao band-math --name ndwi \
    --formula "(green - nir) / (green + nir)" \
    --band green=560 --band nir=842 \
    products/S2C_T36RUU_20260815_L2 products/

# subset an area of interest (map coords, or pixel coords for RPC products)
fundayao subset products/S2C_T36RUU_20260815_L2 products/ \
    --bbox 300000 3350000 320000 3370000
fundayao subset products/SVN101_20241203_MUX1_L1 products/ \
    --bbox 0 0 2000 2000 --coords pixel

# statistics and quicklooks
fundayao stats products/S2C_T36RUU_20260815_L2 --calibrated
fundayao quicklook products/S2C_T36RUU_20260815_L2 rgb.png
fundayao quicklook products/S2C_T36RUU_20260815_L2 cir.png --cir
fundayao quicklook products/S2C_T36RUU_20260815_L2_NDVI ndvi.png --band NDVI

# SAM segmentation (requires fundayao[sam])
fundayao segment-sam products/S2C_T36RUU_20260815_L2 products/
fundayao segment-sam products/S2C_T36RUU_20260815_L2 products/ --binary --scale 2
fundayao segment-sam products/S2C_T36RUU_20260815_L2 products/ \
    --model vit_l --device cuda --vector segments.geojson

# unsupervised classification
fundayao classify kmeans products/S2C_T36RUU_20260815_L2 products/ --clusters 6
fundayao classify isodata products/S2C_T36RUU_20260815_L2 products/ \
    --clusters 8 --max-clusters 16 --max-std 0.02 --min-distance 0.05

# supervised classification (training labels from a vector file)
fundayao classify nearest-neighbour products/S2C_T36RUU_20260815_L2 products/ \
    --labels training_polygons.shp --field label
fundayao classify box products/S2C_T36RUU_20260815_L2 products/ \
    --labels training_polygons.geojson --resolution 20 --box-std 2.0
fundayao classify max-likelihood products/S2C_T36RUU_20260815_L2 products/ \
    --labels training_polygons.gpkg --max-per-class 5000
fundayao classify sam products/S2C_T36RUU_20260815_L2 products/ \
    --labels training_polygons.shp
fundayao classify rf products/S2C_T36RUU_20260815_L2 products/ \
    --labels training_polygons.shp --trees 200 --depth 20

# accuracy assessment against independent reference polygons
fundayao accuracy products/S2C_T36RUU_20260815_L2_ML \
    --labels validation_polygons.shp
```

### Python API

```python
from fundayao.io import (
    import_sentinel2_l1c, import_superview_neo1, import_wh1)
from fundayao.radiometry import toa_reflectance, dos
from fundayao.indices import ndvi, mndwi, band_math
from fundayao.segment import sam_segment
from fundayao.classify import (kmeans, isodata, nearest_neighbour, box,
                               maximum_likelihood, spectral_angle_mapper,
                               random_forest, accuracy_assessment)
from fundayao.subset import subset
from fundayao.stats import band_statistics
from fundayao.visualize import (
    rgb_composite, plot_band, spectral_profile, band_scatter)

product = import_sentinel2_l1c("S2C_MSIL1C_....SAFE", "products")
boa = dos(toa_reflectance(product, "products"), "products")
ndvi_product = ndvi(boa, "products")

table = band_statistics(product, apply_calibration=True)
sam_mask = sam_segment(product, "products", unique=False, scale=2)
classes = kmeans(product, "products", n_clusters=6)
classes = isodata(product, "products", n_clusters=8, max_clusters=16)
# supervised: train on polygons from any geopandas-readable vector file
classes = nearest_neighbour(product, "products", "training_polygons.shp")
classes = box(product, "products", "training_polygons.shp", box_std=2.0)
classes = maximum_likelihood(product, "products", "training_polygons.shp",
                             resolution=20)
classes = spectral_angle_mapper(product, "products", "training_polygons.shp")
classes = random_forest(product, "products", "training_polygons.shp",
                        n_estimators=200)
# accuracy assessment against independent reference polygons
report = accuracy_assessment(classes, "validation_polygons.shp")
print(report["overall"], report["kappa"])
fig = rgb_composite(product, out_path="rgb.png")
fig = plot_band(ndvi_product, "NDVI", cmap="RdYlGn", out_path="ndvi.png")
fig = spectral_profile(product, [(305100.0, 3356200.0)], out_path="profile.png")
fig = band_scatter(product, 665, 842, out_path="red_nir.png")

# sensor-geometry data (SuperView Neo-1 / WH-1): pixel-coordinate subset, RPCs refit
svn_products = import_superview_neo1("SVN1-01_..._01/", "products")
sub = subset(svn_products[0], "products", (0, 0, 2000, 2000), coords="pixel")
wh1_products = import_wh1("/path/to/WH-1/", "products")
sub = subset(wh1_products[0], "products", (0, 0, 2000, 2000), coords="pixel")
```

## Project layout

```
├── docs/file-format.md      # fundayao product format specification
├── src/fundayao/
│   ├── cli.py               # command line interface (thin wrapper)
│   ├── io/
│   │   ├── sentinel2.py     # Sentinel-2 L1C importer
│   │   ├── superview.py     # SuperView Neo-1 L1B importer
│   │   └── wh1.py           # WH-1 / LJ-3 L1A importer
│   ├── radiometry.py        # TOA / sun-elevation / DOS corrections
│   ├── indices.py           # spectral indices + band math
│   ├── subset.py            # AOI subsetting (+ RPC re-estimation)
│   ├── segment.py           # SAM segmentation via segment-geospatial
│   ├── classify.py          # classification: kmeans/isodata (unsupervised),
│   │                        # nearest_neighbour/box/maximum_likelihood/
│   │                        # spectral_angle_mapper/random_forest
│   │                        # (supervised) + accuracy_assessment
│   ├── stats.py             # per-band statistics
│   ├── visualize.py         # composites, maps, profiles, scatterplots
│   └── _rgb.py              # internal RGB GeoTIFF builder
└── tests/                   # pytest suite (synthetic fixtures + integration)
```

## Testing

```sh
.venv/bin/python -m pytest
```

The unit tests run on small synthetic products. Additional integration tests
run against real data when pointed at it via environment variables:

```sh
FUNDAO_S2_TEST_DATA=/path/to/S2 \
FUNDAO_SVN1_TEST_DATA=/path/to/SVN1 \
FUNDAO_WH1_TEST_DATA=/path/to/WH-1 \
FUNDAO_SEGMENT_SAM_TEST_DATA=/path/to/fundayao/product \
FUNDAO_S2_CLASSIFY_PRODUCT=/path/to/imported/fundayao/product \
FUNDAO_S2_CLASSIFY_LABELS=/path/to/labels.shp \
.venv/bin/python -m pytest
```

## AI-generated code disclaimer

This repository was created by AI vibe-coding tools. The code,
documentation, and other materials are provided without any warranty and may
contain errors. No guarantee is made that the contents are free of third-party
intellectual-property rights, including copyright. Use this software entirely at
your own risk; verify its correctness and legal status for your jurisdiction
before relying on it.

## License

Unlicense (public domain dedication) — see [LICENSE](LICENSE).
