Skip to content

Commit 317bf53

Browse files
committed
stashing, estimate rotations now matches MATLAB
1 parent 89d3d81 commit 317bf53

File tree

2 files changed

+46
-22
lines changed

2 files changed

+46
-22
lines changed

src/aspire/abinitio/commonline_sync3n.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -810,12 +810,11 @@ def _syncmatrix_ij_vote_3n(self, clmatrix, i, j, k_list, n_theta):
810810
:param n_theta: The number of points in the theta direction (common lines)
811811
:return: The (i,j) rotation block of the synchronization matrix
812812
"""
813-
good_k = self._vote_ij(clmatrix, n_theta, i, j, k_list)
813+
alpha, good_k = self._vote_ij(clmatrix, n_theta, i, j, k_list)
814814

815-
angle = self._rotratio_eulerangle_vec(clmatrix, i, j, good_k, n_theta)
816815
angles = np.zeros(3)
817816

818-
if angle is not None:
817+
if alpha is not None:
819818
# # # BAD
820819
# # Convert the Euler angles with ZYZ conversion to rotation matrices
821820
# angles[0] = clmatrix[i, j] * 2 * np.pi / n_theta + np.pi / 2
@@ -824,7 +823,7 @@ def _syncmatrix_ij_vote_3n(self, clmatrix, i, j, k_list, n_theta):
824823
# rot = Rotation.from_euler(angles).matrices
825824

826825
angles[0] = clmatrix[i, j] * 2 * np.pi / n_theta - np.pi
827-
angles[1] = angle
826+
angles[1] = np.mean(alpha)
828827
angles[2] = np.pi - clmatrix[j, i] * 2 * np.pi / n_theta
829828
rot = sprot.from_euler("ZXZ", angles).as_matrix()
830829

src/aspire/abinitio/sync_voting.py

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ def _rotratio_eulerangle_vec(self, clmatrix, i, j, good_k, n_theta):
3535
# cl_diff2 is for the angle on C2 created by its intersection with C1 and C3.
3636
# cl_diff3 is for the angle on C3 created by its intersection with C2 and C1.
3737
cl_diff1 = clmatrix[i, good_k] - clmatrix[i, j] # for theta1
38-
cl_diff2 = clmatrix[j, good_k] - clmatrix[j, i] # for - theta2
38+
cl_diff2 = clmatrix[j, good_k] - clmatrix[j, i] # for theta2
3939
cl_diff3 = clmatrix[good_k, j] - clmatrix[good_k, i] # for theta3
4040

4141
# Calculate the cos values of rotation angles between i an j images for good k images
42-
c_alpha, good_idx = self._get_cos_phis(cl_diff1, cl_diff2, cl_diff3, n_theta)
42+
c_alpha, good_idx = self._get_cos_phis(
43+
cl_diff1, cl_diff2, cl_diff3, n_theta, sync=True
44+
)
4345
if len(c_alpha) == 0:
4446
return None
4547
alpha = np.arccos(c_alpha)
@@ -82,6 +84,7 @@ def _vote_ij(self, clmatrix, n_theta, i, j, k_list):
8284
k_list = k_list[
8385
(k_list != i) & (clmatrix[i, k_list] != -1) & (clmatrix[j, k_list] != -1)
8486
]
87+
8588
cl_idx13 = clmatrix[i, k_list]
8689
cl_idx31 = clmatrix[k_list, i]
8790
cl_idx23 = clmatrix[j, k_list]
@@ -94,12 +97,16 @@ def _vote_ij(self, clmatrix, n_theta, i, j, k_list):
9497
# cl_diff3 is for the angle on C3 created by its intersection with C2 and C1.
9598
cl_diff1 = cl_idx13 - cl_idx12
9699
# bad or just a trig identity?
97-
cl_diff2 = cl_idx21 - cl_idx23
98-
# cl_diff2 = cl_idx23 - cl_idx21 # theta2 = (clmatrix(j,K)-clmatrix(j,i)) * 2*pi/L;
100+
# cl_diff2 = cl_idx21 - cl_idx23
101+
cl_diff2 = (
102+
cl_idx23 - cl_idx21
103+
) # theta2 = (clmatrix(j,K)-clmatrix(j,i)) * 2*pi/L;
99104
cl_diff3 = cl_idx32 - cl_idx31
100105

101106
# Calculate the cos values of rotation angles between i an j images for good k images
102-
cos_phi2, good_idx = self._get_cos_phis(cl_diff1, cl_diff2, cl_diff3, n_theta)
107+
cos_phi2, good_idx = self._get_cos_phis(
108+
cl_diff1, cl_diff2, cl_diff3, n_theta, sync=True
109+
)
103110

104111
if np.any(np.abs(cos_phi2) - 1 > 1e-12):
105112
logger.warning(
@@ -115,28 +122,23 @@ def _vote_ij(self, clmatrix, n_theta, i, j, k_list):
115122
inds = k_list[good_idx]
116123

117124
if phis.shape[0] == 0:
118-
return []
125+
return None, []
119126

120127
# Parameters used to compute the smoothed angle histogram.
121128
ntics = int(180 / self.hist_bin_width)
122-
angles_grid = np.linspace(0, 180, ntics, True)
129+
angles_grid = np.linspace(0, 180, ntics + 1, True)
123130
# Get angles between images i and j for computing the histogram
124131
angles = np.arccos(phis[:]) * 180 / np.pi
132+
125133
# Angles that are up to 10 degrees apart are considered
126134
# similar. This sigma ensures that the width of the density
127135
# estimation kernel is roughly 10 degrees. For 15 degrees, the
128136
# value of the kernel is negligible.
129137
sigma = 3.0
130138

131139
# Compute the histogram of the angles between images i and j
132-
squared_values = np.add.outer(np.square(angles), np.square(angles_grid))
133-
angles_hist = np.sum(
134-
np.exp(
135-
(2 * np.multiply.outer(angles, angles_grid) - squared_values)
136-
/ (2 * sigma**2)
137-
),
138-
0,
139-
)
140+
angles_distances = angles_grid[None, :] - angles[:, None]
141+
angles_hist = np.sum(np.exp(-(angles_distances**2) / (2 * sigma**2)), axis=0)
140142

141143
# We assume that at the location of the peak we get the true angle
142144
# between images i and j. Find all third images k, that induce an
@@ -169,10 +171,11 @@ def _vote_ij(self, clmatrix, n_theta, i, j, k_list):
169171
idx = np.abs(angles - angles_grid[peak_idx]) < self.full_width
170172

171173
good_k = inds[idx]
174+
alpha = np.arccos(phis[idx])
172175

173-
return good_k.astype("int")
176+
return alpha, good_k.astype("int")
174177

175-
def _get_cos_phis(self, cl_diff1, cl_diff2, cl_diff3, n_theta):
178+
def _get_cos_phis(self, cl_diff1, cl_diff2, cl_diff3, n_theta, sync=False):
176179
"""
177180
Calculate cos values of rotation angles between i and j images
178181
@@ -242,7 +245,29 @@ def _get_cos_phis(self, cl_diff1, cl_diff2, cl_diff3, n_theta):
242245

