191 lines
5.3 KiB
Python
191 lines
5.3 KiB
Python
import os
|
|
import math
|
|
from typing import Generator
|
|
|
|
from torch import Tensor
|
|
import numpy as np
|
|
from PIL import Image
|
|
|
|
from modules import shared
|
|
|
|
from scripts.dumpunet import layerinfo
|
|
from scripts.dumpunet.features.featureinfo import FeatureInfo, Features, MultiImageFeatures
|
|
from scripts.dumpunet.report import message as E
|
|
|
|
def feature_diff(
|
|
features1: MultiImageFeatures,
|
|
features2: MultiImageFeatures
|
|
) -> Generator[tuple[int,int,str,Tensor],None,None]:
|
|
# features1 and features2 must be have same keys...
|
|
for img_idx in sorted(features1.keys()):
|
|
assert img_idx in features1
|
|
assert img_idx in features2
|
|
|
|
fs1 = features1[img_idx]
|
|
fs2 = features2[img_idx]
|
|
|
|
for step in sorted(fs1.keys()):
|
|
assert step in fs1
|
|
assert step in fs2
|
|
|
|
f1 = fs1[step]
|
|
f2 = fs2[step]
|
|
|
|
for layer in f1.layers():
|
|
assert layer in f1
|
|
assert layer in f2
|
|
|
|
l1, l2 = f1[layer], f2[layer]
|
|
a, b = l1.output, l2.output
|
|
assert a.size() == b.size()
|
|
assert len(a.size()) == 3
|
|
c = (b - a).abs()
|
|
|
|
yield img_idx, step, layer, c
|
|
|
|
def feature_to_grid_images(
|
|
feature: FeatureInfo,
|
|
layer: str,
|
|
width: int,
|
|
height: int,
|
|
color: bool
|
|
):
|
|
return tensor_to_grid_images(feature.output, layer, width, height, color)
|
|
|
|
def tensor_to_grid_images(
|
|
tensor: Tensor,
|
|
layer: str,
|
|
width: int,
|
|
height: int,
|
|
color: bool
|
|
):
|
|
assert len(tensor.size()) == 3
|
|
|
|
grid_x, grid_y = _get_grid_num(layer, width, height)
|
|
canvases = tensor_to_image(tensor, grid_x, grid_y, color)
|
|
|
|
return canvases
|
|
|
|
|
|
def save_features(
|
|
feature: FeatureInfo,
|
|
save_dir: str,
|
|
basename: str
|
|
):
|
|
save_tensor(feature.output, save_dir, basename)
|
|
|
|
def tensor_to_image(
|
|
tensor: Tensor,
|
|
grid_x: int,
|
|
grid_y: int,
|
|
color: bool,
|
|
):
|
|
# Regardless of wheather --opt-channelslast is enabled or not,
|
|
# feature.size() seems to return (batch, ch, h, w).
|
|
# Is this intended behaviour???
|
|
|
|
assert len(tensor.size()) == 3
|
|
|
|
max_ch, ih, iw = tensor.size()
|
|
width = (grid_x * (iw + 1) - 1)
|
|
height = (grid_y * (ih + 1) - 1)
|
|
|
|
def each_slice(it: range, n: int):
|
|
cur = []
|
|
for x in it:
|
|
cur.append(x)
|
|
if n == len(cur):
|
|
yield cur
|
|
cur = []
|
|
if 0 < len(cur):
|
|
yield cur
|
|
|
|
canvases: list[Image.Image] = []
|
|
color_format = "RGB" if color else "L"
|
|
|
|
for chs in each_slice(range(max_ch), grid_x * grid_y):
|
|
chs = list(chs)
|
|
|
|
canvas = Image.new(color_format, (width, height), color=0)
|
|
for iy in range(grid_y):
|
|
if len(chs) == 0:
|
|
break
|
|
|
|
for ix in range(grid_x):
|
|
if shared.state.interrupted:
|
|
break
|
|
|
|
if len(chs) == 0:
|
|
break
|
|
|
|
ch = chs.pop(0)
|
|
array = tensor[ch].cpu().numpy().astype(np.float32)
|
|
|
|
# create image
|
|
x = (iw+1) * ix
|
|
y = (ih+1) * iy
|
|
image = _tensor_to_image(array, color)
|
|
canvas.paste(Image.fromarray(image, color_format), (x, y))
|
|
|
|
canvases.append(canvas)
|
|
return canvases
|
|
|
|
def save_tensor(
|
|
tensor: Tensor,
|
|
save_dir: str,
|
|
basename: str
|
|
):
|
|
assert len(tensor.size()) == 3
|
|
for ch, t in enumerate(tensor):
|
|
filename = basename.format(ch=ch)
|
|
binpath = os.path.join(save_dir, filename + ".bin")
|
|
with open(binpath, "wb") as io:
|
|
array = t.cpu().numpy().astype(np.float32)
|
|
io.write(bytearray(array))
|
|
|
|
|
|
def _tensor_to_image(array: np.ndarray, color: bool):
|
|
# array := (-∞, ∞)
|
|
|
|
if color:
|
|
def colorize(v: float):
|
|
# v = -1 .. 1 を
|
|
# v < 0 のとき青 (0, 0, 1)
|
|
# v > 0 のとき赤 (1 ,0, 0)
|
|
# にする
|
|
rgb = (v if v > 0.0 else 0.0, 0.0, -v if v < 0.0 else 0.0)
|
|
return rgb
|
|
colorize2 = np.vectorize(colorize, otypes=[np.float32, np.float32, np.float32])
|
|
rgb = colorize2(np.clip(array, -1.0, 1.0))
|
|
return np.clip((np.dstack(rgb) * 256), 0, 255).astype(np.uint8)
|
|
|
|
else:
|
|
return np.clip(np.abs(array) * 256, 0, 255).astype(np.uint8)
|
|
|
|
def _get_grid_num(layer: str, width: int, height: int):
|
|
assert layer is not None and layer != "", E("<Layers> must not be empty.")
|
|
assert layer in layerinfo.Settings, E(f"Invalid <Layers> value: {layer}.")
|
|
_, (ch, mh, mw) = layerinfo.Settings[layer]
|
|
iw = math.ceil(width / 64)
|
|
ih = math.ceil(height / 64)
|
|
w = mw * iw
|
|
h = mh * ih
|
|
# w : width of a feature map
|
|
# h : height of a feature map
|
|
# ch: a number of a feature map
|
|
n = [w, h]
|
|
while ch % 2 == 0:
|
|
n[n[0]>n[1]] *= 2
|
|
ch //= 2
|
|
n[n[0]>n[1]] *= ch
|
|
if n[0] > n[1]:
|
|
while n[0] > n[1] * 2 and (n[0] // w) % 2 == 0:
|
|
n[0] //= 2
|
|
n[1] *= 2
|
|
else:
|
|
while n[0] * 2 < n[1] and (n[1] // h) % 2 == 0:
|
|
n[0] *= 2
|
|
n[1] //= 2
|
|
|
|
return [n[0] // w, n[1] // h]
|