justchenhao commited on
Commit
d43892c
·
1 Parent(s): 7e9deba
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +1 -2
  2. README.md +79 -5
  3. checkpoints/BIT_LEVIR/best_ckpt.pt +3 -0
  4. data_config.py +22 -0
  5. datasets/CD_dataset.py +120 -0
  6. datasets/data_utils.py +185 -0
  7. demo.py +79 -0
  8. eval_cd.py +59 -0
  9. main_cd.py +77 -0
  10. misc/imutils.py +401 -0
  11. misc/logger_tool.py +73 -0
  12. misc/metric_tool.py +164 -0
  13. misc/pyutils.py +42 -0
  14. misc/torchutils.py +576 -0
  15. models/__init__.py +1 -0
  16. models/basic_model.py +75 -0
  17. models/evaluator.py +172 -0
  18. models/help_funcs.py +188 -0
  19. models/losses.py +20 -0
  20. models/networks.py +367 -0
  21. models/resnet.py +358 -0
  22. models/trainer.py +297 -0
  23. samples/A/test_102_0512_0000.png +3 -0
  24. samples/A/test_113_0256.png +3 -0
  25. samples/A/test_121_0768_0256.png +3 -0
  26. samples/A/test_2_0000_0000.png +3 -0
  27. samples/A/test_2_0000_0512.png +3 -0
  28. samples/A/test_55_0256_0000.png +3 -0
  29. samples/A/test_77_0512_0256.png +3 -0
  30. samples/A/test_7_0256_0512.png +3 -0
  31. samples/A/train_36_0512_0512.png +3 -0
  32. samples/A/train_386_0512_0768.png +3 -0
  33. samples/A/train_412_0512_0768.png +3 -0
  34. samples/A/val_27_0000_0256.png +3 -0
  35. samples/B/test_102_0512_0000.png +3 -0
  36. samples/B/test_113_0256.png +3 -0
  37. samples/B/test_121_0768_0256.png +3 -0
  38. samples/B/test_2_0000_0000.png +3 -0
  39. samples/B/test_2_0000_0512.png +3 -0
  40. samples/B/test_55_0256_0000.png +3 -0
  41. samples/B/test_77_0512_0256.png +3 -0
  42. samples/B/test_7_0256_0512.png +3 -0
  43. samples/B/train_36_0512_0512.png +3 -0
  44. samples/B/train_386_0512_0768.png +3 -0
  45. samples/B/train_412_0512_0768.png +3 -0
  46. samples/B/val_27_0000_0256.png +3 -0
  47. samples/label/test_102_0512_0000.png +3 -0
  48. samples/label/test_121_0768_0256.png +3 -0
  49. samples/label/test_2_0000_0000.png +3 -0
  50. 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
- ![image-20210228153142126](./images/pipeline.png)
6
 
7
- Code is coming soon~~
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
- ## Test
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ ![image-20210228153142126](./images/pipeline.png)
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

  • SHA256: 8e3221cef502953e06538e1ebddfdb609780c0c1cb6366aa3eee9208012aeab3
  • Pointer size: 130 Bytes
  • Size of remote file: 78.7 kB
samples/A/test_113_0256.png ADDED

Git LFS Details

  • SHA256: 0910d9eede706b3c315e6369f0ea2963b728bf29bc4e140d03c0902c16c5f642
  • Pointer size: 131 Bytes
  • Size of remote file: 905 kB
samples/A/test_121_0768_0256.png ADDED

Git LFS Details

  • SHA256: f51526b822696395992565a70f0d2223f6af7c5a1bf1dfef47edc9cd7450e257
  • Pointer size: 131 Bytes
  • Size of remote file: 102 kB
samples/A/test_2_0000_0000.png ADDED

Git LFS Details

  • SHA256: 99572d7b22f39f2c3947f817ab857784ab85cdbd1307fa0892d41c564f1966bd
  • Pointer size: 131 Bytes
  • Size of remote file: 131 kB
samples/A/test_2_0000_0512.png ADDED

Git LFS Details

  • SHA256: 2b53d8b252e3aa0d205c0d5af432163d4637cc4e6793480ff74d27de5959de10
  • Pointer size: 131 Bytes
  • Size of remote file: 140 kB
samples/A/test_55_0256_0000.png ADDED

Git LFS Details

  • SHA256: fab174503235b57c43a8cf8b8bf4d4a2fb952990b03ef23aa7b86f3d9eb69cab
  • Pointer size: 131 Bytes
  • Size of remote file: 115 kB
samples/A/test_77_0512_0256.png ADDED

Git LFS Details

  • SHA256: fe56308b53815e8b8c81705741236a93d1f75948ce3ad66168a0aed56f51684a
  • Pointer size: 131 Bytes
  • Size of remote file: 159 kB
samples/A/test_7_0256_0512.png ADDED

Git LFS Details

  • SHA256: b6c49095a63807ed29acaadef7153a4e66e7b22f9fa974c07a58f2831fcfe7f3
  • Pointer size: 131 Bytes
  • Size of remote file: 147 kB
samples/A/train_36_0512_0512.png ADDED

Git LFS Details

  • SHA256: faa96e07dc9c4c22be2f5200555712269d260c27977f68f660cc0f1b4cab50cd
  • Pointer size: 131 Bytes
  • Size of remote file: 108 kB
samples/A/train_386_0512_0768.png ADDED

Git LFS Details

  • SHA256: 47e0a39a005e32b91a465564f7f244ddf3cf4b288909b492d041c83640fa19d5
  • Pointer size: 131 Bytes
  • Size of remote file: 127 kB
samples/A/train_412_0512_0768.png ADDED

Git LFS Details

  • SHA256: 72f3dbbb090874de7c84f445c24d103b9b8a4254c2549d6aa55586ea662cbe25
  • Pointer size: 131 Bytes
  • Size of remote file: 102 kB
samples/A/val_27_0000_0256.png ADDED

Git LFS Details

  • SHA256: f8791b7acca8632d1684e5aabe9d01a5b30e06b4b8000a71074f038c76a8c5fa
  • Pointer size: 131 Bytes
  • Size of remote file: 110 kB
samples/B/test_102_0512_0000.png ADDED

Git LFS Details

  • SHA256: c9ec0d2f4f58e3537a1f4685ef16368c5546300d04aef9e8a84d9a6dd3a3cc48
  • Pointer size: 131 Bytes
  • Size of remote file: 127 kB
samples/B/test_113_0256.png ADDED

Git LFS Details

  • SHA256: f701c5918da864e2846d073bc4545e0ef29899fdaccad7736e7a39eef56d0021
  • Pointer size: 131 Bytes
  • Size of remote file: 793 kB
samples/B/test_121_0768_0256.png ADDED

Git LFS Details

  • SHA256: 867fdc2fdad0292cc6561e5aab71fc97c8d67bedd6ebd8d1012e094e3b6092b9
  • Pointer size: 131 Bytes
  • Size of remote file: 128 kB
samples/B/test_2_0000_0000.png ADDED

Git LFS Details

  • SHA256: c37302e1bde614f032291544d371bbe3631f773e3cf4d7130cee5dabfdbae961
  • Pointer size: 131 Bytes
  • Size of remote file: 130 kB
samples/B/test_2_0000_0512.png ADDED

Git LFS Details

  • SHA256: a3235f14acf81726e9036f0e4fe16b87e8d59d75975b3e688bf47ee7f532626a
  • Pointer size: 131 Bytes
  • Size of remote file: 133 kB
samples/B/test_55_0256_0000.png ADDED

Git LFS Details

  • SHA256: be7248f746cfdb1c592489be9fe23ae2b689961a668f8d047a7b0c398ec94018
  • Pointer size: 131 Bytes
  • Size of remote file: 138 kB
samples/B/test_77_0512_0256.png ADDED

Git LFS Details

  • SHA256: 4f53af9029bce438330555c491bbf901b8252eb0062f27351954b5e898eccdaa
  • Pointer size: 131 Bytes
  • Size of remote file: 135 kB
samples/B/test_7_0256_0512.png ADDED

Git LFS Details

  • SHA256: 034282f91bfe65709bb147a31f03b4cf713f0980f2da00a576ed30c535628eb0
  • Pointer size: 131 Bytes
  • Size of remote file: 135 kB
samples/B/train_36_0512_0512.png ADDED

Git LFS Details

  • SHA256: 17bc7800c8dd2a467c6ca89b785dbf15dd1923fbd386fa7a4ddce4324ff6ed67
  • Pointer size: 131 Bytes
  • Size of remote file: 145 kB
samples/B/train_386_0512_0768.png ADDED

Git LFS Details

  • SHA256: c95df4f0d903c88f3c35515b5577c69f9292f88ee953dd5444a8332680f8a11a
  • Pointer size: 131 Bytes
  • Size of remote file: 108 kB
samples/B/train_412_0512_0768.png ADDED

Git LFS Details

  • SHA256: 7b246772966f5fbd6200cd29656aaf62c9868a46afbd813ad5bbc4ea9a6f6669
  • Pointer size: 131 Bytes
  • Size of remote file: 134 kB
samples/B/val_27_0000_0256.png ADDED

Git LFS Details

  • SHA256: 5b90bf93b8cc5a810dcf4447316a87c93173ffe633f670fb5c421d8d2ad39679
  • Pointer size: 131 Bytes
  • Size of remote file: 135 kB
samples/label/test_102_0512_0000.png ADDED

Git LFS Details

  • SHA256: a4223b16cda4fda2bda583e4232603ad91a8700676d1a977401851fb746818ff
  • Pointer size: 129 Bytes
  • Size of remote file: 1.19 kB
samples/label/test_121_0768_0256.png ADDED

Git LFS Details

  • SHA256: ce1fec32f2d6792ea3eddcea7861899c5985ccdfde61a179098694f28d927a3a
  • Pointer size: 129 Bytes
  • Size of remote file: 2.1 kB
samples/label/test_2_0000_0000.png ADDED

Git LFS Details

  • SHA256: c2094d81c4dbd1739cf5b028dff2bdad9eb0c0c6a8c8150f863fa08de338e107
  • Pointer size: 129 Bytes
  • Size of remote file: 1.08 kB
samples/label/test_2_0000_0512.png ADDED

Git LFS Details

  • SHA256: 8d6ce41349c2b149edd5700acc0c42657c2ce553d2f52b39cc795e0924828509
  • Pointer size: 129 Bytes
  • Size of remote file: 1.76 kB