243246
# Calculated cos values of angle between i and j images
244247
cos_phi2 = (c3[good_idx] - c1[good_idx] * c2[good_idx]) / (
245-
np.sin(theta1[good_idx]) * np.sin(theta2[good_idx])
248+
np.sqrt(1 - c1[good_idx] ** 2) * np.sqrt(1 - c2[good_idx] ** 2)
246249
)
247250

251+
if sync:
252+
# % Some synchronization must be applied when common line is
253+
# % out by 180 degrees.
254+
# % Here fix the angles between c_ij(c_ji) and c_ik(c_jk) to be smaller than pi/2,
255+
# % otherwise there will be an ambiguity between alpha and pi-alpha.
256+
# ind1 = ( (theta1 > pi+TOL_idx) | (theta1<-TOL_idx & theta1>-pi) );
257+
# ind2 = ( (theta2 > pi+TOL_idx) | (theta2<-TOL_idx & theta2>-pi) );
258+
# align180 = (ind1 & ~ind2 ) | (~ind1 & ind2 );
259+
# cos_phi2(align180) = -cos_phi2(align180);
260+
TOL_idx = 1e-12
261+
theta1 = theta1[good_idx]
262+
theta2 = theta2[good_idx]
263+
theta3 = theta3[good_idx]
264+
ind1 = (theta1 > (np.pi + TOL_idx)) | (
265+
(theta1 < -TOL_idx) & (theta1 > -np.pi)
266+
)
267+
ind2 = (theta2 > (np.pi + TOL_idx)) | (
268+
(theta2 < -TOL_idx) & (theta2 > -np.pi)
269+
)
270+
align180 = (ind1 & ~ind2) | (~ind1 & ind2)
271+
cos_phi2[align180] = -cos_phi2[align180]
272+
248273
return cos_phi2, good_idx

0 commit comments

Comments
 (0)