From a07698497d472f0cc5a8d98ec421bdb6ee0cca3b Mon Sep 17 00:00:00 2001 From: Jens Ahrensfeld Date: Tue, 2 Jun 2026 08:20:38 +0200 Subject: [PATCH] =?UTF-8?q?[refactor]=20SubImage=20=E2=86=92=20SubImageExt?= =?UTF-8?q?ract,=20add=20SubImageCombine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- cifar_test_sub_image.ipynb | 29 ++++++++--------------------- mnist_test_sub_image.ipynb | 4 ++-- src/image/sub_image.py | 31 ++++++++++++++++++++++++++++++- src/tests/test_faces_sub_image.py | 8 ++++---- src/tests/test_sub_image.py | 4 ++-- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/cifar_test_sub_image.ipynb b/cifar_test_sub_image.ipynb index 18c63cc..c431df5 100644 --- a/cifar_test_sub_image.ipynb +++ b/cifar_test_sub_image.ipynb @@ -33,7 +33,7 @@ "from rbm.entity import Entity, EntityParams, TrainingParams\n", "from rbm.matrix import Mat, np, rms_error_accu, sample_gaussian\n", "from rbm.torch import Optimizer\n", - "from image.sub_image import SubImage, normalize\n", + "from image.sub_image import SubImageExtract, SubImageExtractCombine, normalize\n", "import math\n", "import random" ], @@ -216,7 +216,7 @@ "mean_training_batch = np.reshape(np.repeat(np.mean(train_images, axis=1), 3072, axis=0), train_images.shape)\n", "var_training_batch = np.reshape(np.repeat(np.std(train_images, axis=1), 3072, axis=0), train_images.shape)\n", "train_images_norm = (train_images - mean_training_batch) / var_training_batch\n", - "sub_generator = SubImage(PATCH, PATCH, STRIDE, STRIDE)\n", + "sub_generator = SubImageExtract(PATCH, PATCH, STRIDE, STRIDE)\n", "train_subimages = sub_generator(np.reshape(train_images, (N, 3, 32, 32)), 3)\n", "print(train_subimages.shape)\n", "train_subimages = np.reshape(train_subimages, (train_subimages.shape[0], N_VIS))\n", @@ -387,12 +387,10 @@ } }, "source": [ - "import numpy\n", - "\n", - "# Reconstruct full 32x32 images by assembling reconstructed sub-images (overlap-averaged)\n", + "combiner = SubImageExtractCombine(PATCH, PATCH, STRIDE, STRIDE, 32, 32, n_ch=3)\n", + "n_images = n_side\n", "nx_steps = (32 - PATCH) // STRIDE + 1\n", "ny_steps = (32 - PATCH) // STRIDE + 1\n", - "n_images = n_side\n", "\n", "fig, axes = plt.subplots(n_images, 2, figsize=(6, n_images * 3))\n", "\n", @@ -400,21 +398,10 @@ " orig = np.reshape(train_images[img_idx], (3, 32, 32))\n", " orig_display = np.asnumpy((orig - np.min(orig)) / (np.max(orig) - np.min(orig)))\n", "\n", - " accum = numpy.zeros((3, 32, 32), dtype=numpy.float64)\n", - " count = numpy.zeros((1, 32, 32), dtype=numpy.float64)\n", - " for ix in range(nx_steps):\n", - " for iy in range(ny_steps):\n", - " sub_idx = img_idx * nx_steps * ny_steps + ix * ny_steps + iy\n", - " inp = train_subimages[sub_idx]\n", - " recon = model.backward(model.forward(inp))\n", - " arr = np.asnumpy(np.reshape(recon, (3, PATCH, PATCH)))\n", - " x0, y0 = ix * STRIDE, iy * STRIDE\n", - " accum[:, x0:x0+PATCH, y0:y0+PATCH] += arr\n", - " count[0, x0:x0+PATCH, y0:y0+PATCH] += 1.0\n", - "\n", - " recon_full = (accum / numpy.maximum(count, 1)).transpose(1, 2, 0)\n", - " lo, hi = recon_full.min(), recon_full.max()\n", - " recon_full = numpy.clip((recon_full - lo) / (hi - lo + 1e-8), 0, 1)\n", + " start = img_idx * nx_steps * ny_steps\n", + " patches = train_subimages[start : start + nx_steps * ny_steps]\n", + " recon_patches = np.array([model.backward(model.forward(p)) for p in patches])\n", + " recon_full = combiner(recon_patches)\n", "\n", " axes[img_idx, 0].imshow(orig_display.transpose(1, 2, 0))\n", " axes[img_idx, 0].axis('off')\n", diff --git a/mnist_test_sub_image.ipynb b/mnist_test_sub_image.ipynb index b693dd8..5ba2e94 100644 --- a/mnist_test_sub_image.ipynb +++ b/mnist_test_sub_image.ipynb @@ -27,7 +27,7 @@ "from rbm.entity import Entity, EntityParams, TrainingParams\n", "from rbm.matrix import Mat, np, rms_error_accu\n", "from rbm.torch import Optimizer\n", - "from image.sub_image import SubImage\n", + "from image.sub_image import SubImageExtract\n", "import math" ], "outputs": [], @@ -179,7 +179,7 @@ "source": [ "print(train_images.shape)\n", "image_indices = np.random.randint(0, N-1, n_side*n_side)\n", - "sub_generator = SubImage(8,8,4,4)\n", + "sub_generator = SubImageExtract(8,8,4,4)\n", "train_subimages = sub_generator(np.reshape(train_images, (N, 28,28)), 1)\n", "print(train_subimages.shape)\n", "train_subimages = np.reshape(train_subimages, (train_subimages.shape[0], 8*8))\n", diff --git a/src/image/sub_image.py b/src/image/sub_image.py index 9b382a9..f10f35c 100644 --- a/src/image/sub_image.py +++ b/src/image/sub_image.py @@ -1,3 +1,4 @@ +import numpy as _np from rbm.matrix import Mat, np def normalize(image: Mat): @@ -6,7 +7,7 @@ def normalize(image: Mat): std = np.where(std < 1e-8, np.ones_like(std), std) return (image - mean) / std -class SubImage: +class SubImageExtract: def __init__(self, sx: int, sy: int, px: int, py: int): self.sx = sx self.sy = sy @@ -48,6 +49,34 @@ class SubImage: return result +class SubImageCombine: + def __init__(self, sx: int, sy: int, px: int, py: int, image_h: int, image_w: int, n_ch: int = 3): + self.sx = sx + self.sy = sy + self.px = px + self.py = py + self.image_h = image_h + self.image_w = image_w + self.n_ch = n_ch + + def __call__(self, subimages: Mat) -> _np.ndarray: + nx = (self.image_h - self.sx) // self.px + 1 + ny = (self.image_w - self.sy) // self.py + 1 + + accum = _np.zeros((self.n_ch, self.image_h, self.image_w), dtype=_np.float64) + count = _np.zeros((1, self.image_h, self.image_w), dtype=_np.float64) + + for ix in range(nx): + for iy in range(ny): + patch = _np.asarray(np.reshape(subimages[ix * ny + iy], (self.n_ch, self.sx, self.sy))) + x0, y0 = ix * self.px, iy * self.py + accum[:, x0:x0+self.sx, y0:y0+self.sy] += patch + count[0, x0:x0+self.sx, y0:y0+self.sy] += 1.0 + + result = (accum / _np.maximum(count, 1)).transpose(1, 2, 0) + lo, hi = result.min(), result.max() + return _np.clip((result - lo) / (hi - lo + 1e-8), 0, 1) + def conv2d(image: Mat, kernel: Mat, stride: tuple[int,int]): pad_x = int((kernel.shape[0] - 1)/2) pad_y = int((kernel.shape[1] - 1)/2) diff --git a/src/tests/test_faces_sub_image.py b/src/tests/test_faces_sub_image.py index bcf5298..93e0b2a 100644 --- a/src/tests/test_faces_sub_image.py +++ b/src/tests/test_faces_sub_image.py @@ -5,7 +5,7 @@ import matplotlib.pyplot as plt from rbm.model import Model from rbm.entity import Entity, EntityParams, TrainingParams -from image.sub_image import SubImage, normalize +from image.sub_image import SubImageExtract, normalize from rbm.matrix import Mat, np, convert from rbm.status import CheckpointStatus @@ -38,7 +38,7 @@ class TestModel(Model): def pad_to_stride(img: numpy.ndarray) -> numpy.ndarray: - """Pad so (H - PATCH) and (W - PATCH) are divisible by STRIDE (required by SubImage.check).""" + """Pad so (H - PATCH) and (W - PATCH) are divisible by STRIDE (required by SubImageExtract.check).""" h, w = img.shape[:2] ph = (-h) % STRIDE if h >= PATCH else PATCH - h pw = (-w) % STRIDE if w >= PATCH else PATCH - w @@ -86,8 +86,8 @@ def load_patches(data_dir: str, n_images: int = N_IMAGES, start: int = 0): def load_image_patches(path: str): - """Extract normalised patches from a single image via SubImage; return (patches, nx_steps, ny_steps).""" - sub = SubImage(PATCH, PATCH, STRIDE, STRIDE) + """Extract normalised patches from a single image via SubImageExtract; return (patches, nx_steps, ny_steps).""" + sub = SubImageExtract(PATCH, PATCH, STRIDE, STRIDE) img = cv2.imread(path) img = pad_to_stride(img) h, w = img.shape[:2] diff --git a/src/tests/test_sub_image.py b/src/tests/test_sub_image.py index 07969bc..15312a8 100644 --- a/src/tests/test_sub_image.py +++ b/src/tests/test_sub_image.py @@ -1,4 +1,4 @@ -from image.sub_image import SubImage +from image.sub_image import SubImageExtract from rbm.matrix import np @@ -8,7 +8,7 @@ if __name__ == "__main__": w = 8 h = 8 img_rbb = np.reshape(np.linspace(1, n*c*w*h, num=n*c*w*h, dtype=np.float64), (n, c, w, h)) - sub = SubImage(4, 4, 1, 1) + sub = SubImageExtract(4, 4, 1, 1) sub(img_rbb, 3) img_gray = np.reshape(np.linspace(1, n*w*h, num=n*w*h, dtype=np.float64), (n, w, h))