-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_transform.py
More file actions
121 lines (97 loc) · 3.85 KB
/
test_transform.py
File metadata and controls
121 lines (97 loc) · 3.85 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
import numpy as np
from skimage import transform
class ScaleTranslationTransform(transform.SimilarityTransform):
"""
A custom transformation model that only considers uniform scaling and translation.
This class is a simplified version of `skimage.transform.SimilarityTransform`
that ignores rotation and shear. It is used to estimate the transformation
from a set of source and destination points.
"""
def estimate(self, src, dst):
# src and dst are (N, 2) arrays
if src.shape[0] < 2:
return False
# 1. Compute centroids
src_mean = src.mean(axis=0)
dst_mean = dst.mean(axis=0)
# 2. Center the points
src_centered = src - src_mean
dst_centered = dst - dst_mean
# 3. Compute scale s
# s = sum(src_centered * dst_centered) / sum(src_centered^2)
# We sum over both dimensions and all points
num = np.sum(src_centered * dst_centered)
den = np.sum(src_centered**2)
if den == 0:
return False
s = num / den
# 4. Compute translation
# t = dst_mean - s * src_mean
t = dst_mean - s * src_mean
# 5. Set parameters
# Update the matrix
self.params = np.array([[s, 0, t[0]], [0, s, t[1]], [0, 0, 1]])
return True
def test():
# Create synthetic data
# src: square
src = np.array([[0, 0], [10, 0], [10, 10], [0, 10]])
# Case 1: Pure translation
dst_trans = src + [5, 5]
model = ScaleTranslationTransform()
model.estimate(src, dst_trans)
print("Case 1 (Translation):")
print(f"Scale: {model.scale} (Expected 1.0)")
print(f"Translation: {model.translation} (Expected [5. 5.])")
print(f"Rotation: {model.rotation} (Expected 0)")
# Case 2: Pure scale
dst_scale = src * 2.0
model = ScaleTranslationTransform()
model.estimate(src, dst_scale)
print("\nCase 2 (Scale):")
print(f"Scale: {model.scale} (Expected 2.0)")
print(f"Translation: {model.translation} (Expected [0. 0.])")
# Case 3: Scale + Translation
dst_st = src * 0.5 + [10, 20]
model = ScaleTranslationTransform()
model.estimate(src, dst_st)
print("\nCase 3 (Scale + Translation):")
print(f"Scale: {model.scale} (Expected 0.5)")
print(f"Translation: {model.translation} (Expected [10. 20.])")
# Case 4: Rotation (should fail to capture rotation, but find best scale/trans)
# Rotate 90 degrees: (x,y) -> (-y, x)
# src: (0,0), (10,0), (10,10), (0,10)
# dst: (0,0), (0,10), (-10,10), (-10,0)
# Centroids: src(5, 5), dst(-5, 5)
# src_c: (-5,-5), (5,-5), (5,5), (-5,5)
# dst_c: (5,-5), (5,5), (-5,5), (-5,-5)
# num = (-25 + 25) + (25 - 25) + (-25 + 25) + (25 - 25) = 0?
# Let's check.
# (-5)*5 + (-5)*(-5) = -25 + 25 = 0
# 5*5 + (-5)*5 = 25 - 25 = 0
# ...
# So scale should be 0? That makes sense as projection of orthogonal vectors.
dst_rot = np.array([[0, 0], [0, 10], [-10, 10], [-10, 0]])
model = ScaleTranslationTransform()
model.estimate(src, dst_rot)
print("\nCase 4 (Rotation 90deg):")
print(f"Scale: {model.scale}")
print(f"Translation: {model.translation}")
# Case 5: Rotation small (10 deg)
# Should find scale ~1 and translation approx correct
theta = np.radians(10)
c, s = np.cos(theta), np.sin(theta)
rot_mat = np.array([[c, -s], [s, c]])
dst_rot_small = src @ rot_mat.T
model = ScaleTranslationTransform()
model.estimate(src, dst_rot_small)
print("\nCase 5 (Rotation 10deg):")
print(f"Scale: {model.scale}")
print(f"Translation: {model.translation}")
# Compare with SimilarityTransform
sim_model = transform.SimilarityTransform()
sim_model.estimate(src, dst_rot_small)
print(f"Similarity Scale: {sim_model.scale}")
print(f"Similarity Rotation: {sim_model.rotation}")
if __name__ == "__main__":
test()