-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathdiff.py
More file actions
126 lines (98 loc) · 3.46 KB
/
Copy pathdiff.py
File metadata and controls
126 lines (98 loc) · 3.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# pylint: disable=no-member, invalid-name, line-too-long
"""
Computes diffeomorphism of 2D images in pytorch
"""
import functools
import math
import torch
@functools.lru_cache()
def scalar_field_modes(n, m, dtype=torch.float64, device='cpu'):
"""
sqrt(1 / Energy per mode) and the modes
"""
x = torch.linspace(0, 1, n, dtype=dtype, device=device)
k = torch.arange(1, m + 1, dtype=dtype, device=device)
i, j = torch.meshgrid(k, k)
r = (i.pow(2) + j.pow(2)).sqrt()
e = (r < m + 0.5) / r
s = torch.sin(math.pi * x[:, None] * k[None, :])
return e, s
def scalar_field(n, m, device='cpu'):
"""
random scalar field of size nxn made of the first m modes
"""
e, s = scalar_field_modes(n, m, dtype=torch.get_default_dtype(), device=device)
c = torch.randn(m, m, device=device) * e
return torch.einsum('ij,xi,yj->yx', c, s, s)
def deform(image, T, cut, interp='linear'):
"""
1. Sample a displacement field tau: R2 -> R2, using tempertature `T` and cutoff `cut`
2. Apply tau to `image`
:param img Tensor: square image(s) [..., y, x]
:param T float: temperature
:param cut int: high frequency cutoff
"""
n = image.shape[-1]
assert image.shape[-2] == n, 'Image(s) should be square.'
device = image.device.type
# Sample dx, dy
# u, v are defined in [0, 1]^2
# dx, dx are defined in [0, n]^2
u = scalar_field(n, cut, device) # [n,n]
v = scalar_field(n, cut, device) # [n,n]
dx = T ** 0.5 * u * n
dy = T ** 0.5 * v * n
# Apply tau
return remap(image, dx, dy, interp).contiguous()
def remap(a, dx, dy, interp):
"""
:param a: Tensor of shape [..., y, x]
:param dx: Tensor of shape [y, x]
:param dy: Tensor of shape [y, x]
:param interp: interpolation method
"""
n, m = a.shape[-2:]
assert dx.shape == (n, m) and dy.shape == (n, m), 'Image(s) and displacement fields shapes should match.'
dtype = dx.dtype
device = dx.device.type
y, x = torch.meshgrid(torch.arange(n, dtype=dtype, device=device), torch.arange(m, dtype=dtype, device=device), indexing='ij')
xn = (x - dx).clamp(0, m-1)
yn = (y - dy).clamp(0, n-1)
if interp == 'linear':
xf = xn.floor().long()
yf = yn.floor().long()
xc = xn.ceil().long()
yc = yn.ceil().long()
xv = xn - xf
yv = yn - yf
return (1-yv)*(1-xv)*a[..., yf, xf] + (1-yv)*xv*a[..., yf, xc] + yv*(1-xv)*a[..., yc, xf] + yv*xv*a[..., yc, xc]
if interp == 'gaussian':
# can be implemented more efficiently by adding a cutoff to the Gaussian
sigma = 0.4715
dx = (xn[:, :, None, None] - x)
dy = (yn[:, :, None, None] - y)
c = (-dx**2 - dy**2).div(2 * sigma**2).exp()
c = c / c.sum([2, 3], keepdim=True)
return (c * a[..., None, None, :, :]).sum([-1, -2])
if interp == 'nearest':
xn = xn.round().long()
yn = yn.round().long()
return a[..., yn, xn]
def temperature_range(n, cut):
"""
Define the range of allowed temperature
for given image size and cut.
"""
if isinstance(cut, (float, int)):
log = math.log(cut)
else:
log = cut.log()
T1 = 1 / (math.pi * n ** 2 * log)
T2 = 4 / (math.pi ** 3 * cut ** 2 * log)
return T1, T2
def typical_displacement(T, cut, n):
if isinstance(cut, (float, int)):
log = math.log(cut)
else:
log = cut.log()
return n * (math.pi * T * log) ** .5 / 2