Upload 2 files
Browse files- core/logger.py +118 -0
- core/metrics.py +107 -0
core/logger.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
from collections import OrderedDict
|
| 4 |
+
import json
|
| 5 |
+
from datetime import datetime
|
| 6 |
+
import argparse
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def mkdirs(paths):
|
| 10 |
+
if isinstance(paths, str):
|
| 11 |
+
os.makedirs(paths, exist_ok=True)
|
| 12 |
+
else:
|
| 13 |
+
for path in paths:
|
| 14 |
+
os.makedirs(path, exist_ok=True)
|
| 15 |
+
|
| 16 |
+
def get_timestamp():
|
| 17 |
+
return datetime.now().strftime('%y%m%d_%H%M%S')
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def parse(args):
|
| 21 |
+
phase = args.phase
|
| 22 |
+
opt_path =args.config
|
| 23 |
+
gpu_ids = args.gpu_ids
|
| 24 |
+
|
| 25 |
+
json_str = ''
|
| 26 |
+
with open(opt_path, 'r') as f:
|
| 27 |
+
for line in f:
|
| 28 |
+
line = line.split('//')[0] + '\n'
|
| 29 |
+
json_str += line
|
| 30 |
+
#print(json_str)
|
| 31 |
+
opt =json.loads(json_str, object_pairs_hook=OrderedDict)
|
| 32 |
+
#print(opt)
|
| 33 |
+
|
| 34 |
+
#create experiments folder
|
| 35 |
+
experiments_root = os.path.join(
|
| 36 |
+
'experiments', '{}_{}'.format(opt['name'], get_timestamp()))
|
| 37 |
+
opt['path_cd']['experiments_root'] = experiments_root
|
| 38 |
+
for key, path in opt['path_cd'].items():
|
| 39 |
+
if 'resume' not in key and 'experiments' not in key:
|
| 40 |
+
opt['path_cd'][key] = os.path.join(experiments_root, path)
|
| 41 |
+
mkdirs(opt['path_cd'][key])
|
| 42 |
+
|
| 43 |
+
#chaneg dataset len
|
| 44 |
+
opt['phase'] = phase
|
| 45 |
+
|
| 46 |
+
# export CUDA_VISIBLE_DEVICES
|
| 47 |
+
if gpu_ids is not None:
|
| 48 |
+
opt['gpu_ids'] = [int(id) for id in gpu_ids.split(',')]
|
| 49 |
+
gpu_list = gpu_ids
|
| 50 |
+
else:
|
| 51 |
+
gpu_list = ','.join(str(x) for x in opt['gpu_ids'])
|
| 52 |
+
#print(gpu_list)
|
| 53 |
+
os.environ['CUDA_VISIBLE_DEVICES'] = gpu_list
|
| 54 |
+
print('expert CUDA_VISIBLE_DEVICES=' + gpu_list)
|
| 55 |
+
if len(gpu_list) > 1:
|
| 56 |
+
opt['distributed'] = True
|
| 57 |
+
else:
|
| 58 |
+
opt['distributed'] = False
|
| 59 |
+
|
| 60 |
+
return opt
|
| 61 |
+
|
| 62 |
+
class NoneDict(dict):
|
| 63 |
+
def __missing__(self, key):
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
# convert to NoneDict, which return None for missing key.
|
| 67 |
+
def dict_to_nonedict(opt):
|
| 68 |
+
if isinstance(opt, dict):
|
| 69 |
+
new_opt = dict()
|
| 70 |
+
for key, sub_opt in opt.items():
|
| 71 |
+
new_opt[key] = dict_to_nonedict(sub_opt)
|
| 72 |
+
return NoneDict(**new_opt)
|
| 73 |
+
elif isinstance(opt, list):
|
| 74 |
+
return [dict_to_nonedict(sub_opt) for sub_opt in opt]
|
| 75 |
+
else:
|
| 76 |
+
return opt
|
| 77 |
+
|
| 78 |
+
def dict2str(opt, indent_l=1):
|
| 79 |
+
'''dict to string for logger'''
|
| 80 |
+
msg = ''
|
| 81 |
+
for k, v in opt.items():
|
| 82 |
+
if isinstance(v, dict):
|
| 83 |
+
msg += ' ' * (indent_l * 2) + k + ':[\n'
|
| 84 |
+
msg += dict2str(v, indent_l + 1)
|
| 85 |
+
msg += ' ' * (indent_l * 2) + ']\n'
|
| 86 |
+
else:
|
| 87 |
+
msg += ' ' * (indent_l * 2) + k + ': ' + str(v) + '\n'
|
| 88 |
+
return msg
|
| 89 |
+
|
| 90 |
+
def setup_logger(logger_name, root, phase, level=logging.INFO, screen=False):
|
| 91 |
+
'''set up logger'''
|
| 92 |
+
l = logging.getLogger(logger_name)
|
| 93 |
+
formatter = logging.Formatter(
|
| 94 |
+
'%(asctime)s.%(msecs)03d - %(levelname)s: %(message)s', datefmt='%y-%m-%d %H:%M:%S')
|
| 95 |
+
print(formatter)
|
| 96 |
+
log_file = os.path.join(root, '{}.log'.format(phase))
|
| 97 |
+
print(log_file)
|
| 98 |
+
fh = logging.FileHandler(log_file, mode='w')
|
| 99 |
+
fh.setFormatter(formatter)
|
| 100 |
+
l.setLevel(level)
|
| 101 |
+
l.addHandler(fh)
|
| 102 |
+
if screen:
|
| 103 |
+
sh = logging.StreamHandler()
|
| 104 |
+
sh.setFormatter(formatter)
|
| 105 |
+
l.addHandler(sh)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
if __name__ == "__main__":
|
| 109 |
+
parser = argparse.ArgumentParser()
|
| 110 |
+
parser.add_argument('-c', '--config', type=str, default='../config/levir.json')
|
| 111 |
+
parser.add_argument('-p', '--phase', type=str, choices=['train', 'test'], default='train')
|
| 112 |
+
parser.add_argument('-gpu', '--gpu_ids', type=str, default=None)
|
| 113 |
+
|
| 114 |
+
args = parser.parse_args()
|
| 115 |
+
opt = parse(args)
|
| 116 |
+
print(opt)
|
| 117 |
+
opt = dict_to_nonedict(opt)
|
| 118 |
+
print(opt)
|
core/metrics.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import math
|
| 3 |
+
import numpy as np
|
| 4 |
+
import cv2
|
| 5 |
+
from torchvision.utils import make_grid
|
| 6 |
+
from data.colormap import second_colormap
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def tensor2img(tensor, out_type=np.uint8, min_max=(-1, 1)):
|
| 10 |
+
'''
|
| 11 |
+
Converts a torch Tensor into an image Numpy array
|
| 12 |
+
Input: 4D(B,(3/1),H,W), 3D(C,H,W), or 2D(H,W), any range, RGB channel order
|
| 13 |
+
Output: 3D(H,W,C) or 2D(H,W), [0,255], np.uint8 (default)
|
| 14 |
+
'''
|
| 15 |
+
tensor = tensor.squeeze().float().cpu().clamp_(*min_max) # clamp
|
| 16 |
+
tensor = (tensor - min_max[0]) / \
|
| 17 |
+
(min_max[1] - min_max[0]) # to range [0,1]
|
| 18 |
+
n_dim = tensor.dim()
|
| 19 |
+
if n_dim == 4:
|
| 20 |
+
n_img = len(tensor)
|
| 21 |
+
img_np = make_grid(tensor, nrow=int(
|
| 22 |
+
math.sqrt(n_img)), normalize=False).numpy()
|
| 23 |
+
img_np = np.transpose(img_np, (1, 2, 0)) # HWC, RGB
|
| 24 |
+
elif n_dim == 3:
|
| 25 |
+
img_np = tensor.numpy()
|
| 26 |
+
img_np = np.transpose(img_np, (1, 2, 0)) # HWC, RGB
|
| 27 |
+
elif n_dim == 2:
|
| 28 |
+
img_np = tensor.numpy()
|
| 29 |
+
else:
|
| 30 |
+
raise TypeError(
|
| 31 |
+
'Only support 4D, 3D and 2D tensor. But received with dimension: {:d}'.format(n_dim))
|
| 32 |
+
if out_type == np.uint8:
|
| 33 |
+
img_np = (img_np * 255.0).round()
|
| 34 |
+
# Important. Unlike matlab, numpy.unit8() WILL NOT round by default.
|
| 35 |
+
return img_np.astype(out_type)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def Index2Color(pred, cmap=second_colormap):
|
| 39 |
+
colormap = np.asarray(cmap, dtype='uint8')
|
| 40 |
+
x = np.asarray(pred, dtype='int32')
|
| 41 |
+
return colormap[x, :]
|
| 42 |
+
|
| 43 |
+
def save_img(img, img_path, mode='RGB'):
|
| 44 |
+
cv2.imwrite(img_path, cv2.cvtColor(img, cv2.COLOR_RGB2BGR))
|
| 45 |
+
# cv2.imwrite(img_path, img)
|
| 46 |
+
|
| 47 |
+
def save_scdimg(img, img_path, mode='RGB'):
|
| 48 |
+
cv2.imwrite(img_path, cv2.cvtColor(np.squeeze(img, axis=0), cv2.COLOR_RGB2BGR))
|
| 49 |
+
|
| 50 |
+
def save_feat(img, img_path, mode='RGB'):
|
| 51 |
+
cv2.imwrite(img_path, cv2.applyColorMap(cv2.resize(img, (256,256), interpolation=cv2.INTER_CUBIC), cv2.COLORMAP_JET))
|
| 52 |
+
# cv2.imwrite(img_path, cv2.resize(img, (256,256), interpolation=cv2.INTER_))
|
| 53 |
+
# cv2.imwrite(img_path, img)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def calculate_psnr(img1, img2):
|
| 57 |
+
# img1 and img2 have range [0, 255]
|
| 58 |
+
img1 = img1.astype(np.float64)
|
| 59 |
+
img2 = img2.astype(np.float64)
|
| 60 |
+
mse = np.mean((img1 - img2)**2)
|
| 61 |
+
if mse == 0:
|
| 62 |
+
return float('inf')
|
| 63 |
+
return 20 * math.log10(255.0 / math.sqrt(mse))
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def ssim(img1, img2):
|
| 67 |
+
C1 = (0.01 * 255)**2
|
| 68 |
+
C2 = (0.03 * 255)**2
|
| 69 |
+
|
| 70 |
+
img1 = img1.astype(np.float64)
|
| 71 |
+
img2 = img2.astype(np.float64)
|
| 72 |
+
kernel = cv2.getGaussianKernel(11, 1.5)
|
| 73 |
+
window = np.outer(kernel, kernel.transpose())
|
| 74 |
+
|
| 75 |
+
mu1 = cv2.filter2D(img1, -1, window)[5:-5, 5:-5] # valid
|
| 76 |
+
mu2 = cv2.filter2D(img2, -1, window)[5:-5, 5:-5]
|
| 77 |
+
mu1_sq = mu1**2
|
| 78 |
+
mu2_sq = mu2**2
|
| 79 |
+
mu1_mu2 = mu1 * mu2
|
| 80 |
+
sigma1_sq = cv2.filter2D(img1**2, -1, window)[5:-5, 5:-5] - mu1_sq
|
| 81 |
+
sigma2_sq = cv2.filter2D(img2**2, -1, window)[5:-5, 5:-5] - mu2_sq
|
| 82 |
+
sigma12 = cv2.filter2D(img1 * img2, -1, window)[5:-5, 5:-5] - mu1_mu2
|
| 83 |
+
|
| 84 |
+
ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / ((mu1_sq + mu2_sq + C1) *
|
| 85 |
+
(sigma1_sq + sigma2_sq + C2))
|
| 86 |
+
return ssim_map.mean()
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def calculate_ssim(img1, img2):
|
| 90 |
+
'''calculate SSIM
|
| 91 |
+
the same outputs as MATLAB's
|
| 92 |
+
img1, img2: [0, 255]
|
| 93 |
+
'''
|
| 94 |
+
if not img1.shape == img2.shape:
|
| 95 |
+
raise ValueError('Input images must have the same dimensions.')
|
| 96 |
+
if img1.ndim == 2:
|
| 97 |
+
return ssim(img1, img2)
|
| 98 |
+
elif img1.ndim == 3:
|
| 99 |
+
if img1.shape[2] == 3:
|
| 100 |
+
ssims = []
|
| 101 |
+
for i in range(3):
|
| 102 |
+
ssims.append(ssim(img1, img2))
|
| 103 |
+
return np.array(ssims).mean()
|
| 104 |
+
elif img1.shape[2] == 1:
|
| 105 |
+
return ssim(np.squeeze(img1), np.squeeze(img2))
|
| 106 |
+
else:
|
| 107 |
+
raise ValueError('Wrong input image dimensions.')
|