Module generativepy.nparray
Expand source code
# Author: Martin McBride
# Created: 2020-11-22
# Copyright (C) 2020, Martin McBride
# License: MIT
import numpy as np
from generativepy.movie import save_frame, save_frames
from generativepy.color import make_colormap
def make_nparray_frame(paint, pixel_width, pixel_height, channels=3, out=None):
"""
Create a frame using numpy
Args:
paint: function - the paint function.
pixel_width: int - width in pixels.
pixel_height: int - height in pixels.
channels: int - 1 for greyscale, 3 for rgb, 4 for rgba.
out: numpy array - optional array to hold result. Must be correct width, height and channels, but can be any int type.
Returns:
A numpy array frame buffer
"""
if out is not None:
if out.shape != (pixel_height, pixel_width, channels):
raise ValueError('out array shape not compatible with image dimensions')
array = out
else:
array = np.full((pixel_height, pixel_width, channels), 255, dtype=np.uint)
paint(array, pixel_width, pixel_height, 0, 1)
array = np.clip(array, 0, 255).astype(np.uint8)
return array
def make_nparray_data(paint, pixel_width, pixel_height, channels=3, dtype=np.uint):
"""
Create a data array using numpy.
This is similar to `make_nparray_frame` except that it produce a general purpose numpy array rather than a
frame buffer. The difference is that the data from this function isn't constrained to the range 0-255.
The data can be outside that normal range, and it can also be a different type (eg signed integer or floating point).
This allows the method to be used for storing intermediate data such as per pixel counts.
Args:
paint: function - the paint function.
pixel_width: int - width in pixels.
pixel_height: int - height in pixels.
channels: int - 1 for greyscale, 3 for rgb, 4 for rgba.
dtype: numpy data type - the type of the array.
Returns:
A numpy array
"""
array = np.full((pixel_height, pixel_width, channels), 0, dtype=dtype)
paint(array, pixel_width, pixel_height, 0, 1)
return array
def make_nparray_frames(paint, pixel_width, pixel_height, count, channels=3):
"""
Create a frame sequence using numpy.
This function returns a lazy iterator that can be used to access the sequence. Images will be
created as they are requested.
Args:
paint: function - the paint function.
pixel_width: int - width in pixels.
pixel_height: int - height in pixels.
count: int - number of frames ot create.
channels: int - 1 for greyscale, 3 for rgb, 4 for rgba.
Yields:
Lazy iterator of frames.
"""
for i in range(count):
array = np.full((pixel_height, pixel_width, channels), 255, dtype=np.uint)
paint(array, pixel_width, pixel_height, i, count)
array = np.clip(array, 0, 255).astype(np.uint8)
yield array
def make_nparray(outfile, paint, pixel_width, pixel_height, channels=3):
"""
Create a PNG file using numpy.
Args:
outfile: str - Name of output file.
paint: function - the paint function.
pixel_width: int - width in pixels.
pixel_height: int - height in pixels.
channels: int - 1 for greyscale, 3 for rgb, 4 for rgba.
"""
frame = make_nparray_frame(paint, pixel_width, pixel_height, channels)
save_frame(outfile, frame)
def make_nparrays(outfile, paint, pixel_width, pixel_height, count, channels=3):
"""
Create a set of PNG files using numpy.
Args:
outfile: str - Name of output file.
paint: function - the paint function.
pixel_width: int - width in pixels.
pixel_height: int - height in pixels.
count: int - number of frames to create.
channels: int - 1 for greyscale, 3 for rgb, 4 for rgba.
"""
frames = make_nparray_frames(paint, pixel_width, pixel_height, count, channels)
save_frames(outfile, frames)
def overlay_nparrays(array1, array2):
"""
Overlay array2 on top of array1. Any pixels in array2 that are fully white are treated as transparent.
Both frames nust be same size, and both must be 4 channel RGBA data.
Args:
array1: numpy frame - first (lower) frame.
array2: numpy frame - second (upper) frame.
Returns:
A numpy array frame buffer
"""
if (array1.shape[0] != array2.shape[0]
or array1.shape[1] != array2.shape[1]
or array1.shape[2] != 4
or array2.shape[2] != 4):
raise ValueError("array1 and array2 must be same shape and must contain 4 channel (RGBA) data.")
# Create a mask. The mask has value 255 if the corresponding array2 pixel is pure white.
m0 = array2[:, :, 0]
m1 = array2[:, :, 1]
m2 = array2[:, :, 2]
mask = m0 & m1 & m2 # Bitwise AND of RGB values is 255 only if all 3 values are 255.
# Extend the mask to be width by height by 4 to match image data
mask = np.repeat(mask[:, :, np.newaxis], 4, axis=2)
# Array full of 255 for comparison
white = np.full_like(mask, 255)
# Create a merged image, using the array1 pixel if the mask is white, else array2
image = np.where(mask==white, array1, array2)
return image
def save_nparray(outfile, array):
"""
Save a general array to file in mumpy format. The saved file is not an image file.
The file created can be read back in using `load_nparray`.
Args:
outfile: str - numpy file path including extension.
array: numpy array - data to be saved.
"""
with open(outfile, 'wb') as f:
np.save(f, array)
def save_nparray_image(outfile, array):
"""
Save an array to an image file.
Args:
outfile: str - image file path including extension.
array: numpy array - data to be saved.
"""
array = np.clip(array, 0, 255).astype(np.uint8)
save_frame(outfile, array)
def load_nparray(infile):
"""
Load a numpy array from file
Args:
infile: str - file path including extension.
Returns:
A numpy array. No checking is done on the array.
"""
with open(infile, 'rb') as f:
return np.load(f)
def make_npcolormap(length, colors, bands=None, channels=3):
"""
Create a colormap, a list of varying colors, as a numpy array.
Args:
length: - int, required size of list
colors: - tuple of Color objects - the list of colours, must be at least 2 long.
bands: tuple of numbers - Relative size of each band. bands[i] gives the size of the band between color[i] and color[i+1].
len(bands) must be exactly 1 less than len(colors). If bands is None, equal bands will be used.
channels: int 3 for RGB, 4 for RGBA
Returns:
An array of shape (length, channels) containing the RGB(A) values for each entry, as integers from 0-255
"""
colors = make_colormap(length, colors, bands)
npcolormap = np.zeros((length, channels), dtype=np.uint8)
for i in range(length):
rgba = colors[i].as_rgba_bytes()
npcolormap[i, 0] = rgba[0]
npcolormap[i, 1] = rgba[1]
npcolormap[i, 2] = rgba[2]
if channels==4:
npcolormap[i, 3] = rgba[3]
return npcolormap
def apply_npcolormap(out, counts, npcolormap):
"""
Apply a color map to an array of counts, filling an existing output array
Args:
out: numpy array - the output array, height x width x channels (channels is 3 or 4).
counts: numpy array - the counts array, height x width, count range 0 to max_count.
npcolormap: numpy array - a numpy color map, must have at least maxcount+1 elements.
"""
if out.shape[0] != counts.shape[0] or out.shape[1] != counts.shape[1]:
raise ValueError('out and counts are incompatible shapes')
if np.max(counts) > npcolormap.shape[0]:
raise ValueError('npcolormap too small for maximum value in counts array')
out[...] = npcolormap[counts]
Functions
def apply_npcolormap(out, counts, npcolormap)
-
Apply a color map to an array of counts, filling an existing output array
Args
out
- numpy array - the output array, height x width x channels (channels is 3 or 4).
counts
- numpy array - the counts array, height x width, count range 0 to max_count.
npcolormap
- numpy array - a numpy color map, must have at least maxcount+1 elements.
Expand source code
def apply_npcolormap(out, counts, npcolormap): """ Apply a color map to an array of counts, filling an existing output array Args: out: numpy array - the output array, height x width x channels (channels is 3 or 4). counts: numpy array - the counts array, height x width, count range 0 to max_count. npcolormap: numpy array - a numpy color map, must have at least maxcount+1 elements. """ if out.shape[0] != counts.shape[0] or out.shape[1] != counts.shape[1]: raise ValueError('out and counts are incompatible shapes') if np.max(counts) > npcolormap.shape[0]: raise ValueError('npcolormap too small for maximum value in counts array') out[...] = npcolormap[counts]
def load_nparray(infile)
-
Load a numpy array from file
Args
infile
- str - file path including extension.
Returns
A numpy array. No checking is done on the array.
Expand source code
def load_nparray(infile): """ Load a numpy array from file Args: infile: str - file path including extension. Returns: A numpy array. No checking is done on the array. """ with open(infile, 'rb') as f: return np.load(f)
def make_nparray(outfile, paint, pixel_width, pixel_height, channels=3)
-
Create a PNG file using numpy.
Args
outfile
- str - Name of output file.
paint
- function - the paint function.
pixel_width
- int - width in pixels.
pixel_height
- int - height in pixels.
channels
- int - 1 for greyscale, 3 for rgb, 4 for rgba.
Expand source code
def make_nparray(outfile, paint, pixel_width, pixel_height, channels=3): """ Create a PNG file using numpy. Args: outfile: str - Name of output file. paint: function - the paint function. pixel_width: int - width in pixels. pixel_height: int - height in pixels. channels: int - 1 for greyscale, 3 for rgb, 4 for rgba. """ frame = make_nparray_frame(paint, pixel_width, pixel_height, channels) save_frame(outfile, frame)
def make_nparray_data(paint, pixel_width, pixel_height, channels=3, dtype=numpy.uint64)
-
Create a data array using numpy.
This is similar to
make_nparray_frame()
except that it produce a general purpose numpy array rather than a frame buffer. The difference is that the data from this function isn't constrained to the range 0-255.The data can be outside that normal range, and it can also be a different type (eg signed integer or floating point). This allows the method to be used for storing intermediate data such as per pixel counts.
Args
paint
- function - the paint function.
pixel_width
- int - width in pixels.
pixel_height
- int - height in pixels.
channels
- int - 1 for greyscale, 3 for rgb, 4 for rgba.
dtype
- numpy data type - the type of the array.
Returns
A numpy array
Expand source code
def make_nparray_data(paint, pixel_width, pixel_height, channels=3, dtype=np.uint): """ Create a data array using numpy. This is similar to `make_nparray_frame` except that it produce a general purpose numpy array rather than a frame buffer. The difference is that the data from this function isn't constrained to the range 0-255. The data can be outside that normal range, and it can also be a different type (eg signed integer or floating point). This allows the method to be used for storing intermediate data such as per pixel counts. Args: paint: function - the paint function. pixel_width: int - width in pixels. pixel_height: int - height in pixels. channels: int - 1 for greyscale, 3 for rgb, 4 for rgba. dtype: numpy data type - the type of the array. Returns: A numpy array """ array = np.full((pixel_height, pixel_width, channels), 0, dtype=dtype) paint(array, pixel_width, pixel_height, 0, 1) return array
def make_nparray_frame(paint, pixel_width, pixel_height, channels=3, out=None)
-
Create a frame using numpy
Args
paint
- function - the paint function.
pixel_width
- int - width in pixels.
pixel_height
- int - height in pixels.
channels
- int - 1 for greyscale, 3 for rgb, 4 for rgba.
out
- numpy array - optional array to hold result. Must be correct width, height and channels, but can be any int type.
Returns
A numpy array frame buffer
Expand source code
def make_nparray_frame(paint, pixel_width, pixel_height, channels=3, out=None): """ Create a frame using numpy Args: paint: function - the paint function. pixel_width: int - width in pixels. pixel_height: int - height in pixels. channels: int - 1 for greyscale, 3 for rgb, 4 for rgba. out: numpy array - optional array to hold result. Must be correct width, height and channels, but can be any int type. Returns: A numpy array frame buffer """ if out is not None: if out.shape != (pixel_height, pixel_width, channels): raise ValueError('out array shape not compatible with image dimensions') array = out else: array = np.full((pixel_height, pixel_width, channels), 255, dtype=np.uint) paint(array, pixel_width, pixel_height, 0, 1) array = np.clip(array, 0, 255).astype(np.uint8) return array
def make_nparray_frames(paint, pixel_width, pixel_height, count, channels=3)
-
Create a frame sequence using numpy.
This function returns a lazy iterator that can be used to access the sequence. Images will be created as they are requested.
Args
paint
- function - the paint function.
pixel_width
- int - width in pixels.
pixel_height
- int - height in pixels.
count
- int - number of frames ot create.
channels
- int - 1 for greyscale, 3 for rgb, 4 for rgba.
Yields
Lazy iterator of frames.
Expand source code
def make_nparray_frames(paint, pixel_width, pixel_height, count, channels=3): """ Create a frame sequence using numpy. This function returns a lazy iterator that can be used to access the sequence. Images will be created as they are requested. Args: paint: function - the paint function. pixel_width: int - width in pixels. pixel_height: int - height in pixels. count: int - number of frames ot create. channels: int - 1 for greyscale, 3 for rgb, 4 for rgba. Yields: Lazy iterator of frames. """ for i in range(count): array = np.full((pixel_height, pixel_width, channels), 255, dtype=np.uint) paint(array, pixel_width, pixel_height, i, count) array = np.clip(array, 0, 255).astype(np.uint8) yield array
def make_nparrays(outfile, paint, pixel_width, pixel_height, count, channels=3)
-
Create a set of PNG files using numpy.
Args
outfile
- str - Name of output file.
paint
- function - the paint function.
pixel_width
- int - width in pixels.
pixel_height
- int - height in pixels.
count
- int - number of frames to create.
channels
- int - 1 for greyscale, 3 for rgb, 4 for rgba.
Expand source code
def make_nparrays(outfile, paint, pixel_width, pixel_height, count, channels=3): """ Create a set of PNG files using numpy. Args: outfile: str - Name of output file. paint: function - the paint function. pixel_width: int - width in pixels. pixel_height: int - height in pixels. count: int - number of frames to create. channels: int - 1 for greyscale, 3 for rgb, 4 for rgba. """ frames = make_nparray_frames(paint, pixel_width, pixel_height, count, channels) save_frames(outfile, frames)
def make_npcolormap(length, colors, bands=None, channels=3)
-
Create a colormap, a list of varying colors, as a numpy array.
Args
length
-
- int, required size of list
colors
-
- tuple of Color objects - the list of colours, must be at least 2 long.
bands
- tuple of numbers - Relative size of each band. bands[i] gives the size of the band between color[i] and color[i+1]. len(bands) must be exactly 1 less than len(colors). If bands is None, equal bands will be used.
channels
- int 3 for RGB, 4 for RGBA
Returns
An array of shape (length, channels) containing the RGB(A) values for each entry, as integers from 0-255
Expand source code
def make_npcolormap(length, colors, bands=None, channels=3): """ Create a colormap, a list of varying colors, as a numpy array. Args: length: - int, required size of list colors: - tuple of Color objects - the list of colours, must be at least 2 long. bands: tuple of numbers - Relative size of each band. bands[i] gives the size of the band between color[i] and color[i+1]. len(bands) must be exactly 1 less than len(colors). If bands is None, equal bands will be used. channels: int 3 for RGB, 4 for RGBA Returns: An array of shape (length, channels) containing the RGB(A) values for each entry, as integers from 0-255 """ colors = make_colormap(length, colors, bands) npcolormap = np.zeros((length, channels), dtype=np.uint8) for i in range(length): rgba = colors[i].as_rgba_bytes() npcolormap[i, 0] = rgba[0] npcolormap[i, 1] = rgba[1] npcolormap[i, 2] = rgba[2] if channels==4: npcolormap[i, 3] = rgba[3] return npcolormap
def overlay_nparrays(array1, array2)
-
Overlay array2 on top of array1. Any pixels in array2 that are fully white are treated as transparent.
Both frames nust be same size, and both must be 4 channel RGBA data.
Args
array1
- numpy frame - first (lower) frame.
array2
- numpy frame - second (upper) frame.
Returns
A numpy array frame buffer
Expand source code
def overlay_nparrays(array1, array2): """ Overlay array2 on top of array1. Any pixels in array2 that are fully white are treated as transparent. Both frames nust be same size, and both must be 4 channel RGBA data. Args: array1: numpy frame - first (lower) frame. array2: numpy frame - second (upper) frame. Returns: A numpy array frame buffer """ if (array1.shape[0] != array2.shape[0] or array1.shape[1] != array2.shape[1] or array1.shape[2] != 4 or array2.shape[2] != 4): raise ValueError("array1 and array2 must be same shape and must contain 4 channel (RGBA) data.") # Create a mask. The mask has value 255 if the corresponding array2 pixel is pure white. m0 = array2[:, :, 0] m1 = array2[:, :, 1] m2 = array2[:, :, 2] mask = m0 & m1 & m2 # Bitwise AND of RGB values is 255 only if all 3 values are 255. # Extend the mask to be width by height by 4 to match image data mask = np.repeat(mask[:, :, np.newaxis], 4, axis=2) # Array full of 255 for comparison white = np.full_like(mask, 255) # Create a merged image, using the array1 pixel if the mask is white, else array2 image = np.where(mask==white, array1, array2) return image
def save_nparray(outfile, array)
-
Save a general array to file in mumpy format. The saved file is not an image file.
The file created can be read back in using
load_nparray()
.Args
outfile
- str - numpy file path including extension.
array
- numpy array - data to be saved.
Expand source code
def save_nparray(outfile, array): """ Save a general array to file in mumpy format. The saved file is not an image file. The file created can be read back in using `load_nparray`. Args: outfile: str - numpy file path including extension. array: numpy array - data to be saved. """ with open(outfile, 'wb') as f: np.save(f, array)
def save_nparray_image(outfile, array)
-
Save an array to an image file.
Args
outfile
- str - image file path including extension.
array
- numpy array - data to be saved.
Expand source code
def save_nparray_image(outfile, array): """ Save an array to an image file. Args: outfile: str - image file path including extension. array: numpy array - data to be saved. """ array = np.clip(array, 0, 255).astype(np.uint8) save_frame(outfile, array)