Image-to-Image
Diffusers
Safetensors
sar-to-eo
remote-sensing
synthetic-aperture-radar
flow-matching
lora
Instructions to use JeonghyeokDo/GeoSET with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use JeonghyeokDo/GeoSET with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("black-forest-labs/FLUX.2-klein-base-4B", dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("JeonghyeokDo/GeoSET") prompt = "Turn this cat into a dog" input_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat.png") image = pipe(image=input_image, prompt=prompt).images[0] - Inference
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
Add GeoSET release files (189/211)
Browse files- baselines/spacenet6/cbbdm/README.md +42 -0
- baselines/spacenet6/cbbdm/last_model.pth +3 -0
- baselines/spacenet6/conddiff/README.md +40 -0
- baselines/spacenet6/conddiff/ema_final.pt +3 -0
- baselines/spacenet6/controlnet/README.md +41 -0
- baselines/spacenet6/controlnet/config.json +56 -0
- baselines/spacenet6/controlnet/diffusion_pytorch_model.safetensors +3 -0
- baselines/spacenet6/cyclegan/README.md +43 -0
- baselines/spacenet6/cyclegan/net_G_A.pth +3 -0
- baselines/spacenet6/cyclegan/net_G_B.pth +3 -0
- baselines/spacenet6/ddpm-sr3/README.md +40 -0
- baselines/spacenet6/ddpm-sr3/gen.pth +3 -0
- baselines/spacenet6/e3diff/README.md +41 -0
- baselines/spacenet6/e3diff/gen.pth +3 -0
- baselines/spacenet6/hi-diff/README.md +44 -0
- baselines/spacenet6/hi-diff/S1_net_g_latest.pth +3 -0
- baselines/spacenet6/hi-diff/S1_net_le_latest.pth +3 -0
- baselines/spacenet6/hi-diff/S2_net_d_latest.pth +3 -0
- baselines/spacenet6/hi-diff/S2_net_g_latest.pth +3 -0
- baselines/spacenet6/hi-diff/S2_net_le_dm_latest.pth +3 -0
- baselines/spacenet6/pix2pix/README.md +41 -0
- baselines/spacenet6/pix2pix/net_G.pth +3 -0
- baselines/spacenet6/pix2pixhd/README.md +42 -0
- baselines/spacenet6/pix2pixhd/net_G.pth +3 -0
- baselines/spacenet6/resshift/README.md +40 -0
- baselines/spacenet6/resshift/ema_model.pth +3 -0
- baselines/spacenet6/sd21-ft/README.md +42 -0
- baselines/spacenet6/sd21-ft/config.json +45 -0
- baselines/spacenet6/sd21-ft/diffusion_pytorch_model.safetensors +3 -0
- baselines/spacenet6/seg-cyclegan/README.md +44 -0
- baselines/spacenet6/seg-cyclegan/net_G_A.pth +3 -0
- baselines/spacenet6/seg-cyclegan/net_G_B.pth +3 -0
- baselines/spacenet6/seg-cyclegan/segnet_guide.pth +3 -0
- baselines/spacenet6/spade/README.md +42 -0
- baselines/spacenet6/spade/net_G.pth +3 -0
- baselines/spacenet6/stegogan/README.md +43 -0
- baselines/spacenet6/stegogan/net_G_A.pth +3 -0
- baselines/spacenet6/stegogan/net_G_B.pth +3 -0
- generalist/autoencoder_flux2.py +224 -0
- generalist/lora.py +130 -0
- generalist/model_index.json +20 -0
- generalist/pipeline.py +121 -0
- generalist/pipeline_geoset.py +121 -0
- generalist/sar_encoder/autoencoder_flux2.py +224 -0
- generalist/sar_encoder/config.json +13 -0
- generalist/sar_encoder/diffusion_pytorch_model.safetensors +3 -0
- generalist/sar_encoder/sar_encoder_geoset.py +102 -0
- generalist/sar_encoder_geoset.py +102 -0
- generalist/scheduler/scheduler_config.json +4 -0
- generalist/scheduler/scheduler_flow_bridge.py +59 -0
baselines/spacenet6/cbbdm/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# cBBDM · SpaceNet6
|
| 2 |
+
|
| 3 |
+
cBBDM (GRSL'25) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the cBBDM row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Kim and Chung, "Conditional Brownian bridge diffusion model for VHR SAR to optical image translation", IEEE Geoscience and Remote Sensing Letters, 2025.
|
| 8 |
+
- Implementation: [egshkim/ConditionalBBDM-for-VHR-SAR-to-Optical](https://github.com/egshkim/ConditionalBBDM-for-VHR-SAR-to-Optical) at commit `8ce15934f4d4e3f01efe70d11e2d9b9e0859210c`.
|
| 9 |
+
- Training and inference code: [`baselines/cbbdm`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/cbbdm) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `last_model.pth` | 2,118,345,622 | `4c6148b7e86c1764ddbdfaa655d6a84a2f663a1174d61a9107cc98af25836f0b` | cBBDM checkpoint (`model`, `ema`, `epoch`, `step`), including the vq-f4 autoencoder; test.sh samples with the EMA weights |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 280.4 | 0.347 | 0.2620 | 0.450 | 0.419 | 0.447 | 19.72 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/cbbdm/fetch.sh
|
| 31 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 32 |
+
python baselines/data/ldm_prepare.py spacenet6
|
| 33 |
+
wget https://ommer-lab.com/files/latent-diffusion/vq-f4.zip && unzip vq-f4.zip -d $CKPT_ROOT/vq-f4
|
| 34 |
+
hf download JeonghyeokDo/GeoSET "baselines/spacenet6/cbbdm/last_model.pth" --local-dir hf
|
| 35 |
+
mkdir -p $CKPT_ROOT/cbbdm/spacenet6 && cp hf/baselines/spacenet6/cbbdm/* $CKPT_ROOT/cbbdm/spacenet6/
|
| 36 |
+
bash baselines/cbbdm/test.sh spacenet6 # writes $WORK_DIR/results/cbbdm/spacenet6/<stem>.png
|
| 37 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/cbbdm/spacenet6
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Licence
|
| 41 |
+
|
| 42 |
+
These weights are released under the MIT licence of the cBBDM code (Copyright (c) 2025 egshkim, [LICENSE](https://github.com/egshkim/ConditionalBBDM-for-VHR-SAR-to-Optical/blob/8ce15934f4d4e3f01efe70d11e2d9b9e0859210c/LICENSE)). The checkpoint contains the weights of the CompVis latent-diffusion vq-f4 autoencoder, MIT licence, Copyright (c) 2022 Machine Vision and Learning Group, LMU Munich ([LICENSE](https://github.com/CompVis/latent-diffusion/blob/main/LICENSE)).
|
baselines/spacenet6/cbbdm/last_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4c6148b7e86c1764ddbdfaa655d6a84a2f663a1174d61a9107cc98af25836f0b
|
| 3 |
+
size 2118345622
|
baselines/spacenet6/conddiff/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CondDiff · SpaceNet6
|
| 2 |
+
|
| 3 |
+
CondDiff (GRSL'23) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the CondDiff row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Bai, Pu and Xu, "Conditional diffusion for SAR to optical image translation", GRSL 2023.
|
| 8 |
+
- Implementation: [Coordi777/Conditional-Diffusion-for-SAR-to-Optical-Image-Translation](https://github.com/Coordi777/Conditional-Diffusion-for-SAR-to-Optical-Image-Translation) at commit `8326f54adf3a8d717792cb7abc7d3d2b7dfac565`.
|
| 9 |
+
- Training and inference code: [`baselines/conddiff`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/conddiff) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `ema_final.pt` | 657,495,287 | `7ba72b69057de1aaef89ce7aabd3adc358dd7f1123874952fbc29cf9e7bf5b66` | UNet EMA (0.9999) weights at step 50,000 (256 × 256 model) |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 160.1 | 0.274 | 0.1012 | 0.514 | 0.517 | 0.198 | 14.68 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/conddiff/fetch.sh
|
| 31 |
+
python baselines/data/diff_data.py conddiff spacenet6
|
| 32 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/conddiff/*" --local-dir hf
|
| 33 |
+
mkdir -p $CKPT_ROOT/conddiff/spacenet6 && cp hf/baselines/spacenet6/conddiff/ema_final.pt $CKPT_ROOT/conddiff/spacenet6/
|
| 34 |
+
bash baselines/conddiff/test.sh spacenet6 # writes $WORK_DIR/results/conddiff/spacenet6/<stem>.png
|
| 35 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/conddiff/spacenet6
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## Licence
|
| 39 |
+
|
| 40 |
+
The CondDiff repository contains no licence file; it is derived from [openai/guided-diffusion](https://github.com/openai/guided-diffusion) (MIT). These weights were trained by the GeoSET authors with that code; the GeoSET repository distributes only its changes to the code.
|
baselines/spacenet6/conddiff/ema_final.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7ba72b69057de1aaef89ce7aabd3adc358dd7f1123874952fbc29cf9e7bf5b66
|
| 3 |
+
size 657495287
|
baselines/spacenet6/controlnet/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ControlNet · SpaceNet6
|
| 2 |
+
|
| 3 |
+
ControlNet (ICCV'23) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the ControlNet row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Zhang, Rao and Agrawala, "Adding conditional control to text-to-image diffusion models", ICCV 2023.
|
| 8 |
+
- Implementation: the diffusers ControlNet training example ([`examples/controlnet/train_controlnet.py`](https://github.com/huggingface/diffusers/blob/v0.37.0/examples/controlnet/train_controlnet.py), v0.37.0) on Stable Diffusion 2.1-base (`Manojb/stable-diffusion-2-1-base`, revision `0094d483a120f3f33dafbd187ea4aa60d10de75c`).
|
| 9 |
+
- Training and inference code: [`baselines/controlnet`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/controlnet) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `diffusion_pytorch_model.safetensors` | 1,456,953,560 | `39c14b3d5059b9380673b9c9341f0aa7774a36ad8d39419d0360e5ff76afeea4` | ControlNet weights (diffusers `ControlNetModel`, fp32) |
|
| 16 |
+
| `config.json` | 1,293 | `911d4ffe1344ecfbb778a561186875c08ae2fc51a9f3a4c54a087363ef09b881` | ControlNet configuration |
|
| 17 |
+
|
| 18 |
+
## Results (GeoSET paper, Table 3)
|
| 19 |
+
|
| 20 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 21 |
+
|
| 22 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 23 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 24 |
+
| 131.3 | 0.279 | 0.0635 | 0.615 | 0.413 | 0.448 | 14.45 |
|
| 25 |
+
|
| 26 |
+
## Inference
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 30 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 31 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 32 |
+
python baselines/data/ldm_prepare.py spacenet6
|
| 33 |
+
hf download JeonghyeokDo/GeoSET "baselines/spacenet6/controlnet/diffusion_pytorch_model.safetensors" "baselines/spacenet6/controlnet/config.json" --local-dir hf
|
| 34 |
+
mkdir -p $CKPT_ROOT/controlnet/spacenet6 && cp hf/baselines/spacenet6/controlnet/* $CKPT_ROOT/controlnet/spacenet6/
|
| 35 |
+
bash baselines/controlnet/test.sh spacenet6 # writes $WORK_DIR/results/controlnet/spacenet6/<stem>.png
|
| 36 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/controlnet/spacenet6
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
## Licence
|
| 40 |
+
|
| 41 |
+
These weights are a derivative of Stable Diffusion 2.1-base and are released under the CreativeML Open RAIL++-M licence ([text](https://huggingface.co/stabilityai/stable-diffusion-2/blob/main/LICENSE-MODEL)); the use-based restrictions of its Attachment A apply to any use and redistribution. The training script is Apache-2.0 (diffusers).
|
baselines/spacenet6/controlnet/config.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "ControlNetModel",
|
| 3 |
+
"_diffusers_version": "0.37.1",
|
| 4 |
+
"act_fn": "silu",
|
| 5 |
+
"addition_embed_type": null,
|
| 6 |
+
"addition_embed_type_num_heads": 64,
|
| 7 |
+
"addition_time_embed_dim": null,
|
| 8 |
+
"attention_head_dim": [
|
| 9 |
+
5,
|
| 10 |
+
10,
|
| 11 |
+
20,
|
| 12 |
+
20
|
| 13 |
+
],
|
| 14 |
+
"block_out_channels": [
|
| 15 |
+
320,
|
| 16 |
+
640,
|
| 17 |
+
1280,
|
| 18 |
+
1280
|
| 19 |
+
],
|
| 20 |
+
"class_embed_type": null,
|
| 21 |
+
"conditioning_channels": 3,
|
| 22 |
+
"conditioning_embedding_out_channels": [
|
| 23 |
+
16,
|
| 24 |
+
32,
|
| 25 |
+
96,
|
| 26 |
+
256
|
| 27 |
+
],
|
| 28 |
+
"controlnet_conditioning_channel_order": "rgb",
|
| 29 |
+
"cross_attention_dim": 1024,
|
| 30 |
+
"down_block_types": [
|
| 31 |
+
"CrossAttnDownBlock2D",
|
| 32 |
+
"CrossAttnDownBlock2D",
|
| 33 |
+
"CrossAttnDownBlock2D",
|
| 34 |
+
"DownBlock2D"
|
| 35 |
+
],
|
| 36 |
+
"downsample_padding": 1,
|
| 37 |
+
"encoder_hid_dim": null,
|
| 38 |
+
"encoder_hid_dim_type": null,
|
| 39 |
+
"flip_sin_to_cos": true,
|
| 40 |
+
"freq_shift": 0,
|
| 41 |
+
"global_pool_conditions": false,
|
| 42 |
+
"in_channels": 4,
|
| 43 |
+
"layers_per_block": 2,
|
| 44 |
+
"mid_block_scale_factor": 1,
|
| 45 |
+
"mid_block_type": "UNetMidBlock2DCrossAttn",
|
| 46 |
+
"norm_eps": 1e-05,
|
| 47 |
+
"norm_num_groups": 32,
|
| 48 |
+
"num_attention_heads": null,
|
| 49 |
+
"num_class_embeds": null,
|
| 50 |
+
"only_cross_attention": false,
|
| 51 |
+
"projection_class_embeddings_input_dim": null,
|
| 52 |
+
"resnet_time_scale_shift": "default",
|
| 53 |
+
"transformer_layers_per_block": 1,
|
| 54 |
+
"upcast_attention": false,
|
| 55 |
+
"use_linear_projection": true
|
| 56 |
+
}
|
baselines/spacenet6/controlnet/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:39c14b3d5059b9380673b9c9341f0aa7774a36ad8d39419d0360e5ff76afeea4
|
| 3 |
+
size 1456953560
|
baselines/spacenet6/cyclegan/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CycleGAN · SpaceNet6
|
| 2 |
+
|
| 3 |
+
CycleGAN (ICCV'17) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the CycleGAN row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Zhu, Park, Isola and Efros, "Unpaired image-to-image translation using cycle-consistent adversarial networks", ICCV 2017.
|
| 8 |
+
- Implementation: [junyanz/pytorch-CycleGAN-and-pix2pix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix) at commit `2a7afba2895d52556dd5dfe07e8555ef657ced6f`.
|
| 9 |
+
- Training and inference code: [`baselines/cyclegan`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/cyclegan) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `net_G_A.pth` | 45,533,133 | `580135302c8bfa73b258f37c3194c5ab4a3bbe2b26c25bdd17cb77506f6e99a2` | Generator G_A (`resnet_9blocks`), SAR → EO; produces the output image |
|
| 16 |
+
| `net_G_B.pth` | 45,533,133 | `bdd465d9502581b9181d0a8f6f8dd7ddda35e549815b6c8dba147f525a917aa3` | Generator G_B (`resnet_9blocks`), EO → SAR; loaded by the upstream test script |
|
| 17 |
+
|
| 18 |
+
## Results (GeoSET paper, Table 3)
|
| 19 |
+
|
| 20 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 21 |
+
|
| 22 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 23 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 24 |
+
| 119.3 | 0.219 | 0.0436 | 0.660 | 0.373 | 0.478 | 16.88 |
|
| 25 |
+
|
| 26 |
+
## Inference
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 30 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 31 |
+
bash baselines/cyclegan/fetch.sh
|
| 32 |
+
bash baselines/pix2pix/fetch.sh # combine_A_and_B.py, used by gan_prepare.py
|
| 33 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 34 |
+
python baselines/data/gan_prepare.py spacenet6
|
| 35 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/cyclegan/*.pth" --local-dir hf
|
| 36 |
+
mkdir -p $CKPT_ROOT/cyclegan/spacenet6 && cp hf/baselines/spacenet6/cyclegan/*.pth $CKPT_ROOT/cyclegan/spacenet6/
|
| 37 |
+
bash baselines/cyclegan/test.sh spacenet6 # writes $WORK_DIR/results/cyclegan/spacenet6/<stem>.png
|
| 38 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/cyclegan/spacenet6
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
## Licence
|
| 42 |
+
|
| 43 |
+
These weights were trained with the pytorch-CycleGAN-and-pix2pix code and are released under its BSD licence (Copyright (c) 2017 Jun-Yan Zhu and Taesung Park; Copyright (c) 2016 Phillip Isola and Jun-Yan Zhu; [LICENSE](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/2a7afba2895d52556dd5dfe07e8555ef657ced6f/LICENSE)). The GeoSET repository states its change to the code (`baselines/cyclegan`).
|
baselines/spacenet6/cyclegan/net_G_A.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:580135302c8bfa73b258f37c3194c5ab4a3bbe2b26c25bdd17cb77506f6e99a2
|
| 3 |
+
size 45533133
|
baselines/spacenet6/cyclegan/net_G_B.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bdd465d9502581b9181d0a8f6f8dd7ddda35e549815b6c8dba147f525a917aa3
|
| 3 |
+
size 45533133
|
baselines/spacenet6/ddpm-sr3/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DDPM (SR3) · SpaceNet6
|
| 2 |
+
|
| 3 |
+
DDPM (SR3) (TPAMI'22) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the DDPM (SR3) row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Saharia, Ho, Chan, Salimans, Fleet and Norouzi, "Image super-resolution via iterative refinement", TPAMI 2022.
|
| 8 |
+
- Implementation: stage 1 (conditional DDPM) of [DeepSARRS/E3Diff](https://github.com/DeepSARRS/E3Diff) at commit `38601093ab8f8e4b478144621f20890b100a3b74`.
|
| 9 |
+
- Training and inference code: [`baselines/ddpm-sr3`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/ddpm-sr3) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `gen.pth` | 768,427,299 | `204f19caaa3efb59cd4d351a2792a6671bdb40f3400eae39aa6ee006fd203db6` | Conditional DDPM UNet (E3Diff stage 1), state dict at iteration 250,000; 3-channel output, [SAR-PPB, SAR-canny, SAR] condition |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 219.1 | 0.333 | 0.1616 | 0.419 | 0.622 | 0.111 | 12.45 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/ddpm-sr3/fetch.sh
|
| 31 |
+
python baselines/data/diff_data.py e3diff spacenet6
|
| 32 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/ddpm-sr3/*" --local-dir hf
|
| 33 |
+
mkdir -p $CKPT_ROOT/ddpm-sr3/spacenet6 && cp hf/baselines/spacenet6/ddpm-sr3/gen.pth $CKPT_ROOT/ddpm-sr3/spacenet6/
|
| 34 |
+
bash baselines/ddpm-sr3/test.sh spacenet6 # writes $WORK_DIR/results/ddpm-sr3/spacenet6/<stem>.png
|
| 35 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/ddpm-sr3/spacenet6
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## Licence
|
| 39 |
+
|
| 40 |
+
The E3Diff repository contains no licence file (its bundled `SoftPool/` directory is MIT). These weights were trained by the GeoSET authors with that code; the GeoSET repository distributes only its changes to the code.
|
baselines/spacenet6/ddpm-sr3/gen.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:204f19caaa3efb59cd4d351a2792a6671bdb40f3400eae39aa6ee006fd203db6
|
| 3 |
+
size 768427299
|
baselines/spacenet6/e3diff/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# E3Diff · SpaceNet6
|
| 2 |
+
|
| 3 |
+
E3Diff (GRSL'24) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the E3Diff row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Qin, Zou, Li and Zhang, "Efficient end-to-end diffusion model for one-step SAR-to-optical translation", GRSL 2024.
|
| 8 |
+
- Implementation: [DeepSARRS/E3Diff](https://github.com/DeepSARRS/E3Diff) at commit `38601093ab8f8e4b478144621f20890b100a3b74` (stage 2, initialised from the DDPM (SR3) stage-1 weights).
|
| 9 |
+
- Training and inference code: [`baselines/e3diff`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/e3diff) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `gen.pth` | 768,427,299 | `53786dd3255871070effc0353ffefffa3339ab3ebacccbde3f9d5ed04fef542c` | One-step generator UNet (E3Diff stage 2), state dict at iteration 310,000; 3-channel output, [SAR-PPB, SAR-canny, SAR] condition |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 110.6 | 0.197 | 0.0440 | 0.696 | 0.361 | 0.462 | 18.32 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/e3diff/fetch.sh
|
| 31 |
+
# vision-aided-loss (CLIP discriminator, built by the model): see baselines/e3diff/README.md
|
| 32 |
+
python baselines/data/diff_data.py e3diff spacenet6
|
| 33 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/e3diff/*" --local-dir hf
|
| 34 |
+
mkdir -p $CKPT_ROOT/e3diff/spacenet6 && cp hf/baselines/spacenet6/e3diff/gen.pth $CKPT_ROOT/e3diff/spacenet6/
|
| 35 |
+
bash baselines/e3diff/test.sh spacenet6 # writes $WORK_DIR/results/e3diff/spacenet6/<stem>.png
|
| 36 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/e3diff/spacenet6
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
## Licence
|
| 40 |
+
|
| 41 |
+
The E3Diff repository contains no licence file (its bundled `SoftPool/` directory is MIT). These weights were trained by the GeoSET authors with that code; the GeoSET repository distributes only its changes to the code.
|
baselines/spacenet6/e3diff/gen.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:53786dd3255871070effc0353ffefffa3339ab3ebacccbde3f9d5ed04fef542c
|
| 3 |
+
size 768427299
|
baselines/spacenet6/hi-diff/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HI-Diff · SpaceNet6
|
| 2 |
+
|
| 3 |
+
HI-Diff (NeurIPS'23) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the HI-Diff row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Chen, Zhang, Liu, Gu, Kong, Yuan et al., "Hierarchical integration diffusion model for realistic image deblurring", NeurIPS 2023.
|
| 8 |
+
- Implementation: [zhengchen1999/HI-Diff](https://github.com/zhengchen1999/HI-Diff) at commit `b3bfd167997e27f8edd57681cf70e5031a0e35f2`.
|
| 9 |
+
- Training and inference code: [`baselines/hi-diff`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/hi-diff) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `S1_net_g_latest.pth` | 101,401,457 | `7e5b2546e43ed2ab3b6543733ffc7bec2ec74fbdc43f150a134527ac4c8aef71` | Stage-1 Transformer restorer (iteration 25,000) |
|
| 16 |
+
| `S1_net_le_latest.pth` | 2,357,509 | `b2ea2d2c8df523aac1220377dc05bf62655963842c4098fa13adc6eac0205993` | Stage-1 latent encoder, SAR + EO input (iteration 25,000) |
|
| 17 |
+
| `S2_net_g_latest.pth` | 101,401,457 | `8de9e57dc6e2a8c6c7299e846d0021eebcb1090ce162e023c8e804aa6f42b0c0` | Stage-2 Transformer restorer (iteration 25,000); used by test.sh |
|
| 18 |
+
| `S2_net_le_dm_latest.pth` | 2,247,061 | `b8ed232b742fcbb4eb836b1185b9947d65781a87afa9a627b412553ea028773a` | Stage-2 latent encoder, SAR input (iteration 25,000); used by test.sh |
|
| 19 |
+
| `S2_net_d_latest.pth` | 10,530,491 | `6fb28955d58f72a71f9d4e7da7049d12430749340dd7d6fb7847cdcdea4e6878` | Stage-2 latent denoising network (iteration 25,000); used by test.sh |
|
| 20 |
+
|
| 21 |
+
## Results (GeoSET paper, Table 3)
|
| 22 |
+
|
| 23 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 24 |
+
|
| 25 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 26 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 27 |
+
| 261.2 | 0.300 | 0.2036 | 0.499 | 0.403 | 0.589 | 20.61 |
|
| 28 |
+
|
| 29 |
+
## Inference
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 33 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 34 |
+
bash baselines/hi-diff/fetch.sh
|
| 35 |
+
python baselines/data/diff_data.py paired spacenet6
|
| 36 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/hi-diff/*" --local-dir hf
|
| 37 |
+
mkdir -p $CKPT_ROOT/hi-diff/spacenet6 && cp hf/baselines/spacenet6/hi-diff/*.pth $CKPT_ROOT/hi-diff/spacenet6/
|
| 38 |
+
bash baselines/hi-diff/test.sh spacenet6 # writes $WORK_DIR/results/hi-diff/spacenet6/<stem>.png
|
| 39 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/hi-diff/spacenet6
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
## Licence
|
| 43 |
+
|
| 44 |
+
These weights were trained with the HI-Diff code and are released under its [Apache-2.0](https://github.com/zhengchen1999/HI-Diff/blob/b3bfd167997e27f8edd57681cf70e5031a0e35f2/LICENSE) licence; the GeoSET repository states its modifications to the code (`baselines/hi-diff`).
|
baselines/spacenet6/hi-diff/S1_net_g_latest.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7e5b2546e43ed2ab3b6543733ffc7bec2ec74fbdc43f150a134527ac4c8aef71
|
| 3 |
+
size 101401457
|
baselines/spacenet6/hi-diff/S1_net_le_latest.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b2ea2d2c8df523aac1220377dc05bf62655963842c4098fa13adc6eac0205993
|
| 3 |
+
size 2357509
|
baselines/spacenet6/hi-diff/S2_net_d_latest.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6fb28955d58f72a71f9d4e7da7049d12430749340dd7d6fb7847cdcdea4e6878
|
| 3 |
+
size 10530491
|
baselines/spacenet6/hi-diff/S2_net_g_latest.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8de9e57dc6e2a8c6c7299e846d0021eebcb1090ce162e023c8e804aa6f42b0c0
|
| 3 |
+
size 101401457
|
baselines/spacenet6/hi-diff/S2_net_le_dm_latest.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b8ed232b742fcbb4eb836b1185b9947d65781a87afa9a627b412553ea028773a
|
| 3 |
+
size 2247061
|
baselines/spacenet6/pix2pix/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pix2pix · SpaceNet6
|
| 2 |
+
|
| 3 |
+
pix2pix (CVPR'17) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the pix2pix row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Isola, Zhu, Zhou and Efros, "Image-to-image translation with conditional adversarial networks", CVPR 2017.
|
| 8 |
+
- Implementation: [junyanz/pytorch-CycleGAN-and-pix2pix](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix) at commit `2a7afba2895d52556dd5dfe07e8555ef657ced6f`.
|
| 9 |
+
- Training and inference code: [`baselines/pix2pix`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/pix2pix) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `net_G.pth` | 217,728,293 | `a9a8c502b453643b26f5718ece86d27a7af503adce5d0e85cb694adbd4aeaf26` | U-Net generator (`unet_256`), SAR → EO; used by test.sh |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 166.7 | 0.242 | 0.1091 | 0.589 | 0.419 | 0.459 | 17.07 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/pix2pix/fetch.sh
|
| 31 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 32 |
+
python baselines/data/gan_prepare.py spacenet6
|
| 33 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/pix2pix/*.pth" --local-dir hf
|
| 34 |
+
mkdir -p $CKPT_ROOT/pix2pix/spacenet6 && cp hf/baselines/spacenet6/pix2pix/*.pth $CKPT_ROOT/pix2pix/spacenet6/
|
| 35 |
+
bash baselines/pix2pix/test.sh spacenet6 # writes $WORK_DIR/results/pix2pix/spacenet6/<stem>.png
|
| 36 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/pix2pix/spacenet6
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
## Licence
|
| 40 |
+
|
| 41 |
+
These weights were trained with the pytorch-CycleGAN-and-pix2pix code and are released under its BSD licence (Copyright (c) 2017 Jun-Yan Zhu and Taesung Park; Copyright (c) 2016 Phillip Isola and Jun-Yan Zhu; [LICENSE](https://github.com/junyanz/pytorch-CycleGAN-and-pix2pix/blob/2a7afba2895d52556dd5dfe07e8555ef657ced6f/LICENSE)). The GeoSET repository states its change to the code (`baselines/pix2pix`).
|
baselines/spacenet6/pix2pix/net_G.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a9a8c502b453643b26f5718ece86d27a7af503adce5d0e85cb694adbd4aeaf26
|
| 3 |
+
size 217728293
|
baselines/spacenet6/pix2pixhd/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pix2pixHD · SpaceNet6
|
| 2 |
+
|
| 3 |
+
pix2pixHD (CVPR'18) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the pix2pixHD row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Wang, Liu, Zhu, Tao, Kautz and Catanzaro, "High-resolution image synthesis and semantic manipulation with conditional GANs", CVPR 2018.
|
| 8 |
+
- Implementation: [NVIDIA/pix2pixHD](https://github.com/NVIDIA/pix2pixHD) at commit `14b3b3c7fff413086e3b58df52096f16b6891172`.
|
| 9 |
+
- Training and inference code: [`baselines/pix2pixhd`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/pix2pixhd) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `net_G.pth` | 729,795,505 | `1f32e16f7468b5fe285645926b75e6730bbb5068cd2d7f3e5b840a0931df5eca` | Global generator (4 downsampling layers, 9 residual blocks), SAR → EO; used by test.sh |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 175.1 | 0.246 | 0.1136 | 0.682 | 0.361 | 0.525 | 19.21 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/pix2pixhd/fetch.sh
|
| 31 |
+
bash baselines/pix2pix/fetch.sh # combine_A_and_B.py, used by gan_prepare.py
|
| 32 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 33 |
+
python baselines/data/gan_prepare.py spacenet6
|
| 34 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/pix2pixhd/*.pth" --local-dir hf
|
| 35 |
+
mkdir -p $CKPT_ROOT/pix2pixhd/spacenet6 && cp hf/baselines/spacenet6/pix2pixhd/*.pth $CKPT_ROOT/pix2pixhd/spacenet6/
|
| 36 |
+
bash baselines/pix2pixhd/test.sh spacenet6 # writes $WORK_DIR/results/pix2pixhd/spacenet6/<stem>.png
|
| 37 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/pix2pixhd/spacenet6
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Licence
|
| 41 |
+
|
| 42 |
+
These weights were trained with the pix2pixHD code and are released under its BSD licence (Copyright (C) 2019 NVIDIA Corporation, Ting-Chun Wang, Ming-Yu Liu, Jun-Yan Zhu; [LICENSE.txt](https://github.com/NVIDIA/pix2pixHD/blob/14b3b3c7fff413086e3b58df52096f16b6891172/LICENSE.txt)). The GeoSET repository states its changes to the code (`baselines/pix2pixhd`).
|
baselines/spacenet6/pix2pixhd/net_G.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1f32e16f7468b5fe285645926b75e6730bbb5068cd2d7f3e5b840a0931df5eca
|
| 3 |
+
size 729795505
|
baselines/spacenet6/resshift/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ResShift · SpaceNet6
|
| 2 |
+
|
| 3 |
+
ResShift (NeurIPS'23) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the ResShift row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Yue, Wang and Loy, "ResShift: Efficient diffusion model for image super-resolution by residual shifting", NeurIPS 2023.
|
| 8 |
+
- Implementation: [zsyOAOA/ResShift](https://github.com/zsyOAOA/ResShift) at commit `bb03b7d21614cace01787e097c8a6ab6b945227d`.
|
| 9 |
+
- Training and inference code: [`baselines/resshift`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/resshift) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `ema_model.pth` | 478,392,663 | `a5c3d1b910970efd770bee33e76d6ab0e308df9d721a2c1f2174cd89e56e904d` | UNetModelSwin EMA (0.999) weights at iteration 50,000; used with the ResShift VQ-f4 autoencoder, which `baselines/resshift/fetch.sh` downloads |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 155.4 | 0.230 | 0.0858 | 0.540 | 0.367 | 0.421 | 18.58 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/resshift/fetch.sh
|
| 31 |
+
python baselines/data/diff_data.py paired spacenet6
|
| 32 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/resshift/*" --local-dir hf
|
| 33 |
+
mkdir -p $CKPT_ROOT/resshift/spacenet6 && cp hf/baselines/spacenet6/resshift/ema_model.pth $CKPT_ROOT/resshift/spacenet6/
|
| 34 |
+
bash baselines/resshift/test.sh spacenet6 # writes $WORK_DIR/results/resshift/spacenet6/<stem>.png
|
| 35 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/resshift/spacenet6
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## Licence
|
| 39 |
+
|
| 40 |
+
These weights were trained with the ResShift code and are released under its [S-Lab License 1.0](https://github.com/zsyOAOA/ResShift/blob/bb03b7d21614cace01787e097c8a6ab6b945227d/LICENSE) (Copyright 2022 S-Lab): non-commercial use only. The VQ-f4 autoencoder used at inference is part of the ResShift v2.0 release and is not included here.
|
baselines/spacenet6/resshift/ema_model.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a5c3d1b910970efd770bee33e76d6ab0e308df9d721a2c1f2174cd89e56e904d
|
| 3 |
+
size 478392663
|
baselines/spacenet6/sd21-ft/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SD2.1 FT · SpaceNet6
|
| 2 |
+
|
| 3 |
+
SD2.1 FT (CVPR'22) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the SD2.1 FT row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Rombach, Blattmann, Lorenz, Esser and Ommer, "High-resolution image synthesis with latent diffusion models", CVPR 2022.
|
| 8 |
+
- Implementation: Stable Diffusion 2.1-base (`Manojb/stable-diffusion-2-1-base`, revision `0094d483a120f3f33dafbd187ea4aa60d10de75c`), fine-tuned with the stage-1 scripts that `baselines/sd21-ft/sd21-ft.patch` adds to [KAIST-VICLab/C-DiffSET](https://github.com/KAIST-VICLab/C-DiffSET) at commit `6fc380215ae97c9d134e8f1434061b652339b45c`.
|
| 9 |
+
- Training and inference code: [`baselines/sd21-ft`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/sd21-ft) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `diffusion_pytorch_model.safetensors` | 3,463,772,592 | `93c99b5bf5cefb5df57a403f9691b3b50a138d6a41b1d4726f64035ddf8c5fd4` | UNet weights (diffusers `UNet2DConditionModel`, fp32); input = SAR latent and noisy EO latent (8 channels), output = noise (4 channels) |
|
| 16 |
+
| `config.json` | 911 | `4ea373ed4a09313343a02c2ebb22a839cca967e7e97a9dd281f68b7d0fbee7c3` | UNet configuration |
|
| 17 |
+
|
| 18 |
+
## Results (GeoSET paper, Table 3)
|
| 19 |
+
|
| 20 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 21 |
+
|
| 22 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 23 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 24 |
+
| 124.7 | 0.196 | 0.0537 | 0.612 | 0.348 | 0.513 | 19.29 |
|
| 25 |
+
|
| 26 |
+
## Inference
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 30 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 31 |
+
bash baselines/sd21-ft/fetch.sh
|
| 32 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 33 |
+
python baselines/data/ldm_prepare.py spacenet6
|
| 34 |
+
hf download JeonghyeokDo/GeoSET "baselines/spacenet6/sd21-ft/diffusion_pytorch_model.safetensors" "baselines/spacenet6/sd21-ft/config.json" --local-dir hf
|
| 35 |
+
mkdir -p $CKPT_ROOT/sd21-ft/spacenet6 && cp hf/baselines/spacenet6/sd21-ft/* $CKPT_ROOT/sd21-ft/spacenet6/
|
| 36 |
+
bash baselines/sd21-ft/test.sh spacenet6 # writes $WORK_DIR/results/sd21-ft/spacenet6/<stem>.png
|
| 37 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/sd21-ft/spacenet6
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Licence
|
| 41 |
+
|
| 42 |
+
These weights are a derivative of Stable Diffusion 2.1-base and are released under the CreativeML Open RAIL++-M licence ([text](https://huggingface.co/stabilityai/stable-diffusion-2/blob/main/LICENSE-MODEL)); the use-based restrictions of its Attachment A apply to any use and redistribution. The training code is MIT (C-DiffSET, Copyright (c) 2026 KAIST VICLab).
|
baselines/spacenet6/sd21-ft/config.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "UNet2DConditionModel",
|
| 3 |
+
"_diffusers_version": "0.10.0.dev0",
|
| 4 |
+
"act_fn": "silu",
|
| 5 |
+
"attention_head_dim": [
|
| 6 |
+
5,
|
| 7 |
+
10,
|
| 8 |
+
20,
|
| 9 |
+
20
|
| 10 |
+
],
|
| 11 |
+
"block_out_channels": [
|
| 12 |
+
320,
|
| 13 |
+
640,
|
| 14 |
+
1280,
|
| 15 |
+
1280
|
| 16 |
+
],
|
| 17 |
+
"center_input_sample": false,
|
| 18 |
+
"cross_attention_dim": 1024,
|
| 19 |
+
"down_block_types": [
|
| 20 |
+
"CrossAttnDownBlock2D",
|
| 21 |
+
"CrossAttnDownBlock2D",
|
| 22 |
+
"CrossAttnDownBlock2D",
|
| 23 |
+
"DownBlock2D"
|
| 24 |
+
],
|
| 25 |
+
"downsample_padding": 1,
|
| 26 |
+
"dual_cross_attention": false,
|
| 27 |
+
"flip_sin_to_cos": true,
|
| 28 |
+
"freq_shift": 0,
|
| 29 |
+
"in_channels": 8,
|
| 30 |
+
"layers_per_block": 2,
|
| 31 |
+
"mid_block_scale_factor": 1,
|
| 32 |
+
"norm_eps": 1e-05,
|
| 33 |
+
"norm_num_groups": 32,
|
| 34 |
+
"num_class_embeds": null,
|
| 35 |
+
"only_cross_attention": false,
|
| 36 |
+
"out_channels": 4,
|
| 37 |
+
"sample_size": 64,
|
| 38 |
+
"up_block_types": [
|
| 39 |
+
"UpBlock2D",
|
| 40 |
+
"CrossAttnUpBlock2D",
|
| 41 |
+
"CrossAttnUpBlock2D",
|
| 42 |
+
"CrossAttnUpBlock2D"
|
| 43 |
+
],
|
| 44 |
+
"use_linear_projection": true
|
| 45 |
+
}
|
baselines/spacenet6/sd21-ft/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:93c99b5bf5cefb5df57a403f9691b3b50a138d6a41b1d4726f64035ddf8c5fd4
|
| 3 |
+
size 3463772592
|
baselines/spacenet6/seg-cyclegan/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Seg-CycleGAN · SpaceNet6
|
| 2 |
+
|
| 3 |
+
Seg-CycleGAN (GRSL'25) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the Seg-CycleGAN row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Zhang, Li, Lin, Zhang, Fan, Liu and Liu, "Seg-CycleGAN: SAR-to-optical image translation guided by a downstream task", IEEE GRSL 2025.
|
| 8 |
+
- Implementation: [NWPU-LHH/Seg-CycleGAN](https://github.com/NWPU-LHH/Seg-CycleGAN) at commit `f12ff88ac089b7099443ffa2c34fc47e035def6a`.
|
| 9 |
+
- Training and inference code: [`baselines/seg-cyclegan`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/seg-cyclegan) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `net_G_A.pth` | 45,532,493 | `a3cfe869a0d67bc298d52527b95eec952fbe301959d43c7d971a74cbca3c5b9f` | Generator G_A (`resnet_9blocks`), SAR → EO; produces the output image |
|
| 16 |
+
| `net_G_B.pth` | 45,532,493 | `592a9e9770f78bd9df29a14e01344a5a4bf29f1121e2a6c3ad7252d79ac5824f` | Generator G_B (`resnet_9blocks`), EO → SAR; loaded by the upstream test script |
|
| 17 |
+
| `segnet_guide.pth` | 117,899,184 | `f48ed0a584d9dde5a6a4b8cde3eace2104dbb6006d378ea26c2b460ceded1934` | Guide SegNet (VGG16-BN encoder; background / building) trained by the GeoSET authors with `baselines/seg-cyclegan/train_segnet.py` on the SpaceNet6 training tiles; used by train.sh only |
|
| 18 |
+
|
| 19 |
+
## Results (GeoSET paper, Table 3)
|
| 20 |
+
|
| 21 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 22 |
+
|
| 23 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 24 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 25 |
+
| 131.4 | 0.229 | 0.0611 | 0.649 | 0.386 | 0.471 | 16.62 |
|
| 26 |
+
|
| 27 |
+
## Inference
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 31 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 32 |
+
bash baselines/seg-cyclegan/fetch.sh
|
| 33 |
+
bash baselines/pix2pix/fetch.sh # combine_A_and_B.py, used by gan_prepare.py
|
| 34 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 35 |
+
python baselines/data/gan_prepare.py spacenet6
|
| 36 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/seg-cyclegan/*.pth" --local-dir hf
|
| 37 |
+
mkdir -p $CKPT_ROOT/seg-cyclegan/spacenet6 && cp hf/baselines/spacenet6/seg-cyclegan/*.pth $CKPT_ROOT/seg-cyclegan/spacenet6/
|
| 38 |
+
bash baselines/seg-cyclegan/test.sh spacenet6 # writes $WORK_DIR/results/seg-cyclegan/spacenet6/<stem>.png
|
| 39 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/seg-cyclegan/spacenet6
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
## Licence
|
| 43 |
+
|
| 44 |
+
The Seg-CycleGAN repository contains no licence file. These weights were trained by the GeoSET authors with that code; the GeoSET repository distributes only its changes to the code. The CycleGAN components of that code derive from junyanz/pytorch-CycleGAN-and-pix2pix (BSD).
|
baselines/spacenet6/seg-cyclegan/net_G_A.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a3cfe869a0d67bc298d52527b95eec952fbe301959d43c7d971a74cbca3c5b9f
|
| 3 |
+
size 45532493
|
baselines/spacenet6/seg-cyclegan/net_G_B.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:592a9e9770f78bd9df29a14e01344a5a4bf29f1121e2a6c3ad7252d79ac5824f
|
| 3 |
+
size 45532493
|
baselines/spacenet6/seg-cyclegan/segnet_guide.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f48ed0a584d9dde5a6a4b8cde3eace2104dbb6006d378ea26c2b460ceded1934
|
| 3 |
+
size 117899184
|
baselines/spacenet6/spade/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPADE · SpaceNet6
|
| 2 |
+
|
| 3 |
+
SPADE (CVPR'19) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the SPADE row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Park, Liu, Wang and Zhu, "Semantic image synthesis with spatially-adaptive normalization", CVPR 2019.
|
| 8 |
+
- Implementation: [NVlabs/SPADE](https://github.com/NVlabs/SPADE) at commit `fecacc920c1367a038995c45a39c15f6521ca64f`.
|
| 9 |
+
- Training and inference code: [`baselines/spade`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/spade) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `net_G.pth` | 368,775,707 | `b6714fb30bd34f62c8495af92bc225b96d9363cb8edc2839df3918784f2a4e6f` | SPADE generator with the SAR image as its 3-channel input map, SAR → EO; loads only into the code patched by `baselines/spade/spade.patch`; used by test.sh |
|
| 16 |
+
|
| 17 |
+
## Results (GeoSET paper, Table 3)
|
| 18 |
+
|
| 19 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 20 |
+
|
| 21 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 22 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 23 |
+
| 179.7 | 0.242 | 0.1228 | 0.662 | 0.385 | 0.489 | 17.77 |
|
| 24 |
+
|
| 25 |
+
## Inference
|
| 26 |
+
|
| 27 |
+
```bash
|
| 28 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 29 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 30 |
+
bash baselines/spade/fetch.sh
|
| 31 |
+
bash baselines/pix2pix/fetch.sh # combine_A_and_B.py, used by gan_prepare.py
|
| 32 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 33 |
+
python baselines/data/gan_prepare.py spacenet6
|
| 34 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/spade/*.pth" --local-dir hf
|
| 35 |
+
mkdir -p $CKPT_ROOT/spade/spacenet6 && cp hf/baselines/spacenet6/spade/*.pth $CKPT_ROOT/spade/spacenet6/
|
| 36 |
+
bash baselines/spade/test.sh spacenet6 # writes $WORK_DIR/results/spade/spacenet6/<stem>.png
|
| 37 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/spade/spacenet6
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
## Licence
|
| 41 |
+
|
| 42 |
+
These weights were trained with the SPADE code (Copyright (C) 2019 NVIDIA Corporation) and are released under its licence, [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode) ([LICENSE.md](https://github.com/NVlabs/SPADE/blob/fecacc920c1367a038995c45a39c15f6521ca64f/LICENSE.md)): non-commercial use only, and adaptations must be shared under the same licence. The GeoSET repository states its changes to the code (`baselines/spade`, same licence).
|
baselines/spacenet6/spade/net_G.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b6714fb30bd34f62c8495af92bc225b96d9363cb8edc2839df3918784f2a4e6f
|
| 3 |
+
size 368775707
|
baselines/spacenet6/stegogan/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# StegoGAN · SpaceNet6
|
| 2 |
+
|
| 3 |
+
StegoGAN (CVPR'24) retrained by the GeoSET authors on the SpaceNet6 split. These are the weights behind
|
| 4 |
+
the StegoGAN row for SpaceNet6 in Table 3 of the GeoSET paper
|
| 5 |
+
([arXiv:2609.37496](https://arxiv.org/abs/2609.37496)).
|
| 6 |
+
|
| 7 |
+
- Method: Wu, Chen, Mermet, Hurni, Schindler, Gonthier and Landrieu, "StegoGAN: Leveraging steganography for non-bijective image-to-image translation", CVPR 2024.
|
| 8 |
+
- Implementation: [sian-wusidi/StegoGAN](https://github.com/sian-wusidi/StegoGAN) at commit `cad61997c0f82793444f60f81298142b80cdf3c1`.
|
| 9 |
+
- Training and inference code: [`baselines/stegogan`](https://github.com/KAIST-VICLab/GeoSET/tree/main/baselines/stegogan) in the GeoSET repository.
|
| 10 |
+
|
| 11 |
+
## Files
|
| 12 |
+
|
| 13 |
+
| File | Bytes | SHA-256 | Role |
|
| 14 |
+
|---|---:|---|---|
|
| 15 |
+
| `net_G_A.pth` | 50,254,749 | `27aad6ddc483d83eba63c500080cf7f6b4a02205defbf468c7dd222ab4c445fa` | Generator G_A (`resnet_9blocks_maskv1`), SAR → EO; the output image is G_A applied to the SAR image alone (`fake_B_clean`) |
|
| 16 |
+
| `net_G_B.pth` | 52,615,525 | `425e3a0a0a08a0cd30d257f8085b8c33e68efb26a174f3443740e3f8d74bc6dc` | Generator G_B (`resnet_9blocks_maskv3`), EO → SAR; loaded by the upstream test script |
|
| 17 |
+
|
| 18 |
+
## Results (GeoSET paper, Table 3)
|
| 19 |
+
|
| 20 |
+
SpaceNet6 test split, 495 pairs, 256 × 256.
|
| 21 |
+
|
| 22 |
+
| FID↓ | DISTS↓ | KID↓ | DINO↑ | LPIPS↓ | SSIM↑ | PSNR↑ |
|
| 23 |
+
|---:|---:|---:|---:|---:|---:|---:|
|
| 24 |
+
| 113.1 | 0.225 | 0.0361 | 0.649 | 0.388 | 0.457 | 16.04 |
|
| 25 |
+
|
| 26 |
+
## Inference
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
git clone https://github.com/KAIST-VICLab/GeoSET && cd GeoSET
|
| 30 |
+
export BASELINES_ROOT=/path/to/upstream_code DATA_ROOT=/path/to/data WORK_DIR=/path/to/work CKPT_ROOT=/path/to/checkpoints
|
| 31 |
+
bash baselines/stegogan/fetch.sh
|
| 32 |
+
bash baselines/pix2pix/fetch.sh # combine_A_and_B.py, used by gan_prepare.py
|
| 33 |
+
python scripts/datasets/build_spacenet6.py --data-root $DATA_ROOT
|
| 34 |
+
python baselines/data/gan_prepare.py spacenet6
|
| 35 |
+
hf download JeonghyeokDo/GeoSET --include "baselines/spacenet6/stegogan/*.pth" --local-dir hf
|
| 36 |
+
mkdir -p $CKPT_ROOT/stegogan/spacenet6 && cp hf/baselines/spacenet6/stegogan/*.pth $CKPT_ROOT/stegogan/spacenet6/
|
| 37 |
+
bash baselines/stegogan/test.sh spacenet6 # writes $WORK_DIR/results/stegogan/spacenet6/<stem>.png
|
| 38 |
+
python scripts/evaluate.py --dataset spacenet6 --pred-dir $WORK_DIR/results/stegogan/spacenet6
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
## Licence
|
| 42 |
+
|
| 43 |
+
The StegoGAN repository contains no licence file. These weights were trained by the GeoSET authors with that code; the GeoSET repository distributes only scripts that run the unmodified code.
|
baselines/spacenet6/stegogan/net_G_A.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:27aad6ddc483d83eba63c500080cf7f6b4a02205defbf468c7dd222ab4c445fa
|
| 3 |
+
size 50254749
|
baselines/spacenet6/stegogan/net_G_B.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:425e3a0a0a08a0cd30d257f8085b8c33e68efb26a174f3443740e3f8d74bc6dc
|
| 3 |
+
size 52615525
|
generalist/autoencoder_flux2.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Black Forest Labs.
|
| 2 |
+
# Copyright (c) 2026 The GeoSET Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
#
|
| 16 |
+
# Adapted from black-forest-labs/flux2 (Apache-2.0). Modified by the GeoSET authors.
|
| 17 |
+
"""Frozen FLUX.2 autoencoder with the packed (2x2 space-to-depth), BatchNorm-normalised 128-channel latent."""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 23 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 24 |
+
from einops import rearrange
|
| 25 |
+
from torch import Tensor, nn
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def swish(x: Tensor) -> Tensor:
|
| 29 |
+
return x * torch.sigmoid(x)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class AttnBlock(nn.Module):
|
| 33 |
+
def __init__(self, in_channels: int):
|
| 34 |
+
super().__init__()
|
| 35 |
+
self.norm = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 36 |
+
self.q = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 37 |
+
self.k = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 38 |
+
self.v = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 39 |
+
self.proj_out = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 40 |
+
|
| 41 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 42 |
+
h_ = self.norm(x)
|
| 43 |
+
q, k, v = self.q(h_), self.k(h_), self.v(h_)
|
| 44 |
+
b, c, h, w = q.shape
|
| 45 |
+
q, k, v = (rearrange(t, "b c h w -> b 1 (h w) c").contiguous() for t in (q, k, v))
|
| 46 |
+
h_ = nn.functional.scaled_dot_product_attention(q, k, v)
|
| 47 |
+
return x + self.proj_out(rearrange(h_, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class ResnetBlock(nn.Module):
|
| 51 |
+
def __init__(self, in_channels: int, out_channels: int):
|
| 52 |
+
super().__init__()
|
| 53 |
+
self.in_channels = in_channels
|
| 54 |
+
self.out_channels = out_channels
|
| 55 |
+
self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 56 |
+
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 57 |
+
self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
|
| 58 |
+
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 59 |
+
if in_channels != out_channels:
|
| 60 |
+
self.nin_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
| 61 |
+
|
| 62 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 63 |
+
h = self.conv1(swish(self.norm1(x)))
|
| 64 |
+
h = self.conv2(swish(self.norm2(h)))
|
| 65 |
+
if self.in_channels != self.out_channels:
|
| 66 |
+
x = self.nin_shortcut(x)
|
| 67 |
+
return x + h
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class Downsample(nn.Module):
|
| 71 |
+
def __init__(self, in_channels: int):
|
| 72 |
+
super().__init__()
|
| 73 |
+
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0)
|
| 74 |
+
|
| 75 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 76 |
+
return self.conv(nn.functional.pad(x, (0, 1, 0, 1), mode="constant", value=0))
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class Upsample(nn.Module):
|
| 80 |
+
def __init__(self, in_channels: int):
|
| 81 |
+
super().__init__()
|
| 82 |
+
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
|
| 83 |
+
|
| 84 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 85 |
+
return self.conv(nn.functional.interpolate(x, scale_factor=2.0, mode="nearest"))
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class Encoder(nn.Module):
|
| 89 |
+
"""Image -> latent moments (mean and log-variance, 2 * z_channels at 1/8 resolution)."""
|
| 90 |
+
|
| 91 |
+
def __init__(self, in_channels: int, ch: int, ch_mult: list[int], num_res_blocks: int, z_channels: int):
|
| 92 |
+
super().__init__()
|
| 93 |
+
self.quant_conv = nn.Conv2d(2 * z_channels, 2 * z_channels, 1)
|
| 94 |
+
self.num_resolutions = len(ch_mult)
|
| 95 |
+
self.num_res_blocks = num_res_blocks
|
| 96 |
+
self.conv_in = nn.Conv2d(in_channels, ch, kernel_size=3, stride=1, padding=1)
|
| 97 |
+
in_ch_mult = (1,) + tuple(ch_mult)
|
| 98 |
+
self.down = nn.ModuleList()
|
| 99 |
+
for i_level in range(self.num_resolutions):
|
| 100 |
+
block = nn.ModuleList()
|
| 101 |
+
block_in = ch * in_ch_mult[i_level]
|
| 102 |
+
block_out = ch * ch_mult[i_level]
|
| 103 |
+
for _ in range(num_res_blocks):
|
| 104 |
+
block.append(ResnetBlock(block_in, block_out))
|
| 105 |
+
block_in = block_out
|
| 106 |
+
down = nn.Module()
|
| 107 |
+
down.block = block
|
| 108 |
+
if i_level != self.num_resolutions - 1:
|
| 109 |
+
down.downsample = Downsample(block_in)
|
| 110 |
+
self.down.append(down)
|
| 111 |
+
self.mid = nn.Module()
|
| 112 |
+
self.mid.block_1 = ResnetBlock(block_in, block_in)
|
| 113 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 114 |
+
self.mid.block_2 = ResnetBlock(block_in, block_in)
|
| 115 |
+
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
|
| 116 |
+
self.conv_out = nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1)
|
| 117 |
+
|
| 118 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 119 |
+
h = self.conv_in(x)
|
| 120 |
+
for i_level in range(self.num_resolutions):
|
| 121 |
+
for i_block in range(self.num_res_blocks):
|
| 122 |
+
h = self.down[i_level].block[i_block](h)
|
| 123 |
+
if i_level != self.num_resolutions - 1:
|
| 124 |
+
h = self.down[i_level].downsample(h)
|
| 125 |
+
h = self.mid.block_2(self.mid.attn_1(self.mid.block_1(h)))
|
| 126 |
+
return self.quant_conv(self.conv_out(swish(self.norm_out(h))))
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class Decoder(nn.Module):
|
| 130 |
+
"""Latent (z_channels at 1/8 resolution) -> image."""
|
| 131 |
+
|
| 132 |
+
def __init__(self, ch: int, out_ch: int, ch_mult: list[int], num_res_blocks: int, z_channels: int):
|
| 133 |
+
super().__init__()
|
| 134 |
+
self.post_quant_conv = nn.Conv2d(z_channels, z_channels, 1)
|
| 135 |
+
self.num_resolutions = len(ch_mult)
|
| 136 |
+
self.num_res_blocks = num_res_blocks
|
| 137 |
+
block_in = ch * ch_mult[-1]
|
| 138 |
+
self.conv_in = nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1)
|
| 139 |
+
self.mid = nn.Module()
|
| 140 |
+
self.mid.block_1 = ResnetBlock(block_in, block_in)
|
| 141 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 142 |
+
self.mid.block_2 = ResnetBlock(block_in, block_in)
|
| 143 |
+
self.up = nn.ModuleList()
|
| 144 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 145 |
+
block = nn.ModuleList()
|
| 146 |
+
block_out = ch * ch_mult[i_level]
|
| 147 |
+
for _ in range(num_res_blocks + 1):
|
| 148 |
+
block.append(ResnetBlock(block_in, block_out))
|
| 149 |
+
block_in = block_out
|
| 150 |
+
up = nn.Module()
|
| 151 |
+
up.block = block
|
| 152 |
+
if i_level != 0:
|
| 153 |
+
up.upsample = Upsample(block_in)
|
| 154 |
+
self.up.insert(0, up)
|
| 155 |
+
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
|
| 156 |
+
self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1)
|
| 157 |
+
|
| 158 |
+
def trunk(self, z: Tensor) -> Tensor:
|
| 159 |
+
"""Every layer except ``conv_out``: returns the ``ch``-channel features at full resolution."""
|
| 160 |
+
h = self.conv_in(self.post_quant_conv(z))
|
| 161 |
+
h = self.mid.block_2(self.mid.attn_1(self.mid.block_1(h)))
|
| 162 |
+
h = h.to(next(self.up.parameters()).dtype)
|
| 163 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 164 |
+
for i_block in range(self.num_res_blocks + 1):
|
| 165 |
+
h = self.up[i_level].block[i_block](h)
|
| 166 |
+
if i_level != 0:
|
| 167 |
+
h = self.up[i_level].upsample(h)
|
| 168 |
+
return swish(self.norm_out(h))
|
| 169 |
+
|
| 170 |
+
def forward(self, z: Tensor) -> Tensor:
|
| 171 |
+
return self.conv_out(self.trunk(z))
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
class AutoencoderFlux2(ModelMixin, ConfigMixin):
|
| 175 |
+
"""FLUX.2 autoencoder: ``encode`` maps ``[B, 3, H, W]`` in [-1, 1] to ``[B, 128, H/16, W/16]``, ``decode`` inverts it.
|
| 176 |
+
|
| 177 |
+
The latent is the posterior mean, packed 2x2 space-to-depth and normalised by a BatchNorm with fixed running
|
| 178 |
+
statistics. The module is frozen and always in eval mode.
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
@register_to_config
|
| 182 |
+
def __init__(
|
| 183 |
+
self,
|
| 184 |
+
in_channels: int = 3,
|
| 185 |
+
out_ch: int = 3,
|
| 186 |
+
ch: int = 128,
|
| 187 |
+
ch_mult: tuple[int, ...] = (1, 2, 4, 4),
|
| 188 |
+
num_res_blocks: int = 2,
|
| 189 |
+
z_channels: int = 32,
|
| 190 |
+
):
|
| 191 |
+
super().__init__()
|
| 192 |
+
self.encoder = Encoder(in_channels, ch, list(ch_mult), num_res_blocks, z_channels)
|
| 193 |
+
self.decoder = Decoder(ch, out_ch, list(ch_mult), num_res_blocks, z_channels)
|
| 194 |
+
self.bn = nn.BatchNorm2d(4 * z_channels, eps=1e-4, momentum=0.1, affine=False, track_running_stats=True)
|
| 195 |
+
self.requires_grad_(False)
|
| 196 |
+
self.eval()
|
| 197 |
+
|
| 198 |
+
@staticmethod
|
| 199 |
+
def pack(z: Tensor) -> Tensor:
|
| 200 |
+
return rearrange(z, "... c (i pi) (j pj) -> ... (c pi pj) i j", pi=2, pj=2)
|
| 201 |
+
|
| 202 |
+
@staticmethod
|
| 203 |
+
def unpack(z: Tensor) -> Tensor:
|
| 204 |
+
return rearrange(z, "... (c pi pj) i j -> ... c (i pi) (j pj)", pi=2, pj=2)
|
| 205 |
+
|
| 206 |
+
def normalize(self, z: Tensor) -> Tensor:
|
| 207 |
+
self.bn.eval()
|
| 208 |
+
return self.bn(z)
|
| 209 |
+
|
| 210 |
+
def inv_normalize(self, z: Tensor) -> Tensor:
|
| 211 |
+
self.bn.eval()
|
| 212 |
+
s = torch.sqrt(self.bn.running_var.view(1, -1, 1, 1) + self.bn.eps)
|
| 213 |
+
m = self.bn.running_mean.view(1, -1, 1, 1)
|
| 214 |
+
return z * s + m
|
| 215 |
+
|
| 216 |
+
def encode(self, x: Tensor) -> Tensor:
|
| 217 |
+
moments = self.encoder(x)
|
| 218 |
+
return self.normalize(self.pack(torch.chunk(moments, 2, dim=1)[0]))
|
| 219 |
+
|
| 220 |
+
def decode(self, z: Tensor) -> Tensor:
|
| 221 |
+
return self.decoder(self.unpack(self.inv_normalize(z)))
|
| 222 |
+
|
| 223 |
+
def train(self, mode: bool = True) -> "AutoencoderFlux2":
|
| 224 |
+
return super().train(False)
|
generalist/lora.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""LoRA for the GeoSET transformer: rank-r adapters on the attention and MLP projections of every block.
|
| 2 |
+
|
| 3 |
+
An adapter directory holds ``adapter_model.safetensors`` (``<module path>.lora_A`` ``[r, in]`` and
|
| 4 |
+
``<module path>.lora_B`` ``[out, r]``, float32) and ``adapter_config.json``.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import hashlib
|
| 10 |
+
import json
|
| 11 |
+
import math
|
| 12 |
+
import os
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
import torch.nn.functional as F
|
| 17 |
+
from safetensors.torch import load_file, save_file
|
| 18 |
+
from torch import Tensor, nn
|
| 19 |
+
|
| 20 |
+
LORA_TARGETS = (
|
| 21 |
+
"img_attn.qkv", "img_attn.proj", "img_mlp.0", "img_mlp.2",
|
| 22 |
+
"txt_attn.qkv", "txt_attn.proj", "txt_mlp.0", "txt_mlp.2",
|
| 23 |
+
"linear1", "linear2",
|
| 24 |
+
)
|
| 25 |
+
_FINGERPRINT_TENSORS = ("img_in.weight", "final_layer.linear.weight", "time_in.in_layer.weight")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class LoRALinear(nn.Linear):
|
| 29 |
+
"""``W x + (alpha / r) B A x`` sharing the base weight; ``A`` is Kaiming-uniform, ``B`` starts at zero."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, base: nn.Linear, rank: int, alpha: float):
|
| 32 |
+
nn.Module.__init__(self)
|
| 33 |
+
self.in_features = base.in_features
|
| 34 |
+
self.out_features = base.out_features
|
| 35 |
+
self.weight = base.weight
|
| 36 |
+
self.register_parameter("bias", base.bias)
|
| 37 |
+
self.rank = int(rank)
|
| 38 |
+
self.alpha = float(alpha)
|
| 39 |
+
self.scale = self.alpha / self.rank
|
| 40 |
+
kw = dict(dtype=self.weight.dtype, device=self.weight.device)
|
| 41 |
+
self.lora_A = nn.Parameter(torch.empty(self.rank, self.in_features, **kw))
|
| 42 |
+
self.lora_B = nn.Parameter(torch.zeros(self.out_features, self.rank, **kw))
|
| 43 |
+
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
| 44 |
+
|
| 45 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 46 |
+
return F.linear(x, self.weight, self.bias) + F.linear(F.linear(x, self.lora_A), self.lora_B) * self.scale
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def add_lora(model: nn.Module, rank: int = 16, alpha: float = 16.0) -> list[str]:
|
| 50 |
+
"""Wrap the target projections in place and freeze every other parameter; returns the wrapped module paths."""
|
| 51 |
+
targets = [name for name, mod in model.named_modules()
|
| 52 |
+
if isinstance(mod, nn.Linear) and name.startswith(("double_blocks.", "single_blocks."))
|
| 53 |
+
and name.split(".", 2)[2] in LORA_TARGETS]
|
| 54 |
+
model.requires_grad_(False)
|
| 55 |
+
for path in targets:
|
| 56 |
+
parent, _, leaf = path.rpartition(".")
|
| 57 |
+
setattr(model.get_submodule(parent), leaf, LoRALinear(model.get_submodule(path), rank, alpha))
|
| 58 |
+
for p in lora_parameters(model).values():
|
| 59 |
+
p.requires_grad_(True)
|
| 60 |
+
return targets
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def lora_parameters(model: nn.Module) -> dict[str, nn.Parameter]:
|
| 64 |
+
"""The adapter parameters by name, in module order."""
|
| 65 |
+
return {n: p for n, p in model.named_parameters() if n.endswith((".lora_A", ".lora_B"))}
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def check_lora_grads(model: nn.Module) -> int:
|
| 69 |
+
"""Raise unless exactly the adapter parameters carry gradients; returns their count."""
|
| 70 |
+
params = dict(model.named_parameters())
|
| 71 |
+
adapter = lora_parameters(model)
|
| 72 |
+
wrong = [n for n, p in params.items() if p.grad is not None and n not in adapter]
|
| 73 |
+
dead = [n for n, p in adapter.items() if p.grad is None]
|
| 74 |
+
if wrong or dead or not adapter:
|
| 75 |
+
raise RuntimeError(f"LoRA gradient check failed: base tensors with grad {wrong[:3]}, adapter tensors without {dead[:3]}")
|
| 76 |
+
return len(adapter)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def base_fingerprint(model: nn.Module) -> str:
|
| 80 |
+
"""sha256 over three base tensors that no adapter modifies."""
|
| 81 |
+
sd = model.state_dict()
|
| 82 |
+
h = hashlib.sha256()
|
| 83 |
+
for name in _FINGERPRINT_TENSORS:
|
| 84 |
+
h.update(name.encode())
|
| 85 |
+
h.update(sd[name].detach().float().cpu().contiguous().numpy().tobytes())
|
| 86 |
+
return h.hexdigest()
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def save_lora(save_directory: str | os.PathLike, adapter: dict[str, Tensor], rank: int, alpha: float,
|
| 90 |
+
base_fingerprint: str) -> None:
|
| 91 |
+
"""Write ``adapter_model.safetensors`` and ``adapter_config.json``."""
|
| 92 |
+
out = Path(save_directory)
|
| 93 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 94 |
+
tensors = {k: v.detach().float().cpu().contiguous() for k, v in adapter.items()}
|
| 95 |
+
save_file(tensors, str(out / "adapter_model.safetensors"), metadata={"format": "pt"})
|
| 96 |
+
config = {
|
| 97 |
+
"rank": int(rank),
|
| 98 |
+
"alpha": float(alpha),
|
| 99 |
+
"target_modules": sorted({k.rsplit(".", 1)[0] for k in tensors}),
|
| 100 |
+
"base_fingerprint": base_fingerprint,
|
| 101 |
+
}
|
| 102 |
+
(out / "adapter_config.json").write_text(json.dumps(config, indent=2) + "\n")
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def load_lora(path: str | os.PathLike) -> tuple[dict[str, Tensor], dict]:
|
| 106 |
+
"""Read an adapter directory: ``(tensors, config)``."""
|
| 107 |
+
path = Path(path)
|
| 108 |
+
return load_file(str(path / "adapter_model.safetensors")), json.loads((path / "adapter_config.json").read_text())
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@torch.no_grad()
|
| 112 |
+
def merge_lora(model: nn.Module, path: str | os.PathLike) -> dict:
|
| 113 |
+
"""Fold an adapter into an unadapted model in place: ``W += (alpha / r) B A`` (float32); returns a report."""
|
| 114 |
+
if getattr(model, "_lora_merged", None):
|
| 115 |
+
raise RuntimeError(f"{model._lora_merged} is already merged into this model")
|
| 116 |
+
tensors, cfg = load_lora(path)
|
| 117 |
+
if base_fingerprint(model) != cfg["base_fingerprint"]:
|
| 118 |
+
raise RuntimeError("this adapter was trained on different base weights")
|
| 119 |
+
scale = float(cfg["alpha"]) / float(cfg["rank"])
|
| 120 |
+
params = dict(model.named_parameters())
|
| 121 |
+
num = den = 0.0
|
| 122 |
+
for name in cfg["target_modules"]:
|
| 123 |
+
w = params[f"{name}.weight"]
|
| 124 |
+
d = (tensors[f"{name}.lora_B"].float() @ tensors[f"{name}.lora_A"].float()) * scale
|
| 125 |
+
num += float(d.pow(2).sum())
|
| 126 |
+
den += float(w.detach().float().pow(2).sum())
|
| 127 |
+
w.add_(d.to(w.dtype, copy=False).to(w.device))
|
| 128 |
+
model._lora_merged = str(path)
|
| 129 |
+
return {"rank": cfg["rank"], "alpha": cfg["alpha"], "merged_modules": len(cfg["target_modules"]),
|
| 130 |
+
"delta_rel_fro": math.sqrt(num / den)}
|
generalist/model_index.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "GeoSETPipeline",
|
| 3 |
+
"_diffusers_version": "0.37.1",
|
| 4 |
+
"sar_encoder": [
|
| 5 |
+
"sar_encoder_geoset",
|
| 6 |
+
"GeoSETSAREncoder"
|
| 7 |
+
],
|
| 8 |
+
"scheduler": [
|
| 9 |
+
"scheduler_flow_bridge",
|
| 10 |
+
"FlowBridgeScheduler"
|
| 11 |
+
],
|
| 12 |
+
"transformer": [
|
| 13 |
+
"transformer_geoset",
|
| 14 |
+
"GeoSETTransformer2DModel"
|
| 15 |
+
],
|
| 16 |
+
"vae": [
|
| 17 |
+
"autoencoder_flux2",
|
| 18 |
+
"AutoencoderFlux2"
|
| 19 |
+
]
|
| 20 |
+
}
|
generalist/pipeline.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GeoSET SAR-to-EO translation pipeline."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Optional, Union
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 12 |
+
from diffusers.pipelines.pipeline_utils import DiffusionPipeline, ImagePipelineOutput
|
| 13 |
+
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
| 14 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 15 |
+
from huggingface_hub import snapshot_download
|
| 16 |
+
from PIL import Image
|
| 17 |
+
|
| 18 |
+
from .lora import merge_lora
|
| 19 |
+
|
| 20 |
+
HUB_REPO = "JeonghyeokDo/GeoSET"
|
| 21 |
+
LORA_ADAPTERS = ("qxs-saropt", "sar2opt", "sar2eo", "spacenet6")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def sar_to_tensor(image: Image.Image) -> torch.Tensor:
|
| 25 |
+
"""8-bit SAR image -> ``[1, H, W]`` float32 in [-1, 1] (colour inputs are reduced to luminance)."""
|
| 26 |
+
return torch.from_numpy(np.asarray(image.convert("L"), np.float32)[None] / 127.5 - 1.0)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class GeoSETPipeline(DiffusionPipeline):
|
| 30 |
+
"""SAR image -> EO image.
|
| 31 |
+
|
| 32 |
+
Components: ``transformer`` (GeoSETTransformer2DModel), ``sar_encoder`` (GeoSETSAREncoder), ``vae``
|
| 33 |
+
(AutoencoderFlux2) and ``scheduler`` (FlowBridgeScheduler). The SAR encoder maps the input to the condition
|
| 34 |
+
latent ``z_s``; the flow bridge is integrated from Gaussian noise with classifier-free guidance
|
| 35 |
+
``v_u + w (v_c - v_u)`` (all-zero null condition) and the result is decoded by the FLUX.2 autoencoder. The
|
| 36 |
+
transformer and the SAR encoder run in float32 (the transformer under bfloat16 autocast on CUDA); the
|
| 37 |
+
autoencoder decodes in bfloat16.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
model_cpu_offload_seq = "sar_encoder->transformer->vae"
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
transformer: ModelMixin,
|
| 45 |
+
sar_encoder: ModelMixin,
|
| 46 |
+
vae: ModelMixin,
|
| 47 |
+
scheduler: SchedulerMixin,
|
| 48 |
+
):
|
| 49 |
+
super().__init__()
|
| 50 |
+
vae.to(dtype=torch.bfloat16, memory_format=torch.channels_last)
|
| 51 |
+
self.register_modules(transformer=transformer, sar_encoder=sar_encoder, vae=vae, scheduler=scheduler)
|
| 52 |
+
|
| 53 |
+
def load_lora(self, adapter: Union[str, os.PathLike], repo_id: str = HUB_REPO) -> dict:
|
| 54 |
+
"""Merge a dataset adapter into the transformer: a released key (``LORA_ADAPTERS``) or an adapter directory."""
|
| 55 |
+
path = Path(adapter)
|
| 56 |
+
if not path.is_dir():
|
| 57 |
+
if adapter not in LORA_ADAPTERS:
|
| 58 |
+
raise ValueError(f"{adapter!r} is neither an adapter directory nor one of {LORA_ADAPTERS}")
|
| 59 |
+
root = Path(self.name_or_path).parent if self.name_or_path else None
|
| 60 |
+
if root is None or not (root / "lora" / adapter).is_dir():
|
| 61 |
+
root = Path(snapshot_download(repo_id, allow_patterns=[f"lora/{adapter}/*"]))
|
| 62 |
+
path = root / "lora" / adapter
|
| 63 |
+
return merge_lora(self.transformer, path)
|
| 64 |
+
|
| 65 |
+
@staticmethod
|
| 66 |
+
def preprocess(image: Union[Image.Image, list, torch.Tensor]) -> torch.Tensor:
|
| 67 |
+
"""PIL image(s) -> ``[B, 1, H, W]`` in [-1, 1]; tensors ``[C, H, W]`` / ``[B, C, H, W]`` must be in [-1, 1]."""
|
| 68 |
+
if isinstance(image, torch.Tensor):
|
| 69 |
+
return image if image.ndim == 4 else image[None]
|
| 70 |
+
images = image if isinstance(image, (list, tuple)) else [image]
|
| 71 |
+
return torch.stack([sar_to_tensor(im) for im in images])
|
| 72 |
+
|
| 73 |
+
@torch.no_grad()
|
| 74 |
+
def __call__(
|
| 75 |
+
self,
|
| 76 |
+
image: Union[Image.Image, list, torch.Tensor],
|
| 77 |
+
num_inference_steps: int = 50,
|
| 78 |
+
guidance_scale: float = 2.0,
|
| 79 |
+
generator: Optional[Union[torch.Generator, list]] = None,
|
| 80 |
+
latents: Optional[torch.Tensor] = None,
|
| 81 |
+
output_type: str = "pil",
|
| 82 |
+
return_dict: bool = True,
|
| 83 |
+
) -> Union[ImagePipelineOutput, tuple]:
|
| 84 |
+
"""Translate SAR image(s); height and width must be multiples of 16.
|
| 85 |
+
|
| 86 |
+
Returns EO images as PIL (8-bit RGB), or as float tensors / arrays in [0, 1] (``"pt"`` ``[B, 3, H, W]``,
|
| 87 |
+
``"np"`` ``[B, H, W, 3]``).
|
| 88 |
+
"""
|
| 89 |
+
if output_type not in ("pil", "np", "pt"):
|
| 90 |
+
raise ValueError(f"output_type must be 'pil', 'np' or 'pt', got {output_type!r}")
|
| 91 |
+
device = self._execution_device
|
| 92 |
+
sar = self.preprocess(image).to(device=device, dtype=torch.float32)
|
| 93 |
+
if sar.shape[-2] % 16 or sar.shape[-1] % 16:
|
| 94 |
+
raise ValueError(f"SAR size {tuple(sar.shape[-2:])} must be a multiple of 16")
|
| 95 |
+
z_s = self.sar_encoder(sar)
|
| 96 |
+
if latents is None:
|
| 97 |
+
latents = randn_tensor(z_s.shape, generator=generator, device=device, dtype=torch.float32)
|
| 98 |
+
latents = latents.to(device=device, dtype=torch.float32)
|
| 99 |
+
null = torch.zeros_like(z_s)
|
| 100 |
+
|
| 101 |
+
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
| 102 |
+
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=device.type == "cuda"):
|
| 103 |
+
for t in self.progress_bar(self.scheduler.timesteps):
|
| 104 |
+
t_batch = t.expand(latents.shape[0])
|
| 105 |
+
v = self.transformer(latents, t_batch, z_s)
|
| 106 |
+
if guidance_scale != 1.0:
|
| 107 |
+
v_u = self.transformer(latents, t_batch, null)
|
| 108 |
+
v = v_u + guidance_scale * (v - v_u)
|
| 109 |
+
latents = self.scheduler.step(v, t, latents, return_dict=False)[0]
|
| 110 |
+
image = self.vae.decode(latents.to(self.vae.dtype)).float()
|
| 111 |
+
image = ((image + 1) / 2).clamp(0, 1)
|
| 112 |
+
self.maybe_free_model_hooks()
|
| 113 |
+
|
| 114 |
+
if output_type == "pil":
|
| 115 |
+
arr = (image * 255).round().to(torch.uint8).permute(0, 2, 3, 1).cpu().numpy()
|
| 116 |
+
image = [Image.fromarray(a) for a in arr]
|
| 117 |
+
elif output_type == "np":
|
| 118 |
+
image = image.permute(0, 2, 3, 1).cpu().numpy()
|
| 119 |
+
if not return_dict:
|
| 120 |
+
return (image,)
|
| 121 |
+
return ImagePipelineOutput(images=image)
|
generalist/pipeline_geoset.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GeoSET SAR-to-EO translation pipeline."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import Optional, Union
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 12 |
+
from diffusers.pipelines.pipeline_utils import DiffusionPipeline, ImagePipelineOutput
|
| 13 |
+
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
| 14 |
+
from diffusers.utils.torch_utils import randn_tensor
|
| 15 |
+
from huggingface_hub import snapshot_download
|
| 16 |
+
from PIL import Image
|
| 17 |
+
|
| 18 |
+
from .lora import merge_lora
|
| 19 |
+
|
| 20 |
+
HUB_REPO = "JeonghyeokDo/GeoSET"
|
| 21 |
+
LORA_ADAPTERS = ("qxs-saropt", "sar2opt", "sar2eo", "spacenet6")
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def sar_to_tensor(image: Image.Image) -> torch.Tensor:
|
| 25 |
+
"""8-bit SAR image -> ``[1, H, W]`` float32 in [-1, 1] (colour inputs are reduced to luminance)."""
|
| 26 |
+
return torch.from_numpy(np.asarray(image.convert("L"), np.float32)[None] / 127.5 - 1.0)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class GeoSETPipeline(DiffusionPipeline):
|
| 30 |
+
"""SAR image -> EO image.
|
| 31 |
+
|
| 32 |
+
Components: ``transformer`` (GeoSETTransformer2DModel), ``sar_encoder`` (GeoSETSAREncoder), ``vae``
|
| 33 |
+
(AutoencoderFlux2) and ``scheduler`` (FlowBridgeScheduler). The SAR encoder maps the input to the condition
|
| 34 |
+
latent ``z_s``; the flow bridge is integrated from Gaussian noise with classifier-free guidance
|
| 35 |
+
``v_u + w (v_c - v_u)`` (all-zero null condition) and the result is decoded by the FLUX.2 autoencoder. The
|
| 36 |
+
transformer and the SAR encoder run in float32 (the transformer under bfloat16 autocast on CUDA); the
|
| 37 |
+
autoencoder decodes in bfloat16.
|
| 38 |
+
"""
|
| 39 |
+
|
| 40 |
+
model_cpu_offload_seq = "sar_encoder->transformer->vae"
|
| 41 |
+
|
| 42 |
+
def __init__(
|
| 43 |
+
self,
|
| 44 |
+
transformer: ModelMixin,
|
| 45 |
+
sar_encoder: ModelMixin,
|
| 46 |
+
vae: ModelMixin,
|
| 47 |
+
scheduler: SchedulerMixin,
|
| 48 |
+
):
|
| 49 |
+
super().__init__()
|
| 50 |
+
vae.to(dtype=torch.bfloat16, memory_format=torch.channels_last)
|
| 51 |
+
self.register_modules(transformer=transformer, sar_encoder=sar_encoder, vae=vae, scheduler=scheduler)
|
| 52 |
+
|
| 53 |
+
def load_lora(self, adapter: Union[str, os.PathLike], repo_id: str = HUB_REPO) -> dict:
|
| 54 |
+
"""Merge a dataset adapter into the transformer: a released key (``LORA_ADAPTERS``) or an adapter directory."""
|
| 55 |
+
path = Path(adapter)
|
| 56 |
+
if not path.is_dir():
|
| 57 |
+
if adapter not in LORA_ADAPTERS:
|
| 58 |
+
raise ValueError(f"{adapter!r} is neither an adapter directory nor one of {LORA_ADAPTERS}")
|
| 59 |
+
root = Path(self.name_or_path).parent if self.name_or_path else None
|
| 60 |
+
if root is None or not (root / "lora" / adapter).is_dir():
|
| 61 |
+
root = Path(snapshot_download(repo_id, allow_patterns=[f"lora/{adapter}/*"]))
|
| 62 |
+
path = root / "lora" / adapter
|
| 63 |
+
return merge_lora(self.transformer, path)
|
| 64 |
+
|
| 65 |
+
@staticmethod
|
| 66 |
+
def preprocess(image: Union[Image.Image, list, torch.Tensor]) -> torch.Tensor:
|
| 67 |
+
"""PIL image(s) -> ``[B, 1, H, W]`` in [-1, 1]; tensors ``[C, H, W]`` / ``[B, C, H, W]`` must be in [-1, 1]."""
|
| 68 |
+
if isinstance(image, torch.Tensor):
|
| 69 |
+
return image if image.ndim == 4 else image[None]
|
| 70 |
+
images = image if isinstance(image, (list, tuple)) else [image]
|
| 71 |
+
return torch.stack([sar_to_tensor(im) for im in images])
|
| 72 |
+
|
| 73 |
+
@torch.no_grad()
|
| 74 |
+
def __call__(
|
| 75 |
+
self,
|
| 76 |
+
image: Union[Image.Image, list, torch.Tensor],
|
| 77 |
+
num_inference_steps: int = 50,
|
| 78 |
+
guidance_scale: float = 2.0,
|
| 79 |
+
generator: Optional[Union[torch.Generator, list]] = None,
|
| 80 |
+
latents: Optional[torch.Tensor] = None,
|
| 81 |
+
output_type: str = "pil",
|
| 82 |
+
return_dict: bool = True,
|
| 83 |
+
) -> Union[ImagePipelineOutput, tuple]:
|
| 84 |
+
"""Translate SAR image(s); height and width must be multiples of 16.
|
| 85 |
+
|
| 86 |
+
Returns EO images as PIL (8-bit RGB), or as float tensors / arrays in [0, 1] (``"pt"`` ``[B, 3, H, W]``,
|
| 87 |
+
``"np"`` ``[B, H, W, 3]``).
|
| 88 |
+
"""
|
| 89 |
+
if output_type not in ("pil", "np", "pt"):
|
| 90 |
+
raise ValueError(f"output_type must be 'pil', 'np' or 'pt', got {output_type!r}")
|
| 91 |
+
device = self._execution_device
|
| 92 |
+
sar = self.preprocess(image).to(device=device, dtype=torch.float32)
|
| 93 |
+
if sar.shape[-2] % 16 or sar.shape[-1] % 16:
|
| 94 |
+
raise ValueError(f"SAR size {tuple(sar.shape[-2:])} must be a multiple of 16")
|
| 95 |
+
z_s = self.sar_encoder(sar)
|
| 96 |
+
if latents is None:
|
| 97 |
+
latents = randn_tensor(z_s.shape, generator=generator, device=device, dtype=torch.float32)
|
| 98 |
+
latents = latents.to(device=device, dtype=torch.float32)
|
| 99 |
+
null = torch.zeros_like(z_s)
|
| 100 |
+
|
| 101 |
+
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
| 102 |
+
with torch.autocast("cuda", dtype=torch.bfloat16, enabled=device.type == "cuda"):
|
| 103 |
+
for t in self.progress_bar(self.scheduler.timesteps):
|
| 104 |
+
t_batch = t.expand(latents.shape[0])
|
| 105 |
+
v = self.transformer(latents, t_batch, z_s)
|
| 106 |
+
if guidance_scale != 1.0:
|
| 107 |
+
v_u = self.transformer(latents, t_batch, null)
|
| 108 |
+
v = v_u + guidance_scale * (v - v_u)
|
| 109 |
+
latents = self.scheduler.step(v, t, latents, return_dict=False)[0]
|
| 110 |
+
image = self.vae.decode(latents.to(self.vae.dtype)).float()
|
| 111 |
+
image = ((image + 1) / 2).clamp(0, 1)
|
| 112 |
+
self.maybe_free_model_hooks()
|
| 113 |
+
|
| 114 |
+
if output_type == "pil":
|
| 115 |
+
arr = (image * 255).round().to(torch.uint8).permute(0, 2, 3, 1).cpu().numpy()
|
| 116 |
+
image = [Image.fromarray(a) for a in arr]
|
| 117 |
+
elif output_type == "np":
|
| 118 |
+
image = image.permute(0, 2, 3, 1).cpu().numpy()
|
| 119 |
+
if not return_dict:
|
| 120 |
+
return (image,)
|
| 121 |
+
return ImagePipelineOutput(images=image)
|
generalist/sar_encoder/autoencoder_flux2.py
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Black Forest Labs.
|
| 2 |
+
# Copyright (c) 2026 The GeoSET Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
#
|
| 16 |
+
# Adapted from black-forest-labs/flux2 (Apache-2.0). Modified by the GeoSET authors.
|
| 17 |
+
"""Frozen FLUX.2 autoencoder with the packed (2x2 space-to-depth), BatchNorm-normalised 128-channel latent."""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 23 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 24 |
+
from einops import rearrange
|
| 25 |
+
from torch import Tensor, nn
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def swish(x: Tensor) -> Tensor:
|
| 29 |
+
return x * torch.sigmoid(x)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class AttnBlock(nn.Module):
|
| 33 |
+
def __init__(self, in_channels: int):
|
| 34 |
+
super().__init__()
|
| 35 |
+
self.norm = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 36 |
+
self.q = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 37 |
+
self.k = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 38 |
+
self.v = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 39 |
+
self.proj_out = nn.Conv2d(in_channels, in_channels, kernel_size=1)
|
| 40 |
+
|
| 41 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 42 |
+
h_ = self.norm(x)
|
| 43 |
+
q, k, v = self.q(h_), self.k(h_), self.v(h_)
|
| 44 |
+
b, c, h, w = q.shape
|
| 45 |
+
q, k, v = (rearrange(t, "b c h w -> b 1 (h w) c").contiguous() for t in (q, k, v))
|
| 46 |
+
h_ = nn.functional.scaled_dot_product_attention(q, k, v)
|
| 47 |
+
return x + self.proj_out(rearrange(h_, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class ResnetBlock(nn.Module):
|
| 51 |
+
def __init__(self, in_channels: int, out_channels: int):
|
| 52 |
+
super().__init__()
|
| 53 |
+
self.in_channels = in_channels
|
| 54 |
+
self.out_channels = out_channels
|
| 55 |
+
self.norm1 = nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 56 |
+
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 57 |
+
self.norm2 = nn.GroupNorm(num_groups=32, num_channels=out_channels, eps=1e-6, affine=True)
|
| 58 |
+
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1)
|
| 59 |
+
if in_channels != out_channels:
|
| 60 |
+
self.nin_shortcut = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
|
| 61 |
+
|
| 62 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 63 |
+
h = self.conv1(swish(self.norm1(x)))
|
| 64 |
+
h = self.conv2(swish(self.norm2(h)))
|
| 65 |
+
if self.in_channels != self.out_channels:
|
| 66 |
+
x = self.nin_shortcut(x)
|
| 67 |
+
return x + h
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class Downsample(nn.Module):
|
| 71 |
+
def __init__(self, in_channels: int):
|
| 72 |
+
super().__init__()
|
| 73 |
+
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0)
|
| 74 |
+
|
| 75 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 76 |
+
return self.conv(nn.functional.pad(x, (0, 1, 0, 1), mode="constant", value=0))
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class Upsample(nn.Module):
|
| 80 |
+
def __init__(self, in_channels: int):
|
| 81 |
+
super().__init__()
|
| 82 |
+
self.conv = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1)
|
| 83 |
+
|
| 84 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 85 |
+
return self.conv(nn.functional.interpolate(x, scale_factor=2.0, mode="nearest"))
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class Encoder(nn.Module):
|
| 89 |
+
"""Image -> latent moments (mean and log-variance, 2 * z_channels at 1/8 resolution)."""
|
| 90 |
+
|
| 91 |
+
def __init__(self, in_channels: int, ch: int, ch_mult: list[int], num_res_blocks: int, z_channels: int):
|
| 92 |
+
super().__init__()
|
| 93 |
+
self.quant_conv = nn.Conv2d(2 * z_channels, 2 * z_channels, 1)
|
| 94 |
+
self.num_resolutions = len(ch_mult)
|
| 95 |
+
self.num_res_blocks = num_res_blocks
|
| 96 |
+
self.conv_in = nn.Conv2d(in_channels, ch, kernel_size=3, stride=1, padding=1)
|
| 97 |
+
in_ch_mult = (1,) + tuple(ch_mult)
|
| 98 |
+
self.down = nn.ModuleList()
|
| 99 |
+
for i_level in range(self.num_resolutions):
|
| 100 |
+
block = nn.ModuleList()
|
| 101 |
+
block_in = ch * in_ch_mult[i_level]
|
| 102 |
+
block_out = ch * ch_mult[i_level]
|
| 103 |
+
for _ in range(num_res_blocks):
|
| 104 |
+
block.append(ResnetBlock(block_in, block_out))
|
| 105 |
+
block_in = block_out
|
| 106 |
+
down = nn.Module()
|
| 107 |
+
down.block = block
|
| 108 |
+
if i_level != self.num_resolutions - 1:
|
| 109 |
+
down.downsample = Downsample(block_in)
|
| 110 |
+
self.down.append(down)
|
| 111 |
+
self.mid = nn.Module()
|
| 112 |
+
self.mid.block_1 = ResnetBlock(block_in, block_in)
|
| 113 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 114 |
+
self.mid.block_2 = ResnetBlock(block_in, block_in)
|
| 115 |
+
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
|
| 116 |
+
self.conv_out = nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1)
|
| 117 |
+
|
| 118 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 119 |
+
h = self.conv_in(x)
|
| 120 |
+
for i_level in range(self.num_resolutions):
|
| 121 |
+
for i_block in range(self.num_res_blocks):
|
| 122 |
+
h = self.down[i_level].block[i_block](h)
|
| 123 |
+
if i_level != self.num_resolutions - 1:
|
| 124 |
+
h = self.down[i_level].downsample(h)
|
| 125 |
+
h = self.mid.block_2(self.mid.attn_1(self.mid.block_1(h)))
|
| 126 |
+
return self.quant_conv(self.conv_out(swish(self.norm_out(h))))
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class Decoder(nn.Module):
|
| 130 |
+
"""Latent (z_channels at 1/8 resolution) -> image."""
|
| 131 |
+
|
| 132 |
+
def __init__(self, ch: int, out_ch: int, ch_mult: list[int], num_res_blocks: int, z_channels: int):
|
| 133 |
+
super().__init__()
|
| 134 |
+
self.post_quant_conv = nn.Conv2d(z_channels, z_channels, 1)
|
| 135 |
+
self.num_resolutions = len(ch_mult)
|
| 136 |
+
self.num_res_blocks = num_res_blocks
|
| 137 |
+
block_in = ch * ch_mult[-1]
|
| 138 |
+
self.conv_in = nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1)
|
| 139 |
+
self.mid = nn.Module()
|
| 140 |
+
self.mid.block_1 = ResnetBlock(block_in, block_in)
|
| 141 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 142 |
+
self.mid.block_2 = ResnetBlock(block_in, block_in)
|
| 143 |
+
self.up = nn.ModuleList()
|
| 144 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 145 |
+
block = nn.ModuleList()
|
| 146 |
+
block_out = ch * ch_mult[i_level]
|
| 147 |
+
for _ in range(num_res_blocks + 1):
|
| 148 |
+
block.append(ResnetBlock(block_in, block_out))
|
| 149 |
+
block_in = block_out
|
| 150 |
+
up = nn.Module()
|
| 151 |
+
up.block = block
|
| 152 |
+
if i_level != 0:
|
| 153 |
+
up.upsample = Upsample(block_in)
|
| 154 |
+
self.up.insert(0, up)
|
| 155 |
+
self.norm_out = nn.GroupNorm(num_groups=32, num_channels=block_in, eps=1e-6, affine=True)
|
| 156 |
+
self.conv_out = nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1)
|
| 157 |
+
|
| 158 |
+
def trunk(self, z: Tensor) -> Tensor:
|
| 159 |
+
"""Every layer except ``conv_out``: returns the ``ch``-channel features at full resolution."""
|
| 160 |
+
h = self.conv_in(self.post_quant_conv(z))
|
| 161 |
+
h = self.mid.block_2(self.mid.attn_1(self.mid.block_1(h)))
|
| 162 |
+
h = h.to(next(self.up.parameters()).dtype)
|
| 163 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 164 |
+
for i_block in range(self.num_res_blocks + 1):
|
| 165 |
+
h = self.up[i_level].block[i_block](h)
|
| 166 |
+
if i_level != 0:
|
| 167 |
+
h = self.up[i_level].upsample(h)
|
| 168 |
+
return swish(self.norm_out(h))
|
| 169 |
+
|
| 170 |
+
def forward(self, z: Tensor) -> Tensor:
|
| 171 |
+
return self.conv_out(self.trunk(z))
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
class AutoencoderFlux2(ModelMixin, ConfigMixin):
|
| 175 |
+
"""FLUX.2 autoencoder: ``encode`` maps ``[B, 3, H, W]`` in [-1, 1] to ``[B, 128, H/16, W/16]``, ``decode`` inverts it.
|
| 176 |
+
|
| 177 |
+
The latent is the posterior mean, packed 2x2 space-to-depth and normalised by a BatchNorm with fixed running
|
| 178 |
+
statistics. The module is frozen and always in eval mode.
|
| 179 |
+
"""
|
| 180 |
+
|
| 181 |
+
@register_to_config
|
| 182 |
+
def __init__(
|
| 183 |
+
self,
|
| 184 |
+
in_channels: int = 3,
|
| 185 |
+
out_ch: int = 3,
|
| 186 |
+
ch: int = 128,
|
| 187 |
+
ch_mult: tuple[int, ...] = (1, 2, 4, 4),
|
| 188 |
+
num_res_blocks: int = 2,
|
| 189 |
+
z_channels: int = 32,
|
| 190 |
+
):
|
| 191 |
+
super().__init__()
|
| 192 |
+
self.encoder = Encoder(in_channels, ch, list(ch_mult), num_res_blocks, z_channels)
|
| 193 |
+
self.decoder = Decoder(ch, out_ch, list(ch_mult), num_res_blocks, z_channels)
|
| 194 |
+
self.bn = nn.BatchNorm2d(4 * z_channels, eps=1e-4, momentum=0.1, affine=False, track_running_stats=True)
|
| 195 |
+
self.requires_grad_(False)
|
| 196 |
+
self.eval()
|
| 197 |
+
|
| 198 |
+
@staticmethod
|
| 199 |
+
def pack(z: Tensor) -> Tensor:
|
| 200 |
+
return rearrange(z, "... c (i pi) (j pj) -> ... (c pi pj) i j", pi=2, pj=2)
|
| 201 |
+
|
| 202 |
+
@staticmethod
|
| 203 |
+
def unpack(z: Tensor) -> Tensor:
|
| 204 |
+
return rearrange(z, "... (c pi pj) i j -> ... c (i pi) (j pj)", pi=2, pj=2)
|
| 205 |
+
|
| 206 |
+
def normalize(self, z: Tensor) -> Tensor:
|
| 207 |
+
self.bn.eval()
|
| 208 |
+
return self.bn(z)
|
| 209 |
+
|
| 210 |
+
def inv_normalize(self, z: Tensor) -> Tensor:
|
| 211 |
+
self.bn.eval()
|
| 212 |
+
s = torch.sqrt(self.bn.running_var.view(1, -1, 1, 1) + self.bn.eps)
|
| 213 |
+
m = self.bn.running_mean.view(1, -1, 1, 1)
|
| 214 |
+
return z * s + m
|
| 215 |
+
|
| 216 |
+
def encode(self, x: Tensor) -> Tensor:
|
| 217 |
+
moments = self.encoder(x)
|
| 218 |
+
return self.normalize(self.pack(torch.chunk(moments, 2, dim=1)[0]))
|
| 219 |
+
|
| 220 |
+
def decode(self, z: Tensor) -> Tensor:
|
| 221 |
+
return self.decoder(self.unpack(self.inv_normalize(z)))
|
| 222 |
+
|
| 223 |
+
def train(self, mode: bool = True) -> "AutoencoderFlux2":
|
| 224 |
+
return super().train(False)
|
generalist/sar_encoder/config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "GeoSETSAREncoder",
|
| 3 |
+
"_diffusers_version": "0.37.1",
|
| 4 |
+
"ch": 128,
|
| 5 |
+
"ch_mult": [
|
| 6 |
+
1,
|
| 7 |
+
2,
|
| 8 |
+
4,
|
| 9 |
+
4
|
| 10 |
+
],
|
| 11 |
+
"num_res_blocks": 2,
|
| 12 |
+
"z_channels": 32
|
| 13 |
+
}
|
generalist/sar_encoder/diffusion_pytorch_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:49d44c88ed075c262115ced4ffb3f1e010cd6ce84423b85bea3e0243d61694f4
|
| 3 |
+
size 137749092
|
generalist/sar_encoder/sar_encoder_geoset.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Black Forest Labs.
|
| 2 |
+
# Copyright (c) 2026 The GeoSET Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
#
|
| 16 |
+
# Adapted from black-forest-labs/flux2 (Apache-2.0). Modified by the GeoSET authors.
|
| 17 |
+
"""Speckle-robust SAR encoder (stage 1): the FLUX.2 encoder trunk with channel-specific input and output stems."""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 23 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 24 |
+
from torch import Tensor, nn
|
| 25 |
+
|
| 26 |
+
from .autoencoder_flux2 import AutoencoderFlux2, Encoder
|
| 27 |
+
|
| 28 |
+
_CHUNK = 32
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class GeoSETSAREncoder(ModelMixin, ConfigMixin):
|
| 32 |
+
"""SAR image ``[B, 1|2, H, W]`` in [-1, 1] -> SAR latent ``z_s`` ``[B, 128, H/16, W/16]``.
|
| 33 |
+
|
| 34 |
+
Stems are keyed by input channels: ``"3"`` takes single-channel SAR folded to three channels, ``"2"`` takes
|
| 35 |
+
VV/VH. The latent is normalised with the autoencoder's fixed BatchNorm statistics. ``stem_out`` (stage-1
|
| 36 |
+
reconstruction heads) is not used by ``forward``.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
@register_to_config
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
ch: int = 128,
|
| 43 |
+
ch_mult: tuple[int, ...] = (1, 2, 4, 4),
|
| 44 |
+
num_res_blocks: int = 2,
|
| 45 |
+
z_channels: int = 32,
|
| 46 |
+
):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.encoder = Encoder(3, ch, list(ch_mult), num_res_blocks, z_channels)
|
| 49 |
+
self.encoder.conv_in = nn.Identity()
|
| 50 |
+
self.bn = nn.BatchNorm2d(4 * z_channels, eps=1e-4, momentum=0.1, affine=False, track_running_stats=True)
|
| 51 |
+
self.bn.eval()
|
| 52 |
+
self.stem_in = nn.ModuleDict({"3": nn.Conv2d(3, ch, 3, 1, 1), "2": nn.Conv2d(2, ch, 3, 1, 1)})
|
| 53 |
+
self.stem_out = nn.ModuleDict({"3": nn.Conv2d(ch, 3, 3, 1, 1), "2": nn.Conv2d(ch, 2, 3, 1, 1)})
|
| 54 |
+
|
| 55 |
+
def train(self, mode: bool = True) -> "GeoSETSAREncoder":
|
| 56 |
+
super().train(mode)
|
| 57 |
+
self.bn.eval()
|
| 58 |
+
return self
|
| 59 |
+
|
| 60 |
+
def encode(self, x: Tensor) -> tuple[Tensor, Tensor]:
|
| 61 |
+
"""``[B, 3|2, H, W]`` -> ``(z, z_raw)``: the BatchNorm-normalised and the raw packed posterior mean."""
|
| 62 |
+
moments = self.encoder(self.stem_in[str(x.shape[1])](x))
|
| 63 |
+
z_raw = AutoencoderFlux2.pack(torch.chunk(moments, 2, dim=1)[0])
|
| 64 |
+
return self.bn(z_raw), z_raw
|
| 65 |
+
|
| 66 |
+
def forward(self, sar: Tensor) -> Tensor:
|
| 67 |
+
if sar.shape[1] == 1:
|
| 68 |
+
sar = sar.repeat(1, 3, 1, 1)
|
| 69 |
+
return torch.cat([self.encode(c)[0] for c in sar.split(_CHUNK)], dim=0).float()
|
| 70 |
+
|
| 71 |
+
def reconstruct(self, x: Tensor, vae: AutoencoderFlux2) -> Tensor:
|
| 72 |
+
"""Stage-1 reconstruction ``[B, 3|2, H, W]`` -> ``[B, 3|2, H, W]`` through the frozen decoder trunk."""
|
| 73 |
+
_, z_raw = self.encode(x)
|
| 74 |
+
return self.stem_out[str(x.shape[1])](vae.decoder.trunk(AutoencoderFlux2.unpack(z_raw)))
|
| 75 |
+
|
| 76 |
+
@classmethod
|
| 77 |
+
@torch.no_grad()
|
| 78 |
+
def from_autoencoder(cls, vae: AutoencoderFlux2) -> "GeoSETSAREncoder":
|
| 79 |
+
"""Stage-1 initialisation from the FLUX.2 autoencoder.
|
| 80 |
+
|
| 81 |
+
The trunk and latent BatchNorm are copies of the encoder's; ``conv_in`` and ``conv_out`` become the
|
| 82 |
+
three-channel stems; the two-channel stems start from their input-channel sum and output-channel mean.
|
| 83 |
+
Trainable: the trunk, both input stems and ``stem_out["2"]``; ``stem_out["3"]`` and the BatchNorm stay fixed.
|
| 84 |
+
"""
|
| 85 |
+
c = vae.config
|
| 86 |
+
model = cls(ch=c.ch, ch_mult=c.ch_mult, num_res_blocks=c.num_res_blocks, z_channels=c.z_channels)
|
| 87 |
+
model.encoder.load_state_dict({k: v for k, v in vae.encoder.state_dict().items()
|
| 88 |
+
if not k.startswith("conv_in.")})
|
| 89 |
+
model.bn.load_state_dict(vae.bn.state_dict())
|
| 90 |
+
w_in, b_in = vae.encoder.conv_in.weight.float(), vae.encoder.conv_in.bias.float()
|
| 91 |
+
w_out, b_out = vae.decoder.conv_out.weight.float(), vae.decoder.conv_out.bias.float()
|
| 92 |
+
stems = {
|
| 93 |
+
("stem_in", "3"): (w_in, b_in),
|
| 94 |
+
("stem_in", "2"): (w_in.sum(dim=1, keepdim=True).repeat(1, 2, 1, 1) / 2, b_in.clone()),
|
| 95 |
+
("stem_out", "3"): (w_out, b_out),
|
| 96 |
+
("stem_out", "2"): (w_out.mean(dim=0, keepdim=True).repeat(2, 1, 1, 1), b_out.mean().repeat(2)),
|
| 97 |
+
}
|
| 98 |
+
for (group, key), (w, b) in stems.items():
|
| 99 |
+
getattr(model, group)[key].weight.copy_(w)
|
| 100 |
+
getattr(model, group)[key].bias.copy_(b)
|
| 101 |
+
model.stem_out["3"].requires_grad_(False)
|
| 102 |
+
return model
|
generalist/sar_encoder_geoset.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Black Forest Labs.
|
| 2 |
+
# Copyright (c) 2026 The GeoSET Authors.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
#
|
| 16 |
+
# Adapted from black-forest-labs/flux2 (Apache-2.0). Modified by the GeoSET authors.
|
| 17 |
+
"""Speckle-robust SAR encoder (stage 1): the FLUX.2 encoder trunk with channel-specific input and output stems."""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 23 |
+
from diffusers.models.modeling_utils import ModelMixin
|
| 24 |
+
from torch import Tensor, nn
|
| 25 |
+
|
| 26 |
+
from .autoencoder_flux2 import AutoencoderFlux2, Encoder
|
| 27 |
+
|
| 28 |
+
_CHUNK = 32
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class GeoSETSAREncoder(ModelMixin, ConfigMixin):
|
| 32 |
+
"""SAR image ``[B, 1|2, H, W]`` in [-1, 1] -> SAR latent ``z_s`` ``[B, 128, H/16, W/16]``.
|
| 33 |
+
|
| 34 |
+
Stems are keyed by input channels: ``"3"`` takes single-channel SAR folded to three channels, ``"2"`` takes
|
| 35 |
+
VV/VH. The latent is normalised with the autoencoder's fixed BatchNorm statistics. ``stem_out`` (stage-1
|
| 36 |
+
reconstruction heads) is not used by ``forward``.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
@register_to_config
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
ch: int = 128,
|
| 43 |
+
ch_mult: tuple[int, ...] = (1, 2, 4, 4),
|
| 44 |
+
num_res_blocks: int = 2,
|
| 45 |
+
z_channels: int = 32,
|
| 46 |
+
):
|
| 47 |
+
super().__init__()
|
| 48 |
+
self.encoder = Encoder(3, ch, list(ch_mult), num_res_blocks, z_channels)
|
| 49 |
+
self.encoder.conv_in = nn.Identity()
|
| 50 |
+
self.bn = nn.BatchNorm2d(4 * z_channels, eps=1e-4, momentum=0.1, affine=False, track_running_stats=True)
|
| 51 |
+
self.bn.eval()
|
| 52 |
+
self.stem_in = nn.ModuleDict({"3": nn.Conv2d(3, ch, 3, 1, 1), "2": nn.Conv2d(2, ch, 3, 1, 1)})
|
| 53 |
+
self.stem_out = nn.ModuleDict({"3": nn.Conv2d(ch, 3, 3, 1, 1), "2": nn.Conv2d(ch, 2, 3, 1, 1)})
|
| 54 |
+
|
| 55 |
+
def train(self, mode: bool = True) -> "GeoSETSAREncoder":
|
| 56 |
+
super().train(mode)
|
| 57 |
+
self.bn.eval()
|
| 58 |
+
return self
|
| 59 |
+
|
| 60 |
+
def encode(self, x: Tensor) -> tuple[Tensor, Tensor]:
|
| 61 |
+
"""``[B, 3|2, H, W]`` -> ``(z, z_raw)``: the BatchNorm-normalised and the raw packed posterior mean."""
|
| 62 |
+
moments = self.encoder(self.stem_in[str(x.shape[1])](x))
|
| 63 |
+
z_raw = AutoencoderFlux2.pack(torch.chunk(moments, 2, dim=1)[0])
|
| 64 |
+
return self.bn(z_raw), z_raw
|
| 65 |
+
|
| 66 |
+
def forward(self, sar: Tensor) -> Tensor:
|
| 67 |
+
if sar.shape[1] == 1:
|
| 68 |
+
sar = sar.repeat(1, 3, 1, 1)
|
| 69 |
+
return torch.cat([self.encode(c)[0] for c in sar.split(_CHUNK)], dim=0).float()
|
| 70 |
+
|
| 71 |
+
def reconstruct(self, x: Tensor, vae: AutoencoderFlux2) -> Tensor:
|
| 72 |
+
"""Stage-1 reconstruction ``[B, 3|2, H, W]`` -> ``[B, 3|2, H, W]`` through the frozen decoder trunk."""
|
| 73 |
+
_, z_raw = self.encode(x)
|
| 74 |
+
return self.stem_out[str(x.shape[1])](vae.decoder.trunk(AutoencoderFlux2.unpack(z_raw)))
|
| 75 |
+
|
| 76 |
+
@classmethod
|
| 77 |
+
@torch.no_grad()
|
| 78 |
+
def from_autoencoder(cls, vae: AutoencoderFlux2) -> "GeoSETSAREncoder":
|
| 79 |
+
"""Stage-1 initialisation from the FLUX.2 autoencoder.
|
| 80 |
+
|
| 81 |
+
The trunk and latent BatchNorm are copies of the encoder's; ``conv_in`` and ``conv_out`` become the
|
| 82 |
+
three-channel stems; the two-channel stems start from their input-channel sum and output-channel mean.
|
| 83 |
+
Trainable: the trunk, both input stems and ``stem_out["2"]``; ``stem_out["3"]`` and the BatchNorm stay fixed.
|
| 84 |
+
"""
|
| 85 |
+
c = vae.config
|
| 86 |
+
model = cls(ch=c.ch, ch_mult=c.ch_mult, num_res_blocks=c.num_res_blocks, z_channels=c.z_channels)
|
| 87 |
+
model.encoder.load_state_dict({k: v for k, v in vae.encoder.state_dict().items()
|
| 88 |
+
if not k.startswith("conv_in.")})
|
| 89 |
+
model.bn.load_state_dict(vae.bn.state_dict())
|
| 90 |
+
w_in, b_in = vae.encoder.conv_in.weight.float(), vae.encoder.conv_in.bias.float()
|
| 91 |
+
w_out, b_out = vae.decoder.conv_out.weight.float(), vae.decoder.conv_out.bias.float()
|
| 92 |
+
stems = {
|
| 93 |
+
("stem_in", "3"): (w_in, b_in),
|
| 94 |
+
("stem_in", "2"): (w_in.sum(dim=1, keepdim=True).repeat(1, 2, 1, 1) / 2, b_in.clone()),
|
| 95 |
+
("stem_out", "3"): (w_out, b_out),
|
| 96 |
+
("stem_out", "2"): (w_out.mean(dim=0, keepdim=True).repeat(2, 1, 1, 1), b_out.mean().repeat(2)),
|
| 97 |
+
}
|
| 98 |
+
for (group, key), (w, b) in stems.items():
|
| 99 |
+
getattr(model, group)[key].weight.copy_(w)
|
| 100 |
+
getattr(model, group)[key].bias.copy_(b)
|
| 101 |
+
model.stem_out["3"].requires_grad_(False)
|
| 102 |
+
return model
|
generalist/scheduler/scheduler_config.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_class_name": "FlowBridgeScheduler",
|
| 3 |
+
"_diffusers_version": "0.37.1"
|
| 4 |
+
}
|
generalist/scheduler/scheduler_flow_bridge.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Explicit-Euler solver for the GeoSET flow bridge (t = 0: Gaussian noise, t = 1: EO latent)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Optional, Union
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
| 10 |
+
from diffusers.schedulers.scheduling_utils import SchedulerMixin
|
| 11 |
+
from diffusers.utils import BaseOutput
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
@dataclass
|
| 15 |
+
class FlowBridgeSchedulerOutput(BaseOutput):
|
| 16 |
+
prev_sample: torch.Tensor
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class FlowBridgeScheduler(SchedulerMixin, ConfigMixin):
|
| 20 |
+
"""Uniform grid ``linspace(0, 1, N + 1)``; the model is queried at the first ``N`` points, ``t`` ascending.
|
| 21 |
+
|
| 22 |
+
``step`` integrates the velocity ``dz/dt`` over one grid interval in float32.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
order = 1
|
| 26 |
+
|
| 27 |
+
@register_to_config
|
| 28 |
+
def __init__(self):
|
| 29 |
+
self.grid: Optional[torch.Tensor] = None
|
| 30 |
+
self.num_inference_steps: Optional[int] = None
|
| 31 |
+
self.step_index = 0
|
| 32 |
+
|
| 33 |
+
@property
|
| 34 |
+
def timesteps(self) -> torch.Tensor:
|
| 35 |
+
return self.grid[:-1]
|
| 36 |
+
|
| 37 |
+
def set_timesteps(self, num_inference_steps: int, device: Optional[Union[str, torch.device]] = None) -> None:
|
| 38 |
+
if num_inference_steps < 1:
|
| 39 |
+
raise ValueError(f"num_inference_steps must be >= 1, got {num_inference_steps}")
|
| 40 |
+
self.num_inference_steps = num_inference_steps
|
| 41 |
+
self.grid = torch.linspace(0.0, 1.0, num_inference_steps + 1, device=device, dtype=torch.float32)
|
| 42 |
+
self.step_index = 0
|
| 43 |
+
|
| 44 |
+
def step(
|
| 45 |
+
self,
|
| 46 |
+
model_output: torch.Tensor,
|
| 47 |
+
timestep: Union[float, torch.Tensor],
|
| 48 |
+
sample: torch.Tensor,
|
| 49 |
+
return_dict: bool = True,
|
| 50 |
+
) -> Union[FlowBridgeSchedulerOutput, tuple]:
|
| 51 |
+
"""``z + (t_next - t) * v``; steps must follow ``timesteps`` in order."""
|
| 52 |
+
t, t_next = self.grid[self.step_index], self.grid[self.step_index + 1]
|
| 53 |
+
if float(timestep) != float(t):
|
| 54 |
+
raise ValueError(f"expected timestep {float(t)} at step {self.step_index}, got {float(timestep)}")
|
| 55 |
+
prev_sample = sample.float() + (t_next - t) * model_output.float()
|
| 56 |
+
self.step_index += 1
|
| 57 |
+
if not return_dict:
|
| 58 |
+
return (prev_sample,)
|
| 59 |
+
return FlowBridgeSchedulerOutput(prev_sample=prev_sample)
|