Fast differentiable resizing and warping of arbitrary grids
Project description
Hugues Hoppe Aug 2022.
[Open in Colab] [Kaggle] [MyBinder] [DeepNote] [GitHub source] [API docs] [PyPI package]
The notebook resampler_notebook.ipynb
hosts the source code for the
resampler
library in PyPI,
interleaved with documentation, usage examples, unit tests, and signal-processing experiments.
Overview
The resampler
library enables fast differentiable resizing and warping of arbitrary grids.
It supports:
-
grids of any dimension (e.g., 1D, 2D images, 3D video, 4D batches of videos), containing
-
samples of any shape (e.g., scalars, colors, motion vectors, Jacobian matrices) and
-
any numeric type (integer, floating, and complex);
-
either
dual
("half-integer") orprimal
grid-type for each dimension; -
many boundary rules, specified per dimension, extensible via subclassing;
-
an extensible set of filter kernels, selectable per dimension;
-
optional gamma transfer functions for correct linear-space filtering;
-
prefiltering for accurate antialiasing when downsampling;
-
processing within several array libraries (
numpy
,tensorflow
, andtorch
); -
efficient backpropagation of gradients for both
tensorflow
andtorch
; -
easy installation, with no native code, yet
-
faster resizing than C++ implementations in
tf.image
,torch.nn
, andtorchvision
.
A key strategy is to leverage existing sparse matrix representations and operations.
Example usage
!pip install -q mediapy resampler
import mediapy as media
import numpy as np
import resampler
array = np.random.default_rng(1).random((4, 6, 3)) # 4x6 RGB image.
upsampled = resampler.resize(array, (128, 192)) # To 128x192 resolution.
media.show_images({'4x6': array, '128x192': upsampled}, height=128)
image = media.read_image('https://github.com/hhoppe/data/raw/main/image.png')
downsampled = resampler.resize(image, (32, 32))
media.show_images({'128x128': image, '32x32': downsampled}, height=128)
import matplotlib.pyplot as plt
array = [3.0, 5.0, 8.0, 7.0] # 4 source samples in 1D.
new_dual = resampler.resize(array, (32,)) # (default gridtype='dual') 8x resolution.
new_primal = resampler.resize(array, (25,), gridtype='primal') # 8x resolution.
_, axs = plt.subplots(1, 2, figsize=(7, 1.5))
axs[0].set_title('gridtype dual')
axs[0].plot((np.arange(len(array)) + 0.5) / len(array), array, 'o')
axs[0].plot((np.arange(len(new_dual)) + 0.5) / len(new_dual), new_dual, '.')
axs[1].set_title('gridtype primal')
axs[1].plot(np.arange(len(array)) / (len(array) - 1), array, 'o')
axs[1].plot(np.arange(len(new_primal)) / (len(new_primal) - 1), new_primal, '.')
plt.show()
batch_size = 4
batch_of_images = media.moving_circle((16, 16), batch_size)
upsampled = resampler.resize(batch_of_images, (batch_size, 64, 64))
spacer = np.ones((64, 16, 3))
media.show_images([*batch_of_images, spacer, *upsampled], border=True, height=64)
media.show_videos({'original': batch_of_images, 'upsampled': upsampled}, fps=1)
upsampledoriginal
Most examples above use the default
resize()
settings:
gridtype='dual'
for both source and destination arrays,boundary='auto'
which uses'reflect'
for upsampling and'clamp'
for downsampling,filter='lanczos3'
(a Lanczos kernel with radius 3),gamma=None
which by default uses the'power2'
transfer function for theuint8
image in the second example,scale=1.0, translate=0.0
(no domain transformation),- default
precision
and outputdtype
.
Advanced usage:
Map an image to a wider grid using custom scale
and translate
vectors,
with horizontal 'reflect'
and vertical 'natural'
boundary rules,
providing a constant value for the exterior,
using different filters (Lanczos and O-MOMS) in the two dimensions,
disabling gamma correction, performing computations in double-precision,
and returning an output array in single-precision:
new = resampler.resize(
image, (128, 512), boundary=('natural', 'reflect'), cval=(0.2, 0.7, 0.3),
filter=('lanczos3', 'omoms5'), gamma='identity', scale=(0.8, 0.25),
translate=(0.1, 0.35), precision='float64', dtype='float32')
media.show_images({'image': image, 'new': new})
Warp an image by transforming it using polar coordinates:
shape = image.shape[:2]
yx = ((np.indices(shape).T + 0.5) / shape - 0.5).T # [-0.5, 0.5]^2
radius, angle = np.linalg.norm(yx, axis=0), np.arctan2(*yx)
angle += (0.8 - radius).clip(0, 1) * 2.0 - 0.6
coords = np.dstack((np.sin(angle) * radius, np.cos(angle) * radius)) + 0.5
resampled = resampler.resample(image, coords, boundary='constant')
media.show_images({'image': image, 'resampled': resampled})
Limitations:
- Filters are assumed to be separable. For rotation equivariance (e.g., bandlimit the signal uniformly in all directions), it would be nice to support the (non-separable) 2D rotationally symmetric sombrero function $f(\textbf{x}) = \text{jinc}(|\textbf{x}|)$, where $\text{jinc}(r) = 2J_1(\pi r)/(\pi r)$. (The Fourier transform of a circle involves the first-order Bessel function of the first kind.)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file resampler-0.3.2.tar.gz
.
File metadata
- Download URL: resampler-0.3.2.tar.gz
- Upload date:
- Size: 37.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | ad2be0e0ba0981094f16d97df7a7375d2998e143f9fb3a489a6697fd63f21d61 |
|
MD5 | 7416465c375144518b4211c72b2d5de9 |
|
BLAKE2b-256 | 42681a21a8e75caab865bdc8b7ad60bda0455c22a26ed419dc6dd84123eabb5f |
File details
Details for the file resampler-0.3.2-py3-none-any.whl
.
File metadata
- Download URL: resampler-0.3.2-py3-none-any.whl
- Upload date:
- Size: 34.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.13
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3a522be899703f5fd5159414ca904a05170d8fa02a6094850c59de48ec2b0ca6 |
|
MD5 | 4d594647a6c3875f9289bd48272d9c8d |
|
BLAKE2b-256 | 31e4e089211e8dca019d8a3a32afd60debe8f6adaaee32761a00131985ad5778 |