Skip to content

Commit c60c03e

Browse files
committed
Initial implementation of adding symmetry to estimate_shifts. It's slow. Need to vectorize.
1 parent 20a8ac2 commit c60c03e

1 file changed

Lines changed: 106 additions & 78 deletions

File tree

src/aspire/abinitio/commonline_base.py

Lines changed: 106 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -259,14 +259,19 @@ def _get_shift_equations_approx(self):
259259

260260
n_theta_half = self.n_theta // 2
261261
n_img = self.n_img
262+
pf = self.pf.copy()
262263

263264
# `estimate_shifts()` requires that rotations have already been estimated.
264-
rotations = Rotation(self.rotations)
265+
rotations = self.rotations
265266

266-
pf = self.pf.copy()
267+
# Apply symmetry group to rotations
268+
sym_rots = self.src.symmetry_group.matrices.astype(self.dtype, copy=False)
269+
n_sym = len(sym_rots)
267270

268-
# Estimate number of equations that will be used to calculate the shifts
269-
n_equations = self._estimate_num_shift_equations(n_img)
271+
# Estimate number of equations that will be used to calculate the shifts,
272+
# taking into account particle symmetry.
273+
n_pair_equations = self._estimate_num_shift_equations(n_img)
274+
n_equations = n_pair_equations * n_sym
270275

271276
# Allocate local variables for estimating 2D shifts based on the estimated number
272277
# of equations. The shift equations are represented using a sparse matrix,
@@ -291,86 +296,94 @@ def _get_shift_equations_approx(self):
291296
d_theta = np.pi / n_theta_half
292297

293298
# Generate two index lists for [i, j] pairs of images
294-
idx_i, idx_j = self._generate_index_pairs(n_equations)
299+
idx_i, idx_j = self._generate_index_pairs(n_pair_equations)
295300

296301
# Go through all shift equations in the size of n_equations
297302
# Iterate over the common lines pairs and for each pair find the 1D
298303
# relative shift between the two Fourier lines in the pair.
299-
for shift_eq_idx in range(n_equations):
300-
i = idx_i[shift_eq_idx]
301-
j = idx_j[shift_eq_idx]
302-
# get the common line indices based on the rotations from i and j images
303-
c_ij, c_ji = self._get_cl_indices(rotations, i, j, n_theta_half)
304-
305-
# Extract the Fourier rays that correspond to the common line
306-
pf_i = pf[i, c_ij]
307-
308-
# Check whether need to flip or not Fourier ray of j image
309-
# Is the common line in image j in the positive
310-
# direction of the ray (is_pf_j_flipped=False) or in the
311-
# negative direction (is_pf_j_flipped=True).
312-
is_pf_j_flipped = c_ji >= n_theta_half
313-
if not is_pf_j_flipped:
314-
pf_j = pf[j, c_ji]
315-
else:
316-
pf_j = pf[j, c_ji - n_theta_half]
317-
318-
# Use ray from opposite side of origin.
319-
# Correpsonds to `freqs` convention in PFT,
320-
# where the legacy code used a negated frequency grid.
321-
pf_i, pf_j = np.conj(pf_i), np.conj(pf_j)
322-
323-
# perform bandpass filter, normalize each ray of each image,
324-
pf_i = self._apply_filter_and_norm("i, i -> i", pf_i, r_max, h)
325-
pf_j = self._apply_filter_and_norm("i, i -> i", pf_j, r_max, h)
326-
327-
# apply the shifts to images
328-
pf_i_flipped = np.conj(pf_i)
329-
pf_i_stack = pf_i[:, None] * shift_phases.T
330-
pf_i_flipped_stack = pf_i_flipped[:, None] * shift_phases.T
331-
332-
c1 = 2 * np.dot(pf_i_stack.T.conj(), pf_j).real
333-
c2 = 2 * np.dot(pf_i_flipped_stack.T.conj(), pf_j).real
334-
335-
# find the indices for the maximum values
336-
# and apply corresponding shifts
337-
sidx1 = np.argmax(c1)
338-
sidx2 = np.argmax(c2)
339-
sidx = sidx1 if c1[sidx1] > c2[sidx2] else sidx2
340-
dx = -self.offsets_max_shift + sidx * self.offsets_shift_step
341-
342-
# angle of common ray in image i
343-
shift_alpha = c_ij * d_theta
344-
# Angle of common ray in image j.
345-
shift_beta = c_ji * d_theta
346-
# Row index to construct the sparse equations
347-
shift_i[shift_eq_idx] = shift_eq_idx
348-
# Columns of the shift variables that correspond to the current pair [i, j]
349-
shift_j[shift_eq_idx] = [2 * i, 2 * i + 1, 2 * j, 2 * j + 1]
350-
# Right hand side of the current equation
351-
shift_b[shift_eq_idx] = dx
352-
353-
# Compute the coefficients of the current equation
354-
if not is_pf_j_flipped:
355-
shift_eq[shift_eq_idx] = np.array(
356-
[
357-
np.sin(shift_alpha),
358-
np.cos(shift_alpha),
359-
-np.sin(shift_beta),
360-
-np.cos(shift_beta),
361-
]
362-
)
363-
else:
364-
shift_beta = shift_beta - np.pi
365-
shift_eq[shift_eq_idx] = np.array(
366-
[
367-
-np.sin(shift_alpha),
368-
-np.cos(shift_alpha),
369-
-np.sin(shift_beta),
370-
-np.cos(shift_beta),
371-
]
304+
for pair_eq_idx in range(n_pair_equations):
305+
i = idx_i[pair_eq_idx]
306+
j = idx_j[pair_eq_idx]
307+
308+
for sym_idx, g in enumerate(sym_rots):
309+
shift_eq_idx = pair_eq_idx + sym_idx * n_pair_equations
310+
311+
# get the common line indices based on the rotations from i and j images
312+
c_ij, c_ji = self._get_cl_indices_from_rot_pair(
313+
rotations[i],
314+
g @ rotations[j],
315+
n_theta_half,
372316
)
373317

318+
# Extract the Fourier rays that correspond to the common line
319+
pf_i = pf[i, c_ij]
320+
321+
# Check whether need to flip or not Fourier ray of j image
322+
# Is the common line in image j in the positive
323+
# direction of the ray (is_pf_j_flipped=False) or in the
324+
# negative direction (is_pf_j_flipped=True).
325+
is_pf_j_flipped = c_ji >= n_theta_half
326+
if not is_pf_j_flipped:
327+
pf_j = pf[j, c_ji]
328+
else:
329+
pf_j = pf[j, c_ji - n_theta_half]
330+
331+
# Use ray from opposite side of origin.
332+
# Correpsonds to `freqs` convention in PFT,
333+
# where the legacy code used a negated frequency grid.
334+
pf_i, pf_j = np.conj(pf_i), np.conj(pf_j)
335+
336+
# perform bandpass filter, normalize each ray of each image,
337+
pf_i = self._apply_filter_and_norm("i, i -> i", pf_i, r_max, h)
338+
pf_j = self._apply_filter_and_norm("i, i -> i", pf_j, r_max, h)
339+
340+
# apply the shifts to images
341+
pf_i_flipped = np.conj(pf_i)
342+
pf_i_stack = pf_i[:, None] * shift_phases.T
343+
pf_i_flipped_stack = pf_i_flipped[:, None] * shift_phases.T
344+
345+
c1 = 2 * np.dot(pf_i_stack.T.conj(), pf_j).real
346+
c2 = 2 * np.dot(pf_i_flipped_stack.T.conj(), pf_j).real
347+
348+
# find the indices for the maximum values
349+
# and apply corresponding shifts
350+
sidx1 = np.argmax(c1)
351+
sidx2 = np.argmax(c2)
352+
sidx = sidx1 if c1[sidx1] > c2[sidx2] else sidx2
353+
dx = -self.offsets_max_shift + sidx * self.offsets_shift_step
354+
355+
# angle of common ray in image i
356+
shift_alpha = c_ij * d_theta
357+
# Angle of common ray in image j.
358+
shift_beta = c_ji * d_theta
359+
# Row index to construct the sparse equations
360+
shift_i[shift_eq_idx] = shift_eq_idx
361+
# Columns of the shift variables that correspond to the current pair [i, j]
362+
shift_j[shift_eq_idx] = [2 * i, 2 * i + 1, 2 * j, 2 * j + 1]
363+
# Right hand side of the current equation
364+
shift_b[shift_eq_idx] = dx
365+
366+
# Compute the coefficients of the current equation
367+
if not is_pf_j_flipped:
368+
shift_eq[shift_eq_idx] = np.array(
369+
[
370+
np.sin(shift_alpha),
371+
np.cos(shift_alpha),
372+
-np.sin(shift_beta),
373+
-np.cos(shift_beta),
374+
]
375+
)
376+
else:
377+
shift_beta = shift_beta - np.pi
378+
shift_eq[shift_eq_idx] = np.array(
379+
[
380+
-np.sin(shift_alpha),
381+
-np.cos(shift_alpha),
382+
-np.sin(shift_beta),
383+
-np.cos(shift_beta),
384+
]
385+
)
386+
374387
# create sparse matrix object only containing non-zero elements
375388
shift_equations = sparse.csr_matrix(
376389
(shift_eq.flatten(), (shift_i.flatten(), shift_j.flatten())),
@@ -458,6 +471,21 @@ def _get_cl_indices(self, rotations, i, j, n_theta):
458471

459472
return c_ij, c_ji
460473

474+
def _get_cl_indices_from_rot_pair(self, Ri, Rj, n_theta):
475+
"""
476+
Get common-line indices for an explicit pair of rotation matrices.
477+
"""
478+
rotations = Rotation(np.stack((Ri, Rj))).invert()
479+
c_ij, c_ji = rotations.common_lines(0, 1, 2 * n_theta)
480+
481+
if c_ij >= n_theta:
482+
c_ij -= n_theta
483+
c_ji -= n_theta
484+
if c_ji < 0:
485+
c_ji += 2 * n_theta
486+
487+
return c_ij, c_ji
488+
461489
def _apply_filter_and_norm(self, subscripts, pf, r_max, h):
462490
"""
463491
Apply common line filter and normalize each ray

0 commit comments

Comments
 (0)