InvokeAI 2.2.4.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. backend/__init__.py +0 -0
  2. backend/invoke_ai_web_server.py +1724 -0
  3. backend/modules/__init__.py +0 -0
  4. backend/modules/create_cmd_parser.py +55 -0
  5. backend/modules/get_canvas_generation_mode.py +117 -0
  6. backend/modules/parameters.py +71 -0
  7. backend/modules/parse_seed_weights.py +47 -0
  8. configs/INITIAL_MODELS.yaml +80 -0
  9. configs/models.yaml.example +29 -0
  10. configs/sd-concepts.txt +803 -0
  11. configs/stable-diffusion/v1-finetune.yaml +110 -0
  12. configs/stable-diffusion/v1-finetune_style.yaml +103 -0
  13. configs/stable-diffusion/v1-inference.yaml +79 -0
  14. configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
  15. configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
  16. docs/assets/invoke-web-server-1.png +0 -0
  17. docs/assets/invoke_ai_banner.png +0 -0
  18. invokeai-2.2.4.5.data/data/assets/caution.png +0 -0
  19. invokeai-2.2.4.5.data/data/configs/INITIAL_MODELS.yaml +80 -0
  20. invokeai-2.2.4.5.data/data/configs/models.yaml.example +29 -0
  21. invokeai-2.2.4.5.data/data/configs/sd-concepts.txt +803 -0
  22. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune.yaml +110 -0
  23. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-finetune_style.yaml +103 -0
  24. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inference.yaml +79 -0
  25. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-inpainting-inference.yaml +79 -0
  26. invokeai-2.2.4.5.data/data/configs/stable-diffusion/v1-m1-finetune.yaml +110 -0
  27. invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter-Bold.790c108b.ttf +0 -0
  28. invokeai-2.2.4.5.data/data/frontend/dist/assets/Inter.b9a8e5e2.ttf +0 -0
  29. invokeai-2.2.4.5.data/data/frontend/dist/assets/favicon.0d253ced.ico +0 -0
  30. invokeai-2.2.4.5.data/data/frontend/dist/assets/index-legacy-b741077f.js +48 -0
  31. invokeai-2.2.4.5.data/data/frontend/dist/assets/index.0dadf5d0.css +1 -0
  32. invokeai-2.2.4.5.data/data/frontend/dist/assets/index.21ff81eb.js +625 -0
  33. invokeai-2.2.4.5.data/data/frontend/dist/assets/logo.13003d72.png +0 -0
  34. invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills-legacy-dde3a68a.js +4 -0
  35. invokeai-2.2.4.5.data/data/frontend/dist/assets/polyfills.1ff60148.js +1 -0
  36. invokeai-2.2.4.5.data/data/frontend/dist/index.html +23 -0
  37. invokeai-2.2.4.5.data/data/scripts/configure_invokeai.py +797 -0
  38. invokeai-2.2.4.5.data/data/scripts/create_root.py +43 -0
  39. invokeai-2.2.4.5.data/data/scripts/dream.py +10 -0
  40. invokeai-2.2.4.5.data/data/scripts/images2prompt.py +30 -0
  41. invokeai-2.2.4.5.data/data/scripts/invoke.py +5 -0
  42. invokeai-2.2.4.5.data/data/scripts/legacy_api.py +681 -0
  43. invokeai-2.2.4.5.data/data/scripts/load_models.py +12 -0
  44. invokeai-2.2.4.5.data/data/scripts/merge_embeddings.py +124 -0
  45. invokeai-2.2.4.5.data/data/scripts/preload_models.py +12 -0
  46. invokeai-2.2.4.5.data/data/scripts/sd-metadata.py +22 -0
  47. invokeai-2.2.4.5.dist-info/LICENSE +21 -0
  48. invokeai-2.2.4.5.dist-info/LICENSE-ModelWeights.txt +294 -0
  49. invokeai-2.2.4.5.dist-info/METADATA +288 -0
  50. invokeai-2.2.4.5.dist-info/RECORD +169 -0
  51. invokeai-2.2.4.5.dist-info/WHEEL +5 -0
  52. invokeai-2.2.4.5.dist-info/entry_points.txt +4 -0
  53. invokeai-2.2.4.5.dist-info/top_level.txt +6 -0
  54. invokeai-2.2.4.5.dist-info/zip-safe +1 -0
  55. ldm/__init__.py +0 -0
  56. ldm/data/__init__.py +0 -0
  57. ldm/data/base.py +31 -0
  58. ldm/data/imagenet.py +466 -0
  59. ldm/data/lsun.py +126 -0
  60. ldm/data/personalized.py +202 -0
  61. ldm/data/personalized_style.py +169 -0
  62. ldm/generate.py +1159 -0
  63. ldm/invoke/CLI.py +960 -0
  64. ldm/invoke/__init__.py +3 -0
  65. ldm/invoke/args.py +1257 -0
  66. ldm/invoke/concepts_lib.py +169 -0
  67. ldm/invoke/conditioning.py +289 -0
  68. ldm/invoke/devices.py +27 -0
  69. ldm/invoke/generator/__init__.py +4 -0
  70. ldm/invoke/generator/base.py +335 -0
  71. ldm/invoke/generator/embiggen.py +501 -0
  72. ldm/invoke/generator/img2img.py +97 -0
  73. ldm/invoke/generator/inpaint.py +358 -0
  74. ldm/invoke/generator/omnibus.py +175 -0
  75. ldm/invoke/generator/txt2img.py +88 -0
  76. ldm/invoke/generator/txt2img2img.py +182 -0
  77. ldm/invoke/globals.py +31 -0
  78. ldm/invoke/image_util.py +70 -0
  79. ldm/invoke/log.py +66 -0
  80. ldm/invoke/model_cache.py +449 -0
  81. ldm/invoke/patchmatch.py +44 -0
  82. ldm/invoke/pngwriter.py +113 -0
  83. ldm/invoke/prompt_parser.py +655 -0
  84. ldm/invoke/readline.py +442 -0
  85. ldm/invoke/restoration/__init__.py +4 -0
  86. ldm/invoke/restoration/base.py +38 -0
  87. ldm/invoke/restoration/codeformer.py +103 -0
  88. ldm/invoke/restoration/codeformer_arch.py +276 -0
  89. ldm/invoke/restoration/gfpgan.py +84 -0
  90. ldm/invoke/restoration/outcrop.py +108 -0
  91. ldm/invoke/restoration/outpaint.py +92 -0
  92. ldm/invoke/restoration/realesrgan.py +90 -0
  93. ldm/invoke/restoration/vqgan_arch.py +435 -0
  94. ldm/invoke/seamless.py +30 -0
  95. ldm/invoke/server.py +282 -0
  96. ldm/invoke/server_legacy.py +246 -0
  97. ldm/invoke/txt2mask.py +136 -0
  98. ldm/lr_scheduler.py +143 -0
  99. ldm/models/__init__.py +0 -0
  100. ldm/models/autoencoder.py +596 -0
  101. ldm/models/diffusion/__init__.py +0 -0
  102. ldm/models/diffusion/classifier.py +355 -0
  103. ldm/models/diffusion/cross_attention_control.py +443 -0
  104. ldm/models/diffusion/cross_attention_map_saving.py +95 -0
  105. ldm/models/diffusion/ddim.py +111 -0
  106. ldm/models/diffusion/ddpm.py +2263 -0
  107. ldm/models/diffusion/ksampler.py +312 -0
  108. ldm/models/diffusion/plms.py +146 -0
  109. ldm/models/diffusion/sampler.py +450 -0
  110. ldm/models/diffusion/shared_invokeai_diffusion.py +244 -0
  111. ldm/modules/__init__.py +0 -0
  112. ldm/modules/attention.py +274 -0
  113. ldm/modules/diffusionmodules/__init__.py +0 -0
  114. ldm/modules/diffusionmodules/model.py +894 -0
  115. ldm/modules/diffusionmodules/openaimodel.py +1036 -0
  116. ldm/modules/diffusionmodules/util.py +314 -0
  117. ldm/modules/distributions/__init__.py +0 -0
  118. ldm/modules/distributions/distributions.py +104 -0
  119. ldm/modules/ema.py +88 -0
  120. ldm/modules/embedding_manager.py +413 -0
  121. ldm/modules/encoders/__init__.py +0 -0
  122. ldm/modules/encoders/modules.py +772 -0
  123. ldm/modules/image_degradation/__init__.py +6 -0
  124. ldm/modules/image_degradation/bsrgan.py +846 -0
  125. ldm/modules/image_degradation/bsrgan_light.py +751 -0
  126. ldm/modules/image_degradation/utils_image.py +1024 -0
  127. ldm/modules/losses/__init__.py +1 -0
  128. ldm/modules/losses/contperceptual.py +175 -0
  129. ldm/modules/losses/vqperceptual.py +240 -0
  130. ldm/modules/x_transformer.py +752 -0
  131. ldm/simplet2i.py +13 -0
  132. ldm/util.py +264 -0
  133. scripts/configure_invokeai.py +797 -0
  134. scripts/create_root.py +43 -0
  135. scripts/dream.py +10 -0
  136. scripts/images2prompt.py +30 -0
  137. scripts/invoke.py +5 -0
  138. scripts/legacy_api.py +681 -0
  139. scripts/load_models.py +12 -0
  140. scripts/merge_embeddings.py +124 -0
  141. scripts/orig_scripts/img2img.py +296 -0
  142. scripts/orig_scripts/inpaint.py +98 -0
  143. scripts/orig_scripts/knn2img.py +397 -0
  144. scripts/orig_scripts/sample_diffusion.py +313 -0
  145. scripts/orig_scripts/train_searcher.py +147 -0
  146. scripts/orig_scripts/txt2img.py +324 -0
  147. scripts/preload_models.py +12 -0
  148. scripts/sd-metadata.py +22 -0
  149. vendored/clip/clip/__init__.py +1 -0
  150. vendored/clip/clip/bpe_simple_vocab_16e6.txt.gz +0 -0
  151. vendored/clip/clip/clip.py +237 -0
  152. vendored/clip/clip/model.py +436 -0
  153. vendored/clip/clip/simple_tokenizer.py +132 -0
  154. vendored/clip/hubconf.py +42 -0
  155. vendored/clip/setup.py +21 -0
  156. vendored/clip/tests/test_consistency.py +25 -0
  157. vendored/clipseg/datasets/coco_wrapper.py +99 -0
  158. vendored/clipseg/datasets/pascal_zeroshot.py +60 -0
  159. vendored/clipseg/datasets/pfe_dataset.py +129 -0
  160. vendored/clipseg/datasets/phrasecut.py +380 -0
  161. vendored/clipseg/datasets/utils.py +68 -0
  162. vendored/clipseg/evaluation_utils.py +292 -0
  163. vendored/clipseg/general_utils.py +272 -0
  164. vendored/clipseg/metrics.py +271 -0
  165. vendored/clipseg/models/clipseg.py +538 -0
  166. vendored/clipseg/models/vitseg.py +286 -0
  167. vendored/clipseg/score.py +453 -0
  168. vendored/clipseg/setup.py +30 -0
  169. vendored/clipseg/training.py +266 -0
