scunet_model.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. import sys
  2. import PIL.Image
  3. import numpy as np
  4. import torch
  5. from tqdm import tqdm
  6. import modules.upscaler
  7. from modules import devices, modelloader, script_callbacks, errors
  8. from modules.shared import opts
  9. class UpscalerScuNET(modules.upscaler.Upscaler):
  10. def __init__(self, dirname):
  11. self.name = "ScuNET"
  12. self.model_name = "ScuNET GAN"
  13. self.model_name2 = "ScuNET PSNR"
  14. self.model_url = "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_gan.pth"
  15. self.model_url2 = "https://github.com/cszn/KAIR/releases/download/v1.0/scunet_color_real_psnr.pth"
  16. self.user_path = dirname
  17. super().__init__()
  18. model_paths = self.find_models(ext_filter=[".pth"])
  19. scalers = []
  20. add_model2 = True
  21. for file in model_paths:
  22. if file.startswith("http"):
  23. name = self.model_name
  24. else:
  25. name = modelloader.friendly_name(file)
  26. if name == self.model_name2 or file == self.model_url2:
  27. add_model2 = False
  28. try:
  29. scaler_data = modules.upscaler.UpscalerData(name, file, self, 4)
  30. scalers.append(scaler_data)
  31. except Exception:
  32. errors.report(f"Error loading ScuNET model: {file}", exc_info=True)
  33. if add_model2:
  34. scaler_data2 = modules.upscaler.UpscalerData(self.model_name2, self.model_url2, self)
  35. scalers.append(scaler_data2)
  36. self.scalers = scalers
  37. @staticmethod
  38. @torch.no_grad()
  39. def tiled_inference(img, model):
  40. # test the image tile by tile
  41. h, w = img.shape[2:]
  42. tile = opts.SCUNET_tile
  43. tile_overlap = opts.SCUNET_tile_overlap
  44. if tile == 0:
  45. return model(img)
  46. device = devices.get_device_for('scunet')
  47. assert tile % 8 == 0, "tile size should be a multiple of window_size"
  48. sf = 1
  49. stride = tile - tile_overlap
  50. h_idx_list = list(range(0, h - tile, stride)) + [h - tile]
  51. w_idx_list = list(range(0, w - tile, stride)) + [w - tile]
  52. E = torch.zeros(1, 3, h * sf, w * sf, dtype=img.dtype, device=device)
  53. W = torch.zeros_like(E, dtype=devices.dtype, device=device)
  54. with tqdm(total=len(h_idx_list) * len(w_idx_list), desc="ScuNET tiles") as pbar:
  55. for h_idx in h_idx_list:
  56. for w_idx in w_idx_list:
  57. in_patch = img[..., h_idx: h_idx + tile, w_idx: w_idx + tile]
  58. out_patch = model(in_patch)
  59. out_patch_mask = torch.ones_like(out_patch)
  60. E[
  61. ..., h_idx * sf: (h_idx + tile) * sf, w_idx * sf: (w_idx + tile) * sf
  62. ].add_(out_patch)
  63. W[
  64. ..., h_idx * sf: (h_idx + tile) * sf, w_idx * sf: (w_idx + tile) * sf
  65. ].add_(out_patch_mask)
  66. pbar.update(1)
  67. output = E.div_(W)
  68. return output
  69. def do_upscale(self, img: PIL.Image.Image, selected_file):
  70. devices.torch_gc()
  71. try:
  72. model = self.load_model(selected_file)
  73. except Exception as e:
  74. print(f"ScuNET: Unable to load model from {selected_file}: {e}", file=sys.stderr)
  75. return img
  76. device = devices.get_device_for('scunet')
  77. tile = opts.SCUNET_tile
  78. h, w = img.height, img.width
  79. np_img = np.array(img)
  80. np_img = np_img[:, :, ::-1] # RGB to BGR
  81. np_img = np_img.transpose((2, 0, 1)) / 255 # HWC to CHW
  82. torch_img = torch.from_numpy(np_img).float().unsqueeze(0).to(device) # type: ignore
  83. if tile > h or tile > w:
  84. _img = torch.zeros(1, 3, max(h, tile), max(w, tile), dtype=torch_img.dtype, device=torch_img.device)
  85. _img[:, :, :h, :w] = torch_img # pad image
  86. torch_img = _img
  87. torch_output = self.tiled_inference(torch_img, model).squeeze(0)
  88. torch_output = torch_output[:, :h * 1, :w * 1] # remove padding, if any
  89. np_output: np.ndarray = torch_output.float().cpu().clamp_(0, 1).numpy()
  90. del torch_img, torch_output
  91. devices.torch_gc()
  92. output = np_output.transpose((1, 2, 0)) # CHW to HWC
  93. output = output[:, :, ::-1] # BGR to RGB
  94. return PIL.Image.fromarray((output * 255).astype(np.uint8))
  95. def load_model(self, path: str):
  96. device = devices.get_device_for('scunet')
  97. if path.startswith("http"):
  98. # TODO: this doesn't use `path` at all?
  99. filename = modelloader.load_file_from_url(self.model_url, model_dir=self.model_download_path, file_name=f"{self.name}.pth")
  100. else:
  101. filename = path
  102. return modelloader.load_spandrel_model(filename, device=device, expected_architecture='SCUNet')
  103. def on_ui_settings():
  104. import gradio as gr
  105. from modules import shared
  106. shared.opts.add_option("SCUNET_tile", shared.OptionInfo(256, "Tile size for SCUNET upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}, section=('upscaling', "Upscaling")).info("0 = no tiling"))
  107. shared.opts.add_option("SCUNET_tile_overlap", shared.OptionInfo(8, "Tile overlap for SCUNET upscalers.", gr.Slider, {"minimum": 0, "maximum": 64, "step": 1}, section=('upscaling', "Upscaling")).info("Low values = visible seam"))
  108. script_callbacks.on_ui_settings(on_ui_settings)