Created
June 10, 2018 21:24
-
-
Save elistevens/6e3bfb20d8d0652b83bd16b3e911285b to your computer and use it in GitHub Desktop.
Tests for volumetric affine grid generation in pytorch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
import random | |
import numpy as np | |
import scipy.ndimage | |
import torch | |
import pytest | |
from util.logconf import logging | |
log = logging.getLogger(__name__) | |
# log.setLevel(logging.WARN) | |
# log.setLevel(logging.INFO) | |
log.setLevel(logging.DEBUG) | |
from .affine import affine_grid_generator | |
if torch.cuda.is_available(): | |
@pytest.fixture(params=['cpu', 'cuda']) | |
# @pytest.fixture(params=['cpu']) | |
def device(request): | |
return request.param | |
else: | |
@pytest.fixture(params=['cpu']) | |
def device(request): | |
return request.param | |
@pytest.fixture(params=[0.0, 0.5, 0.25, 0.125, 'random']) | |
# @pytest.fixture(params=[0., 0.25]) | |
def angle_rad(request): | |
if request.param == 'random': | |
return random.random() * math.pi * 2 | |
return request.param * math.pi * 2 | |
@pytest.fixture(params=[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), 'random']) | |
def axis_vector(request): | |
if request.param == 'random': | |
t = (random.random(), random.random(), random.random()) | |
l = sum(x**2 for x in t)**0.5 | |
return tuple(x/l for x in t) | |
return request.param | |
@pytest.fixture(params=[torch.nn.functional.affine_grid, affine_grid_generator]) | |
# @pytest.fixture(params=[]) | |
def affine_func2d(request): | |
return request.param | |
@pytest.fixture(params=[affine_grid_generator]) | |
def affine_func3d(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 3, 5], [1, 1, 3, 3], [1, 1, 4, 4], [1, 1, 3, 4]]) | |
def input_size2d(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 5, 3], [1, 1, 3, 5], [1, 1, 4, 3], [1, 1, 5, 5], [1, 1, 6, 6]]) | |
def output_size2d(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 2, 2], [1, 1, 3, 3], [1, 1, 4, 4], [1, 1, 6, 6], ]) | |
def input_size2dsq(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 2, 2], [1, 1, 3, 3], [1, 1, 4, 4], [1, 1, 5, 5], [1, 1, 6, 6], ]) | |
def output_size2dsq(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 2, 2, 2], [1, 1, 2, 3, 4], [1, 1, 3, 3, 3], [1, 1, 4, 4, 4], [1, 1, 3, 4, 5]]) | |
def input_size3d(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 2, 2, 2], [1, 1, 3, 3, 3], [1, 1, 4, 4, 4], [1, 1, 6, 6, 6]]) | |
def input_size3dsq(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 2, 2, 2], [1, 1, 3, 3, 3], [1, 1, 4, 4, 4], [1, 1, 5, 5, 5], [1, 1, 6, 6, 6]]) | |
def output_size3dsq(request): | |
return request.param | |
@pytest.fixture(params=[[1, 1, 2, 2, 2], [1, 1, 3, 3, 3], [1, 1, 3, 4, 5], [1, 1, 4, 3, 2], [1, 1, 5, 5, 5], [1, 1, 6, 6, 6]]) | |
def output_size3d(request): | |
return request.param | |
def _buildEquivalentTransforms2d(device, input_size, output_size, angle_rad): | |
print("_buildEquivalentTransforms2d", device, input_size, output_size, angle_rad * 180 / math.pi) | |
input_center = [(x-1)/2 for x in input_size] | |
output_center = [(x-1)/2 for x in output_size] | |
s = math.sin(angle_rad) | |
c = math.cos(angle_rad) | |
intrans_ary = np.array([ | |
[1, 0, input_center[2]], | |
[0, 1, input_center[3]], | |
[0, 0, 1], | |
], dtype=np.float64) | |
inscale_ary = np.array([ | |
[input_center[2], 0, 0], | |
[0, input_center[3], 0], | |
[0, 0, 1], | |
], dtype=np.float64) | |
rotation_ary = np.array([ | |
[c, -s, 0], | |
[s, c, 0], | |
[0, 0, 1], | |
], dtype=np.float64) | |
outscale_ary = np.array([ | |
[1/output_center[2], 0, 0], | |
[0, 1/output_center[3], 0], | |
[0, 0, 1], | |
], dtype=np.float64) | |
outtrans_ary = np.array([ | |
[1, 0, -output_center[2]], | |
[0, 1, -output_center[3]], | |
[0, 0, 1], | |
], dtype=np.float64) | |
reorder_ary = np.array([ | |
[0, 1, 0], | |
[1, 0, 0], | |
[0, 0, 1], | |
], dtype=np.float64) | |
transform_ary = intrans_ary @ inscale_ary @ rotation_ary.T @ outscale_ary @ outtrans_ary | |
grid_ary = reorder_ary @ rotation_ary.T @ outscale_ary @ outtrans_ary | |
transform_tensor = torch.from_numpy((rotation_ary)).to(device, torch.float32) | |
transform_tensor = transform_tensor[:2].unsqueeze(0) | |
print('transform_tensor', transform_tensor.size(), transform_tensor.dtype, transform_tensor.device) | |
print(transform_tensor) | |
print('outtrans_ary', outtrans_ary.shape, outtrans_ary.dtype) | |
print(outtrans_ary.round(3)) | |
print('outscale_ary', outscale_ary.shape, outscale_ary.dtype) | |
print(outscale_ary.round(3)) | |
print('rotation_ary', rotation_ary.shape, rotation_ary.dtype) | |
print(rotation_ary.round(3)) | |
print('inscale_ary', inscale_ary.shape, inscale_ary.dtype) | |
print(inscale_ary.round(3)) | |
print('intrans_ary', intrans_ary.shape, intrans_ary.dtype) | |
print(intrans_ary.round(3)) | |
print('transform_ary', transform_ary.shape, transform_ary.dtype) | |
print(transform_ary.round(3)) | |
print('grid_ary', grid_ary.shape, grid_ary.dtype) | |
print(grid_ary.round(3)) | |
def prtf(pt): | |
print(pt, 'transformed', (transform_ary @ (pt + [1]))[:2].round(3)) | |
prtf([0, 0]) | |
prtf([1, 0]) | |
prtf([2, 0]) | |
print('') | |
prtf([0, 0]) | |
prtf([0, 1]) | |
prtf([0, 2]) | |
prtf(output_center[2:]) | |
return transform_tensor, transform_ary, grid_ary | |
def _buildEquivalentTransforms3d(device, input_size, output_size, angle_rad, axis_vector): | |
print("_buildEquivalentTransforms2d", device, input_size, output_size, angle_rad * 180 / math.pi, axis_vector) | |
input_center = [(x-1)/2 for x in input_size] | |
output_center = [(x-1)/2 for x in output_size] | |
s = math.sin(angle_rad) | |
c = math.cos(angle_rad) | |
c1 = 1 - c | |
intrans_ary = np.array([ | |
[1, 0, 0, input_center[2]], | |
[0, 1, 0, input_center[3]], | |
[0, 0, 1, input_center[4]], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
inscale_ary = np.array([ | |
[input_center[2], 0, 0, 0], | |
[0, input_center[3], 0, 0], | |
[0, 0, input_center[4], 0], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
l, m, n = axis_vector | |
scipyRotation_ary = np.array([ | |
[l*l*c1 + c, m*l*c1 - n*s, n*l*c1 + m*s, 0], | |
[l*m*c1 + n*s, m*m*c1 + c, n*m*c1 - l*s, 0], | |
[l*n*c1 - m*s, m*n*c1 + l*s, n*n*c1 + c, 0], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
z, y, x = axis_vector | |
torchRotation_ary = np.array([ | |
[x*x*c1 + c, y*x*c1 - z*s, z*x*c1 + y*s, 0], | |
[x*y*c1 + z*s, y*y*c1 + c, z*y*c1 - x*s, 0], | |
[x*z*c1 - y*s, y*z*c1 + x*s, z*z*c1 + c, 0], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
outscale_ary = np.array([ | |
[1/output_center[2], 0, 0, 0], | |
[0, 1/output_center[3], 0, 0], | |
[0, 0, 1/output_center[4], 0], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
outtrans_ary = np.array([ | |
[1, 0, 0, -output_center[2]], | |
[0, 1, 0, -output_center[3]], | |
[0, 0, 1, -output_center[4]], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
reorder_ary = np.array([ | |
[0, 0, 1, 0], | |
[0, 1, 0, 0], | |
[1, 0, 0, 0], | |
[0, 0, 0, 1], | |
], dtype=np.float64) | |
transform_ary = intrans_ary @ inscale_ary @ np.linalg.inv(scipyRotation_ary) @ outscale_ary @ outtrans_ary | |
grid_ary = reorder_ary @ np.linalg.inv(scipyRotation_ary) @ outscale_ary @ outtrans_ary | |
transform_tensor = torch.from_numpy((torchRotation_ary)).to(device, torch.float32) | |
transform_tensor = transform_tensor[:3].unsqueeze(0) | |
print('transform_tensor', transform_tensor.size(), transform_tensor.dtype, transform_tensor.device) | |
print(transform_tensor) | |
print('outtrans_ary', outtrans_ary.shape, outtrans_ary.dtype) | |
print(outtrans_ary.round(3)) | |
print('outscale_ary', outscale_ary.shape, outscale_ary.dtype) | |
print(outscale_ary.round(3)) | |
print('rotation_ary', scipyRotation_ary.shape, scipyRotation_ary.dtype, axis_vector, angle_rad) | |
print(scipyRotation_ary.round(3)) | |
print('inscale_ary', inscale_ary.shape, inscale_ary.dtype) | |
print(inscale_ary.round(3)) | |
print('intrans_ary', intrans_ary.shape, intrans_ary.dtype) | |
print(intrans_ary.round(3)) | |
print('transform_ary', transform_ary.shape, transform_ary.dtype) | |
print(transform_ary.round(3)) | |
print('grid_ary', grid_ary.shape, grid_ary.dtype) | |
print(grid_ary.round(3)) | |
def prtf(pt): | |
print(pt, 'transformed', (transform_ary @ (pt + [1]))[:3].round(3)) | |
prtf([0, 0, 0]) | |
prtf([1, 0, 0]) | |
prtf([2, 0, 0]) | |
print('') | |
prtf([0, 0, 0]) | |
prtf([0, 1, 0]) | |
prtf([0, 2, 0]) | |
print('') | |
prtf([0, 0, 0]) | |
prtf([0, 0, 1]) | |
prtf([0, 0, 2]) | |
prtf(output_center[2:]) | |
return transform_tensor, transform_ary, grid_ary | |
def test_affine_2d_rotate0(device, affine_func2d): | |
input_size = [1, 1, 3, 3] | |
input_ary = np.array(np.random.random(input_size), dtype=np.float32) | |
output_size = [1, 1, 5, 5] | |
angle_rad = 0. | |
transform_tensor, transform_ary, offset = _buildEquivalentTransforms2d(device, input_size, output_size, angle_rad) | |
# reference | |
# https://stackoverflow.com/questions/20161175/how-can-i-use-scipy-ndimage-interpolation-affine-transform-to-rotate-an-image-ab | |
scipy_ary = scipy.ndimage.affine_transform( | |
input_ary[0,0], | |
transform_ary, | |
offset=offset, | |
output_shape=output_size[2:], | |
# output=None, | |
order=1, | |
mode='nearest', | |
# cval=0.0, | |
prefilter=False) | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary) | |
affine_tensor = affine_func2d( | |
transform_tensor, | |
torch.Size(output_size) | |
) | |
print('affine_tensor', affine_tensor.size(), affine_tensor.dtype, affine_tensor.device) | |
print(affine_tensor) | |
gridsample_ary = torch.nn.functional.grid_sample( | |
torch.tensor(input_ary, device=device).to(device), | |
affine_tensor, | |
padding_mode='border' | |
).to('cpu').numpy() | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary) | |
print('gridsample_ary', gridsample_ary.shape, gridsample_ary.dtype) | |
print(gridsample_ary) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary) | |
assert np.abs(scipy_ary.mean() - gridsample_ary.mean()) < 1e-6 | |
assert np.abs(scipy_ary - gridsample_ary).max() < 1e-6 | |
# assert False | |
def test_affine_2d_rotate90(device, affine_func2d, input_size2dsq, output_size2dsq): | |
input_size = input_size2dsq | |
input_ary = np.array(np.random.random(input_size), dtype=np.float32) | |
output_size = output_size2dsq | |
angle_rad = 0.25 * math.pi * 2 | |
transform_tensor, transform_ary, offset = _buildEquivalentTransforms2d(device, input_size, output_size, angle_rad) | |
# reference | |
# https://stackoverflow.com/questions/20161175/how-can-i-use-scipy-ndimage-interpolation-affine-transform-to-rotate-an-image-ab | |
scipy_ary = scipy.ndimage.affine_transform( | |
input_ary[0,0], | |
transform_ary, | |
offset=offset, | |
output_shape=output_size[2:], | |
# output=None, | |
order=1, | |
mode='nearest', | |
# cval=0.0, | |
prefilter=True) | |
print('input_ary', input_ary.shape, input_ary.dtype, input_ary.mean()) | |
print(input_ary) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype, scipy_ary.mean()) | |
print(scipy_ary) | |
if input_size2dsq == output_size2dsq: | |
assert np.abs(scipy_ary.mean() - input_ary.mean()) < 1e-6 | |
assert np.abs(scipy_ary[0,0] - input_ary[0,0,0,-1]).max() < 1e-6 | |
assert np.abs(scipy_ary[0,-1] - input_ary[0,0,-1,-1]).max() < 1e-6 | |
assert np.abs(scipy_ary[-1,-1] - input_ary[0,0,-1,0]).max() < 1e-6 | |
assert np.abs(scipy_ary[-1,0] - input_ary[0,0,0,0]).max() < 1e-6 | |
affine_tensor = affine_func2d( | |
transform_tensor, | |
torch.Size(output_size) | |
) | |
print('affine_tensor', affine_tensor.size(), affine_tensor.dtype, affine_tensor.device) | |
print(affine_tensor) | |
gridsample_ary = torch.nn.functional.grid_sample( | |
torch.tensor(input_ary, device=device).to(device), | |
affine_tensor, | |
padding_mode='border' | |
).to('cpu').numpy() | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary) | |
print('gridsample_ary', gridsample_ary.shape, gridsample_ary.dtype) | |
print(gridsample_ary) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary) | |
assert np.abs(scipy_ary.mean() - gridsample_ary.mean()) < 1e-6 | |
assert np.abs(scipy_ary - gridsample_ary).max() < 1e-6 | |
# assert False | |
def test_affine_2d_rotate45(device, affine_func2d): | |
input_size = [1, 1, 3, 3] | |
# input_ary = np.array(np.random.random(input_size), dtype=np.float32) | |
input_ary = np.array(np.zeros(input_size), dtype=np.float32) | |
input_ary[0,0,0,:] = 0.5 | |
input_ary[0,0,2,2] = 1.0 | |
output_size = [1, 1, 3, 3] | |
angle_rad = 0.125 * math.pi * 2 | |
transform_tensor, transform_ary, offset = _buildEquivalentTransforms2d(device, input_size, output_size, angle_rad) | |
# reference | |
# https://stackoverflow.com/questions/20161175/how-can-i-use-scipy-ndimage-interpolation-affine-transform-to-rotate-an-image-ab | |
scipy_ary = scipy.ndimage.affine_transform( | |
input_ary[0,0], | |
transform_ary, | |
offset=offset, | |
output_shape=output_size[2:], | |
# output=None, | |
order=1, | |
mode='nearest', | |
# cval=0.0, | |
prefilter=False) | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary) | |
affine_tensor = affine_func2d( | |
transform_tensor, | |
torch.Size(output_size) | |
) | |
print('affine_tensor', affine_tensor.size(), affine_tensor.dtype, affine_tensor.device) | |
print(affine_tensor) | |
gridsample_ary = torch.nn.functional.grid_sample( | |
torch.tensor(input_ary, device=device).to(device), | |
affine_tensor, | |
padding_mode='border' | |
).to('cpu').numpy() | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary) | |
print('gridsample_ary', gridsample_ary.shape, gridsample_ary.dtype) | |
print(gridsample_ary) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary) | |
assert np.abs(scipy_ary - gridsample_ary).max() < 1e-6 | |
# assert False | |
def test_affine_2d_rotateRandom(device, affine_func2d, angle_rad, input_size2d, output_size2d): | |
input_size = input_size2d | |
input_ary = np.array(np.random.random(input_size), dtype=np.float32).round(3) | |
output_size = output_size2d | |
# angle_rad = random.random() * math.pi * 2 | |
input_ary[0,0,0,0] = 2 | |
input_ary[0,0,0,-1] = 4 | |
input_ary[0,0,-1,0] = 6 | |
input_ary[0,0,-1,-1] = 8 | |
transform_tensor, transform_ary, grid_ary = _buildEquivalentTransforms2d(device, input_size, output_size, angle_rad) | |
# reference | |
# https://stackoverflow.com/questions/20161175/how-can-i-use-scipy-ndimage-interpolation-affine-transform-to-rotate-an-image-ab | |
scipy_ary = scipy.ndimage.affine_transform( | |
input_ary[0,0], | |
transform_ary, | |
# offset=offset, | |
output_shape=output_size[2:], | |
# output=None, | |
order=1, | |
mode='nearest', | |
# cval=0.0, | |
prefilter=False) | |
affine_tensor = affine_func2d( | |
transform_tensor, | |
torch.Size(output_size) | |
) | |
print('affine_tensor', affine_tensor.size(), affine_tensor.dtype, affine_tensor.device) | |
print(affine_tensor) | |
for r in range(affine_tensor.size(1)): | |
for c in range(affine_tensor.size(2)): | |
grid_out = grid_ary @ [r, c, 1] | |
print(r, c, 'affine:', affine_tensor[0,r,c], 'grid:', grid_out[:2]) | |
gridsample_ary = torch.nn.functional.grid_sample( | |
torch.tensor(input_ary, device=device).to(device), | |
affine_tensor, | |
padding_mode='border' | |
).to('cpu').numpy() | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary.round(3)) | |
print('gridsample_ary', gridsample_ary.shape, gridsample_ary.dtype) | |
print(gridsample_ary.round(3)) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary.round(3)) | |
for r in range(affine_tensor.size(1)): | |
for c in range(affine_tensor.size(2)): | |
grid_out = grid_ary @ [r, c, 1] | |
try: | |
assert np.allclose(affine_tensor[0,r,c], grid_out[:2], atol=1e-5) | |
except: | |
print(r, c, 'affine:', affine_tensor[0,r,c], 'grid:', grid_out[:2]) | |
raise | |
assert np.abs(scipy_ary - gridsample_ary).max() < 1e-5 | |
# assert False | |
def test_affine_3d_rotateRandom(device, affine_func3d, angle_rad, axis_vector, input_size3d, output_size3d): | |
input_size = input_size3d | |
input_ary = np.array(np.random.random(input_size), dtype=np.float32) | |
output_size = output_size3d | |
# angle_rad = random.random() * math.pi * 2 | |
input_ary[0,0, 0, 0, 0] = 2 | |
input_ary[0,0, 0, 0, -1] = 3 | |
input_ary[0,0, 0, -1, 0] = 4 | |
input_ary[0,0, 0, -1, -1] = 5 | |
input_ary[0,0, -1, 0, 0] = 6 | |
input_ary[0,0, -1, 0, -1] = 7 | |
input_ary[0,0, -1, -1, 0] = 8 | |
input_ary[0,0, -1, -1, -1] = 9 | |
transform_tensor, transform_ary, grid_ary = _buildEquivalentTransforms3d(device, input_size, output_size, angle_rad, axis_vector) | |
# reference | |
# https://stackoverflow.com/questions/20161175/how-can-i-use-scipy-ndimage-interpolation-affine-transform-to-rotate-an-image-ab | |
scipy_ary = scipy.ndimage.affine_transform( | |
input_ary[0,0], | |
transform_ary, | |
# offset=offset, | |
output_shape=output_size[2:], | |
# output=None, | |
order=1, | |
mode='nearest', | |
# cval=0.0, | |
prefilter=False) | |
affine_tensor = affine_func3d( | |
transform_tensor, | |
torch.Size(output_size) | |
) | |
print('affine_tensor', affine_tensor.size(), affine_tensor.dtype, affine_tensor.device) | |
print(affine_tensor) | |
for i in range(affine_tensor.size(1)): | |
for r in range(affine_tensor.size(2)): | |
for c in range(affine_tensor.size(3)): | |
grid_out = grid_ary @ [i, r, c, 1] | |
print(i, r, c, 'affine:', affine_tensor[0,i,r,c], 'grid:', grid_out[:3].round(3)) | |
print('input_ary', input_ary.shape, input_ary.dtype) | |
print(input_ary.round(3)) | |
gridsample_ary = torch.nn.functional.grid_sample( | |
torch.tensor(input_ary, device=device).to(device), | |
affine_tensor, | |
padding_mode='border' | |
).to('cpu').numpy() | |
print('gridsample_ary', gridsample_ary.shape, gridsample_ary.dtype) | |
print(gridsample_ary.round(3)) | |
print('scipy_ary', scipy_ary.shape, scipy_ary.dtype) | |
print(scipy_ary.round(3)) | |
for i in range(affine_tensor.size(1)): | |
for r in range(affine_tensor.size(2)): | |
for c in range(affine_tensor.size(3)): | |
grid_out = grid_ary @ [i, r, c, 1] | |
try: | |
assert np.allclose(affine_tensor[0,i,r,c], grid_out[:3], atol=1e-5) | |
except: | |
print(i, r, c, 'affine:', affine_tensor[0,i,r,c], 'grid:', grid_out[:3].round(3)) | |
raise | |
assert np.abs(scipy_ary - gridsample_ary).max() < 1e-5 | |
# assert False |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment