Metadata-Version: 2.4
Name: geoembed
Version: 1.0.0
Summary: NetCL visual feature encoders for aerial and satellite imagery
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.22
Requires-Dist: Pillow>=9.0
Requires-Dist: netcl>=0.4.0

# GeoEmbed

NetCL-based visual feature encoders for aerial and satellite imagery.

---

## What It Can Do

* **256d L2-Normalized Vectors**: Encodes aerial and satellite imagery into compact 256-dimensional unit vectors ($\|z\|_2 = 1.0$).
* **Direct Similarity Measurement**: Vector dot product directly yields cosine similarity in $[-1.0, 1.0]$:

$$S(z_1, z_2) = z_1 \cdot z_2$$

* **Environmental Invariance**: Stable across multi-year temporal drift, seasons, foliage changes, sun angles, and illumination conditions.
* **Topological & Semantic Structure**: Captures road networks, parcel boundaries, forested areas, water bodies, and built-up patterns.
* **Unsupervised Clustering**: Latent vectors organically group semantic landscape and terrain types without manual labels:

| Reference Orthophoto | Continuous Latent Field (PCA) | Low Cluster ($k=2$) |
|:---:|:---:|:---:|
| ![Reference Orthophoto](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example11.png) | ![Continuous Field](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example1.png) | ![Cluster k=2](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example2.png) |

| Low Cluster ($k=3$) | Low Cluster ($k=4$) | Cluster Map ($k=6$) |
|:---:|:---:|:---:|
| ![Cluster k=3](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example3.png) | ![Cluster k=4](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example4.png) | ![Cluster k=6](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example6.png) |

| Cluster Map ($k=8$) | Cluster Map ($k=10$) | Cluster Map ($k=20$) |
|:---:|:---:|:---:|
| ![Cluster k=8](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example7.png) | ![Cluster k=10](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example8.png) | ![Cluster k=20](https://raw.githubusercontent.com/GodOfLores/geoembed/main/example10.png) |

At low cluster counts ($k=2, 3, 4$), the embedding space separates macroscopic terrain classes (dense forest vs. open agricultural land). As $k$ increases ($k=6, 8, 10, 20$), granular sub-structures such as access roads, parcel boundaries, and built forms emerge organically.

> *Data source of sample orthophotos: © LGL Baden-Württemberg (www.lgl-bw.de), licensed under dl-de/by-2-0. See [ATTRIBUTION.md](https://github.com/GodOfLores/geoembed/blob/main/ATTRIBUTION.md) for details.*

* **Downstream Applications**:
  * Unsupervised land-cover segmentation and zone maps
  * Parcel and boundary structure delineation
  * Temporal change detection across multi-year captures
  * Visual localization and map matching against orthophoto databases

---

## What It Expects as Input

* **Image Type**: Nadir aerial and satellite imagery (orthophotos, top-down perspective).
* **Format**: File path (`str` / `Path`), PIL `Image`, or NumPy array.
* **Color Channels**: 3-channel RGB (automatically converted and normalized to $[-1.0, 1.0]$).
* **Geometry**: Square image tiles.
* **Preferred Ground Scale / Zoom Level**: **`z=18` Web-Mercator** ($\approx 0.5\text{ m/pixel}$ GSD). The encoders are natively calibrated for $z=18$ aerial tiles. Any single, consistent scale works as long as queries and reference index match, but $z=18$ provides optimal feature separation.

---

## Limitations

* **No Scale Invariance**: The models deliberately do not enforce cross-scale invariance to preserve fine discriminative details. All tiles in an index and query set must share the same ground scale (preferably **$z=18$**). Mixing different zoom levels (e.g. $z=16$ and $z=18$) leads to scale aliasing and false matches.
* **Nadir Perspective Only**: Strictly top-down orthophotos. Oblique aerial imagery, perspective drone shots, or street-level photos are not supported.
* **RGB Only**: Standard 3-channel RGB. Multispectral, near-infrared (NIR), or synthetic-aperture radar (SAR) imagery are not supported without prior conversion.

---

## Code

```python
from geoembed import Model

# Default model (v3)
model = Model()

# Alternative iterations
model = Model.v11()
model = Model.v12()

# Encode a single image -> (256,) L2-normalized vector
vector = model.encode("tile.png")

# Encode a batch of images -> (N, 256) NumPy array
vectors = model.encode(["tile_a.png", "tile_b.png"])

# Cosine similarity between two images -> float in [-1.0, 1.0]
sim = model.similarity("tile_a.png", "tile_b.png")
print(f"Similarity: {sim:.4f}")
```
