Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/loss/base.yaml +3 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/anyup.yaml +2 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/bilinear.yaml +2 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/featup.yaml +5 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/ircnn.yaml +5 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/jafar.yaml +10 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/jbf.yaml +5 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/jbu.yaml +5 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/naf.yaml +16 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/nearest.yaml +3 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/rednet.yaml +5 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/restormer.yaml +4 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/config/optimizer/adamw.yaml +4 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/DATASETS.md +25 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/EVALUATIONS.md +8 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/INSTALL.md +39 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/README.md +18 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/TRAINING.md +23 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/data.sh +51 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/ade20k.py +231 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/cityscapes.py +115 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/coco.py +317 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/davis.py +61 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/image_dataset.py +118 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/kitti360.py +196 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/kitti360/val_split.json +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/voc.py +113 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/eval_depth_probing.py +448 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/eval_seg_probing.py +393 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/eval_video_seg.py +816 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/hydra_plugins/resolvers.py +41 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/notebooks/attention_maps.ipynb +389 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/notebooks/inference.ipynb +294 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/backbone/__init__.py +1 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/backbone/vit_wrapper.py +180 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__init__.py +3 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/__init__.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/attentions.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/convolutions.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/rope.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/attentions.py +75 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/convolutions.py +92 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/rope.py +174 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/loss.py +45 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__init__.py +12 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/__init__.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/anyup.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/base.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/bilinear.cpython-311.pyc +0 -0
- Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/featup.cpython-311.pyc +0 -0
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/loss/base.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
mse:
|
| 2 |
+
_target_: src.loss.Loss
|
| 3 |
+
loss_type: mse
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/anyup.yaml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.AnyUpsampler
|
| 2 |
+
name: anyup
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/bilinear.yaml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.Bilinear
|
| 2 |
+
name: bilinear
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/featup.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.FeatUp
|
| 2 |
+
feature_dim: ${get_feature:${backbone.name}}
|
| 3 |
+
backbone_name: ${backbone.name}
|
| 4 |
+
name: featup
|
| 5 |
+
ratio: 16
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/ircnn.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.IRCNN
|
| 2 |
+
in_nc: 3
|
| 3 |
+
out_nc: 3
|
| 4 |
+
nc: 64
|
| 5 |
+
name: ircnn
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/jafar.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- _self_
|
| 3 |
+
|
| 4 |
+
_target_: src.model.JAFAR
|
| 5 |
+
dim: 128
|
| 6 |
+
v_dim: ${model.feature_dim}
|
| 7 |
+
feature_dim: ${get_feature:${backbone.name}}
|
| 8 |
+
kernel_size: 1
|
| 9 |
+
num_heads: 4
|
| 10 |
+
name: jafar
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/jbf.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.JBF
|
| 2 |
+
kernel_size: 5
|
| 3 |
+
sigma_color: 0.1
|
| 4 |
+
sigma_spatial: 1.5
|
| 5 |
+
name: jbf
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/jbu.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.JBU
|
| 2 |
+
dim: 256
|
| 3 |
+
radius: 5
|
| 4 |
+
combine: False
|
| 5 |
+
name: jbu
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/naf.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- _self_
|
| 3 |
+
|
| 4 |
+
_target_: src.model.naf.NAF
|
| 5 |
+
dim: 256
|
| 6 |
+
heads_attn: 4
|
| 7 |
+
heads_rope: 4
|
| 8 |
+
kernel_size: 9
|
| 9 |
+
|
| 10 |
+
# ImageEncoder options
|
| 11 |
+
use_semencoder: true
|
| 12 |
+
use_encoder: true
|
| 13 |
+
img_layers: 2
|
| 14 |
+
rope_rescale: 2
|
| 15 |
+
|
| 16 |
+
name: naf
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/nearest.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.Nearest
|
| 2 |
+
feature_dim: ${get_feature:${backbone.name}}
|
| 3 |
+
name: nearest
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/rednet.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.REDNet
|
| 2 |
+
num_layers: 15
|
| 3 |
+
num_features: 64
|
| 4 |
+
input_dim: 3
|
| 5 |
+
name: dncnn
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/model/restormer.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: src.model.Restormer
|
| 2 |
+
inp_channels: 3
|
| 3 |
+
out_channels: 3
|
| 4 |
+
name: restormer
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/config/optimizer/adamw.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: torch.optim.AdamW
|
| 2 |
+
lr: 2e-4
|
| 3 |
+
betas: [0.9, 0.999]
|
| 4 |
+
weight_decay: 1e-5
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/DATASETS.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Datasets
|
| 2 |
+
|
| 3 |
+
Create a data folder corresponding to the root data directory represented in the configuration file by `data_root: data/`.
|
| 4 |
+
|
| 5 |
+
We train NAF using the ImageNet dataset, but it can be trained on any dataset of natural images. Then we evaluate NAF on several downstream tasks using the following datasets:
|
| 6 |
+
- Semantic Segmentation: Cityscapes, PASCAL VOC 2012, ADE20K, KITTI-360
|
| 7 |
+
- Depth Estimation: NYU Depth V2
|
| 8 |
+
- Video Object Segmentation: DAVIS 2017
|
| 9 |
+
|
| 10 |
+
To download the datasets used for training and evaluation, please refer to the [docs/data.sh](data.sh) file or see the official dataset documentation.
|
| 11 |
+
|
| 12 |
+
The data folder should have the following structure:
|
| 13 |
+
|
| 14 |
+
```
|
| 15 |
+
data/
|
| 16 |
+
(training)
|
| 17 |
+
└── ImageNet/
|
| 18 |
+
(evaluation)
|
| 19 |
+
├── cityscapes/
|
| 20 |
+
├── VOCdevkit/
|
| 21 |
+
├── ADEChallengeData2016/
|
| 22 |
+
├── KITTI-360/
|
| 23 |
+
├── DAVIS/
|
| 24 |
+
└── ...
|
| 25 |
+
```
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/EVALUATIONS.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🔄 Evaluation
|
| 2 |
+
|
| 3 |
+
You should put the loaded weight file `naf_release.pth` in the `output/` folder.
|
| 4 |
+
|
| 5 |
+
To evaluate the upsampler on semantic segmentation, simply run:
|
| 6 |
+
``` python
|
| 7 |
+
python evaluation/eval_seg_probing.py dataset=voc eval.model_ckpt=output/naf_release.pth
|
| 8 |
+
```
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/INSTALL.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Installation Instructions
|
| 2 |
+
## Install Natten
|
| 3 |
+
NAF is based on natten. Natten is build for specific torch versions. You simply need to install the compatible natten version for your torch version. For instance:
|
| 4 |
+
|
| 5 |
+
```bash
|
| 6 |
+
# Version used for developping.
|
| 7 |
+
pip install natten==0.17.4+torch240cu118 -f https://shi-labs.com/natten/wheels/
|
| 8 |
+
```
|
| 9 |
+
For newer versions of torch or natten check this [url](https://github.com/SHI-Labs/NATTEN/blob/main/docs/install.md).
|
| 10 |
+
For older versions of torch or natten check this [url](https://shi-labs.com/natten/wheels/).
|
| 11 |
+
|
| 12 |
+
We have verified the following compatibilities:
|
| 13 |
+
```base
|
| 14 |
+
# For torch 2.7.0 + cu118
|
| 15 |
+
pip install torch==2.7.0 torchvision==0.22.0 --index-url https://download.pytorch.org/whl/cu128
|
| 16 |
+
pip install natten==0.20.1+torch270cu128 -f https://whl.natten.org
|
| 17 |
+
|
| 18 |
+
# For torch 2.4.0 + cu118
|
| 19 |
+
pip install torch==2.0.0 torchvision==0.15.1 --index-url https://download.pytorch.org/whl/cu118
|
| 20 |
+
pip install natten==0.17.3+torch200cu117 -f https://shi-labs.com/natten/wheels/
|
| 21 |
+
```
|
| 22 |
+
|
| 23 |
+
Do not hesitate to open an issue if you face any problem installing natten.
|
| 24 |
+
|
| 25 |
+
## Complete environment
|
| 26 |
+
We used the following environment for developping NAF. We recommend using [micromamba](https://mamba.readthedocs.io/en/latest/installation/micromamba-installation.html) to create the environment and using [uv](https://github.com/astral-sh/uv) to speed up pip installs.
|
| 27 |
+
|
| 28 |
+
``` bash
|
| 29 |
+
micromamba create -n naf python==3.10.14 -y -c defaults
|
| 30 |
+
micromamba activate naf
|
| 31 |
+
pip install uv
|
| 32 |
+
|
| 33 |
+
# Install torch and natten
|
| 34 |
+
uv pip install torch==2.4.0 torchvision==0.19.0 --index-url https://download.pytorch.org/whl/cu118
|
| 35 |
+
pip install natten==0.17.4+torch240cu118 -f https://shi-labs.com/natten/wheels/
|
| 36 |
+
|
| 37 |
+
# Install other dependencies
|
| 38 |
+
uv pip install einops==0.8.0 numpy==1.24.4 timm==1.0.22 plotly==6.0.0 tensorboard==2.20.0 hydra-core==1.3.2 plotly==6.0.0 matplotlib==3.7.0 rich==14.2.0 torchmetrics==1.6.2 scipy==1.15.2 kornia==0.8.2 ipykernel ipympl pytest
|
| 39 |
+
```
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/README.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 📁 Repository Structure
|
| 2 |
+
|
| 3 |
+
The repository contains the important folders:
|
| 4 |
+
```
|
| 5 |
+
|-- configs/ # Configuration files
|
| 6 |
+
|-- docs/ # Documentation
|
| 7 |
+
|-- evaluation/ # Scripts to reproduce results and datasets initialization
|
| 8 |
+
|-- notebooks/ # Jupyter notebooks for inference (of any VFM at any scale) and attention map visualizations
|
| 9 |
+
|-- src/ # Source code of the project
|
| 10 |
+
|-- test/ # Unit tests to compare model efficiency
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# 🔍 Tests
|
| 15 |
+
|
| 16 |
+
We provide unit tests to evaluate the efficiency of different model configurations, including forward/backward runtime, GPU memory usage, GFLOPs, and number of parameters.
|
| 17 |
+
All tests are available in the `test/` directory and we provide the test_results.json file with pre-computed results for reference computed on a 1 A100 40GB GPU.
|
| 18 |
+
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/TRAINING.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Reproducibility
|
| 2 |
+
|
| 3 |
+
⚠️ Before launching commands, please make sure to set the `dataroot` variable in [config/base.yaml](config/base.yaml) to the path where the datasets are stored.
|
| 4 |
+
|
| 5 |
+
General instructions:
|
| 6 |
+
- Change the VFM: add `model.backbone=<backbone_name>` to the command line (e.g. `model.backbone=franca_vitb16`).
|
| 7 |
+
- Change the dataset: add `dataset=<dataset_name>` to the command line (e.g. `dataset=cityscapes`).
|
| 8 |
+
|
| 9 |
+
For debugging, add `HYDRA_FULL_ERROR=1` before the command to get full stack traces.
|
| 10 |
+
|
| 11 |
+
# 🔄 Training
|
| 12 |
+
Training NAF take less than 2 hours consuming less than 8GB of GPU memory on a single NVIDIA A100.
|
| 13 |
+
|
| 14 |
+
To train the VFM-agnostic upsampler using NAF, simply run:
|
| 15 |
+
``` python
|
| 16 |
+
python train.py hydra.run.dir=output/naf
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
To train the denoising model using NAF, simply run:
|
| 20 |
+
``` python
|
| 21 |
+
python denoising.py denoising.noise_type=gaussian
|
| 22 |
+
```
|
| 23 |
+
You can select salt-and-pepper noise by setting `denoising.noise_type=salt_pepper`.
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/docs/data.sh
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ---------- COCOStuff ----------
|
| 2 |
+
# Follow
|
| 3 |
+
# https://github.com/nightrome/cocostuff
|
| 4 |
+
# Download everything
|
| 5 |
+
mkdir COCOStuff
|
| 6 |
+
cd COCOStuff
|
| 7 |
+
wget --directory-prefix=downloads http://images.cocodataset.org/zips/train2017.zip
|
| 8 |
+
wget --directory-prefix=downloads http://images.cocodataset.org/zips/val2017.zip
|
| 9 |
+
wget --directory-prefix=downloads http://calvin.inf.ed.ac.uk/wp-content/uploads/data/cocostuffdataset/stuffthingmaps_trainval2017.zip
|
| 10 |
+
|
| 11 |
+
# Unpack everything
|
| 12 |
+
mkdir -p dataset/images
|
| 13 |
+
mkdir -p dataset/annotations
|
| 14 |
+
unzip downloads/train2017.zip -d dataset/images/
|
| 15 |
+
unzip downloads/val2017.zip -d dataset/images/
|
| 16 |
+
unzip downloads/stuffthingmaps_trainval2017.zip -d dataset/annotations/
|
| 17 |
+
|
| 18 |
+
# https://github.com/xu-ji/IIC/blob/master/datasets/README.txt
|
| 19 |
+
wget https://www.robots.ox.ac.uk/~xuji/datasets/COCOStuff164kCurated.tar.gz
|
| 20 |
+
tar -xzf COCOStuff164kCurated.tar.gz
|
| 21 |
+
mv COCO/COCOStuff164k ./currated
|
| 22 |
+
rmdir COCO
|
| 23 |
+
rm COCOStuff164kCurated.tar.gz
|
| 24 |
+
|
| 25 |
+
# ---------- Cityscapes ----------
|
| 26 |
+
# https://github.com/cemsaz/city-scapes-script
|
| 27 |
+
wget --keep-session-cookies --save-cookies=cookies.txt --post-data 'username=XXXX&password=XXX&submit=Login' https://www.cityscapes-dataset.com/login/
|
| 28 |
+
wget --load-cookies cookies.txt --content-disposition https://www.cityscapes-dataset.com/file-handling/?packageID=1
|
| 29 |
+
wget --load-cookies cookies.txt --content-disposition https://www.cityscapes-dataset.com/file-handling/?packageID=3
|
| 30 |
+
unzip gtFine_trainvaltest.zip
|
| 31 |
+
unzip leftImg8bit_trainvaltest.zip
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
# ---------- VOC ----------
|
| 35 |
+
wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar
|
| 36 |
+
# generate folder VOCdevkit/VOC2012
|
| 37 |
+
tar -xvf VOCtrainval_11-May-2012.tar
|
| 38 |
+
|
| 39 |
+
# ---------- ADE20K ----------
|
| 40 |
+
wget http://data.csail.mit.edu/places/ADEchallenge/ADEChallengeData2016.zip
|
| 41 |
+
unzip ADEChallengeData2016.zip
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# ---------- DAVIS ----------
|
| 45 |
+
mkdir DAVIS
|
| 46 |
+
cd DAVIS
|
| 47 |
+
wget https://data.vision.ee.ethz.ch/csergi/share/davis/DAVIS-2017-trainval-480p.zip
|
| 48 |
+
unzip DAVIS-2017-trainval-480p.zip
|
| 49 |
+
rm DAVIS-2017-trainval-480p.zip
|
| 50 |
+
mv DAVIS/* ./
|
| 51 |
+
rmdir DAVIS
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/ade20k.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from PIL import Image
|
| 6 |
+
from torch.utils.data import Dataset
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class ADE20KDataset(Dataset):
|
| 10 |
+
split_to_dir = {"train": "training", "val": "validation"}
|
| 11 |
+
|
| 12 |
+
def __init__(
|
| 13 |
+
self,
|
| 14 |
+
root,
|
| 15 |
+
transform,
|
| 16 |
+
target_transform,
|
| 17 |
+
split="train",
|
| 18 |
+
skip_other_class=False,
|
| 19 |
+
file_set=None,
|
| 20 |
+
num_classes=None,
|
| 21 |
+
tag=None,
|
| 22 |
+
):
|
| 23 |
+
super().__init__()
|
| 24 |
+
self.transform = transform
|
| 25 |
+
self.target_transform = target_transform
|
| 26 |
+
self.split = split
|
| 27 |
+
self.root = root
|
| 28 |
+
self.skip_other_class = skip_other_class
|
| 29 |
+
self.file_set = file_set
|
| 30 |
+
|
| 31 |
+
# Collect the data
|
| 32 |
+
self.data = self.collect_data()
|
| 33 |
+
|
| 34 |
+
if split == "train":
|
| 35 |
+
assert self.__len__() == 20210
|
| 36 |
+
elif split == "val":
|
| 37 |
+
assert self.__len__() == 2000
|
| 38 |
+
|
| 39 |
+
def get_class_names(self):
|
| 40 |
+
return [
|
| 41 |
+
"background",
|
| 42 |
+
"wall",
|
| 43 |
+
"building",
|
| 44 |
+
"sky",
|
| 45 |
+
"floor",
|
| 46 |
+
"tree",
|
| 47 |
+
"ceiling",
|
| 48 |
+
"road",
|
| 49 |
+
"bed ",
|
| 50 |
+
"windowpane",
|
| 51 |
+
"grass",
|
| 52 |
+
"cabinet",
|
| 53 |
+
"sidewalk",
|
| 54 |
+
"person",
|
| 55 |
+
"earth",
|
| 56 |
+
"door",
|
| 57 |
+
"table",
|
| 58 |
+
"mountain",
|
| 59 |
+
"plant",
|
| 60 |
+
"curtain",
|
| 61 |
+
"chair",
|
| 62 |
+
"car",
|
| 63 |
+
"water",
|
| 64 |
+
"painting",
|
| 65 |
+
"sofa",
|
| 66 |
+
"shelf",
|
| 67 |
+
"house",
|
| 68 |
+
"sea",
|
| 69 |
+
"mirror",
|
| 70 |
+
"rug",
|
| 71 |
+
"field",
|
| 72 |
+
"armchair",
|
| 73 |
+
"seat",
|
| 74 |
+
"fence",
|
| 75 |
+
"desk",
|
| 76 |
+
"rock",
|
| 77 |
+
"wardrobe",
|
| 78 |
+
"lamp",
|
| 79 |
+
"bathtub",
|
| 80 |
+
"railing",
|
| 81 |
+
"cushion",
|
| 82 |
+
"base",
|
| 83 |
+
"box",
|
| 84 |
+
"column",
|
| 85 |
+
"signboard",
|
| 86 |
+
"chest of drawers",
|
| 87 |
+
"counter",
|
| 88 |
+
"sand",
|
| 89 |
+
"sink",
|
| 90 |
+
"skyscraper",
|
| 91 |
+
"fireplace",
|
| 92 |
+
"refrigerator",
|
| 93 |
+
"grandstand",
|
| 94 |
+
"path",
|
| 95 |
+
"stairs",
|
| 96 |
+
"runway",
|
| 97 |
+
"case",
|
| 98 |
+
"pool table",
|
| 99 |
+
"pillow",
|
| 100 |
+
"screen door",
|
| 101 |
+
"stairway",
|
| 102 |
+
"river",
|
| 103 |
+
"bridge",
|
| 104 |
+
"bookcase",
|
| 105 |
+
"blind",
|
| 106 |
+
"coffee table",
|
| 107 |
+
"toilet",
|
| 108 |
+
"flower",
|
| 109 |
+
"book",
|
| 110 |
+
"hill",
|
| 111 |
+
"bench",
|
| 112 |
+
"countertop",
|
| 113 |
+
"stove",
|
| 114 |
+
"palm",
|
| 115 |
+
"kitchen island",
|
| 116 |
+
"computer",
|
| 117 |
+
"swivel chair",
|
| 118 |
+
"boat",
|
| 119 |
+
"bar",
|
| 120 |
+
"arcade machine",
|
| 121 |
+
"hovel",
|
| 122 |
+
"bus",
|
| 123 |
+
"towel",
|
| 124 |
+
"light",
|
| 125 |
+
"truck",
|
| 126 |
+
"tower",
|
| 127 |
+
"chandelier",
|
| 128 |
+
"awning",
|
| 129 |
+
"streetlight",
|
| 130 |
+
"booth",
|
| 131 |
+
"television receiver",
|
| 132 |
+
"airplane",
|
| 133 |
+
"dirt track",
|
| 134 |
+
"apparel",
|
| 135 |
+
"pole",
|
| 136 |
+
"land",
|
| 137 |
+
"bannister",
|
| 138 |
+
"escalator",
|
| 139 |
+
"ottoman",
|
| 140 |
+
"bottle",
|
| 141 |
+
"buffet",
|
| 142 |
+
"poster",
|
| 143 |
+
"stage",
|
| 144 |
+
"van",
|
| 145 |
+
"ship",
|
| 146 |
+
"fountain",
|
| 147 |
+
"conveyer belt",
|
| 148 |
+
"canopy",
|
| 149 |
+
"washer",
|
| 150 |
+
"plaything",
|
| 151 |
+
"swimming pool",
|
| 152 |
+
"stool",
|
| 153 |
+
"barrel",
|
| 154 |
+
"basket",
|
| 155 |
+
"waterfall",
|
| 156 |
+
"tent",
|
| 157 |
+
"bag",
|
| 158 |
+
"minibike",
|
| 159 |
+
"cradle",
|
| 160 |
+
"oven",
|
| 161 |
+
"ball",
|
| 162 |
+
"food",
|
| 163 |
+
"step",
|
| 164 |
+
"tank",
|
| 165 |
+
"trade name",
|
| 166 |
+
"microwave",
|
| 167 |
+
"pot",
|
| 168 |
+
"animal",
|
| 169 |
+
"bicycle",
|
| 170 |
+
"lake",
|
| 171 |
+
"dishwasher",
|
| 172 |
+
"screen",
|
| 173 |
+
"blanket",
|
| 174 |
+
"sculpture",
|
| 175 |
+
"hood",
|
| 176 |
+
"sconce",
|
| 177 |
+
"vase",
|
| 178 |
+
"traffic light",
|
| 179 |
+
"tray",
|
| 180 |
+
"ashcan",
|
| 181 |
+
"fan",
|
| 182 |
+
"pier",
|
| 183 |
+
"crt screen",
|
| 184 |
+
"plate",
|
| 185 |
+
"monitor",
|
| 186 |
+
"bulletin board",
|
| 187 |
+
"shower",
|
| 188 |
+
"radiator",
|
| 189 |
+
"glass",
|
| 190 |
+
"clock",
|
| 191 |
+
"flag",
|
| 192 |
+
]
|
| 193 |
+
|
| 194 |
+
def collect_data(self):
|
| 195 |
+
# Get the image and annotation dirs
|
| 196 |
+
image_dir = os.path.join(self.root, f"images/{self.split_to_dir[self.split]}")
|
| 197 |
+
annotation_dir = os.path.join(self.root, f"annotations/{self.split_to_dir[self.split]}")
|
| 198 |
+
|
| 199 |
+
# Collect the filepaths
|
| 200 |
+
if self.file_set is None:
|
| 201 |
+
image_paths = [os.path.join(image_dir, f) for f in sorted(os.listdir(image_dir))]
|
| 202 |
+
annotation_paths = [os.path.join(annotation_dir, f) for f in sorted(os.listdir(annotation_dir))]
|
| 203 |
+
else:
|
| 204 |
+
image_paths = [os.path.join(image_dir, f"{f}.jpg") for f in sorted(self.file_set)]
|
| 205 |
+
annotation_paths = [os.path.join(annotation_dir, f"{f}.png") for f in sorted(self.file_set)]
|
| 206 |
+
|
| 207 |
+
data = list(zip(image_paths, annotation_paths))
|
| 208 |
+
return data
|
| 209 |
+
|
| 210 |
+
def __len__(self):
|
| 211 |
+
return len(self.data)
|
| 212 |
+
|
| 213 |
+
def __getitem__(self, index):
|
| 214 |
+
# Get the paths
|
| 215 |
+
image_path, annotation_path = self.data[index]
|
| 216 |
+
|
| 217 |
+
# Load
|
| 218 |
+
image = Image.open(image_path).convert("RGB")
|
| 219 |
+
target = Image.open(annotation_path)
|
| 220 |
+
|
| 221 |
+
# Augment
|
| 222 |
+
image = self.transform(image).squeeze(0)
|
| 223 |
+
target = self.target_transform(target)
|
| 224 |
+
if self.skip_other_class == True:
|
| 225 |
+
target = target * 255.0
|
| 226 |
+
target[target.type(torch.int64) == 0] = 255.0
|
| 227 |
+
target /= 255.0
|
| 228 |
+
target = target.squeeze(0)
|
| 229 |
+
|
| 230 |
+
batch = {"image": image, "label": target}
|
| 231 |
+
return batch
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/cityscapes.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
from torchvision.datasets import Cityscapes
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class CityscapesDataset(Dataset):
|
| 12 |
+
BASE_DIR = "cityscapes"
|
| 13 |
+
NUM_CLASS = 19
|
| 14 |
+
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
root,
|
| 18 |
+
split="train",
|
| 19 |
+
transform=None,
|
| 20 |
+
target_transform=None,
|
| 21 |
+
include_labels=True,
|
| 22 |
+
num_classes=19,
|
| 23 |
+
tag=None,
|
| 24 |
+
**kwargs
|
| 25 |
+
):
|
| 26 |
+
super().__init__()
|
| 27 |
+
self.root = root
|
| 28 |
+
self.split = split
|
| 29 |
+
self.transform = transform
|
| 30 |
+
self.target_transform = target_transform
|
| 31 |
+
self.include_labels = include_labels
|
| 32 |
+
self.num_classes = num_classes
|
| 33 |
+
self.tag = tag
|
| 34 |
+
|
| 35 |
+
# fmt: off
|
| 36 |
+
self._key = np.array([-1, -1, -1, -1, -1, -1,
|
| 37 |
+
-1, -1, 0, 1, -1, -1,
|
| 38 |
+
2, 3, 4, -1, -1, -1,
|
| 39 |
+
5, -1, 6, 7, 8, 9,
|
| 40 |
+
10, 11, 12, 13, 14, 15,
|
| 41 |
+
-1, -1, 16, 17, 18])
|
| 42 |
+
self._mapping = np.array(range(-1, len(self._key) - 1)).astype("int32")
|
| 43 |
+
# fmt: on
|
| 44 |
+
|
| 45 |
+
# Get image/mask path pairs
|
| 46 |
+
self.cityscapes_dataset = Cityscapes(
|
| 47 |
+
root=root,
|
| 48 |
+
split=split,
|
| 49 |
+
mode="fine",
|
| 50 |
+
target_type=["instance", "semantic"],
|
| 51 |
+
transform=None,
|
| 52 |
+
target_transform=None,
|
| 53 |
+
)
|
| 54 |
+
# fmt: on
|
| 55 |
+
|
| 56 |
+
if split == "train":
|
| 57 |
+
assert self.__len__() == 2975
|
| 58 |
+
elif split == "val":
|
| 59 |
+
assert self.__len__() == 500
|
| 60 |
+
|
| 61 |
+
def get_class_names(
|
| 62 |
+
self,
|
| 63 |
+
):
|
| 64 |
+
return [
|
| 65 |
+
"road",
|
| 66 |
+
"sidewalk",
|
| 67 |
+
"building",
|
| 68 |
+
"wall",
|
| 69 |
+
"fence",
|
| 70 |
+
"pole",
|
| 71 |
+
"traffic_light",
|
| 72 |
+
"traffic_sign",
|
| 73 |
+
"vegetation",
|
| 74 |
+
"terrain",
|
| 75 |
+
"sky",
|
| 76 |
+
"person",
|
| 77 |
+
"rider",
|
| 78 |
+
"car",
|
| 79 |
+
"truck",
|
| 80 |
+
"bus",
|
| 81 |
+
"train",
|
| 82 |
+
"motorcycle",
|
| 83 |
+
"bicycle",
|
| 84 |
+
]
|
| 85 |
+
|
| 86 |
+
def __len__(self):
|
| 87 |
+
return len(self.cityscapes_dataset)
|
| 88 |
+
|
| 89 |
+
def __getitem__(self, index):
|
| 90 |
+
batch = {}
|
| 91 |
+
|
| 92 |
+
# Load image
|
| 93 |
+
data = self.cityscapes_dataset[index]
|
| 94 |
+
img = data[0]
|
| 95 |
+
mask = data[1][1]
|
| 96 |
+
|
| 97 |
+
if self.transform:
|
| 98 |
+
img = self.transform(img)
|
| 99 |
+
batch["image"] = img
|
| 100 |
+
|
| 101 |
+
if self.include_labels:
|
| 102 |
+
if self.target_transform:
|
| 103 |
+
mask = self.target_transform(mask)
|
| 104 |
+
# Convert to tensor and match COCO/ADE20K format
|
| 105 |
+
mask = torch.from_numpy(self._class_to_index(np.array(mask))).to(torch.uint8)
|
| 106 |
+
batch["label"] = mask.squeeze()
|
| 107 |
+
|
| 108 |
+
return batch
|
| 109 |
+
|
| 110 |
+
def _class_to_index(self, mask):
|
| 111 |
+
values = np.unique(mask)
|
| 112 |
+
for value in values:
|
| 113 |
+
assert value in self._mapping
|
| 114 |
+
index = np.digitize(mask.ravel(), self._mapping, right=True)
|
| 115 |
+
return self._key[index].reshape(mask.shape).astype(np.int64)
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/coco.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from os.path import join
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
import torch.multiprocessing
|
| 7 |
+
from PIL import Image
|
| 8 |
+
from torch.utils.data import Dataset
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def bit_get(val, idx):
|
| 12 |
+
"""Gets the bit value.
|
| 13 |
+
Args:
|
| 14 |
+
val: Input value, int or numpy int array.
|
| 15 |
+
idx: Which bit of the input val.
|
| 16 |
+
Returns:
|
| 17 |
+
The "idx"-th bit of input val.
|
| 18 |
+
"""
|
| 19 |
+
return (val >> idx) & 1
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def create_pascal_label_colormap():
|
| 23 |
+
"""Creates a label colormap used in PASCAL VOC segmentation benchmark.
|
| 24 |
+
Returns:
|
| 25 |
+
A colormap for visualizing segmentation results.
|
| 26 |
+
"""
|
| 27 |
+
colormap = np.zeros((512, 3), dtype=int)
|
| 28 |
+
ind = np.arange(512, dtype=int)
|
| 29 |
+
|
| 30 |
+
for shift in reversed(list(range(8))):
|
| 31 |
+
for channel in range(3):
|
| 32 |
+
colormap[:, channel] |= bit_get(ind, channel) << shift
|
| 33 |
+
ind >>= 3
|
| 34 |
+
|
| 35 |
+
return colormap
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class COCODataset(Dataset):
|
| 39 |
+
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
root,
|
| 43 |
+
split,
|
| 44 |
+
transform,
|
| 45 |
+
target_transform,
|
| 46 |
+
include_labels=True,
|
| 47 |
+
coarse_labels=False,
|
| 48 |
+
exclude_things=False,
|
| 49 |
+
subset=None,
|
| 50 |
+
num_classes=None,
|
| 51 |
+
tag=None,
|
| 52 |
+
):
|
| 53 |
+
super().__init__()
|
| 54 |
+
self.split = split
|
| 55 |
+
self.root = root
|
| 56 |
+
self.coarse_labels = coarse_labels
|
| 57 |
+
self.transform = transform
|
| 58 |
+
self.label_transform = target_transform
|
| 59 |
+
self.subset = subset
|
| 60 |
+
self.exclude_things = exclude_things
|
| 61 |
+
self.include_labels = include_labels
|
| 62 |
+
|
| 63 |
+
if self.subset is None:
|
| 64 |
+
self.image_list = "Coco164kFull_Stuff_Coarse.txt"
|
| 65 |
+
elif self.subset == 6: # IIC Coarse
|
| 66 |
+
self.image_list = "Coco164kFew_Stuff_6.txt"
|
| 67 |
+
elif self.subset == 7: # IIC Fine
|
| 68 |
+
self.image_list = "Coco164kFull_Stuff_Coarse_7.txt"
|
| 69 |
+
|
| 70 |
+
assert self.split in ["train", "val", "train+val"]
|
| 71 |
+
split_dirs = {
|
| 72 |
+
"train": ["train2017"],
|
| 73 |
+
"val": ["val2017"],
|
| 74 |
+
"train+val": ["train2017", "val2017"],
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
self.image_files = []
|
| 78 |
+
self.label_files = []
|
| 79 |
+
for split_dir in split_dirs[self.split]:
|
| 80 |
+
with open(join(self.root, "curated", split_dir, self.image_list), "r") as f:
|
| 81 |
+
img_ids = [fn.rstrip() for fn in f.readlines()]
|
| 82 |
+
for img_id in img_ids:
|
| 83 |
+
self.image_files.append(join(self.root, "images", split_dir, img_id + ".jpg"))
|
| 84 |
+
self.label_files.append(join(self.root, "annotations", split_dir, img_id + ".png"))
|
| 85 |
+
|
| 86 |
+
self.fine_to_coarse = {
|
| 87 |
+
0: 9,
|
| 88 |
+
1: 11,
|
| 89 |
+
2: 11,
|
| 90 |
+
3: 11,
|
| 91 |
+
4: 11,
|
| 92 |
+
5: 11,
|
| 93 |
+
6: 11,
|
| 94 |
+
7: 11,
|
| 95 |
+
8: 11,
|
| 96 |
+
9: 8,
|
| 97 |
+
10: 8,
|
| 98 |
+
11: 8,
|
| 99 |
+
12: 8,
|
| 100 |
+
13: 8,
|
| 101 |
+
14: 8,
|
| 102 |
+
15: 7,
|
| 103 |
+
16: 7,
|
| 104 |
+
17: 7,
|
| 105 |
+
18: 7,
|
| 106 |
+
19: 7,
|
| 107 |
+
20: 7,
|
| 108 |
+
21: 7,
|
| 109 |
+
22: 7,
|
| 110 |
+
23: 7,
|
| 111 |
+
24: 7,
|
| 112 |
+
25: 6,
|
| 113 |
+
26: 6,
|
| 114 |
+
27: 6,
|
| 115 |
+
28: 6,
|
| 116 |
+
29: 6,
|
| 117 |
+
30: 6,
|
| 118 |
+
31: 6,
|
| 119 |
+
32: 6,
|
| 120 |
+
33: 10,
|
| 121 |
+
34: 10,
|
| 122 |
+
35: 10,
|
| 123 |
+
36: 10,
|
| 124 |
+
37: 10,
|
| 125 |
+
38: 10,
|
| 126 |
+
39: 10,
|
| 127 |
+
40: 10,
|
| 128 |
+
41: 10,
|
| 129 |
+
42: 10,
|
| 130 |
+
43: 5,
|
| 131 |
+
44: 5,
|
| 132 |
+
45: 5,
|
| 133 |
+
46: 5,
|
| 134 |
+
47: 5,
|
| 135 |
+
48: 5,
|
| 136 |
+
49: 5,
|
| 137 |
+
50: 5,
|
| 138 |
+
51: 2,
|
| 139 |
+
52: 2,
|
| 140 |
+
53: 2,
|
| 141 |
+
54: 2,
|
| 142 |
+
55: 2,
|
| 143 |
+
56: 2,
|
| 144 |
+
57: 2,
|
| 145 |
+
58: 2,
|
| 146 |
+
59: 2,
|
| 147 |
+
60: 2,
|
| 148 |
+
61: 3,
|
| 149 |
+
62: 3,
|
| 150 |
+
63: 3,
|
| 151 |
+
64: 3,
|
| 152 |
+
65: 3,
|
| 153 |
+
66: 3,
|
| 154 |
+
67: 3,
|
| 155 |
+
68: 3,
|
| 156 |
+
69: 3,
|
| 157 |
+
70: 3,
|
| 158 |
+
71: 0,
|
| 159 |
+
72: 0,
|
| 160 |
+
73: 0,
|
| 161 |
+
74: 0,
|
| 162 |
+
75: 0,
|
| 163 |
+
76: 0,
|
| 164 |
+
77: 1,
|
| 165 |
+
78: 1,
|
| 166 |
+
79: 1,
|
| 167 |
+
80: 1,
|
| 168 |
+
81: 1,
|
| 169 |
+
82: 1,
|
| 170 |
+
83: 4,
|
| 171 |
+
84: 4,
|
| 172 |
+
85: 4,
|
| 173 |
+
86: 4,
|
| 174 |
+
87: 4,
|
| 175 |
+
88: 4,
|
| 176 |
+
89: 4,
|
| 177 |
+
90: 4,
|
| 178 |
+
91: 17,
|
| 179 |
+
92: 17,
|
| 180 |
+
93: 22,
|
| 181 |
+
94: 20,
|
| 182 |
+
95: 20,
|
| 183 |
+
96: 22,
|
| 184 |
+
97: 15,
|
| 185 |
+
98: 25,
|
| 186 |
+
99: 16,
|
| 187 |
+
100: 13,
|
| 188 |
+
101: 12,
|
| 189 |
+
102: 12,
|
| 190 |
+
103: 17,
|
| 191 |
+
104: 17,
|
| 192 |
+
105: 23,
|
| 193 |
+
106: 15,
|
| 194 |
+
107: 15,
|
| 195 |
+
108: 17,
|
| 196 |
+
109: 15,
|
| 197 |
+
110: 21,
|
| 198 |
+
111: 15,
|
| 199 |
+
112: 25,
|
| 200 |
+
113: 13,
|
| 201 |
+
114: 13,
|
| 202 |
+
115: 13,
|
| 203 |
+
116: 13,
|
| 204 |
+
117: 13,
|
| 205 |
+
118: 22,
|
| 206 |
+
119: 26,
|
| 207 |
+
120: 14,
|
| 208 |
+
121: 14,
|
| 209 |
+
122: 15,
|
| 210 |
+
123: 22,
|
| 211 |
+
124: 21,
|
| 212 |
+
125: 21,
|
| 213 |
+
126: 24,
|
| 214 |
+
127: 20,
|
| 215 |
+
128: 22,
|
| 216 |
+
129: 15,
|
| 217 |
+
130: 17,
|
| 218 |
+
131: 16,
|
| 219 |
+
132: 15,
|
| 220 |
+
133: 22,
|
| 221 |
+
134: 24,
|
| 222 |
+
135: 21,
|
| 223 |
+
136: 17,
|
| 224 |
+
137: 25,
|
| 225 |
+
138: 16,
|
| 226 |
+
139: 21,
|
| 227 |
+
140: 17,
|
| 228 |
+
141: 22,
|
| 229 |
+
142: 16,
|
| 230 |
+
143: 21,
|
| 231 |
+
144: 21,
|
| 232 |
+
145: 25,
|
| 233 |
+
146: 21,
|
| 234 |
+
147: 26,
|
| 235 |
+
148: 21,
|
| 236 |
+
149: 24,
|
| 237 |
+
150: 20,
|
| 238 |
+
151: 17,
|
| 239 |
+
152: 14,
|
| 240 |
+
153: 21,
|
| 241 |
+
154: 26,
|
| 242 |
+
155: 15,
|
| 243 |
+
156: 23,
|
| 244 |
+
157: 20,
|
| 245 |
+
158: 21,
|
| 246 |
+
159: 24,
|
| 247 |
+
160: 15,
|
| 248 |
+
161: 24,
|
| 249 |
+
162: 22,
|
| 250 |
+
163: 25,
|
| 251 |
+
164: 15,
|
| 252 |
+
165: 20,
|
| 253 |
+
166: 17,
|
| 254 |
+
167: 17,
|
| 255 |
+
168: 22,
|
| 256 |
+
169: 14,
|
| 257 |
+
170: 18,
|
| 258 |
+
171: 18,
|
| 259 |
+
172: 18,
|
| 260 |
+
173: 18,
|
| 261 |
+
174: 18,
|
| 262 |
+
175: 18,
|
| 263 |
+
176: 18,
|
| 264 |
+
177: 26,
|
| 265 |
+
178: 26,
|
| 266 |
+
179: 19,
|
| 267 |
+
180: 19,
|
| 268 |
+
181: 24,
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
self._label_names = [
|
| 272 |
+
"ground-stuff",
|
| 273 |
+
"plant-stuff",
|
| 274 |
+
"sky-stuff",
|
| 275 |
+
]
|
| 276 |
+
self.cocostuff3_coarse_classes = [23, 22, 21]
|
| 277 |
+
self.first_stuff_index = 12
|
| 278 |
+
if split == "train":
|
| 279 |
+
assert self.__len__() == 97702
|
| 280 |
+
elif split == "val":
|
| 281 |
+
assert self.__len__() == 4172
|
| 282 |
+
|
| 283 |
+
def __len__(self):
|
| 284 |
+
return len(self.image_files)
|
| 285 |
+
|
| 286 |
+
def __getitem__(self, index):
|
| 287 |
+
image_path = self.image_files[index]
|
| 288 |
+
label_path = self.label_files[index]
|
| 289 |
+
batch = {}
|
| 290 |
+
|
| 291 |
+
img = self.transform(Image.open(image_path).convert("RGB"))
|
| 292 |
+
batch["image"] = img
|
| 293 |
+
batch["img_path"] = image_path
|
| 294 |
+
|
| 295 |
+
if self.include_labels:
|
| 296 |
+
label = self.label_transform(Image.open(label_path)).squeeze(0)
|
| 297 |
+
label[label == 255] = -1 # to be consistent with 10k
|
| 298 |
+
batch["label"] = label
|
| 299 |
+
|
| 300 |
+
coarse_label = torch.zeros_like(label)
|
| 301 |
+
for fine, coarse in self.fine_to_coarse.items():
|
| 302 |
+
coarse_label[label == fine] = coarse
|
| 303 |
+
coarse_label[label == -1] = -1
|
| 304 |
+
|
| 305 |
+
if self.coarse_labels:
|
| 306 |
+
coarser_labels = -torch.ones_like(label)
|
| 307 |
+
for i, c in enumerate(self.cocostuff3_coarse_classes):
|
| 308 |
+
coarser_labels[coarse_label == c] = i
|
| 309 |
+
batch["label"] = coarser_labels
|
| 310 |
+
else:
|
| 311 |
+
if self.exclude_things:
|
| 312 |
+
batch["label"] = coarse_label - self.first_stuff_index
|
| 313 |
+
else:
|
| 314 |
+
batch["label"] = coarse_label
|
| 315 |
+
|
| 316 |
+
batch["label"] = batch["label"].to(torch.uint8)
|
| 317 |
+
return batch
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/davis.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import glob
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from PIL import Image
|
| 6 |
+
from torch.utils.data import Dataset
|
| 7 |
+
from torchvision import transforms
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class DAVIS(Dataset):
|
| 11 |
+
def __init__(self, root, split="train", transform=None, target_transform=None, tag=None):
|
| 12 |
+
"""
|
| 13 |
+
Args:
|
| 14 |
+
root (string): Directory with all the videos.
|
| 15 |
+
transform (callable, optional): Optional transform to be applied on a sample.
|
| 16 |
+
"""
|
| 17 |
+
videos = self.load_split(root, split)
|
| 18 |
+
|
| 19 |
+
frames = []
|
| 20 |
+
for video in videos:
|
| 21 |
+
video_path = os.path.join(root, f"JPEGImages/480p/{video}/*/*.jpg")
|
| 22 |
+
video_frames = sorted(glob.glob(video_path))
|
| 23 |
+
frames += video_frames
|
| 24 |
+
self.frames = frames
|
| 25 |
+
self.transform = transform
|
| 26 |
+
self.target_transform = target_transform
|
| 27 |
+
self.tag = tag
|
| 28 |
+
|
| 29 |
+
def load_split(self, root, split):
|
| 30 |
+
"""
|
| 31 |
+
Load the split files for DAVIS dataset.
|
| 32 |
+
Args:
|
| 33 |
+
root (string): Directory with all the videos.
|
| 34 |
+
split (string): Split name (train, val, test).
|
| 35 |
+
Returns:
|
| 36 |
+
list: List of video paths.
|
| 37 |
+
"""
|
| 38 |
+
split_path = os.path.join(root, "ImageSets/2017/", f"{split}.txt")
|
| 39 |
+
with open(split_path, "r") as f:
|
| 40 |
+
videos = [line.strip() for line in f.readlines()]
|
| 41 |
+
return videos
|
| 42 |
+
|
| 43 |
+
def __len__(self):
|
| 44 |
+
return len(self.frames)
|
| 45 |
+
|
| 46 |
+
def __getitem__(self, index):
|
| 47 |
+
image_path = self.frames[index]
|
| 48 |
+
annotation_path = image_path.replace("JPEGImages", "Annotations")
|
| 49 |
+
batch = {}
|
| 50 |
+
|
| 51 |
+
# Load
|
| 52 |
+
image = Image.open(image_path).convert("RGB")
|
| 53 |
+
target = Image.open(annotation_path)
|
| 54 |
+
|
| 55 |
+
# Augment
|
| 56 |
+
image = self.transform(image).squeeze(0)
|
| 57 |
+
target = self.target_transform(target)
|
| 58 |
+
target = target.squeeze(0)
|
| 59 |
+
|
| 60 |
+
batch = {"image": image, "label": target}
|
| 61 |
+
return batch
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/image_dataset.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torchvision.transforms as T
|
| 6 |
+
from torchvision.datasets import folder
|
| 7 |
+
from torchvision.datasets.vision import VisionDataset
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def remove_prefix(s, prefix):
|
| 11 |
+
if s.startswith(prefix):
|
| 12 |
+
s = s[len(prefix) :]
|
| 13 |
+
return s
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class ImageDataset(VisionDataset):
|
| 17 |
+
"""
|
| 18 |
+
modified from: https://pytorch.org/docs/stable/_modules/torchvision/datasets/folder.html#ImageFolder
|
| 19 |
+
uses cached directory listing if available rather than walking directory
|
| 20 |
+
Attributes:
|
| 21 |
+
classes (list): List of the class names.
|
| 22 |
+
class_to_idx (dict): Dict with items (class_name, class_index).
|
| 23 |
+
samples (list): List of (sample path, class_index) tuples
|
| 24 |
+
targets (list): The class_index value for each image in the dataset
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
def __init__(
|
| 28 |
+
self,
|
| 29 |
+
root,
|
| 30 |
+
root_cache,
|
| 31 |
+
loader=folder.default_loader,
|
| 32 |
+
extensions=folder.IMG_EXTENSIONS,
|
| 33 |
+
transform=None,
|
| 34 |
+
is_valid_file=None,
|
| 35 |
+
include_labels=False,
|
| 36 |
+
**kwargs,
|
| 37 |
+
):
|
| 38 |
+
super(ImageDataset, self).__init__(
|
| 39 |
+
root,
|
| 40 |
+
transform=transform,
|
| 41 |
+
)
|
| 42 |
+
self.root = root
|
| 43 |
+
classes, class_to_idx = self._find_classes(self.root)
|
| 44 |
+
if root_cache is None:
|
| 45 |
+
root_cache = root
|
| 46 |
+
cache = root_cache.rstrip("/") + ".txt"
|
| 47 |
+
print(cache)
|
| 48 |
+
# cache = './val_paths.txt'
|
| 49 |
+
if os.path.isfile(cache):
|
| 50 |
+
print("Using directory list at: %s" % cache)
|
| 51 |
+
with open(cache) as f:
|
| 52 |
+
samples = []
|
| 53 |
+
for line in f:
|
| 54 |
+
(path, idx) = line.strip().split(";")
|
| 55 |
+
samples.append((os.path.join(path), int(idx)))
|
| 56 |
+
else:
|
| 57 |
+
print("Walking directory: %s" % self.root)
|
| 58 |
+
samples = folder.make_dataset(self.root, class_to_idx, extensions, is_valid_file)
|
| 59 |
+
with open(cache, "w") as f:
|
| 60 |
+
for line in samples:
|
| 61 |
+
path, label = line
|
| 62 |
+
f.write("%s;%d\n" % (remove_prefix(path, self.root).lstrip("/"), label))
|
| 63 |
+
|
| 64 |
+
if len(samples) == 0:
|
| 65 |
+
raise (
|
| 66 |
+
RuntimeError(
|
| 67 |
+
"Found 0 files in subfolders of: " + self.root + "\n" "Supported extensions are: " + ",".join(extensions)
|
| 68 |
+
)
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
self.loader = loader
|
| 72 |
+
self.extensions = extensions
|
| 73 |
+
self.classes = classes
|
| 74 |
+
self.class_to_idx = class_to_idx
|
| 75 |
+
self.samples = samples
|
| 76 |
+
self.targets = [s[1] for s in samples]
|
| 77 |
+
self.transform = transform
|
| 78 |
+
self.include_labels = include_labels
|
| 79 |
+
|
| 80 |
+
def _find_classes(self, dir):
|
| 81 |
+
"""
|
| 82 |
+
Finds the class folders in a dataset.
|
| 83 |
+
Ensures:
|
| 84 |
+
No class is a subdirectory of another.
|
| 85 |
+
"""
|
| 86 |
+
if sys.version_info >= (3, 5):
|
| 87 |
+
# Faster and available in Python 3.5 and above
|
| 88 |
+
classes = [d.name for d in os.scandir(dir) if d.is_dir()]
|
| 89 |
+
else:
|
| 90 |
+
classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))]
|
| 91 |
+
classes.sort()
|
| 92 |
+
class_to_idx = {classes[i]: i for i in range(len(classes))}
|
| 93 |
+
return classes, class_to_idx
|
| 94 |
+
|
| 95 |
+
def __getitem__(self, index):
|
| 96 |
+
path, target = self.samples[index]
|
| 97 |
+
path = os.path.join(self.root, path)
|
| 98 |
+
sample = self.loader(path)
|
| 99 |
+
if self.transform is not None:
|
| 100 |
+
sample = self.transform(sample)
|
| 101 |
+
|
| 102 |
+
batch = {
|
| 103 |
+
"index": index,
|
| 104 |
+
"image": sample,
|
| 105 |
+
"target": target,
|
| 106 |
+
"path": path,
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
if self.include_labels:
|
| 110 |
+
target = self.targets[index]
|
| 111 |
+
if self.target_transform is not None:
|
| 112 |
+
target = self.target_transform(target)
|
| 113 |
+
batch["label"] = target
|
| 114 |
+
|
| 115 |
+
return batch
|
| 116 |
+
|
| 117 |
+
def __len__(self):
|
| 118 |
+
return len(self.samples)
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/kitti360.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
+
from collections import namedtuple
|
| 5 |
+
from typing import Optional
|
| 6 |
+
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
from PIL import Image
|
| 10 |
+
from torch.utils.data import Dataset
|
| 11 |
+
|
| 12 |
+
# https://github.com/autonomousvision/kitti360Scripts/blob/32f6d64eef27c32b52c542b4e95be8af9c9c6444/kitti360scripts/helpers/labels.py
|
| 13 |
+
# a label and all meta information
|
| 14 |
+
Label = namedtuple(
|
| 15 |
+
"Label",
|
| 16 |
+
["name", "id", "kittiId", "trainId", "category", "categoryId", "hasInstances", "ignoreInEval", "ignoreInInst", "color"],
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
labels = [
|
| 21 |
+
# name id kittiId, trainId category catId hasInstances ignoreInEval ignoreInInst color
|
| 22 |
+
Label("unlabeled", 0, -1, 255, "void", 0, False, True, True, (0, 0, 0)),
|
| 23 |
+
Label("ego vehicle", 1, -1, 255, "void", 0, False, True, True, (0, 0, 0)),
|
| 24 |
+
Label("rectification border", 2, -1, 255, "void", 0, False, True, True, (0, 0, 0)),
|
| 25 |
+
Label("out of roi", 3, -1, 255, "void", 0, False, True, True, (0, 0, 0)),
|
| 26 |
+
Label("static", 4, -1, 255, "void", 0, False, True, True, (0, 0, 0)),
|
| 27 |
+
Label("dynamic", 5, -1, 255, "void", 0, False, True, True, (111, 74, 0)),
|
| 28 |
+
Label("ground", 6, -1, 255, "void", 0, False, True, True, (81, 0, 81)),
|
| 29 |
+
Label("road", 7, 1, 0, "flat", 1, False, False, False, (128, 64, 128)),
|
| 30 |
+
Label("sidewalk", 8, 3, 1, "flat", 1, False, False, False, (244, 35, 232)),
|
| 31 |
+
Label("parking", 9, 2, 255, "flat", 1, False, True, True, (250, 170, 160)),
|
| 32 |
+
Label("rail track", 10, 10, 255, "flat", 1, False, True, True, (230, 150, 140)),
|
| 33 |
+
Label("building", 11, 11, 2, "construction", 2, True, False, False, (70, 70, 70)),
|
| 34 |
+
Label("wall", 12, 7, 3, "construction", 2, False, False, False, (102, 102, 156)),
|
| 35 |
+
Label("fence", 13, 8, 4, "construction", 2, False, False, False, (190, 153, 153)),
|
| 36 |
+
Label("guard rail", 14, 30, 255, "construction", 2, False, True, True, (180, 165, 180)),
|
| 37 |
+
Label("bridge", 15, 31, 255, "construction", 2, False, True, True, (150, 100, 100)),
|
| 38 |
+
Label("tunnel", 16, 32, 255, "construction", 2, False, True, True, (150, 120, 90)),
|
| 39 |
+
Label("pole", 17, 21, 5, "object", 3, True, False, True, (153, 153, 153)),
|
| 40 |
+
Label("polegroup", 18, -1, 255, "object", 3, False, True, True, (153, 153, 153)),
|
| 41 |
+
Label("traffic light", 19, 23, 6, "object", 3, True, False, True, (250, 170, 30)),
|
| 42 |
+
Label("traffic sign", 20, 24, 7, "object", 3, True, False, True, (220, 220, 0)),
|
| 43 |
+
Label("vegetation", 21, 5, 8, "nature", 4, False, False, False, (107, 142, 35)),
|
| 44 |
+
Label("terrain", 22, 4, 9, "nature", 4, False, False, False, (152, 251, 152)),
|
| 45 |
+
Label("sky", 23, 9, 10, "sky", 5, False, False, False, (70, 130, 180)),
|
| 46 |
+
Label("person", 24, 19, 11, "human", 6, True, False, False, (220, 20, 60)),
|
| 47 |
+
Label("rider", 25, 20, 12, "human", 6, True, False, False, (255, 0, 0)),
|
| 48 |
+
Label("car", 26, 13, 13, "vehicle", 7, True, False, False, (0, 0, 142)),
|
| 49 |
+
Label("truck", 27, 14, 14, "vehicle", 7, True, False, False, (0, 0, 70)),
|
| 50 |
+
Label("bus", 28, 34, 15, "vehicle", 7, True, False, False, (0, 60, 100)),
|
| 51 |
+
Label("caravan", 29, 16, 255, "vehicle", 7, True, True, True, (0, 0, 90)),
|
| 52 |
+
Label("trailer", 30, 15, 255, "vehicle", 7, True, True, True, (0, 0, 110)),
|
| 53 |
+
Label("train", 31, 33, 16, "vehicle", 7, True, False, False, (0, 80, 100)),
|
| 54 |
+
Label("motorcycle", 32, 17, 17, "vehicle", 7, True, False, False, (0, 0, 230)),
|
| 55 |
+
Label("bicycle", 33, 18, 18, "vehicle", 7, True, False, False, (119, 11, 32)),
|
| 56 |
+
Label("garage", 34, 12, 2, "construction", 2, True, True, True, (64, 128, 128)),
|
| 57 |
+
Label("gate", 35, 6, 4, "construction", 2, False, True, True, (190, 153, 153)),
|
| 58 |
+
Label("stop", 36, 29, 255, "construction", 2, True, True, True, (150, 120, 90)),
|
| 59 |
+
Label("smallpole", 37, 22, 5, "object", 3, True, True, True, (153, 153, 153)),
|
| 60 |
+
Label("lamp", 38, 25, 255, "object", 3, True, True, True, (0, 64, 64)),
|
| 61 |
+
Label("trash bin", 39, 26, 255, "object", 3, True, True, True, (0, 128, 192)),
|
| 62 |
+
Label("vending machine", 40, 27, 255, "object", 3, True, True, True, (128, 64, 0)),
|
| 63 |
+
Label("box", 41, 28, 255, "object", 3, True, True, True, (64, 64, 128)),
|
| 64 |
+
Label("unknown construction", 42, 35, 255, "void", 0, False, True, True, (102, 0, 0)),
|
| 65 |
+
Label("unknown vehicle", 43, 36, 255, "void", 0, False, True, True, (51, 0, 51)),
|
| 66 |
+
Label("unknown object", 44, 37, 255, "void", 0, False, True, True, (32, 32, 32)),
|
| 67 |
+
Label("license plate", -1, -1, -1, "vehicle", 7, False, True, True, (0, 0, 142)),
|
| 68 |
+
]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class KITTIDataset(Dataset):
|
| 72 |
+
def __init__(
|
| 73 |
+
self,
|
| 74 |
+
root: str,
|
| 75 |
+
split: str = "train",
|
| 76 |
+
transform: Optional[callable] = None,
|
| 77 |
+
target_transform: Optional[callable] = None,
|
| 78 |
+
include_labels: bool = True,
|
| 79 |
+
num_classes: int = 19,
|
| 80 |
+
tag: Optional[str] = None,
|
| 81 |
+
**kwargs,
|
| 82 |
+
):
|
| 83 |
+
super().__init__()
|
| 84 |
+
self.root = root
|
| 85 |
+
self.split = split
|
| 86 |
+
self.transform = transform
|
| 87 |
+
self.target_transform = target_transform
|
| 88 |
+
self.include_labels = include_labels
|
| 89 |
+
self.num_classes = num_classes
|
| 90 |
+
self.tag = tag
|
| 91 |
+
|
| 92 |
+
# Create mapping from id to trainId
|
| 93 |
+
self.id_to_trainid = {label.id: label.trainId for label in labels}
|
| 94 |
+
|
| 95 |
+
# Initialize paths
|
| 96 |
+
self.image_paths = []
|
| 97 |
+
self.label_paths = []
|
| 98 |
+
|
| 99 |
+
# Set up split management
|
| 100 |
+
self.split_file = os.path.join(os.path.dirname(__file__), "kitti360", f"{split}_split.json")
|
| 101 |
+
|
| 102 |
+
# Create split if needed
|
| 103 |
+
if not os.path.exists(self.split_file):
|
| 104 |
+
self._create_split()
|
| 105 |
+
|
| 106 |
+
# Load paths from split file
|
| 107 |
+
self._load_split()
|
| 108 |
+
|
| 109 |
+
def _create_split(self):
|
| 110 |
+
"""Create and save train/val split if it doesn't exist"""
|
| 111 |
+
# Collect all valid paths first
|
| 112 |
+
all_image_paths = []
|
| 113 |
+
all_label_paths = []
|
| 114 |
+
|
| 115 |
+
raw_base = os.path.join(self.root, "data_2d_raw")
|
| 116 |
+
sem_base = os.path.join(self.root, "data_2d_semantics", "train")
|
| 117 |
+
|
| 118 |
+
for drive in os.listdir(raw_base):
|
| 119 |
+
drive_path = os.path.join(raw_base, drive)
|
| 120 |
+
|
| 121 |
+
for camera in ["image_00"]:
|
| 122 |
+
img_dir = os.path.join(drive_path, camera, "data_rect")
|
| 123 |
+
lbl_dir = os.path.join(sem_base, drive, camera, "semantic")
|
| 124 |
+
|
| 125 |
+
if os.path.exists(img_dir) and os.path.exists(lbl_dir):
|
| 126 |
+
for fname in os.listdir(img_dir):
|
| 127 |
+
if fname.endswith(".png"):
|
| 128 |
+
base_name = os.path.splitext(fname)[0]
|
| 129 |
+
img_path = os.path.join(img_dir, fname)
|
| 130 |
+
lbl_path = os.path.join(lbl_dir, f"{base_name}.png")
|
| 131 |
+
|
| 132 |
+
if os.path.exists(lbl_path):
|
| 133 |
+
all_image_paths.append(img_path)
|
| 134 |
+
all_label_paths.append(lbl_path)
|
| 135 |
+
|
| 136 |
+
# Create 80-20 split
|
| 137 |
+
random.seed(42) # For reproducibility
|
| 138 |
+
combined = list(zip(all_image_paths, all_label_paths))
|
| 139 |
+
random.shuffle(combined)
|
| 140 |
+
split_idx = int(0.8 * len(combined))
|
| 141 |
+
|
| 142 |
+
train_split = combined[:split_idx]
|
| 143 |
+
val_split = combined[split_idx:]
|
| 144 |
+
|
| 145 |
+
# Create directory if needed
|
| 146 |
+
split_dir = os.path.dirname(self.split_file)
|
| 147 |
+
os.makedirs(split_dir, exist_ok=True)
|
| 148 |
+
|
| 149 |
+
# Save splits
|
| 150 |
+
with open(os.path.join(split_dir, "train_split.json"), "w") as f:
|
| 151 |
+
json.dump(train_split, f)
|
| 152 |
+
|
| 153 |
+
with open(os.path.join(split_dir, "val_split.json"), "w") as f:
|
| 154 |
+
json.dump(val_split, f)
|
| 155 |
+
|
| 156 |
+
def _load_split(self):
|
| 157 |
+
"""Load paths from existing split file"""
|
| 158 |
+
with open(self.split_file, "r") as f:
|
| 159 |
+
split_data = json.load(f)
|
| 160 |
+
|
| 161 |
+
self.image_paths, self.label_paths = zip(*split_data)
|
| 162 |
+
|
| 163 |
+
def __len__(self):
|
| 164 |
+
return len(self.image_paths)
|
| 165 |
+
|
| 166 |
+
def __getitem__(self, index):
|
| 167 |
+
batch = {}
|
| 168 |
+
seed = random.randint(0, 2**32 - 1)
|
| 169 |
+
|
| 170 |
+
# Load image
|
| 171 |
+
random.seed(seed)
|
| 172 |
+
torch.manual_seed(seed)
|
| 173 |
+
img = Image.open(self.image_paths[index]).convert("RGB")
|
| 174 |
+
if self.transform:
|
| 175 |
+
img = self.transform(img)
|
| 176 |
+
batch["image"] = img
|
| 177 |
+
batch["img_path"] = self.image_paths[index]
|
| 178 |
+
|
| 179 |
+
if self.include_labels:
|
| 180 |
+
# Load and process label
|
| 181 |
+
random.seed(seed)
|
| 182 |
+
torch.manual_seed(seed)
|
| 183 |
+
label = Image.open(self.label_paths[index])
|
| 184 |
+
|
| 185 |
+
if self.target_transform:
|
| 186 |
+
label = self.target_transform(label)
|
| 187 |
+
|
| 188 |
+
# Convert from id to trainId
|
| 189 |
+
label_array = np.array(label)
|
| 190 |
+
for id_val, trainid_val in self.id_to_trainid.items():
|
| 191 |
+
label_array[label_array == id_val] = trainid_val
|
| 192 |
+
|
| 193 |
+
label = torch.from_numpy(label_array).to(torch.uint8)
|
| 194 |
+
batch["label"] = label.squeeze(0)
|
| 195 |
+
|
| 196 |
+
return batch
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/kitti360/val_split.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/dataset/voc.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
from typing import Optional
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
from torchvision.datasets import VOCSegmentation
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class VOCDataset(Dataset):
|
| 12 |
+
def __init__(
|
| 13 |
+
self,
|
| 14 |
+
root: str,
|
| 15 |
+
split: str = "train",
|
| 16 |
+
transform: Optional[callable] = None,
|
| 17 |
+
target_transform: Optional[callable] = None,
|
| 18 |
+
include_labels: bool = True,
|
| 19 |
+
num_classes: int = 21,
|
| 20 |
+
tag: Optional[str] = None,
|
| 21 |
+
year: str = "2012",
|
| 22 |
+
download: bool = False,
|
| 23 |
+
**kwargs
|
| 24 |
+
):
|
| 25 |
+
super().__init__()
|
| 26 |
+
self.root = root
|
| 27 |
+
self.split = split
|
| 28 |
+
self.transform = transform
|
| 29 |
+
self.target_transform = target_transform
|
| 30 |
+
self.include_labels = include_labels
|
| 31 |
+
self.num_classes = num_classes
|
| 32 |
+
self.tag = tag
|
| 33 |
+
|
| 34 |
+
# Initialize torchvision's VOC dataset
|
| 35 |
+
self.voc_dataset = VOCSegmentation(
|
| 36 |
+
root=root,
|
| 37 |
+
year=year,
|
| 38 |
+
image_set="train" if split == "train" else "val",
|
| 39 |
+
download=download,
|
| 40 |
+
transform=None,
|
| 41 |
+
target_transform=None,
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
if split == "train":
|
| 45 |
+
assert self.__len__() == 1464
|
| 46 |
+
elif split == "val":
|
| 47 |
+
assert self.__len__() == 1449
|
| 48 |
+
|
| 49 |
+
def __len__(self):
|
| 50 |
+
return len(self.voc_dataset)
|
| 51 |
+
|
| 52 |
+
def __getitem__(self, index):
|
| 53 |
+
batch = {}
|
| 54 |
+
seed = random.randint(0, 2**32 - 1)
|
| 55 |
+
|
| 56 |
+
# Load image
|
| 57 |
+
img_path, mask_path = (
|
| 58 |
+
self.voc_dataset.images[index],
|
| 59 |
+
self.voc_dataset.masks[index],
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
random.seed(seed)
|
| 63 |
+
torch.manual_seed(seed)
|
| 64 |
+
img = Image.open(img_path).convert("RGB")
|
| 65 |
+
if self.transform:
|
| 66 |
+
img = self.transform(img)
|
| 67 |
+
batch["image"] = img
|
| 68 |
+
batch["img_path"] = img_path
|
| 69 |
+
|
| 70 |
+
if self.include_labels:
|
| 71 |
+
# Load and process mask
|
| 72 |
+
random.seed(seed)
|
| 73 |
+
torch.manual_seed(seed)
|
| 74 |
+
mask = Image.open(mask_path)
|
| 75 |
+
|
| 76 |
+
if self.target_transform:
|
| 77 |
+
mask = self.target_transform(mask)
|
| 78 |
+
|
| 79 |
+
# Convert to tensor and match COCO/ADE20K format
|
| 80 |
+
mask = torch.from_numpy(np.array(mask)).to(torch.uint8)
|
| 81 |
+
batch["label"] = mask.squeeze()
|
| 82 |
+
|
| 83 |
+
return batch
|
| 84 |
+
|
| 85 |
+
@property
|
| 86 |
+
def pred_offset(self):
|
| 87 |
+
return 0
|
| 88 |
+
|
| 89 |
+
def get_class_names(self):
|
| 90 |
+
"""Return the class names for the VOC dataset."""
|
| 91 |
+
return [
|
| 92 |
+
"background",
|
| 93 |
+
"aeroplane",
|
| 94 |
+
"bicycle",
|
| 95 |
+
"bird",
|
| 96 |
+
"boat",
|
| 97 |
+
"bottle",
|
| 98 |
+
"bus",
|
| 99 |
+
"car",
|
| 100 |
+
"cat",
|
| 101 |
+
"chair",
|
| 102 |
+
"cow",
|
| 103 |
+
"diningtable",
|
| 104 |
+
"dog",
|
| 105 |
+
"horse",
|
| 106 |
+
"motorbike",
|
| 107 |
+
"person",
|
| 108 |
+
"pottedplant",
|
| 109 |
+
"sheep",
|
| 110 |
+
"sofa",
|
| 111 |
+
"train",
|
| 112 |
+
"tvmonitor",
|
| 113 |
+
]
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/eval_depth_probing.py
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import hydra
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
import torchvision.transforms as T
|
| 12 |
+
from einops import rearrange
|
| 13 |
+
from hydra.core.global_hydra import GlobalHydra
|
| 14 |
+
from hydra.core.hydra_config import HydraConfig
|
| 15 |
+
from hydra.utils import instantiate
|
| 16 |
+
from omegaconf import OmegaConf
|
| 17 |
+
from rich.console import Console
|
| 18 |
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
| 19 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 20 |
+
from tqdm import tqdm
|
| 21 |
+
|
| 22 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 23 |
+
|
| 24 |
+
import random
|
| 25 |
+
|
| 26 |
+
from src.loss import DepthLoss
|
| 27 |
+
from utils.training import get_batch, get_dataloaders, load_multiple_backbones
|
| 28 |
+
|
| 29 |
+
LOG_INTERVAL = 100
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def eval_metrics(gt, pred, min_depth=1e-3, max_depth=10):
|
| 33 |
+
mask_1 = gt > min_depth
|
| 34 |
+
mask_2 = gt < max_depth
|
| 35 |
+
mask = np.logical_and(mask_1, mask_2)
|
| 36 |
+
|
| 37 |
+
gt = gt[mask]
|
| 38 |
+
pred = pred[mask]
|
| 39 |
+
|
| 40 |
+
thresh = np.maximum((gt / pred), (pred / gt))
|
| 41 |
+
d1 = (thresh < 1.25).mean()
|
| 42 |
+
d2 = (thresh < 1.25**2).mean()
|
| 43 |
+
d3 = (thresh < 1.25**3).mean()
|
| 44 |
+
|
| 45 |
+
abs_rel = np.mean(np.abs(gt - pred) / gt)
|
| 46 |
+
sq_rel = np.mean(((gt - pred) ** 2) / gt)
|
| 47 |
+
|
| 48 |
+
rmse = (gt - pred) ** 2
|
| 49 |
+
rmse = np.sqrt(rmse.mean())
|
| 50 |
+
|
| 51 |
+
rmse_log = (np.log(gt) - np.log(pred)) ** 2
|
| 52 |
+
rmse_log = np.sqrt(rmse_log.mean())
|
| 53 |
+
|
| 54 |
+
err = np.log(pred) - np.log(gt)
|
| 55 |
+
silog = np.sqrt(np.mean(err**2) - np.mean(err) ** 2) * 100
|
| 56 |
+
|
| 57 |
+
log_10 = (np.abs(np.log10(gt) - np.log10(pred))).mean()
|
| 58 |
+
return dict(
|
| 59 |
+
d1=d1,
|
| 60 |
+
d2=d2,
|
| 61 |
+
d3=d3,
|
| 62 |
+
abs_rel=abs_rel,
|
| 63 |
+
rmse=rmse,
|
| 64 |
+
log_10=log_10,
|
| 65 |
+
rmse_log=rmse_log,
|
| 66 |
+
silog=silog,
|
| 67 |
+
sq_rel=sq_rel,
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class UpsamplerEvaluator(nn.Module):
|
| 72 |
+
|
| 73 |
+
def __init__(self, model, backbone, device, cfg, writer, console):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.model, self.backbone, self.device, self.cfg, self.writer, self.console = (
|
| 76 |
+
model,
|
| 77 |
+
backbone,
|
| 78 |
+
device,
|
| 79 |
+
cfg,
|
| 80 |
+
writer,
|
| 81 |
+
console,
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
# Use backbone-specific normalization (like train_jafar.py)
|
| 85 |
+
self.mean_bck = backbone.config["mean"]
|
| 86 |
+
self.std_bck = backbone.config["std"]
|
| 87 |
+
|
| 88 |
+
# Upsampler normalization (ImageNet standard)
|
| 89 |
+
self.mean_ups = (0.485, 0.456, 0.406)
|
| 90 |
+
self.std_ups = (0.229, 0.224, 0.225)
|
| 91 |
+
|
| 92 |
+
# Initialize depth estimation components
|
| 93 |
+
self.classifier = nn.Conv2d(backbone.embed_dim, 256, kernel_size=1, padding=0, stride=1).to(device)
|
| 94 |
+
self.depthloss = DepthLoss(max_depth=cfg.metrics.depth.max_depth)
|
| 95 |
+
self.register_buffer("bins", torch.linspace(cfg.metrics.depth.min_depth, cfg.metrics.depth.max_depth, 256))
|
| 96 |
+
|
| 97 |
+
def set_up_classifier(self, checkpoint_path):
|
| 98 |
+
"""Load classifier weights from a checkpoint."""
|
| 99 |
+
if Path(checkpoint_path).exists():
|
| 100 |
+
checkpoint = torch.load(checkpoint_path)
|
| 101 |
+
self.classifier.load_state_dict(checkpoint)
|
| 102 |
+
self.console.print(f"Loaded classifier from checkpoint: {checkpoint_path}")
|
| 103 |
+
else:
|
| 104 |
+
raise FileNotFoundError(f"Checkpoint file not found: {checkpoint_path}")
|
| 105 |
+
|
| 106 |
+
def set_optimizer(self, cfg, loader):
|
| 107 |
+
params = []
|
| 108 |
+
params_classifier = self.classifier.parameters()
|
| 109 |
+
params_model = self.model.parameters()
|
| 110 |
+
|
| 111 |
+
params = list(params_classifier)
|
| 112 |
+
if self.cfg.eval.supervise_model == True:
|
| 113 |
+
params += params_model
|
| 114 |
+
optimizer = instantiate(cfg.optimizer, params=params)
|
| 115 |
+
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=cfg.num_epochs * len(loader))
|
| 116 |
+
self.optimizer = optimizer
|
| 117 |
+
self.scheduler = scheduler
|
| 118 |
+
|
| 119 |
+
num_params = sum(p.numel() for p in params if p.requires_grad)
|
| 120 |
+
self.log_print(f"[bold cyan]Number of optimized parameters: {num_params:,}[/bold cyan]")
|
| 121 |
+
|
| 122 |
+
def log_print(self, *args, **kwargs):
|
| 123 |
+
Console(force_terminal=True).print(*args, **kwargs)
|
| 124 |
+
self.console.print(*args, **kwargs)
|
| 125 |
+
if hasattr(self.console, "file") and self.console.file:
|
| 126 |
+
self.console.file.flush()
|
| 127 |
+
|
| 128 |
+
def log_tensorboard(self, step, loss=None, metrics=None):
|
| 129 |
+
if loss is not None:
|
| 130 |
+
self.writer.add_scalar("Loss/Step", loss, step)
|
| 131 |
+
if metrics is not None:
|
| 132 |
+
for metric_name, metric_value in metrics.items():
|
| 133 |
+
self.writer.add_scalar(f"Metrics/{metric_name}", metric_value, step)
|
| 134 |
+
|
| 135 |
+
def process_batch(self, image_batch, target, is_training=True, batch_idx=0):
|
| 136 |
+
H, W = target.shape[-2:]
|
| 137 |
+
|
| 138 |
+
# Apply separate normalization like in train_jafar.py
|
| 139 |
+
image_batch_bck = T.functional.normalize(image_batch, mean=self.mean_bck, std=self.std_bck)
|
| 140 |
+
image_batch_ups = T.functional.normalize(image_batch, mean=self.mean_ups, std=self.std_ups)
|
| 141 |
+
|
| 142 |
+
with torch.no_grad():
|
| 143 |
+
pred = self.backbone(image_batch_bck)
|
| 144 |
+
|
| 145 |
+
if self.cfg.eval.supervise_model:
|
| 146 |
+
pred = self.model(image_batch_ups, pred, (H, W))
|
| 147 |
+
else:
|
| 148 |
+
with torch.no_grad():
|
| 149 |
+
pred = self.model(image_batch_ups, pred, (H, W)).detach()
|
| 150 |
+
|
| 151 |
+
if batch_idx == 0:
|
| 152 |
+
self.log_print(f"[red]Model output size {pred.shape[-2:]} for expected size {(H, W)}[/red]")
|
| 153 |
+
pred = self.classifier(pred)
|
| 154 |
+
|
| 155 |
+
if pred.shape[-2:] != (H, W):
|
| 156 |
+
pred = F.interpolate(pred, size=(H, W), mode="bilinear")
|
| 157 |
+
|
| 158 |
+
# https://github.com/mbanani/probe3d/blob/c52d00b069d949b2f00c544d4991716df68d5233/evals/models/probes.py#L108
|
| 159 |
+
pred = F.relu(pred)
|
| 160 |
+
eps = 0.1
|
| 161 |
+
pred = pred + eps
|
| 162 |
+
pred = pred / pred.sum(dim=1, keepdim=True)
|
| 163 |
+
pred = torch.einsum("ikmn,k->imn", [pred, self.bins]).unsqueeze(dim=1)
|
| 164 |
+
return pred, target
|
| 165 |
+
|
| 166 |
+
def train(
|
| 167 |
+
self,
|
| 168 |
+
train_dataloader,
|
| 169 |
+
progress,
|
| 170 |
+
epoch,
|
| 171 |
+
start_time,
|
| 172 |
+
):
|
| 173 |
+
self.log_print(f"[yellow]Training model epoch {epoch+1}...[/yellow]")
|
| 174 |
+
self.backbone.eval()
|
| 175 |
+
if self.cfg.eval.supervise_model:
|
| 176 |
+
self.model.train()
|
| 177 |
+
else:
|
| 178 |
+
self.model.eval()
|
| 179 |
+
self.classifier.train()
|
| 180 |
+
|
| 181 |
+
epoch_task = progress.add_task(
|
| 182 |
+
f"Epoch {epoch+1}/{self.cfg.num_epochs}",
|
| 183 |
+
total=len(train_dataloader),
|
| 184 |
+
loss=0.0,
|
| 185 |
+
step=0,
|
| 186 |
+
)
|
| 187 |
+
total_loss = 0
|
| 188 |
+
|
| 189 |
+
for batch_idx, batch in enumerate(train_dataloader):
|
| 190 |
+
# Process batch using get_batch
|
| 191 |
+
batch = get_batch(batch, self.device)
|
| 192 |
+
image_batch = batch["image"]
|
| 193 |
+
target = batch["depth"].to(self.device)
|
| 194 |
+
|
| 195 |
+
if random.random() < 0.5:
|
| 196 |
+
image_batch = torch.flip(image_batch, dims=[3])
|
| 197 |
+
target = torch.flip(target, dims=[3])
|
| 198 |
+
|
| 199 |
+
self.optimizer.zero_grad()
|
| 200 |
+
|
| 201 |
+
pred, target = self.process_batch(image_batch, target, is_training=True, batch_idx=batch_idx)
|
| 202 |
+
|
| 203 |
+
loss = self.depthloss(pred, target)
|
| 204 |
+
|
| 205 |
+
loss.backward()
|
| 206 |
+
self.optimizer.step()
|
| 207 |
+
total_loss += loss.item()
|
| 208 |
+
|
| 209 |
+
avg_loss = total_loss / (batch_idx + 1)
|
| 210 |
+
|
| 211 |
+
if (batch_idx + 1) % LOG_INTERVAL == 0 or batch_idx == len(train_dataloader) - 1:
|
| 212 |
+
elapsed_time = datetime.datetime.now() - start_time
|
| 213 |
+
elapsed_str = str(elapsed_time).split(".")[0]
|
| 214 |
+
current_lr = self.optimizer.param_groups[0]["lr"]
|
| 215 |
+
|
| 216 |
+
progress.update(epoch_task, advance=LOG_INTERVAL, loss=avg_loss, step=batch_idx + 1)
|
| 217 |
+
|
| 218 |
+
self.log_print(
|
| 219 |
+
f"[cyan]Iteration {batch_idx + 1}[/cyan] - "
|
| 220 |
+
f"Loss: {avg_loss:.6f} - "
|
| 221 |
+
f"LR: {current_lr:.5e} - "
|
| 222 |
+
f"Elapsed Time: {elapsed_str}"
|
| 223 |
+
)
|
| 224 |
+
if self.console and hasattr(self.console, "file"):
|
| 225 |
+
self.console.file.flush()
|
| 226 |
+
|
| 227 |
+
progress.refresh()
|
| 228 |
+
|
| 229 |
+
self.log_tensorboard(epoch * len(train_dataloader) + batch_idx, loss=avg_loss)
|
| 230 |
+
|
| 231 |
+
if self.cfg.sanity and batch_idx == 0:
|
| 232 |
+
break
|
| 233 |
+
|
| 234 |
+
self.scheduler.step()
|
| 235 |
+
|
| 236 |
+
if self.cfg.sanity:
|
| 237 |
+
break
|
| 238 |
+
|
| 239 |
+
current_lr = self.optimizer.param_groups[0]["lr"]
|
| 240 |
+
self.log_print(
|
| 241 |
+
f"[bold cyan]Epoch {epoch+1} Summary:[/bold cyan] " f"Loss = {avg_loss:.6f} - " f"LR = {current_lr:.2e}"
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
return
|
| 245 |
+
|
| 246 |
+
def save_checkpoint(self, checkpoint_path):
|
| 247 |
+
console = self.console
|
| 248 |
+
# Save final model state after training
|
| 249 |
+
torch.save(self.classifier.state_dict(), checkpoint_path)
|
| 250 |
+
self.log_print(f"[bold green]Training completed. Model saved at: {checkpoint_path}[/bold green]")
|
| 251 |
+
console.file.flush()
|
| 252 |
+
return
|
| 253 |
+
|
| 254 |
+
@torch.inference_mode()
|
| 255 |
+
def evaluate(self, dataloader, epoch):
|
| 256 |
+
self.log_print("[yellow]Evaluating model...[/yellow]")
|
| 257 |
+
torch.cuda.empty_cache()
|
| 258 |
+
|
| 259 |
+
self.backbone.eval()
|
| 260 |
+
self.model.eval()
|
| 261 |
+
self.classifier.eval()
|
| 262 |
+
|
| 263 |
+
# Initialize depth metrics
|
| 264 |
+
results = {"d1": 0, "d2": 0, "d3": 0, "abs_rel": 0, "sq_rel": 0, "rmse": 0, "rmse_log": 0, "log_10": 0, "silog": 0}
|
| 265 |
+
|
| 266 |
+
nsamples = 0
|
| 267 |
+
|
| 268 |
+
for batch_idx, batch in enumerate(tqdm(dataloader, desc="Evaluating")):
|
| 269 |
+
batch = get_batch(batch, self.device)
|
| 270 |
+
image_batch = batch["image"]
|
| 271 |
+
target = batch["depth"].to(self.device)
|
| 272 |
+
|
| 273 |
+
# Process batch and get masked predictions and targets
|
| 274 |
+
pred, target = self.process_batch(image_batch, target, is_training=False, batch_idx=batch_idx)
|
| 275 |
+
|
| 276 |
+
cur_results = eval_metrics(
|
| 277 |
+
target.cpu().detach().numpy(),
|
| 278 |
+
pred.cpu().detach().numpy(),
|
| 279 |
+
self.cfg.metrics.depth.min_depth,
|
| 280 |
+
self.cfg.metrics.depth.max_depth,
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
for k in results.keys():
|
| 284 |
+
results[k] += cur_results[k]
|
| 285 |
+
nsamples += 1
|
| 286 |
+
|
| 287 |
+
if self.cfg.sanity and batch_idx == 0:
|
| 288 |
+
break
|
| 289 |
+
|
| 290 |
+
metrics = {}
|
| 291 |
+
for k in results.keys():
|
| 292 |
+
metrics[k] = results[k] / nsamples
|
| 293 |
+
|
| 294 |
+
# Log metrics to TensorBoard
|
| 295 |
+
self.log_tensorboard(step=epoch, metrics=metrics)
|
| 296 |
+
|
| 297 |
+
self.log_print(f"[bold green]Results: {metrics}[/bold green]")
|
| 298 |
+
return
|
| 299 |
+
|
| 300 |
+
@torch.inference_mode()
|
| 301 |
+
def simple_inference(self, image_batch):
|
| 302 |
+
self.backbone.eval()
|
| 303 |
+
self.model.eval()
|
| 304 |
+
self.classifier.eval()
|
| 305 |
+
|
| 306 |
+
H, W = image_batch.shape[-2:]
|
| 307 |
+
with torch.no_grad():
|
| 308 |
+
lr_feats = self.backbone(image_batch)
|
| 309 |
+
hr_feats = self.model(image_batch, lr_feats, (H, W))
|
| 310 |
+
|
| 311 |
+
labels = self.classifier(hr_feats) # Pass through the classifier
|
| 312 |
+
|
| 313 |
+
labels = F.relu(labels)
|
| 314 |
+
eps = 0.1
|
| 315 |
+
labels = labels + eps
|
| 316 |
+
labels = labels / labels.sum(dim=1, keepdim=True)
|
| 317 |
+
labels = torch.einsum("ikmn,k->imn", [labels, self.bins]).unsqueeze(dim=1)
|
| 318 |
+
return hr_feats, labels, lr_feats
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
@hydra.main(config_path="../config", config_name="eval_probing")
|
| 322 |
+
def main(cfg):
|
| 323 |
+
# Use Hydra's current working directory for all outputs
|
| 324 |
+
task = "depth"
|
| 325 |
+
model_name = cfg.model.name if hasattr(cfg.model, "name") else "base"
|
| 326 |
+
|
| 327 |
+
# Get Hydra's current working directory
|
| 328 |
+
hydra_cfg = HydraConfig.get()
|
| 329 |
+
current_run_dir = hydra_cfg.runtime.output_dir
|
| 330 |
+
|
| 331 |
+
# Setup Classifier checkpoint in current Hydra directory
|
| 332 |
+
checkpoint_filename = "linear_probe.pth"
|
| 333 |
+
checkpoint_path = os.path.join(current_run_dir, checkpoint_filename)
|
| 334 |
+
|
| 335 |
+
# Create persistent consoles instead of creating new ones each time
|
| 336 |
+
terminal_console = Console() # Terminal output
|
| 337 |
+
tag = ""
|
| 338 |
+
if cfg.eval.model_ckpt:
|
| 339 |
+
try:
|
| 340 |
+
tag = cfg.eval.model_ckpt.split("/")[-3]
|
| 341 |
+
except:
|
| 342 |
+
tag = ""
|
| 343 |
+
|
| 344 |
+
# Create log file names
|
| 345 |
+
if Path(checkpoint_path).exists():
|
| 346 |
+
file_name = f"eval_{model_name}_{tag}_{task}_prob_from_ups.log"
|
| 347 |
+
else:
|
| 348 |
+
file_name = f"train_{model_name}_{tag}_{task}_prob_from_ups.log"
|
| 349 |
+
|
| 350 |
+
# All outputs in Hydra's current directory
|
| 351 |
+
log_file_path = os.path.join(current_run_dir, file_name)
|
| 352 |
+
tb_dir = os.path.join(current_run_dir, "tb")
|
| 353 |
+
os.makedirs(tb_dir, exist_ok=True)
|
| 354 |
+
|
| 355 |
+
file_console = Console(file=open(log_file_path, "w"))
|
| 356 |
+
|
| 357 |
+
# Initialize TensorBoard writer in current Hydra directory
|
| 358 |
+
writer = SummaryWriter(log_dir=tb_dir)
|
| 359 |
+
|
| 360 |
+
def log_print(*args, **kwargs):
|
| 361 |
+
"""Log to both terminal and file with immediate flushing"""
|
| 362 |
+
terminal_console.print(*args, **kwargs)
|
| 363 |
+
file_console.print(*args, **kwargs)
|
| 364 |
+
file_console.file.flush()
|
| 365 |
+
|
| 366 |
+
# Start logging
|
| 367 |
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 368 |
+
log_print(f"\n[bold blue]{'='*50}[/bold blue]")
|
| 369 |
+
log_print(f"[bold blue]Starting at {timestamp}[/bold blue]")
|
| 370 |
+
log_print(f"[bold green]Configuration:[/bold green]")
|
| 371 |
+
log_print(OmegaConf.to_yaml(cfg))
|
| 372 |
+
|
| 373 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 374 |
+
log_print(f"[bold yellow]Using device: {device}[/bold yellow]")
|
| 375 |
+
|
| 376 |
+
log_print(f"\n[bold cyan]Processing {task} task:[/bold cyan]")
|
| 377 |
+
log_print(f"\n[bold cyan]Image size: {cfg.img_size}[/bold cyan]")
|
| 378 |
+
|
| 379 |
+
# Setup Backbone
|
| 380 |
+
backbones, backbone_names, *_ = load_multiple_backbones(cfg, cfg.backbone, device)
|
| 381 |
+
backbone = backbones[0]
|
| 382 |
+
backbone.requires_grad_(False)
|
| 383 |
+
backbone.eval()
|
| 384 |
+
|
| 385 |
+
# Setup Model
|
| 386 |
+
model = instantiate(cfg.model).to(device)
|
| 387 |
+
if cfg.eval.model_ckpt:
|
| 388 |
+
checkpoint = torch.load(cfg.eval.model_ckpt, map_location=device, weights_only=False)
|
| 389 |
+
if cfg.model.name == "featup":
|
| 390 |
+
new_ckpts = {
|
| 391 |
+
k.replace("model.1.", "norm."): v
|
| 392 |
+
for k, v in checkpoint["state_dict"].items()
|
| 393 |
+
if "upsampler" in k or "model.1.norm" in k
|
| 394 |
+
}
|
| 395 |
+
model.load_state_dict(new_ckpts, strict=True)
|
| 396 |
+
else:
|
| 397 |
+
model.load_state_dict(checkpoint, strict=True)
|
| 398 |
+
log_print(f"[green]Loaded model from checkpoint: {cfg.eval.model_ckpt}[/green]")
|
| 399 |
+
else:
|
| 400 |
+
log_print(f"[yellow]No model checkpoint provided, using untrained model[/yellow]")
|
| 401 |
+
|
| 402 |
+
if cfg.eval.supervise_model == False:
|
| 403 |
+
model.requires_grad_(False)
|
| 404 |
+
model.eval()
|
| 405 |
+
else:
|
| 406 |
+
model.requires_grad_(True)
|
| 407 |
+
model.train()
|
| 408 |
+
|
| 409 |
+
# Setup Dataloaders - Modified to match train_jafar.py approach
|
| 410 |
+
train_loader, val_loader = get_dataloaders(cfg, shuffle=False)
|
| 411 |
+
log_print(f"[bold cyan]Train Dataset size: {len(train_loader.dataset)}[/bold cyan]")
|
| 412 |
+
log_print(f"[bold cyan]Val Dataset size: {len(val_loader.dataset)}[/bold cyan]")
|
| 413 |
+
|
| 414 |
+
# Setup Evaluator
|
| 415 |
+
evaluator = UpsamplerEvaluator(model, backbone, device, cfg, writer, file_console)
|
| 416 |
+
evaluator.to(device)
|
| 417 |
+
|
| 418 |
+
log_print("[yellow]Training classifier...[/yellow]\n")
|
| 419 |
+
evaluator.set_optimizer(cfg, train_loader)
|
| 420 |
+
|
| 421 |
+
progress = Progress(
|
| 422 |
+
SpinnerColumn(),
|
| 423 |
+
TextColumn("[progress.description]{task.description}"),
|
| 424 |
+
BarColumn(),
|
| 425 |
+
TaskProgressColumn(),
|
| 426 |
+
TextColumn("[yellow]Loss: {task.fields[loss]:.6f}"),
|
| 427 |
+
TextColumn("[green]Step: {task.fields[step]:.6e}"),
|
| 428 |
+
console=file_console,
|
| 429 |
+
)
|
| 430 |
+
|
| 431 |
+
start_time = datetime.datetime.now()
|
| 432 |
+
|
| 433 |
+
with progress:
|
| 434 |
+
# Standard training for upsampler
|
| 435 |
+
log_print(f"[yellow]Training for {cfg.num_epochs} epochs[/yellow]\n")
|
| 436 |
+
|
| 437 |
+
for epoch in range(cfg.num_epochs):
|
| 438 |
+
evaluator.train(train_loader, progress, epoch, start_time)
|
| 439 |
+
evaluator.evaluate(val_loader, epoch)
|
| 440 |
+
|
| 441 |
+
evaluator.save_checkpoint(checkpoint_path)
|
| 442 |
+
|
| 443 |
+
file_console.file.close()
|
| 444 |
+
writer.close() # Close TensorBoard writer
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
if __name__ == "__main__":
|
| 448 |
+
main()
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/eval_seg_probing.py
ADDED
|
@@ -0,0 +1,393 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import hydra
|
| 7 |
+
import numpy as np
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import torch.nn.functional as F
|
| 11 |
+
import torchvision.transforms as T
|
| 12 |
+
from einops import rearrange
|
| 13 |
+
from hydra.core.global_hydra import GlobalHydra
|
| 14 |
+
from hydra.core.hydra_config import HydraConfig
|
| 15 |
+
from hydra.utils import instantiate
|
| 16 |
+
from omegaconf import OmegaConf
|
| 17 |
+
from rich.console import Console
|
| 18 |
+
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
| 19 |
+
from torch.utils.tensorboard import SummaryWriter
|
| 20 |
+
from torchmetrics.classification import Accuracy, JaccardIndex
|
| 21 |
+
from tqdm import tqdm
|
| 22 |
+
|
| 23 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 24 |
+
|
| 25 |
+
import random
|
| 26 |
+
|
| 27 |
+
from utils.training import get_batch, get_dataloaders, load_multiple_backbones
|
| 28 |
+
|
| 29 |
+
LOG_INTERVAL = 100
|
| 30 |
+
IGNORE = 255
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class UpsamplerEvaluator:
|
| 34 |
+
|
| 35 |
+
def __init__(self, model, backbone, device, cfg, writer, console):
|
| 36 |
+
self.model, self.backbone, self.device, self.cfg, self.writer, self.console = (
|
| 37 |
+
model,
|
| 38 |
+
backbone,
|
| 39 |
+
device,
|
| 40 |
+
cfg,
|
| 41 |
+
writer,
|
| 42 |
+
console,
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
# Use backbone-specific normalization (like train_jafar.py)
|
| 46 |
+
self.mean_bck = backbone.config["mean"]
|
| 47 |
+
self.std_bck = backbone.config["std"]
|
| 48 |
+
|
| 49 |
+
# Upsampler normalization (ImageNet standard)
|
| 50 |
+
self.mean_ups = (0.485, 0.456, 0.406)
|
| 51 |
+
self.std_ups = (0.229, 0.224, 0.225)
|
| 52 |
+
|
| 53 |
+
# Initialize task-specific components
|
| 54 |
+
self.accuracy_metric = Accuracy(num_classes=cfg.metrics.seg.num_classes, task="multiclass").to(device)
|
| 55 |
+
self.iou_metric = JaccardIndex(num_classes=cfg.metrics.seg.num_classes, task="multiclass").to(device)
|
| 56 |
+
self.classifier = nn.Conv2d(backbone.embed_dim, cfg.metrics.seg.num_classes, 1).to(device)
|
| 57 |
+
|
| 58 |
+
def set_up_classifier(self, checkpoint_path):
|
| 59 |
+
"""Load classifier weights from a checkpoint."""
|
| 60 |
+
if Path(checkpoint_path).exists():
|
| 61 |
+
checkpoint = torch.load(checkpoint_path)
|
| 62 |
+
self.classifier.load_state_dict(checkpoint)
|
| 63 |
+
self.console.print(f"Loaded classifier from checkpoint: {checkpoint_path}")
|
| 64 |
+
else:
|
| 65 |
+
raise FileNotFoundError(f"Checkpoint file not found: {checkpoint_path}")
|
| 66 |
+
|
| 67 |
+
def set_optimizer(self, cfg, loader):
|
| 68 |
+
params = []
|
| 69 |
+
params_classifier = self.classifier.parameters()
|
| 70 |
+
params_model = self.model.parameters()
|
| 71 |
+
|
| 72 |
+
params = list(params_classifier)
|
| 73 |
+
optimizer = instantiate(cfg.optimizer, params=params)
|
| 74 |
+
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=cfg.num_epochs * len(loader))
|
| 75 |
+
self.optimizer = optimizer
|
| 76 |
+
self.scheduler = scheduler
|
| 77 |
+
|
| 78 |
+
num_params = sum(p.numel() for p in params if p.requires_grad)
|
| 79 |
+
self.log_print(f"[bold cyan]Number of optimized parameters: {num_params:,}[/bold cyan]")
|
| 80 |
+
|
| 81 |
+
def log_print(self, *args, **kwargs):
|
| 82 |
+
Console(force_terminal=True).print(*args, **kwargs)
|
| 83 |
+
self.console.print(*args, **kwargs)
|
| 84 |
+
if hasattr(self.console, "file") and self.console.file:
|
| 85 |
+
self.console.file.flush()
|
| 86 |
+
|
| 87 |
+
def log_tensorboard(self, step, loss=None, metrics=None):
|
| 88 |
+
if loss is not None:
|
| 89 |
+
self.writer.add_scalar("Loss/Step", loss, step)
|
| 90 |
+
if metrics is not None:
|
| 91 |
+
for metric_name, metric_value in metrics.items():
|
| 92 |
+
self.writer.add_scalar(f"Metrics/{metric_name}", metric_value, step)
|
| 93 |
+
|
| 94 |
+
def process_batch(self, image_batch, target, batch_idx=0):
|
| 95 |
+
H, W = target.shape[-2:]
|
| 96 |
+
|
| 97 |
+
image_batch_bck = T.functional.normalize(image_batch, mean=self.mean_bck, std=self.std_bck)
|
| 98 |
+
image_batch_ups = T.functional.normalize(image_batch, mean=self.mean_ups, std=self.std_ups)
|
| 99 |
+
|
| 100 |
+
with torch.no_grad():
|
| 101 |
+
pred = self.backbone(image_batch_bck)
|
| 102 |
+
|
| 103 |
+
input_shape = pred.shape[-2:]
|
| 104 |
+
with torch.no_grad():
|
| 105 |
+
pred = self.model(image_batch_ups, pred, (H, W)).detach()
|
| 106 |
+
|
| 107 |
+
if batch_idx == 0:
|
| 108 |
+
self.log_print(
|
| 109 |
+
f"[red]Model input size {input_shape}, output size {pred.shape[-2:]} for expected size {(H, W)}[/red]"
|
| 110 |
+
)
|
| 111 |
+
pred = self.classifier(pred)
|
| 112 |
+
|
| 113 |
+
if pred.shape[-2:] != (H, W):
|
| 114 |
+
pred = F.interpolate(pred, size=(H, W), mode="bilinear")
|
| 115 |
+
|
| 116 |
+
if target.shape[-2:] != pred.shape[-2:]:
|
| 117 |
+
target = (
|
| 118 |
+
F.interpolate(
|
| 119 |
+
target.unsqueeze(1),
|
| 120 |
+
size=pred.shape[-2:],
|
| 121 |
+
mode="nearest-exact",
|
| 122 |
+
)
|
| 123 |
+
.squeeze(1)
|
| 124 |
+
.to(target.dtype)
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
valid_mask = target != IGNORE
|
| 128 |
+
|
| 129 |
+
pred = rearrange(pred, "b c h w -> (b h w) c")
|
| 130 |
+
target = rearrange(target, "b h w -> (b h w)")
|
| 131 |
+
valid_mask = rearrange(valid_mask, "b h w -> (b h w)")
|
| 132 |
+
|
| 133 |
+
pred = pred[valid_mask]
|
| 134 |
+
target = target[valid_mask]
|
| 135 |
+
return pred, target
|
| 136 |
+
|
| 137 |
+
def train(
|
| 138 |
+
self,
|
| 139 |
+
train_dataloader,
|
| 140 |
+
progress,
|
| 141 |
+
epoch,
|
| 142 |
+
start_time,
|
| 143 |
+
):
|
| 144 |
+
self.log_print(f"[yellow]Training model epoch {epoch+1}...[/yellow]")
|
| 145 |
+
self.backbone.eval()
|
| 146 |
+
self.model.eval()
|
| 147 |
+
self.classifier.train()
|
| 148 |
+
|
| 149 |
+
epoch_task = progress.add_task(
|
| 150 |
+
f"Epoch {epoch+1}/{self.cfg.num_epochs}",
|
| 151 |
+
total=len(train_dataloader),
|
| 152 |
+
loss=0.0,
|
| 153 |
+
step=0,
|
| 154 |
+
)
|
| 155 |
+
total_loss = 0
|
| 156 |
+
|
| 157 |
+
for batch_idx, batch in enumerate(train_dataloader):
|
| 158 |
+
batch = get_batch(batch, self.device)
|
| 159 |
+
image_batch = batch["image"]
|
| 160 |
+
target = batch["label"].to(self.device)
|
| 161 |
+
|
| 162 |
+
if random.random() < 0.5:
|
| 163 |
+
assert len(image_batch.shape) == 4 and len(target.shape) == 3
|
| 164 |
+
image_batch = torch.flip(image_batch, dims=[3])
|
| 165 |
+
target = torch.flip(target, dims=[2])
|
| 166 |
+
|
| 167 |
+
self.optimizer.zero_grad()
|
| 168 |
+
|
| 169 |
+
pred, target = self.process_batch(image_batch, target, batch_idx=batch_idx)
|
| 170 |
+
|
| 171 |
+
loss = F.cross_entropy(pred, target)
|
| 172 |
+
|
| 173 |
+
loss.backward()
|
| 174 |
+
self.optimizer.step()
|
| 175 |
+
total_loss += loss.item()
|
| 176 |
+
|
| 177 |
+
avg_loss = total_loss / (batch_idx + 1)
|
| 178 |
+
|
| 179 |
+
if (batch_idx + 1) % LOG_INTERVAL == 0 or batch_idx == len(train_dataloader) - 1:
|
| 180 |
+
elapsed_time = datetime.datetime.now() - start_time
|
| 181 |
+
elapsed_str = str(elapsed_time).split(".")[0]
|
| 182 |
+
current_lr = self.optimizer.param_groups[0]["lr"]
|
| 183 |
+
|
| 184 |
+
progress.update(epoch_task, advance=LOG_INTERVAL, loss=avg_loss, step=batch_idx + 1)
|
| 185 |
+
|
| 186 |
+
self.log_print(
|
| 187 |
+
f"[cyan]Iteration {batch_idx + 1}[/cyan] - "
|
| 188 |
+
f"Loss: {avg_loss:.6f} - "
|
| 189 |
+
f"LR: {current_lr:.5e} - "
|
| 190 |
+
f"Elapsed Time: {elapsed_str}"
|
| 191 |
+
)
|
| 192 |
+
if self.console and hasattr(self.console, "file"):
|
| 193 |
+
self.console.file.flush()
|
| 194 |
+
|
| 195 |
+
progress.refresh()
|
| 196 |
+
|
| 197 |
+
self.log_tensorboard(epoch * len(train_dataloader) + batch_idx, loss=avg_loss)
|
| 198 |
+
|
| 199 |
+
if self.cfg.sanity and batch_idx == 0:
|
| 200 |
+
break
|
| 201 |
+
|
| 202 |
+
self.scheduler.step()
|
| 203 |
+
|
| 204 |
+
if self.cfg.sanity:
|
| 205 |
+
break
|
| 206 |
+
|
| 207 |
+
current_lr = self.optimizer.param_groups[0]["lr"]
|
| 208 |
+
self.log_print(
|
| 209 |
+
f"[bold cyan]Epoch {epoch+1} Summary:[/bold cyan] " f"Loss = {avg_loss:.6f} - " f"LR = {current_lr:.2e}"
|
| 210 |
+
)
|
| 211 |
+
|
| 212 |
+
return
|
| 213 |
+
|
| 214 |
+
def save_checkpoint(self, checkpoint_path):
|
| 215 |
+
console = self.console
|
| 216 |
+
torch.save(self.classifier.state_dict(), checkpoint_path)
|
| 217 |
+
self.log_print(f"[bold green]Training completed. Model saved at: {checkpoint_path}[/bold green]")
|
| 218 |
+
console.file.flush()
|
| 219 |
+
return
|
| 220 |
+
|
| 221 |
+
@torch.inference_mode()
|
| 222 |
+
def evaluate(self, dataloader, epoch):
|
| 223 |
+
self.log_print("[yellow]Evaluating model...[/yellow]")
|
| 224 |
+
torch.cuda.empty_cache()
|
| 225 |
+
|
| 226 |
+
self.backbone.eval()
|
| 227 |
+
self.model.eval()
|
| 228 |
+
self.classifier.eval()
|
| 229 |
+
|
| 230 |
+
self.accuracy_metric.reset()
|
| 231 |
+
self.iou_metric.reset()
|
| 232 |
+
|
| 233 |
+
for batch_idx, batch in enumerate(tqdm(dataloader, desc="Evaluating")):
|
| 234 |
+
batch = get_batch(batch, self.device)
|
| 235 |
+
image_batch = batch["image"]
|
| 236 |
+
target = batch["label"].to(self.device)
|
| 237 |
+
|
| 238 |
+
pred, target = self.process_batch(image_batch, target, batch_idx=batch_idx)
|
| 239 |
+
|
| 240 |
+
self.accuracy_metric(pred, target)
|
| 241 |
+
self.iou_metric(pred, target)
|
| 242 |
+
|
| 243 |
+
if self.cfg.sanity and batch_idx == 0:
|
| 244 |
+
break
|
| 245 |
+
|
| 246 |
+
metrics = {}
|
| 247 |
+
metrics.update(
|
| 248 |
+
{
|
| 249 |
+
"accuracy": self.accuracy_metric.compute().item(),
|
| 250 |
+
"iou": self.iou_metric.compute().item(),
|
| 251 |
+
}
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
self.log_tensorboard(step=epoch, metrics=metrics)
|
| 255 |
+
|
| 256 |
+
self.log_print(f"[bold green]Results: {metrics}[/bold green]")
|
| 257 |
+
return
|
| 258 |
+
|
| 259 |
+
@torch.inference_mode()
|
| 260 |
+
def simple_inference(self, image_batch):
|
| 261 |
+
self.backbone.eval()
|
| 262 |
+
self.model.eval()
|
| 263 |
+
self.classifier.eval()
|
| 264 |
+
|
| 265 |
+
H, W = image_batch.shape[-2:]
|
| 266 |
+
image_batch_bck = T.functional.normalize(image_batch, mean=self.mean_bck, std=self.std_bck)
|
| 267 |
+
image_batch_ups = T.functional.normalize(image_batch, mean=self.mean_ups, std=self.std_ups)
|
| 268 |
+
|
| 269 |
+
with torch.no_grad():
|
| 270 |
+
lr_feats = self.backbone(image_batch_bck)
|
| 271 |
+
hr_feats = self.model(image_batch_ups, lr_feats, (H, W))
|
| 272 |
+
|
| 273 |
+
labels = self.classifier(hr_feats).argmax(dim=1, keepdim=True)
|
| 274 |
+
return hr_feats, labels, lr_feats
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
@hydra.main(config_path="../config", config_name="eval_probing")
|
| 278 |
+
def main(cfg):
|
| 279 |
+
hydra_cfg = HydraConfig.get()
|
| 280 |
+
current_run_dir = hydra_cfg.runtime.output_dir
|
| 281 |
+
|
| 282 |
+
task = cfg.eval.task
|
| 283 |
+
model_name = cfg.model.name if hasattr(cfg.model, "name") else "base"
|
| 284 |
+
|
| 285 |
+
# Setup Classifier checkpoint in current Hydra directory
|
| 286 |
+
checkpoint_filename = "linear_probe.pth"
|
| 287 |
+
checkpoint_path = os.path.join(current_run_dir, checkpoint_filename)
|
| 288 |
+
|
| 289 |
+
# Create persistent consoles instead of creating new ones each time
|
| 290 |
+
terminal_console = Console()
|
| 291 |
+
tag = ""
|
| 292 |
+
if cfg.eval.model_ckpt:
|
| 293 |
+
try:
|
| 294 |
+
tag = cfg.eval.model_ckpt.split("/")[-3]
|
| 295 |
+
except:
|
| 296 |
+
tag = ""
|
| 297 |
+
|
| 298 |
+
# Create log file names
|
| 299 |
+
file_name = f"train_{model_name}_{tag}_{task}.log"
|
| 300 |
+
|
| 301 |
+
# All outputs in Hydra's current directory
|
| 302 |
+
log_file_path = os.path.join(current_run_dir, file_name)
|
| 303 |
+
tb_dir = os.path.join(current_run_dir, "tb")
|
| 304 |
+
os.makedirs(tb_dir, exist_ok=True)
|
| 305 |
+
|
| 306 |
+
file_console = Console(file=open(log_file_path, "w"))
|
| 307 |
+
|
| 308 |
+
# Initialize TensorBoard writer in current Hydra directory
|
| 309 |
+
writer = SummaryWriter(log_dir=tb_dir)
|
| 310 |
+
|
| 311 |
+
def log_print(*args, **kwargs):
|
| 312 |
+
"""Log to both terminal and file with immediate flushing"""
|
| 313 |
+
terminal_console.print(*args, **kwargs)
|
| 314 |
+
file_console.print(*args, **kwargs)
|
| 315 |
+
file_console.file.flush()
|
| 316 |
+
|
| 317 |
+
# Start logging
|
| 318 |
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 319 |
+
log_print(f"\n[bold blue]{'='*50}[/bold blue]")
|
| 320 |
+
log_print(f"[bold blue]Starting at {timestamp}[/bold blue]")
|
| 321 |
+
log_print(f"[bold green]Configuration:[/bold green]")
|
| 322 |
+
log_print(OmegaConf.to_yaml(cfg))
|
| 323 |
+
|
| 324 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 325 |
+
log_print(f"[bold yellow]Using device: {device}[/bold yellow]")
|
| 326 |
+
|
| 327 |
+
log_print(f"\n[bold cyan]Processing {task} task:[/bold cyan]")
|
| 328 |
+
log_print(f"\n[bold cyan]Image size: {cfg.img_size}[/bold cyan]")
|
| 329 |
+
|
| 330 |
+
# Setup Backbone
|
| 331 |
+
backbones, *_ = load_multiple_backbones(cfg, cfg.backbone, device)
|
| 332 |
+
backbone = backbones[0]
|
| 333 |
+
backbone.requires_grad_(False)
|
| 334 |
+
backbone.eval()
|
| 335 |
+
|
| 336 |
+
# Setup Model
|
| 337 |
+
model = instantiate(cfg.model).to(device)
|
| 338 |
+
if cfg.eval.model_ckpt:
|
| 339 |
+
checkpoint = torch.load(cfg.eval.model_ckpt, map_location=device, weights_only=False)
|
| 340 |
+
if cfg.model.name == "featup":
|
| 341 |
+
new_ckpts = {
|
| 342 |
+
k.replace("model.1.", "norm."): v
|
| 343 |
+
for k, v in checkpoint["state_dict"].items()
|
| 344 |
+
if "upsampler" in k or "model.1.norm" in k
|
| 345 |
+
}
|
| 346 |
+
model.load_state_dict(new_ckpts, strict=True)
|
| 347 |
+
else:
|
| 348 |
+
model.load_state_dict(checkpoint, strict=True)
|
| 349 |
+
log_print(f"[green]Loaded model from checkpoint: {cfg.eval.model_ckpt}[/green]")
|
| 350 |
+
else:
|
| 351 |
+
log_print(f"[yellow]No model checkpoint provided, using untrained model[/yellow]")
|
| 352 |
+
|
| 353 |
+
model.requires_grad_(True)
|
| 354 |
+
model.train()
|
| 355 |
+
|
| 356 |
+
# Setup Dataloaders - Modified to match train_jafar.py approach
|
| 357 |
+
train_loader, val_loader = get_dataloaders(cfg, shuffle=False)
|
| 358 |
+
log_print(f"[bold cyan]Train Dataset size: {len(train_loader.dataset)}[/bold cyan]")
|
| 359 |
+
log_print(f"[bold cyan]Val Dataset size: {len(val_loader.dataset)}[/bold cyan]")
|
| 360 |
+
|
| 361 |
+
# Setup Evaluator
|
| 362 |
+
evaluator = UpsamplerEvaluator(model, backbone, device, cfg, writer, file_console)
|
| 363 |
+
|
| 364 |
+
log_print("[yellow]Training classifier...[/yellow]\n")
|
| 365 |
+
evaluator.set_optimizer(cfg, loader=train_loader)
|
| 366 |
+
|
| 367 |
+
progress = Progress(
|
| 368 |
+
SpinnerColumn(),
|
| 369 |
+
TextColumn("[progress.description]{task.description}"),
|
| 370 |
+
BarColumn(),
|
| 371 |
+
TaskProgressColumn(),
|
| 372 |
+
TextColumn("[yellow]Loss: {task.fields[loss]:.6f}"),
|
| 373 |
+
TextColumn("[green]Step: {task.fields[step]:.6e}"),
|
| 374 |
+
console=file_console,
|
| 375 |
+
)
|
| 376 |
+
|
| 377 |
+
start_time = datetime.datetime.now()
|
| 378 |
+
|
| 379 |
+
with progress:
|
| 380 |
+
log_print(f"[yellow]Training for {cfg.num_epochs} epochs[/yellow]\n")
|
| 381 |
+
|
| 382 |
+
for epoch in range(cfg.num_epochs):
|
| 383 |
+
evaluator.train(train_loader, progress, epoch, start_time)
|
| 384 |
+
evaluator.evaluate(val_loader, epoch)
|
| 385 |
+
|
| 386 |
+
evaluator.save_checkpoint(checkpoint_path)
|
| 387 |
+
|
| 388 |
+
file_console.file.close()
|
| 389 |
+
writer.close()
|
| 390 |
+
|
| 391 |
+
|
| 392 |
+
if __name__ == "__main__":
|
| 393 |
+
main()
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/evaluation/eval_video_seg.py
ADDED
|
@@ -0,0 +1,816 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluate JAFAR performance on DAVIS video segmentation. Based on https://github.com/davisvideochallenge/davis2017-evaluation and https://github.com/facebookresearch/dino/blob/main/eval_video_segmentation.py
|
| 3 |
+
|
| 4 |
+
Code adapted by: Saksham Suri and Matthew Walmer
|
| 5 |
+
|
| 6 |
+
Sample Usage:
|
| 7 |
+
python eval_davis.py
|
| 8 |
+
python eval_davis.py backbone.name=vit_small_patch14_reg4_dinov2 model=jafar
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import copy
|
| 13 |
+
import datetime
|
| 14 |
+
import glob
|
| 15 |
+
import math
|
| 16 |
+
import os
|
| 17 |
+
import queue
|
| 18 |
+
import sys
|
| 19 |
+
import time
|
| 20 |
+
import warnings
|
| 21 |
+
from collections import defaultdict
|
| 22 |
+
from urllib.request import urlopen
|
| 23 |
+
|
| 24 |
+
import cv2
|
| 25 |
+
import hydra
|
| 26 |
+
import matplotlib.pyplot as plt
|
| 27 |
+
import numpy as np
|
| 28 |
+
import pandas as pd
|
| 29 |
+
import torch
|
| 30 |
+
import torch.multiprocessing
|
| 31 |
+
import torch.nn as nn
|
| 32 |
+
import torch.nn.functional as F
|
| 33 |
+
from einops import rearrange
|
| 34 |
+
from hydra.core.hydra_config import HydraConfig
|
| 35 |
+
from hydra.utils import instantiate
|
| 36 |
+
from omegaconf import DictConfig, OmegaConf
|
| 37 |
+
from PIL import Image
|
| 38 |
+
from rich.console import Console
|
| 39 |
+
from torch import nn
|
| 40 |
+
from torch.nn import functional as F
|
| 41 |
+
from torchvision import transforms
|
| 42 |
+
from tqdm import tqdm
|
| 43 |
+
|
| 44 |
+
warnings.filterwarnings("ignore", category=RuntimeWarning)
|
| 45 |
+
|
| 46 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 47 |
+
|
| 48 |
+
from utils.training import load_multiple_backbones
|
| 49 |
+
|
| 50 |
+
torch.multiprocessing.set_sharing_strategy("file_system")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# ============================================================================
|
| 54 |
+
# DAVIS Evaluation Classes and Functions (Self-contained)
|
| 55 |
+
# ============================================================================
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class DAVISDataset(object):
|
| 59 |
+
"""Simplified DAVIS dataset class for evaluation"""
|
| 60 |
+
|
| 61 |
+
SUBSET_OPTIONS = ["train", "val", "test-dev", "test-challenge"]
|
| 62 |
+
TASKS = ["semi-supervised", "unsupervised"]
|
| 63 |
+
VOID_LABEL = 255
|
| 64 |
+
|
| 65 |
+
def __init__(self, root, task="semi-supervised", subset="val", sequences="all", resolution="480p"):
|
| 66 |
+
self.task = task
|
| 67 |
+
self.subset = subset
|
| 68 |
+
self.root = root
|
| 69 |
+
self.img_path = os.path.join(self.root, "JPEGImages", resolution)
|
| 70 |
+
self.mask_path = os.path.join(self.root, "Annotations", resolution)
|
| 71 |
+
self.imagesets_path = os.path.join(self.root, "ImageSets", "2017")
|
| 72 |
+
|
| 73 |
+
if sequences == "all":
|
| 74 |
+
with open(os.path.join(self.imagesets_path, f"{self.subset}.txt"), "r") as f:
|
| 75 |
+
tmp = f.readlines()
|
| 76 |
+
sequences_names = [x.strip() for x in tmp]
|
| 77 |
+
else:
|
| 78 |
+
sequences_names = sequences if isinstance(sequences, list) else [sequences]
|
| 79 |
+
|
| 80 |
+
self.sequences = defaultdict(dict)
|
| 81 |
+
for seq in sequences_names:
|
| 82 |
+
images = np.sort(glob.glob(os.path.join(self.img_path, seq, "*.jpg"))).tolist()
|
| 83 |
+
self.sequences[seq]["images"] = images
|
| 84 |
+
masks = np.sort(glob.glob(os.path.join(self.mask_path, seq, "*.png"))).tolist()
|
| 85 |
+
self.sequences[seq]["masks"] = masks
|
| 86 |
+
|
| 87 |
+
def get_sequences(self):
|
| 88 |
+
return list(self.sequences.keys())
|
| 89 |
+
|
| 90 |
+
def get_all_masks(self, sequence, separate_objects_masks=False):
|
| 91 |
+
"""Get all ground truth masks for a sequence"""
|
| 92 |
+
masks = []
|
| 93 |
+
mask_ids = []
|
| 94 |
+
|
| 95 |
+
for mask_path in self.sequences[sequence]["masks"]:
|
| 96 |
+
mask = np.array(Image.open(mask_path))
|
| 97 |
+
masks.append(mask)
|
| 98 |
+
mask_ids.append(os.path.splitext(os.path.basename(mask_path))[0])
|
| 99 |
+
|
| 100 |
+
masks = np.array(masks)
|
| 101 |
+
|
| 102 |
+
if separate_objects_masks:
|
| 103 |
+
# Convert to separate object masks
|
| 104 |
+
num_objects = int(np.max(masks))
|
| 105 |
+
if num_objects > 0:
|
| 106 |
+
object_masks = np.zeros((num_objects, *masks.shape))
|
| 107 |
+
for obj_id in range(1, num_objects + 1):
|
| 108 |
+
object_masks[obj_id - 1] = (masks == obj_id).astype(np.uint8)
|
| 109 |
+
void_masks = np.zeros_like(masks[0]) # No void pixels for simplicity
|
| 110 |
+
return object_masks, void_masks, mask_ids
|
| 111 |
+
|
| 112 |
+
return masks, mask_ids
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class DAVISResults(object):
|
| 116 |
+
"""Results reader for DAVIS evaluation"""
|
| 117 |
+
|
| 118 |
+
def __init__(self, root_dir):
|
| 119 |
+
self.root_dir = root_dir
|
| 120 |
+
|
| 121 |
+
def _read_mask(self, sequence, frame_id):
|
| 122 |
+
mask_path = os.path.join(self.root_dir, sequence, f"{frame_id}.png")
|
| 123 |
+
if not os.path.exists(mask_path):
|
| 124 |
+
raise FileNotFoundError(f"Mask not found: {mask_path}")
|
| 125 |
+
return np.array(Image.open(mask_path))
|
| 126 |
+
|
| 127 |
+
def read_masks(self, sequence, masks_id):
|
| 128 |
+
"""Read all prediction masks for a sequence"""
|
| 129 |
+
mask_0 = self._read_mask(sequence, masks_id[0])
|
| 130 |
+
masks = np.zeros((len(masks_id), *mask_0.shape))
|
| 131 |
+
for ii, m in enumerate(masks_id):
|
| 132 |
+
masks[ii, ...] = self._read_mask(sequence, m)
|
| 133 |
+
|
| 134 |
+
# Convert to separate object masks
|
| 135 |
+
num_objects = int(np.max(masks))
|
| 136 |
+
if num_objects == 0:
|
| 137 |
+
return np.zeros((1, *masks.shape))
|
| 138 |
+
|
| 139 |
+
tmp = np.ones((num_objects, *masks.shape))
|
| 140 |
+
tmp = tmp * np.arange(1, num_objects + 1)[:, None, None, None]
|
| 141 |
+
masks = (tmp == masks[None, ...]) > 0
|
| 142 |
+
return masks
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def davis_db_eval_iou(annotation, segmentation, void_pixels=None):
|
| 146 |
+
"""Compute region similarity as the Jaccard Index"""
|
| 147 |
+
assert (
|
| 148 |
+
annotation.shape == segmentation.shape
|
| 149 |
+
), f"Annotation({annotation.shape}) and segmentation:{segmentation.shape} dimensions do not match."
|
| 150 |
+
|
| 151 |
+
annotation = annotation.astype(bool)
|
| 152 |
+
segmentation = segmentation.astype(bool)
|
| 153 |
+
|
| 154 |
+
if void_pixels is not None:
|
| 155 |
+
void_pixels = void_pixels.astype(bool)
|
| 156 |
+
else:
|
| 157 |
+
void_pixels = np.zeros_like(segmentation)
|
| 158 |
+
|
| 159 |
+
# Intersection between all sets
|
| 160 |
+
inters = np.sum((segmentation & annotation) & np.logical_not(void_pixels), axis=(-2, -1))
|
| 161 |
+
union = np.sum((segmentation | annotation) & np.logical_not(void_pixels), axis=(-2, -1))
|
| 162 |
+
|
| 163 |
+
j = inters / union
|
| 164 |
+
if j.ndim == 0:
|
| 165 |
+
j = 1 if np.isclose(union, 0) else j
|
| 166 |
+
else:
|
| 167 |
+
j[np.isclose(union, 0)] = 1
|
| 168 |
+
return j
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def davis_f_measure(foreground_mask, gt_mask, void_pixels=None, bound_th=0.008):
|
| 172 |
+
"""Compute F-measure for boundary accuracy"""
|
| 173 |
+
assert foreground_mask.shape == gt_mask.shape
|
| 174 |
+
|
| 175 |
+
if void_pixels is not None:
|
| 176 |
+
assert foreground_mask.shape == void_pixels.shape
|
| 177 |
+
# Apply void mask
|
| 178 |
+
foreground_mask = foreground_mask.copy()
|
| 179 |
+
gt_mask = gt_mask.copy()
|
| 180 |
+
foreground_mask[void_pixels] = 0
|
| 181 |
+
gt_mask[void_pixels] = 0
|
| 182 |
+
|
| 183 |
+
bound_pix = bound_th if bound_th >= 1 else np.ceil(bound_th * np.linalg.norm(foreground_mask.shape))
|
| 184 |
+
|
| 185 |
+
# Get the pixel boundaries of both masks
|
| 186 |
+
fg_boundary = _seg2bmap(foreground_mask)
|
| 187 |
+
gt_boundary = _seg2bmap(gt_mask)
|
| 188 |
+
|
| 189 |
+
from scipy.ndimage import distance_transform_edt
|
| 190 |
+
|
| 191 |
+
# Distance transform
|
| 192 |
+
fg_dist = distance_transform_edt(1 - fg_boundary)
|
| 193 |
+
gt_dist = distance_transform_edt(1 - gt_boundary)
|
| 194 |
+
|
| 195 |
+
# Precision: how many predicted boundary pixels are close to gt
|
| 196 |
+
precision = np.sum(fg_boundary * (gt_dist <= bound_pix)) / (np.sum(fg_boundary) + 1e-10)
|
| 197 |
+
|
| 198 |
+
# Recall: how many gt boundary pixels are close to predicted
|
| 199 |
+
recall = np.sum(gt_boundary * (fg_dist <= bound_pix)) / (np.sum(gt_boundary) + 1e-10)
|
| 200 |
+
|
| 201 |
+
# F-measure
|
| 202 |
+
if precision + recall == 0:
|
| 203 |
+
f_score = 0
|
| 204 |
+
else:
|
| 205 |
+
f_score = 2 * precision * recall / (precision + recall)
|
| 206 |
+
|
| 207 |
+
return f_score
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def _seg2bmap(seg, width=None, height=None):
|
| 211 |
+
"""Convert segmentation to boundary map"""
|
| 212 |
+
seg = seg.astype(bool)
|
| 213 |
+
seg = seg.astype(np.uint8)
|
| 214 |
+
|
| 215 |
+
# Compute edges using Sobel operator
|
| 216 |
+
kernel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], dtype=np.float32)
|
| 217 |
+
kernel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]], dtype=np.float32)
|
| 218 |
+
|
| 219 |
+
edges_x = cv2.filter2D(seg.astype(np.float32), -1, kernel_x)
|
| 220 |
+
edges_y = cv2.filter2D(seg.astype(np.float32), -1, kernel_y)
|
| 221 |
+
edges = np.sqrt(edges_x**2 + edges_y**2)
|
| 222 |
+
|
| 223 |
+
# Threshold to get binary boundary
|
| 224 |
+
bmap = edges > 0.1
|
| 225 |
+
|
| 226 |
+
return bmap.astype(bool)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def davis_db_eval_boundary(annotation, segmentation, void_pixels=None, bound_th=0.008):
|
| 230 |
+
"""Compute boundary F-measure"""
|
| 231 |
+
assert annotation.shape == segmentation.shape
|
| 232 |
+
if void_pixels is not None:
|
| 233 |
+
assert annotation.shape == void_pixels.shape
|
| 234 |
+
|
| 235 |
+
if annotation.ndim == 3:
|
| 236 |
+
n_frames = annotation.shape[0]
|
| 237 |
+
f_res = np.zeros(n_frames)
|
| 238 |
+
for frame_id in range(n_frames):
|
| 239 |
+
void_pixels_frame = None if void_pixels is None else void_pixels[frame_id, :, :]
|
| 240 |
+
f_res[frame_id] = davis_f_measure(
|
| 241 |
+
segmentation[frame_id, :, :], annotation[frame_id, :, :], void_pixels_frame, bound_th=bound_th
|
| 242 |
+
)
|
| 243 |
+
elif annotation.ndim == 2:
|
| 244 |
+
f_res = davis_f_measure(segmentation, annotation, void_pixels, bound_th=bound_th)
|
| 245 |
+
else:
|
| 246 |
+
raise ValueError(f"Annotation and segmentation must be 2D or 3D arrays, got {annotation.ndim}D")
|
| 247 |
+
|
| 248 |
+
return f_res
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def davis_db_statistics(per_frame_values):
|
| 252 |
+
"""Compute mean, recall and decay from per-frame evaluation"""
|
| 253 |
+
# Strip off nan values
|
| 254 |
+
with warnings.catch_warnings():
|
| 255 |
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
| 256 |
+
M = np.nanmean(per_frame_values)
|
| 257 |
+
O = np.nanmean(per_frame_values > 0.5)
|
| 258 |
+
|
| 259 |
+
N_bins = 4
|
| 260 |
+
ids = np.round(np.linspace(1, len(per_frame_values), N_bins + 1) + 1e-10) - 1
|
| 261 |
+
ids = ids.astype(np.uint8)
|
| 262 |
+
|
| 263 |
+
D_bins = [per_frame_values[ids[i] : ids[i + 1] + 1] for i in range(0, 4)]
|
| 264 |
+
|
| 265 |
+
with warnings.catch_warnings():
|
| 266 |
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
| 267 |
+
D = np.nanmean(D_bins[0]) - np.nanmean(D_bins[3])
|
| 268 |
+
|
| 269 |
+
return M, O, D
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
class DAVISEvaluation(object):
|
| 273 |
+
"""Complete DAVIS evaluation class"""
|
| 274 |
+
|
| 275 |
+
def __init__(self, davis_root, task="semi-supervised", gt_set="val", sequences="all"):
|
| 276 |
+
self.davis_root = davis_root
|
| 277 |
+
self.task = task
|
| 278 |
+
self.dataset = DAVISDataset(root=davis_root, task=task, subset=gt_set, sequences=sequences)
|
| 279 |
+
|
| 280 |
+
@staticmethod
|
| 281 |
+
def _evaluate_semisupervised(all_gt_masks, all_res_masks, all_void_masks, metric):
|
| 282 |
+
if all_res_masks.shape[0] > all_gt_masks.shape[0]:
|
| 283 |
+
print("\nIn your PNG files there is an index higher than the number of objects in the sequence!")
|
| 284 |
+
return None, None
|
| 285 |
+
elif all_res_masks.shape[0] < all_gt_masks.shape[0]:
|
| 286 |
+
zero_padding = np.zeros((all_gt_masks.shape[0] - all_res_masks.shape[0], *all_res_masks.shape[1:]))
|
| 287 |
+
all_res_masks = np.concatenate([all_res_masks, zero_padding], axis=0)
|
| 288 |
+
|
| 289 |
+
j_metrics_res, f_metrics_res = np.zeros(all_gt_masks.shape[:2]), np.zeros(all_gt_masks.shape[:2])
|
| 290 |
+
for ii in range(all_gt_masks.shape[0]):
|
| 291 |
+
if "J" in metric:
|
| 292 |
+
j_metrics_res[ii, :] = davis_db_eval_iou(all_gt_masks[ii, ...], all_res_masks[ii, ...], all_void_masks)
|
| 293 |
+
if "F" in metric:
|
| 294 |
+
f_metrics_res[ii, :] = davis_db_eval_boundary(all_gt_masks[ii, ...], all_res_masks[ii, ...], all_void_masks)
|
| 295 |
+
return j_metrics_res, f_metrics_res
|
| 296 |
+
|
| 297 |
+
def evaluate(self, res_path, metric=("J", "F"), debug=False):
|
| 298 |
+
"""Run complete DAVIS evaluation"""
|
| 299 |
+
metric = metric if isinstance(metric, tuple) or isinstance(metric, list) else [metric]
|
| 300 |
+
if "T" in metric:
|
| 301 |
+
raise ValueError("Temporal metric not supported!")
|
| 302 |
+
if "J" not in metric and "F" not in metric:
|
| 303 |
+
raise ValueError("Metric possible values are J for IoU or F for Boundary")
|
| 304 |
+
|
| 305 |
+
# Containers
|
| 306 |
+
metrics_res = {}
|
| 307 |
+
if "J" in metric:
|
| 308 |
+
metrics_res["J"] = {"M": [], "R": [], "D": [], "M_per_object": {}}
|
| 309 |
+
if "F" in metric:
|
| 310 |
+
metrics_res["F"] = {"M": [], "R": [], "D": [], "M_per_object": {}}
|
| 311 |
+
|
| 312 |
+
# Sweep all sequences
|
| 313 |
+
results = DAVISResults(root_dir=res_path)
|
| 314 |
+
for seq in tqdm(list(self.dataset.get_sequences())):
|
| 315 |
+
try:
|
| 316 |
+
all_gt_masks, all_void_masks, all_masks_id = self.dataset.get_all_masks(seq, True)
|
| 317 |
+
if self.task == "semi-supervised":
|
| 318 |
+
all_gt_masks, all_masks_id = all_gt_masks[:, 1:-1, :, :], all_masks_id[1:-1]
|
| 319 |
+
all_res_masks = results.read_masks(seq, all_masks_id)
|
| 320 |
+
|
| 321 |
+
if self.task == "semi-supervised":
|
| 322 |
+
j_metrics_res, f_metrics_res = self._evaluate_semisupervised(all_gt_masks, all_res_masks, None, metric)
|
| 323 |
+
else:
|
| 324 |
+
# For unsupervised, use semi-supervised for now (simplified)
|
| 325 |
+
j_metrics_res, f_metrics_res = self._evaluate_semisupervised(all_gt_masks, all_res_masks, None, metric)
|
| 326 |
+
|
| 327 |
+
if j_metrics_res is None:
|
| 328 |
+
continue
|
| 329 |
+
|
| 330 |
+
for ii in range(all_gt_masks.shape[0]):
|
| 331 |
+
seq_name = f"{seq}_{ii+1}"
|
| 332 |
+
if "J" in metric:
|
| 333 |
+
JM, JR, JD = davis_db_statistics(j_metrics_res[ii])
|
| 334 |
+
metrics_res["J"]["M"].append(JM)
|
| 335 |
+
metrics_res["J"]["R"].append(JR)
|
| 336 |
+
metrics_res["J"]["D"].append(JD)
|
| 337 |
+
metrics_res["J"]["M_per_object"][seq_name] = JM
|
| 338 |
+
if "F" in metric:
|
| 339 |
+
FM, FR, FD = davis_db_statistics(f_metrics_res[ii])
|
| 340 |
+
metrics_res["F"]["M"].append(FM)
|
| 341 |
+
metrics_res["F"]["R"].append(FR)
|
| 342 |
+
metrics_res["F"]["D"].append(FD)
|
| 343 |
+
metrics_res["F"]["M_per_object"][seq_name] = FM
|
| 344 |
+
|
| 345 |
+
# Show progress
|
| 346 |
+
if debug:
|
| 347 |
+
print(f"Evaluated sequence: {seq}")
|
| 348 |
+
|
| 349 |
+
except Exception as e:
|
| 350 |
+
print(f"Error evaluating sequence {seq}: {e}")
|
| 351 |
+
continue
|
| 352 |
+
|
| 353 |
+
return metrics_res
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
@torch.no_grad()
|
| 357 |
+
def eval_video_tracking_davis(
|
| 358 |
+
cfg, backbone, upsampler, transform, frame_list, video_dir, first_seg, seg_ori, color_palette, log_print
|
| 359 |
+
):
|
| 360 |
+
"""
|
| 361 |
+
Evaluate tracking on a video given first frame & segmentation
|
| 362 |
+
"""
|
| 363 |
+
output_dir = HydraConfig.get().runtime.output_dir
|
| 364 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 365 |
+
|
| 366 |
+
# Create output directory based on model configuration
|
| 367 |
+
exp_name = f"davis_vidseg_{cfg.eval.ups_factor}_{cfg.model.name}_{cfg.backbone.name}"
|
| 368 |
+
output_subdir = os.path.join(output_dir, exp_name)
|
| 369 |
+
os.makedirs(output_subdir, exist_ok=True)
|
| 370 |
+
|
| 371 |
+
video_folder = os.path.join(output_subdir, video_dir.split("/")[-1])
|
| 372 |
+
os.makedirs(video_folder, exist_ok=True)
|
| 373 |
+
|
| 374 |
+
# Setup device and normalization
|
| 375 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 376 |
+
|
| 377 |
+
# Setup normalization transforms for backbone
|
| 378 |
+
mean_bck = backbone.config["mean"]
|
| 379 |
+
std_bck = backbone.config["std"]
|
| 380 |
+
mean_bck_tensor = torch.tensor(mean_bck, device=device).view(1, 3, 1, 1)
|
| 381 |
+
std_bck_tensor = torch.tensor(std_bck, device=device).view(1, 3, 1, 1)
|
| 382 |
+
|
| 383 |
+
# Upsampler normalization (ImageNet standard)
|
| 384 |
+
mean_ups = (0.485, 0.456, 0.406)
|
| 385 |
+
std_ups = (0.229, 0.224, 0.225)
|
| 386 |
+
mean_ups_tensor = torch.tensor(mean_ups, device=device).view(1, 3, 1, 1)
|
| 387 |
+
std_ups_tensor = torch.tensor(std_ups, device=device).view(1, 3, 1, 1)
|
| 388 |
+
|
| 389 |
+
# The queue stores the n preceding frames
|
| 390 |
+
que = queue.Queue(cfg.eval.n_last_frames)
|
| 391 |
+
|
| 392 |
+
# first frame
|
| 393 |
+
frame1, ori_h, ori_w = read_frame(frame_list[0], backbone.config["ps"])
|
| 394 |
+
|
| 395 |
+
# extract first frame feature using JAFAR pipeline
|
| 396 |
+
frame1_feat = extract_feature(
|
| 397 |
+
cfg,
|
| 398 |
+
backbone,
|
| 399 |
+
upsampler,
|
| 400 |
+
transform,
|
| 401 |
+
frame1,
|
| 402 |
+
mean_bck_tensor,
|
| 403 |
+
std_bck_tensor,
|
| 404 |
+
mean_ups_tensor,
|
| 405 |
+
std_ups_tensor,
|
| 406 |
+
)
|
| 407 |
+
frame1_feat = F.interpolate(frame1_feat, size=first_seg.shape[-2:], mode="bilinear", align_corners=False)
|
| 408 |
+
frame1_feat = rearrange(frame1_feat, "1 c h w -> c (h w)")
|
| 409 |
+
|
| 410 |
+
# saving first segmentation
|
| 411 |
+
out_path = os.path.join(video_folder, "00000.png")
|
| 412 |
+
imwrite_indexed(out_path, seg_ori, color_palette)
|
| 413 |
+
mask_neighborhood = None
|
| 414 |
+
|
| 415 |
+
for cnt in tqdm(range(1, len(frame_list))):
|
| 416 |
+
frame_tar = read_frame(frame_list[cnt], backbone.config["ps"])[0]
|
| 417 |
+
|
| 418 |
+
# we use the first segmentation and the n previous ones
|
| 419 |
+
used_frame_feats = [frame1_feat] + [pair[0] for pair in list(que.queue)]
|
| 420 |
+
used_segs = [first_seg] + [pair[1] for pair in list(que.queue)]
|
| 421 |
+
frame_tar_avg, feat_tar, mask_neighborhood = label_propagation(
|
| 422 |
+
cfg,
|
| 423 |
+
backbone,
|
| 424 |
+
upsampler,
|
| 425 |
+
transform,
|
| 426 |
+
frame_tar,
|
| 427 |
+
used_frame_feats,
|
| 428 |
+
used_segs,
|
| 429 |
+
mask_neighborhood,
|
| 430 |
+
mean_bck_tensor,
|
| 431 |
+
std_bck_tensor,
|
| 432 |
+
mean_ups_tensor,
|
| 433 |
+
std_ups_tensor,
|
| 434 |
+
)
|
| 435 |
+
|
| 436 |
+
# pop out oldest frame if necessary
|
| 437 |
+
if que.qsize() == cfg.eval.n_last_frames:
|
| 438 |
+
que.get()
|
| 439 |
+
# push current results into queue
|
| 440 |
+
seg = copy.deepcopy(frame_tar_avg)
|
| 441 |
+
que.put([feat_tar, seg])
|
| 442 |
+
|
| 443 |
+
# upsampling & argmax
|
| 444 |
+
frame_tar_avg = F.interpolate(
|
| 445 |
+
frame_tar_avg,
|
| 446 |
+
size=[v * backbone.config["ps"] // cfg.eval.ups_factor for v in frame_tar_avg.shape[-2:]],
|
| 447 |
+
mode="bilinear",
|
| 448 |
+
align_corners=False,
|
| 449 |
+
recompute_scale_factor=False,
|
| 450 |
+
)[0]
|
| 451 |
+
frame_tar_avg = frame_tar_avg.squeeze(0)
|
| 452 |
+
frame_tar_avg = norm_mask(frame_tar_avg)
|
| 453 |
+
_, frame_tar_seg = torch.max(frame_tar_avg, dim=0)
|
| 454 |
+
|
| 455 |
+
# saving to disk
|
| 456 |
+
frame_tar_seg = np.array(frame_tar_seg.squeeze().cpu(), dtype=np.uint8)
|
| 457 |
+
frame_tar_seg = np.array(Image.fromarray(frame_tar_seg).resize((ori_w, ori_h), 0))
|
| 458 |
+
frame_nm = frame_list[cnt].split("/")[-1].replace(".jpg", ".png")
|
| 459 |
+
imwrite_indexed(os.path.join(video_folder, frame_nm), frame_tar_seg, color_palette)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def restrict_neighborhood(cfg, h, w):
|
| 463 |
+
"""
|
| 464 |
+
Ultra-fast approach using meshgrid and advanced indexing
|
| 465 |
+
"""
|
| 466 |
+
size_mask = cfg.eval.size_mask_neighborhood
|
| 467 |
+
|
| 468 |
+
# Create coordinate meshgrids
|
| 469 |
+
query_i, query_j = torch.meshgrid(torch.arange(h), torch.arange(w), indexing="ij")
|
| 470 |
+
source_i, source_j = torch.meshgrid(torch.arange(h), torch.arange(w), indexing="ij")
|
| 471 |
+
|
| 472 |
+
# Flatten coordinates
|
| 473 |
+
query_i_flat = query_i.flatten().unsqueeze(1) # (h*w, 1)
|
| 474 |
+
query_j_flat = query_j.flatten().unsqueeze(1) # (h*w, 1)
|
| 475 |
+
source_i_flat = source_i.flatten().unsqueeze(0) # (1, h*w)
|
| 476 |
+
source_j_flat = source_j.flatten().unsqueeze(0) # (1, h*w)
|
| 477 |
+
|
| 478 |
+
# Calculate distances using broadcasting
|
| 479 |
+
i_dist = torch.abs(query_i_flat - source_i_flat) # (h*w, h*w)
|
| 480 |
+
j_dist = torch.abs(query_j_flat - source_j_flat) # (h*w, h*w)
|
| 481 |
+
|
| 482 |
+
# Create mask
|
| 483 |
+
mask = ((i_dist <= size_mask) & (j_dist <= size_mask)).float()
|
| 484 |
+
|
| 485 |
+
return mask.cuda(non_blocking=True)
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def norm_mask(mask):
|
| 489 |
+
c, h, w = mask.size()
|
| 490 |
+
for cnt in range(c):
|
| 491 |
+
mask_cnt = mask[cnt, :, :]
|
| 492 |
+
if mask_cnt.max() > 0:
|
| 493 |
+
mask_cnt = mask_cnt - mask_cnt.min()
|
| 494 |
+
mask_cnt = mask_cnt / mask_cnt.max()
|
| 495 |
+
mask[cnt, :, :] = mask_cnt
|
| 496 |
+
return mask
|
| 497 |
+
|
| 498 |
+
|
| 499 |
+
def label_propagation(
|
| 500 |
+
cfg,
|
| 501 |
+
backbone,
|
| 502 |
+
upsampler,
|
| 503 |
+
transform,
|
| 504 |
+
frame_tar,
|
| 505 |
+
list_frame_feats,
|
| 506 |
+
list_segs,
|
| 507 |
+
mask_neighborhood,
|
| 508 |
+
mean_bck_tensor,
|
| 509 |
+
std_bck_tensor,
|
| 510 |
+
mean_ups_tensor,
|
| 511 |
+
std_ups_tensor,
|
| 512 |
+
):
|
| 513 |
+
"""
|
| 514 |
+
propagate segs of frames in list_frames to frame_tar
|
| 515 |
+
"""
|
| 516 |
+
list_segs = [s.cuda() for s in list_segs]
|
| 517 |
+
segs = torch.cat(list_segs)
|
| 518 |
+
nmb_context, C, h, w = segs.shape
|
| 519 |
+
|
| 520 |
+
## we only need to extract feature of the target frame
|
| 521 |
+
feat_tar = extract_feature(
|
| 522 |
+
cfg,
|
| 523 |
+
backbone,
|
| 524 |
+
upsampler,
|
| 525 |
+
transform,
|
| 526 |
+
frame_tar,
|
| 527 |
+
mean_bck_tensor,
|
| 528 |
+
std_bck_tensor,
|
| 529 |
+
mean_ups_tensor,
|
| 530 |
+
std_ups_tensor,
|
| 531 |
+
)
|
| 532 |
+
feat_tar = F.interpolate(feat_tar, size=(h, w), mode="bilinear", align_corners=False)
|
| 533 |
+
feat_tar = rearrange(feat_tar, "1 c h w -> (h w) c")
|
| 534 |
+
return_feat_tar = feat_tar.T # dim x h*w
|
| 535 |
+
|
| 536 |
+
ncontext = len(list_frame_feats)
|
| 537 |
+
feat_sources = torch.stack(list_frame_feats) # nmb_context x dim x h*w
|
| 538 |
+
|
| 539 |
+
feat_tar = F.normalize(feat_tar, dim=1, p=2)
|
| 540 |
+
feat_sources = F.normalize(feat_sources, dim=1, p=2)
|
| 541 |
+
|
| 542 |
+
feat_tar = feat_tar.unsqueeze(0).repeat(ncontext, 1, 1)
|
| 543 |
+
aff = torch.exp(torch.bmm(feat_tar, feat_sources) / 0.1) # nmb_context x h*w (tar: query) x h*w (source: keys)
|
| 544 |
+
|
| 545 |
+
if cfg.eval.size_mask_neighborhood > 0:
|
| 546 |
+
if mask_neighborhood is None:
|
| 547 |
+
mask_neighborhood = restrict_neighborhood(cfg, h, w)
|
| 548 |
+
mask_neighborhood = mask_neighborhood.unsqueeze(0).repeat(ncontext, 1, 1)
|
| 549 |
+
aff *= mask_neighborhood
|
| 550 |
+
|
| 551 |
+
aff = aff.transpose(2, 1).reshape(-1, h * w) # nmb_context*h*w (source: keys) x h*w (tar: queries)
|
| 552 |
+
tk_val, _ = torch.topk(aff, dim=0, k=cfg.eval.topk)
|
| 553 |
+
tk_val_min, _ = torch.min(tk_val, dim=0)
|
| 554 |
+
aff[aff < tk_val_min] = 0
|
| 555 |
+
|
| 556 |
+
aff = aff / torch.sum(aff, keepdim=True, axis=0)
|
| 557 |
+
|
| 558 |
+
segs = segs.reshape(nmb_context, C, -1).transpose(2, 1).reshape(-1, C).T # C x nmb_context*h*w
|
| 559 |
+
seg_tar = torch.mm(segs, aff)
|
| 560 |
+
seg_tar = seg_tar.reshape(1, C, h, w)
|
| 561 |
+
return seg_tar, return_feat_tar, mask_neighborhood
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
@torch.no_grad()
|
| 565 |
+
def extract_feature(
|
| 566 |
+
cfg,
|
| 567 |
+
backbone,
|
| 568 |
+
upsampler,
|
| 569 |
+
transform,
|
| 570 |
+
frame,
|
| 571 |
+
mean_bck_tensor,
|
| 572 |
+
std_bck_tensor,
|
| 573 |
+
mean_ups_tensor,
|
| 574 |
+
std_ups_tensor,
|
| 575 |
+
):
|
| 576 |
+
"""Extract one frame feature using JAFAR pipeline."""
|
| 577 |
+
# Transform frame and move to device
|
| 578 |
+
frame_tensor = transform(frame).unsqueeze(0).cuda()
|
| 579 |
+
frame_tensor = F.interpolate(
|
| 580 |
+
frame_tensor,
|
| 581 |
+
size=[v // backbone.config["ps"] * backbone.config["ps"] for v in frame_tensor.shape[-2:]],
|
| 582 |
+
mode="bilinear",
|
| 583 |
+
align_corners=False,
|
| 584 |
+
)
|
| 585 |
+
|
| 586 |
+
# Prepare image for backbone (normalize)
|
| 587 |
+
img_bck = (frame_tensor - mean_bck_tensor) / std_bck_tensor
|
| 588 |
+
lr_feats = backbone(img_bck)
|
| 589 |
+
|
| 590 |
+
# Upsample features
|
| 591 |
+
img_ups = (frame_tensor - mean_ups_tensor) / std_ups_tensor
|
| 592 |
+
|
| 593 |
+
# Resize input for upsampler to match target dimensions
|
| 594 |
+
hr_size = [v * cfg.eval.ups_factor for v in lr_feats.shape[-2:]]
|
| 595 |
+
img_ups = F.interpolate(img_ups, size=hr_size, mode="bicubic", align_corners=False)
|
| 596 |
+
|
| 597 |
+
hr_feats = upsampler(img_ups, lr_feats, hr_size)
|
| 598 |
+
return hr_feats
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
def imwrite_indexed(filename, array, color_palette):
|
| 602 |
+
"""Save indexed png for DAVIS."""
|
| 603 |
+
if np.atleast_3d(array).shape[2] != 1:
|
| 604 |
+
raise Exception("Saving indexed PNGs requires 2D array.")
|
| 605 |
+
|
| 606 |
+
im = Image.fromarray(array)
|
| 607 |
+
im.putpalette(color_palette.ravel())
|
| 608 |
+
im.save(filename, format="PNG")
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
def to_one_hot(y_tensor, n_dims=None):
|
| 612 |
+
"""
|
| 613 |
+
Take integer y (tensor or variable) with n dims &
|
| 614 |
+
convert it to 1-hot representation with n+1 dims.
|
| 615 |
+
"""
|
| 616 |
+
if n_dims is None:
|
| 617 |
+
n_dims = int(y_tensor.max() + 1)
|
| 618 |
+
_, h, w = y_tensor.size()
|
| 619 |
+
y_tensor = y_tensor.type(torch.LongTensor).view(-1, 1)
|
| 620 |
+
n_dims = n_dims if n_dims is not None else int(torch.max(y_tensor)) + 1
|
| 621 |
+
y_one_hot = torch.zeros(y_tensor.size()[0], n_dims).scatter_(1, y_tensor, 1)
|
| 622 |
+
y_one_hot = y_one_hot.view(h, w, n_dims)
|
| 623 |
+
return y_one_hot.permute(2, 0, 1).unsqueeze(0)
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def read_frame_list(video_dir):
|
| 627 |
+
frame_list = [img for img in glob.glob(os.path.join(video_dir, "*.jpg"))]
|
| 628 |
+
frame_list = sorted(frame_list)
|
| 629 |
+
return frame_list
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
def read_frame(frame_dir, ps=None):
|
| 633 |
+
img = Image.open(frame_dir)
|
| 634 |
+
ori_w, ori_h = img.size
|
| 635 |
+
return img, ori_h, ori_w
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
def read_seg(seg_dir, ps, factor):
|
| 639 |
+
seg = Image.open(seg_dir)
|
| 640 |
+
_w, _h = seg.size # note PIL.Image.Image's size is (w, h)
|
| 641 |
+
small_seg = np.array(seg.resize((_w // ps * factor, _h // ps * factor), 0))
|
| 642 |
+
small_seg = torch.from_numpy(small_seg.copy()).contiguous().float().unsqueeze(0)
|
| 643 |
+
return to_one_hot(small_seg), np.asarray(seg)
|
| 644 |
+
|
| 645 |
+
|
| 646 |
+
def run_video_segmentation(cfg, backbone, upsampler, log_print):
|
| 647 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 648 |
+
|
| 649 |
+
transform = transforms.Compose(
|
| 650 |
+
[
|
| 651 |
+
transforms.ToTensor(),
|
| 652 |
+
]
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
color_palette = []
|
| 656 |
+
for line in urlopen("https://raw.githubusercontent.com/Liusifei/UVC/master/libs/data/palette.txt"):
|
| 657 |
+
color_palette.append([int(i) for i in line.decode("utf-8").split("\n")[0].split(" ")])
|
| 658 |
+
color_palette = np.asarray(color_palette, dtype=np.uint8).reshape(-1, 3)
|
| 659 |
+
|
| 660 |
+
video_list = open(os.path.join(cfg.dataset.dataroot, "ImageSets/2017/val.txt")).readlines()
|
| 661 |
+
|
| 662 |
+
start_time = time.time()
|
| 663 |
+
|
| 664 |
+
for i, video_name in enumerate(video_list):
|
| 665 |
+
video_name = video_name.strip()
|
| 666 |
+
log_print(f"[{i}/{len(video_list)}] Begin to segmentate video {video_name}.")
|
| 667 |
+
|
| 668 |
+
video_dir = os.path.join(cfg.dataset.dataroot, "JPEGImages/480p/", video_name)
|
| 669 |
+
frame_list = read_frame_list(video_dir)
|
| 670 |
+
seg_path = frame_list[0].replace("JPEGImages", "Annotations").replace("jpg", "png")
|
| 671 |
+
|
| 672 |
+
# Read segmentation with appropriate scaling
|
| 673 |
+
first_seg, seg_ori = read_seg(seg_path, backbone.config["ps"], cfg.eval.ups_factor)
|
| 674 |
+
|
| 675 |
+
eval_video_tracking_davis(
|
| 676 |
+
cfg, backbone, upsampler, transform, frame_list, video_dir, first_seg, seg_ori, color_palette, log_print
|
| 677 |
+
)
|
| 678 |
+
|
| 679 |
+
end_time = time.time()
|
| 680 |
+
log_print(f"[bold blue]Total time taken: {(end_time - start_time) / 60:.2f} minutes[/bold blue]")
|
| 681 |
+
log_print(f"[bold blue]Max GPU memory used: {torch.cuda.max_memory_allocated() / 1e9:.2f} GB[/bold blue]")
|
| 682 |
+
|
| 683 |
+
# Run comprehensive DAVIS evaluation
|
| 684 |
+
log_print("[bold cyan]Running comprehensive DAVIS eval...[/bold cyan]")
|
| 685 |
+
run_davis_evaluation(cfg, log_print)
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
def run_davis_evaluation(cfg, log_print):
|
| 689 |
+
"""Run comprehensive DAVIS evaluation using the integrated evaluation method"""
|
| 690 |
+
output_dir = HydraConfig.get().runtime.output_dir
|
| 691 |
+
exp_name = f"davis_vidseg_{cfg.eval.ups_factor}_{cfg.model.name}_{cfg.backbone.name}"
|
| 692 |
+
results_path = os.path.join(output_dir, exp_name)
|
| 693 |
+
|
| 694 |
+
if not os.path.exists(results_path):
|
| 695 |
+
log_print(f"[bold red]Results path does not exist: {results_path}[/bold red]")
|
| 696 |
+
return
|
| 697 |
+
|
| 698 |
+
# Check if results already exist
|
| 699 |
+
csv_name_global = "global_results-val.csv"
|
| 700 |
+
csv_name_per_sequence = "per-sequence_results-val.csv"
|
| 701 |
+
csv_name_global_path = os.path.join(results_path, csv_name_global)
|
| 702 |
+
csv_name_per_sequence_path = os.path.join(results_path, csv_name_per_sequence)
|
| 703 |
+
|
| 704 |
+
if os.path.exists(csv_name_global_path) and os.path.exists(csv_name_per_sequence_path):
|
| 705 |
+
log_print("[bold yellow]Using precomputed DAVIS results...[/bold yellow]")
|
| 706 |
+
table_g = pd.read_csv(csv_name_global_path)
|
| 707 |
+
table_seq = pd.read_csv(csv_name_per_sequence_path)
|
| 708 |
+
else:
|
| 709 |
+
log_print("[bold cyan]Computing DAVIS evaluation metrics...[/bold cyan]")
|
| 710 |
+
|
| 711 |
+
# Create dataset and evaluate
|
| 712 |
+
dataset_eval = DAVISEvaluation(davis_root=cfg.dataset.dataroot, task="semi-supervised", gt_set="val")
|
| 713 |
+
metrics_res = dataset_eval.evaluate(results_path)
|
| 714 |
+
|
| 715 |
+
if not metrics_res or "J" not in metrics_res or "F" not in metrics_res:
|
| 716 |
+
log_print("[bold red]DAVIS evaluation failed![/bold red]")
|
| 717 |
+
return
|
| 718 |
+
|
| 719 |
+
J, F = metrics_res["J"], metrics_res["F"]
|
| 720 |
+
|
| 721 |
+
# Generate dataframe for the general results
|
| 722 |
+
g_measures = ["J&F-Mean", "J-Mean", "J-Recall", "J-Decay", "F-Mean", "F-Recall", "F-Decay"]
|
| 723 |
+
final_mean = (np.mean(J["M"]) + np.mean(F["M"])) / 2.0
|
| 724 |
+
g_res = np.array(
|
| 725 |
+
[final_mean, np.mean(J["M"]), np.mean(J["R"]), np.mean(J["D"]), np.mean(F["M"]), np.mean(F["R"]), np.mean(F["D"])]
|
| 726 |
+
)
|
| 727 |
+
g_res = np.reshape(g_res, [1, len(g_res)])
|
| 728 |
+
table_g = pd.DataFrame(data=g_res, columns=g_measures)
|
| 729 |
+
|
| 730 |
+
with open(csv_name_global_path, "w") as f:
|
| 731 |
+
table_g.to_csv(f, index=False, float_format="%.3f")
|
| 732 |
+
log_print(f"[bold green]Global results saved in {csv_name_global_path}[/bold green]")
|
| 733 |
+
|
| 734 |
+
# Generate a dataframe for the per sequence results
|
| 735 |
+
seq_names = list(J["M_per_object"].keys())
|
| 736 |
+
seq_measures = ["Sequence", "J-Mean", "F-Mean"]
|
| 737 |
+
J_per_object = [J["M_per_object"][x] for x in seq_names]
|
| 738 |
+
F_per_object = [F["M_per_object"][x] for x in seq_names]
|
| 739 |
+
table_seq = pd.DataFrame(data=list(zip(seq_names, J_per_object, F_per_object)), columns=seq_measures)
|
| 740 |
+
|
| 741 |
+
with open(csv_name_per_sequence_path, "w") as f:
|
| 742 |
+
table_seq.to_csv(f, index=False, float_format="%.3f")
|
| 743 |
+
log_print(f"[bold green]Per-sequence results saved in {csv_name_per_sequence_path}[/bold green]")
|
| 744 |
+
|
| 745 |
+
# Print the results
|
| 746 |
+
log_print("[bold blue]--------------------------- Global DAVIS Results ---------------------------[/bold blue]")
|
| 747 |
+
log_print(table_g.to_string(index=False))
|
| 748 |
+
log_print("[bold blue]---------- Per-sequence DAVIS Results ----------[/bold blue]")
|
| 749 |
+
log_print(table_seq.to_string(index=False))
|
| 750 |
+
|
| 751 |
+
# Also save as JSON for compatibility
|
| 752 |
+
metrics_dict = table_g.iloc[0].to_dict()
|
| 753 |
+
import json
|
| 754 |
+
|
| 755 |
+
with open(os.path.join(output_dir, "davis_metrics.json"), "w") as f:
|
| 756 |
+
json.dump(metrics_dict, f, indent=2)
|
| 757 |
+
log_print(f"[bold cyan]DAVIS metrics saved to: {os.path.join(output_dir, 'davis_metrics.json')}[/bold cyan]")
|
| 758 |
+
|
| 759 |
+
|
| 760 |
+
@hydra.main(config_path="../config", config_name="eval_video_seg", version_base=None)
|
| 761 |
+
def main(cfg: DictConfig):
|
| 762 |
+
# Setup dual console logging (terminal and file)
|
| 763 |
+
terminal_console = Console()
|
| 764 |
+
current_run_dir = HydraConfig.get().runtime.output_dir
|
| 765 |
+
log_file_path = os.path.join(current_run_dir, "eval_davis.log")
|
| 766 |
+
file_console = Console(file=open(log_file_path, "w"))
|
| 767 |
+
|
| 768 |
+
def log_print(*args, **kwargs):
|
| 769 |
+
"""Log to both terminal and file with immediate flushing"""
|
| 770 |
+
terminal_console.print(*args, **kwargs)
|
| 771 |
+
file_console.print(*args, **kwargs)
|
| 772 |
+
file_console.file.flush()
|
| 773 |
+
|
| 774 |
+
# Start logging
|
| 775 |
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 776 |
+
log_print(f"\n[bold blue]{'='*50}[/bold blue]")
|
| 777 |
+
log_print(f"[bold blue]Starting DAVIS Video Segmentation Evaluation at {timestamp}[/bold blue]")
|
| 778 |
+
log_print(f"[bold green]Configuration:[/bold green]")
|
| 779 |
+
log_print(OmegaConf.to_yaml(cfg))
|
| 780 |
+
|
| 781 |
+
device = "cuda"
|
| 782 |
+
log_print(f"[bold yellow]Using device: {device}[/bold yellow]")
|
| 783 |
+
|
| 784 |
+
# Load backbone and upsampler using Hydra configuration
|
| 785 |
+
backbone_configs = [{"name": cfg.backbone.name}]
|
| 786 |
+
backbones = load_multiple_backbones(cfg, backbone_configs, device="cuda")[0]
|
| 787 |
+
backbone = backbones[0].eval().cuda()
|
| 788 |
+
upsampler = instantiate(cfg.model).eval().cuda()
|
| 789 |
+
|
| 790 |
+
# Load model checkpoint if provided
|
| 791 |
+
if cfg.eval.model_ckpt:
|
| 792 |
+
checkpoint = torch.load(cfg.eval.model_ckpt, map_location=device, weights_only=False)
|
| 793 |
+
if cfg.model.name == "featup":
|
| 794 |
+
new_ckpts = {
|
| 795 |
+
k.replace("model.1.", "norm."): v
|
| 796 |
+
for k, v in checkpoint["state_dict"].items()
|
| 797 |
+
if "upsampler" in k or "model.1.norm" in k
|
| 798 |
+
}
|
| 799 |
+
upsampler.load_state_dict(new_ckpts, strict=True)
|
| 800 |
+
else:
|
| 801 |
+
upsampler.load_state_dict(checkpoint, strict=True)
|
| 802 |
+
log_print(f"[green]Loaded model from checkpoint: {cfg.eval.model_ckpt}[/green]")
|
| 803 |
+
else:
|
| 804 |
+
log_print(f"[yellow]No model checkpoint provided, using untrained model[/yellow]")
|
| 805 |
+
|
| 806 |
+
run_video_segmentation(cfg, backbone, upsampler, log_print)
|
| 807 |
+
|
| 808 |
+
# Log completion before closing file
|
| 809 |
+
log_print(f"[bold blue]Evaluation completed. Log saved to: {log_file_path}[/bold blue]")
|
| 810 |
+
|
| 811 |
+
# Close file console
|
| 812 |
+
file_console.file.close()
|
| 813 |
+
|
| 814 |
+
|
| 815 |
+
if __name__ == "__main__":
|
| 816 |
+
main()
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/hydra_plugins/resolvers.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
from omegaconf import OmegaConf
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def get_feature(target: str) -> int:
|
| 7 |
+
"""Resolve feature dimensions from backbone name patterns"""
|
| 8 |
+
model_name = target.lower()
|
| 9 |
+
|
| 10 |
+
if "vits" in model_name or "small" in model_name:
|
| 11 |
+
return 384
|
| 12 |
+
elif "vitb" in model_name or "base" in model_name or model_name == "radio_v2.5-b":
|
| 13 |
+
return 768
|
| 14 |
+
elif "vitl" in model_name or "large" in model_name or model_name == "radio_v2.5-l":
|
| 15 |
+
return 1024
|
| 16 |
+
elif "tiny" in model_name:
|
| 17 |
+
return 192
|
| 18 |
+
else:
|
| 19 |
+
print(f"Warning: get_feature() - Unsupported backbone: {model_name}. Returning default 0.")
|
| 20 |
+
return 0
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_patch_size(target: str) -> int:
|
| 24 |
+
"""Resolve patch size from backbone name patterns using regex"""
|
| 25 |
+
model_name = target.lower()
|
| 26 |
+
|
| 27 |
+
# Return a default value for consistency
|
| 28 |
+
if "franca" in model_name:
|
| 29 |
+
return 14
|
| 30 |
+
|
| 31 |
+
# Use regex to find patchXX pattern
|
| 32 |
+
match = re.search(r"patch(\d+)", model_name)
|
| 33 |
+
if match:
|
| 34 |
+
return int(match.group(1))
|
| 35 |
+
|
| 36 |
+
# Default to 16 if no patch size found
|
| 37 |
+
return 16
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
OmegaConf.register_new_resolver("get_feature", lambda name: get_feature(name))
|
| 41 |
+
OmegaConf.register_new_resolver("get_patch_size", lambda name: get_patch_size(name))
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/notebooks/attention_maps.ipynb
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "fa359c75",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# NAF: Neighborhood Attention Map\n",
|
| 9 |
+
"\n",
|
| 10 |
+
"This notebook provides tools to inspect and visualize attention weights from the NAF model.\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"**Features:**\n",
|
| 13 |
+
"- Load pre-trained NAF model and test images\n",
|
| 14 |
+
"- Select query points on the image\n",
|
| 15 |
+
"- Extract and visualize attention weights for the selected position\n",
|
| 16 |
+
"- Compare attention patterns across different image regions"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "markdown",
|
| 21 |
+
"id": "13d519e9",
|
| 22 |
+
"metadata": {},
|
| 23 |
+
"source": [
|
| 24 |
+
"## Setup and Imports"
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"cell_type": "code",
|
| 29 |
+
"execution_count": null,
|
| 30 |
+
"id": "fdfa00f8",
|
| 31 |
+
"metadata": {},
|
| 32 |
+
"outputs": [],
|
| 33 |
+
"source": [
|
| 34 |
+
"# Use inline matplotlib for better compatibility\n",
|
| 35 |
+
"%matplotlib widget"
|
| 36 |
+
]
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"cell_type": "code",
|
| 40 |
+
"execution_count": null,
|
| 41 |
+
"id": "c5aaceb7",
|
| 42 |
+
"metadata": {},
|
| 43 |
+
"outputs": [],
|
| 44 |
+
"source": [
|
| 45 |
+
"import sys\n",
|
| 46 |
+
"sys.path.append('..')\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"import torch\n",
|
| 49 |
+
"import numpy as np\n",
|
| 50 |
+
"import matplotlib.pyplot as plt\n",
|
| 51 |
+
"import matplotlib.patches as patches\n",
|
| 52 |
+
"from PIL import Image\n",
|
| 53 |
+
"import torchvision.transforms as T\n",
|
| 54 |
+
"\n",
|
| 55 |
+
"from hydra import compose, initialize\n",
|
| 56 |
+
"from hydra.core.global_hydra import GlobalHydra\n",
|
| 57 |
+
"from hydra.utils import instantiate\n",
|
| 58 |
+
"from IPython.display import clear_output\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"from src.model.naf import NAF\n",
|
| 61 |
+
"from utils.training import load_multiple_backbones\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n",
|
| 64 |
+
"print(f\"Using device: {device}\")"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"cell_type": "markdown",
|
| 69 |
+
"id": "2417fe5b",
|
| 70 |
+
"metadata": {},
|
| 71 |
+
"source": [
|
| 72 |
+
"## 2. Load Model and Configuration"
|
| 73 |
+
]
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"cell_type": "code",
|
| 77 |
+
"execution_count": null,
|
| 78 |
+
"id": "5139fa03",
|
| 79 |
+
"metadata": {},
|
| 80 |
+
"outputs": [],
|
| 81 |
+
"source": [
|
| 82 |
+
"# Initialize Hydra config\n",
|
| 83 |
+
"if not GlobalHydra.instance().is_initialized():\n",
|
| 84 |
+
" initialize(config_path=\"../config\", version_base=None)\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"cfg = compose(config_name=\"base\", overrides=[\"model=naf\", \"img_size=1024\"])\n",
|
| 87 |
+
"\n",
|
| 88 |
+
"# Initialize NAF model\n",
|
| 89 |
+
"model = torch.hub.load(\"valeoai/NAF\", \"naf\", pretrained=True, device=device)\n",
|
| 90 |
+
"model.eval()\n",
|
| 91 |
+
"\n",
|
| 92 |
+
"# Load backbone\n",
|
| 93 |
+
"backbone_configs = [{\"name\": \"vit_base_patch16_dinov3.lvd1689m\"}]\n",
|
| 94 |
+
"backbones, names, _ = load_multiple_backbones(cfg, backbone_configs, device)\n",
|
| 95 |
+
"backbone = backbones[0]\n",
|
| 96 |
+
"backbone.eval()\n",
|
| 97 |
+
"\n",
|
| 98 |
+
"print(f\"Model loaded: {type(model).__name__}\")\n",
|
| 99 |
+
"print(f\"Backbone loaded: {names[0]}\")\n",
|
| 100 |
+
"print(f\"Kernel size: {model.upsampler.kernel_size}\")"
|
| 101 |
+
]
|
| 102 |
+
},
|
| 103 |
+
{
|
| 104 |
+
"cell_type": "markdown",
|
| 105 |
+
"id": "b7ae72e4",
|
| 106 |
+
"metadata": {},
|
| 107 |
+
"source": [
|
| 108 |
+
"## 3. Load and Preprocess Image"
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"cell_type": "code",
|
| 113 |
+
"execution_count": null,
|
| 114 |
+
"id": "ae219175",
|
| 115 |
+
"metadata": {},
|
| 116 |
+
"outputs": [],
|
| 117 |
+
"source": [
|
| 118 |
+
"# Load image\n",
|
| 119 |
+
"image_path = \"../asset/dog0.jpg\"\n",
|
| 120 |
+
"pil_image = Image.open(image_path).convert(\"RGB\")\n",
|
| 121 |
+
"\n",
|
| 122 |
+
"# Resize to model input size\n",
|
| 123 |
+
"H, W = cfg.img_size, cfg.img_size\n",
|
| 124 |
+
"transform = T.Compose([T.Resize((H, W)), T.ToTensor()])\n",
|
| 125 |
+
"image = transform(pil_image).unsqueeze(0).to(device)\n",
|
| 126 |
+
"\n",
|
| 127 |
+
"# Normalize for backbone\n",
|
| 128 |
+
"mean_bck = backbone.config[\"mean\"]\n",
|
| 129 |
+
"std_bck = backbone.config[\"std\"]\n",
|
| 130 |
+
"normalize_bck = T.Normalize(mean=mean_bck, std=std_bck)\n",
|
| 131 |
+
"image_bck = normalize_bck(image)\n",
|
| 132 |
+
"\n",
|
| 133 |
+
"# Normalize for upsampler (ImageNet normalization)\n",
|
| 134 |
+
"normalize_ups = T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n",
|
| 135 |
+
"image_ups = normalize_ups(image)\n",
|
| 136 |
+
"\n",
|
| 137 |
+
"# Convert to display format\n",
|
| 138 |
+
"image_vis = (image.clone().cpu().squeeze(0).permute(1, 2, 0).numpy() * 255).astype(np.uint8)\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"print(f\"Loaded image from: {image_path}\")\n",
|
| 141 |
+
"print(f\"Image size: {image.shape}\")"
|
| 142 |
+
]
|
| 143 |
+
},
|
| 144 |
+
{
|
| 145 |
+
"cell_type": "markdown",
|
| 146 |
+
"id": "0457092d",
|
| 147 |
+
"metadata": {},
|
| 148 |
+
"source": [
|
| 149 |
+
"## 4. Extract Backbone Features"
|
| 150 |
+
]
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"cell_type": "code",
|
| 154 |
+
"execution_count": null,
|
| 155 |
+
"id": "784c862e",
|
| 156 |
+
"metadata": {},
|
| 157 |
+
"outputs": [],
|
| 158 |
+
"source": [
|
| 159 |
+
"# Extract features from backbone\n",
|
| 160 |
+
"with torch.no_grad():\n",
|
| 161 |
+
" hr_feats = backbone(image_bck)\n",
|
| 162 |
+
"\n",
|
| 163 |
+
"# Define output size for encoder (match backbone output)\n",
|
| 164 |
+
"output_size = (hr_feats.shape[2], hr_feats.shape[3])\n",
|
| 165 |
+
"\n",
|
| 166 |
+
"# Compute scaling factors between image and feature space\n",
|
| 167 |
+
"scale_h = output_size[0] / H\n",
|
| 168 |
+
"scale_w = output_size[1] / W\n",
|
| 169 |
+
"\n",
|
| 170 |
+
"print(f\"Backbone features shape: {hr_feats.shape}\")\n",
|
| 171 |
+
"print(f\"Feature map size: {output_size}\")\n",
|
| 172 |
+
"print(f\"Scaling factors - h: {scale_h:.3f}, w: {scale_w:.3f}\")"
|
| 173 |
+
]
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"cell_type": "markdown",
|
| 177 |
+
"id": "d4a80c95",
|
| 178 |
+
"metadata": {},
|
| 179 |
+
"source": [
|
| 180 |
+
"## 5. Helper Functions for Query Position and Attention Extraction"
|
| 181 |
+
]
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"cell_type": "code",
|
| 185 |
+
"execution_count": null,
|
| 186 |
+
"id": "a62a3b4a",
|
| 187 |
+
"metadata": {},
|
| 188 |
+
"outputs": [],
|
| 189 |
+
"source": [
|
| 190 |
+
"def extract_attention_at_position(query_img_h, query_img_w):\n",
|
| 191 |
+
" \"\"\"\n",
|
| 192 |
+
" Extract attention weights for a query position.\n",
|
| 193 |
+
" \n",
|
| 194 |
+
" Args:\n",
|
| 195 |
+
" query_img_h: Query position height in image coordinates (0-447)\n",
|
| 196 |
+
" query_img_w: Query position width in image coordinates (0-447)\n",
|
| 197 |
+
" \n",
|
| 198 |
+
" Returns:\n",
|
| 199 |
+
" attn_map: 2D attention map of shape (kernel_h, kernel_w)\n",
|
| 200 |
+
" pos_feat: Query position in feature space\n",
|
| 201 |
+
" \"\"\"\n",
|
| 202 |
+
" # Convert image coordinates to feature space\n",
|
| 203 |
+
" pos_feat = (int(query_img_h * scale_h), int(query_img_w * scale_w))\n",
|
| 204 |
+
" \n",
|
| 205 |
+
" # Extract attention weights\n",
|
| 206 |
+
" with torch.no_grad():\n",
|
| 207 |
+
" # Get RoPE-transformed features (queries)\n",
|
| 208 |
+
" _, attn_weights = model(image_ups, hr_feats, output_size=output_size, return_weights=True)\n",
|
| 209 |
+
" \n",
|
| 210 |
+
" # Extract attention for the query position\n",
|
| 211 |
+
" # attn_weights: [B, num_heads, H, W, kernel_h * kernel_w]\n",
|
| 212 |
+
" query_h, query_w = pos_feat\n",
|
| 213 |
+
" attn_at_query = attn_weights[0, :, query_h, query_w, :] # [num_heads, kernel_h*kernel_w]\n",
|
| 214 |
+
" \n",
|
| 215 |
+
" # Average across heads\n",
|
| 216 |
+
" attn_mean = attn_at_query.mean(dim=0) # [kernel_h*kernel_w]\n",
|
| 217 |
+
" \n",
|
| 218 |
+
" # Reshape to 2D attention map\n",
|
| 219 |
+
" kernel_h, kernel_w = model.upsampler.kernel_size\n",
|
| 220 |
+
" attn_map = attn_mean.view(kernel_h, kernel_w).cpu().numpy()\n",
|
| 221 |
+
" \n",
|
| 222 |
+
" return attn_map, pos_feat\n",
|
| 223 |
+
"\n",
|
| 224 |
+
"\n",
|
| 225 |
+
"print(\"Helper functions loaded\")"
|
| 226 |
+
]
|
| 227 |
+
},
|
| 228 |
+
{
|
| 229 |
+
"cell_type": "markdown",
|
| 230 |
+
"id": "f289e60c",
|
| 231 |
+
"metadata": {},
|
| 232 |
+
"source": [
|
| 233 |
+
"## 6. Visualize Attention for a Single Query Point"
|
| 234 |
+
]
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
"cell_type": "code",
|
| 238 |
+
"execution_count": null,
|
| 239 |
+
"id": "ccd18126",
|
| 240 |
+
"metadata": {},
|
| 241 |
+
"outputs": [],
|
| 242 |
+
"source": [
|
| 243 |
+
"# Interactive attention visualization\n",
|
| 244 |
+
"fig, axes = plt.subplots(1, 3, figsize=(12*2/3, 4*2/3))\n",
|
| 245 |
+
"\n",
|
| 246 |
+
"# Initialize with default position\n",
|
| 247 |
+
"query_img_h, query_img_w = 650, 270\n",
|
| 248 |
+
"\n",
|
| 249 |
+
"# Plot elements that will be updated\n",
|
| 250 |
+
"img_display = axes[0].imshow(image_vis)\n",
|
| 251 |
+
"query_point, = axes[0].plot([], [], 'r*', markersize=15, markeredgecolor='white', markeredgewidth=1)\n",
|
| 252 |
+
"rect_patch = patches.Rectangle((0, 0), 0, 0, linewidth=2, edgecolor='red', facecolor='none', linestyle='--')\n",
|
| 253 |
+
"axes[0].add_patch(rect_patch)\n",
|
| 254 |
+
"\n",
|
| 255 |
+
"axes[0].set_xlabel('Image Width')\n",
|
| 256 |
+
"axes[0].set_ylabel('Image Height')\n",
|
| 257 |
+
"axes[0].set_title('Click on image to select query point')\n",
|
| 258 |
+
"axes[0].set_aspect('equal')\n",
|
| 259 |
+
"\n",
|
| 260 |
+
"# Attention heatmap (will be updated)\n",
|
| 261 |
+
"attn_display = axes[1].imshow(np.zeros((5, 5)), cmap='RdBu_r', aspect='equal')\n",
|
| 262 |
+
"axes[1].set_xlabel('Kernel Width')\n",
|
| 263 |
+
"axes[1].set_ylabel('Kernel Height')\n",
|
| 264 |
+
"axes[1].set_title('Attention Map')\n",
|
| 265 |
+
"\n",
|
| 266 |
+
"cbar1 = plt.colorbar(attn_display, ax=axes[1], fraction=0.046, pad=0.04)\n",
|
| 267 |
+
"cbar1.set_label('Attention Weight')\n",
|
| 268 |
+
"\n",
|
| 269 |
+
"# Overlay visualization\n",
|
| 270 |
+
"overlay_display = axes[2].imshow(np.zeros((10, 10, 3), dtype=np.uint8))\n",
|
| 271 |
+
"axes[2].set_xlabel('Width')\n",
|
| 272 |
+
"axes[2].set_ylabel('Height')\n",
|
| 273 |
+
"axes[2].set_title('Attention Overlay on Neighborhood')\n",
|
| 274 |
+
"axes[2].set_aspect('equal')\n",
|
| 275 |
+
"\n",
|
| 276 |
+
"# Statistics text\n",
|
| 277 |
+
"stats_text = axes[1].text(0.02, 0.98, '', transform=axes[1].transAxes, \n",
|
| 278 |
+
" verticalalignment='top', fontsize=8, \n",
|
| 279 |
+
" bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5))\n",
|
| 280 |
+
"\n",
|
| 281 |
+
"def update_attention(query_h, query_w):\n",
|
| 282 |
+
" \"\"\"Update the attention visualization for a new query position.\"\"\"\n",
|
| 283 |
+
" # Extract attention map\n",
|
| 284 |
+
" attn_map, pos_feat = extract_attention_at_position(query_h, query_w)\n",
|
| 285 |
+
" kernel_h, kernel_w = model.upsampler.kernel_size\n",
|
| 286 |
+
" \n",
|
| 287 |
+
" # Update query point\n",
|
| 288 |
+
" query_point.set_data([query_w], [query_h])\n",
|
| 289 |
+
" \n",
|
| 290 |
+
" # Update neighborhood rectangle\n",
|
| 291 |
+
" half_h, half_w = kernel_h // 2, kernel_w // 2\n",
|
| 292 |
+
" box_x0 = max(0, query_w - int(half_w / scale_w))\n",
|
| 293 |
+
" box_y0 = max(0, query_h - int(half_h / scale_h))\n",
|
| 294 |
+
" box_x1 = min(W, query_w + int(half_w / scale_w))\n",
|
| 295 |
+
" box_y1 = min(H, query_h + int(half_h / scale_h))\n",
|
| 296 |
+
" \n",
|
| 297 |
+
" rect_patch.set_xy((box_x0, box_y0))\n",
|
| 298 |
+
" rect_patch.set_width(box_x1 - box_x0)\n",
|
| 299 |
+
" rect_patch.set_height(box_y1 - box_y0)\n",
|
| 300 |
+
" \n",
|
| 301 |
+
" # Update attention heatmap\n",
|
| 302 |
+
" attn_display.set_data(attn_map)\n",
|
| 303 |
+
" attn_display.set_clim(vmin=attn_map.min(), vmax=attn_map.max())\n",
|
| 304 |
+
" axes[1].set_title(f'Attention Map ({kernel_h}×{kernel_w})')\n",
|
| 305 |
+
" \n",
|
| 306 |
+
" # Create overlay visualization\n",
|
| 307 |
+
" # Extract neighborhood from image\n",
|
| 308 |
+
" neighborhood = image_vis[box_y0:box_y1, box_x0:box_x1, :]\n",
|
| 309 |
+
" \n",
|
| 310 |
+
" # Resize attention map to match neighborhood size\n",
|
| 311 |
+
" from scipy.ndimage import zoom\n",
|
| 312 |
+
" attn_resized = zoom(attn_map, (neighborhood.shape[0] / attn_map.shape[0], \n",
|
| 313 |
+
" neighborhood.shape[1] / attn_map.shape[1]), order=1)\n",
|
| 314 |
+
" \n",
|
| 315 |
+
" # Normalize attention to 0-1 for overlay\n",
|
| 316 |
+
" attn_norm = (attn_resized - attn_resized.min()) / (attn_resized.max() - attn_resized.min() + 1e-8)\n",
|
| 317 |
+
" \n",
|
| 318 |
+
" # Create overlay: blend image with attention heatmap\n",
|
| 319 |
+
" overlay = neighborhood.copy().astype(float)\n",
|
| 320 |
+
" \n",
|
| 321 |
+
" # Apply colormap to attention (red-blue)\n",
|
| 322 |
+
" import matplotlib.cm as cm\n",
|
| 323 |
+
" attn_colored = cm.RdBu_r(attn_norm)[:, :, :3] * 255 # RGB only\n",
|
| 324 |
+
" \n",
|
| 325 |
+
" # Blend: 50% image, 50% attention heatmap\n",
|
| 326 |
+
" overlay_blended = (0.5 * overlay + 0.5 * attn_colored).astype(np.uint8)\n",
|
| 327 |
+
" \n",
|
| 328 |
+
" # Update overlay display\n",
|
| 329 |
+
" overlay_display.set_data(overlay_blended)\n",
|
| 330 |
+
" overlay_display.set_extent([box_x0, box_x1, box_y1, box_y0])\n",
|
| 331 |
+
" axes[2].set_xlim(box_x0, box_x1)\n",
|
| 332 |
+
" axes[2].set_ylim(box_y1, box_y0)\n",
|
| 333 |
+
" axes[2].set_title('Attention Overlay on Neighborhood')\n",
|
| 334 |
+
" \n",
|
| 335 |
+
" # Update statistics text\n",
|
| 336 |
+
" fig.canvas.draw_idle()\n",
|
| 337 |
+
" \n",
|
| 338 |
+
" # Print to console\n",
|
| 339 |
+
" print(f\"Query: ({query_h}, {query_w}) | Feature: {pos_feat} | Center attn: {attn_map[kernel_h//2, kernel_w//2]:.4f}\")\n",
|
| 340 |
+
"\n",
|
| 341 |
+
"def onclick(event):\n",
|
| 342 |
+
" \"\"\"Handle click events on the image.\"\"\"\n",
|
| 343 |
+
" if event.inaxes == axes[0] and event.xdata is not None and event.ydata is not None:\n",
|
| 344 |
+
" new_w = int(np.clip(event.xdata, 0, W-1))\n",
|
| 345 |
+
" new_h = int(np.clip(event.ydata, 0, H-1))\n",
|
| 346 |
+
" update_attention(new_h, new_w)\n",
|
| 347 |
+
"\n",
|
| 348 |
+
"fig.canvas.mpl_connect('button_press_event', onclick)\n",
|
| 349 |
+
"\n",
|
| 350 |
+
"plt.tight_layout()\n",
|
| 351 |
+
"\n",
|
| 352 |
+
"# Initial visualization\n",
|
| 353 |
+
"update_attention(query_img_h, query_img_w)\n",
|
| 354 |
+
"plt.show()\n",
|
| 355 |
+
"\n",
|
| 356 |
+
"print(\"Click on the left image to explore attention patterns!\")"
|
| 357 |
+
]
|
| 358 |
+
},
|
| 359 |
+
{
|
| 360 |
+
"cell_type": "code",
|
| 361 |
+
"execution_count": null,
|
| 362 |
+
"id": "04e96127",
|
| 363 |
+
"metadata": {},
|
| 364 |
+
"outputs": [],
|
| 365 |
+
"source": []
|
| 366 |
+
}
|
| 367 |
+
],
|
| 368 |
+
"metadata": {
|
| 369 |
+
"kernelspec": {
|
| 370 |
+
"display_name": "naf",
|
| 371 |
+
"language": "python",
|
| 372 |
+
"name": "python3"
|
| 373 |
+
},
|
| 374 |
+
"language_info": {
|
| 375 |
+
"codemirror_mode": {
|
| 376 |
+
"name": "ipython",
|
| 377 |
+
"version": 3
|
| 378 |
+
},
|
| 379 |
+
"file_extension": ".py",
|
| 380 |
+
"mimetype": "text/x-python",
|
| 381 |
+
"name": "python",
|
| 382 |
+
"nbconvert_exporter": "python",
|
| 383 |
+
"pygments_lexer": "ipython3",
|
| 384 |
+
"version": "3.10.14"
|
| 385 |
+
}
|
| 386 |
+
},
|
| 387 |
+
"nbformat": 4,
|
| 388 |
+
"nbformat_minor": 5
|
| 389 |
+
}
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/notebooks/inference.ipynb
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# NAF: Zero-Shot Feature Upsampling via Neighborhood Attention Filtering.\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"In this notebook we present how to upsample **Any Feature from Any VFM at Any Resolution**"
|
| 10 |
+
]
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"cell_type": "markdown",
|
| 14 |
+
"metadata": {},
|
| 15 |
+
"source": [
|
| 16 |
+
"## Setup"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "markdown",
|
| 21 |
+
"metadata": {},
|
| 22 |
+
"source": [
|
| 23 |
+
"Imports"
|
| 24 |
+
]
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
"cell_type": "code",
|
| 28 |
+
"execution_count": null,
|
| 29 |
+
"metadata": {},
|
| 30 |
+
"outputs": [],
|
| 31 |
+
"source": [
|
| 32 |
+
"import random\n",
|
| 33 |
+
"import sys\n",
|
| 34 |
+
"from pathlib import Path\n",
|
| 35 |
+
"\n",
|
| 36 |
+
"import matplotlib.pyplot as plt\n",
|
| 37 |
+
"import torch\n",
|
| 38 |
+
"import PIL\n",
|
| 39 |
+
"import torch.nn.functional as F\n",
|
| 40 |
+
"import torchvision.transforms as T\n",
|
| 41 |
+
"from hydra import compose, initialize\n",
|
| 42 |
+
"from hydra.core.global_hydra import GlobalHydra\n",
|
| 43 |
+
"from hydra.utils import instantiate\n",
|
| 44 |
+
"from IPython.display import clear_output\n",
|
| 45 |
+
"\n",
|
| 46 |
+
"project_root = str(Path().absolute().parent)\n",
|
| 47 |
+
"sys.path.append(project_root)\n",
|
| 48 |
+
"\n",
|
| 49 |
+
"from utils.training import get_batch, get_dataloaders, load_multiple_backbones\n",
|
| 50 |
+
"from utils.visualization import plot_feats"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"cell_type": "markdown",
|
| 55 |
+
"metadata": {},
|
| 56 |
+
"source": [
|
| 57 |
+
"Load config"
|
| 58 |
+
]
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"cell_type": "code",
|
| 62 |
+
"execution_count": null,
|
| 63 |
+
"metadata": {},
|
| 64 |
+
"outputs": [],
|
| 65 |
+
"source": [
|
| 66 |
+
"if not GlobalHydra.instance().is_initialized():\n",
|
| 67 |
+
" initialize(config_path=\"../config\", version_base=None)\n",
|
| 68 |
+
"\n",
|
| 69 |
+
"overrides = [\"val_dataloader.batch_size=1\", \n",
|
| 70 |
+
" \"train_dataloader.batch_size=1\", \n",
|
| 71 |
+
" \"model=naf\", \n",
|
| 72 |
+
" \"img_size=448\"]\n",
|
| 73 |
+
"\n",
|
| 74 |
+
"cfg = compose(config_name=\"base\", overrides=overrides)\n",
|
| 75 |
+
"clear_output()"
|
| 76 |
+
]
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"cell_type": "markdown",
|
| 80 |
+
"metadata": {},
|
| 81 |
+
"source": [
|
| 82 |
+
"Dataloader"
|
| 83 |
+
]
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"cell_type": "code",
|
| 87 |
+
"execution_count": null,
|
| 88 |
+
"metadata": {},
|
| 89 |
+
"outputs": [],
|
| 90 |
+
"source": [
|
| 91 |
+
"train_dataloader, val_dataloader = get_dataloaders(cfg, shuffle=True)\n",
|
| 92 |
+
"clear_output()"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "markdown",
|
| 97 |
+
"metadata": {},
|
| 98 |
+
"source": [
|
| 99 |
+
"Model"
|
| 100 |
+
]
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"cell_type": "code",
|
| 104 |
+
"execution_count": null,
|
| 105 |
+
"metadata": {},
|
| 106 |
+
"outputs": [],
|
| 107 |
+
"source": [
|
| 108 |
+
"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
|
| 109 |
+
"model = torch.hub.load(\"valeoai/NAF\", \"naf\", pretrained=True, device=device)\n",
|
| 110 |
+
"model.cuda()\n",
|
| 111 |
+
"\n",
|
| 112 |
+
"clear_output()"
|
| 113 |
+
]
|
| 114 |
+
},
|
| 115 |
+
{
|
| 116 |
+
"cell_type": "markdown",
|
| 117 |
+
"metadata": {},
|
| 118 |
+
"source": [
|
| 119 |
+
"Data"
|
| 120 |
+
]
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"cell_type": "code",
|
| 124 |
+
"execution_count": null,
|
| 125 |
+
"metadata": {},
|
| 126 |
+
"outputs": [],
|
| 127 |
+
"source": [
|
| 128 |
+
"# Either iterate over the dataloader\n",
|
| 129 |
+
"batch = next(iter(val_dataloader))\n",
|
| 130 |
+
"batch = get_batch(batch, device)\n",
|
| 131 |
+
"img_batch = batch[\"image\"]\n",
|
| 132 |
+
"bs = img_batch.shape[0]\n",
|
| 133 |
+
"IMG_PATH = None\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"# Or load a custom image\n",
|
| 136 |
+
"IMG_PATH = \"../asset/dinov3.png\"\n",
|
| 137 |
+
"img_batch = PIL.Image.open(IMG_PATH).convert(\"RGB\")\n",
|
| 138 |
+
"img_batch = val_dataloader.dataset.transform(img_batch).unsqueeze(0).to(device)"
|
| 139 |
+
]
|
| 140 |
+
},
|
| 141 |
+
{
|
| 142 |
+
"cell_type": "markdown",
|
| 143 |
+
"metadata": {},
|
| 144 |
+
"source": [
|
| 145 |
+
"Backbone"
|
| 146 |
+
]
|
| 147 |
+
},
|
| 148 |
+
{
|
| 149 |
+
"cell_type": "code",
|
| 150 |
+
"execution_count": null,
|
| 151 |
+
"metadata": {},
|
| 152 |
+
"outputs": [],
|
| 153 |
+
"source": [
|
| 154 |
+
"@torch.no_grad()\n",
|
| 155 |
+
"def upsample_backbone(backbone, img, model, mean_std_bck, mean_std_ups, sizes=[512]):\n",
|
| 156 |
+
" torch.cuda.empty_cache()\n",
|
| 157 |
+
" model.eval()\n",
|
| 158 |
+
" mean_bck, std_bck = mean_std_bck\n",
|
| 159 |
+
" mean_ups, std_ups = mean_std_ups\n",
|
| 160 |
+
"\n",
|
| 161 |
+
" img_bck = T.functional.normalize(img, mean=mean_bck, std=std_bck)\n",
|
| 162 |
+
" img_ups = T.functional.normalize(img, mean=mean_ups, std=std_ups)\n",
|
| 163 |
+
"\n",
|
| 164 |
+
" hr_feats = backbone(img_bck)\n",
|
| 165 |
+
" hr_size = hr_feats.shape[-1]\n",
|
| 166 |
+
" preds = []\n",
|
| 167 |
+
" for size in sizes:\n",
|
| 168 |
+
" pred = model(img_ups, hr_feats, (size, size))\n",
|
| 169 |
+
" preds.append(pred)\n",
|
| 170 |
+
"\n",
|
| 171 |
+
" hr_feats_ = torch.nn.functional.interpolate(hr_feats, size, mode=\"nearest-exact\")\n",
|
| 172 |
+
" plot_feats(\n",
|
| 173 |
+
" img_batch[0],\n",
|
| 174 |
+
" hr_feats_[0],\n",
|
| 175 |
+
" [p[0] for p in preds],\n",
|
| 176 |
+
" legend=[f\"Input\", f\"{hr_size}x{hr_size}\"] + [f\"{size}x{size}\" for size in sizes],\n",
|
| 177 |
+
" font_size=20,\n",
|
| 178 |
+
" )\n",
|
| 179 |
+
" plt.show()\n",
|
| 180 |
+
" torch.cuda.empty_cache()"
|
| 181 |
+
]
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"cell_type": "markdown",
|
| 185 |
+
"metadata": {},
|
| 186 |
+
"source": [
|
| 187 |
+
"## Any Backbone"
|
| 188 |
+
]
|
| 189 |
+
},
|
| 190 |
+
{
|
| 191 |
+
"cell_type": "code",
|
| 192 |
+
"execution_count": null,
|
| 193 |
+
"metadata": {},
|
| 194 |
+
"outputs": [],
|
| 195 |
+
"source": [
|
| 196 |
+
"backbone_configs = [\n",
|
| 197 |
+
" {\"name\": \"vit_base_patch16_dinov3.lvd1689m\"},\n",
|
| 198 |
+
" {\"name\": \"radio_v2.5-b\"},\n",
|
| 199 |
+
" {\"name\": \"franca_vitb14\"},\n",
|
| 200 |
+
" {\"name\": \"vit_base_patch14_reg4_dinov2\"},\n",
|
| 201 |
+
" {\"name\": \"vit_base_patch14_dinov2.lvd142m\"},\n",
|
| 202 |
+
"]\n",
|
| 203 |
+
"mapping = {\n",
|
| 204 |
+
" \"vit_base_patch16_dinov3.lvd1689m\": \"Dinov3-B\",\n",
|
| 205 |
+
" \"radio_v2.5-b\": \"Radio-v2.5-B\",\n",
|
| 206 |
+
" \"franca_vitb14\": \"Franca-B14\",\n",
|
| 207 |
+
" \"vit_base_patch14_reg4_dinov2\": \"Dinov2-R-B\",\n",
|
| 208 |
+
" \"vit_base_patch14_dinov2.lvd142m\": \"Dinov2-B\",\n",
|
| 209 |
+
"}\n",
|
| 210 |
+
"backbones, *_ = load_multiple_backbones(cfg, backbone_configs, device)\n",
|
| 211 |
+
"clear_output()"
|
| 212 |
+
]
|
| 213 |
+
},
|
| 214 |
+
{
|
| 215 |
+
"cell_type": "code",
|
| 216 |
+
"execution_count": null,
|
| 217 |
+
"metadata": {},
|
| 218 |
+
"outputs": [],
|
| 219 |
+
"source": [
|
| 220 |
+
"mean_ups, std_ups = (0.485, 0.456, 0.406), (0.229, 0.224, 0.225)\n",
|
| 221 |
+
"for backbone in backbones:\n",
|
| 222 |
+
" backbone.cuda().eval()\n",
|
| 223 |
+
"\n",
|
| 224 |
+
" mean_bck, std_bck = backbone.config[\"mean\"], backbone.config[\"std\"]\n",
|
| 225 |
+
"\n",
|
| 226 |
+
" print(f\"BACKBONE: {mapping[backbone_configs[backbones.index(backbone)]['name']].upper()}\")\n",
|
| 227 |
+
" upsample_backbone(backbone, img_batch, model, (mean_bck, std_bck), (mean_ups, std_ups), sizes=[448])"
|
| 228 |
+
]
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"cell_type": "markdown",
|
| 232 |
+
"metadata": {},
|
| 233 |
+
"source": [
|
| 234 |
+
"## Any Resolution"
|
| 235 |
+
]
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"cell_type": "code",
|
| 239 |
+
"execution_count": null,
|
| 240 |
+
"metadata": {},
|
| 241 |
+
"outputs": [],
|
| 242 |
+
"source": [
|
| 243 |
+
"backbone_configs = [\n",
|
| 244 |
+
" {\"name\": \"vit_base_patch16_dinov3.lvd1689m\"},\n",
|
| 245 |
+
"]\n",
|
| 246 |
+
"backbones, *_ = load_multiple_backbones(cfg, backbone_configs, device)\n",
|
| 247 |
+
"clear_output()"
|
| 248 |
+
]
|
| 249 |
+
},
|
| 250 |
+
{
|
| 251 |
+
"cell_type": "code",
|
| 252 |
+
"execution_count": null,
|
| 253 |
+
"metadata": {},
|
| 254 |
+
"outputs": [],
|
| 255 |
+
"source": [
|
| 256 |
+
"backbone = backbones[0]\n",
|
| 257 |
+
"backbone.cuda().eval()\n",
|
| 258 |
+
"\n",
|
| 259 |
+
"mean_bck, std_bck = backbone.config[\"mean\"], backbone.config[\"std\"]\n",
|
| 260 |
+
"\n",
|
| 261 |
+
"print(f\"BACKBONE: {backbone_configs[backbones.index(backbone)]['name']}\")\n",
|
| 262 |
+
"upsample_backbone(backbone, img_batch, model, (mean_bck, std_bck), (mean_ups, std_ups), sizes=[64, 128, 256, 512, 1024])"
|
| 263 |
+
]
|
| 264 |
+
},
|
| 265 |
+
{
|
| 266 |
+
"cell_type": "code",
|
| 267 |
+
"execution_count": null,
|
| 268 |
+
"metadata": {},
|
| 269 |
+
"outputs": [],
|
| 270 |
+
"source": []
|
| 271 |
+
}
|
| 272 |
+
],
|
| 273 |
+
"metadata": {
|
| 274 |
+
"kernelspec": {
|
| 275 |
+
"display_name": "naf",
|
| 276 |
+
"language": "python",
|
| 277 |
+
"name": "python3"
|
| 278 |
+
},
|
| 279 |
+
"language_info": {
|
| 280 |
+
"codemirror_mode": {
|
| 281 |
+
"name": "ipython",
|
| 282 |
+
"version": 3
|
| 283 |
+
},
|
| 284 |
+
"file_extension": ".py",
|
| 285 |
+
"mimetype": "text/x-python",
|
| 286 |
+
"name": "python",
|
| 287 |
+
"nbconvert_exporter": "python",
|
| 288 |
+
"pygments_lexer": "ipython3",
|
| 289 |
+
"version": "3.10.14"
|
| 290 |
+
}
|
| 291 |
+
},
|
| 292 |
+
"nbformat": 4,
|
| 293 |
+
"nbformat_minor": 4
|
| 294 |
+
}
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/backbone/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .vit_wrapper import PretrainedViTWrapper
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/backbone/vit_wrapper.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import types
|
| 3 |
+
from typing import List, Tuple, Union
|
| 4 |
+
|
| 5 |
+
import timm
|
| 6 |
+
import timm.data
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
from einops import rearrange
|
| 10 |
+
from timm.models.vision_transformer import VisionTransformer
|
| 11 |
+
from torch import nn
|
| 12 |
+
from torchvision import transforms
|
| 13 |
+
|
| 14 |
+
# We provide a list of timm model names, more are available on their official repo.
|
| 15 |
+
MODEL_LIST = [
|
| 16 |
+
# DINO
|
| 17 |
+
"vit_base_patch16_224.dino",
|
| 18 |
+
# DINOv2
|
| 19 |
+
"vit_base_patch14_dinov2.lvd142m",
|
| 20 |
+
# DINOv2-R
|
| 21 |
+
"vit_base_patch14_reg4_dinov2",
|
| 22 |
+
# Franca
|
| 23 |
+
"franca_vitb14",
|
| 24 |
+
# DINOv3-ViT
|
| 25 |
+
"vit_base_patch16_dinov3.lvd1689m",
|
| 26 |
+
"vit_large_patch16_dinov3.lvd1689m",
|
| 27 |
+
"vit_7b_patch16_dinov3.lvd1689m",
|
| 28 |
+
# SigLIP2
|
| 29 |
+
"vit_base_patch16_siglip_512.v2_webli",
|
| 30 |
+
# PE Core
|
| 31 |
+
"vit_pe_core_small_patch16_384.fb",
|
| 32 |
+
# PE Spatial
|
| 33 |
+
"vit_pe_spatial_tiny_patch16_512.fb",
|
| 34 |
+
# RADIO
|
| 35 |
+
"radio_v2.5-b",
|
| 36 |
+
# CAPI
|
| 37 |
+
"capi_vitl14_lvd",
|
| 38 |
+
# MAE
|
| 39 |
+
"vit_large_patch16_224.mae",
|
| 40 |
+
]
|
| 41 |
+
|
| 42 |
+
IMAGENET_DEFAULT_MEAN = (0.485, 0.456, 0.406)
|
| 43 |
+
IMAGENET_DEFAULT_STD = (0.229, 0.224, 0.225)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class PretrainedViTWrapper(nn.Module):
|
| 47 |
+
|
| 48 |
+
def __init__(
|
| 49 |
+
self,
|
| 50 |
+
name,
|
| 51 |
+
norm: bool = True,
|
| 52 |
+
dynamic_img_size: bool = True,
|
| 53 |
+
dynamic_img_pad: bool = False,
|
| 54 |
+
**kwargs,
|
| 55 |
+
):
|
| 56 |
+
super().__init__()
|
| 57 |
+
# comment out the following line to test the models not in the list
|
| 58 |
+
self.name = name
|
| 59 |
+
|
| 60 |
+
load_weights = False
|
| 61 |
+
if "dvt_" == name[:4]:
|
| 62 |
+
load_weights = True
|
| 63 |
+
load_tag = "dvt"
|
| 64 |
+
name = name.replace("dvt_", "")
|
| 65 |
+
if "fit3d_" == name[:6]:
|
| 66 |
+
load_weights = True
|
| 67 |
+
load_tag = "fit3d"
|
| 68 |
+
name = name.replace("fit3d_", "")
|
| 69 |
+
|
| 70 |
+
# Set patch size
|
| 71 |
+
try:
|
| 72 |
+
self.patch_size = int(re.search(r"patch(\d+)", name).group(1))
|
| 73 |
+
except:
|
| 74 |
+
self.patch_size = 16
|
| 75 |
+
if "franca" in name or "capi" in name:
|
| 76 |
+
self.patch_size = 14
|
| 77 |
+
if "convnext" in name:
|
| 78 |
+
self.patch_size = 32
|
| 79 |
+
|
| 80 |
+
name, self.patch_size
|
| 81 |
+
|
| 82 |
+
self.dynamic_img_size = dynamic_img_size
|
| 83 |
+
self.dynamic_img_pad = dynamic_img_pad
|
| 84 |
+
self.model, self.config = self.create_model(name, **kwargs)
|
| 85 |
+
self.config["ps"] = self.patch_size
|
| 86 |
+
self.embed_dim = self.model.embed_dim
|
| 87 |
+
self.norm = norm
|
| 88 |
+
|
| 89 |
+
if load_weights:
|
| 90 |
+
ckpt = torch.load(f"/home/lchambon/workspace/JAFAR/ckpts/{load_tag}_{name}.pth", map_location="cpu")
|
| 91 |
+
if load_tag == "dvt":
|
| 92 |
+
self.load_state_dict(ckpt["model"], strict=True)
|
| 93 |
+
elif load_tag == "fit3d":
|
| 94 |
+
self.model.load_state_dict(ckpt, strict=True)
|
| 95 |
+
|
| 96 |
+
def create_model(self, name: str, **kwargs) -> Tuple[VisionTransformer, transforms.Compose]:
|
| 97 |
+
if "radio" in self.name:
|
| 98 |
+
model = torch.hub.load(
|
| 99 |
+
"NVlabs/RADIO",
|
| 100 |
+
"radio_model",
|
| 101 |
+
version=name,
|
| 102 |
+
progress=True,
|
| 103 |
+
skip_validation=True,
|
| 104 |
+
)
|
| 105 |
+
data_config = {
|
| 106 |
+
"mean": torch.tensor([0.0, 0.0, 0.0]),
|
| 107 |
+
"std": torch.tensor([1.0, 1.0, 1.0]),
|
| 108 |
+
"input_size": (3, 512, 512),
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
elif "franca" in self.name:
|
| 112 |
+
model = torch.hub.load("valeoai/Franca", name, use_rasa_head=True)
|
| 113 |
+
data_config = {"mean": IMAGENET_DEFAULT_MEAN, "std": IMAGENET_DEFAULT_STD, "input_size": (3, 448, 448)}
|
| 114 |
+
|
| 115 |
+
elif "capi" in self.name:
|
| 116 |
+
model = torch.hub.load("facebookresearch/capi:main", name, force_reload=False)
|
| 117 |
+
|
| 118 |
+
data_config = {"mean": IMAGENET_DEFAULT_MEAN, "std": IMAGENET_DEFAULT_STD, "input_size": (3, 448, 448)}
|
| 119 |
+
|
| 120 |
+
else:
|
| 121 |
+
timm_kwargs = dict(
|
| 122 |
+
pretrained=True,
|
| 123 |
+
num_classes=0,
|
| 124 |
+
patch_size=self.patch_size,
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if "sam" not in self.name and "convnext" not in self.name:
|
| 128 |
+
timm_kwargs["dynamic_img_size"] = self.dynamic_img_size
|
| 129 |
+
timm_kwargs["dynamic_img_pad"] = self.dynamic_img_pad
|
| 130 |
+
|
| 131 |
+
timm_kwargs.update(kwargs)
|
| 132 |
+
model = timm.create_model(name, **timm_kwargs)
|
| 133 |
+
data_config = timm.data.resolve_model_data_config(model=model)
|
| 134 |
+
|
| 135 |
+
model = model.eval()
|
| 136 |
+
|
| 137 |
+
return model, data_config
|
| 138 |
+
|
| 139 |
+
def forward(
|
| 140 |
+
self,
|
| 141 |
+
x: torch.Tensor,
|
| 142 |
+
n: Union[int, List[int], Tuple[int]] = 1,
|
| 143 |
+
return_prefix_tokens: bool = False,
|
| 144 |
+
) -> Union[List[torch.Tensor], Tuple[torch.Tensor, List[torch.Tensor]]]:
|
| 145 |
+
"""Intermediate layer accessor inspired by DINO / DINOv2 interface.
|
| 146 |
+
Args:
|
| 147 |
+
x: Input tensor.
|
| 148 |
+
n: Take last n blocks if int, all if None, select matching indices if sequence
|
| 149 |
+
reshape: Whether to reshape the output.
|
| 150 |
+
"""
|
| 151 |
+
|
| 152 |
+
common_kwargs = dict(
|
| 153 |
+
norm=self.norm,
|
| 154 |
+
output_fmt="NCHW",
|
| 155 |
+
intermediates_only=True,
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
if "sam" not in self.name and return_prefix_tokens:
|
| 159 |
+
common_kwargs["return_prefix_tokens"] = return_prefix_tokens
|
| 160 |
+
|
| 161 |
+
elif "franca" in self.name:
|
| 162 |
+
B, C, H, W = x.shape
|
| 163 |
+
feats = self.model.forward_features(x, use_rasa_head=True)
|
| 164 |
+
out = feats["patch_token_rasa"]
|
| 165 |
+
out = rearrange(out, "b (h w) c -> b c h w", h=H // self.patch_size, w=W // self.patch_size)
|
| 166 |
+
|
| 167 |
+
elif "capi" in self.name:
|
| 168 |
+
*_, out = self.model(x)
|
| 169 |
+
out = out.permute(0, 3, 1, 2)
|
| 170 |
+
|
| 171 |
+
else:
|
| 172 |
+
out = self.model.forward_intermediates(x, n, **common_kwargs)
|
| 173 |
+
|
| 174 |
+
# "sam" models return feats only, others may return (feats, prefix)
|
| 175 |
+
if not isinstance(out, list) and not isinstance(out, tuple):
|
| 176 |
+
out = [out]
|
| 177 |
+
return out[0]
|
| 178 |
+
else:
|
| 179 |
+
assert len(out) == 1, f"Out contains {len(out)} elements, expected 1."
|
| 180 |
+
return out[0]
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__init__.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .attentions import CrossAttention
|
| 2 |
+
from .convolutions import encoder
|
| 3 |
+
from .rope import RoPE
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (354 Bytes). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/attentions.cpython-311.pyc
ADDED
|
Binary file (4.08 kB). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/convolutions.cpython-311.pyc
ADDED
|
Binary file (3.49 kB). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/__pycache__/rope.cpython-311.pyc
ADDED
|
Binary file (8.44 kB). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/attentions.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from einops import rearrange
|
| 5 |
+
|
| 6 |
+
try:
|
| 7 |
+
NATTEN_RECENT = False
|
| 8 |
+
from natten.functional import na2d_av, na2d_qk
|
| 9 |
+
except:
|
| 10 |
+
NATTEN_RECENT = True
|
| 11 |
+
from natten import na2d
|
| 12 |
+
|
| 13 |
+
from src.layers.rope import RoPE
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def legacy_attention(q, k, v, kernel_size, dilation, scale=1, return_weights=False):
|
| 17 |
+
q = rearrange(q, "b h w n d -> b n h w d")
|
| 18 |
+
k = rearrange(k, "b h w n d -> b n h w d")
|
| 19 |
+
v = rearrange(v, "b h w n d -> b n h w d")
|
| 20 |
+
attn_scores = na2d_qk(q, k, kernel_size=kernel_size, dilation=dilation)
|
| 21 |
+
attn_scores = attn_scores * scale
|
| 22 |
+
|
| 23 |
+
attn_weights = attn_scores.softmax(dim=-1)
|
| 24 |
+
features = na2d_av(attn_weights, v, kernel_size=kernel_size, dilation=dilation)
|
| 25 |
+
features = rearrange(features, "b n h w d -> b h w n d")
|
| 26 |
+
|
| 27 |
+
if return_weights:
|
| 28 |
+
return features, attn_scores
|
| 29 |
+
return features
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class CrossAttention(nn.Module):
|
| 33 |
+
def __init__(
|
| 34 |
+
self,
|
| 35 |
+
dim,
|
| 36 |
+
num_heads,
|
| 37 |
+
kernel_size=(9, 9),
|
| 38 |
+
**kwargs,
|
| 39 |
+
):
|
| 40 |
+
super().__init__()
|
| 41 |
+
assert dim % num_heads == 0, "dim must be divisible by num_heads"
|
| 42 |
+
|
| 43 |
+
self.num_heads = num_heads
|
| 44 |
+
self.kernel_size = kernel_size
|
| 45 |
+
|
| 46 |
+
self.scale = (dim // num_heads) ** -0.5
|
| 47 |
+
|
| 48 |
+
def _resize(self, x, size, dtype):
|
| 49 |
+
x = F.interpolate(x, size=size, mode="nearest-exact")
|
| 50 |
+
x = rearrange(x, "b (n d) h w -> b h w n d", n=self.num_heads)
|
| 51 |
+
return x.to(dtype)
|
| 52 |
+
|
| 53 |
+
def forward(self, q, k, v, image=None, return_weights=False, **kwargs):
|
| 54 |
+
hq, wq = q.shape[-2:]
|
| 55 |
+
hk, wk = k.shape[-2:]
|
| 56 |
+
dilation = (hq // hk, wq // wk)
|
| 57 |
+
self.dilation = dilation
|
| 58 |
+
|
| 59 |
+
q = rearrange(q, "b (n d) h w -> b h w n d", n=self.num_heads)
|
| 60 |
+
k = self._resize(k, size=(hq, wq), dtype=q.dtype)
|
| 61 |
+
v = self._resize(v, size=(hq, wq), dtype=q.dtype)
|
| 62 |
+
|
| 63 |
+
# Use legacy attention pattern
|
| 64 |
+
if return_weights:
|
| 65 |
+
assert not NATTEN_RECENT, "Return weights not supported with recent natten versions"
|
| 66 |
+
out, attn_weights = legacy_attention(q, k, v, self.kernel_size, dilation, scale=self.scale, return_weights=True)
|
| 67 |
+
return rearrange(out, "b h w n d -> b (n d) h w"), attn_weights
|
| 68 |
+
else:
|
| 69 |
+
if NATTEN_RECENT:
|
| 70 |
+
# Use modern na2d attention
|
| 71 |
+
# Note: Modern na2d doesn't support position bias directly
|
| 72 |
+
out = na2d(q, k, v, kernel_size=self.kernel_size, dilation=dilation, stride=1, backend="cutlass-fna")
|
| 73 |
+
else:
|
| 74 |
+
out = legacy_attention(q, k, v, self.kernel_size, dilation, scale=self.scale)
|
| 75 |
+
return rearrange(out, "b h w n d -> b (n d) h w")
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/convolutions.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# Convolutions
|
| 6 |
+
class EncBlock(nn.Module):
|
| 7 |
+
def __init__(
|
| 8 |
+
self,
|
| 9 |
+
in_channels,
|
| 10 |
+
out_channels,
|
| 11 |
+
kernel_size=3,
|
| 12 |
+
norm_kwargs={},
|
| 13 |
+
pad_mode="zeros",
|
| 14 |
+
norm_fn=None,
|
| 15 |
+
activation_fn=nn.SiLU,
|
| 16 |
+
use_conv_shortcut=False,
|
| 17 |
+
bias=True,
|
| 18 |
+
residual=False,
|
| 19 |
+
):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.use_conv_shortcut = use_conv_shortcut
|
| 22 |
+
self.norm1 = norm_fn(**norm_kwargs)
|
| 23 |
+
self.conv1 = nn.Conv2d(
|
| 24 |
+
in_channels,
|
| 25 |
+
out_channels,
|
| 26 |
+
kernel_size=kernel_size,
|
| 27 |
+
padding=kernel_size // 2,
|
| 28 |
+
padding_mode=pad_mode,
|
| 29 |
+
bias=bias,
|
| 30 |
+
)
|
| 31 |
+
self.norm2 = norm_fn(**norm_kwargs)
|
| 32 |
+
self.conv2 = nn.Conv2d(
|
| 33 |
+
out_channels,
|
| 34 |
+
out_channels,
|
| 35 |
+
kernel_size=kernel_size,
|
| 36 |
+
padding=kernel_size // 2,
|
| 37 |
+
padding_mode=pad_mode,
|
| 38 |
+
bias=bias,
|
| 39 |
+
)
|
| 40 |
+
self.activation_fn = activation_fn()
|
| 41 |
+
if in_channels != out_channels:
|
| 42 |
+
self.shortcut = nn.Conv2d(
|
| 43 |
+
in_channels,
|
| 44 |
+
out_channels,
|
| 45 |
+
kernel_size=1,
|
| 46 |
+
padding=0,
|
| 47 |
+
padding_mode=pad_mode,
|
| 48 |
+
bias=bias,
|
| 49 |
+
)
|
| 50 |
+
self.residual = residual
|
| 51 |
+
|
| 52 |
+
def forward(self, x):
|
| 53 |
+
residual = x
|
| 54 |
+
x = self.norm1(x)
|
| 55 |
+
x = self.activation_fn(x)
|
| 56 |
+
x = self.conv1(x)
|
| 57 |
+
x = self.norm2(x)
|
| 58 |
+
x = self.activation_fn(x)
|
| 59 |
+
x = self.conv2(x)
|
| 60 |
+
if self.use_conv_shortcut or residual.shape != x.shape:
|
| 61 |
+
residual = self.shortcut(residual)
|
| 62 |
+
if self.residual:
|
| 63 |
+
return x + residual
|
| 64 |
+
return x
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def encoder(in_dim, hidden_dim, kernel_size=1, ks_res=1, num_layers=2, bias=True, num_groups=8, residual=False):
|
| 68 |
+
return nn.Sequential(
|
| 69 |
+
nn.Conv2d(
|
| 70 |
+
in_dim,
|
| 71 |
+
hidden_dim,
|
| 72 |
+
kernel_size=kernel_size,
|
| 73 |
+
padding=kernel_size // 2,
|
| 74 |
+
padding_mode="reflect",
|
| 75 |
+
bias=bias,
|
| 76 |
+
),
|
| 77 |
+
*[
|
| 78 |
+
EncBlock(
|
| 79 |
+
hidden_dim,
|
| 80 |
+
hidden_dim,
|
| 81 |
+
kernel_size=ks_res,
|
| 82 |
+
pad_mode="reflect",
|
| 83 |
+
norm_fn=nn.GroupNorm,
|
| 84 |
+
norm_kwargs={"num_groups": num_groups, "num_channels": hidden_dim},
|
| 85 |
+
activation_fn=nn.SiLU,
|
| 86 |
+
use_conv_shortcut=False,
|
| 87 |
+
bias=bias,
|
| 88 |
+
residual=residual,
|
| 89 |
+
)
|
| 90 |
+
for _ in range(num_layers)
|
| 91 |
+
],
|
| 92 |
+
)
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/layers/rope.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This software may be used and distributed in accordance with
|
| 4 |
+
# the terms of the DINOv3 License Agreement.
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
from typing import Literal, Optional
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
from einops import rearrange
|
| 12 |
+
from torch import Tensor, nn
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def rope_rotate_half(x: Tensor) -> Tensor:
|
| 16 |
+
# x: [ x0 x1 x2 x3 x4 x5]
|
| 17 |
+
# out: [-x3 -x4 -x5 x0 x1 x2]
|
| 18 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 19 |
+
return torch.cat([-x2, x1], dim=-1)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def rope_apply(x: Tensor, sin: Tensor, cos: Tensor) -> Tensor:
|
| 23 |
+
"""
|
| 24 |
+
- x: feature vector of shape [..., D] (e.g., [x0, x1, ..., x_{D-1}])
|
| 25 |
+
- sin, cos: shape [..., D], e.g., [sin0, sin1, ..., sin_{D/2-1}, sin0, ..., sin_{D/2-1}]
|
| 26 |
+
- rope_apply rotates the embedding by pairing the first half with the second half:
|
| 27 |
+
- out = [ x0*cos0 - x_{D/2}*sin0,
|
| 28 |
+
x1*cos1 - x_{D/2+1}*sin1,
|
| 29 |
+
...,
|
| 30 |
+
x_{D/2}*cos0 + x0*sin0,
|
| 31 |
+
x_{D/2+1}*cos1 + x1*sin1,
|
| 32 |
+
... ]
|
| 33 |
+
"""
|
| 34 |
+
return (x * cos) + (rope_rotate_half(x) * sin)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# RoPE positional embedding with no mixing of coordinates (axial) and no learnable weights
|
| 38 |
+
# Supports two parametrizations of the rope parameters: either using `base` or `min_period` and `max_period`.
|
| 39 |
+
class RoPE(nn.Module):
|
| 40 |
+
def __init__(
|
| 41 |
+
self,
|
| 42 |
+
embed_dim: int,
|
| 43 |
+
*,
|
| 44 |
+
num_heads: int,
|
| 45 |
+
base: float = 100.0,
|
| 46 |
+
min_period: float = None,
|
| 47 |
+
max_period: float = None,
|
| 48 |
+
normalize_coords: Literal["min", "max", "separate"] = "separate",
|
| 49 |
+
shift_coords: float = None,
|
| 50 |
+
jitter_coords: float = None,
|
| 51 |
+
rescale_coords: float = None,
|
| 52 |
+
dtype: torch.dtype = None,
|
| 53 |
+
device: torch.device = None,
|
| 54 |
+
):
|
| 55 |
+
super().__init__()
|
| 56 |
+
assert embed_dim % (4 * num_heads) == 0
|
| 57 |
+
both_periods = min_period is not None and max_period is not None
|
| 58 |
+
if (base is None and not both_periods) or (base is not None and both_periods):
|
| 59 |
+
raise ValueError("Either `base` or `min_period`+`max_period` must be provided.")
|
| 60 |
+
|
| 61 |
+
D_head = embed_dim // num_heads
|
| 62 |
+
self.num_heads = num_heads
|
| 63 |
+
self.base = base
|
| 64 |
+
self.min_period = min_period
|
| 65 |
+
self.max_period = max_period
|
| 66 |
+
self.D_head = D_head
|
| 67 |
+
self.normalize_coords = normalize_coords
|
| 68 |
+
self.shift_coords = shift_coords
|
| 69 |
+
self.jitter_coords = jitter_coords
|
| 70 |
+
self.rescale_coords = rescale_coords
|
| 71 |
+
|
| 72 |
+
self._cached_coords = None
|
| 73 |
+
self._cached_hw = None
|
| 74 |
+
|
| 75 |
+
# Needs persistent=True because we do teacher.load_state_dict(student.state_dict()) to initialize the teacher
|
| 76 |
+
self.dtype = dtype # Don't rely on self.periods.dtype
|
| 77 |
+
self.register_buffer(
|
| 78 |
+
"periods",
|
| 79 |
+
torch.empty(D_head // 4, device=device, dtype=dtype),
|
| 80 |
+
persistent=True,
|
| 81 |
+
)
|
| 82 |
+
self._init_weights()
|
| 83 |
+
|
| 84 |
+
def create_coordinate(self, *, H: int, W: int) -> tuple[Tensor, Tensor]:
|
| 85 |
+
device = self.periods.device
|
| 86 |
+
dtype = self.dtype
|
| 87 |
+
dd = {"device": device, "dtype": dtype}
|
| 88 |
+
|
| 89 |
+
# Prepare coords in range [-1, +1]
|
| 90 |
+
if self.normalize_coords == "max":
|
| 91 |
+
max_HW = max(H, W)
|
| 92 |
+
coords_h = torch.arange(0.5, H, **dd) / max_HW # [H]
|
| 93 |
+
coords_w = torch.arange(0.5, W, **dd) / max_HW # [W]
|
| 94 |
+
elif self.normalize_coords == "min":
|
| 95 |
+
min_HW = min(H, W)
|
| 96 |
+
coords_h = torch.arange(0.5, H, **dd) / min_HW # [H]
|
| 97 |
+
coords_w = torch.arange(0.5, W, **dd) / min_HW # [W]
|
| 98 |
+
elif self.normalize_coords == "separate":
|
| 99 |
+
coords_h = torch.arange(0.5, H, **dd) / H # [H]
|
| 100 |
+
coords_w = torch.arange(0.5, W, **dd) / W # [W]
|
| 101 |
+
else:
|
| 102 |
+
raise ValueError(f"Unknown normalize_coords: {self.normalize_coords}")
|
| 103 |
+
coords = torch.stack(torch.meshgrid(coords_h, coords_w, indexing="ij"), dim=-1) # [H, W, 2]
|
| 104 |
+
coords = coords.flatten(0, 1) # [HW, 2]
|
| 105 |
+
coords = 2.0 * coords - 1.0 # Shift range [0, 1] to [-1, +1]
|
| 106 |
+
|
| 107 |
+
# Shift coords by adding a uniform value in [-shift, shift]
|
| 108 |
+
if self.training and self.shift_coords is not None:
|
| 109 |
+
shift_hw = torch.empty(2, **dd).uniform_(-self.shift_coords, self.shift_coords)
|
| 110 |
+
coords += shift_hw[None, :]
|
| 111 |
+
|
| 112 |
+
# Jitter coords by multiplying the range [-1, 1] by a log-uniform value in [1/jitter, jitter]
|
| 113 |
+
if self.training and self.jitter_coords is not None:
|
| 114 |
+
jitter_max = np.log(self.jitter_coords)
|
| 115 |
+
jitter_min = -jitter_max
|
| 116 |
+
jitter_hw = torch.empty(2, **dd).uniform_(jitter_min, jitter_max).exp()
|
| 117 |
+
coords *= jitter_hw[None, :]
|
| 118 |
+
|
| 119 |
+
# Rescale coords by multiplying the range [-1, 1] by a log-uniform value in [1/rescale, rescale]
|
| 120 |
+
if self.training and self.rescale_coords is not None:
|
| 121 |
+
rescale_max = np.log(self.rescale_coords)
|
| 122 |
+
rescale_min = -rescale_max
|
| 123 |
+
rescale_hw = torch.empty(1, **dd).uniform_(rescale_min, rescale_max).exp()
|
| 124 |
+
coords *= rescale_hw
|
| 125 |
+
|
| 126 |
+
return coords
|
| 127 |
+
|
| 128 |
+
def _init_weights(self):
|
| 129 |
+
device = self.periods.device
|
| 130 |
+
dtype = self.dtype
|
| 131 |
+
if self.base is not None:
|
| 132 |
+
periods = self.base ** (2 * torch.arange(self.D_head // 4, device=device, dtype=dtype) / (self.D_head // 2))
|
| 133 |
+
else:
|
| 134 |
+
periods = torch.logspace(math.log10(self.min_period), math.log10(self.max_period), steps=self.D_head // 4)
|
| 135 |
+
self.periods.data = periods
|
| 136 |
+
|
| 137 |
+
def rotate(self, x, coords):
|
| 138 |
+
# Prepare angles and sin/cos
|
| 139 |
+
angles = 2 * math.pi * coords[:, :, None] / self.periods[None, None, :] # [HW, 2, D//4]
|
| 140 |
+
# Flatten u and v frequencies into a single vector per spatial location: [u1, ..., u_{D//4}, v1, ..., v_{D//4}]
|
| 141 |
+
angles = angles.flatten(1, 2) # [HW, D//2]
|
| 142 |
+
# Repeat the vector to match embedding dimension D: [u1,...,u_{D//4}, v1,...,v_{D//4}, u1,...,u_{D//4}, v1,...,v_{D//4}]
|
| 143 |
+
angles = angles.tile(2) # [HW, D]
|
| 144 |
+
|
| 145 |
+
cos = torch.cos(angles) # [HW, D]
|
| 146 |
+
sin = torch.sin(angles) # [HW, D]
|
| 147 |
+
|
| 148 |
+
# x_u0 ... x_u{D//2-1} x_v0 ... x_v{D//2-1}
|
| 149 |
+
|
| 150 |
+
# Apply RoPE: rotates each (u_i, v_i) pair
|
| 151 |
+
# first half: x[i]*cos[i] - x[i+D/2]*sin[i]
|
| 152 |
+
# second half: x[i+D/2]*cos[i] + x[i]*sin[i]
|
| 153 |
+
return rope_apply(x, sin, cos)
|
| 154 |
+
|
| 155 |
+
def forward(self, x, layout: str = "spatial"):
|
| 156 |
+
h, w = x.shape[-2:]
|
| 157 |
+
x = rearrange(x, "b (n d) h w -> b n (h w) d", n=self.num_heads)
|
| 158 |
+
|
| 159 |
+
if (h, w) != self._cached_hw:
|
| 160 |
+
self._cached_coords = self.create_coordinate(H=h, W=w)
|
| 161 |
+
self._cached_hw = (h, w)
|
| 162 |
+
|
| 163 |
+
coords = self._cached_coords
|
| 164 |
+
|
| 165 |
+
# Rotate
|
| 166 |
+
x = self.rotate(x, coords)
|
| 167 |
+
|
| 168 |
+
# Reshape
|
| 169 |
+
if layout == "spatial":
|
| 170 |
+
x = rearrange(x, f"b n (h w) d -> b (n d) h w", h=h, w=w)
|
| 171 |
+
elif layout == "flatten":
|
| 172 |
+
x = rearrange(x, f"b n (h w) d -> b h w n d", h=h, w=w)
|
| 173 |
+
|
| 174 |
+
return x
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/loss.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torch import nn
|
| 4 |
+
from torch.nn import functional as F
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class MSELoss(nn.Module):
|
| 8 |
+
def __init__(self):
|
| 9 |
+
super().__init__()
|
| 10 |
+
self.mse_loss = torch.nn.MSELoss()
|
| 11 |
+
|
| 12 |
+
def forward(self, pred, target, normalize=False):
|
| 13 |
+
if normalize:
|
| 14 |
+
# If you must normalize (example: min-max scaling)
|
| 15 |
+
min_val = torch.min(target, dim=1, keepdim=True).values
|
| 16 |
+
max_val = torch.max(target, dim=1, keepdim=True).values
|
| 17 |
+
pred_normalized = (pred - min_val) / (max_val - min_val + 1e-6)
|
| 18 |
+
target_normalized = (target - min_val) / (max_val - min_val + 1e-6)
|
| 19 |
+
else:
|
| 20 |
+
pred_normalized = pred
|
| 21 |
+
target_normalized = target
|
| 22 |
+
|
| 23 |
+
return self.mse_loss(pred_normalized, target_normalized)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Loss(nn.Module):
|
| 27 |
+
|
| 28 |
+
def __init__(
|
| 29 |
+
self,
|
| 30 |
+
loss_type,
|
| 31 |
+
dim=384,
|
| 32 |
+
):
|
| 33 |
+
super().__init__()
|
| 34 |
+
self.dim = dim
|
| 35 |
+
|
| 36 |
+
if loss_type == "mse":
|
| 37 |
+
loss = MSELoss()
|
| 38 |
+
else:
|
| 39 |
+
raise NotImplementedError(f"Loss type {loss_type} not implemented")
|
| 40 |
+
|
| 41 |
+
self.loss_func = loss
|
| 42 |
+
|
| 43 |
+
def __call__(self, pred, target, *args, **kwargs):
|
| 44 |
+
loss = self.loss_func(pred, target, *args, **kwargs)
|
| 45 |
+
return {"total": loss}
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .anyup import AnyUpsampler
|
| 2 |
+
from .base import BaseUpsampler
|
| 3 |
+
from .bilinear import Bilinear
|
| 4 |
+
from .featup import FeatUp
|
| 5 |
+
from .ircnn import IRCNN
|
| 6 |
+
from .jafar import JAFAR
|
| 7 |
+
from .jbf import JBF
|
| 8 |
+
from .jbu import JBU
|
| 9 |
+
from .naf import NAF
|
| 10 |
+
from .nearest import Nearest
|
| 11 |
+
from .rednet import REDNet
|
| 12 |
+
from .restormer import Restormer
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (815 Bytes). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/anyup.cpython-311.pyc
ADDED
|
Binary file (1.53 kB). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/base.cpython-311.pyc
ADDED
|
Binary file (1.09 kB). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/bilinear.cpython-311.pyc
ADDED
|
Binary file (1.18 kB). View file
|
|
|
Pixal3D/torch_hub/hub/valeoai_NAF_main/src/model/__pycache__/featup.cpython-311.pyc
ADDED
|
Binary file (11.3 kB). View file
|
|
|