简体   繁体   中英

Faster way to iterarate over numpy array

i have a very large array which i need to iterate over. The array is a large tiff image and instead of a color value i want to add a 4-4 bit 2d pattern. My code looks like this right now. It needs very long to finish. tiff is a array(x,y,4). x and y are very large. Values is a list with patterns who match the value i am searching for and giving me the index. Patterns is a array of 4-4 Patterns. Thanks

for iy, ix in np.ndindex(tiff[:, :, 0].shape):
    tiff[iy, ix, 0] = np.random.choice(np.argwhere(Values == tiff[iy, ix, 0])[:, 0], 1, replace=False)
    tiff[iy, ix, 1] = np.random.choice(np.argwhere(Values == tiff[iy, ix, 1])[:, 0], 1, replace=False)
    tiff[iy, ix, 2] = np.random.choice(np.argwhere(Values == tiff[iy, ix, 2])[:, 0], 1, replace=False)
    tiff[iy, ix, 3] = np.random.choice(np.argwhere(Values == tiff[iy, ix, 3])[:, 0], 1, replace=False)
    Rippedimage[iy * 8 : (iy + 1) * 8 - 4, ix * 8 : (ix + 1) * 8 - 4] = Array_Pattern_4_4[tiff[iy, ix, 0]]
    Rippedimage[iy * 8 : (iy + 1) * 8 - 4, ix * 8 + 4 : (ix + 1) * 8] = Array_Pattern_4_4[tiff[iy, ix, 1]]
    Rippedimage[iy * 8 + 4 : (iy + 1) * 8, ix * 8 : (ix + 1) * 8 - 4] = Array_Pattern_4_4[tiff[iy, ix, 2]]
    Rippedimage[iy * 8 + 4 : (iy + 1) * 8, ix * 8 + 4 : (ix + 1) * 8] = Array_Pattern_4_4[tiff[iy, ix, 3]]

left is before, right how it should look like after

It's honestly a bit hard to tell what you're really looking for, but here's some code that:

  • generates a variety of NxN random dither patterns for each grayscale shade (assuming 8-bit images)
  • chooses a random pattern per NxN pixels in the original image to generate a dithered version

On my Macbook, dithering an 920x920 image takes about 17 milliseconds:

image generation 4.377
pattern generation 6.06
dither generation 16.915
import time
from contextlib import contextmanager

import numpy as np
from PIL import Image


def generate_patterns(
    *,
    pattern_size: int = 8,
    pattern_options_per_shade: int = 8,
    shades: int = 256,
):
    patterns = []
    for shade in range(shades):
        shade_patterns = [
            np.random.random((pattern_size, pattern_size)) < (shade / shades)
            for i in range(pattern_options_per_shade)
        ]
        patterns.append(shade_patterns)
    return np.array(patterns)


def dither(image, patterns):
    (
        shades,
        pattern_options_per_shade,
        pattern_width,
        pattern_height,
    ) = patterns.shape
    assert shades == 256  # TODO

    # image sampled at pattern_sizes
    resampled = (
        image[::pattern_width, ::pattern_height].round().astype(np.uint8)
    )
    # mask of pattern option per pattern_size block
    pat_mask = np.random.randint(
        0, pattern_options_per_shade, size=resampled.shape
    )

    dithered = np.zeros_like(image)
    for (iy, ix), c in np.ndenumerate(resampled):
        pattern = patterns[c, pat_mask[iy, ix]]
        dithered[
            iy * pattern_height : (iy + 1) * pattern_height,
            ix * pattern_width : (ix + 1) * pattern_width,
        ] = pattern

    return dithered * 255


@contextmanager
def stopwatch(title):
    t0 = time.perf_counter()
    yield
    t1 = time.perf_counter()
    print(title, round((t1 - t0) * 1000, 3))


def main():
    with stopwatch("image generation"):
        img_size = 920
        image = (
            np.linspace(0, 255, img_size)
            .repeat(img_size)
            .reshape((img_size, img_size))
        )
        image[200:280, 200:280] = 0

    with stopwatch("pattern generation"):
        patterns = generate_patterns()

    with stopwatch("dither generation"):
        dithered = dither(image, patterns)

    import matplotlib.pyplot as plt

    plt.figure(dpi=450)
    plt.imshow(dithered, interpolation="none")
    plt.show()


if __name__ == "__main__":
    main()

The output image looks like (eg)

在此处输入图像描述

EDIT

A version that upscales the source image to the dithered version:

image generation 3.886
pattern generation 5.581
dither generation 1361.194
def dither_embiggen(image, patterns):
    shades, pattern_options_per_shade, pattern_width, pattern_height = patterns.shape
    assert shades == 256  # TODO

    # mask of pattern option per source pixel
    pat_mask = np.random.randint(0, pattern_options_per_shade, size=image.shape)

    dithered = np.zeros((image.shape[0] * pattern_height, image.shape[1] * pattern_width))
    for (iy, ix), c in np.ndenumerate(image.round().astype(np.uint8)):
        pattern = patterns[c, pat_mask[iy, ix]]
        dithered[iy * pattern_height:(iy + 1) * pattern_height, ix * pattern_width:(ix + 1) * pattern_width] = pattern

    return (dithered * 255)

EDIT 2

This version directly writes the dithered lines to disk as a raw binary file. The reader is expected to know how many pixels there are per line. Based on a little empirical testing this seems to do the trick...

import time
from contextlib import contextmanager

import numpy as np


def generate_patterns(
    *,
    pattern_size: int = 8,
    pattern_options_per_shade: int = 16,
    shades: int = 256,
):
    patterns = []
    for shade in range(shades):
        shade_patterns = [
            np.packbits(
                np.random.random((pattern_size, pattern_size))
                < (shade / shades),
                axis=0,
            )[0]
            for i in range(pattern_options_per_shade)
        ]
        patterns.append(shade_patterns)
    return np.array(patterns)


def dither_to_disk(bio, image, patterns):
    assert image.dtype == np.uint8
    shades, pattern_options_per_shade, pattern_height = patterns.shape
    pat_mask = np.random.randint(0, pattern_options_per_shade, size=image.shape)
    for y in range(image.shape[0]):
        patterns[image[y, :], pat_mask[y, :]].tofile(bio)


@contextmanager
def stopwatch(title):
    t0 = time.perf_counter()
    yield
    t1 = time.perf_counter()
    print(title, round((t1 - t0) * 1000, 3))


def main():
    with stopwatch("image generation"):
        img_width = 25_000
        img_height = 5_000
        image = (
            np.linspace(0, 255, img_height)
            .repeat(img_width)
            .reshape((img_height, img_width))
        )
        image[200:280, 200:280] = 0
        image = image.round().astype(np.uint8)

    with stopwatch("pattern generation"):
        patterns = generate_patterns()

    with stopwatch(f"dither_to_disk {image.shape}"):
        with open("x.bin", "wb") as f:
            dither_to_disk(f, image, patterns)


if __name__ == "__main__":
    main()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM