From bedd0280615f1ae10dbdc6c781979b40531705db Mon Sep 17 00:00:00 2001 From: 20kdc Date: Fri, 20 Oct 2023 06:07:15 +0100 Subject: [PATCH] waifu2x vgg7: testcase, auto-RGBA->RGB, function to grab pretrained models, training "fix" (#2117) --- examples/vgg7.py | 16 ++++++++-------- examples/vgg7_helpers/waifu2x.py | 19 ++++++++++++++++++- test/models/test_waifu2x.py | 25 +++++++++++++++++++++++++ test/models/waifu2x/input.png | Bin 0 -> 7321 bytes test/models/waifu2x/output.png | Bin 0 -> 14906 bytes 5 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 test/models/test_waifu2x.py create mode 100644 test/models/waifu2x/input.png create mode 100644 test/models/waifu2x/output.png diff --git a/examples/vgg7.py b/examples/vgg7.py index 0cac968c..a4a5835e 100644 --- a/examples/vgg7.py +++ b/examples/vgg7.py @@ -26,23 +26,23 @@ def set_sample_count(samples_dir, sc): file.write(str(sc) + "\n") if len(sys.argv) < 2: - print("python3 -m examples.vgg7 import MODELJSON MODELDIR") + print("python3 -m examples.vgg7 import MODELJSON MODEL") print(" imports a waifu2x JSON vgg_7 model, i.e. waifu2x/models/vgg_7/art/scale2.0x_model.json") print(" into a safetensors file") print(" weight tensors are ordered in tinygrad/ncnn form, as so: (outC,inC,H,W)") print(" *this format is used by most other commands in this program*") - print("python3 -m examples.vgg7 import_kinne MODELDIR MODEL_SAFETENSORS") + print("python3 -m examples.vgg7 import_kinne MODEL_KINNE MODEL_SAFETENSORS") print(" imports a model in 'KINNE' format (raw floats: used by older versions of this example) into safetensors") - print("python3 -m examples.vgg7 execute MODELDIR IMG_IN IMG_OUT") + print("python3 -m examples.vgg7 execute MODEL IMG_IN IMG_OUT") print(" given an already-nearest-neighbour-scaled image, runs vgg7 on it") print(" output image has 7 pixels removed on all edges") print(" do not run on large images, will have *hilarious* RAM use") - print("python3 -m examples.vgg7 execute_full MODELDIR IMG_IN IMG_OUT") + print("python3 -m examples.vgg7 execute_full MODEL IMG_IN IMG_OUT") print(" does the 'whole thing' (padding, tiling)") print(" safe for large images, etc.") - print("python3 -m examples.vgg7 new MODELDIR") + print("python3 -m examples.vgg7 new MODEL") print(" creates a new model (experimental)") - print("python3 -m examples.vgg7 train MODELDIR SAMPLES_DIR ROUNDS ROUNDS_SAVE") + print("python3 -m examples.vgg7 train MODEL SAMPLES_DIR ROUNDS ROUNDS_SAVE") print(" trains a model (experimental)") print(" (how experimental? well, every time I tried it, it flooded w/ NaNs)") print(" note: ROUNDS < 0 means 'forever'. ROUNDS_SAVE <= 0 is not a good idea.") @@ -130,7 +130,7 @@ elif cmd == "train": # This is used to try and get the network to focus on "interesting" samples, # which works nicely with the microsample system. sample_probs = None - sample_probs_path = model + "/sample_probs.bin" + sample_probs_path = model + "_sample_probs.bin" try: # try to read... sample_probs = numpy.fromfile(sample_probs_path, " numpy.ndarray: """ # file na = numpy.array(Image.open(path)) + if na.shape[2] == 4: + # RGBA -> RGB (covers opaque images with alpha channels) + na = na[:,:,0:3] # fix shape na = numpy.moveaxis(na, [2,0,1], [0,1,2]) # shape is now (3,h,w), add 1 @@ -113,6 +118,19 @@ class Vgg7: def get_parameters(self) -> list: return self.conv1.get_parameters() + self.conv2.get_parameters() + self.conv3.get_parameters() + self.conv4.get_parameters() + self.conv5.get_parameters() + self.conv6.get_parameters() + self.conv7.get_parameters() + def load_from_pretrained(self, intent = "art", subtype = "scale2.0x"): + """ + Downloads a nagadomi/waifu2x JSON weight file and loads it. + """ + fn = Path(__file__).parents[2] / ("weights/vgg_7_" + intent + "_" + subtype + "_model.json") + download_file("https://github.com/nagadomi/waifu2x/raw/master/models/vgg_7/" + intent + "/" + subtype + "_model.json", fn) + + import json + with open(fn, "rb") as f: + data = json.load(f) + + self.load_waifu2x_json(data) + def load_waifu2x_json(self, data: list): """ Loads weights from one of the waifu2x JSON files, i.e. waifu2x/models/vgg_7/art/noise0_model.json @@ -126,7 +144,6 @@ class Vgg7: self.conv6.load_waifu2x_json(data[5]) self.conv7.load_waifu2x_json(data[6]) - def forward_tiled(self, image: numpy.ndarray, tile_size: int) -> numpy.ndarray: """ Given an ndarray image as loaded by image_load (NOT a tensor), scales it, pads it, splits it up, forwards the pieces, and reconstitutes it. diff --git a/test/models/test_waifu2x.py b/test/models/test_waifu2x.py new file mode 100644 index 00000000..0b34ae03 --- /dev/null +++ b/test/models/test_waifu2x.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +import pathlib +import unittest +import numpy as np +from tinygrad.tensor import Tensor +from tinygrad.ops import Device + +class TestVGG7(unittest.TestCase): + def test_vgg7(self): + from examples.vgg7_helpers.waifu2x import Vgg7, image_load + + # Create in tinygrad + Tensor.manual_seed(1337) + mdl = Vgg7() + mdl.load_from_pretrained() + + # Scale up an image + test_x = image_load(pathlib.Path(__file__).parent / 'waifu2x/input.png') + test_y = image_load(pathlib.Path(__file__).parent / 'waifu2x/output.png') + scaled = mdl.forward_tiled(test_x, 156) + scaled = np.fmax(0, np.fmin(1, scaled)) + np.testing.assert_allclose(scaled, test_y, atol=5e-3, rtol=5e-3) + +if __name__ == '__main__': + unittest.main() diff --git a/test/models/waifu2x/input.png b/test/models/waifu2x/input.png new file mode 100644 index 0000000000000000000000000000000000000000..9ae415a9531a21ecc587b503ecc2e893386bf242 GIT binary patch literal 7321 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 literal 0 HcmV?d00001 diff --git a/test/models/waifu2x/output.png b/test/models/waifu2x/output.png new file mode 100644 index 0000000000000000000000000000000000000000..b105a2e2cf668f6a055ec1a560dea68f6dcd1582 GIT binary patch literal 14906 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