import torch import torch.nn as nn import torch.utils.checkpoint as checkpoint from torch import Tensor from torch.nn import functional as F from torchvision.utils import make_grid from timm.models.layers import DropPath, trunc_normal_ from einops.layers.torch import Rearrange from einops import rearrange, repeat import math import numpy as np import random from typing import Union import cv2 from loguru import logger def img2windows(img, H_sp, W_sp): """ Input: Image (B, C, H, W) Output: Window Partition (B', N, C) """ B, C, H, W = img.shape img_reshape = img.view(B, C, H // H_sp, H_sp, W // W_sp, W_sp) img_perm = img_reshape.permute(0, 2, 4, 3, 5, 1).contiguous().reshape(-1, H_sp* W_sp, C) return img_perm def windows2img(img_splits_hw, H_sp, W_sp, H, W): """ Input: Window Partition (B', N, C) Output: Image (B, H, W, C) """ B = int(img_splits_hw.shape[0] / (H * W / H_sp / W_sp)) img = img_splits_hw.view(B, H // H_sp, W // W_sp, H_sp, W_sp, -1) img = img.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1) return img class Gate(nn.Module): def __init__(self, dim): super().__init__() self.norm = nn.LayerNorm(dim) self.conv = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=1, groups=dim) # DW Conv def forward(self, x, H, W): # Split x1, x2 = x.chunk(2, dim = -1) B, N, C = x.shape x2 = self.conv(self.norm(x2).transpose(1, 2).contiguous().view(B, C//2, H, W)).flatten(2).transpose(-1, -2).contiguous() return x1 * x2 class MLP(nn.Module): def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.): super().__init__() out_features = out_features or in_features hidden_features = hidden_features or in_features self.fc1 = nn.Linear(in_features, hidden_features) self.act = act_layer() self.sg = Gate(hidden_features//2) self.fc2 = nn.Linear(hidden_features//2, out_features) self.drop = nn.Dropout(drop) def forward(self, x, H, W): """ Input: x: (B, H*W, C), H, W Output: x: (B, H*W, C) """ x = self.fc1(x) x = self.act(x) x = self.drop(x) x = self.sg(x, H, W) x = self.drop(x) x = self.fc2(x) x = self.drop(x) return x class DynamicPosBias(nn.Module): # The implementation builds on Crossformer code https://github.com/cheerss/CrossFormer/blob/main/models/crossformer.py """ Dynamic Relative Position Bias. Args: dim (int): Number of input channels. num_heads (int): Number of attention heads. residual (bool): If True, use residual strage to connect conv. """ def __init__(self, dim, num_heads, residual): super().__init__() self.residual = residual self.num_heads = num_heads self.pos_dim = dim // 4 self.pos_proj = nn.Linear(2, self.pos_dim) self.pos1 = nn.Sequential( nn.LayerNorm(self.pos_dim), nn.ReLU(inplace=True), nn.Linear(self.pos_dim, self.pos_dim), ) self.pos2 = nn.Sequential( nn.LayerNorm(self.pos_dim), nn.ReLU(inplace=True), nn.Linear(self.pos_dim, self.pos_dim) ) self.pos3 = nn.Sequential( nn.LayerNorm(self.pos_dim), nn.ReLU(inplace=True), nn.Linear(self.pos_dim, self.num_heads) ) def forward(self, biases): if self.residual: pos = self.pos_proj(biases) # 2Gh-1 * 2Gw-1, heads pos = pos + self.pos1(pos) pos = pos + self.pos2(pos) pos = self.pos3(pos) else: pos = self.pos3(self.pos2(self.pos1(self.pos_proj(biases)))) return pos class WindowAttention(nn.Module): def __init__(self, dim, idx, split_size=[8,8], dim_out=None, num_heads=6, attn_drop=0., proj_drop=0., qk_scale=None, position_bias=True): super().__init__() self.dim = dim self.dim_out = dim_out or dim self.split_size = split_size self.num_heads = num_heads self.idx = idx self.position_bias = position_bias head_dim = dim // num_heads self.scale = qk_scale or head_dim ** -0.5 if idx == 0: H_sp, W_sp = self.split_size[0], self.split_size[1] elif idx == 1: W_sp, H_sp = self.split_size[0], self.split_size[1] else: print ("ERROR MODE", idx) exit(0) self.H_sp = H_sp self.W_sp = W_sp if self.position_bias: self.pos = DynamicPosBias(self.dim // 4, self.num_heads, residual=False) # generate mother-set position_bias_h = torch.arange(1 - self.H_sp, self.H_sp) position_bias_w = torch.arange(1 - self.W_sp, self.W_sp) biases = torch.stack(torch.meshgrid([position_bias_h, position_bias_w])) biases = biases.flatten(1).transpose(0, 1).contiguous().float() self.register_buffer('rpe_biases', biases) # get pair-wise relative position index for each token inside the window coords_h = torch.arange(self.H_sp) coords_w = torch.arange(self.W_sp) coords = torch.stack(torch.meshgrid([coords_h, coords_w])) coords_flatten = torch.flatten(coords, 1) relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] relative_coords = relative_coords.permute(1, 2, 0).contiguous() relative_coords[:, :, 0] += self.H_sp - 1 relative_coords[:, :, 1] += self.W_sp - 1 relative_coords[:, :, 0] *= 2 * self.W_sp - 1 relative_position_index = relative_coords.sum(-1) self.register_buffer('relative_position_index', relative_position_index) self.attn_drop = nn.Dropout(attn_drop) def im2win(self, x, H, W): B, N, C = x.shape x = x.transpose(-2,-1).contiguous().view(B, C, H, W) x = img2windows(x, self.H_sp, self.W_sp) x = x.reshape(-1, self.H_sp* self.W_sp, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3).contiguous() return x def forward(self, qkv, H, W, mask=None): """ Input: qkv: (B, 3*L, C), H, W, mask: (B, N, N), N is the window size Output: x (B, H, W, C) """ q,k,v = qkv[0], qkv[1], qkv[2] B, L, C = q.shape assert L == H * W, "flatten img_tokens has wrong size" # partition the q,k,v, image to window q = self.im2win(q, H, W) k = self.im2win(k, H, W) v = self.im2win(v, H, W) q = q * self.scale attn = (q @ k.transpose(-2, -1)) # B head N C @ B head C N --> B head N N # calculate drpe if self.position_bias: pos = self.pos(self.rpe_biases) # select position bias relative_position_bias = pos[self.relative_position_index.view(-1)].view( self.H_sp * self.W_sp, self.H_sp * self.W_sp, -1) relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() attn = attn + relative_position_bias.unsqueeze(0) N = attn.shape[3] # use mask for shift window if mask is not None: nW = mask.shape[0] attn = attn.view(B, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0) attn = attn.view(-1, self.num_heads, N, N) attn = nn.functional.softmax(attn, dim=-1, dtype=attn.dtype) attn = self.attn_drop(attn) x = (attn @ v) x = x.transpose(1, 2).reshape(-1, self.H_sp* self.W_sp, C) # B head N N @ B head N C # merge the window, window to image x = windows2img(x, self.H_sp, self.W_sp, H, W) # B H' W' C return x class L_SA(nn.Module): # The implementation builds on CAT code https://github.com/zhengchen1999/CAT/blob/main/basicsr/archs/cat_arch.py def __init__(self, dim, num_heads, split_size=[2,4], shift_size=[1,2], qkv_bias=False, qk_scale=None, drop=0., attn_drop=0., idx=0, reso=64, rs_id=0): super().__init__() self.dim = dim self.num_heads = num_heads self.split_size = split_size self.shift_size = shift_size self.idx = idx self.rs_id = rs_id self.patches_resolution = reso self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) assert 0 <= self.shift_size[0] < self.split_size[0], "shift_size must in 0-split_size0" assert 0 <= self.shift_size[1] < self.split_size[1], "shift_size must in 0-split_size1" self.branch_num = 2 self.proj = nn.Linear(dim, dim) self.proj_drop = nn.Dropout(drop) self.attns = nn.ModuleList([ WindowAttention( dim//2, idx = i, split_size=split_size, num_heads=num_heads//2, dim_out=dim//2, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop, position_bias=True) for i in range(self.branch_num)]) if (self.rs_id % 2 == 0 and self.idx > 0 and (self.idx - 2) % 4 == 0) or (self.rs_id % 2 != 0 and self.idx % 4 == 0): attn_mask = self.calculate_mask(self.patches_resolution, self.patches_resolution) self.register_buffer("attn_mask_0", attn_mask[0]) self.register_buffer("attn_mask_1", attn_mask[1]) else: attn_mask = None self.register_buffer("attn_mask_0", None) self.register_buffer("attn_mask_1", None) self.get_v = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=1,groups=dim) # DW Conv def calculate_mask(self, H, W): # The implementation builds on Swin Transformer code https://github.com/microsoft/Swin-Transformer/blob/main/models/swin_transformer.py # calculate attention mask for Rwin img_mask_0 = torch.zeros((1, H, W, 1)) # 1 H W 1 idx=0 img_mask_1 = torch.zeros((1, H, W, 1)) # 1 H W 1 idx=1 h_slices_0 = (slice(0, -self.split_size[0]), slice(-self.split_size[0], -self.shift_size[0]), slice(-self.shift_size[0], None)) w_slices_0 = (slice(0, -self.split_size[1]), slice(-self.split_size[1], -self.shift_size[1]), slice(-self.shift_size[1], None)) h_slices_1 = (slice(0, -self.split_size[1]), slice(-self.split_size[1], -self.shift_size[1]), slice(-self.shift_size[1], None)) w_slices_1 = (slice(0, -self.split_size[0]), slice(-self.split_size[0], -self.shift_size[0]), slice(-self.shift_size[0], None)) cnt = 0 for h in h_slices_0: for w in w_slices_0: img_mask_0[:, h, w, :] = cnt cnt += 1 cnt = 0 for h in h_slices_1: for w in w_slices_1: img_mask_1[:, h, w, :] = cnt cnt += 1 # calculate mask for H-Shift img_mask_0 = img_mask_0.view(1, H // self.split_size[0], self.split_size[0], W // self.split_size[1], self.split_size[1], 1) img_mask_0 = img_mask_0.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, self.split_size[0], self.split_size[1], 1) # nW, sw[0], sw[1], 1 mask_windows_0 = img_mask_0.view(-1, self.split_size[0] * self.split_size[1]) attn_mask_0 = mask_windows_0.unsqueeze(1) - mask_windows_0.unsqueeze(2) attn_mask_0 = attn_mask_0.masked_fill(attn_mask_0 != 0, float(-100.0)).masked_fill(attn_mask_0 == 0, float(0.0)) # calculate mask for V-Shift img_mask_1 = img_mask_1.view(1, H // self.split_size[1], self.split_size[1], W // self.split_size[0], self.split_size[0], 1) img_mask_1 = img_mask_1.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, self.split_size[1], self.split_size[0], 1) # nW, sw[1], sw[0], 1 mask_windows_1 = img_mask_1.view(-1, self.split_size[1] * self.split_size[0]) attn_mask_1 = mask_windows_1.unsqueeze(1) - mask_windows_1.unsqueeze(2) attn_mask_1 = attn_mask_1.masked_fill(attn_mask_1 != 0, float(-100.0)).masked_fill(attn_mask_1 == 0, float(0.0)) return attn_mask_0, attn_mask_1 def forward(self, x, H, W): """ Input: x: (B, H*W, C), x_size: (H, W) Output: x: (B, H*W, C) """ B, L, C = x.shape assert L == H * W, "flatten img_tokens has wrong size" qkv = self.qkv(x).reshape(B, -1, 3, C).permute(2, 0, 1, 3) # 3, B, HW, C # v without partition v = qkv[2].transpose(-2,-1).contiguous().view(B, C, H, W) max_split_size = max(self.split_size[0], self.split_size[1]) pad_l = pad_t = 0 pad_r = (max_split_size - W % max_split_size) % max_split_size pad_b = (max_split_size - H % max_split_size) % max_split_size qkv = qkv.reshape(3*B, H, W, C).permute(0, 3, 1, 2) # 3B C H W qkv = F.pad(qkv, (pad_l, pad_r, pad_t, pad_b)).reshape(3, B, C, -1).transpose(-2, -1) # l r t b _H = pad_b + H _W = pad_r + W _L = _H * _W if (self.rs_id % 2 == 0 and self.idx > 0 and (self.idx - 2) % 4 == 0) or (self.rs_id % 2 != 0 and self.idx % 4 == 0): qkv = qkv.view(3, B, _H, _W, C) # H-Shift qkv_0 = torch.roll(qkv[:,:,:,:,:C//2], shifts=(-self.shift_size[0], -self.shift_size[1]), dims=(2, 3)) qkv_0 = qkv_0.view(3, B, _L, C//2) # V-Shift qkv_1 = torch.roll(qkv[:,:,:,:,C//2:], shifts=(-self.shift_size[1], -self.shift_size[0]), dims=(2, 3)) qkv_1 = qkv_1.view(3, B, _L, C//2) if self.patches_resolution != _H or self.patches_resolution != _W: mask_tmp = self.calculate_mask(_H, _W) # H-Rwin x1_shift = self.attns[0](qkv_0, _H, _W, mask=mask_tmp[0].to(x.device)) # V-Rwin x2_shift = self.attns[1](qkv_1, _H, _W, mask=mask_tmp[1].to(x.device)) else: # H-Rwin x1_shift = self.attns[0](qkv_0, _H, _W, mask=self.attn_mask_0) # V-Rwin x2_shift = self.attns[1](qkv_1, _H, _W, mask=self.attn_mask_1) x1 = torch.roll(x1_shift, shifts=(self.shift_size[0], self.shift_size[1]), dims=(1, 2)) x2 = torch.roll(x2_shift, shifts=(self.shift_size[1], self.shift_size[0]), dims=(1, 2)) x1 = x1[:, :H, :W, :].reshape(B, L, C//2) x2 = x2[:, :H, :W, :].reshape(B, L, C//2) # Concat attened_x = torch.cat([x1,x2], dim=2) else: # V-Rwin x1 = self.attns[0](qkv[:,:,:,:C//2], _H, _W)[:, :H, :W, :].reshape(B, L, C//2) # H-Rwin x2 = self.attns[1](qkv[:,:,:,C//2:], _H, _W)[:, :H, :W, :].reshape(B, L, C//2) # Concat attened_x = torch.cat([x1,x2], dim=2) # mix lcm = self.get_v(v) lcm = lcm.permute(0, 2, 3, 1).contiguous().view(B, L, C) x = attened_x + lcm x = self.proj(x) x = self.proj_drop(x) return x class RG_SA(nn.Module): """ Recursive-Generalization Self-Attention (RG-SA). Args: dim (int): Number of input channels. num_heads (int): Number of attention heads. qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0 proj_drop (float, optional): Dropout ratio of output. Default: 0.0 c_ratio (float): channel adjustment factor. """ def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0., c_ratio=0.5): super(RG_SA, self).__init__() assert dim % num_heads == 0, f"dim {dim} should be divided by num_heads {num_heads}." self.num_heads = num_heads head_dim = dim // num_heads self.cr = int(dim * c_ratio) # scaled channel dimension # self.scale = qk_scale or head_dim ** -0.5 self.scale = qk_scale or (head_dim * c_ratio) ** -0.5 # RGM self.reduction1 = nn.Conv2d(dim, dim, kernel_size=4, stride=4, groups=dim) self.dwconv = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=1,groups=dim) self.conv = nn.Conv2d(dim, self.cr, kernel_size=1, stride=1) self.norm_act = nn.Sequential( nn.LayerNorm(self.cr), nn.GELU()) # CA self.q = nn.Linear(dim, self.cr, bias=qkv_bias) self.k = nn.Linear(self.cr, self.cr, bias=qkv_bias) self.v = nn.Linear(self.cr, dim, bias=qkv_bias) # CPE self.cpe = nn.Conv2d(dim, dim, kernel_size=3, stride=1, padding=1, groups=dim) self.proj = nn.Linear(dim, dim) self.attn_drop = nn.Dropout(attn_drop) self.proj_drop = nn.Dropout(proj_drop) def forward(self, x, H, W): B, N, C = x.shape _scale = 1 # reduction _x = x.permute(0, 2, 1).reshape(B, C, H, W).contiguous() if self.training: _time = max(int(math.log(H//4, 4)), int(math.log(W//4, 4))) else: _time = max(int(math.log(H//16, 4)), int(math.log(W//16, 4))) if _time < 2: _time = 2 # testing _time must equal or larger than training _time (2) _scale = 4 ** _time # Recursion xT for _ in range(_time): _x = self.reduction1(_x) _x = self.conv(self.dwconv(_x)).reshape(B, self.cr, -1).permute(0, 2, 1).contiguous() # shape=(B, N', C') _x = self.norm_act(_x) # q, k, v, where q_shape=(B, N, C'), k_shape=(B, N', C'), v_shape=(B, N', C) q = self.q(x).reshape(B, N, self.num_heads, int(self.cr / self.num_heads)).permute(0, 2, 1, 3) k = self.k(_x).reshape(B, -1, self.num_heads, int(self.cr / self.num_heads)).permute(0, 2, 1, 3) v = self.v(_x).reshape(B, -1, self.num_heads, int(C / self.num_heads)).permute(0, 2, 1, 3) # corss-attention attn = (q @ k.transpose(-2, -1)) * self.scale attn = attn.softmax(dim=-1) attn = self.attn_drop(attn) # CPE # v_shape=(B, H, N', C//H) v = v + self.cpe(v.transpose(1, 2).reshape(B, -1, C).transpose(1, 2).contiguous().view(B, C, H // _scale, W // _scale)).view(B, C, -1).view(B, self.num_heads, int(C / self.num_heads), -1).transpose(-1, -2) x = (attn @ v).transpose(1, 2).reshape(B, N, C) x = self.proj(x) x = self.proj_drop(x) return x class Block(nn.Module): def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0., drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm, idx=0, rs_id=0, split_size=[2,4], shift_size=[1,2], reso=64, c_ratio=0.5, layerscale_value=1e-4): super().__init__() self.norm1 = norm_layer(dim) if idx % 2 == 0: self.attn = L_SA( dim, split_size=split_size, shift_size=shift_size, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, drop=drop, idx=idx, reso=reso, rs_id=rs_id ) else: self.attn = RG_SA( dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop, c_ratio=c_ratio ) self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() mlp_hidden_dim = int(dim * mlp_ratio) self.mlp = MLP(in_features=dim, hidden_features=mlp_hidden_dim, out_features=dim, act_layer=act_layer) self.norm2 = norm_layer(dim) # HAI self.gamma = nn.Parameter(layerscale_value * torch.ones((dim)), requires_grad=True) def forward(self, x, x_size): H , W = x_size res = x x = x + self.drop_path(self.attn(self.norm1(x), H, W)) x = x + self.drop_path(self.mlp(self.norm2(x), H, W)) # HAI x = x + (res * self.gamma) return x class ResidualGroup(nn.Module): def __init__( self, dim, reso, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0., drop_paths=None, act_layer=nn.GELU, norm_layer=nn.LayerNorm, depth=2, use_chk=False, resi_connection='1conv', rs_id=0, split_size=[8,8], c_ratio = 0.5): super().__init__() self.use_chk = use_chk self.reso = reso self.blocks = nn.ModuleList([ Block( dim=dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, drop=drop, attn_drop=attn_drop, drop_path=drop_paths[i], act_layer=act_layer, norm_layer=norm_layer, idx = i, rs_id = rs_id, split_size = split_size, shift_size = [split_size[0]//2, split_size[1]//2], c_ratio = c_ratio, )for i in range(depth)]) if resi_connection == '1conv': self.conv = nn.Conv2d(dim, dim, 3, 1, 1) elif resi_connection == '3conv': self.conv = nn.Sequential( nn.Conv2d(dim, dim // 4, 3, 1, 1), nn.LeakyReLU(negative_slope=0.2, inplace=True), nn.Conv2d(dim // 4, dim // 4, 1, 1, 0), nn.LeakyReLU(negative_slope=0.2, inplace=True), nn.Conv2d(dim // 4, dim, 3, 1, 1)) def forward(self, x, x_size): """ Input: x: (B, H*W, C), x_size: (H, W) Output: x: (B, H*W, C) """ H, W = x_size res = x for blk in self.blocks: if self.use_chk: x = checkpoint.checkpoint(blk, x, x_size) else: x = blk(x, x_size) x = rearrange(x, "b (h w) c -> b c h w", h=H, w=W) x = self.conv(x) x = rearrange(x, "b c h w -> b (h w) c") x = res + x return x class Upsample(nn.Sequential): """Upsample module. Args: scale (int): Scale factor. Supported scales: 2^n and 3. num_feat (int): Channel number of intermediate features. """ def __init__(self, scale, num_feat): m = [] if (scale & (scale - 1)) == 0: # scale = 2^n for _ in range(int(math.log(scale, 2))): m.append(nn.Conv2d(num_feat, 4 * num_feat, 3, 1, 1)) m.append(nn.PixelShuffle(2)) elif scale == 3: m.append(nn.Conv2d(num_feat, 9 * num_feat, 3, 1, 1)) m.append(nn.PixelShuffle(3)) else: raise ValueError(f'scale {scale} is not supported. ' 'Supported scales: 2^n and 3.') super(Upsample, self).__init__(*m) class RGT(nn.Module): def __init__(self, img_size=64, in_chans=3, embed_dim=180, depth=[2,2,2,2], num_heads=[2,2,2,2], mlp_ratio=4., qkv_bias=True, qk_scale=None, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, act_layer=nn.GELU, norm_layer=nn.LayerNorm, use_chk=False, upscale=2, img_range=1., resi_connection='1conv', split_size=[8,8], c_ratio=0.5, **kwargs): super().__init__() num_in_ch = in_chans num_out_ch = in_chans num_feat = 64 self.img_range = img_range if in_chans == 3: rgb_mean = (0.4488, 0.4371, 0.4040) self.mean = torch.Tensor(rgb_mean).view(1, 3, 1, 1) else: self.mean = torch.zeros(1, 1, 1, 1) self.upscale = upscale # ------------------------- 1, Shallow Feature Extraction ------------------------- # self.conv_first = nn.Conv2d(num_in_ch, embed_dim, 3, 1, 1) # ------------------------- 2, Deep Feature Extraction ------------------------- # self.num_layers = len(depth) self.use_chk = use_chk self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models heads=num_heads self.before_RG = nn.Sequential( Rearrange('b c h w -> b (h w) c'), nn.LayerNorm(embed_dim) ) curr_dim = embed_dim dpr = [x.item() for x in torch.linspace(0, drop_path_rate, np.sum(depth))] # stochastic depth decay rule self.layers = nn.ModuleList() for i in range(self.num_layers): layer = ResidualGroup( dim=embed_dim, num_heads=heads[i], reso=img_size, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale, drop=drop_rate, attn_drop=attn_drop_rate, drop_paths=dpr[sum(depth[:i]):sum(depth[:i + 1])], act_layer=act_layer, norm_layer=norm_layer, depth=depth[i], use_chk=use_chk, resi_connection=resi_connection, rs_id=i, split_size = split_size, c_ratio = c_ratio ) self.layers.append(layer) self.norm = norm_layer(curr_dim) # build the last conv layer in deep feature extraction if resi_connection == '1conv': self.conv_after_body = nn.Conv2d(embed_dim, embed_dim, 3, 1, 1) elif resi_connection == '3conv': # to save parameters and memory self.conv_after_body = nn.Sequential( nn.Conv2d(embed_dim, embed_dim // 4, 3, 1, 1), nn.LeakyReLU(negative_slope=0.2, inplace=True), nn.Conv2d(embed_dim // 4, embed_dim // 4, 1, 1, 0), nn.LeakyReLU(negative_slope=0.2, inplace=True), nn.Conv2d(embed_dim // 4, embed_dim, 3, 1, 1)) # ------------------------- 3, Reconstruction ------------------------- # self.conv_before_upsample = nn.Sequential( nn.Conv2d(embed_dim, num_feat, 3, 1, 1), nn.LeakyReLU(inplace=True)) self.upsample = Upsample(upscale, num_feat) self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1) def forward_features(self, x): _, _, H, W = x.shape x_size = [H, W] x = self.before_RG(x) for layer in self.layers: x = layer(x, x_size) x = self.norm(x) x = rearrange(x, "b (h w) c -> b c h w", h=H, w=W) return x def forward(self, x): """ Input: x: (B, C, H, W) """ self.mean = self.mean.type_as(x) x = (x - self.mean) * self.img_range x = self.conv_first(x) x = self.conv_after_body(self.forward_features(x)) + x x = self.conv_before_upsample(x) x = self.conv_last(self.upsample(x)) x = x / self.img_range + self.mean return x def img2tensor(imgs, bgr2rgb=True, float32=True): """Numpy array to tensor. Args: imgs (list[ndarray] | ndarray): Input images. bgr2rgb (bool): Whether to change bgr to rgb. float32 (bool): Whether to change to float32. Returns: list[tensor] | tensor: Tensor images. If returned results only have one element, just return tensor. """ def _totensor(img, bgr2rgb, float32): if img.shape[2] == 3 and bgr2rgb: if img.dtype == 'float64': img = img.astype('float32') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = torch.from_numpy(img.transpose(2, 0, 1)) if float32: img = img.float() return img if isinstance(imgs, list): return [_totensor(img, bgr2rgb, float32) for img in imgs] else: return _totensor(imgs, bgr2rgb, float32) def tensor2img(tensor, rgb2bgr=True, out_type=np.uint8, min_max=(0, 1)): """Convert torch Tensors into image numpy arrays. After clamping to [min, max], values will be normalized to [0, 1]. Args: tensor (Tensor or list[Tensor]): Accept shapes: 1) 4D mini-batch Tensor of shape (B x 3/1 x H x W); 2) 3D Tensor of shape (3/1 x H x W); 3) 2D Tensor of shape (H x W). Tensor channel should be in RGB order. rgb2bgr (bool): Whether to change rgb to bgr. out_type (numpy type): output types. If ``np.uint8``, transform outputs to uint8 type with range [0, 255]; otherwise, float type with range [0, 1]. Default: ``np.uint8``. min_max (tuple[int]): min and max values for clamp. Returns: (Tensor or list): 3D ndarray of shape (H x W x C) OR 2D ndarray of shape (H x W). The channel order is BGR. """ if not (torch.is_tensor(tensor) or (isinstance(tensor, list) and all(torch.is_tensor(t) for t in tensor))): raise TypeError(f'tensor or list of tensors expected, got {type(tensor)}') if torch.is_tensor(tensor): tensor = [tensor] result = [] for _tensor in tensor: _tensor = _tensor.squeeze(0).float().detach().cpu().clamp_(*min_max) _tensor = (_tensor - min_max[0]) / (min_max[1] - min_max[0]) n_dim = _tensor.dim() if n_dim == 4: img_np = make_grid(_tensor, nrow=int(math.sqrt(_tensor.size(0))), normalize=False).numpy() img_np = img_np.transpose(1, 2, 0) if rgb2bgr: img_np = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR) elif n_dim == 3: img_np = _tensor.numpy() img_np = img_np.transpose(1, 2, 0) if img_np.shape[2] == 1: # gray image img_np = np.squeeze(img_np, axis=2) else: if rgb2bgr: img_np = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR) elif n_dim == 2: img_np = _tensor.numpy() else: raise TypeError(f'Only support 4D, 3D or 2D tensor. But received with dimension: {n_dim}') if out_type == np.uint8: # Unlike MATLAB, numpy.unit8() WILL NOT round by default. img_np = (img_np * 255.0).round() img_np = img_np.astype(out_type) result.append(img_np) if len(result) == 1: result = result[0] return result def data_pre_preprocess(image): img = cv2.imread(image) img = img.astype(np.float32) / 255. img_lq = img2tensor(img, bgr2rgb=True, float32=True) return img_lq class RGTModel: def __init__(self, name:str, upscale:int, device:str, model_path:str): self.name = None self.upscale = None self. v = None self.device = None self.load_model(name, upscale, device, model_path) def load_model(self, name, upscale, device, model_path): if self.name == name and self.upscale == upscale and self.device == upscale: logger.debug(f"Nothing changed, continued use loaded model") return # init model by config if name == "RGT": num_heads = depth = [6,6,6,6,6,6,6,6] elif name == "RGT_S": num_heads = depth = [6,6,6,6,6,6] else: raise ValueError(f"model name wrong, should be RGT or RGT-S, but your input is: {name}") self.device = device self.upscale = upscale self.net_g = RGT( img_size=64, in_chans=3, embed_dim=180, depth=depth, num_heads=num_heads, mlp_ratio=2, qkv_bias=True, qk_scale=None, drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1, use_chk=False, upscale=upscale, img_range=1., split_size=[8,32] ) logger.debug(f"checkpoints path:{model_path}") checkpoints = torch.load(model_path) self.net_g.load_state_dict(checkpoints["params"]) self.net_g.to(torch.device(device)) self.net_g.eval() def __call__(self, image: Union[str, torch.Tensor], use_chop: bool = True): if isinstance(image, str): image = data_pre_preprocess(image) logger.debug(f"image: {image.shape}") image = image.to(self.device) if use_chop: _, C, h, w = image.size() split_token_h = h // 200 + 1 # number of horizontal cut sections split_token_w = w // 200 + 1 # number of vertical cut sections patch_size_tmp_h = split_token_h patch_size_tmp_w = split_token_w # padding mod_pad_h, mod_pad_w = 0, 0 if h % patch_size_tmp_h != 0: mod_pad_h = patch_size_tmp_h - h % patch_size_tmp_h if w % patch_size_tmp_w != 0: mod_pad_w = patch_size_tmp_w - w % patch_size_tmp_w img = image img = torch.cat([img, torch.flip(img, [2])], 2)[:, :, :h+mod_pad_h, :] img = torch.cat([img, torch.flip(img, [3])], 3)[:, :, :, :w+mod_pad_w] _, _, H, W = img.size() split_h = H // split_token_h # height of each partition split_w = W // split_token_w # width of each partition # overlapping shave_h = 16 shave_w = 16 scale = self.upscale ral = H // split_h row = W // split_w slices = [] # list of partition borders for i in range(ral): for j in range(row): if i == 0 and i == ral - 1: top = slice(i * split_h, (i + 1) * split_h) elif i == 0: top = slice(i*split_h, (i+1)*split_h+shave_h) elif i == ral - 1: top = slice(i*split_h-shave_h, (i+1)*split_h) else: top = slice(i*split_h-shave_h, (i+1)*split_h+shave_h) if j == 0 and j == row - 1: left = slice(j*split_w, (j+1)*split_w) elif j == 0: left = slice(j*split_w, (j+1)*split_w+shave_w) elif j == row - 1: left = slice(j*split_w-shave_w, (j+1)*split_w) else: left = slice(j*split_w-shave_w, (j+1)*split_w+shave_w) temp = (top, left) slices.append(temp) img_chops = [] # list of partitions for temp in slices: top, left = temp img_chops.append(img[..., top, left]) with torch.no_grad(): outputs = [] for chop in img_chops: out = self.net_g(chop) # image processing of each partition outputs.append(out) _img = torch.zeros(1, C, H * scale, W * scale) # merge for i in range(ral): for j in range(row): top = slice(i * split_h * scale, (i + 1) * split_h * scale) left = slice(j * split_w * scale, (j + 1) * split_w * scale) if i == 0: _top = slice(0, split_h * scale) else: _top = slice(shave_h * scale, (shave_h + split_h) * scale) if j == 0: _left = slice(0, split_w * scale) else: _left = slice(shave_w * scale, (shave_w + split_w) * scale) _img[..., top, left] = outputs[i * row + j][..., _top, _left] output = _img self.net_g.train() _, _, h, w = output.size() output = output[:, :, 0:h - mod_pad_h * scale, 0:w - mod_pad_w * scale] else: with torch.no_grad(): output = self.net_g(image) return output if __name__ == "__main__": model_path = "/root/code/ComfyUI/models/RGT/RGT/RGT_x2.pth" rgt_model = RGTModel(name="RGT", upscale=2, device="cuda:0", model_path=model_path) res = rgt_model(image="/root/code/RGT/datasets/single/test_4.png", use_chop=True) # index = 0 # for name in ["RGT", "RGT_S"]: # for upsacle in [2, 3, 4]: # rgt_model = RGTModel(name=name, upscale=upsacle, device="cuda:0") # res = rgt_model(image="/root/code/RGT/datasets/single/test_4.png", use_chop=True) # cv2.imwrite(f"/root/code/RGT/datasets/single/res_{index}.png", res) # index += 1