@@ -0,0 +1,1024 @@
1
+ import os
2
+ import math
3
+ import random
4
+ import numpy as np
5
+ import torch
6
+ import cv2
7
+ from torchvision.utils import make_grid
8
+ from datetime import datetime
9
+
10
+ # import matplotlib.pyplot as plt # TODO: check with Dominik, also bsrgan.py vs bsrgan_light.py
11
+
12
+
13
+ os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
14
+
15
+
16
+ """
17
+ # --------------------------------------------
18
+ # Kai Zhang (github: https://github.com/cszn)
19
+ # 03/Mar/2019
20
+ # --------------------------------------------
21
+ # https://github.com/twhui/SRGAN-pyTorch
22
+ # https://github.com/xinntao/BasicSR
23
+ # --------------------------------------------
24
+ """
25
+
26
+
27
+ IMG_EXTENSIONS = [
28
+ '.jpg',
29
+ '.JPG',
30
+ '.jpeg',
31
+ '.JPEG',
32
+ '.png',
33
+ '.PNG',
34
+ '.ppm',
35
+ '.PPM',
36
+ '.bmp',
37
+ '.BMP',
38
+ '.tif',
39
+ ]
40
+
41
+
42
+ def is_image_file(filename):
43
+ return any(filename.endswith(extension) for extension in IMG_EXTENSIONS)
44
+
45
+
46
+ def get_timestamp():
47
+ return datetime.now().strftime('%y%m%d-%H%M%S')
48
+
49
+
50
+ def imshow(x, title=None, cbar=False, figsize=None):
51
+ plt.figure(figsize=figsize)
52
+ plt.imshow(np.squeeze(x), interpolation='nearest', cmap='gray')
53
+ if title:
54
+ plt.title(title)
55
+ if cbar:
56
+ plt.colorbar()
57
+ plt.show()
58
+
59
+
60
+ def surf(Z, cmap='rainbow', figsize=None):
61
+ plt.figure(figsize=figsize)
62
+ ax3 = plt.axes(projection='3d')
63
+
64
+ w, h = Z.shape[:2]
65
+ xx = np.arange(0, w, 1)
66
+ yy = np.arange(0, h, 1)
67
+ X, Y = np.meshgrid(xx, yy)
68
+ ax3.plot_surface(X, Y, Z, cmap=cmap)
69
+ # ax3.contour(X,Y,Z, zdim='z',offset=-2,cmap=cmap)
70
+ plt.show()
71
+
72
+
73
+ """
74
+ # --------------------------------------------
75
+ # get image pathes
76
+ # --------------------------------------------
77
+ """
78
+
79
+
80
+ def get_image_paths(dataroot):
81
+ paths = None # return None if dataroot is None
82
+ if dataroot is not None:
83
+ paths = sorted(_get_paths_from_images(dataroot))
84
+ return paths
85
+
86
+
87
+ def _get_paths_from_images(path):
88
+ assert os.path.isdir(path), '{:s} is not a valid directory'.format(path)
89
+ images = []
90
+ for dirpath, _, fnames in sorted(os.walk(path)):
91
+ for fname in sorted(fnames):
92
+ if is_image_file(fname):
93
+ img_path = os.path.join(dirpath, fname)
94
+ images.append(img_path)
95
+ assert images, '{:s} has no valid image file'.format(path)
96
+ return images
97
+
98
+
99
+ """
100
+ # --------------------------------------------
101
+ # split large images into small images
102
+ # --------------------------------------------
103
+ """
104
+
105
+
106
+ def patches_from_image(img, p_size=512, p_overlap=64, p_max=800):
107
+ w, h = img.shape[:2]
108
+ patches = []
109
+ if w > p_max and h > p_max:
110
+ w1 = list(np.arange(0, w - p_size, p_size - p_overlap, dtype=np.int))
111
+ h1 = list(np.arange(0, h - p_size, p_size - p_overlap, dtype=np.int))
112
+ w1.append(w - p_size)
113
+ h1.append(h - p_size)
114
+ # print(w1)
115
+ # print(h1)
116
+ for i in w1:
117
+ for j in h1:
118
+ patches.append(img[i : i + p_size, j : j + p_size, :])
119
+ else:
120
+ patches.append(img)
121
+
122
+ return patches
123
+
124
+
125
+ def imssave(imgs, img_path):
126
+ """
127
+ imgs: list, N images of size WxHxC
128
+ """
129
+ img_name, ext = os.path.splitext(os.path.basename(img_path))
130
+
131
+ for i, img in enumerate(imgs):
132
+ if img.ndim == 3:
133
+ img = img[:, :, [2, 1, 0]]
134
+ new_path = os.path.join(
135
+ os.path.dirname(img_path),
136
+ img_name + str('_s{:04d}'.format(i)) + '.png',
137
+ )
138
+ cv2.imwrite(new_path, img)
139
+
140
+
141
+ def split_imageset(
142
+ original_dataroot,
143
+ taget_dataroot,
144
+ n_channels=3,
145
+ p_size=800,
146
+ p_overlap=96,
147
+ p_max=1000,
148
+ ):
149
+ """
150
+ split the large images from original_dataroot into small overlapped images with size (p_size)x(p_size),
151
+ and save them into taget_dataroot; only the images with larger size than (p_max)x(p_max)
152
+ will be splitted.
153
+ Args:
154
+ original_dataroot:
155
+ taget_dataroot:
156
+ p_size: size of small images
157
+ p_overlap: patch size in training is a good choice
158
+ p_max: images with smaller size than (p_max)x(p_max) keep unchanged.
159
+ """
160
+ paths = get_image_paths(original_dataroot)
161
+ for img_path in paths:
162
+ # img_name, ext = os.path.splitext(os.path.basename(img_path))
163
+ img = imread_uint(img_path, n_channels=n_channels)
164
+ patches = patches_from_image(img, p_size, p_overlap, p_max)
165
+ imssave(
166
+ patches, os.path.join(taget_dataroot, os.path.basename(img_path))
167
+ )
168
+ # if original_dataroot == taget_dataroot:
169
+ # del img_path
170
+
171
+
172
+ """
173
+ # --------------------------------------------
174
+ # makedir
175
+ # --------------------------------------------
176
+ """
177
+
178
+
179
+ def mkdir(path):
180
+ if not os.path.exists(path):
181
+ os.makedirs(path)
182
+
183
+
184
+ def mkdirs(paths):
185
+ if isinstance(paths, str):
186
+ mkdir(paths)
187
+ else:
188
+ for path in paths:
189
+ mkdir(path)
190
+
191
+
192
+ def mkdir_and_rename(path):
193
+ if os.path.exists(path):
194
+ new_name = path + '_archived_' + get_timestamp()
195
+ print('Path already exists. Rename it to [{:s}]'.format(new_name))
196
+ os.replace(path, new_name)
197
+ os.makedirs(path)
198
+
199
+
200
+ """
201
+ # --------------------------------------------
202
+ # read image from path
203
+ # opencv is fast, but read BGR numpy image
204
+ # --------------------------------------------
205
+ """
206
+
207
+
208
+ # --------------------------------------------
209
+ # get uint8 image of size HxWxn_channles (RGB)
210
+ # --------------------------------------------
211
+ def imread_uint(path, n_channels=3):
212
+ # input: path
213
+ # output: HxWx3(RGB or GGG), or HxWx1 (G)
214
+ if n_channels == 1:
215
+ img = cv2.imread(path, 0) # cv2.IMREAD_GRAYSCALE
216
+ img = np.expand_dims(img, axis=2) # HxWx1
217
+ elif n_channels == 3:
218
+ img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # BGR or G
219
+ if img.ndim == 2:
220
+ img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) # GGG
221
+ else:
222
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # RGB
223
+ return img
224
+
225
+
226
+ # --------------------------------------------
227
+ # matlab's imwrite
228
+ # --------------------------------------------
229
+ def imsave(img, img_path):
230
+ img = np.squeeze(img)
231
+ if img.ndim == 3:
232
+ img = img[:, :, [2, 1, 0]]
233
+ cv2.imwrite(img_path, img)
234
+
235
+
236
+ def imwrite(img, img_path):
237
+ img = np.squeeze(img)
238
+ if img.ndim == 3:
239
+ img = img[:, :, [2, 1, 0]]
240
+ cv2.imwrite(img_path, img)
241
+
242
+
243
+ # --------------------------------------------
244
+ # get single image of size HxWxn_channles (BGR)
245
+ # --------------------------------------------
246
+ def read_img(path):
247
+ # read image by cv2
248
+ # return: Numpy float32, HWC, BGR, [0,1]
249
+ img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # cv2.IMREAD_GRAYSCALE
250
+ img = img.astype(np.float32) / 255.0
251
+ if img.ndim == 2:
252
+ img = np.expand_dims(img, axis=2)
253
+ # some images have 4 channels
254
+ if img.shape[2] > 3:
255
+ img = img[:, :, :3]
256
+ return img
257
+
258
+
259
+ """
260
+ # --------------------------------------------
261
+ # image format conversion
262
+ # --------------------------------------------
263
+ # numpy(single) <---> numpy(unit)
264
+ # numpy(single) <---> tensor
265
+ # numpy(unit) <---> tensor
266
+ # --------------------------------------------
267
+ """
268
+
269
+
270
+ # --------------------------------------------
271
+ # numpy(single) [0, 1] <---> numpy(unit)
272
+ # --------------------------------------------
273
+
274
+
275
+ def uint2single(img):
276
+
277
+ return np.float32(img / 255.0)
278
+
279
+
280
+ def single2uint(img):
281
+
282
+ return np.uint8((img.clip(0, 1) * 255.0).round())
283
+
284
+
285
+ def uint162single(img):
286
+
287
+ return np.float32(img / 65535.0)
288
+
289
+
290
+ def single2uint16(img):
291
+
292
+ return np.uint16((img.clip(0, 1) * 65535.0).round())
293
+
294
+
295
+ # --------------------------------------------
296
+ # numpy(unit) (HxWxC or HxW) <---> tensor
297
+ # --------------------------------------------
298
+
299
+
300
+ # convert uint to 4-dimensional torch tensor
301
+ def uint2tensor4(img):
302
+ if img.ndim == 2:
303
+ img = np.expand_dims(img, axis=2)
304
+ return (
305
+ torch.from_numpy(np.ascontiguousarray(img))
306
+ .permute(2, 0, 1)
307
+ .float()
308
+ .div(255.0)
309
+ .unsqueeze(0)
310
+ )
311
+
312
+
313
+ # convert uint to 3-dimensional torch tensor
314
+ def uint2tensor3(img):
315
+ if img.ndim == 2:
316
+ img = np.expand_dims(img, axis=2)
317
+ return (
318
+ torch.from_numpy(np.ascontiguousarray(img))
319
+ .permute(2, 0, 1)
320
+ .float()
321
+ .div(255.0)
322
+ )
323
+
324
+
325
+ # convert 2/3/4-dimensional torch tensor to uint
326
+ def tensor2uint(img):
327
+ img = img.data.squeeze().float().clamp_(0, 1).cpu().numpy()
328
+ if img.ndim == 3:
329
+ img = np.transpose(img, (1, 2, 0))
330
+ return np.uint8((img * 255.0).round())
331
+
332
+
333
+ # --------------------------------------------
334
+ # numpy(single) (HxWxC) <---> tensor
335
+ # --------------------------------------------
336
+
337
+
338
+ # convert single (HxWxC) to 3-dimensional torch tensor
339
+ def single2tensor3(img):
340
+ return torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1).float()
341
+
342
+
343
+ # convert single (HxWxC) to 4-dimensional torch tensor
344
+ def single2tensor4(img):
345
+ return (
346
+ torch.from_numpy(np.ascontiguousarray(img))
347
+ .permute(2, 0, 1)
348
+ .float()
349
+ .unsqueeze(0)
350
+ )
351
+
352
+
353
+ # convert torch tensor to single
354
+ def tensor2single(img):
355
+ img = img.data.squeeze().float().cpu().numpy()
356
+ if img.ndim == 3:
357
+ img = np.transpose(img, (1, 2, 0))
358
+
359
+ return img
360
+
361
+
362
+ # convert torch tensor to single
363
+ def tensor2single3(img):
364
+ img = img.data.squeeze().float().cpu().numpy()
365
+ if img.ndim == 3:
366
+ img = np.transpose(img, (1, 2, 0))
367
+ elif img.ndim == 2:
368
+ img = np.expand_dims(img, axis=2)
369
+ return img
370
+
371
+
372
+ def single2tensor5(img):
373
+ return (
374
+ torch.from_numpy(np.ascontiguousarray(img))
375
+ .permute(2, 0, 1, 3)
376
+ .float()
377
+ .unsqueeze(0)
378
+ )
379
+
380
+
381
+ def single32tensor5(img):
382
+ return (
383
+ torch.from_numpy(np.ascontiguousarray(img))
384
+ .float()
385
+ .unsqueeze(0)
386
+ .unsqueeze(0)
387
+ )
388
+
389
+
390
+ def single42tensor4(img):
391
+ return (
392
+ torch.from_numpy(np.ascontiguousarray(img)).permute(2, 0, 1, 3).float()
393
+ )
394
+
395
+
396
+ # from skimage.io import imread, imsave
397
+ def tensor2img(tensor, out_type=np.uint8, min_max=(0, 1)):
398
+ """
399
+ Converts a torch Tensor into an image Numpy array of BGR channel order
400
+ Input: 4D(B,(3/1),H,W), 3D(C,H,W), or 2D(H,W), any range, RGB channel order
401
+ Output: 3D(H,W,C) or 2D(H,W), [0,255], np.uint8 (default)
402
+ """
403
+ tensor = (
404
+ tensor.squeeze().float().cpu().clamp_(*min_max)
405
+ ) # squeeze first, then clamp
406
+ tensor = (tensor - min_max[0]) / (
407
+ min_max[1] - min_max[0]
408
+ ) # to range [0,1]
409
+ n_dim = tensor.dim()
410
+ if n_dim == 4:
411
+ n_img = len(tensor)
412
+ img_np = make_grid(
413
+ tensor, nrow=int(math.sqrt(n_img)), normalize=False
414
+ ).numpy()
415
+ img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR
416
+ elif n_dim == 3:
417
+ img_np = tensor.numpy()
418
+ img_np = np.transpose(img_np[[2, 1, 0], :, :], (1, 2, 0)) # HWC, BGR
419
+ elif n_dim == 2:
420
+ img_np = tensor.numpy()
421
+ else:
422
+ raise TypeError(
423
+ 'Only support 4D, 3D and 2D tensor. But received with dimension: {:d}'.format(
424
+ n_dim
425
+ )
426
+ )
427
+ if out_type == np.uint8:
428
+ img_np = (img_np * 255.0).round()
429
+ # Important. Unlike matlab, numpy.unit8() WILL NOT round by default.
430
+ return img_np.astype(out_type)
431
+
432
+
433
+ """
434
+ # --------------------------------------------
435
+ # Augmentation, flipe and/or rotate
436
+ # --------------------------------------------
437
+ # The following two are enough.
438
+ # (1) augmet_img: numpy image of WxHxC or WxH
439
+ # (2) augment_img_tensor4: tensor image 1xCxWxH
440
+ # --------------------------------------------
441
+ """
442
+
443
+
444
+ def augment_img(img, mode=0):
445
+ """Kai Zhang (github: https://github.com/cszn)"""
446
+ if mode == 0:
447
+ return img
448
+ elif mode == 1:
449
+ return np.flipud(np.rot90(img))
450
+ elif mode == 2:
451
+ return np.flipud(img)
452
+ elif mode == 3:
453
+ return np.rot90(img, k=3)
454
+ elif mode == 4:
455
+ return np.flipud(np.rot90(img, k=2))
456
+ elif mode == 5:
457
+ return np.rot90(img)
458
+ elif mode == 6:
459
+ return np.rot90(img, k=2)
460
+ elif mode == 7:
461
+ return np.flipud(np.rot90(img, k=3))
462
+
463
+
464
+ def augment_img_tensor4(img, mode=0):
465
+ """Kai Zhang (github: https://github.com/cszn)"""
466
+ if mode == 0:
467
+ return img
468
+ elif mode == 1:
469
+ return img.rot90(1, [2, 3]).flip([2])
470
+ elif mode == 2:
471
+ return img.flip([2])
472
+ elif mode == 3:
473
+ return img.rot90(3, [2, 3])
474
+ elif mode == 4:
475
+ return img.rot90(2, [2, 3]).flip([2])
476
+ elif mode == 5:
477
+ return img.rot90(1, [2, 3])
478
+ elif mode == 6:
479
+ return img.rot90(2, [2, 3])
480
+ elif mode == 7:
481
+ return img.rot90(3, [2, 3]).flip([2])
482
+
483
+
484
+ def augment_img_tensor(img, mode=0):
485
+ """Kai Zhang (github: https://github.com/cszn)"""
486
+ img_size = img.size()
487
+ img_np = img.data.cpu().numpy()
488
+ if len(img_size) == 3:
489
+ img_np = np.transpose(img_np, (1, 2, 0))
490
+ elif len(img_size) == 4:
491
+ img_np = np.transpose(img_np, (2, 3, 1, 0))
492
+ img_np = augment_img(img_np, mode=mode)
493
+ img_tensor = torch.from_numpy(np.ascontiguousarray(img_np))
494
+ if len(img_size) == 3:
495
+ img_tensor = img_tensor.permute(2, 0, 1)
496
+ elif len(img_size) == 4:
497
+ img_tensor = img_tensor.permute(3, 2, 0, 1)
498
+
499
+ return img_tensor.type_as(img)
500
+
501
+
502
+ def augment_img_np3(img, mode=0):
503
+ if mode == 0:
504
+ return img
505
+ elif mode == 1:
506
+ return img.transpose(1, 0, 2)
507
+ elif mode == 2:
508
+ return img[::-1, :, :]
509
+ elif mode == 3:
510
+ img = img[::-1, :, :]
511
+ img = img.transpose(1, 0, 2)
512
+ return img
513
+ elif mode == 4:
514
+ return img[:, ::-1, :]
515
+ elif mode == 5:
516
+ img = img[:, ::-1, :]
517
+ img = img.transpose(1, 0, 2)
518
+ return img
519
+ elif mode == 6:
520
+ img = img[:, ::-1, :]
521
+ img = img[::-1, :, :]
522
+ return img
523
+ elif mode == 7:
524
+ img = img[:, ::-1, :]
525
+ img = img[::-1, :, :]
526
+ img = img.transpose(1, 0, 2)
527
+ return img
528
+
529
+
530
+ def augment_imgs(img_list, hflip=True, rot=True):
531
+ # horizontal flip OR rotate
532
+ hflip = hflip and random.random() < 0.5
533
+ vflip = rot and random.random() < 0.5
534
+ rot90 = rot and random.random() < 0.5
535
+
536
+ def _augment(img):
537
+ if hflip:
538
+ img = img[:, ::-1, :]
539
+ if vflip:
540
+ img = img[::-1, :, :]
541
+ if rot90:
542
+ img = img.transpose(1, 0, 2)
543
+ return img
544
+
545
+ return [_augment(img) for img in img_list]
546
+
547
+
548
+ """
549
+ # --------------------------------------------
550
+ # modcrop and shave
551
+ # --------------------------------------------
552
+ """
553
+
554
+
555
+ def modcrop(img_in, scale):
556
+ # img_in: Numpy, HWC or HW
557
+ img = np.copy(img_in)
558
+ if img.ndim == 2:
559
+ H, W = img.shape
560
+ H_r, W_r = H % scale, W % scale
561
+ img = img[: H - H_r, : W - W_r]
562
+ elif img.ndim == 3:
563
+ H, W, C = img.shape
564
+ H_r, W_r = H % scale, W % scale
565
+ img = img[: H - H_r, : W - W_r, :]
566
+ else:
567
+ raise ValueError('Wrong img ndim: [{:d}].'.format(img.ndim))
568
+ return img
569
+
570
+
571
+ def shave(img_in, border=0):
572
+ # img_in: Numpy, HWC or HW
573
+ img = np.copy(img_in)
574
+ h, w = img.shape[:2]
575
+ img = img[border : h - border, border : w - border]
576
+ return img
577
+
578
+
579
+ """
580
+ # --------------------------------------------
581
+ # image processing process on numpy image
582
+ # channel_convert(in_c, tar_type, img_list):
583
+ # rgb2ycbcr(img, only_y=True):
584
+ # bgr2ycbcr(img, only_y=True):
585
+ # ycbcr2rgb(img):
586
+ # --------------------------------------------
587
+ """
588
+
589
+
590
+ def rgb2ycbcr(img, only_y=True):
591
+ """same as matlab rgb2ycbcr
592
+ only_y: only return Y channel
593
+ Input:
594
+ uint8, [0, 255]
595
+ float, [0, 1]
596
+ """
597
+ in_img_type = img.dtype
598
+ img.astype(np.float32)
599
+ if in_img_type != np.uint8:
600
+ img *= 255.0
601
+ # convert
602
+ if only_y:
603
+ rlt = np.dot(img, [65.481, 128.553, 24.966]) / 255.0 + 16.0
604
+ else:
605
+ rlt = np.matmul(
606
+ img,
607
+ [
608
+ [65.481, -37.797, 112.0],
609
+ [128.553, -74.203, -93.786],
610
+ [24.966, 112.0, -18.214],
611
+ ],
612
+ ) / 255.0 + [16, 128, 128]
613
+ if in_img_type == np.uint8:
614
+ rlt = rlt.round()
615
+ else:
616
+ rlt /= 255.0
617
+ return rlt.astype(in_img_type)
618
+
619
+
620
+ def ycbcr2rgb(img):
621
+ """same as matlab ycbcr2rgb
622
+ Input:
623
+ uint8, [0, 255]
624
+ float, [0, 1]
625
+ """
626
+ in_img_type = img.dtype
627
+ img.astype(np.float32)
628
+ if in_img_type != np.uint8:
629
+ img *= 255.0
630
+ # convert
631
+ rlt = np.matmul(
632
+ img,
633
+ [
634
+ [0.00456621, 0.00456621, 0.00456621],
635
+ [0, -0.00153632, 0.00791071],
636
+ [0.00625893, -0.00318811, 0],
637
+ ],
638
+ ) * 255.0 + [-222.921, 135.576, -276.836]
639
+ if in_img_type == np.uint8:
640
+ rlt = rlt.round()
641
+ else:
642
+ rlt /= 255.0
643
+ return rlt.astype(in_img_type)
644
+
645
+
646
+ def bgr2ycbcr(img, only_y=True):
647
+ """bgr version of rgb2ycbcr
648
+ only_y: only return Y channel
649
+ Input:
650
+ uint8, [0, 255]
651
+ float, [0, 1]
652
+ """
653
+ in_img_type = img.dtype
654
+ img.astype(np.float32)
655
+ if in_img_type != np.uint8:
656
+ img *= 255.0
657
+ # convert
658
+ if only_y:
659
+ rlt = np.dot(img, [24.966, 128.553, 65.481]) / 255.0 + 16.0
660
+ else:
661
+ rlt = np.matmul(
662
+ img,
663
+ [
664
+ [24.966, 112.0, -18.214],
665
+ [128.553, -74.203, -93.786],
666
+ [65.481, -37.797, 112.0],
667
+ ],
668
+ ) / 255.0 + [16, 128, 128]
669
+ if in_img_type == np.uint8:
670
+ rlt = rlt.round()
671
+ else:
672
+ rlt /= 255.0
673
+ return rlt.astype(in_img_type)
674
+
675
+
676
+ def channel_convert(in_c, tar_type, img_list):
677
+ # conversion among BGR, gray and y
678
+ if in_c == 3 and tar_type == 'gray': # BGR to gray
679
+ gray_list = [cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for img in img_list]
680
+ return [np.expand_dims(img, axis=2) for img in gray_list]
681
+ elif in_c == 3 and tar_type == 'y': # BGR to y
682
+ y_list = [bgr2ycbcr(img, only_y=True) for img in img_list]
683
+ return [np.expand_dims(img, axis=2) for img in y_list]
684
+ elif in_c == 1 and tar_type == 'RGB': # gray/y to BGR
685
+ return [cv2.cvtColor(img, cv2.COLOR_GRAY2BGR) for img in img_list]
686
+ else:
687
+ return img_list
688
+
689
+
690
+ """
691
+ # --------------------------------------------
692
+ # metric, PSNR and SSIM
693
+ # --------------------------------------------
694
+ """
695
+
696
+
697
+ # --------------------------------------------
698
+ # PSNR
699
+ # --------------------------------------------
700
+ def calculate_psnr(img1, img2, border=0):
701
+ # img1 and img2 have range [0, 255]
702
+ # img1 = img1.squeeze()
703
+ # img2 = img2.squeeze()
704
+ if not img1.shape == img2.shape:
705
+ raise ValueError('Input images must have the same dimensions.')
706
+ h, w = img1.shape[:2]
707
+ img1 = img1[border : h - border, border : w - border]
708
+ img2 = img2[border : h - border, border : w - border]
709
+
710
+ img1 = img1.astype(np.float64)
711
+ img2 = img2.astype(np.float64)
712
+ mse = np.mean((img1 - img2) ** 2)
713
+ if mse == 0:
714
+ return float('inf')
715
+ return 20 * math.log10(255.0 / math.sqrt(mse))
716
+
717
+
718
+ # --------------------------------------------
719
+ # SSIM
720
+ # --------------------------------------------
721
+ def calculate_ssim(img1, img2, border=0):
722
+ """calculate SSIM
723
+ the same outputs as MATLAB's
724
+ img1, img2: [0, 255]
725
+ """
726
+ # img1 = img1.squeeze()
727
+ # img2 = img2.squeeze()
728
+ if not img1.shape == img2.shape:
729
+ raise ValueError('Input images must have the same dimensions.')
730
+ h, w = img1.shape[:2]
731
+ img1 = img1[border : h - border, border : w - border]
732
+ img2 = img2[border : h - border, border : w - border]
733
+
734
+ if img1.ndim == 2:
735
+ return ssim(img1, img2)
736
+ elif img1.ndim == 3:
737
+ if img1.shape[2] == 3:
738
+ ssims = []
739
+ for i in range(3):
740
+ ssims.append(ssim(img1[:, :, i], img2[:, :, i]))
741
+ return np.array(ssims).mean()
742
+ elif img1.shape[2] == 1:
743
+ return ssim(np.squeeze(img1), np.squeeze(img2))
744
+ else:
745
+ raise ValueError('Wrong input image dimensions.')
746
+
747
+
748
+ def ssim(img1, img2):
749
+ C1 = (0.01 * 255) ** 2
750
+ C2 = (0.03 * 255) ** 2
751
+
752
+ img1 = img1.astype(np.float64)
753
+ img2 = img2.astype(np.float64)
754
+ kernel = cv2.getGaussianKernel(11, 1.5)
755
+ window = np.outer(kernel, kernel.transpose())
756
+
757
+ mu1 = cv2.filter2D(img1, -1, window)[5:-5, 5:-5] # valid
758
+ mu2 = cv2.filter2D(img2, -1, window)[5:-5, 5:-5]
759
+ mu1_sq = mu1**2
760
+ mu2_sq = mu2**2
761
+ mu1_mu2 = mu1 * mu2
762
+ sigma1_sq = cv2.filter2D(img1**2, -1, window)[5:-5, 5:-5] - mu1_sq
763
+ sigma2_sq = cv2.filter2D(img2**2, -1, window)[5:-5, 5:-5] - mu2_sq
764
+ sigma12 = cv2.filter2D(img1 * img2, -1, window)[5:-5, 5:-5] - mu1_mu2
765
+
766
+ ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) / (
767
+ (mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2)
768
+ )
769
+ return ssim_map.mean()
770
+
771
+
772
+ """
773
+ # --------------------------------------------
774
+ # matlab's bicubic imresize (numpy and torch) [0, 1]
775
+ # --------------------------------------------
776
+ """
777
+
778
+
779
+ # matlab 'imresize' function, now only support 'bicubic'
780
+ def cubic(x):
781
+ absx = torch.abs(x)
782
+ absx2 = absx**2
783
+ absx3 = absx**3
784
+ return (1.5 * absx3 - 2.5 * absx2 + 1) * ((absx <= 1).type_as(absx)) + (
785
+ -0.5 * absx3 + 2.5 * absx2 - 4 * absx + 2
786
+ ) * (((absx > 1) * (absx <= 2)).type_as(absx))
787
+
788
+
789
+ def calculate_weights_indices(
790
+ in_length, out_length, scale, kernel, kernel_width, antialiasing
791
+ ):
792
+ if (scale < 1) and (antialiasing):
793
+ # Use a modified kernel to simultaneously interpolate and antialias- larger kernel width
794
+ kernel_width = kernel_width / scale
795
+
796
+ # Output-space coordinates
797
+ x = torch.linspace(1, out_length, out_length)
798
+
799
+ # Input-space coordinates. Calculate the inverse mapping such that 0.5
800
+ # in output space maps to 0.5 in input space, and 0.5+scale in output
801
+ # space maps to 1.5 in input space.
802
+ u = x / scale + 0.5 * (1 - 1 / scale)
803
+
804
+ # What is the left-most pixel that can be involved in the computation?
805
+ left = torch.floor(u - kernel_width / 2)
806
+
807
+ # What is the maximum number of pixels that can be involved in the
808
+ # computation? Note: it's OK to use an extra pixel here; if the
809
+ # corresponding weights are all zero, it will be eliminated at the end
810
+ # of this function.
811
+ P = math.ceil(kernel_width) + 2
812
+
813
+ # The indices of the input pixels involved in computing the k-th output
814
+ # pixel are in row k of the indices matrix.
815
+ indices = left.view(out_length, 1).expand(out_length, P) + torch.linspace(
816
+ 0, P - 1, P
817
+ ).view(1, P).expand(out_length, P)
818
+
819
+ # The weights used to compute the k-th output pixel are in row k of the
820
+ # weights matrix.
821
+ distance_to_center = u.view(out_length, 1).expand(out_length, P) - indices
822
+ # apply cubic kernel
823
+ if (scale < 1) and (antialiasing):
824
+ weights = scale * cubic(distance_to_center * scale)
825
+ else:
826
+ weights = cubic(distance_to_center)
827
+ # Normalize the weights matrix so that each row sums to 1.
828
+ weights_sum = torch.sum(weights, 1).view(out_length, 1)
829
+ weights = weights / weights_sum.expand(out_length, P)
830
+
831
+ # If a column in weights is all zero, get rid of it. only consider the first and last column.
832
+ weights_zero_tmp = torch.sum((weights == 0), 0)
833
+ if not math.isclose(weights_zero_tmp[0], 0, rel_tol=1e-6):
834
+ indices = indices.narrow(1, 1, P - 2)
835
+ weights = weights.narrow(1, 1, P - 2)
836
+ if not math.isclose(weights_zero_tmp[-1], 0, rel_tol=1e-6):
837
+ indices = indices.narrow(1, 0, P - 2)
838
+ weights = weights.narrow(1, 0, P - 2)
839
+ weights = weights.contiguous()
840
+ indices = indices.contiguous()
841
+ sym_len_s = -indices.min() + 1
842
+ sym_len_e = indices.max() - in_length
843
+ indices = indices + sym_len_s - 1
844
+ return weights, indices, int(sym_len_s), int(sym_len_e)
845
+
846
+
847
+ # --------------------------------------------
848
+ # imresize for tensor image [0, 1]
849
+ # --------------------------------------------
850
+ def imresize(img, scale, antialiasing=True):
851
+ # Now the scale should be the same for H and W
852
+ # input: img: pytorch tensor, CHW or HW [0,1]
853
+ # output: CHW or HW [0,1] w/o round
854
+ need_squeeze = True if img.dim() == 2 else False
855
+ if need_squeeze:
856
+ img.unsqueeze_(0)
857
+ in_C, in_H, in_W = img.size()
858
+ out_C, out_H, out_W = (
859
+ in_C,
860
+ math.ceil(in_H * scale),
861
+ math.ceil(in_W * scale),
862
+ )
863
+ kernel_width = 4
864
+ kernel = 'cubic'
865
+
866
+ # Return the desired dimension order for performing the resize. The
867
+ # strategy is to perform the resize first along the dimension with the
868
+ # smallest scale factor.
869
+ # Now we do not support this.
870
+
871
+ # get weights and indices
872
+ weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices(
873
+ in_H, out_H, scale, kernel, kernel_width, antialiasing
874
+ )
875
+ weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices(
876
+ in_W, out_W, scale, kernel, kernel_width, antialiasing
877
+ )
878
+ # process H dimension
879
+ # symmetric copying
880
+ img_aug = torch.FloatTensor(in_C, in_H + sym_len_Hs + sym_len_He, in_W)
881
+ img_aug.narrow(1, sym_len_Hs, in_H).copy_(img)
882
+
883
+ sym_patch = img[:, :sym_len_Hs, :]
884
+ inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
885
+ sym_patch_inv = sym_patch.index_select(1, inv_idx)
886
+ img_aug.narrow(1, 0, sym_len_Hs).copy_(sym_patch_inv)
887
+
888
+ sym_patch = img[:, -sym_len_He:, :]
889
+ inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
890
+ sym_patch_inv = sym_patch.index_select(1, inv_idx)
891
+ img_aug.narrow(1, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv)
892
+
893
+ out_1 = torch.FloatTensor(in_C, out_H, in_W)
894
+ kernel_width = weights_H.size(1)
895
+ for i in range(out_H):
896
+ idx = int(indices_H[i][0])
897
+ for j in range(out_C):
898
+ out_1[j, i, :] = (
899
+ img_aug[j, idx : idx + kernel_width, :]
900
+ .transpose(0, 1)
901
+ .mv(weights_H[i])
902
+ )
903
+
904
+ # process W dimension
905
+ # symmetric copying
906
+ out_1_aug = torch.FloatTensor(in_C, out_H, in_W + sym_len_Ws + sym_len_We)
907
+ out_1_aug.narrow(2, sym_len_Ws, in_W).copy_(out_1)
908
+
909
+ sym_patch = out_1[:, :, :sym_len_Ws]
910
+ inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long()
911
+ sym_patch_inv = sym_patch.index_select(2, inv_idx)
912
+ out_1_aug.narrow(2, 0, sym_len_Ws).copy_(sym_patch_inv)
913
+
914
+ sym_patch = out_1[:, :, -sym_len_We:]
915
+ inv_idx = torch.arange(sym_patch.size(2) - 1, -1, -1).long()
916
+ sym_patch_inv = sym_patch.index_select(2, inv_idx)
917
+ out_1_aug.narrow(2, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv)
918
+
919
+ out_2 = torch.FloatTensor(in_C, out_H, out_W)
920
+ kernel_width = weights_W.size(1)
921
+ for i in range(out_W):
922
+ idx = int(indices_W[i][0])
923
+ for j in range(out_C):
924
+ out_2[j, :, i] = out_1_aug[j, :, idx : idx + kernel_width].mv(
925
+ weights_W[i]
926
+ )
927
+ if need_squeeze:
928
+ out_2.squeeze_()
929
+ return out_2
930
+
931
+
932
+ # --------------------------------------------
933
+ # imresize for numpy image [0, 1]
934
+ # --------------------------------------------
935
+ def imresize_np(img, scale, antialiasing=True):
936
+ # Now the scale should be the same for H and W
937
+ # input: img: Numpy, HWC or HW [0,1]
938
+ # output: HWC or HW [0,1] w/o round
939
+ img = torch.from_numpy(img)
940
+ need_squeeze = True if img.dim() == 2 else False
941
+ if need_squeeze:
942
+ img.unsqueeze_(2)
943
+
944
+ in_H, in_W, in_C = img.size()
945
+ out_C, out_H, out_W = (
946
+ in_C,
947
+ math.ceil(in_H * scale),
948
+ math.ceil(in_W * scale),
949
+ )
950
+ kernel_width = 4
951
+ kernel = 'cubic'
952
+
953
+ # Return the desired dimension order for performing the resize. The
954
+ # strategy is to perform the resize first along the dimension with the
955
+ # smallest scale factor.
956
+ # Now we do not support this.
957
+
958
+ # get weights and indices
959
+ weights_H, indices_H, sym_len_Hs, sym_len_He = calculate_weights_indices(
960
+ in_H, out_H, scale, kernel, kernel_width, antialiasing
961
+ )
962
+ weights_W, indices_W, sym_len_Ws, sym_len_We = calculate_weights_indices(
963
+ in_W, out_W, scale, kernel, kernel_width, antialiasing
964
+ )
965
+ # process H dimension
966
+ # symmetric copying
967
+ img_aug = torch.FloatTensor(in_H + sym_len_Hs + sym_len_He, in_W, in_C)
968
+ img_aug.narrow(0, sym_len_Hs, in_H).copy_(img)
969
+
970
+ sym_patch = img[:sym_len_Hs, :, :]
971
+ inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long()
972
+ sym_patch_inv = sym_patch.index_select(0, inv_idx)
973
+ img_aug.narrow(0, 0, sym_len_Hs).copy_(sym_patch_inv)
974
+
975
+ sym_patch = img[-sym_len_He:, :, :]
976
+ inv_idx = torch.arange(sym_patch.size(0) - 1, -1, -1).long()
977
+ sym_patch_inv = sym_patch.index_select(0, inv_idx)
978
+ img_aug.narrow(0, sym_len_Hs + in_H, sym_len_He).copy_(sym_patch_inv)
979
+
980
+ out_1 = torch.FloatTensor(out_H, in_W, in_C)
981
+ kernel_width = weights_H.size(1)
982
+ for i in range(out_H):
983
+ idx = int(indices_H[i][0])
984
+ for j in range(out_C):
985
+ out_1[i, :, j] = (
986
+ img_aug[idx : idx + kernel_width, :, j]
987
+ .transpose(0, 1)
988
+ .mv(weights_H[i])
989
+ )
990
+
991
+ # process W dimension
992
+ # symmetric copying
993
+ out_1_aug = torch.FloatTensor(out_H, in_W + sym_len_Ws + sym_len_We, in_C)
994
+ out_1_aug.narrow(1, sym_len_Ws, in_W).copy_(out_1)
995
+
996
+ sym_patch = out_1[:, :sym_len_Ws, :]
997
+ inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
998
+ sym_patch_inv = sym_patch.index_select(1, inv_idx)
999
+ out_1_aug.narrow(1, 0, sym_len_Ws).copy_(sym_patch_inv)
1000
+
1001
+ sym_patch = out_1[:, -sym_len_We:, :]
1002
+ inv_idx = torch.arange(sym_patch.size(1) - 1, -1, -1).long()
1003
+ sym_patch_inv = sym_patch.index_select(1, inv_idx)
1004
+ out_1_aug.narrow(1, sym_len_Ws + in_W, sym_len_We).copy_(sym_patch_inv)
1005
+
1006
+ out_2 = torch.FloatTensor(out_H, out_W, in_C)
1007
+ kernel_width = weights_W.size(1)
1008
+ for i in range(out_W):
1009
+ idx = int(indices_W[i][0])
1010
+ for j in range(out_C):
1011
+ out_2[:, i, j] = out_1_aug[:, idx : idx + kernel_width, j].mv(
1012
+ weights_W[i]
1013
+ )
1014
+ if need_squeeze:
1015
+ out_2.squeeze_()
1016
+
1017
+ return out_2.numpy()
1018
+
1019
+
1020
+ if __name__ == '__main__':
1021
+ print('---')
1022
+ # img = imread_uint('test.bmp', 3)
1023
+ # img = uint2single(img)
1024
+ # img_bicubic = imresize_np(img, 1/4)