Spaces:
Sleeping
Sleeping
Commit ·
d43892c
1
Parent(s): 7e9deba
BIT
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +1 -2
- README.md +79 -5
- checkpoints/BIT_LEVIR/best_ckpt.pt +3 -0
- data_config.py +22 -0
- datasets/CD_dataset.py +120 -0
- datasets/data_utils.py +185 -0
- demo.py +79 -0
- eval_cd.py +59 -0
- main_cd.py +77 -0
- misc/imutils.py +401 -0
- misc/logger_tool.py +73 -0
- misc/metric_tool.py +164 -0
- misc/pyutils.py +42 -0
- misc/torchutils.py +576 -0
- models/__init__.py +1 -0
- models/basic_model.py +75 -0
- models/evaluator.py +172 -0
- models/help_funcs.py +188 -0
- models/losses.py +20 -0
- models/networks.py +367 -0
- models/resnet.py +358 -0
- models/trainer.py +297 -0
- samples/A/test_102_0512_0000.png +3 -0
- samples/A/test_113_0256.png +3 -0
- samples/A/test_121_0768_0256.png +3 -0
- samples/A/test_2_0000_0000.png +3 -0
- samples/A/test_2_0000_0512.png +3 -0
- samples/A/test_55_0256_0000.png +3 -0
- samples/A/test_77_0512_0256.png +3 -0
- samples/A/test_7_0256_0512.png +3 -0
- samples/A/train_36_0512_0512.png +3 -0
- samples/A/train_386_0512_0768.png +3 -0
- samples/A/train_412_0512_0768.png +3 -0
- samples/A/val_27_0000_0256.png +3 -0
- samples/B/test_102_0512_0000.png +3 -0
- samples/B/test_113_0256.png +3 -0
- samples/B/test_121_0768_0256.png +3 -0
- samples/B/test_2_0000_0000.png +3 -0
- samples/B/test_2_0000_0512.png +3 -0
- samples/B/test_55_0256_0000.png +3 -0
- samples/B/test_77_0512_0256.png +3 -0
- samples/B/test_7_0256_0512.png +3 -0
- samples/B/train_36_0512_0512.png +3 -0
- samples/B/train_386_0512_0768.png +3 -0
- samples/B/train_412_0512_0768.png +3 -0
- samples/B/val_27_0000_0256.png +3 -0
- samples/label/test_102_0512_0000.png +3 -0
- samples/label/test_121_0768_0256.png +3 -0
- samples/label/test_2_0000_0000.png +3 -0
- samples/label/test_2_0000_0512.png +3 -0
.gitignore
CHANGED
|
@@ -15,5 +15,4 @@ docs/src/**/*
|
|
| 15 |
*/*.so*
|
| 16 |
*/**/*.so*
|
| 17 |
*/**/*.dylib*
|
| 18 |
-
*~
|
| 19 |
-
models/pretrain
|
|
|
|
| 15 |
*/*.so*
|
| 16 |
*/**/*.so*
|
| 17 |
*/**/*.dylib*
|
| 18 |
+
*~
|
|
|
README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
| 2 |
|
| 3 |
Here, we provide the pytorch implementation of the paper: Remote Sensing Image Change Detection with Transformers.
|
| 4 |
|
| 5 |
-
|
| 6 |
|
| 7 |
-
|
| 8 |
|
| 9 |
## Requirements
|
| 10 |
|
|
@@ -13,7 +13,6 @@ Python 3.6
|
|
| 13 |
pytorch 1.6.0
|
| 14 |
torchvision 0.7.0
|
| 15 |
einops 0.3.0
|
| 16 |
-
pthflops
|
| 17 |
```
|
| 18 |
|
| 19 |
## Installation
|
|
@@ -27,12 +26,87 @@ cd BIT_CD
|
|
| 27 |
|
| 28 |
## Quick Start
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
## Train
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
## Dataset Preparation
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
LEVIR-CD: https://justchenhao.github.io/LEVIR/
|
| 37 |
|
| 38 |
WHU-CD: https://study.rsgis.whu.edu.cn/pages/download/building_dataset.html
|
|
@@ -56,7 +130,7 @@ If you use this code for your research, please cite our paper:
|
|
| 56 |
volume={},
|
| 57 |
number={},
|
| 58 |
pages={1-14},
|
| 59 |
-
doi={}
|
| 60 |
}
|
| 61 |
```
|
| 62 |
|
|
|
|
| 2 |
|
| 3 |
Here, we provide the pytorch implementation of the paper: Remote Sensing Image Change Detection with Transformers.
|
| 4 |
|
| 5 |
+
For more ore information, please see our published paper at [IEEE TGRS](https://ieeexplore.ieee.org/document/9491802) or [arxiv](https://arxiv.org/abs/2103.00208).
|
| 6 |
|
| 7 |
+

|
| 8 |
|
| 9 |
## Requirements
|
| 10 |
|
|
|
|
| 13 |
pytorch 1.6.0
|
| 14 |
torchvision 0.7.0
|
| 15 |
einops 0.3.0
|
|
|
|
| 16 |
```
|
| 17 |
|
| 18 |
## Installation
|
|
|
|
| 26 |
|
| 27 |
## Quick Start
|
| 28 |
|
| 29 |
+
We have some samples from the [LEVIR-CD](https://justchenhao.github.io/LEVIR/) dataset in the folder `samples` for a quick start.
|
| 30 |
+
|
| 31 |
+
Firstly, you can download our BIT pretrained model——by [baidu drive, code: 2lyz](https://pan.baidu.com/s/1HiXwpspl6odYQKda6pMuZQ) or [google drive](https://drive.google.com/file/d/1IVdF5a3e1_7DiSndtMkhpZuCSgDLLFcg/view?usp=sharing). After downloaded the pretrained model, you can put it in `checkpoints/BIT_LEVIR/`.
|
| 32 |
+
|
| 33 |
+
Then, run a demo to get started as follows:
|
| 34 |
+
|
| 35 |
+
```python
|
| 36 |
+
python demo.py
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
After that, you can find the prediction results in `samples/predict`.
|
| 40 |
+
|
| 41 |
## Train
|
| 42 |
|
| 43 |
+
You can find the training script `run_cd.sh` in the folder `scripts`. You can run the script file by `sh scripts/run_cd.sh` in the command environment.
|
| 44 |
+
|
| 45 |
+
The detailed script file `run_cd.sh` is as follows:
|
| 46 |
+
|
| 47 |
+
```cmd
|
| 48 |
+
gpus=0
|
| 49 |
+
checkpoint_root=checkpoints
|
| 50 |
+
data_name=LEVIR # dataset name
|
| 51 |
+
|
| 52 |
+
img_size=256
|
| 53 |
+
batch_size=8
|
| 54 |
+
lr=0.01
|
| 55 |
+
max_epochs=200 #training epochs
|
| 56 |
+
net_G=base_transformer_pos_s4_dd8 # model name
|
| 57 |
+
#base_resnet18
|
| 58 |
+
#base_transformer_pos_s4_dd8
|
| 59 |
+
#base_transformer_pos_s4_dd8_dedim8
|
| 60 |
+
lr_policy=linear
|
| 61 |
+
|
| 62 |
+
split=train # training txt
|
| 63 |
+
split_val=val #validation txt
|
| 64 |
+
project_name=CD_${net_G}_${data_name}_b${batch_size}_lr${lr}_${split}_${split_val}_${max_epochs}_${lr_policy}
|
| 65 |
+
|
| 66 |
+
python main_cd.py --img_size ${img_size} --checkpoint_root ${checkpoint_root} --lr_policy ${lr_policy} --split ${split} --split_val ${split_val} --net_G ${net_G} --gpu_ids ${gpus} --max_epochs ${max_epochs} --project_name ${project_name} --batch_size ${batch_size} --data_name ${data_name} --lr ${lr}
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
## Evaluate
|
| 70 |
+
|
| 71 |
+
You can find the evaluation script `eval.sh` in the folder `scripts`. You can run the script file by `sh scripts/eval.sh` in the command environment.
|
| 72 |
+
|
| 73 |
+
The detailed script file `eval.sh` is as follows:
|
| 74 |
+
|
| 75 |
+
```cmd
|
| 76 |
+
gpus=0
|
| 77 |
+
data_name=LEVIR # dataset name
|
| 78 |
+
net_G=base_transformer_pos_s4_dd8_dedim8 # model name
|
| 79 |
+
split=test # test.txt
|
| 80 |
+
project_name=BIT_LEVIR # the name of the subfolder in the checkpoints folder
|
| 81 |
+
checkpoint_name=best_ckpt.pt # the name of evaluated model file
|
| 82 |
+
|
| 83 |
+
python eval_cd.py --split ${split} --net_G ${net_G} --checkpoint_name ${checkpoint_name} --gpu_ids ${gpus} --project_name ${project_name} --data_name ${data_name}
|
| 84 |
+
```
|
| 85 |
|
| 86 |
## Dataset Preparation
|
| 87 |
|
| 88 |
+
### Data structure
|
| 89 |
+
|
| 90 |
+
```
|
| 91 |
+
"""
|
| 92 |
+
Change detection data set with pixel-level binary labels;
|
| 93 |
+
├─A
|
| 94 |
+
├─B
|
| 95 |
+
├─label
|
| 96 |
+
└─list
|
| 97 |
+
"""
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
`A`: images of t1 phase;
|
| 101 |
+
|
| 102 |
+
`B`:images of t2 phase;
|
| 103 |
+
|
| 104 |
+
`label`: label maps;
|
| 105 |
+
|
| 106 |
+
`list`: contains `train.txt, val.txt and test.txt`, each file records the image names (XXX.png) in the change detection dataset.
|
| 107 |
+
|
| 108 |
+
### Data Download
|
| 109 |
+
|
| 110 |
LEVIR-CD: https://justchenhao.github.io/LEVIR/
|
| 111 |
|
| 112 |
WHU-CD: https://study.rsgis.whu.edu.cn/pages/download/building_dataset.html
|
|
|
|
| 130 |
volume={},
|
| 131 |
number={},
|
| 132 |
pages={1-14},
|
| 133 |
+
doi={10.1109/TGRS.2021.3095166}
|
| 134 |
}
|
| 135 |
```
|
| 136 |
|
checkpoints/BIT_LEVIR/best_ckpt.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c159ba76143447f58c9f367ce8126a0014f2e4ba218cdb97cca173952c38cb3b
|
| 3 |
+
size 60048699
|
data_config.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
class DataConfig:
|
| 3 |
+
data_name = ""
|
| 4 |
+
root_dir = ""
|
| 5 |
+
label_transform = "norm"
|
| 6 |
+
def get_data_config(self, data_name):
|
| 7 |
+
self.data_name = data_name
|
| 8 |
+
if data_name == 'LEVIR':
|
| 9 |
+
self.root_dir = 'path to the root of LEVIR-CD dataset'
|
| 10 |
+
elif data_name == 'quick_start':
|
| 11 |
+
self.root_dir = './samples/'
|
| 12 |
+
else:
|
| 13 |
+
raise TypeError('%s has not defined' % data_name)
|
| 14 |
+
return self
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
if __name__ == '__main__':
|
| 18 |
+
data = DataConfig().get_data_config(data_name='LEVIR')
|
| 19 |
+
print(data.data_name)
|
| 20 |
+
print(data.root_dir)
|
| 21 |
+
print(data.label_transform)
|
| 22 |
+
|
datasets/CD_dataset.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
变化检测数据集
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
from torch.utils import data
|
| 10 |
+
|
| 11 |
+
from datasets.data_utils import CDDataAugmentation
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
"""
|
| 15 |
+
CD data set with pixel-level labels;
|
| 16 |
+
├─image
|
| 17 |
+
├─image_post
|
| 18 |
+
├─label
|
| 19 |
+
└─list
|
| 20 |
+
"""
|
| 21 |
+
IMG_FOLDER_NAME = "A"
|
| 22 |
+
IMG_POST_FOLDER_NAME = 'B'
|
| 23 |
+
LIST_FOLDER_NAME = 'list'
|
| 24 |
+
ANNOT_FOLDER_NAME = "label"
|
| 25 |
+
|
| 26 |
+
IGNORE = 255
|
| 27 |
+
|
| 28 |
+
label_suffix='.png' # jpg for gan dataset, others : png
|
| 29 |
+
|
| 30 |
+
def load_img_name_list(dataset_path):
|
| 31 |
+
img_name_list = np.loadtxt(dataset_path, dtype=np.str)
|
| 32 |
+
if img_name_list.ndim == 2:
|
| 33 |
+
return img_name_list[:, 0]
|
| 34 |
+
return img_name_list
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def load_image_label_list_from_npy(npy_path, img_name_list):
|
| 38 |
+
cls_labels_dict = np.load(npy_path, allow_pickle=True).item()
|
| 39 |
+
return [cls_labels_dict[img_name] for img_name in img_name_list]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def get_img_post_path(root_dir,img_name):
|
| 43 |
+
return os.path.join(root_dir, IMG_POST_FOLDER_NAME, img_name)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def get_img_path(root_dir, img_name):
|
| 47 |
+
return os.path.join(root_dir, IMG_FOLDER_NAME, img_name)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def get_label_path(root_dir, img_name):
|
| 51 |
+
return os.path.join(root_dir, ANNOT_FOLDER_NAME, img_name.replace('.jpg', label_suffix))
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class ImageDataset(data.Dataset):
|
| 55 |
+
"""VOCdataloder"""
|
| 56 |
+
def __init__(self, root_dir, split='train', img_size=256, is_train=True,to_tensor=True):
|
| 57 |
+
super(ImageDataset, self).__init__()
|
| 58 |
+
self.root_dir = root_dir
|
| 59 |
+
self.img_size = img_size
|
| 60 |
+
self.split = split # train | train_aug | val
|
| 61 |
+
# self.list_path = self.root_dir + '/' + LIST_FOLDER_NAME + '/' + self.list + '.txt'
|
| 62 |
+
self.list_path = os.path.join(self.root_dir, LIST_FOLDER_NAME, self.split+'.txt')
|
| 63 |
+
self.img_name_list = load_img_name_list(self.list_path)
|
| 64 |
+
|
| 65 |
+
self.A_size = len(self.img_name_list) # get the size of dataset A
|
| 66 |
+
self.to_tensor = to_tensor
|
| 67 |
+
if is_train:
|
| 68 |
+
self.augm = CDDataAugmentation(
|
| 69 |
+
img_size=self.img_size,
|
| 70 |
+
with_random_hflip=True,
|
| 71 |
+
with_random_vflip=True,
|
| 72 |
+
with_scale_random_crop=True,
|
| 73 |
+
with_random_blur=True,
|
| 74 |
+
)
|
| 75 |
+
else:
|
| 76 |
+
self.augm = CDDataAugmentation(
|
| 77 |
+
img_size=self.img_size
|
| 78 |
+
)
|
| 79 |
+
def __getitem__(self, index):
|
| 80 |
+
name = self.img_name_list[index]
|
| 81 |
+
A_path = get_img_path(self.root_dir, self.img_name_list[index % self.A_size])
|
| 82 |
+
B_path = get_img_post_path(self.root_dir, self.img_name_list[index % self.A_size])
|
| 83 |
+
|
| 84 |
+
img = np.asarray(Image.open(A_path).convert('RGB'))
|
| 85 |
+
img_B = np.asarray(Image.open(B_path).convert('RGB'))
|
| 86 |
+
|
| 87 |
+
[img, img_B], _ = self.augm.transform([img, img_B],[], to_tensor=self.to_tensor)
|
| 88 |
+
|
| 89 |
+
return {'A': img, 'B': img_B, 'name': name}
|
| 90 |
+
|
| 91 |
+
def __len__(self):
|
| 92 |
+
"""Return the total number of images in the dataset."""
|
| 93 |
+
return self.A_size
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class CDDataset(ImageDataset):
|
| 97 |
+
|
| 98 |
+
def __init__(self, root_dir, img_size, split='train', is_train=True, label_transform=None,
|
| 99 |
+
to_tensor=True):
|
| 100 |
+
super(CDDataset, self).__init__(root_dir, img_size=img_size, split=split, is_train=is_train,
|
| 101 |
+
to_tensor=to_tensor)
|
| 102 |
+
self.label_transform = label_transform
|
| 103 |
+
|
| 104 |
+
def __getitem__(self, index):
|
| 105 |
+
name = self.img_name_list[index]
|
| 106 |
+
A_path = get_img_path(self.root_dir, self.img_name_list[index % self.A_size])
|
| 107 |
+
B_path = get_img_post_path(self.root_dir, self.img_name_list[index % self.A_size])
|
| 108 |
+
img = np.asarray(Image.open(A_path).convert('RGB'))
|
| 109 |
+
img_B = np.asarray(Image.open(B_path).convert('RGB'))
|
| 110 |
+
L_path = get_label_path(self.root_dir, self.img_name_list[index % self.A_size])
|
| 111 |
+
|
| 112 |
+
label = np.array(Image.open(L_path), dtype=np.uint8)
|
| 113 |
+
# 二分类中,前景标注为255
|
| 114 |
+
if self.label_transform == 'norm':
|
| 115 |
+
label = label // 255
|
| 116 |
+
|
| 117 |
+
[img, img_B], [label] = self.augm.transform([img, img_B], [label], to_tensor=self.to_tensor)
|
| 118 |
+
# print(label.max())
|
| 119 |
+
return {'name': name, 'A': img, 'B': img_B, 'L': label}
|
| 120 |
+
|
datasets/data_utils.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from PIL import ImageFilter
|
| 6 |
+
|
| 7 |
+
import torchvision.transforms.functional as TF
|
| 8 |
+
from torchvision import transforms
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def to_tensor_and_norm(imgs, labels):
|
| 13 |
+
# to tensor
|
| 14 |
+
imgs = [TF.to_tensor(img) for img in imgs]
|
| 15 |
+
labels = [torch.from_numpy(np.array(img, np.uint8)).unsqueeze(dim=0)
|
| 16 |
+
for img in labels]
|
| 17 |
+
|
| 18 |
+
imgs = [TF.normalize(img, mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
|
| 19 |
+
for img in imgs]
|
| 20 |
+
return imgs, labels
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class CDDataAugmentation:
|
| 24 |
+
|
| 25 |
+
def __init__(
|
| 26 |
+
self,
|
| 27 |
+
img_size,
|
| 28 |
+
with_random_hflip=False,
|
| 29 |
+
with_random_vflip=False,
|
| 30 |
+
with_random_rot=False,
|
| 31 |
+
with_random_crop=False,
|
| 32 |
+
with_scale_random_crop=False,
|
| 33 |
+
with_random_blur=False,
|
| 34 |
+
):
|
| 35 |
+
self.img_size = img_size
|
| 36 |
+
if self.img_size is None:
|
| 37 |
+
self.img_size_dynamic = True
|
| 38 |
+
else:
|
| 39 |
+
self.img_size_dynamic = False
|
| 40 |
+
self.with_random_hflip = with_random_hflip
|
| 41 |
+
self.with_random_vflip = with_random_vflip
|
| 42 |
+
self.with_random_rot = with_random_rot
|
| 43 |
+
self.with_random_crop = with_random_crop
|
| 44 |
+
self.with_scale_random_crop = with_scale_random_crop
|
| 45 |
+
self.with_random_blur = with_random_blur
|
| 46 |
+
def transform(self, imgs, labels, to_tensor=True):
|
| 47 |
+
"""
|
| 48 |
+
:param imgs: [ndarray,]
|
| 49 |
+
:param labels: [ndarray,]
|
| 50 |
+
:return: [ndarray,],[ndarray,]
|
| 51 |
+
"""
|
| 52 |
+
# resize image and covert to tensor
|
| 53 |
+
imgs = [TF.to_pil_image(img) for img in imgs]
|
| 54 |
+
if self.img_size is None:
|
| 55 |
+
self.img_size = None
|
| 56 |
+
|
| 57 |
+
if not self.img_size_dynamic:
|
| 58 |
+
if imgs[0].size != (self.img_size, self.img_size):
|
| 59 |
+
imgs = [TF.resize(img, [self.img_size, self.img_size], interpolation=3)
|
| 60 |
+
for img in imgs]
|
| 61 |
+
else:
|
| 62 |
+
self.img_size = imgs[0].size[0]
|
| 63 |
+
|
| 64 |
+
labels = [TF.to_pil_image(img) for img in labels]
|
| 65 |
+
if len(labels) != 0:
|
| 66 |
+
if labels[0].size != (self.img_size, self.img_size):
|
| 67 |
+
labels = [TF.resize(img, [self.img_size, self.img_size], interpolation=0)
|
| 68 |
+
for img in labels]
|
| 69 |
+
|
| 70 |
+
random_base = 0.5
|
| 71 |
+
if self.with_random_hflip and random.random() > 0.5:
|
| 72 |
+
imgs = [TF.hflip(img) for img in imgs]
|
| 73 |
+
labels = [TF.hflip(img) for img in labels]
|
| 74 |
+
|
| 75 |
+
if self.with_random_vflip and random.random() > 0.5:
|
| 76 |
+
imgs = [TF.vflip(img) for img in imgs]
|
| 77 |
+
labels = [TF.vflip(img) for img in labels]
|
| 78 |
+
|
| 79 |
+
if self.with_random_rot and random.random() > random_base:
|
| 80 |
+
angles = [90, 180, 270]
|
| 81 |
+
index = random.randint(0, 2)
|
| 82 |
+
angle = angles[index]
|
| 83 |
+
imgs = [TF.rotate(img, angle) for img in imgs]
|
| 84 |
+
labels = [TF.rotate(img, angle) for img in labels]
|
| 85 |
+
|
| 86 |
+
if self.with_random_crop and random.random() > 0:
|
| 87 |
+
i, j, h, w = transforms.RandomResizedCrop(size=self.img_size). \
|
| 88 |
+
get_params(img=imgs[0], scale=(0.8, 1.0), ratio=(1, 1))
|
| 89 |
+
|
| 90 |
+
imgs = [TF.resized_crop(img, i, j, h, w,
|
| 91 |
+
size=(self.img_size, self.img_size),
|
| 92 |
+
interpolation=Image.CUBIC)
|
| 93 |
+
for img in imgs]
|
| 94 |
+
|
| 95 |
+
labels = [TF.resized_crop(img, i, j, h, w,
|
| 96 |
+
size=(self.img_size, self.img_size),
|
| 97 |
+
interpolation=Image.NEAREST)
|
| 98 |
+
for img in labels]
|
| 99 |
+
|
| 100 |
+
if self.with_scale_random_crop:
|
| 101 |
+
# rescale
|
| 102 |
+
scale_range = [1, 1.2]
|
| 103 |
+
target_scale = scale_range[0] + random.random() * (scale_range[1] - scale_range[0])
|
| 104 |
+
|
| 105 |
+
imgs = [pil_rescale(img, target_scale, order=3) for img in imgs]
|
| 106 |
+
labels = [pil_rescale(img, target_scale, order=0) for img in labels]
|
| 107 |
+
# crop
|
| 108 |
+
imgsize = imgs[0].size # h, w
|
| 109 |
+
box = get_random_crop_box(imgsize=imgsize, cropsize=self.img_size)
|
| 110 |
+
imgs = [pil_crop(img, box, cropsize=self.img_size, default_value=0)
|
| 111 |
+
for img in imgs]
|
| 112 |
+
labels = [pil_crop(img, box, cropsize=self.img_size, default_value=255)
|
| 113 |
+
for img in labels]
|
| 114 |
+
|
| 115 |
+
if self.with_random_blur and random.random() > 0:
|
| 116 |
+
radius = random.random()
|
| 117 |
+
imgs = [img.filter(ImageFilter.GaussianBlur(radius=radius))
|
| 118 |
+
for img in imgs]
|
| 119 |
+
|
| 120 |
+
if to_tensor:
|
| 121 |
+
# to tensor
|
| 122 |
+
imgs = [TF.to_tensor(img) for img in imgs]
|
| 123 |
+
labels = [torch.from_numpy(np.array(img, np.uint8)).unsqueeze(dim=0)
|
| 124 |
+
for img in labels]
|
| 125 |
+
|
| 126 |
+
imgs = [TF.normalize(img, mean=[0.5, 0.5, 0.5],std=[0.5, 0.5, 0.5])
|
| 127 |
+
for img in imgs]
|
| 128 |
+
|
| 129 |
+
return imgs, labels
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def pil_crop(image, box, cropsize, default_value):
|
| 133 |
+
assert isinstance(image, Image.Image)
|
| 134 |
+
img = np.array(image)
|
| 135 |
+
|
| 136 |
+
if len(img.shape) == 3:
|
| 137 |
+
cont = np.ones((cropsize, cropsize, img.shape[2]), img.dtype)*default_value
|
| 138 |
+
else:
|
| 139 |
+
cont = np.ones((cropsize, cropsize), img.dtype)*default_value
|
| 140 |
+
cont[box[0]:box[1], box[2]:box[3]] = img[box[4]:box[5], box[6]:box[7]]
|
| 141 |
+
|
| 142 |
+
return Image.fromarray(cont)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def get_random_crop_box(imgsize, cropsize):
|
| 146 |
+
h, w = imgsize
|
| 147 |
+
ch = min(cropsize, h)
|
| 148 |
+
cw = min(cropsize, w)
|
| 149 |
+
|
| 150 |
+
w_space = w - cropsize
|
| 151 |
+
h_space = h - cropsize
|
| 152 |
+
|
| 153 |
+
if w_space > 0:
|
| 154 |
+
cont_left = 0
|
| 155 |
+
img_left = random.randrange(w_space + 1)
|
| 156 |
+
else:
|
| 157 |
+
cont_left = random.randrange(-w_space + 1)
|
| 158 |
+
img_left = 0
|
| 159 |
+
|
| 160 |
+
if h_space > 0:
|
| 161 |
+
cont_top = 0
|
| 162 |
+
img_top = random.randrange(h_space + 1)
|
| 163 |
+
else:
|
| 164 |
+
cont_top = random.randrange(-h_space + 1)
|
| 165 |
+
img_top = 0
|
| 166 |
+
|
| 167 |
+
return cont_top, cont_top+ch, cont_left, cont_left+cw, img_top, img_top+ch, img_left, img_left+cw
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def pil_rescale(img, scale, order):
|
| 171 |
+
assert isinstance(img, Image.Image)
|
| 172 |
+
height, width = img.size
|
| 173 |
+
target_size = (int(np.round(height*scale)), int(np.round(width*scale)))
|
| 174 |
+
return pil_resize(img, target_size, order)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def pil_resize(img, size, order):
|
| 178 |
+
assert isinstance(img, Image.Image)
|
| 179 |
+
if size[0] == img.size[0] and size[1] == img.size[1]:
|
| 180 |
+
return img
|
| 181 |
+
if order == 3:
|
| 182 |
+
resample = Image.BICUBIC
|
| 183 |
+
elif order == 0:
|
| 184 |
+
resample = Image.NEAREST
|
| 185 |
+
return img.resize(size[::-1], resample)
|
demo.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from argparse import ArgumentParser
|
| 2 |
+
|
| 3 |
+
import utils
|
| 4 |
+
import torch
|
| 5 |
+
from models.basic_model import CDEvaluator
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
"""
|
| 10 |
+
quick start
|
| 11 |
+
|
| 12 |
+
sample files in ./samples
|
| 13 |
+
|
| 14 |
+
save prediction files in the ./samples/predict
|
| 15 |
+
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def get_args():
|
| 20 |
+
# ------------
|
| 21 |
+
# args
|
| 22 |
+
# ------------
|
| 23 |
+
parser = ArgumentParser()
|
| 24 |
+
parser.add_argument('--project_name', default='BIT_LEVIR', type=str)
|
| 25 |
+
parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
|
| 26 |
+
parser.add_argument('--checkpoint_root', default='checkpoints', type=str)
|
| 27 |
+
parser.add_argument('--output_folder', default='samples/predict', type=str)
|
| 28 |
+
|
| 29 |
+
# data
|
| 30 |
+
parser.add_argument('--num_workers', default=0, type=int)
|
| 31 |
+
parser.add_argument('--dataset', default='CDDataset', type=str)
|
| 32 |
+
parser.add_argument('--data_name', default='quick_start', type=str)
|
| 33 |
+
|
| 34 |
+
parser.add_argument('--batch_size', default=1, type=int)
|
| 35 |
+
parser.add_argument('--split', default="demo", type=str)
|
| 36 |
+
parser.add_argument('--img_size', default=256, type=int)
|
| 37 |
+
|
| 38 |
+
# model
|
| 39 |
+
parser.add_argument('--n_class', default=2, type=int)
|
| 40 |
+
parser.add_argument('--net_G', default='base_transformer_pos_s4_dd8_dedim8', type=str,
|
| 41 |
+
help='base_resnet18 | base_transformer_pos_s4_dd8 | base_transformer_pos_s4_dd8_dedim8|')
|
| 42 |
+
parser.add_argument('--checkpoint_name', default='best_ckpt.pt', type=str)
|
| 43 |
+
|
| 44 |
+
args = parser.parse_args()
|
| 45 |
+
return args
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
if __name__ == '__main__':
|
| 49 |
+
|
| 50 |
+
args = get_args()
|
| 51 |
+
utils.get_device(args)
|
| 52 |
+
device = torch.device("cuda:%s" % args.gpu_ids[0]
|
| 53 |
+
if torch.cuda.is_available() and len(args.gpu_ids)>0
|
| 54 |
+
else "cpu")
|
| 55 |
+
args.checkpoint_dir = os.path.join(args.checkpoint_root, args.project_name)
|
| 56 |
+
os.makedirs(args.output_folder, exist_ok=True)
|
| 57 |
+
|
| 58 |
+
log_path = os.path.join(args.output_folder, 'log_vis.txt')
|
| 59 |
+
|
| 60 |
+
data_loader = utils.get_loader(args.data_name, img_size=args.img_size,
|
| 61 |
+
batch_size=args.batch_size,
|
| 62 |
+
split=args.split, is_train=False)
|
| 63 |
+
|
| 64 |
+
model = CDEvaluator(args)
|
| 65 |
+
model.load_checkpoint(args.checkpoint_name)
|
| 66 |
+
model.eval()
|
| 67 |
+
|
| 68 |
+
for i, batch in enumerate(data_loader):
|
| 69 |
+
name = batch['name']
|
| 70 |
+
print('process: %s' % name)
|
| 71 |
+
score_map = model._forward_pass(batch)
|
| 72 |
+
model._save_predictions()
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
|
eval_cd.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from argparse import ArgumentParser
|
| 2 |
+
import torch
|
| 3 |
+
from models.evaluator import *
|
| 4 |
+
|
| 5 |
+
print(torch.cuda.is_available())
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
"""
|
| 9 |
+
eval the CD model
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
def main():
|
| 13 |
+
# ------------
|
| 14 |
+
# args
|
| 15 |
+
# ------------
|
| 16 |
+
parser = ArgumentParser()
|
| 17 |
+
parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
|
| 18 |
+
parser.add_argument('--project_name', default='test', type=str)
|
| 19 |
+
parser.add_argument('--print_models', default=False, type=bool, help='print models')
|
| 20 |
+
|
| 21 |
+
# data
|
| 22 |
+
parser.add_argument('--num_workers', default=4, type=int)
|
| 23 |
+
parser.add_argument('--dataset', default='CDDataset', type=str)
|
| 24 |
+
parser.add_argument('--data_name', default='LEVIR', type=str)
|
| 25 |
+
|
| 26 |
+
parser.add_argument('--batch_size', default=8, type=int)
|
| 27 |
+
parser.add_argument('--split', default="test", type=str)
|
| 28 |
+
|
| 29 |
+
parser.add_argument('--img_size', default=256, type=int)
|
| 30 |
+
|
| 31 |
+
# model
|
| 32 |
+
parser.add_argument('--n_class', default=2, type=int)
|
| 33 |
+
parser.add_argument('--net_G', default='base_transformer_pos_s4_dd8_dedim8', type=str,
|
| 34 |
+
help='base_resnet18 | base_transformer_pos_s4_dd8 | base_transformer_pos_s4_dd8_dedim8|')
|
| 35 |
+
|
| 36 |
+
parser.add_argument('--checkpoint_name', default='best_ckpt.pt', type=str)
|
| 37 |
+
|
| 38 |
+
args = parser.parse_args()
|
| 39 |
+
utils.get_device(args)
|
| 40 |
+
print(args.gpu_ids)
|
| 41 |
+
|
| 42 |
+
# checkpoints dir
|
| 43 |
+
args.checkpoint_dir = os.path.join('checkpoints', args.project_name)
|
| 44 |
+
os.makedirs(args.checkpoint_dir, exist_ok=True)
|
| 45 |
+
# visualize dir
|
| 46 |
+
args.vis_dir = os.path.join('vis', args.project_name)
|
| 47 |
+
os.makedirs(args.vis_dir, exist_ok=True)
|
| 48 |
+
|
| 49 |
+
dataloader = utils.get_loader(args.data_name, img_size=args.img_size,
|
| 50 |
+
batch_size=args.batch_size, is_train=False,
|
| 51 |
+
split=args.split)
|
| 52 |
+
model = CDEvaluator(args=args, dataloader=dataloader)
|
| 53 |
+
|
| 54 |
+
model.eval_models(checkpoint_name=args.checkpoint_name)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
if __name__ == '__main__':
|
| 58 |
+
main()
|
| 59 |
+
|
main_cd.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from argparse import ArgumentParser
|
| 2 |
+
import torch
|
| 3 |
+
from models.trainer import *
|
| 4 |
+
|
| 5 |
+
print(torch.cuda.is_available())
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
the main function for training the CD networks
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def train(args):
|
| 13 |
+
dataloaders = utils.get_loaders(args)
|
| 14 |
+
model = CDTrainer(args=args, dataloaders=dataloaders)
|
| 15 |
+
model.train_models()
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test(args):
|
| 19 |
+
from models.evaluator import CDEvaluator
|
| 20 |
+
dataloader = utils.get_loader(args.data_name, img_size=args.img_size,
|
| 21 |
+
batch_size=args.batch_size, is_train=False,
|
| 22 |
+
split='test')
|
| 23 |
+
model = CDEvaluator(args=args, dataloader=dataloader)
|
| 24 |
+
|
| 25 |
+
model.eval_models()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
if __name__ == '__main__':
|
| 29 |
+
# ------------
|
| 30 |
+
# args
|
| 31 |
+
# ------------
|
| 32 |
+
parser = ArgumentParser()
|
| 33 |
+
parser.add_argument('--gpu_ids', type=str, default='0', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
|
| 34 |
+
parser.add_argument('--project_name', default='test', type=str)
|
| 35 |
+
parser.add_argument('--checkpoint_root', default='checkpoints', type=str)
|
| 36 |
+
|
| 37 |
+
# data
|
| 38 |
+
parser.add_argument('--num_workers', default=4, type=int)
|
| 39 |
+
parser.add_argument('--dataset', default='CDDataset', type=str)
|
| 40 |
+
parser.add_argument('--data_name', default='LEVIR', type=str)
|
| 41 |
+
|
| 42 |
+
parser.add_argument('--batch_size', default=8, type=int)
|
| 43 |
+
parser.add_argument('--split', default="train", type=str)
|
| 44 |
+
parser.add_argument('--split_val', default="val", type=str)
|
| 45 |
+
|
| 46 |
+
parser.add_argument('--img_size', default=256, type=int)
|
| 47 |
+
|
| 48 |
+
# model
|
| 49 |
+
parser.add_argument('--n_class', default=2, type=int)
|
| 50 |
+
parser.add_argument('--net_G', default='base_transformer_pos_s4_dd8', type=str,
|
| 51 |
+
help='base_resnet18 | base_transformer_pos_s4 | '
|
| 52 |
+
'base_transformer_pos_s4_dd8 | '
|
| 53 |
+
'base_transformer_pos_s4_dd8_dedim8|')
|
| 54 |
+
parser.add_argument('--loss', default='ce', type=str)
|
| 55 |
+
|
| 56 |
+
# optimizer
|
| 57 |
+
parser.add_argument('--optimizer', default='sgd', type=str)
|
| 58 |
+
parser.add_argument('--lr', default=0.01, type=float)
|
| 59 |
+
parser.add_argument('--max_epochs', default=100, type=int)
|
| 60 |
+
parser.add_argument('--lr_policy', default='linear', type=str,
|
| 61 |
+
help='linear | step')
|
| 62 |
+
parser.add_argument('--lr_decay_iters', default=100, type=int)
|
| 63 |
+
|
| 64 |
+
args = parser.parse_args()
|
| 65 |
+
utils.get_device(args)
|
| 66 |
+
print(args.gpu_ids)
|
| 67 |
+
|
| 68 |
+
# checkpoints dir
|
| 69 |
+
args.checkpoint_dir = os.path.join(args.checkpoint_root, args.project_name)
|
| 70 |
+
os.makedirs(args.checkpoint_dir, exist_ok=True)
|
| 71 |
+
# visualize dir
|
| 72 |
+
args.vis_dir = os.path.join('vis', args.project_name)
|
| 73 |
+
os.makedirs(args.vis_dir, exist_ok=True)
|
| 74 |
+
|
| 75 |
+
train(args)
|
| 76 |
+
|
| 77 |
+
test(args)
|
misc/imutils.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import numpy as np
|
| 3 |
+
import cv2
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from PIL import ImageFilter
|
| 6 |
+
import PIL
|
| 7 |
+
import tifffile
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def cv_rotate(image, angle, borderValue):
|
| 11 |
+
"""
|
| 12 |
+
rot angle, fill with borderValue
|
| 13 |
+
"""
|
| 14 |
+
# grab the dimensions of the image and then determine the
|
| 15 |
+
# center
|
| 16 |
+
(h, w) = image.shape[:2]
|
| 17 |
+
(cX, cY) = (w // 2, h // 2)
|
| 18 |
+
|
| 19 |
+
# grab the rotation matrix (applying the negative of the
|
| 20 |
+
# angle to rotate clockwise), then grab the sine and cosine
|
| 21 |
+
# (i.e., the rotation components of the matrix)
|
| 22 |
+
# -angle位置参数为角度参数负值表示顺时针旋转; 1.0位置参数scale是调整尺寸比例(图像缩放参数),建议0.75
|
| 23 |
+
M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
|
| 24 |
+
cos = np.abs(M[0, 0])
|
| 25 |
+
sin = np.abs(M[0, 1])
|
| 26 |
+
|
| 27 |
+
# compute the new bounding dimensions of the image
|
| 28 |
+
nW = int((h * sin) + (w * cos))
|
| 29 |
+
nH = int((h * cos) + (w * sin))
|
| 30 |
+
|
| 31 |
+
# adjust the rotation matrix to take into account translation
|
| 32 |
+
M[0, 2] += (nW / 2) - cX
|
| 33 |
+
M[1, 2] += (nH / 2) - cY
|
| 34 |
+
if isinstance(borderValue, int):
|
| 35 |
+
values = (borderValue, borderValue, borderValue)
|
| 36 |
+
else:
|
| 37 |
+
values = borderValue
|
| 38 |
+
# perform the actual rotation and return the image
|
| 39 |
+
return cv2.warpAffine(image, M, (nW, nH), borderValue=values)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def pil_resize(img, size, order):
|
| 43 |
+
if size[0] == img.shape[0] and size[1] == img.shape[1]:
|
| 44 |
+
return img
|
| 45 |
+
|
| 46 |
+
if order == 3:
|
| 47 |
+
resample = Image.BICUBIC
|
| 48 |
+
elif order == 0:
|
| 49 |
+
resample = Image.NEAREST
|
| 50 |
+
|
| 51 |
+
return np.asarray(Image.fromarray(img).resize(size[::-1], resample))
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def pil_rescale(img, scale, order):
|
| 55 |
+
height, width = img.shape[:2]
|
| 56 |
+
target_size = (int(np.round(height*scale)), int(np.round(width*scale)))
|
| 57 |
+
return pil_resize(img, target_size, order)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def pil_rotate(img, degree, default_value):
|
| 61 |
+
if isinstance(default_value, tuple):
|
| 62 |
+
values = (default_value[0], default_value[1], default_value[2], 0)
|
| 63 |
+
else:
|
| 64 |
+
values = (default_value, default_value, default_value,0)
|
| 65 |
+
img = Image.fromarray(img)
|
| 66 |
+
if img.mode =='RGB':
|
| 67 |
+
# set img padding == default_value
|
| 68 |
+
img2 = img.convert('RGBA')
|
| 69 |
+
rot = img2.rotate(degree, expand=1)
|
| 70 |
+
fff = Image.new('RGBA', rot.size, values) # 灰色
|
| 71 |
+
out = Image.composite(rot, fff, rot)
|
| 72 |
+
img = out.convert(img.mode)
|
| 73 |
+
|
| 74 |
+
else:
|
| 75 |
+
# set label padding == default_value
|
| 76 |
+
img2 = img.convert('RGBA')
|
| 77 |
+
rot = img2.rotate(degree, expand=1)
|
| 78 |
+
# a white image same size as rotated image
|
| 79 |
+
fff = Image.new('RGBA', rot.size, values)
|
| 80 |
+
# create a composite image using the alpha layer of rot as a mask
|
| 81 |
+
out = Image.composite(rot, fff, rot)
|
| 82 |
+
img = out.convert(img.mode)
|
| 83 |
+
|
| 84 |
+
return np.asarray(img)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def random_resize_long_image_list(img_list, min_long, max_long):
|
| 88 |
+
target_long = random.randint(min_long, max_long)
|
| 89 |
+
h, w = img_list[0].shape[:2]
|
| 90 |
+
if w < h:
|
| 91 |
+
scale = target_long / h
|
| 92 |
+
else:
|
| 93 |
+
scale = target_long / w
|
| 94 |
+
out = []
|
| 95 |
+
for img in img_list:
|
| 96 |
+
out.append(pil_rescale(img, scale, 3) )
|
| 97 |
+
return out
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def random_resize_long(img, min_long, max_long):
|
| 101 |
+
target_long = random.randint(min_long, max_long)
|
| 102 |
+
h, w = img.shape[:2]
|
| 103 |
+
|
| 104 |
+
if w < h:
|
| 105 |
+
scale = target_long / h
|
| 106 |
+
else:
|
| 107 |
+
scale = target_long / w
|
| 108 |
+
|
| 109 |
+
return pil_rescale(img, scale, 3)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def random_scale_list(img_list, scale_range, order):
|
| 113 |
+
"""
|
| 114 |
+
输入:图像列表
|
| 115 |
+
"""
|
| 116 |
+
target_scale = scale_range[0] + random.random() * (scale_range[1] - scale_range[0])
|
| 117 |
+
|
| 118 |
+
if isinstance(img_list, tuple):
|
| 119 |
+
assert img_list.__len__() == 2
|
| 120 |
+
img1 = []
|
| 121 |
+
img2 = []
|
| 122 |
+
for img in img_list[0]:
|
| 123 |
+
img1.append(pil_rescale(img, target_scale, order[0]))
|
| 124 |
+
for img in img_list[1]:
|
| 125 |
+
img2.append(pil_rescale(img, target_scale, order[1]))
|
| 126 |
+
return (img1, img2)
|
| 127 |
+
else:
|
| 128 |
+
out = []
|
| 129 |
+
for img in img_list:
|
| 130 |
+
out.append(pil_rescale(img, target_scale, order))
|
| 131 |
+
return out
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def random_scale(img, scale_range, order):
|
| 135 |
+
|
| 136 |
+
target_scale = scale_range[0] + random.random() * (scale_range[1] - scale_range[0])
|
| 137 |
+
|
| 138 |
+
if isinstance(img, tuple):
|
| 139 |
+
return (pil_rescale(img[0], target_scale, order[0]), pil_rescale(img[1], target_scale, order[1]))
|
| 140 |
+
else:
|
| 141 |
+
return pil_rescale(img, target_scale, order)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def random_rotate_list(img_list, max_degree, default_values):
|
| 145 |
+
degree = random.random() * max_degree
|
| 146 |
+
if isinstance(img_list, tuple):
|
| 147 |
+
assert img_list.__len__() == 2
|
| 148 |
+
img1 = []
|
| 149 |
+
img2 = []
|
| 150 |
+
for img in img_list[0]:
|
| 151 |
+
assert isinstance(img, np.ndarray)
|
| 152 |
+
img1.append((pil_rotate(img, degree, default_values[0])))
|
| 153 |
+
for img in img_list[1]:
|
| 154 |
+
img2.append((pil_rotate(img, degree, default_values[1])))
|
| 155 |
+
return (img1, img2)
|
| 156 |
+
else:
|
| 157 |
+
out = []
|
| 158 |
+
for img in img_list:
|
| 159 |
+
out.append(pil_rotate(img, degree, default_values))
|
| 160 |
+
return out
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def random_rotate(img, max_degree, default_values):
|
| 164 |
+
degree = random.random() * max_degree
|
| 165 |
+
if isinstance(img, tuple):
|
| 166 |
+
return (pil_rotate(img[0], degree, default_values[0]),
|
| 167 |
+
pil_rotate(img[1], degree, default_values[1]))
|
| 168 |
+
else:
|
| 169 |
+
return pil_rotate(img, degree, default_values)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def random_lr_flip_list(img_list):
|
| 173 |
+
|
| 174 |
+
if bool(random.getrandbits(1)):
|
| 175 |
+
if isinstance(img_list, tuple):
|
| 176 |
+
assert img_list.__len__()==2
|
| 177 |
+
img1=list((np.fliplr(m) for m in img_list[0]))
|
| 178 |
+
img2=list((np.fliplr(m) for m in img_list[1]))
|
| 179 |
+
|
| 180 |
+
return (img1, img2)
|
| 181 |
+
else:
|
| 182 |
+
return list([np.fliplr(m) for m in img_list])
|
| 183 |
+
else:
|
| 184 |
+
return img_list
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def random_lr_flip(img):
|
| 188 |
+
|
| 189 |
+
if bool(random.getrandbits(1)):
|
| 190 |
+
if isinstance(img, tuple):
|
| 191 |
+
return tuple([np.fliplr(m) for m in img])
|
| 192 |
+
else:
|
| 193 |
+
return np.fliplr(img)
|
| 194 |
+
else:
|
| 195 |
+
return img
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def get_random_crop_box(imgsize, cropsize):
|
| 199 |
+
h, w = imgsize
|
| 200 |
+
|
| 201 |
+
ch = min(cropsize, h)
|
| 202 |
+
cw = min(cropsize, w)
|
| 203 |
+
|
| 204 |
+
w_space = w - cropsize
|
| 205 |
+
h_space = h - cropsize
|
| 206 |
+
|
| 207 |
+
if w_space > 0:
|
| 208 |
+
cont_left = 0
|
| 209 |
+
img_left = random.randrange(w_space + 1)
|
| 210 |
+
else:
|
| 211 |
+
cont_left = random.randrange(-w_space + 1)
|
| 212 |
+
img_left = 0
|
| 213 |
+
|
| 214 |
+
if h_space > 0:
|
| 215 |
+
cont_top = 0
|
| 216 |
+
img_top = random.randrange(h_space + 1)
|
| 217 |
+
else:
|
| 218 |
+
cont_top = random.randrange(-h_space + 1)
|
| 219 |
+
img_top = 0
|
| 220 |
+
|
| 221 |
+
return cont_top, cont_top+ch, cont_left, cont_left+cw, img_top, img_top+ch, img_left, img_left+cw
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def random_crop_list(images_list, cropsize, default_values):
|
| 225 |
+
|
| 226 |
+
if isinstance(images_list, tuple):
|
| 227 |
+
imgsize = images_list[0][0].shape[:2]
|
| 228 |
+
elif isinstance(images_list, list):
|
| 229 |
+
imgsize = images_list[0].shape[:2]
|
| 230 |
+
else:
|
| 231 |
+
raise RuntimeError('do not support the type of image_list')
|
| 232 |
+
if isinstance(default_values, int): default_values = (default_values,)
|
| 233 |
+
|
| 234 |
+
box = get_random_crop_box(imgsize, cropsize)
|
| 235 |
+
if isinstance(images_list, tuple):
|
| 236 |
+
assert images_list.__len__()==2
|
| 237 |
+
img1 = []
|
| 238 |
+
img2 = []
|
| 239 |
+
for img in images_list[0]:
|
| 240 |
+
f = default_values[0]
|
| 241 |
+
if len(img.shape) == 3:
|
| 242 |
+
cont = np.ones((cropsize, cropsize, img.shape[2]), img.dtype)*f
|
| 243 |
+
else:
|
| 244 |
+
cont = np.ones((cropsize, cropsize), img.dtype)*f
|
| 245 |
+
cont[box[0]:box[1], box[2]:box[3]] = img[box[4]:box[5], box[6]:box[7]]
|
| 246 |
+
img1.append(cont)
|
| 247 |
+
for img in images_list[1]:
|
| 248 |
+
f = default_values[1]
|
| 249 |
+
if len(img.shape) == 3:
|
| 250 |
+
cont = np.ones((cropsize, cropsize, img.shape[2]), img.dtype)*f
|
| 251 |
+
else:
|
| 252 |
+
cont = np.ones((cropsize, cropsize), img.dtype)*f
|
| 253 |
+
cont[box[0]:box[1], box[2]:box[3]] = img[box[4]:box[5], box[6]:box[7]]
|
| 254 |
+
img2.append(cont)
|
| 255 |
+
return (img1, img2)
|
| 256 |
+
else:
|
| 257 |
+
out = []
|
| 258 |
+
for img in images_list:
|
| 259 |
+
f = default_values
|
| 260 |
+
if len(img.shape) == 3:
|
| 261 |
+
cont = np.ones((cropsize, cropsize, img.shape[2]), img.dtype) * f
|
| 262 |
+
else:
|
| 263 |
+
cont = np.ones((cropsize, cropsize), img.dtype) * f
|
| 264 |
+
cont[box[0]:box[1], box[2]:box[3]] = img[box[4]:box[5], box[6]:box[7]]
|
| 265 |
+
out.append(cont)
|
| 266 |
+
return out
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def random_crop(images, cropsize, default_values):
|
| 270 |
+
|
| 271 |
+
if isinstance(images, np.ndarray): images = (images,)
|
| 272 |
+
if isinstance(default_values, int): default_values = (default_values,)
|
| 273 |
+
|
| 274 |
+
imgsize = images[0].shape[:2]
|
| 275 |
+
box = get_random_crop_box(imgsize, cropsize)
|
| 276 |
+
|
| 277 |
+
new_images = []
|
| 278 |
+
for img, f in zip(images, default_values):
|
| 279 |
+
|
| 280 |
+
if len(img.shape) == 3:
|
| 281 |
+
cont = np.ones((cropsize, cropsize, img.shape[2]), img.dtype)*f
|
| 282 |
+
else:
|
| 283 |
+
cont = np.ones((cropsize, cropsize), img.dtype)*f
|
| 284 |
+
cont[box[0]:box[1], box[2]:box[3]] = img[box[4]:box[5], box[6]:box[7]]
|
| 285 |
+
new_images.append(cont)
|
| 286 |
+
|
| 287 |
+
if len(new_images) == 1:
|
| 288 |
+
new_images = new_images[0]
|
| 289 |
+
|
| 290 |
+
return new_images
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def top_left_crop(img, cropsize, default_value):
|
| 294 |
+
|
| 295 |
+
h, w = img.shape[:2]
|
| 296 |
+
|
| 297 |
+
ch = min(cropsize, h)
|
| 298 |
+
cw = min(cropsize, w)
|
| 299 |
+
|
| 300 |
+
if len(img.shape) == 2:
|
| 301 |
+
container = np.ones((cropsize, cropsize), img.dtype)*default_value
|
| 302 |
+
else:
|
| 303 |
+
container = np.ones((cropsize, cropsize, img.shape[2]), img.dtype)*default_value
|
| 304 |
+
|
| 305 |
+
container[:ch, :cw] = img[:ch, :cw]
|
| 306 |
+
|
| 307 |
+
return container
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def center_crop(img, cropsize, default_value=0):
|
| 311 |
+
|
| 312 |
+
h, w = img.shape[:2]
|
| 313 |
+
|
| 314 |
+
ch = min(cropsize, h)
|
| 315 |
+
cw = min(cropsize, w)
|
| 316 |
+
|
| 317 |
+
sh = h - cropsize
|
| 318 |
+
sw = w - cropsize
|
| 319 |
+
|
| 320 |
+
if sw > 0:
|
| 321 |
+
cont_left = 0
|
| 322 |
+
img_left = int(round(sw / 2))
|
| 323 |
+
else:
|
| 324 |
+
cont_left = int(round(-sw / 2))
|
| 325 |
+
img_left = 0
|
| 326 |
+
|
| 327 |
+
if sh > 0:
|
| 328 |
+
cont_top = 0
|
| 329 |
+
img_top = int(round(sh / 2))
|
| 330 |
+
else:
|
| 331 |
+
cont_top = int(round(-sh / 2))
|
| 332 |
+
img_top = 0
|
| 333 |
+
|
| 334 |
+
if len(img.shape) == 2:
|
| 335 |
+
container = np.ones((cropsize, cropsize), img.dtype)*default_value
|
| 336 |
+
else:
|
| 337 |
+
container = np.ones((cropsize, cropsize, img.shape[2]), img.dtype)*default_value
|
| 338 |
+
|
| 339 |
+
container[cont_top:cont_top+ch, cont_left:cont_left+cw] = \
|
| 340 |
+
img[img_top:img_top+ch, img_left:img_left+cw]
|
| 341 |
+
|
| 342 |
+
return container
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
def HWC_to_CHW(img):
|
| 346 |
+
return np.transpose(img, (2, 0, 1))
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def pil_blur(img, radius):
|
| 350 |
+
return np.array(Image.fromarray(img).filter(ImageFilter.GaussianBlur(radius=radius)))
|
| 351 |
+
|
| 352 |
+
|
| 353 |
+
def random_blur(img):
|
| 354 |
+
radius = random.random()
|
| 355 |
+
# print('add blur: ', radius)
|
| 356 |
+
if isinstance(img, list):
|
| 357 |
+
out = []
|
| 358 |
+
for im in img:
|
| 359 |
+
out.append(pil_blur(im, radius))
|
| 360 |
+
return out
|
| 361 |
+
elif isinstance(img, np.ndarray):
|
| 362 |
+
return pil_blur(img, radius)
|
| 363 |
+
else:
|
| 364 |
+
print(img)
|
| 365 |
+
raise RuntimeError("do not support the input image type!")
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
def save_image(image_numpy, image_path):
|
| 369 |
+
"""Save a numpy image to the disk
|
| 370 |
+
Parameters:
|
| 371 |
+
image_numpy (numpy array) -- input numpy array
|
| 372 |
+
image_path (str) -- the path of the image
|
| 373 |
+
"""
|
| 374 |
+
image_pil = Image.fromarray(np.array(image_numpy,dtype=np.uint8))
|
| 375 |
+
image_pil.save(image_path)
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def im2arr(img_path, mode=1, dtype=np.uint8):
|
| 379 |
+
"""
|
| 380 |
+
:param img_path:
|
| 381 |
+
:param mode:
|
| 382 |
+
:return: numpy.ndarray, shape: H*W*C
|
| 383 |
+
"""
|
| 384 |
+
if mode==1:
|
| 385 |
+
img = PIL.Image.open(img_path)
|
| 386 |
+
arr = np.asarray(img, dtype=dtype)
|
| 387 |
+
else:
|
| 388 |
+
arr = tifffile.imread(img_path)
|
| 389 |
+
if arr.ndim == 3:
|
| 390 |
+
a, b, c = arr.shape
|
| 391 |
+
if a < b and a < c: # 当arr为C*H*W时,需要交换通道顺序
|
| 392 |
+
arr = arr.transpose([1,2,0])
|
| 393 |
+
# print('shape: ', arr.shape, 'dytpe: ',arr.dtype)
|
| 394 |
+
return arr
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
|
misc/logger_tool.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
import time
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class Logger(object):
|
| 6 |
+
def __init__(self, outfile):
|
| 7 |
+
self.terminal = sys.stdout
|
| 8 |
+
self.log_path = outfile
|
| 9 |
+
now = time.strftime("%c")
|
| 10 |
+
self.write('================ (%s) ================\n' % now)
|
| 11 |
+
|
| 12 |
+
def write(self, message):
|
| 13 |
+
self.terminal.write(message)
|
| 14 |
+
with open(self.log_path, mode='a') as f:
|
| 15 |
+
f.write(message)
|
| 16 |
+
|
| 17 |
+
def write_dict(self, dict):
|
| 18 |
+
message = ''
|
| 19 |
+
for k, v in dict.items():
|
| 20 |
+
message += '%s: %.7f ' % (k, v)
|
| 21 |
+
self.write(message)
|
| 22 |
+
|
| 23 |
+
def write_dict_str(self, dict):
|
| 24 |
+
message = ''
|
| 25 |
+
for k, v in dict.items():
|
| 26 |
+
message += '%s: %s ' % (k, v)
|
| 27 |
+
self.write(message)
|
| 28 |
+
|
| 29 |
+
def flush(self):
|
| 30 |
+
self.terminal.flush()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class Timer:
|
| 34 |
+
def __init__(self, starting_msg = None):
|
| 35 |
+
self.start = time.time()
|
| 36 |
+
self.stage_start = self.start
|
| 37 |
+
|
| 38 |
+
if starting_msg is not None:
|
| 39 |
+
print(starting_msg, time.ctime(time.time()))
|
| 40 |
+
|
| 41 |
+
def __enter__(self):
|
| 42 |
+
return self
|
| 43 |
+
|
| 44 |
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
def update_progress(self, progress):
|
| 48 |
+
self.elapsed = time.time() - self.start
|
| 49 |
+
self.est_total = self.elapsed / progress
|
| 50 |
+
self.est_remaining = self.est_total - self.elapsed
|
| 51 |
+
self.est_finish = int(self.start + self.est_total)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def str_estimated_complete(self):
|
| 55 |
+
return str(time.ctime(self.est_finish))
|
| 56 |
+
|
| 57 |
+
def str_estimated_remaining(self):
|
| 58 |
+
return str(self.est_remaining/3600) + 'h'
|
| 59 |
+
|
| 60 |
+
def estimated_remaining(self):
|
| 61 |
+
return self.est_remaining/3600
|
| 62 |
+
|
| 63 |
+
def get_stage_elapsed(self):
|
| 64 |
+
return time.time() - self.stage_start
|
| 65 |
+
|
| 66 |
+
def reset_stage(self):
|
| 67 |
+
self.stage_start = time.time()
|
| 68 |
+
|
| 69 |
+
def lapse(self):
|
| 70 |
+
out = time.time() - self.stage_start
|
| 71 |
+
self.stage_start = time.time()
|
| 72 |
+
return out
|
| 73 |
+
|
misc/metric_tool.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
################### metrics ###################
|
| 5 |
+
class AverageMeter(object):
|
| 6 |
+
"""Computes and stores the average and current value"""
|
| 7 |
+
def __init__(self):
|
| 8 |
+
self.initialized = False
|
| 9 |
+
self.val = None
|
| 10 |
+
self.avg = None
|
| 11 |
+
self.sum = None
|
| 12 |
+
self.count = None
|
| 13 |
+
|
| 14 |
+
def initialize(self, val, weight):
|
| 15 |
+
self.val = val
|
| 16 |
+
self.avg = val
|
| 17 |
+
self.sum = val * weight
|
| 18 |
+
self.count = weight
|
| 19 |
+
self.initialized = True
|
| 20 |
+
|
| 21 |
+
def update(self, val, weight=1):
|
| 22 |
+
if not self.initialized:
|
| 23 |
+
self.initialize(val, weight)
|
| 24 |
+
else:
|
| 25 |
+
self.add(val, weight)
|
| 26 |
+
|
| 27 |
+
def add(self, val, weight):
|
| 28 |
+
self.val = val
|
| 29 |
+
self.sum += val * weight
|
| 30 |
+
self.count += weight
|
| 31 |
+
self.avg = self.sum / self.count
|
| 32 |
+
|
| 33 |
+
def value(self):
|
| 34 |
+
return self.val
|
| 35 |
+
|
| 36 |
+
def average(self):
|
| 37 |
+
return self.avg
|
| 38 |
+
|
| 39 |
+
def get_scores(self):
|
| 40 |
+
scores_dict = cm2score(self.sum)
|
| 41 |
+
return scores_dict
|
| 42 |
+
|
| 43 |
+
def clear(self):
|
| 44 |
+
self.initialized = False
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
################### cm metrics ###################
|
| 48 |
+
class ConfuseMatrixMeter(AverageMeter):
|
| 49 |
+
"""Computes and stores the average and current value"""
|
| 50 |
+
def __init__(self, n_class):
|
| 51 |
+
super(ConfuseMatrixMeter, self).__init__()
|
| 52 |
+
self.n_class = n_class
|
| 53 |
+
|
| 54 |
+
def update_cm(self, pr, gt, weight=1):
|
| 55 |
+
"""获得当前混淆矩阵,并计算当前F1得分,并更新混淆矩阵"""
|
| 56 |
+
val = get_confuse_matrix(num_classes=self.n_class, label_gts=gt, label_preds=pr)
|
| 57 |
+
self.update(val, weight)
|
| 58 |
+
current_score = cm2F1(val)
|
| 59 |
+
return current_score
|
| 60 |
+
|
| 61 |
+
def get_scores(self):
|
| 62 |
+
scores_dict = cm2score(self.sum)
|
| 63 |
+
return scores_dict
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def harmonic_mean(xs):
|
| 68 |
+
harmonic_mean = len(xs) / sum((x+1e-6)**-1 for x in xs)
|
| 69 |
+
return harmonic_mean
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def cm2F1(confusion_matrix):
|
| 73 |
+
hist = confusion_matrix
|
| 74 |
+
n_class = hist.shape[0]
|
| 75 |
+
tp = np.diag(hist)
|
| 76 |
+
sum_a1 = hist.sum(axis=1)
|
| 77 |
+
sum_a0 = hist.sum(axis=0)
|
| 78 |
+
# ---------------------------------------------------------------------- #
|
| 79 |
+
# 1. Accuracy & Class Accuracy
|
| 80 |
+
# ---------------------------------------------------------------------- #
|
| 81 |
+
acc = tp.sum() / (hist.sum() + np.finfo(np.float32).eps)
|
| 82 |
+
|
| 83 |
+
# recall
|
| 84 |
+
recall = tp / (sum_a1 + np.finfo(np.float32).eps)
|
| 85 |
+
# acc_cls = np.nanmean(recall)
|
| 86 |
+
|
| 87 |
+
# precision
|
| 88 |
+
precision = tp / (sum_a0 + np.finfo(np.float32).eps)
|
| 89 |
+
|
| 90 |
+
# F1 score
|
| 91 |
+
F1 = 2 * recall * precision / (recall + precision + np.finfo(np.float32).eps)
|
| 92 |
+
mean_F1 = np.nanmean(F1)
|
| 93 |
+
return mean_F1
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def cm2score(confusion_matrix):
|
| 97 |
+
hist = confusion_matrix
|
| 98 |
+
n_class = hist.shape[0]
|
| 99 |
+
tp = np.diag(hist)
|
| 100 |
+
sum_a1 = hist.sum(axis=1)
|
| 101 |
+
sum_a0 = hist.sum(axis=0)
|
| 102 |
+
# ---------------------------------------------------------------------- #
|
| 103 |
+
# 1. Accuracy & Class Accuracy
|
| 104 |
+
# ---------------------------------------------------------------------- #
|
| 105 |
+
acc = tp.sum() / (hist.sum() + np.finfo(np.float32).eps)
|
| 106 |
+
|
| 107 |
+
# recall
|
| 108 |
+
recall = tp / (sum_a1 + np.finfo(np.float32).eps)
|
| 109 |
+
# acc_cls = np.nanmean(recall)
|
| 110 |
+
|
| 111 |
+
# precision
|
| 112 |
+
precision = tp / (sum_a0 + np.finfo(np.float32).eps)
|
| 113 |
+
|
| 114 |
+
# F1 score
|
| 115 |
+
F1 = 2*recall * precision / (recall + precision + np.finfo(np.float32).eps)
|
| 116 |
+
mean_F1 = np.nanmean(F1)
|
| 117 |
+
# ---------------------------------------------------------------------- #
|
| 118 |
+
# 2. Frequency weighted Accuracy & Mean IoU
|
| 119 |
+
# ---------------------------------------------------------------------- #
|
| 120 |
+
iu = tp / (sum_a1 + hist.sum(axis=0) - tp + np.finfo(np.float32).eps)
|
| 121 |
+
mean_iu = np.nanmean(iu)
|
| 122 |
+
|
| 123 |
+
freq = sum_a1 / (hist.sum() + np.finfo(np.float32).eps)
|
| 124 |
+
fwavacc = (freq[freq > 0] * iu[freq > 0]).sum()
|
| 125 |
+
|
| 126 |
+
#
|
| 127 |
+
cls_iou = dict(zip(['iou_'+str(i) for i in range(n_class)], iu))
|
| 128 |
+
|
| 129 |
+
cls_precision = dict(zip(['precision_'+str(i) for i in range(n_class)], precision))
|
| 130 |
+
cls_recall = dict(zip(['recall_'+str(i) for i in range(n_class)], recall))
|
| 131 |
+
cls_F1 = dict(zip(['F1_'+str(i) for i in range(n_class)], F1))
|
| 132 |
+
|
| 133 |
+
score_dict = {'acc': acc, 'miou': mean_iu, 'mf1':mean_F1}
|
| 134 |
+
score_dict.update(cls_iou)
|
| 135 |
+
score_dict.update(cls_F1)
|
| 136 |
+
score_dict.update(cls_precision)
|
| 137 |
+
score_dict.update(cls_recall)
|
| 138 |
+
return score_dict
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def get_confuse_matrix(num_classes, label_gts, label_preds):
|
| 142 |
+
"""计算一组预测的混淆矩阵"""
|
| 143 |
+
def __fast_hist(label_gt, label_pred):
|
| 144 |
+
"""
|
| 145 |
+
Collect values for Confusion Matrix
|
| 146 |
+
For reference, please see: https://en.wikipedia.org/wiki/Confusion_matrix
|
| 147 |
+
:param label_gt: <np.array> ground-truth
|
| 148 |
+
:param label_pred: <np.array> prediction
|
| 149 |
+
:return: <np.ndarray> values for confusion matrix
|
| 150 |
+
"""
|
| 151 |
+
mask = (label_gt >= 0) & (label_gt < num_classes)
|
| 152 |
+
hist = np.bincount(num_classes * label_gt[mask].astype(int) + label_pred[mask],
|
| 153 |
+
minlength=num_classes**2).reshape(num_classes, num_classes)
|
| 154 |
+
return hist
|
| 155 |
+
confusion_matrix = np.zeros((num_classes, num_classes))
|
| 156 |
+
for lt, lp in zip(label_gts, label_preds):
|
| 157 |
+
confusion_matrix += __fast_hist(lt.flatten(), lp.flatten())
|
| 158 |
+
return confusion_matrix
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def get_mIoU(num_classes, label_gts, label_preds):
|
| 162 |
+
confusion_matrix = get_confuse_matrix(num_classes, label_gts, label_preds)
|
| 163 |
+
score_dict = cm2score(confusion_matrix)
|
| 164 |
+
return score_dict['miou']
|
misc/pyutils.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
+
import glob
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def seed_random(seed=2020):
|
| 8 |
+
# 加入以下随机种子,数据输入,随机扩充等保持一致
|
| 9 |
+
random.seed(seed)
|
| 10 |
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
| 11 |
+
np.random.seed(seed)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def mkdir(path):
|
| 15 |
+
"""create a single empty directory if it didn't exist
|
| 16 |
+
|
| 17 |
+
Parameters:
|
| 18 |
+
path (str) -- a single directory path
|
| 19 |
+
"""
|
| 20 |
+
if not os.path.exists(path):
|
| 21 |
+
os.makedirs(path)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def get_paths(image_folder_path, suffix='*.png'):
|
| 25 |
+
"""从文件夹中返回指定格式的文件
|
| 26 |
+
:param image_folder_path: str
|
| 27 |
+
:param suffix: str
|
| 28 |
+
:return: list
|
| 29 |
+
"""
|
| 30 |
+
paths = sorted(glob.glob(os.path.join(image_folder_path, suffix)))
|
| 31 |
+
return paths
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def get_paths_from_list(image_folder_path, list):
|
| 35 |
+
"""从image folder中找到list中的文件,返回path list"""
|
| 36 |
+
out = []
|
| 37 |
+
for item in list:
|
| 38 |
+
path = os.path.join(image_folder_path,item)
|
| 39 |
+
out.append(path)
|
| 40 |
+
return sorted(out)
|
| 41 |
+
|
| 42 |
+
|
misc/torchutils.py
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from torch.optim import lr_scheduler
|
| 3 |
+
from torch.utils.data import Subset
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
import numpy as np
|
| 6 |
+
import math
|
| 7 |
+
import random
|
| 8 |
+
import os
|
| 9 |
+
from torch.nn import MaxPool1d,AvgPool1d
|
| 10 |
+
from torch import Tensor
|
| 11 |
+
from typing import Iterable, Set, Tuple
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
__all__ = ['cls_accuracy']
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def visualize_imgs(*imgs):
|
| 19 |
+
"""
|
| 20 |
+
可视化图像,ndarray格式的图像
|
| 21 |
+
:param imgs: ndarray:H*W*C, C=1/3
|
| 22 |
+
:return:
|
| 23 |
+
"""
|
| 24 |
+
import matplotlib.pyplot as plt
|
| 25 |
+
nums = len(imgs)
|
| 26 |
+
if nums > 1:
|
| 27 |
+
fig, axs = plt.subplots(1, nums)
|
| 28 |
+
for i, image in enumerate(imgs):
|
| 29 |
+
axs[i].imshow(image, cmap='jet')
|
| 30 |
+
elif nums == 1:
|
| 31 |
+
fig, ax = plt.subplots(1, nums)
|
| 32 |
+
for i, image in enumerate(imgs):
|
| 33 |
+
ax.imshow(image, cmap='jet')
|
| 34 |
+
plt.show()
|
| 35 |
+
plt.show()
|
| 36 |
+
|
| 37 |
+
def minmax(tensor):
|
| 38 |
+
assert tensor.ndim >= 2
|
| 39 |
+
shape = tensor.shape
|
| 40 |
+
tensor = tensor.view([*shape[:-2], shape[-1]*shape[-2]])
|
| 41 |
+
min_, _ = tensor.min(-1, keepdim=True)
|
| 42 |
+
max_, _ = tensor.max(-1, keepdim=True)
|
| 43 |
+
return min_, max_
|
| 44 |
+
|
| 45 |
+
def norm_tensor(tensor,min_=None,max_=None, mode='minmax'):
|
| 46 |
+
"""
|
| 47 |
+
输入:N*C*H*W / C*H*W / H*W
|
| 48 |
+
输出:在H*W维度的归一化的与原始等大的图
|
| 49 |
+
"""
|
| 50 |
+
assert tensor.ndim >= 2
|
| 51 |
+
shape = tensor.shape
|
| 52 |
+
tensor = tensor.view([*shape[:-2], shape[-1]*shape[-2]])
|
| 53 |
+
if mode == 'minmax':
|
| 54 |
+
if min_ is None:
|
| 55 |
+
min_, _ = tensor.min(-1, keepdim=True)
|
| 56 |
+
if max_ is None:
|
| 57 |
+
max_, _ = tensor.max(-1, keepdim=True)
|
| 58 |
+
tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 59 |
+
elif mode == 'thres':
|
| 60 |
+
N = tensor.shape[-1]
|
| 61 |
+
thres_a = 0.001
|
| 62 |
+
top_k = round(thres_a*N)
|
| 63 |
+
max_ = tensor.topk(top_k, dim=-1, largest=True)[0][..., -1]
|
| 64 |
+
max_ = max_.unsqueeze(-1)
|
| 65 |
+
min_ = tensor.topk(top_k, dim=-1, largest=False)[0][..., -1]
|
| 66 |
+
min_ = min_.unsqueeze(-1)
|
| 67 |
+
tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 68 |
+
|
| 69 |
+
elif mode == 'std':
|
| 70 |
+
mean, std = torch.std_mean(tensor, [-1], keepdim=True)
|
| 71 |
+
tensor = (tensor - mean)/std
|
| 72 |
+
min_, _ = tensor.min(-1, keepdim=True)
|
| 73 |
+
max_, _ = tensor.max(-1, keepdim=True)
|
| 74 |
+
tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 75 |
+
elif mode == 'exp':
|
| 76 |
+
tai = 1
|
| 77 |
+
tensor = torch.nn.functional.softmax(tensor/tai, dim=-1, )
|
| 78 |
+
min_, _ = tensor.min(-1, keepdim=True)
|
| 79 |
+
max_, _ = tensor.max(-1, keepdim=True)
|
| 80 |
+
tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 81 |
+
else:
|
| 82 |
+
raise NotImplementedError
|
| 83 |
+
tensor = torch.clamp(tensor, 0, 1)
|
| 84 |
+
return tensor.view(shape)
|
| 85 |
+
|
| 86 |
+
# if tensor.ndim == 4:
|
| 87 |
+
# B, C, H, W = tensor.shape
|
| 88 |
+
# tensor = tensor.view([B, C, -1])
|
| 89 |
+
# min_, _ = tensor.min(-1, keepdim=True)
|
| 90 |
+
# max_, _ = tensor.max(-1, keepdim=True)
|
| 91 |
+
# tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 92 |
+
# return tensor.view(B, C, H, W)
|
| 93 |
+
# elif tensor.ndim == 3:
|
| 94 |
+
# C, H, W = tensor.shape
|
| 95 |
+
# tensor = tensor.view([C, -1])
|
| 96 |
+
# min_, _ = tensor.min(-1, keepdim=True)
|
| 97 |
+
# max_, _ = tensor.max(-1, keepdim=True)
|
| 98 |
+
# tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 99 |
+
# return tensor.view(C, H, W)
|
| 100 |
+
# elif tensor.ndim == 2:
|
| 101 |
+
# H, W = tensor.shape
|
| 102 |
+
# tensor = tensor.view([-1])
|
| 103 |
+
# min_, _ = tensor.min(-1, keepdim=True)
|
| 104 |
+
# max_, _ = tensor.max(-1, keepdim=True)
|
| 105 |
+
# tensor = (tensor - min_) / (max_ - min_ + 0.00000000001)
|
| 106 |
+
# return tensor.view(H, W)
|
| 107 |
+
# else:
|
| 108 |
+
# raise NotImplementedError
|
| 109 |
+
|
| 110 |
+
def visulize_features(features, normalize=False):
|
| 111 |
+
"""
|
| 112 |
+
可视化特征图,各维度make grid到一起
|
| 113 |
+
"""
|
| 114 |
+
from torchvision.utils import make_grid
|
| 115 |
+
assert features.ndim == 4
|
| 116 |
+
b,c,h,w = features.shape
|
| 117 |
+
features = features.view((b*c, 1, h, w))
|
| 118 |
+
if normalize:
|
| 119 |
+
features = norm_tensor(features)
|
| 120 |
+
grid = make_grid(features)
|
| 121 |
+
visualize_tensors(grid)
|
| 122 |
+
|
| 123 |
+
def visualize_tensors(*tensors):
|
| 124 |
+
"""
|
| 125 |
+
可视化tensor,支持单通道特征或3通道图像
|
| 126 |
+
:param tensors: tensor: C*H*W, C=1/3
|
| 127 |
+
:return:
|
| 128 |
+
"""
|
| 129 |
+
import matplotlib.pyplot as plt
|
| 130 |
+
# from misc.torchutils import tensor2np
|
| 131 |
+
images = []
|
| 132 |
+
for tensor in tensors:
|
| 133 |
+
assert tensor.ndim == 3 or tensor.ndim==2
|
| 134 |
+
if tensor.ndim ==3:
|
| 135 |
+
assert tensor.shape[0] == 1 or tensor.shape[0] == 3
|
| 136 |
+
images.append(tensor2np(tensor))
|
| 137 |
+
nums = len(images)
|
| 138 |
+
if nums>1:
|
| 139 |
+
fig, axs = plt.subplots(1, nums)
|
| 140 |
+
for i, image in enumerate(images):
|
| 141 |
+
axs[i].imshow(image, cmap='jet')
|
| 142 |
+
plt.show()
|
| 143 |
+
elif nums == 1:
|
| 144 |
+
fig, ax = plt.subplots(1, nums)
|
| 145 |
+
for i, image in enumerate(images):
|
| 146 |
+
ax.imshow(image, cmap='jet')
|
| 147 |
+
plt.show()
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def np_to_tensor(image):
|
| 151 |
+
"""
|
| 152 |
+
input: nd.array: H*W*C/H*W
|
| 153 |
+
"""
|
| 154 |
+
if isinstance(image, torch.Tensor):
|
| 155 |
+
return image
|
| 156 |
+
elif isinstance(image, np.ndarray):
|
| 157 |
+
if image.ndim == 3:
|
| 158 |
+
if image.shape[2]==3:
|
| 159 |
+
image = np.transpose(image,[2,0,1])
|
| 160 |
+
elif image.ndim == 2:
|
| 161 |
+
image = np.newaxis(image, 0)
|
| 162 |
+
image = torch.from_numpy(image)
|
| 163 |
+
return image.unsqueeze(0)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def seed_torch(seed=2019):
|
| 167 |
+
|
| 168 |
+
# 加入以下随机种子,数据输入,随机扩充等保持一致
|
| 169 |
+
random.seed(seed)
|
| 170 |
+
os.environ['PYTHONHASHSEED'] = str(seed)
|
| 171 |
+
np.random.seed(seed)
|
| 172 |
+
torch.manual_seed(seed)
|
| 173 |
+
torch.cuda.manual_seed(seed)
|
| 174 |
+
# 加入所有随机种子后,模型更新后,中间结果还是不一样,
|
| 175 |
+
# 发现这一的现象:前两轮,的结果还是一样;随着模型更新结果会变;
|
| 176 |
+
# torch.backends.cudnn.benchmark = False
|
| 177 |
+
# torch.backends.cudnn.deterministic = True
|
| 178 |
+
|
| 179 |
+
def simplex(t: Tensor, axis=1) -> bool:
|
| 180 |
+
_sum = t.sum(axis).type(torch.float32)
|
| 181 |
+
_ones = torch.ones_like(_sum, dtype=torch.float32)
|
| 182 |
+
return torch.allclose(_sum, _ones)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# Assert utils
|
| 186 |
+
def uniq(a: Tensor) -> Set:
|
| 187 |
+
return set(torch.unique(a.cpu()).numpy())
|
| 188 |
+
|
| 189 |
+
def sset(a: Tensor, sub: Iterable) -> bool:
|
| 190 |
+
return uniq(a).issubset(sub)
|
| 191 |
+
|
| 192 |
+
def eq(a: Tensor, b) -> bool:
|
| 193 |
+
return torch.eq(a, b).all()
|
| 194 |
+
|
| 195 |
+
def one_hot(t: Tensor, axis=1) -> bool:
|
| 196 |
+
return simplex(t, axis) and sset(t, [0, 1])
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def class2one_hot(seg: Tensor, C: int) -> Tensor:
|
| 200 |
+
if len(seg.shape) == 2: # Only w, h, used by the dataloader
|
| 201 |
+
seg = seg.unsqueeze(dim=0)
|
| 202 |
+
assert sset(seg, list(range(C)))
|
| 203 |
+
|
| 204 |
+
b, w, h = seg.shape # type: Tuple[int, int, int]
|
| 205 |
+
|
| 206 |
+
res = torch.stack([seg == c for c in range(C)], dim=1).type(torch.int32)
|
| 207 |
+
assert res.shape == (b, C, w, h)
|
| 208 |
+
assert one_hot(res)
|
| 209 |
+
|
| 210 |
+
return res
|
| 211 |
+
|
| 212 |
+
class ChannelMaxPool(MaxPool1d):
|
| 213 |
+
def forward(self, input):
|
| 214 |
+
n, c, w, h = input.size()
|
| 215 |
+
input = input.view(n,c,w*h).permute(0,2,1)
|
| 216 |
+
pooled = F.max_pool1d(input, self.kernel_size, self.stride,
|
| 217 |
+
self.padding, self.dilation, self.ceil_mode,
|
| 218 |
+
self.return_indices)
|
| 219 |
+
_, _, c = pooled.size()
|
| 220 |
+
pooled = pooled.permute(0,2,1)
|
| 221 |
+
return pooled.view(n,c,w,h)
|
| 222 |
+
|
| 223 |
+
class ChannelAvePool(AvgPool1d):
|
| 224 |
+
def forward(self, input):
|
| 225 |
+
n, c, w, h = input.size()
|
| 226 |
+
input = input.view(n,c,w*h).permute(0,2,1)
|
| 227 |
+
pooled = F.avg_pool1d(input, self.kernel_size, self.stride,
|
| 228 |
+
self.padding)
|
| 229 |
+
_, _, c = pooled.size()
|
| 230 |
+
pooled = pooled.permute(0,2,1)
|
| 231 |
+
return pooled.view(n,c,w,h)
|
| 232 |
+
|
| 233 |
+
def cross_entropy(input, target, weight=None, reduction='mean',ignore_index=255):
|
| 234 |
+
"""
|
| 235 |
+
logSoftmax_with_loss
|
| 236 |
+
:param input: torch.Tensor, N*C*H*W
|
| 237 |
+
:param target: torch.Tensor, N*1*H*W,/ N*H*W
|
| 238 |
+
:param weight: torch.Tensor, C
|
| 239 |
+
:return: torch.Tensor [0]
|
| 240 |
+
"""
|
| 241 |
+
target = target.long()
|
| 242 |
+
if target.dim() == 4:
|
| 243 |
+
target = torch.squeeze(target, dim=1)
|
| 244 |
+
if input.shape[-1] != target.shape[-1]:
|
| 245 |
+
input = F.interpolate(input, size=target.shape[1:], mode='bilinear',align_corners=True)
|
| 246 |
+
|
| 247 |
+
return F.cross_entropy(input=input, target=target, weight=weight,
|
| 248 |
+
ignore_index=ignore_index, reduction=reduction)
|
| 249 |
+
|
| 250 |
+
def balanced_cross_entropy(input, target, weight=None,ignore_index=255):
|
| 251 |
+
"""
|
| 252 |
+
类别均衡的交叉熵损失,暂时只支持2类
|
| 253 |
+
TODO: 扩展到多类C>2
|
| 254 |
+
"""
|
| 255 |
+
if target.dim() == 4:
|
| 256 |
+
target = torch.squeeze(target, dim=1)
|
| 257 |
+
if input.shape[-1] != target.shape[-1]:
|
| 258 |
+
input = F.interpolate(input, size=target.shape[1:], mode='bilinear',align_corners=True)
|
| 259 |
+
|
| 260 |
+
# print('target.sum',target.sum())
|
| 261 |
+
pos = (target==1).float()
|
| 262 |
+
neg = (target==0).float()
|
| 263 |
+
pos_num = torch.sum(pos) + 0.0000001
|
| 264 |
+
neg_num = torch.sum(neg) + 0.0000001
|
| 265 |
+
# print(pos_num)
|
| 266 |
+
# print(neg_num)
|
| 267 |
+
target_pos = target.float()
|
| 268 |
+
target_pos[target_pos!=1] = ignore_index # 忽略不为正样本的区域
|
| 269 |
+
target_neg = target.float()
|
| 270 |
+
target_neg[target_neg!=0] = ignore_index # 忽略不为负样本的区域
|
| 271 |
+
|
| 272 |
+
# print('target.sum',target.sum())
|
| 273 |
+
|
| 274 |
+
loss_pos = cross_entropy(input, target_pos,weight=weight,reduction='sum',ignore_index=ignore_index)
|
| 275 |
+
loss_neg = cross_entropy(input, target_neg,weight=weight,reduction='sum',ignore_index=ignore_index)
|
| 276 |
+
# print(loss_neg, loss_pos)
|
| 277 |
+
loss = 0.5 * loss_pos / pos_num + 0.5 * loss_neg / neg_num
|
| 278 |
+
# loss = (loss_pos + loss_neg)/ (pos_num+neg_num)
|
| 279 |
+
return loss
|
| 280 |
+
|
| 281 |
+
def get_scheduler(optimizer, opt):
|
| 282 |
+
"""Return a learning rate scheduler
|
| 283 |
+
"""
|
| 284 |
+
if opt.lr_policy == 'linear':
|
| 285 |
+
def lambda_rule(epoch):
|
| 286 |
+
lr_l = 1.0 - max(0, epoch + opt.epoch_count - opt.niter) / float(opt.niter_decay + 1)
|
| 287 |
+
return lr_l
|
| 288 |
+
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule)
|
| 289 |
+
elif opt.lr_policy == 'poly':
|
| 290 |
+
max_step = opt.niter+opt.niter_decay
|
| 291 |
+
power = 0.9
|
| 292 |
+
def lambda_rule(epoch):
|
| 293 |
+
current_step = epoch + opt.epoch_count
|
| 294 |
+
lr_l = (1.0 - current_step / (max_step+1)) ** float(power)
|
| 295 |
+
return lr_l
|
| 296 |
+
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule)
|
| 297 |
+
elif opt.lr_policy == 'step':
|
| 298 |
+
scheduler = lr_scheduler.StepLR(optimizer, step_size=opt.lr_decay_iters, gamma=0.1)
|
| 299 |
+
else:
|
| 300 |
+
return NotImplementedError('learning rate policy [%s] is not implemented', opt.lr_policy)
|
| 301 |
+
return scheduler
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def mul_cls_acc(preds, targets, topk=(1,)):
|
| 305 |
+
"""计算multi-label分类的top-k准确率topk-acc,topk-error=1-topk-acc;
|
| 306 |
+
首先计算每张图的的平均准确率,再计算所有图的平均准确率
|
| 307 |
+
:param pred: N * C
|
| 308 |
+
:param target: N * C
|
| 309 |
+
:param topk:
|
| 310 |
+
:return:
|
| 311 |
+
"""
|
| 312 |
+
with torch.no_grad():
|
| 313 |
+
maxk = max(topk)
|
| 314 |
+
bs, C = targets.shape
|
| 315 |
+
_, pred = preds.topk(maxk, 1, True, True)
|
| 316 |
+
pred += 1 # pred 为类别\in [1,C]
|
| 317 |
+
# print('pred: ', pred)
|
| 318 |
+
# print('targets: ', targets)
|
| 319 |
+
correct = torch.zeros([bs, maxk]).long() # 记录预测正确label数量
|
| 320 |
+
if preds.device != torch.device(type='cpu'):
|
| 321 |
+
correct = correct.cuda()
|
| 322 |
+
for i in range(C):
|
| 323 |
+
label = i + 1
|
| 324 |
+
target = targets[:, i] * label
|
| 325 |
+
# print('target.view: ', target.view(-1, 1).expand_as(pred))
|
| 326 |
+
# print('pred: ', pred)
|
| 327 |
+
correct = correct + pred.eq(target.view(-1, 1).expand_as(pred)).long()
|
| 328 |
+
# print('correct: ', pred.eq(target.view(-1, 1).expand_as(pred)).long())
|
| 329 |
+
n = (targets == 1).long().sum(1) # N*1, 每张图中含有目标的数量
|
| 330 |
+
# print(n)
|
| 331 |
+
res = []
|
| 332 |
+
for k in topk:
|
| 333 |
+
acc_k = correct[:, :k].sum(1).float() / n.float() # 每张图的平均正确率,预测正确目标数/总目标数
|
| 334 |
+
# print(correct[:, :k].sum(1).float())
|
| 335 |
+
acc_k = acc_k.sum()/bs
|
| 336 |
+
res.append(acc_k)
|
| 337 |
+
# print(acc_k)
|
| 338 |
+
return res
|
| 339 |
+
|
| 340 |
+
|
| 341 |
+
def cls_accuracy(output, target, topk=(1,)):
|
| 342 |
+
"""
|
| 343 |
+
Computes the accuracy over the k top predictions for the specified values of k
|
| 344 |
+
https://github.com/pytorch/examples/blob/ee964a2eeb41e1712fe719b83645c79bcbd0ba1a/imagenet/main.py#L407
|
| 345 |
+
"""
|
| 346 |
+
|
| 347 |
+
with torch.no_grad():
|
| 348 |
+
maxk = max(topk)
|
| 349 |
+
batch_size = target.size(0)
|
| 350 |
+
|
| 351 |
+
_, pred = output.topk(maxk, 1, True, True)
|
| 352 |
+
pred = pred.t()
|
| 353 |
+
correct = pred.eq(target.view(1, -1).expand_as(pred))
|
| 354 |
+
|
| 355 |
+
res = []
|
| 356 |
+
for k in topk:
|
| 357 |
+
correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)
|
| 358 |
+
res.append(correct_k.mul_(100.0 / batch_size))
|
| 359 |
+
return res
|
| 360 |
+
|
| 361 |
+
class PolyOptimizer(torch.optim.SGD):
|
| 362 |
+
|
| 363 |
+
def __init__(self, params, lr, weight_decay, max_step, init_step=0, momentum=0.9):
|
| 364 |
+
super().__init__(params, lr, weight_decay)
|
| 365 |
+
|
| 366 |
+
self.global_step = init_step
|
| 367 |
+
print(self.global_step)
|
| 368 |
+
self.max_step = max_step
|
| 369 |
+
self.momentum = momentum
|
| 370 |
+
|
| 371 |
+
self.__initial_lr = [group['lr'] for group in self.param_groups]
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def step(self, closure=None):
|
| 375 |
+
|
| 376 |
+
if self.global_step < self.max_step:
|
| 377 |
+
lr_mult = (1 - self.global_step / self.max_step) ** self.momentum
|
| 378 |
+
|
| 379 |
+
for i in range(len(self.param_groups)):
|
| 380 |
+
self.param_groups[i]['lr'] = self.__initial_lr[i] * lr_mult
|
| 381 |
+
|
| 382 |
+
super().step(closure)
|
| 383 |
+
|
| 384 |
+
self.global_step += 1
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
class PolyAdamOptimizer(torch.optim.Adam):
|
| 388 |
+
def __init__(self, params, lr, betas, max_step, momentum=0.9):
|
| 389 |
+
super().__init__(params, lr, betas)
|
| 390 |
+
|
| 391 |
+
self.global_step = 0
|
| 392 |
+
self.max_step = max_step
|
| 393 |
+
self.momentum = momentum
|
| 394 |
+
|
| 395 |
+
self.__initial_lr = [group['lr'] for group in self.param_groups]
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
def step(self, closure=None):
|
| 399 |
+
|
| 400 |
+
if self.global_step < self.max_step:
|
| 401 |
+
lr_mult = (1 - self.global_step / self.max_step) ** self.momentum
|
| 402 |
+
|
| 403 |
+
for i in range(len(self.param_groups)):
|
| 404 |
+
self.param_groups[i]['lr'] = self.__initial_lr[i] * lr_mult
|
| 405 |
+
|
| 406 |
+
super().step(closure)
|
| 407 |
+
self.global_step += 1
|
| 408 |
+
#
|
| 409 |
+
# from ranger import RangerQH,Ranger
|
| 410 |
+
# # https://github.com/lessw2020/Ranger-Deep-Learning-Optimizer/blob/master/ranger/rangerqh.py
|
| 411 |
+
#
|
| 412 |
+
# class PolyRangerOptimizer(RangerQH):
|
| 413 |
+
#
|
| 414 |
+
# def __init__(self, params, lr, betas, max_step, momentum=0.9):
|
| 415 |
+
# super().__init__(params, lr, betas)
|
| 416 |
+
#
|
| 417 |
+
# self.global_step = 0
|
| 418 |
+
# self.max_step = max_step
|
| 419 |
+
# self.momentum = momentum
|
| 420 |
+
#
|
| 421 |
+
# self.__initial_lr = [group['lr'] for group in self.param_groups]
|
| 422 |
+
#
|
| 423 |
+
#
|
| 424 |
+
# def step(self, closure=None):
|
| 425 |
+
#
|
| 426 |
+
# if self.global_step < self.max_step:
|
| 427 |
+
# lr_mult = (1 - self.global_step / self.max_step) ** self.momentum
|
| 428 |
+
#
|
| 429 |
+
# for i in range(len(self.param_groups)):
|
| 430 |
+
# self.param_groups[i]['lr'] = self.__initial_lr[i] * lr_mult
|
| 431 |
+
#
|
| 432 |
+
# super().step(closure)
|
| 433 |
+
# self.global_step += 1
|
| 434 |
+
|
| 435 |
+
class SGDROptimizer(torch.optim.SGD):
|
| 436 |
+
|
| 437 |
+
def __init__(self, params, steps_per_epoch, lr=0, weight_decay=0, epoch_start=1, restart_mult=2):
|
| 438 |
+
super().__init__(params, lr, weight_decay)
|
| 439 |
+
|
| 440 |
+
self.global_step = 0
|
| 441 |
+
self.local_step = 0
|
| 442 |
+
self.total_restart = 0
|
| 443 |
+
|
| 444 |
+
self.max_step = steps_per_epoch * epoch_start
|
| 445 |
+
self.restart_mult = restart_mult
|
| 446 |
+
|
| 447 |
+
self.__initial_lr = [group['lr'] for group in self.param_groups]
|
| 448 |
+
|
| 449 |
+
|
| 450 |
+
def step(self, closure=None):
|
| 451 |
+
|
| 452 |
+
if self.local_step >= self.max_step:
|
| 453 |
+
self.local_step = 0
|
| 454 |
+
self.max_step *= self.restart_mult
|
| 455 |
+
self.total_restart += 1
|
| 456 |
+
|
| 457 |
+
lr_mult = (1 + math.cos(math.pi * self.local_step / self.max_step))/2 / (self.total_restart + 1)
|
| 458 |
+
|
| 459 |
+
for i in range(len(self.param_groups)):
|
| 460 |
+
self.param_groups[i]['lr'] = self.__initial_lr[i] * lr_mult
|
| 461 |
+
|
| 462 |
+
super().step(closure)
|
| 463 |
+
|
| 464 |
+
self.local_step += 1
|
| 465 |
+
self.global_step += 1
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
def split_dataset(dataset, n_splits):
|
| 469 |
+
|
| 470 |
+
return [Subset(dataset, np.arange(i, len(dataset), n_splits)) for i in range(n_splits)]
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def gap2d(x, keepdims=False):
|
| 474 |
+
out = torch.mean(x.view(x.size(0), x.size(1), -1), -1)
|
| 475 |
+
if keepdims:
|
| 476 |
+
out = out.view(out.size(0), out.size(1), 1, 1)
|
| 477 |
+
|
| 478 |
+
return out
|
| 479 |
+
|
| 480 |
+
|
| 481 |
+
def decode_seg(label_mask, toTensor=False):
|
| 482 |
+
"""
|
| 483 |
+
:param label_mask: mask (np.ndarray): (M, N)/ tensor: N*C*H*W
|
| 484 |
+
:return: color label: (M, N, 3),
|
| 485 |
+
"""
|
| 486 |
+
if not isinstance(label_mask, np.ndarray):
|
| 487 |
+
if isinstance(label_mask, torch.Tensor): # get the data from a variable
|
| 488 |
+
image_tensor = label_mask.data
|
| 489 |
+
else:
|
| 490 |
+
return label_mask
|
| 491 |
+
label_mask = image_tensor[0][0].cpu().numpy()
|
| 492 |
+
|
| 493 |
+
rgb = np.zeros((label_mask.shape[0], label_mask.shape[1], 3),dtype=np.float)
|
| 494 |
+
r = label_mask % 6
|
| 495 |
+
g = (label_mask % 36) // 6
|
| 496 |
+
b = label_mask // 36
|
| 497 |
+
# 归一化到[0-1]
|
| 498 |
+
rgb[:, :, 0] = r / 6
|
| 499 |
+
rgb[:, :, 1] = g / 6
|
| 500 |
+
rgb[:, :, 2] = b / 6
|
| 501 |
+
if toTensor:
|
| 502 |
+
rgb = torch.from_numpy(rgb.transpose([2,0,1])).unsqueeze(0)
|
| 503 |
+
|
| 504 |
+
return rgb
|
| 505 |
+
|
| 506 |
+
|
| 507 |
+
def tensor2im(input_image, imtype=np.uint8, normalize=True):
|
| 508 |
+
""""Converts a Tensor array into a numpy image array.
|
| 509 |
+
Parameters:
|
| 510 |
+
input_image (tensor) -- the input image tensor array
|
| 511 |
+
imtype (type) -- the desired type of the converted numpy array
|
| 512 |
+
"""
|
| 513 |
+
if not isinstance(input_image, np.ndarray):
|
| 514 |
+
if isinstance(input_image, torch.Tensor): # get the data from a variable
|
| 515 |
+
image_tensor = input_image.data
|
| 516 |
+
else:
|
| 517 |
+
return input_image
|
| 518 |
+
image_numpy = image_tensor[0].cpu().float().numpy() # convert it into a numpy array
|
| 519 |
+
# if image_numpy.shape[0] == 1: # grayscale to RGB
|
| 520 |
+
# image_numpy = np.tile(image_numpy, (3, 1, 1))
|
| 521 |
+
if image_numpy.shape[0] == 3: # if RGB
|
| 522 |
+
image_numpy = np.transpose(image_numpy, (1, 2, 0))
|
| 523 |
+
if normalize:
|
| 524 |
+
image_numpy = (image_numpy + 1) / 2.0 * 255.0 # post-processing: tranpose and scaling
|
| 525 |
+
else: # if it is a numpy array, do nothing
|
| 526 |
+
image_numpy = input_image
|
| 527 |
+
return image_numpy.astype(imtype)
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
def tensor2np(input_image, if_normalize=True):
|
| 531 |
+
"""
|
| 532 |
+
:param input_image: C*H*W / H*W
|
| 533 |
+
:return: ndarray, H*W*C / H*W
|
| 534 |
+
"""
|
| 535 |
+
if isinstance(input_image, torch.Tensor): # get the data from a variable
|
| 536 |
+
image_tensor = input_image.data
|
| 537 |
+
image_numpy = image_tensor.cpu().float().numpy() # convert it into a numpy array
|
| 538 |
+
|
| 539 |
+
else:
|
| 540 |
+
image_numpy = input_image
|
| 541 |
+
if image_numpy.ndim == 2:
|
| 542 |
+
return image_numpy
|
| 543 |
+
elif image_numpy.ndim == 3:
|
| 544 |
+
C, H, W = image_numpy.shape
|
| 545 |
+
image_numpy = np.transpose(image_numpy, (1, 2, 0))
|
| 546 |
+
# 如果输入为灰度图C==1,则输出array,ndim==2;
|
| 547 |
+
if C == 1:
|
| 548 |
+
image_numpy = image_numpy[:, :, 0]
|
| 549 |
+
if if_normalize and C == 3:
|
| 550 |
+
image_numpy = (image_numpy + 1) / 2.0 * 255.0 # post-processing: tranpose and scaling
|
| 551 |
+
# add to prevent extreme noises in visual images
|
| 552 |
+
image_numpy[image_numpy<0]=0
|
| 553 |
+
image_numpy[image_numpy>255]=255
|
| 554 |
+
image_numpy = image_numpy.astype(np.uint8)
|
| 555 |
+
return image_numpy
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
import ntpath
|
| 559 |
+
from misc.imutils import save_image
|
| 560 |
+
def save_visuals(visuals, img_dir, name, save_one=True, iter='0'):
|
| 561 |
+
"""
|
| 562 |
+
"""
|
| 563 |
+
# save images to the disk
|
| 564 |
+
for label, image in visuals.items():
|
| 565 |
+
N = image.shape[0]
|
| 566 |
+
if save_one:
|
| 567 |
+
N = 1
|
| 568 |
+
# 保存各个bz的数据
|
| 569 |
+
for j in range(N):
|
| 570 |
+
name_ = ntpath.basename(name[j])
|
| 571 |
+
name_ = name_.split(".")[0]
|
| 572 |
+
# print(name_)
|
| 573 |
+
image_numpy = tensor2np(image[j], if_normalize=True).astype(np.uint8)
|
| 574 |
+
# print(image_numpy)
|
| 575 |
+
img_path = os.path.join(img_dir, iter+'_%s_%s.png' % (name_, label))
|
| 576 |
+
save_image(image_numpy, img_path)
|
models/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .resnet import *
|
models/basic_model.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
from misc.imutils import save_image
|
| 6 |
+
from models.networks import *
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class CDEvaluator():
|
| 10 |
+
|
| 11 |
+
def __init__(self, args):
|
| 12 |
+
|
| 13 |
+
self.n_class = args.n_class
|
| 14 |
+
# define G
|
| 15 |
+
self.net_G = define_G(args=args, gpu_ids=args.gpu_ids)
|
| 16 |
+
|
| 17 |
+
self.device = torch.device("cuda:%s" % args.gpu_ids[0]
|
| 18 |
+
if torch.cuda.is_available() and len(args.gpu_ids)>0
|
| 19 |
+
else "cpu")
|
| 20 |
+
|
| 21 |
+
print(self.device)
|
| 22 |
+
|
| 23 |
+
self.checkpoint_dir = args.checkpoint_dir
|
| 24 |
+
|
| 25 |
+
self.pred_dir = args.output_folder
|
| 26 |
+
os.makedirs(self.pred_dir, exist_ok=True)
|
| 27 |
+
|
| 28 |
+
def load_checkpoint(self, checkpoint_name='best_ckpt.pt'):
|
| 29 |
+
|
| 30 |
+
if os.path.exists(os.path.join(self.checkpoint_dir, checkpoint_name)):
|
| 31 |
+
# load the entire checkpoint
|
| 32 |
+
checkpoint = torch.load(os.path.join(self.checkpoint_dir, checkpoint_name),
|
| 33 |
+
map_location=self.device)
|
| 34 |
+
|
| 35 |
+
self.net_G.load_state_dict(checkpoint['model_G_state_dict'])
|
| 36 |
+
self.net_G.to(self.device)
|
| 37 |
+
# update some other states
|
| 38 |
+
self.best_val_acc = checkpoint['best_val_acc']
|
| 39 |
+
self.best_epoch_id = checkpoint['best_epoch_id']
|
| 40 |
+
|
| 41 |
+
else:
|
| 42 |
+
raise FileNotFoundError('no such checkpoint %s' % checkpoint_name)
|
| 43 |
+
return self.net_G
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _visualize_pred(self):
|
| 47 |
+
pred = torch.argmax(self.G_pred, dim=1, keepdim=True)
|
| 48 |
+
pred_vis = pred * 255
|
| 49 |
+
return pred_vis
|
| 50 |
+
|
| 51 |
+
def _forward_pass(self, batch):
|
| 52 |
+
self.batch = batch
|
| 53 |
+
img_in1 = batch['A'].to(self.device)
|
| 54 |
+
img_in2 = batch['B'].to(self.device)
|
| 55 |
+
self.shape_h = img_in1.shape[-2]
|
| 56 |
+
self.shape_w = img_in1.shape[-1]
|
| 57 |
+
self.G_pred = self.net_G(img_in1, img_in2)
|
| 58 |
+
return self._visualize_pred()
|
| 59 |
+
|
| 60 |
+
def eval(self):
|
| 61 |
+
self.net_G.eval()
|
| 62 |
+
|
| 63 |
+
def _save_predictions(self):
|
| 64 |
+
"""
|
| 65 |
+
保存模型输出结果,二分类图像
|
| 66 |
+
"""
|
| 67 |
+
|
| 68 |
+
preds = self._visualize_pred()
|
| 69 |
+
name = self.batch['name']
|
| 70 |
+
for i, pred in enumerate(preds):
|
| 71 |
+
file_name = os.path.join(
|
| 72 |
+
self.pred_dir, name[i].replace('.jpg', '.png'))
|
| 73 |
+
pred = pred[0].cpu().numpy()
|
| 74 |
+
save_image(pred, file_name)
|
| 75 |
+
|
models/evaluator.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
|
| 5 |
+
from models.networks import *
|
| 6 |
+
from misc.metric_tool import ConfuseMatrixMeter
|
| 7 |
+
from misc.logger_tool import Logger
|
| 8 |
+
from utils import de_norm
|
| 9 |
+
import utils
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
# Decide which device we want to run on
|
| 13 |
+
# torch.cuda.current_device()
|
| 14 |
+
|
| 15 |
+
# device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class CDEvaluator():
|
| 19 |
+
|
| 20 |
+
def __init__(self, args, dataloader):
|
| 21 |
+
|
| 22 |
+
self.dataloader = dataloader
|
| 23 |
+
|
| 24 |
+
self.n_class = args.n_class
|
| 25 |
+
# define G
|
| 26 |
+
self.net_G = define_G(args=args, gpu_ids=args.gpu_ids)
|
| 27 |
+
self.device = torch.device("cuda:%s" % args.gpu_ids[0] if torch.cuda.is_available() and len(args.gpu_ids)>0
|
| 28 |
+
else "cpu")
|
| 29 |
+
print(self.device)
|
| 30 |
+
|
| 31 |
+
# define some other vars to record the training states
|
| 32 |
+
self.running_metric = ConfuseMatrixMeter(n_class=self.n_class)
|
| 33 |
+
|
| 34 |
+
# define logger file
|
| 35 |
+
logger_path = os.path.join(args.checkpoint_dir, 'log_test.txt')
|
| 36 |
+
self.logger = Logger(logger_path)
|
| 37 |
+
self.logger.write_dict_str(args.__dict__)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# training log
|
| 41 |
+
self.epoch_acc = 0
|
| 42 |
+
self.best_val_acc = 0.0
|
| 43 |
+
self.best_epoch_id = 0
|
| 44 |
+
|
| 45 |
+
self.steps_per_epoch = len(dataloader)
|
| 46 |
+
|
| 47 |
+
self.G_pred = None
|
| 48 |
+
self.pred_vis = None
|
| 49 |
+
self.batch = None
|
| 50 |
+
self.is_training = False
|
| 51 |
+
self.batch_id = 0
|
| 52 |
+
self.epoch_id = 0
|
| 53 |
+
self.checkpoint_dir = args.checkpoint_dir
|
| 54 |
+
self.vis_dir = args.vis_dir
|
| 55 |
+
|
| 56 |
+
# check and create model dir
|
| 57 |
+
if os.path.exists(self.checkpoint_dir) is False:
|
| 58 |
+
os.mkdir(self.checkpoint_dir)
|
| 59 |
+
if os.path.exists(self.vis_dir) is False:
|
| 60 |
+
os.mkdir(self.vis_dir)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def _load_checkpoint(self, checkpoint_name='best_ckpt.pt'):
|
| 64 |
+
|
| 65 |
+
if os.path.exists(os.path.join(self.checkpoint_dir, checkpoint_name)):
|
| 66 |
+
self.logger.write('loading last checkpoint...\n')
|
| 67 |
+
# load the entire checkpoint
|
| 68 |
+
checkpoint = torch.load(os.path.join(self.checkpoint_dir, checkpoint_name), map_location=self.device)
|
| 69 |
+
|
| 70 |
+
self.net_G.load_state_dict(checkpoint['model_G_state_dict'])
|
| 71 |
+
|
| 72 |
+
self.net_G.to(self.device)
|
| 73 |
+
|
| 74 |
+
# update some other states
|
| 75 |
+
self.best_val_acc = checkpoint['best_val_acc']
|
| 76 |
+
self.best_epoch_id = checkpoint['best_epoch_id']
|
| 77 |
+
|
| 78 |
+
self.logger.write('Eval Historical_best_acc = %.4f (at epoch %d)\n' %
|
| 79 |
+
(self.best_val_acc, self.best_epoch_id))
|
| 80 |
+
self.logger.write('\n')
|
| 81 |
+
|
| 82 |
+
else:
|
| 83 |
+
raise FileNotFoundError('no such checkpoint %s' % checkpoint_name)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _visualize_pred(self):
|
| 87 |
+
pred = torch.argmax(self.G_pred, dim=1, keepdim=True)
|
| 88 |
+
pred_vis = pred * 255
|
| 89 |
+
return pred_vis
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def _update_metric(self):
|
| 93 |
+
"""
|
| 94 |
+
update metric
|
| 95 |
+
"""
|
| 96 |
+
target = self.batch['L'].to(self.device).detach()
|
| 97 |
+
G_pred = self.G_pred.detach()
|
| 98 |
+
G_pred = torch.argmax(G_pred, dim=1)
|
| 99 |
+
|
| 100 |
+
current_score = self.running_metric.update_cm(pr=G_pred.cpu().numpy(), gt=target.cpu().numpy())
|
| 101 |
+
return current_score
|
| 102 |
+
|
| 103 |
+
def _collect_running_batch_states(self):
|
| 104 |
+
|
| 105 |
+
running_acc = self._update_metric()
|
| 106 |
+
|
| 107 |
+
m = len(self.dataloader)
|
| 108 |
+
|
| 109 |
+
if np.mod(self.batch_id, 100) == 1:
|
| 110 |
+
message = 'Is_training: %s. [%d,%d], running_mf1: %.5f\n' %\
|
| 111 |
+
(self.is_training, self.batch_id, m, running_acc)
|
| 112 |
+
self.logger.write(message)
|
| 113 |
+
|
| 114 |
+
if np.mod(self.batch_id, 100) == 1:
|
| 115 |
+
vis_input = utils.make_numpy_grid(de_norm(self.batch['A']))
|
| 116 |
+
vis_input2 = utils.make_numpy_grid(de_norm(self.batch['B']))
|
| 117 |
+
|
| 118 |
+
vis_pred = utils.make_numpy_grid(self._visualize_pred())
|
| 119 |
+
|
| 120 |
+
vis_gt = utils.make_numpy_grid(self.batch['L'])
|
| 121 |
+
vis = np.concatenate([vis_input, vis_input2, vis_pred, vis_gt], axis=0)
|
| 122 |
+
vis = np.clip(vis, a_min=0.0, a_max=1.0)
|
| 123 |
+
file_name = os.path.join(
|
| 124 |
+
self.vis_dir, 'eval_' + str(self.batch_id)+'.jpg')
|
| 125 |
+
plt.imsave(file_name, vis)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def _collect_epoch_states(self):
|
| 129 |
+
|
| 130 |
+
scores_dict = self.running_metric.get_scores()
|
| 131 |
+
|
| 132 |
+
np.save(os.path.join(self.checkpoint_dir, 'scores_dict.npy'), scores_dict)
|
| 133 |
+
|
| 134 |
+
self.epoch_acc = scores_dict['mf1']
|
| 135 |
+
|
| 136 |
+
with open(os.path.join(self.checkpoint_dir, '%s.txt' % (self.epoch_acc)),
|
| 137 |
+
mode='a') as file:
|
| 138 |
+
pass
|
| 139 |
+
|
| 140 |
+
message = ''
|
| 141 |
+
for k, v in scores_dict.items():
|
| 142 |
+
message += '%s: %.5f ' % (k, v)
|
| 143 |
+
self.logger.write('%s\n' % message) # save the message
|
| 144 |
+
|
| 145 |
+
self.logger.write('\n')
|
| 146 |
+
|
| 147 |
+
def _clear_cache(self):
|
| 148 |
+
self.running_metric.clear()
|
| 149 |
+
|
| 150 |
+
def _forward_pass(self, batch):
|
| 151 |
+
self.batch = batch
|
| 152 |
+
img_in1 = batch['A'].to(self.device)
|
| 153 |
+
img_in2 = batch['B'].to(self.device)
|
| 154 |
+
self.G_pred = self.net_G(img_in1, img_in2)
|
| 155 |
+
|
| 156 |
+
def eval_models(self,checkpoint_name='best_ckpt.pt'):
|
| 157 |
+
|
| 158 |
+
self._load_checkpoint(checkpoint_name)
|
| 159 |
+
|
| 160 |
+
################## Eval ##################
|
| 161 |
+
##########################################
|
| 162 |
+
self.logger.write('Begin evaluation...\n')
|
| 163 |
+
self._clear_cache()
|
| 164 |
+
self.is_training = False
|
| 165 |
+
self.net_G.eval()
|
| 166 |
+
|
| 167 |
+
# Iterate over data.
|
| 168 |
+
for self.batch_id, batch in enumerate(self.dataloader, 0):
|
| 169 |
+
with torch.no_grad():
|
| 170 |
+
self._forward_pass(batch)
|
| 171 |
+
self._collect_running_batch_states()
|
| 172 |
+
self._collect_epoch_states()
|
models/help_funcs.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
from einops import rearrange
|
| 4 |
+
from torch import nn
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class TwoLayerConv2d(nn.Sequential):
|
| 8 |
+
def __init__(self, in_channels, out_channels, kernel_size=3):
|
| 9 |
+
super().__init__(nn.Conv2d(in_channels, in_channels, kernel_size=kernel_size,
|
| 10 |
+
padding=kernel_size // 2, stride=1, bias=False),
|
| 11 |
+
nn.BatchNorm2d(in_channels),
|
| 12 |
+
nn.ReLU(),
|
| 13 |
+
nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size,
|
| 14 |
+
padding=kernel_size // 2, stride=1)
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class Residual(nn.Module):
|
| 19 |
+
def __init__(self, fn):
|
| 20 |
+
super().__init__()
|
| 21 |
+
self.fn = fn
|
| 22 |
+
def forward(self, x, **kwargs):
|
| 23 |
+
return self.fn(x, **kwargs) + x
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Residual2(nn.Module):
|
| 27 |
+
def __init__(self, fn):
|
| 28 |
+
super().__init__()
|
| 29 |
+
self.fn = fn
|
| 30 |
+
def forward(self, x, x2, **kwargs):
|
| 31 |
+
return self.fn(x, x2, **kwargs) + x
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class PreNorm(nn.Module):
|
| 35 |
+
def __init__(self, dim, fn):
|
| 36 |
+
super().__init__()
|
| 37 |
+
self.norm = nn.LayerNorm(dim)
|
| 38 |
+
self.fn = fn
|
| 39 |
+
def forward(self, x, **kwargs):
|
| 40 |
+
return self.fn(self.norm(x), **kwargs)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
class PreNorm2(nn.Module):
|
| 44 |
+
def __init__(self, dim, fn):
|
| 45 |
+
super().__init__()
|
| 46 |
+
self.norm = nn.LayerNorm(dim)
|
| 47 |
+
self.fn = fn
|
| 48 |
+
def forward(self, x, x2, **kwargs):
|
| 49 |
+
return self.fn(self.norm(x), self.norm(x2), **kwargs)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class FeedForward(nn.Module):
|
| 53 |
+
def __init__(self, dim, hidden_dim, dropout = 0.):
|
| 54 |
+
super().__init__()
|
| 55 |
+
self.net = nn.Sequential(
|
| 56 |
+
nn.Linear(dim, hidden_dim),
|
| 57 |
+
nn.GELU(),
|
| 58 |
+
nn.Dropout(dropout),
|
| 59 |
+
nn.Linear(hidden_dim, dim),
|
| 60 |
+
nn.Dropout(dropout)
|
| 61 |
+
)
|
| 62 |
+
def forward(self, x):
|
| 63 |
+
return self.net(x)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
class Cross_Attention(nn.Module):
|
| 67 |
+
def __init__(self, dim, heads = 8, dim_head = 64, dropout = 0., softmax=True):
|
| 68 |
+
super().__init__()
|
| 69 |
+
inner_dim = dim_head * heads
|
| 70 |
+
self.heads = heads
|
| 71 |
+
self.scale = dim ** -0.5
|
| 72 |
+
|
| 73 |
+
self.softmax = softmax
|
| 74 |
+
self.to_q = nn.Linear(dim, inner_dim, bias=False)
|
| 75 |
+
self.to_k = nn.Linear(dim, inner_dim, bias=False)
|
| 76 |
+
self.to_v = nn.Linear(dim, inner_dim, bias=False)
|
| 77 |
+
|
| 78 |
+
self.to_out = nn.Sequential(
|
| 79 |
+
nn.Linear(inner_dim, dim),
|
| 80 |
+
nn.Dropout(dropout)
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
def forward(self, x, m, mask = None):
|
| 84 |
+
|
| 85 |
+
b, n, _, h = *x.shape, self.heads
|
| 86 |
+
q = self.to_q(x)
|
| 87 |
+
k = self.to_k(m)
|
| 88 |
+
v = self.to_v(m)
|
| 89 |
+
|
| 90 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), [q,k,v])
|
| 91 |
+
|
| 92 |
+
dots = torch.einsum('bhid,bhjd->bhij', q, k) * self.scale
|
| 93 |
+
mask_value = -torch.finfo(dots.dtype).max
|
| 94 |
+
|
| 95 |
+
if mask is not None:
|
| 96 |
+
mask = F.pad(mask.flatten(1), (1, 0), value = True)
|
| 97 |
+
assert mask.shape[-1] == dots.shape[-1], 'mask has incorrect dimensions'
|
| 98 |
+
mask = mask[:, None, :] * mask[:, :, None]
|
| 99 |
+
dots.masked_fill_(~mask, mask_value)
|
| 100 |
+
del mask
|
| 101 |
+
|
| 102 |
+
if self.softmax:
|
| 103 |
+
attn = dots.softmax(dim=-1)
|
| 104 |
+
else:
|
| 105 |
+
attn = dots
|
| 106 |
+
# attn = dots
|
| 107 |
+
# vis_tmp(dots)
|
| 108 |
+
|
| 109 |
+
out = torch.einsum('bhij,bhjd->bhid', attn, v)
|
| 110 |
+
out = rearrange(out, 'b h n d -> b n (h d)')
|
| 111 |
+
out = self.to_out(out)
|
| 112 |
+
# vis_tmp2(out)
|
| 113 |
+
|
| 114 |
+
return out
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class Attention(nn.Module):
|
| 118 |
+
def __init__(self, dim, heads = 8, dim_head = 64, dropout = 0.):
|
| 119 |
+
super().__init__()
|
| 120 |
+
inner_dim = dim_head * heads
|
| 121 |
+
self.heads = heads
|
| 122 |
+
self.scale = dim ** -0.5
|
| 123 |
+
|
| 124 |
+
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
|
| 125 |
+
self.to_out = nn.Sequential(
|
| 126 |
+
nn.Linear(inner_dim, dim),
|
| 127 |
+
nn.Dropout(dropout)
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
def forward(self, x, mask = None):
|
| 131 |
+
b, n, _, h = *x.shape, self.heads
|
| 132 |
+
qkv = self.to_qkv(x).chunk(3, dim = -1)
|
| 133 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), qkv)
|
| 134 |
+
|
| 135 |
+
dots = torch.einsum('bhid,bhjd->bhij', q, k) * self.scale
|
| 136 |
+
mask_value = -torch.finfo(dots.dtype).max
|
| 137 |
+
|
| 138 |
+
if mask is not None:
|
| 139 |
+
mask = F.pad(mask.flatten(1), (1, 0), value = True)
|
| 140 |
+
assert mask.shape[-1] == dots.shape[-1], 'mask has incorrect dimensions'
|
| 141 |
+
mask = mask[:, None, :] * mask[:, :, None]
|
| 142 |
+
dots.masked_fill_(~mask, mask_value)
|
| 143 |
+
del mask
|
| 144 |
+
|
| 145 |
+
attn = dots.softmax(dim=-1)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
out = torch.einsum('bhij,bhjd->bhid', attn, v)
|
| 149 |
+
out = rearrange(out, 'b h n d -> b n (h d)')
|
| 150 |
+
out = self.to_out(out)
|
| 151 |
+
return out
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class Transformer(nn.Module):
|
| 155 |
+
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout):
|
| 156 |
+
super().__init__()
|
| 157 |
+
self.layers = nn.ModuleList([])
|
| 158 |
+
for _ in range(depth):
|
| 159 |
+
self.layers.append(nn.ModuleList([
|
| 160 |
+
Residual(PreNorm(dim, Attention(dim, heads = heads, dim_head = dim_head, dropout = dropout))),
|
| 161 |
+
Residual(PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout)))
|
| 162 |
+
]))
|
| 163 |
+
def forward(self, x, mask = None):
|
| 164 |
+
for attn, ff in self.layers:
|
| 165 |
+
x = attn(x, mask = mask)
|
| 166 |
+
x = ff(x)
|
| 167 |
+
return x
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
class TransformerDecoder(nn.Module):
|
| 171 |
+
def __init__(self, dim, depth, heads, dim_head, mlp_dim, dropout, softmax=True):
|
| 172 |
+
super().__init__()
|
| 173 |
+
self.layers = nn.ModuleList([])
|
| 174 |
+
for _ in range(depth):
|
| 175 |
+
self.layers.append(nn.ModuleList([
|
| 176 |
+
Residual2(PreNorm2(dim, Cross_Attention(dim, heads = heads,
|
| 177 |
+
dim_head = dim_head, dropout = dropout,
|
| 178 |
+
softmax=softmax))),
|
| 179 |
+
Residual(PreNorm(dim, FeedForward(dim, mlp_dim, dropout = dropout)))
|
| 180 |
+
]))
|
| 181 |
+
def forward(self, x, m, mask = None):
|
| 182 |
+
"""target(query), memory"""
|
| 183 |
+
for attn, ff in self.layers:
|
| 184 |
+
x = attn(x, m, mask = mask)
|
| 185 |
+
x = ff(x)
|
| 186 |
+
return x
|
| 187 |
+
|
| 188 |
+
|
models/losses.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def cross_entropy(input, target, weight=None, reduction='mean',ignore_index=255):
|
| 6 |
+
"""
|
| 7 |
+
logSoftmax_with_loss
|
| 8 |
+
:param input: torch.Tensor, N*C*H*W
|
| 9 |
+
:param target: torch.Tensor, N*1*H*W,/ N*H*W
|
| 10 |
+
:param weight: torch.Tensor, C
|
| 11 |
+
:return: torch.Tensor [0]
|
| 12 |
+
"""
|
| 13 |
+
target = target.long()
|
| 14 |
+
if target.dim() == 4:
|
| 15 |
+
target = torch.squeeze(target, dim=1)
|
| 16 |
+
if input.shape[-1] != target.shape[-1]:
|
| 17 |
+
input = F.interpolate(input, size=target.shape[1:], mode='bilinear',align_corners=True)
|
| 18 |
+
|
| 19 |
+
return F.cross_entropy(input=input, target=target, weight=weight,
|
| 20 |
+
ignore_index=ignore_index, reduction=reduction)
|
models/networks.py
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torch.nn import init
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torch.optim import lr_scheduler
|
| 6 |
+
|
| 7 |
+
import functools
|
| 8 |
+
from einops import rearrange
|
| 9 |
+
|
| 10 |
+
import models
|
| 11 |
+
from models.help_funcs import Transformer, TransformerDecoder, TwoLayerConv2d
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
###############################################################################
|
| 15 |
+
# Helper Functions
|
| 16 |
+
###############################################################################
|
| 17 |
+
|
| 18 |
+
def get_scheduler(optimizer, args):
|
| 19 |
+
"""Return a learning rate scheduler
|
| 20 |
+
|
| 21 |
+
Parameters:
|
| 22 |
+
optimizer -- the optimizer of the network
|
| 23 |
+
args (option class) -- stores all the experiment flags; needs to be a subclass of BaseOptions.
|
| 24 |
+
opt.lr_policy is the name of learning rate policy: linear | step | plateau | cosine
|
| 25 |
+
|
| 26 |
+
For 'linear', we keep the same learning rate for the first <opt.niter> epochs
|
| 27 |
+
and linearly decay the rate to zero over the next <opt.niter_decay> epochs.
|
| 28 |
+
For other schedulers (step, plateau, and cosine), we use the default PyTorch schedulers.
|
| 29 |
+
See https://pytorch.org/docs/stable/optim.html for more details.
|
| 30 |
+
"""
|
| 31 |
+
if args.lr_policy == 'linear':
|
| 32 |
+
def lambda_rule(epoch):
|
| 33 |
+
lr_l = 1.0 - epoch / float(args.max_epochs + 1)
|
| 34 |
+
return lr_l
|
| 35 |
+
scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda_rule)
|
| 36 |
+
elif args.lr_policy == 'step':
|
| 37 |
+
step_size = args.max_epochs//3
|
| 38 |
+
# args.lr_decay_iters
|
| 39 |
+
scheduler = lr_scheduler.StepLR(optimizer, step_size=step_size, gamma=0.1)
|
| 40 |
+
else:
|
| 41 |
+
return NotImplementedError('learning rate policy [%s] is not implemented', args.lr_policy)
|
| 42 |
+
return scheduler
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class Identity(nn.Module):
|
| 46 |
+
def forward(self, x):
|
| 47 |
+
return x
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def get_norm_layer(norm_type='instance'):
|
| 51 |
+
"""Return a normalization layer
|
| 52 |
+
|
| 53 |
+
Parameters:
|
| 54 |
+
norm_type (str) -- the name of the normalization layer: batch | instance | none
|
| 55 |
+
|
| 56 |
+
For BatchNorm, we use learnable affine parameters and track running statistics (mean/stddev).
|
| 57 |
+
For InstanceNorm, we do not use learnable affine parameters. We do not track running statistics.
|
| 58 |
+
"""
|
| 59 |
+
if norm_type == 'batch':
|
| 60 |
+
norm_layer = functools.partial(nn.BatchNorm2d, affine=True, track_running_stats=True)
|
| 61 |
+
elif norm_type == 'instance':
|
| 62 |
+
norm_layer = functools.partial(nn.InstanceNorm2d, affine=False, track_running_stats=False)
|
| 63 |
+
elif norm_type == 'none':
|
| 64 |
+
norm_layer = lambda x: Identity()
|
| 65 |
+
else:
|
| 66 |
+
raise NotImplementedError('normalization layer [%s] is not found' % norm_type)
|
| 67 |
+
return norm_layer
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def init_weights(net, init_type='normal', init_gain=0.02):
|
| 71 |
+
"""Initialize network weights.
|
| 72 |
+
|
| 73 |
+
Parameters:
|
| 74 |
+
net (network) -- network to be initialized
|
| 75 |
+
init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal
|
| 76 |
+
init_gain (float) -- scaling factor for normal, xavier and orthogonal.
|
| 77 |
+
|
| 78 |
+
We use 'normal' in the original pix2pix and CycleGAN paper. But xavier and kaiming might
|
| 79 |
+
work better for some applications. Feel free to try yourself.
|
| 80 |
+
"""
|
| 81 |
+
def init_func(m): # define the initialization function
|
| 82 |
+
classname = m.__class__.__name__
|
| 83 |
+
if hasattr(m, 'weight') and (classname.find('Conv') != -1 or classname.find('Linear') != -1):
|
| 84 |
+
if init_type == 'normal':
|
| 85 |
+
init.normal_(m.weight.data, 0.0, init_gain)
|
| 86 |
+
elif init_type == 'xavier':
|
| 87 |
+
init.xavier_normal_(m.weight.data, gain=init_gain)
|
| 88 |
+
elif init_type == 'kaiming':
|
| 89 |
+
init.kaiming_normal_(m.weight.data, a=0, mode='fan_in')
|
| 90 |
+
elif init_type == 'orthogonal':
|
| 91 |
+
init.orthogonal_(m.weight.data, gain=init_gain)
|
| 92 |
+
else:
|
| 93 |
+
raise NotImplementedError('initialization method [%s] is not implemented' % init_type)
|
| 94 |
+
if hasattr(m, 'bias') and m.bias is not None:
|
| 95 |
+
init.constant_(m.bias.data, 0.0)
|
| 96 |
+
elif classname.find('BatchNorm2d') != -1: # BatchNorm Layer's weight is not a matrix; only normal distribution applies.
|
| 97 |
+
init.normal_(m.weight.data, 1.0, init_gain)
|
| 98 |
+
init.constant_(m.bias.data, 0.0)
|
| 99 |
+
|
| 100 |
+
print('initialize network with %s' % init_type)
|
| 101 |
+
net.apply(init_func) # apply the initialization function <init_func>
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def init_net(net, init_type='normal', init_gain=0.02, gpu_ids=[]):
|
| 105 |
+
"""Initialize a network: 1. register CPU/GPU device (with multi-GPU support); 2. initialize the network weights
|
| 106 |
+
Parameters:
|
| 107 |
+
net (network) -- the network to be initialized
|
| 108 |
+
init_type (str) -- the name of an initialization method: normal | xavier | kaiming | orthogonal
|
| 109 |
+
gain (float) -- scaling factor for normal, xavier and orthogonal.
|
| 110 |
+
gpu_ids (int list) -- which GPUs the network runs on: e.g., 0,1,2
|
| 111 |
+
|
| 112 |
+
Return an initialized network.
|
| 113 |
+
"""
|
| 114 |
+
if len(gpu_ids) > 0:
|
| 115 |
+
assert(torch.cuda.is_available())
|
| 116 |
+
net.to(gpu_ids[0])
|
| 117 |
+
if len(gpu_ids) > 1:
|
| 118 |
+
net = torch.nn.DataParallel(net, gpu_ids) # multi-GPUs
|
| 119 |
+
init_weights(net, init_type, init_gain=init_gain)
|
| 120 |
+
return net
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def define_G(args, init_type='normal', init_gain=0.02, gpu_ids=[]):
|
| 124 |
+
if args.net_G == 'base_resnet18':
|
| 125 |
+
net = ResNet(input_nc=3, output_nc=2, output_sigmoid=False)
|
| 126 |
+
|
| 127 |
+
elif args.net_G == 'base_transformer_pos_s4':
|
| 128 |
+
net = BASE_Transformer(input_nc=3, output_nc=2, token_len=4, resnet_stages_num=4,
|
| 129 |
+
with_pos='learned')
|
| 130 |
+
|
| 131 |
+
elif args.net_G == 'base_transformer_pos_s4_dd8':
|
| 132 |
+
net = BASE_Transformer(input_nc=3, output_nc=2, token_len=4, resnet_stages_num=4,
|
| 133 |
+
with_pos='learned', enc_depth=1, dec_depth=8)
|
| 134 |
+
|
| 135 |
+
elif args.net_G == 'base_transformer_pos_s4_dd8_dedim8':
|
| 136 |
+
net = BASE_Transformer(input_nc=3, output_nc=2, token_len=4, resnet_stages_num=4,
|
| 137 |
+
with_pos='learned', enc_depth=1, dec_depth=8, decoder_dim_head=8)
|
| 138 |
+
|
| 139 |
+
else:
|
| 140 |
+
raise NotImplementedError('Generator model name [%s] is not recognized' % args.net_G)
|
| 141 |
+
return init_net(net, init_type, init_gain, gpu_ids)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
###############################################################################
|
| 145 |
+
# main Functions
|
| 146 |
+
###############################################################################
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class ResNet(torch.nn.Module):
|
| 150 |
+
def __init__(self, input_nc, output_nc,
|
| 151 |
+
resnet_stages_num=5, backbone='resnet18',
|
| 152 |
+
output_sigmoid=False, if_upsample_2x=True):
|
| 153 |
+
"""
|
| 154 |
+
In the constructor we instantiate two nn.Linear modules and assign them as
|
| 155 |
+
member variables.
|
| 156 |
+
"""
|
| 157 |
+
super(ResNet, self).__init__()
|
| 158 |
+
expand = 1
|
| 159 |
+
if backbone == 'resnet18':
|
| 160 |
+
self.resnet = models.resnet18(pretrained=True,
|
| 161 |
+
replace_stride_with_dilation=[False,True,True])
|
| 162 |
+
elif backbone == 'resnet34':
|
| 163 |
+
self.resnet = models.resnet34(pretrained=True,
|
| 164 |
+
replace_stride_with_dilation=[False,True,True])
|
| 165 |
+
elif backbone == 'resnet50':
|
| 166 |
+
self.resnet = models.resnet50(pretrained=True,
|
| 167 |
+
replace_stride_with_dilation=[False,True,True])
|
| 168 |
+
expand = 4
|
| 169 |
+
else:
|
| 170 |
+
raise NotImplementedError
|
| 171 |
+
self.relu = nn.ReLU()
|
| 172 |
+
self.upsamplex2 = nn.Upsample(scale_factor=2)
|
| 173 |
+
self.upsamplex4 = nn.Upsample(scale_factor=4, mode='bilinear')
|
| 174 |
+
|
| 175 |
+
self.classifier = TwoLayerConv2d(in_channels=32, out_channels=output_nc)
|
| 176 |
+
|
| 177 |
+
self.resnet_stages_num = resnet_stages_num
|
| 178 |
+
|
| 179 |
+
self.if_upsample_2x = if_upsample_2x
|
| 180 |
+
if self.resnet_stages_num == 5:
|
| 181 |
+
layers = 512 * expand
|
| 182 |
+
elif self.resnet_stages_num == 4:
|
| 183 |
+
layers = 256 * expand
|
| 184 |
+
elif self.resnet_stages_num == 3:
|
| 185 |
+
layers = 128 * expand
|
| 186 |
+
else:
|
| 187 |
+
raise NotImplementedError
|
| 188 |
+
self.conv_pred = nn.Conv2d(layers, 32, kernel_size=3, padding=1)
|
| 189 |
+
|
| 190 |
+
self.output_sigmoid = output_sigmoid
|
| 191 |
+
self.sigmoid = nn.Sigmoid()
|
| 192 |
+
|
| 193 |
+
def forward(self, x1, x2):
|
| 194 |
+
x1 = self.forward_single(x1)
|
| 195 |
+
x2 = self.forward_single(x2)
|
| 196 |
+
x = torch.abs(x1 - x2)
|
| 197 |
+
if not self.if_upsample_2x:
|
| 198 |
+
x = self.upsamplex2(x)
|
| 199 |
+
x = self.upsamplex4(x)
|
| 200 |
+
x = self.classifier(x)
|
| 201 |
+
|
| 202 |
+
if self.output_sigmoid:
|
| 203 |
+
x = self.sigmoid(x)
|
| 204 |
+
return x
|
| 205 |
+
|
| 206 |
+
def forward_single(self, x):
|
| 207 |
+
# resnet layers
|
| 208 |
+
x = self.resnet.conv1(x)
|
| 209 |
+
x = self.resnet.bn1(x)
|
| 210 |
+
x = self.resnet.relu(x)
|
| 211 |
+
x = self.resnet.maxpool(x)
|
| 212 |
+
|
| 213 |
+
x_4 = self.resnet.layer1(x) # 1/4, in=64, out=64
|
| 214 |
+
x_8 = self.resnet.layer2(x_4) # 1/8, in=64, out=128
|
| 215 |
+
|
| 216 |
+
if self.resnet_stages_num > 3:
|
| 217 |
+
x_8 = self.resnet.layer3(x_8) # 1/8, in=128, out=256
|
| 218 |
+
|
| 219 |
+
if self.resnet_stages_num == 5:
|
| 220 |
+
x_8 = self.resnet.layer4(x_8) # 1/32, in=256, out=512
|
| 221 |
+
elif self.resnet_stages_num > 5:
|
| 222 |
+
raise NotImplementedError
|
| 223 |
+
|
| 224 |
+
if self.if_upsample_2x:
|
| 225 |
+
x = self.upsamplex2(x_8)
|
| 226 |
+
else:
|
| 227 |
+
x = x_8
|
| 228 |
+
# output layers
|
| 229 |
+
x = self.conv_pred(x)
|
| 230 |
+
return x
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class BASE_Transformer(ResNet):
|
| 234 |
+
"""
|
| 235 |
+
Resnet of 8 downsampling + BIT + bitemporal feature Differencing + a small CNN
|
| 236 |
+
"""
|
| 237 |
+
def __init__(self, input_nc, output_nc, with_pos, resnet_stages_num=5,
|
| 238 |
+
token_len=4, token_trans=True,
|
| 239 |
+
enc_depth=1, dec_depth=1,
|
| 240 |
+
dim_head=64, decoder_dim_head=64,
|
| 241 |
+
tokenizer=True, if_upsample_2x=True,
|
| 242 |
+
pool_mode='max', pool_size=2,
|
| 243 |
+
backbone='resnet18',
|
| 244 |
+
decoder_softmax=True, with_decoder_pos=None,
|
| 245 |
+
with_decoder=True):
|
| 246 |
+
super(BASE_Transformer, self).__init__(input_nc, output_nc,backbone=backbone,
|
| 247 |
+
resnet_stages_num=resnet_stages_num,
|
| 248 |
+
if_upsample_2x=if_upsample_2x,
|
| 249 |
+
)
|
| 250 |
+
self.token_len = token_len
|
| 251 |
+
self.conv_a = nn.Conv2d(32, self.token_len, kernel_size=1,
|
| 252 |
+
padding=0, bias=False)
|
| 253 |
+
self.tokenizer = tokenizer
|
| 254 |
+
if not self.tokenizer:
|
| 255 |
+
# if not use tokenzier,then downsample the feature map into a certain size
|
| 256 |
+
self.pooling_size = pool_size
|
| 257 |
+
self.pool_mode = pool_mode
|
| 258 |
+
self.token_len = self.pooling_size * self.pooling_size
|
| 259 |
+
|
| 260 |
+
self.token_trans = token_trans
|
| 261 |
+
self.with_decoder = with_decoder
|
| 262 |
+
dim = 32
|
| 263 |
+
mlp_dim = 2*dim
|
| 264 |
+
|
| 265 |
+
self.with_pos = with_pos
|
| 266 |
+
if with_pos is 'learned':
|
| 267 |
+
self.pos_embedding = nn.Parameter(torch.randn(1, self.token_len*2, 32))
|
| 268 |
+
decoder_pos_size = 256//4
|
| 269 |
+
self.with_decoder_pos = with_decoder_pos
|
| 270 |
+
if self.with_decoder_pos == 'learned':
|
| 271 |
+
self.pos_embedding_decoder =nn.Parameter(torch.randn(1, 32,
|
| 272 |
+
decoder_pos_size,
|
| 273 |
+
decoder_pos_size))
|
| 274 |
+
self.enc_depth = enc_depth
|
| 275 |
+
self.dec_depth = dec_depth
|
| 276 |
+
self.dim_head = dim_head
|
| 277 |
+
self.decoder_dim_head = decoder_dim_head
|
| 278 |
+
self.transformer = Transformer(dim=dim, depth=self.enc_depth, heads=8,
|
| 279 |
+
dim_head=self.dim_head,
|
| 280 |
+
mlp_dim=mlp_dim, dropout=0)
|
| 281 |
+
self.transformer_decoder = TransformerDecoder(dim=dim, depth=self.dec_depth,
|
| 282 |
+
heads=8, dim_head=self.decoder_dim_head, mlp_dim=mlp_dim, dropout=0,
|
| 283 |
+
softmax=decoder_softmax)
|
| 284 |
+
|
| 285 |
+
def _forward_semantic_tokens(self, x):
|
| 286 |
+
b, c, h, w = x.shape
|
| 287 |
+
spatial_attention = self.conv_a(x)
|
| 288 |
+
spatial_attention = spatial_attention.view([b, self.token_len, -1]).contiguous()
|
| 289 |
+
spatial_attention = torch.softmax(spatial_attention, dim=-1)
|
| 290 |
+
x = x.view([b, c, -1]).contiguous()
|
| 291 |
+
tokens = torch.einsum('bln,bcn->blc', spatial_attention, x)
|
| 292 |
+
|
| 293 |
+
return tokens
|
| 294 |
+
|
| 295 |
+
def _forward_reshape_tokens(self, x):
|
| 296 |
+
# b,c,h,w = x.shape
|
| 297 |
+
if self.pool_mode is 'max':
|
| 298 |
+
x = F.adaptive_max_pool2d(x, [self.pooling_size, self.pooling_size])
|
| 299 |
+
elif self.pool_mode is 'ave':
|
| 300 |
+
x = F.adaptive_avg_pool2d(x, [self.pooling_size, self.pooling_size])
|
| 301 |
+
else:
|
| 302 |
+
x = x
|
| 303 |
+
tokens = rearrange(x, 'b c h w -> b (h w) c')
|
| 304 |
+
return tokens
|
| 305 |
+
|
| 306 |
+
def _forward_transformer(self, x):
|
| 307 |
+
if self.with_pos:
|
| 308 |
+
x += self.pos_embedding
|
| 309 |
+
x = self.transformer(x)
|
| 310 |
+
return x
|
| 311 |
+
|
| 312 |
+
def _forward_transformer_decoder(self, x, m):
|
| 313 |
+
b, c, h, w = x.shape
|
| 314 |
+
if self.with_decoder_pos == 'fix':
|
| 315 |
+
x = x + self.pos_embedding_decoder
|
| 316 |
+
elif self.with_decoder_pos == 'learned':
|
| 317 |
+
x = x + self.pos_embedding_decoder
|
| 318 |
+
x = rearrange(x, 'b c h w -> b (h w) c')
|
| 319 |
+
x = self.transformer_decoder(x, m)
|
| 320 |
+
x = rearrange(x, 'b (h w) c -> b c h w', h=h)
|
| 321 |
+
return x
|
| 322 |
+
|
| 323 |
+
def _forward_simple_decoder(self, x, m):
|
| 324 |
+
b, c, h, w = x.shape
|
| 325 |
+
b, l, c = m.shape
|
| 326 |
+
m = m.expand([h,w,b,l,c])
|
| 327 |
+
m = rearrange(m, 'h w b l c -> l b c h w')
|
| 328 |
+
m = m.sum(0)
|
| 329 |
+
x = x + m
|
| 330 |
+
return x
|
| 331 |
+
|
| 332 |
+
def forward(self, x1, x2):
|
| 333 |
+
# forward backbone resnet
|
| 334 |
+
x1 = self.forward_single(x1)
|
| 335 |
+
x2 = self.forward_single(x2)
|
| 336 |
+
|
| 337 |
+
# forward tokenzier
|
| 338 |
+
if self.tokenizer:
|
| 339 |
+
token1 = self._forward_semantic_tokens(x1)
|
| 340 |
+
token2 = self._forward_semantic_tokens(x2)
|
| 341 |
+
else:
|
| 342 |
+
token1 = self._forward_reshape_tokens(x1)
|
| 343 |
+
token2 = self._forward_reshape_tokens(x2)
|
| 344 |
+
# forward transformer encoder
|
| 345 |
+
if self.token_trans:
|
| 346 |
+
self.tokens_ = torch.cat([token1, token2], dim=1)
|
| 347 |
+
self.tokens = self._forward_transformer(self.tokens_)
|
| 348 |
+
token1, token2 = self.tokens.chunk(2, dim=1)
|
| 349 |
+
# forward transformer decoder
|
| 350 |
+
if self.with_decoder:
|
| 351 |
+
x1 = self._forward_transformer_decoder(x1, token1)
|
| 352 |
+
x2 = self._forward_transformer_decoder(x2, token2)
|
| 353 |
+
else:
|
| 354 |
+
x1 = self._forward_simple_decoder(x1, token1)
|
| 355 |
+
x2 = self._forward_simple_decoder(x2, token2)
|
| 356 |
+
# feature differencing
|
| 357 |
+
x = torch.abs(x1 - x2)
|
| 358 |
+
if not self.if_upsample_2x:
|
| 359 |
+
x = self.upsamplex2(x)
|
| 360 |
+
x = self.upsamplex4(x)
|
| 361 |
+
# forward small cnn
|
| 362 |
+
x = self.classifier(x)
|
| 363 |
+
if self.output_sigmoid:
|
| 364 |
+
x = self.sigmoid(x)
|
| 365 |
+
return x
|
| 366 |
+
|
| 367 |
+
|
models/resnet.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torchvision.models.utils import load_state_dict_from_url
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
|
| 7 |
+
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
|
| 8 |
+
'wide_resnet50_2', 'wide_resnet101_2']
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
model_urls = {
|
| 12 |
+
'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
|
| 13 |
+
'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
|
| 14 |
+
'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
|
| 15 |
+
'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
|
| 16 |
+
'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
|
| 17 |
+
'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
|
| 18 |
+
'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
|
| 19 |
+
'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
|
| 20 |
+
'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def conv3x3(in_planes, out_planes, stride=1, groups=1, dilation=1):
|
| 25 |
+
"""3x3 convolution with padding"""
|
| 26 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
|
| 27 |
+
padding=dilation, groups=groups, bias=False, dilation=dilation)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def conv1x1(in_planes, out_planes, stride=1):
|
| 31 |
+
"""1x1 convolution"""
|
| 32 |
+
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class BasicBlock(nn.Module):
|
| 36 |
+
expansion = 1
|
| 37 |
+
|
| 38 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
| 39 |
+
base_width=64, dilation=1, norm_layer=None):
|
| 40 |
+
super(BasicBlock, self).__init__()
|
| 41 |
+
if norm_layer is None:
|
| 42 |
+
norm_layer = nn.BatchNorm2d
|
| 43 |
+
if groups != 1 or base_width != 64:
|
| 44 |
+
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
|
| 45 |
+
if dilation > 1:
|
| 46 |
+
dilation = 1
|
| 47 |
+
# raise NotImplementedError("Dilation > 1 not supported in BasicBlock")
|
| 48 |
+
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
|
| 49 |
+
self.conv1 = conv3x3(inplanes, planes, stride)
|
| 50 |
+
self.bn1 = norm_layer(planes)
|
| 51 |
+
self.relu = nn.ReLU(inplace=True)
|
| 52 |
+
self.conv2 = conv3x3(planes, planes)
|
| 53 |
+
self.bn2 = norm_layer(planes)
|
| 54 |
+
self.downsample = downsample
|
| 55 |
+
self.stride = stride
|
| 56 |
+
|
| 57 |
+
def forward(self, x):
|
| 58 |
+
identity = x
|
| 59 |
+
|
| 60 |
+
out = self.conv1(x)
|
| 61 |
+
out = self.bn1(out)
|
| 62 |
+
out = self.relu(out)
|
| 63 |
+
|
| 64 |
+
out = self.conv2(out)
|
| 65 |
+
out = self.bn2(out)
|
| 66 |
+
|
| 67 |
+
if self.downsample is not None:
|
| 68 |
+
identity = self.downsample(x)
|
| 69 |
+
|
| 70 |
+
out += identity
|
| 71 |
+
out = self.relu(out)
|
| 72 |
+
|
| 73 |
+
return out
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class Bottleneck(nn.Module):
|
| 77 |
+
# Bottleneck in torchvision places the stride for downsampling at 3x3 convolution(self.conv2)
|
| 78 |
+
# while original implementation places the stride at the first 1x1 convolution(self.conv1)
|
| 79 |
+
# according to "Deep residual learning for image recognition"https://arxiv.org/abs/1512.03385.
|
| 80 |
+
# This variant is also known as ResNet V1.5 and improves accuracy according to
|
| 81 |
+
# https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch.
|
| 82 |
+
|
| 83 |
+
expansion = 4
|
| 84 |
+
|
| 85 |
+
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1,
|
| 86 |
+
base_width=64, dilation=1, norm_layer=None):
|
| 87 |
+
super(Bottleneck, self).__init__()
|
| 88 |
+
if norm_layer is None:
|
| 89 |
+
norm_layer = nn.BatchNorm2d
|
| 90 |
+
width = int(planes * (base_width / 64.)) * groups
|
| 91 |
+
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
|
| 92 |
+
self.conv1 = conv1x1(inplanes, width)
|
| 93 |
+
self.bn1 = norm_layer(width)
|
| 94 |
+
self.conv2 = conv3x3(width, width, stride, groups, dilation)
|
| 95 |
+
self.bn2 = norm_layer(width)
|
| 96 |
+
self.conv3 = conv1x1(width, planes * self.expansion)
|
| 97 |
+
self.bn3 = norm_layer(planes * self.expansion)
|
| 98 |
+
self.relu = nn.ReLU(inplace=True)
|
| 99 |
+
self.downsample = downsample
|
| 100 |
+
self.stride = stride
|
| 101 |
+
|
| 102 |
+
def forward(self, x):
|
| 103 |
+
identity = x
|
| 104 |
+
|
| 105 |
+
out = self.conv1(x)
|
| 106 |
+
out = self.bn1(out)
|
| 107 |
+
out = self.relu(out)
|
| 108 |
+
|
| 109 |
+
out = self.conv2(out)
|
| 110 |
+
out = self.bn2(out)
|
| 111 |
+
out = self.relu(out)
|
| 112 |
+
|
| 113 |
+
out = self.conv3(out)
|
| 114 |
+
out = self.bn3(out)
|
| 115 |
+
|
| 116 |
+
if self.downsample is not None:
|
| 117 |
+
identity = self.downsample(x)
|
| 118 |
+
|
| 119 |
+
out += identity
|
| 120 |
+
out = self.relu(out)
|
| 121 |
+
|
| 122 |
+
return out
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class ResNet(nn.Module):
|
| 126 |
+
|
| 127 |
+
def __init__(self, block, layers, num_classes=1000, zero_init_residual=False,
|
| 128 |
+
groups=1, width_per_group=64, replace_stride_with_dilation=None,
|
| 129 |
+
norm_layer=None, strides=None):
|
| 130 |
+
super(ResNet, self).__init__()
|
| 131 |
+
if norm_layer is None:
|
| 132 |
+
norm_layer = nn.BatchNorm2d
|
| 133 |
+
self._norm_layer = norm_layer
|
| 134 |
+
|
| 135 |
+
self.strides = strides
|
| 136 |
+
if self.strides is None:
|
| 137 |
+
self.strides = [2, 2, 2, 2, 2]
|
| 138 |
+
|
| 139 |
+
self.inplanes = 64
|
| 140 |
+
self.dilation = 1
|
| 141 |
+
if replace_stride_with_dilation is None:
|
| 142 |
+
# each element in the tuple indicates if we should replace
|
| 143 |
+
# the 2x2 stride with a dilated convolution instead
|
| 144 |
+
replace_stride_with_dilation = [False, False, False]
|
| 145 |
+
if len(replace_stride_with_dilation) != 3:
|
| 146 |
+
raise ValueError("replace_stride_with_dilation should be None "
|
| 147 |
+
"or a 3-element tuple, got {}".format(replace_stride_with_dilation))
|
| 148 |
+
self.groups = groups
|
| 149 |
+
self.base_width = width_per_group
|
| 150 |
+
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=self.strides[0], padding=3,
|
| 151 |
+
bias=False)
|
| 152 |
+
self.bn1 = norm_layer(self.inplanes)
|
| 153 |
+
self.relu = nn.ReLU(inplace=True)
|
| 154 |
+
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=self.strides[1], padding=1)
|
| 155 |
+
self.layer1 = self._make_layer(block, 64, layers[0])
|
| 156 |
+
self.layer2 = self._make_layer(block, 128, layers[1], stride=self.strides[2],
|
| 157 |
+
dilate=replace_stride_with_dilation[0])
|
| 158 |
+
self.layer3 = self._make_layer(block, 256, layers[2], stride=self.strides[3],
|
| 159 |
+
dilate=replace_stride_with_dilation[1])
|
| 160 |
+
self.layer4 = self._make_layer(block, 512, layers[3], stride=self.strides[4],
|
| 161 |
+
dilate=replace_stride_with_dilation[2])
|
| 162 |
+
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
| 163 |
+
self.fc = nn.Linear(512 * block.expansion, num_classes)
|
| 164 |
+
|
| 165 |
+
for m in self.modules():
|
| 166 |
+
if isinstance(m, nn.Conv2d):
|
| 167 |
+
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
| 168 |
+
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
|
| 169 |
+
nn.init.constant_(m.weight, 1)
|
| 170 |
+
nn.init.constant_(m.bias, 0)
|
| 171 |
+
|
| 172 |
+
# Zero-initialize the last BN in each residual branch,
|
| 173 |
+
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
|
| 174 |
+
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
|
| 175 |
+
if zero_init_residual:
|
| 176 |
+
for m in self.modules():
|
| 177 |
+
if isinstance(m, Bottleneck):
|
| 178 |
+
nn.init.constant_(m.bn3.weight, 0)
|
| 179 |
+
elif isinstance(m, BasicBlock):
|
| 180 |
+
nn.init.constant_(m.bn2.weight, 0)
|
| 181 |
+
|
| 182 |
+
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
|
| 183 |
+
norm_layer = self._norm_layer
|
| 184 |
+
downsample = None
|
| 185 |
+
previous_dilation = self.dilation
|
| 186 |
+
if dilate:
|
| 187 |
+
self.dilation *= stride
|
| 188 |
+
stride = 1
|
| 189 |
+
if stride != 1 or self.inplanes != planes * block.expansion:
|
| 190 |
+
downsample = nn.Sequential(
|
| 191 |
+
conv1x1(self.inplanes, planes * block.expansion, stride),
|
| 192 |
+
norm_layer(planes * block.expansion),
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
layers = []
|
| 196 |
+
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
|
| 197 |
+
self.base_width, previous_dilation, norm_layer))
|
| 198 |
+
self.inplanes = planes * block.expansion
|
| 199 |
+
for _ in range(1, blocks):
|
| 200 |
+
layers.append(block(self.inplanes, planes, groups=self.groups,
|
| 201 |
+
base_width=self.base_width, dilation=self.dilation,
|
| 202 |
+
norm_layer=norm_layer))
|
| 203 |
+
|
| 204 |
+
return nn.Sequential(*layers)
|
| 205 |
+
|
| 206 |
+
def _forward_impl(self, x):
|
| 207 |
+
# See note [TorchScript super()]
|
| 208 |
+
x = self.conv1(x)
|
| 209 |
+
x = self.bn1(x)
|
| 210 |
+
x = self.relu(x)
|
| 211 |
+
x = self.maxpool(x)
|
| 212 |
+
|
| 213 |
+
x = self.layer1(x)
|
| 214 |
+
x = self.layer2(x)
|
| 215 |
+
x = self.layer3(x)
|
| 216 |
+
x = self.layer4(x)
|
| 217 |
+
|
| 218 |
+
x = self.avgpool(x)
|
| 219 |
+
x = torch.flatten(x, 1)
|
| 220 |
+
x = self.fc(x)
|
| 221 |
+
|
| 222 |
+
return x
|
| 223 |
+
|
| 224 |
+
def forward(self, x):
|
| 225 |
+
return self._forward_impl(x)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
|
| 229 |
+
model = ResNet(block, layers, **kwargs)
|
| 230 |
+
if pretrained:
|
| 231 |
+
state_dict = load_state_dict_from_url(model_urls[arch],
|
| 232 |
+
progress=progress)
|
| 233 |
+
model.load_state_dict(state_dict)
|
| 234 |
+
return model
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def resnet18(pretrained=False, progress=True, **kwargs):
|
| 238 |
+
r"""ResNet-18 model from
|
| 239 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 240 |
+
|
| 241 |
+
Args:
|
| 242 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 243 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 244 |
+
"""
|
| 245 |
+
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
|
| 246 |
+
**kwargs)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def resnet34(pretrained=False, progress=True, **kwargs):
|
| 250 |
+
r"""ResNet-34 model from
|
| 251 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 252 |
+
|
| 253 |
+
Args:
|
| 254 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 255 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 256 |
+
"""
|
| 257 |
+
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,
|
| 258 |
+
**kwargs)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def resnet50(pretrained=False, progress=True, **kwargs):
|
| 262 |
+
r"""ResNet-50 model from
|
| 263 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 267 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 268 |
+
"""
|
| 269 |
+
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
|
| 270 |
+
**kwargs)
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def resnet101(pretrained=False, progress=True, **kwargs):
|
| 274 |
+
r"""ResNet-101 model from
|
| 275 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 276 |
+
|
| 277 |
+
Args:
|
| 278 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 279 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 280 |
+
"""
|
| 281 |
+
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress,
|
| 282 |
+
**kwargs)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def resnet152(pretrained=False, progress=True, **kwargs):
|
| 286 |
+
r"""ResNet-152 model from
|
| 287 |
+
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
|
| 288 |
+
|
| 289 |
+
Args:
|
| 290 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 291 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 292 |
+
"""
|
| 293 |
+
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress,
|
| 294 |
+
**kwargs)
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
|
| 298 |
+
r"""ResNeXt-50 32x4d model from
|
| 299 |
+
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
| 300 |
+
|
| 301 |
+
Args:
|
| 302 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 303 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 304 |
+
"""
|
| 305 |
+
kwargs['groups'] = 32
|
| 306 |
+
kwargs['width_per_group'] = 4
|
| 307 |
+
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
|
| 308 |
+
pretrained, progress, **kwargs)
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
|
| 312 |
+
r"""ResNeXt-101 32x8d model from
|
| 313 |
+
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
|
| 314 |
+
|
| 315 |
+
Args:
|
| 316 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 317 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 318 |
+
"""
|
| 319 |
+
kwargs['groups'] = 32
|
| 320 |
+
kwargs['width_per_group'] = 8
|
| 321 |
+
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
|
| 322 |
+
pretrained, progress, **kwargs)
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
|
| 326 |
+
r"""Wide ResNet-50-2 model from
|
| 327 |
+
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
| 328 |
+
|
| 329 |
+
The model is the same as ResNet except for the bottleneck number of channels
|
| 330 |
+
which is twice larger in every block. The number of channels in outer 1x1
|
| 331 |
+
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
| 332 |
+
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
| 333 |
+
|
| 334 |
+
Args:
|
| 335 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 336 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 337 |
+
"""
|
| 338 |
+
kwargs['width_per_group'] = 64 * 2
|
| 339 |
+
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
|
| 340 |
+
pretrained, progress, **kwargs)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
|
| 344 |
+
r"""Wide ResNet-101-2 model from
|
| 345 |
+
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
|
| 346 |
+
|
| 347 |
+
The model is the same as ResNet except for the bottleneck number of channels
|
| 348 |
+
which is twice larger in every block. The number of channels in outer 1x1
|
| 349 |
+
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
|
| 350 |
+
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
|
| 351 |
+
|
| 352 |
+
Args:
|
| 353 |
+
pretrained (bool): If True, returns a model pre-trained on ImageNet
|
| 354 |
+
progress (bool): If True, displays a progress bar of the download to stderr
|
| 355 |
+
"""
|
| 356 |
+
kwargs['width_per_group'] = 64 * 2
|
| 357 |
+
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
|
| 358 |
+
pretrained, progress, **kwargs)
|
models/trainer.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
import utils
|
| 6 |
+
from models.networks import *
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.optim as optim
|
| 10 |
+
|
| 11 |
+
from misc.metric_tool import ConfuseMatrixMeter
|
| 12 |
+
from models.losses import cross_entropy
|
| 13 |
+
import models.losses as losses
|
| 14 |
+
|
| 15 |
+
from misc.logger_tool import Logger, Timer
|
| 16 |
+
|
| 17 |
+
from utils import de_norm
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class CDTrainer():
|
| 21 |
+
|
| 22 |
+
def __init__(self, args, dataloaders):
|
| 23 |
+
|
| 24 |
+
self.dataloaders = dataloaders
|
| 25 |
+
|
| 26 |
+
self.n_class = args.n_class
|
| 27 |
+
# define G
|
| 28 |
+
self.net_G = define_G(args=args, gpu_ids=args.gpu_ids)
|
| 29 |
+
|
| 30 |
+
self.device = torch.device("cuda:%s" % args.gpu_ids[0] if torch.cuda.is_available() and len(args.gpu_ids)>0
|
| 31 |
+
else "cpu")
|
| 32 |
+
print(self.device)
|
| 33 |
+
|
| 34 |
+
# Learning rate and Beta1 for Adam optimizers
|
| 35 |
+
self.lr = args.lr
|
| 36 |
+
|
| 37 |
+
# define optimizers
|
| 38 |
+
self.optimizer_G = optim.SGD(self.net_G.parameters(), lr=self.lr,
|
| 39 |
+
momentum=0.9,
|
| 40 |
+
weight_decay=5e-4)
|
| 41 |
+
|
| 42 |
+
# define lr schedulers
|
| 43 |
+
self.exp_lr_scheduler_G = get_scheduler(self.optimizer_G, args)
|
| 44 |
+
|
| 45 |
+
self.running_metric = ConfuseMatrixMeter(n_class=2)
|
| 46 |
+
|
| 47 |
+
# define logger file
|
| 48 |
+
logger_path = os.path.join(args.checkpoint_dir, 'log.txt')
|
| 49 |
+
self.logger = Logger(logger_path)
|
| 50 |
+
self.logger.write_dict_str(args.__dict__)
|
| 51 |
+
# define timer
|
| 52 |
+
self.timer = Timer()
|
| 53 |
+
self.batch_size = args.batch_size
|
| 54 |
+
|
| 55 |
+
# training log
|
| 56 |
+
self.epoch_acc = 0
|
| 57 |
+
self.best_val_acc = 0.0
|
| 58 |
+
self.best_epoch_id = 0
|
| 59 |
+
self.epoch_to_start = 0
|
| 60 |
+
self.max_num_epochs = args.max_epochs
|
| 61 |
+
|
| 62 |
+
self.global_step = 0
|
| 63 |
+
self.steps_per_epoch = len(dataloaders['train'])
|
| 64 |
+
self.total_steps = (self.max_num_epochs - self.epoch_to_start)*self.steps_per_epoch
|
| 65 |
+
|
| 66 |
+
self.G_pred = None
|
| 67 |
+
self.pred_vis = None
|
| 68 |
+
self.batch = None
|
| 69 |
+
self.G_loss = None
|
| 70 |
+
self.is_training = False
|
| 71 |
+
self.batch_id = 0
|
| 72 |
+
self.epoch_id = 0
|
| 73 |
+
self.checkpoint_dir = args.checkpoint_dir
|
| 74 |
+
self.vis_dir = args.vis_dir
|
| 75 |
+
|
| 76 |
+
# define the loss functions
|
| 77 |
+
if args.loss == 'ce':
|
| 78 |
+
self._pxl_loss = cross_entropy
|
| 79 |
+
elif args.loss == 'bce':
|
| 80 |
+
self._pxl_loss = losses.binary_ce
|
| 81 |
+
else:
|
| 82 |
+
raise NotImplemented(args.loss)
|
| 83 |
+
|
| 84 |
+
self.VAL_ACC = np.array([], np.float32)
|
| 85 |
+
if os.path.exists(os.path.join(self.checkpoint_dir, 'val_acc.npy')):
|
| 86 |
+
self.VAL_ACC = np.load(os.path.join(self.checkpoint_dir, 'val_acc.npy'))
|
| 87 |
+
self.TRAIN_ACC = np.array([], np.float32)
|
| 88 |
+
if os.path.exists(os.path.join(self.checkpoint_dir, 'train_acc.npy')):
|
| 89 |
+
self.TRAIN_ACC = np.load(os.path.join(self.checkpoint_dir, 'train_acc.npy'))
|
| 90 |
+
|
| 91 |
+
# check and create model dir
|
| 92 |
+
if os.path.exists(self.checkpoint_dir) is False:
|
| 93 |
+
os.mkdir(self.checkpoint_dir)
|
| 94 |
+
if os.path.exists(self.vis_dir) is False:
|
| 95 |
+
os.mkdir(self.vis_dir)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def _load_checkpoint(self, ckpt_name='last_ckpt.pt'):
|
| 99 |
+
|
| 100 |
+
if os.path.exists(os.path.join(self.checkpoint_dir, ckpt_name)):
|
| 101 |
+
self.logger.write('loading last checkpoint...\n')
|
| 102 |
+
# load the entire checkpoint
|
| 103 |
+
checkpoint = torch.load(os.path.join(self.checkpoint_dir, ckpt_name),
|
| 104 |
+
map_location=self.device)
|
| 105 |
+
# update net_G states
|
| 106 |
+
self.net_G.load_state_dict(checkpoint['model_G_state_dict'])
|
| 107 |
+
|
| 108 |
+
self.optimizer_G.load_state_dict(checkpoint['optimizer_G_state_dict'])
|
| 109 |
+
self.exp_lr_scheduler_G.load_state_dict(
|
| 110 |
+
checkpoint['exp_lr_scheduler_G_state_dict'])
|
| 111 |
+
|
| 112 |
+
self.net_G.to(self.device)
|
| 113 |
+
|
| 114 |
+
# update some other states
|
| 115 |
+
self.epoch_to_start = checkpoint['epoch_id'] + 1
|
| 116 |
+
self.best_val_acc = checkpoint['best_val_acc']
|
| 117 |
+
self.best_epoch_id = checkpoint['best_epoch_id']
|
| 118 |
+
|
| 119 |
+
self.total_steps = (self.max_num_epochs - self.epoch_to_start)*self.steps_per_epoch
|
| 120 |
+
|
| 121 |
+
self.logger.write('Epoch_to_start = %d, Historical_best_acc = %.4f (at epoch %d)\n' %
|
| 122 |
+
(self.epoch_to_start, self.best_val_acc, self.best_epoch_id))
|
| 123 |
+
self.logger.write('\n')
|
| 124 |
+
|
| 125 |
+
else:
|
| 126 |
+
print('training from scratch...')
|
| 127 |
+
|
| 128 |
+
def _timer_update(self):
|
| 129 |
+
self.global_step = (self.epoch_id-self.epoch_to_start) * self.steps_per_epoch + self.batch_id
|
| 130 |
+
|
| 131 |
+
self.timer.update_progress((self.global_step + 1) / self.total_steps)
|
| 132 |
+
est = self.timer.estimated_remaining()
|
| 133 |
+
imps = (self.global_step + 1) * self.batch_size / self.timer.get_stage_elapsed()
|
| 134 |
+
return imps, est
|
| 135 |
+
|
| 136 |
+
def _visualize_pred(self):
|
| 137 |
+
pred = torch.argmax(self.G_pred, dim=1, keepdim=True)
|
| 138 |
+
pred_vis = pred * 255
|
| 139 |
+
return pred_vis
|
| 140 |
+
|
| 141 |
+
def _save_checkpoint(self, ckpt_name):
|
| 142 |
+
torch.save({
|
| 143 |
+
'epoch_id': self.epoch_id,
|
| 144 |
+
'best_val_acc': self.best_val_acc,
|
| 145 |
+
'best_epoch_id': self.best_epoch_id,
|
| 146 |
+
'model_G_state_dict': self.net_G.state_dict(),
|
| 147 |
+
'optimizer_G_state_dict': self.optimizer_G.state_dict(),
|
| 148 |
+
'exp_lr_scheduler_G_state_dict': self.exp_lr_scheduler_G.state_dict(),
|
| 149 |
+
}, os.path.join(self.checkpoint_dir, ckpt_name))
|
| 150 |
+
|
| 151 |
+
def _update_lr_schedulers(self):
|
| 152 |
+
self.exp_lr_scheduler_G.step()
|
| 153 |
+
|
| 154 |
+
def _update_metric(self):
|
| 155 |
+
"""
|
| 156 |
+
update metric
|
| 157 |
+
"""
|
| 158 |
+
target = self.batch['L'].to(self.device).detach()
|
| 159 |
+
G_pred = self.G_pred.detach()
|
| 160 |
+
|
| 161 |
+
G_pred = torch.argmax(G_pred, dim=1)
|
| 162 |
+
|
| 163 |
+
current_score = self.running_metric.update_cm(pr=G_pred.cpu().numpy(), gt=target.cpu().numpy())
|
| 164 |
+
return current_score
|
| 165 |
+
|
| 166 |
+
def _collect_running_batch_states(self):
|
| 167 |
+
|
| 168 |
+
running_acc = self._update_metric()
|
| 169 |
+
|
| 170 |
+
m = len(self.dataloaders['train'])
|
| 171 |
+
if self.is_training is False:
|
| 172 |
+
m = len(self.dataloaders['val'])
|
| 173 |
+
|
| 174 |
+
imps, est = self._timer_update()
|
| 175 |
+
if np.mod(self.batch_id, 100) == 1:
|
| 176 |
+
message = 'Is_training: %s. [%d,%d][%d,%d], imps: %.2f, est: %.2fh, G_loss: %.5f, running_mf1: %.5f\n' %\
|
| 177 |
+
(self.is_training, self.epoch_id, self.max_num_epochs-1, self.batch_id, m,
|
| 178 |
+
imps*self.batch_size, est,
|
| 179 |
+
self.G_loss.item(), running_acc)
|
| 180 |
+
self.logger.write(message)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
if np.mod(self.batch_id, 500) == 1:
|
| 184 |
+
vis_input = utils.make_numpy_grid(de_norm(self.batch['A']))
|
| 185 |
+
vis_input2 = utils.make_numpy_grid(de_norm(self.batch['B']))
|
| 186 |
+
|
| 187 |
+
vis_pred = utils.make_numpy_grid(self._visualize_pred())
|
| 188 |
+
|
| 189 |
+
vis_gt = utils.make_numpy_grid(self.batch['L'])
|
| 190 |
+
vis = np.concatenate([vis_input, vis_input2, vis_pred, vis_gt], axis=0)
|
| 191 |
+
vis = np.clip(vis, a_min=0.0, a_max=1.0)
|
| 192 |
+
file_name = os.path.join(
|
| 193 |
+
self.vis_dir, 'istrain_'+str(self.is_training)+'_'+
|
| 194 |
+
str(self.epoch_id)+'_'+str(self.batch_id)+'.jpg')
|
| 195 |
+
plt.imsave(file_name, vis)
|
| 196 |
+
|
| 197 |
+
def _collect_epoch_states(self):
|
| 198 |
+
scores = self.running_metric.get_scores()
|
| 199 |
+
self.epoch_acc = scores['mf1']
|
| 200 |
+
self.logger.write('Is_training: %s. Epoch %d / %d, epoch_mF1= %.5f\n' %
|
| 201 |
+
(self.is_training, self.epoch_id, self.max_num_epochs-1, self.epoch_acc))
|
| 202 |
+
message = ''
|
| 203 |
+
for k, v in scores.items():
|
| 204 |
+
message += '%s: %.5f ' % (k, v)
|
| 205 |
+
self.logger.write(message+'\n')
|
| 206 |
+
self.logger.write('\n')
|
| 207 |
+
|
| 208 |
+
def _update_checkpoints(self):
|
| 209 |
+
|
| 210 |
+
# save current model
|
| 211 |
+
self._save_checkpoint(ckpt_name='last_ckpt.pt')
|
| 212 |
+
self.logger.write('Lastest model updated. Epoch_acc=%.4f, Historical_best_acc=%.4f (at epoch %d)\n'
|
| 213 |
+
% (self.epoch_acc, self.best_val_acc, self.best_epoch_id))
|
| 214 |
+
self.logger.write('\n')
|
| 215 |
+
|
| 216 |
+
# update the best model (based on eval acc)
|
| 217 |
+
if self.epoch_acc > self.best_val_acc:
|
| 218 |
+
self.best_val_acc = self.epoch_acc
|
| 219 |
+
self.best_epoch_id = self.epoch_id
|
| 220 |
+
self._save_checkpoint(ckpt_name='best_ckpt.pt')
|
| 221 |
+
self.logger.write('*' * 10 + 'Best model updated!\n')
|
| 222 |
+
self.logger.write('\n')
|
| 223 |
+
|
| 224 |
+
def _update_training_acc_curve(self):
|
| 225 |
+
# update train acc curve
|
| 226 |
+
self.TRAIN_ACC = np.append(self.TRAIN_ACC, [self.epoch_acc])
|
| 227 |
+
np.save(os.path.join(self.checkpoint_dir, 'train_acc.npy'), self.TRAIN_ACC)
|
| 228 |
+
|
| 229 |
+
def _update_val_acc_curve(self):
|
| 230 |
+
# update val acc curve
|
| 231 |
+
self.VAL_ACC = np.append(self.VAL_ACC, [self.epoch_acc])
|
| 232 |
+
np.save(os.path.join(self.checkpoint_dir, 'val_acc.npy'), self.VAL_ACC)
|
| 233 |
+
|
| 234 |
+
def _clear_cache(self):
|
| 235 |
+
self.running_metric.clear()
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _forward_pass(self, batch):
|
| 239 |
+
self.batch = batch
|
| 240 |
+
img_in1 = batch['A'].to(self.device)
|
| 241 |
+
img_in2 = batch['B'].to(self.device)
|
| 242 |
+
self.G_pred = self.net_G(img_in1, img_in2)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def _backward_G(self):
|
| 246 |
+
gt = self.batch['L'].to(self.device).long()
|
| 247 |
+
self.G_loss = self._pxl_loss(self.G_pred, gt)
|
| 248 |
+
self.G_loss.backward()
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def train_models(self):
|
| 252 |
+
|
| 253 |
+
self._load_checkpoint()
|
| 254 |
+
|
| 255 |
+
# loop over the dataset multiple times
|
| 256 |
+
for self.epoch_id in range(self.epoch_to_start, self.max_num_epochs):
|
| 257 |
+
|
| 258 |
+
################## train #################
|
| 259 |
+
##########################################
|
| 260 |
+
self._clear_cache()
|
| 261 |
+
self.is_training = True
|
| 262 |
+
self.net_G.train() # Set model to training mode
|
| 263 |
+
# Iterate over data.
|
| 264 |
+
self.logger.write('lr: %0.7f\n' % self.optimizer_G.param_groups[0]['lr'])
|
| 265 |
+
for self.batch_id, batch in enumerate(self.dataloaders['train'], 0):
|
| 266 |
+
self._forward_pass(batch)
|
| 267 |
+
# update G
|
| 268 |
+
self.optimizer_G.zero_grad()
|
| 269 |
+
self._backward_G()
|
| 270 |
+
self.optimizer_G.step()
|
| 271 |
+
self._collect_running_batch_states()
|
| 272 |
+
self._timer_update()
|
| 273 |
+
|
| 274 |
+
self._collect_epoch_states()
|
| 275 |
+
self._update_training_acc_curve()
|
| 276 |
+
self._update_lr_schedulers()
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
################## Eval ##################
|
| 280 |
+
##########################################
|
| 281 |
+
self.logger.write('Begin evaluation...\n')
|
| 282 |
+
self._clear_cache()
|
| 283 |
+
self.is_training = False
|
| 284 |
+
self.net_G.eval()
|
| 285 |
+
|
| 286 |
+
# Iterate over data.
|
| 287 |
+
for self.batch_id, batch in enumerate(self.dataloaders['val'], 0):
|
| 288 |
+
with torch.no_grad():
|
| 289 |
+
self._forward_pass(batch)
|
| 290 |
+
self._collect_running_batch_states()
|
| 291 |
+
self._collect_epoch_states()
|
| 292 |
+
|
| 293 |
+
########### Update_Checkpoints ###########
|
| 294 |
+
##########################################
|
| 295 |
+
self._update_val_acc_curve()
|
| 296 |
+
self._update_checkpoints()
|
| 297 |
+
|
samples/A/test_102_0512_0000.png
ADDED
|
Git LFS Details
|
samples/A/test_113_0256.png
ADDED
|
Git LFS Details
|
samples/A/test_121_0768_0256.png
ADDED
|
Git LFS Details
|
samples/A/test_2_0000_0000.png
ADDED
|
Git LFS Details
|
samples/A/test_2_0000_0512.png
ADDED
|
Git LFS Details
|
samples/A/test_55_0256_0000.png
ADDED
|
Git LFS Details
|
samples/A/test_77_0512_0256.png
ADDED
|
Git LFS Details
|
samples/A/test_7_0256_0512.png
ADDED
|
Git LFS Details
|
samples/A/train_36_0512_0512.png
ADDED
|
Git LFS Details
|
samples/A/train_386_0512_0768.png
ADDED
|
Git LFS Details
|
samples/A/train_412_0512_0768.png
ADDED
|
Git LFS Details
|
samples/A/val_27_0000_0256.png
ADDED
|
Git LFS Details
|
samples/B/test_102_0512_0000.png
ADDED
|
Git LFS Details
|
samples/B/test_113_0256.png
ADDED
|
Git LFS Details
|
samples/B/test_121_0768_0256.png
ADDED
|
Git LFS Details
|
samples/B/test_2_0000_0000.png
ADDED
|
Git LFS Details
|
samples/B/test_2_0000_0512.png
ADDED
|
Git LFS Details
|
samples/B/test_55_0256_0000.png
ADDED
|
Git LFS Details
|
samples/B/test_77_0512_0256.png
ADDED
|
Git LFS Details
|
samples/B/test_7_0256_0512.png
ADDED
|
Git LFS Details
|
samples/B/train_36_0512_0512.png
ADDED
|
Git LFS Details
|
samples/B/train_386_0512_0768.png
ADDED
|
Git LFS Details
|
samples/B/train_412_0512_0768.png
ADDED
|
Git LFS Details
|
samples/B/val_27_0000_0256.png
ADDED
|
Git LFS Details
|
samples/label/test_102_0512_0000.png
ADDED
|
Git LFS Details
|
samples/label/test_121_0768_0256.png
ADDED
|
Git LFS Details
|
samples/label/test_2_0000_0000.png
ADDED
|
Git LFS Details
|
samples/label/test_2_0000_0512.png
ADDED
|
Git LFS Details
|