From f1ccf3d305e402063ad89036149823fbb73cd9c6 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 22 Aug 2025 11:01:04 -0400 Subject: [PATCH 1/4] move estimate_third_rows to util file --- src/aspire/abinitio/__init__.py | 1 + src/aspire/abinitio/commonline_c2.py | 4 +- src/aspire/abinitio/commonline_c3_c4.py | 53 +----------------------- src/aspire/abinitio/commonline_utils.py | 55 +++++++++++++++++++++++++ src/aspire/utils/__init__.py | 1 + tests/test_orient_symmetric.py | 12 +++--- 6 files changed, 68 insertions(+), 58 deletions(-) create mode 100644 src/aspire/abinitio/commonline_utils.py diff --git a/src/aspire/abinitio/__init__.py b/src/aspire/abinitio/__init__.py index fe91b322ad..a7acb5fc6a 100644 --- a/src/aspire/abinitio/__init__.py +++ b/src/aspire/abinitio/__init__.py @@ -1,6 +1,7 @@ from .commonline_base import CLOrient3D # isort: off +from .commonline_utils import estimate_third_rows from .commonline_sdp import CommonlineSDP from .commonline_lud import CommonlineLUD from .commonline_irls import CommonlineIRLS diff --git a/src/aspire/abinitio/commonline_c2.py b/src/aspire/abinitio/commonline_c2.py index 6bf6cf99ed..538c71cc56 100644 --- a/src/aspire/abinitio/commonline_c2.py +++ b/src/aspire/abinitio/commonline_c2.py @@ -3,7 +3,7 @@ import numpy as np from scipy.linalg import eigh -from aspire.abinitio import CLSymmetryC3C4 +from aspire.abinitio import CLSymmetryC3C4, estimate_third_rows from aspire.utils import J_conjugate, Rotation, all_pairs logger = logging.getLogger(__name__) @@ -224,7 +224,7 @@ def estimate_rotations(self): viis = np.vstack((np.eye(3, dtype=self.dtype),) * self.n_img).reshape( self.n_img, 3, 3 ) - vis = self._estimate_third_rows(vijs, viis) + vis = estimate_third_rows(vijs, viis) logger.info("Estimating in-plane rotations and rotations matrices.") Ris = self._estimate_inplane_rotations(vis, Rijs, Rijgs) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 0170d8b88f..13fd9dc4d3 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -3,7 +3,7 @@ import numpy as np from numpy.linalg import eigh, norm, svd -from aspire.abinitio import CLOrient3D, SyncVotingMixin +from aspire.abinitio import CLOrient3D, SyncVotingMixin, estimate_third_rows from aspire.operators import PolarFT from aspire.utils import ( J_conjugate, @@ -110,7 +110,7 @@ def estimate_rotations(self): vijs, viis = self._global_J_sync(vijs, viis) logger.info("Estimating third rows of rotation matrices.") - vis = self._estimate_third_rows(vijs, viis) + vis = estimate_third_rows(vijs, viis) logger.info("Estimating in-plane rotations and rotations matrices.") Ris = self._estimate_inplane_rotations(vis) @@ -209,55 +209,6 @@ def _global_J_sync(self, vijs, viis): viis[i] = vii_J return vijs, viis - def _estimate_third_rows(self, vijs, viis): - """ - Find the third row of each rotation matrix given a collection of matrices - representing the outer products of the third rows from each rotation matrix. - - :param vijs: An (n-choose-2)x3x3 array where each 3x3 slice holds the third rows - outer product of the rotation matrices Ri and Rj. - - :param viis: An n_imgx3x3 array where the i'th 3x3 slice holds the outer product of - the third row of Ri with itself. - - :param order: The underlying molecular symmetry. - - :return: vis, An n_imgx3 matrix whose i'th row is the third row of the rotation matrix Ri. - """ - - n_img = self.n_img - - # Build matrix V whose (i,j)-th block of size 3x3 holds the outer product vij - V = np.zeros((n_img, n_img, 3, 3), dtype=vijs.dtype) - - # All pairs (i,j) where i Date: Mon, 25 Aug 2025 09:13:43 -0400 Subject: [PATCH 2/4] migrate estimate_inplane_rotations, complete_third_row_to_rot to commonline utils. --- src/aspire/abinitio/__init__.py | 7 +- src/aspire/abinitio/commonline_c2.py | 8 +- src/aspire/abinitio/commonline_c3_c4.py | 212 +----------------------- src/aspire/abinitio/commonline_cn.py | 8 +- src/aspire/abinitio/commonline_utils.py | 204 ++++++++++++++++++++++- tests/test_orient_symmetric.py | 12 +- 6 files changed, 229 insertions(+), 222 deletions(-) diff --git a/src/aspire/abinitio/__init__.py b/src/aspire/abinitio/__init__.py index a7acb5fc6a..3685479732 100644 --- a/src/aspire/abinitio/__init__.py +++ b/src/aspire/abinitio/__init__.py @@ -1,7 +1,12 @@ from .commonline_base import CLOrient3D # isort: off -from .commonline_utils import estimate_third_rows +from .commonline_utils import ( + cl_angles_to_ind, + estimate_third_rows, + complete_third_row_to_rot, + estimate_inplane_rotations, +) from .commonline_sdp import CommonlineSDP from .commonline_lud import CommonlineLUD from .commonline_irls import CommonlineIRLS diff --git a/src/aspire/abinitio/commonline_c2.py b/src/aspire/abinitio/commonline_c2.py index 538c71cc56..52b63cc9e8 100644 --- a/src/aspire/abinitio/commonline_c2.py +++ b/src/aspire/abinitio/commonline_c2.py @@ -3,7 +3,11 @@ import numpy as np from scipy.linalg import eigh -from aspire.abinitio import CLSymmetryC3C4, estimate_third_rows +from aspire.abinitio import ( + CLSymmetryC3C4, + complete_third_row_to_rot, + estimate_third_rows, +) from aspire.utils import J_conjugate, Rotation, all_pairs logger = logging.getLogger(__name__) @@ -301,7 +305,7 @@ def _estimate_inplane_rotations(self, vis, Rijs, Rijgs): H = np.zeros((self.n_img, self.n_img), dtype=complex) # Step 1: Construct all rotation matrices Ris_tilde whose third rows are equal to # the corresponding third rows vis. - Ris_tilde = self._complete_third_row_to_rot(vis) + Ris_tilde = complete_third_row_to_rot(vis) pairs = all_pairs(self.n_img) for idx, (i, j) in enumerate(pairs): diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 13fd9dc4d3..bd9522e865 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -3,7 +3,12 @@ import numpy as np from numpy.linalg import eigh, norm, svd -from aspire.abinitio import CLOrient3D, SyncVotingMixin, estimate_third_rows +from aspire.abinitio import ( + CLOrient3D, + SyncVotingMixin, + estimate_inplane_rotations, + estimate_third_rows, +) from aspire.operators import PolarFT from aspire.utils import ( J_conjugate, @@ -12,7 +17,6 @@ all_triplets, anorm, cyclic_rotations, - tqdm, trange, ) from aspire.utils.random import randn @@ -113,7 +117,7 @@ def estimate_rotations(self): vis = estimate_third_rows(vijs, viis) logger.info("Estimating in-plane rotations and rotations matrices.") - Ris = self._estimate_inplane_rotations(vis) + Ris = estimate_inplane_rotations(self, vis) self.rotations = Ris @@ -209,140 +213,6 @@ def _global_J_sync(self, vijs, viis): viis[i] = vii_J return vijs, viis - def _estimate_inplane_rotations(self, vis): - """ - Estimate the rotation matrices for each image by constructing arbitrary rotation matrices - populated with the given third rows, vis, and then rotating by an appropriate in-plane rotation. - - :param vis: An n_imgx3 array where the i'th row holds the estimate for the third row of - the i'th rotation matrix. - - :return: Rotation matrices Ris and in-plane rotation matrices R_thetas, both size n_imgx3x3. - """ - pf = self.pf - n_img = self.n_img - n_theta = self.n_theta - max_shift_1d = self.max_shift - shift_step = self.shift_step - order = self.order - degree_res = self.degree_res - - # Step 1: Construct all rotation matrices Ri_tildes whose third rows are equal to - # the corresponding third rows vis. - Ri_tildes = self._complete_third_row_to_rot(vis) - - # Step 2: Construct all in-plane rotation matrices, R_theta_ijs. - max_angle = (360 // order) * order - theta_ijs = np.arange(0, max_angle, degree_res) * np.pi / 180 - R_theta_ijs = Rotation.about_axis("z", theta_ijs, dtype=self.dtype).matrices - - # Step 3: Compute the correlation over all shifts. - # Generate shifts. - r_max = pf.shape[-1] - shifts, shift_phases, _ = self._generate_shift_phase_and_filter( - r_max, max_shift_1d, shift_step - ) - n_shifts = len(shifts) - - # Q is the n_img x n_img Hermitian matrix defined by Q = q*q^H, - # where q = (exp(i*order*theta_0), ..., exp(i*order*theta_{n_img-1}))^H, - # and theta_i in [0, 2pi/order) is the in-plane rotation angle for the i'th image. - Q = np.zeros((n_img, n_img), dtype=complex) - - # Reconstruct the full polar Fourier for use in correlation. self.pf only consists of - # rays in the range [180, 360), with shape (n_img, n_theta//2, n_rad-1). - pf = PolarFT.half_to_full(pf) - - # Normalize rays. - pf /= norm(pf, axis=-1)[..., np.newaxis] - - n_pairs = n_img * (n_img - 1) // 2 - with tqdm(total=n_pairs) as pbar: - idx = 0 - # Note: the ordering of i and j in these loops should not be changed as - # they correspond to the ordered tuples (i, j), for i= 1e-5 - - # If the third row coincides with the z-axis we return the identity matrix. - rots[~mask] = np.eye(3, dtype=r3.dtype) - - # 'norm_12' is non-zero since r3 does not coincide with the z-axis. - norm_12 = np.sqrt(r3[mask, 0] ** 2 + r3[mask, 1] ** 2) - - # Populate 1st rows with vector orthogonal to row 3. - rots[mask, 0, 0] = r3[mask, 1] / norm_12 - rots[mask, 0, 1] = -r3[mask, 0] / norm_12 - - # Populate 2nd rows such that r3 = r1 x r2 - rots[mask, 1, 0] = r3[mask, 0] * r3[mask, 2] / norm_12 - rots[mask, 1, 1] = r3[mask, 1] * r3[mask, 2] / norm_12 - rots[mask, 1, 2] = -norm_12 - - if singleton: - rots = rots.reshape(3, 3) - - return rots - - @staticmethod - def cl_angles_to_ind(cl_angles, n_theta): - thetas = np.arctan2(cl_angles[:, 1], cl_angles[:, 0]) - - # Shift from [-pi,pi] to [0,2*pi). - thetas = np.mod(thetas, 2 * np.pi) - - # linear scale from [0,2*pi) to [0,n_theta). - ind = np.mod(np.round(thetas / (2 * np.pi) * n_theta), n_theta).astype(int) - - # Return scalar for single value. - if ind.size == 1: - ind = ind.flat[0] - - return ind - @staticmethod def g_sync(rots, order, rots_gt): """ diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index a1440cadc3..e0d69cb086 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -3,7 +3,7 @@ import numpy as np from numpy.linalg import norm -from aspire.abinitio import CLSymmetryC3C4 +from aspire.abinitio import CLSymmetryC3C4, cl_angles_to_ind, complete_third_row_to_rot from aspire.operators import PolarFT from aspire.utils import ( J_conjugate, @@ -298,8 +298,8 @@ def relative_rots_to_cl_indices(relative_rots, n_theta): c1s = np.array((-relative_rots[:, 1, 2], relative_rots[:, 0, 2])).T c2s = np.array((relative_rots[:, 2, 1], -relative_rots[:, 2, 0])).T - c1s = CLSymmetryC3C4.cl_angles_to_ind(c1s, n_theta) - c2s = CLSymmetryC3C4.cl_angles_to_ind(c2s, n_theta) + c1s = cl_angles_to_ind(c1s, n_theta) + c2s = cl_angles_to_ind(c2s, n_theta) inds = np.where(c1s >= n_theta // 2) c1s[inds] -= n_theta // 2 @@ -331,7 +331,7 @@ def generate_candidate_rots(n, equator_threshold, order, degree_res, seed): while counter < n: third_row = randn(3) third_row /= anorm(third_row, axes=(-1,)) - Ri_tilde = CLSymmetryC3C4._complete_third_row_to_rot(third_row) + Ri_tilde = complete_third_row_to_rot(third_row) # Exclude candidates that represent equator images. Equator candidates # induce collinear self-common-lines, which always have perfect correlation. diff --git a/src/aspire/abinitio/commonline_utils.py b/src/aspire/abinitio/commonline_utils.py index 4a0e88bd5b..bfd1e5ce5f 100644 --- a/src/aspire/abinitio/commonline_utils.py +++ b/src/aspire/abinitio/commonline_utils.py @@ -1,8 +1,12 @@ +import logging + import numpy as np -from numpy.linalg import eigh +from numpy.linalg import eigh, norm + +from aspire.operators import PolarFT +from aspire.utils import Rotation, all_pairs, anorm, tqdm -from aspire.utils.matrix import anorm -from aspire.utils.misc import all_pairs +logger = logging.getLogger(__name__) def estimate_third_rows(vijs, viis): @@ -53,3 +57,197 @@ def estimate_third_rows(vijs, viis): vis /= anorm(vis, axes=(-1,))[:, np.newaxis] return vis + + +def estimate_inplane_rotations(cl_class, vis): + """ + Estimate the rotation matrices for each image by constructing arbitrary rotation matrices + populated with the given third rows, vis, and then rotating by an appropriate in-plane rotation. + + :cl_class: A commonlines class instance. + :param vis: An n_imgx3 array where the i'th row holds the estimate for the third row of + the i'th rotation matrix. + + :return: Rotation matrices Ris and in-plane rotation matrices R_thetas, both size n_imgx3x3. + """ + pf = cl_class.pf + n_img = cl_class.n_img + n_theta = cl_class.n_theta + max_shift_1d = cl_class.max_shift + shift_step = cl_class.shift_step + order = cl_class.order + degree_res = cl_class.degree_res + + # Step 1: Construct all rotation matrices Ri_tildes whose third rows are equal to + # the corresponding third rows vis. + Ri_tildes = complete_third_row_to_rot(vis) + + # Step 2: Construct all in-plane rotation matrices, R_theta_ijs. + max_angle = (360 // order) * order + theta_ijs = np.arange(0, max_angle, degree_res) * np.pi / 180 + R_theta_ijs = Rotation.about_axis("z", theta_ijs, dtype=cl_class.dtype).matrices + + # Step 3: Compute the correlation over all shifts. + # Generate shifts. + r_max = pf.shape[-1] + shifts, shift_phases, _ = cl_class._generate_shift_phase_and_filter( + r_max, max_shift_1d, shift_step + ) + n_shifts = len(shifts) + + # Q is the n_img x n_img Hermitian matrix defined by Q = q*q^H, + # where q = (exp(i*order*theta_0), ..., exp(i*order*theta_{n_img-1}))^H, + # and theta_i in [0, 2pi/order) is the in-plane rotation angle for the i'th image. + Q = np.zeros((n_img, n_img), dtype=complex) + + # Reconstruct the full polar Fourier for use in correlation. cl_class.pf only consists of + # rays in the range [180, 360), with shape (n_img, n_theta//2, n_rad-1). + pf = PolarFT.half_to_full(pf) + + # Normalize rays. + pf /= norm(pf, axis=-1)[..., np.newaxis] + + n_pairs = n_img * (n_img - 1) // 2 + with tqdm(total=n_pairs) as pbar: + idx = 0 + # Note: the ordering of i and j in these loops should not be changed as + # they correspond to the ordered tuples (i, j), for i= 1e-5 + + # If the third row coincides with the z-axis we return the identity matrix. + rots[~mask] = np.eye(3, dtype=r3.dtype) + + # 'norm_12' is non-zero since r3 does not coincide with the z-axis. + norm_12 = np.sqrt(r3[mask, 0] ** 2 + r3[mask, 1] ** 2) + + # Populate 1st rows with vector orthogonal to row 3. + rots[mask, 0, 0] = r3[mask, 1] / norm_12 + rots[mask, 0, 1] = -r3[mask, 0] / norm_12 + + # Populate 2nd rows such that r3 = r1 x r2 + rots[mask, 1, 0] = r3[mask, 0] * r3[mask, 2] / norm_12 + rots[mask, 1, 1] = r3[mask, 1] * r3[mask, 2] / norm_12 + rots[mask, 1, 2] = -norm_12 + + if singleton: + rots = rots.reshape(3, 3) + + return rots + + +def cl_angles_to_ind(cl_angles, n_theta): + thetas = np.arctan2(cl_angles[:, 1], cl_angles[:, 0]) + + # Shift from [-pi,pi] to [0,2*pi). + thetas = np.mod(thetas, 2 * np.pi) + + # linear scale from [0,2*pi) to [0,n_theta). + ind = np.mod(np.round(thetas / (2 * np.pi) * n_theta), n_theta).astype(int) + + # Return scalar for single value. + if ind.size == 1: + ind = ind.flat[0] + + return ind diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index aa44fcfdfb..d7c4c4716f 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -7,6 +7,8 @@ CLSymmetryC2, CLSymmetryC3C4, CLSymmetryCn, + cl_angles_to_ind, + complete_third_row_to_rot, estimate_third_rows, ) from aspire.abinitio.commonline_cn import MeanOuterProductEstimator @@ -509,7 +511,7 @@ def test_complete_third_row(dtype): r3[0] = np.array([0, 0, 1], dtype=dtype) # Generate rotations. - R = CLSymmetryC3C4._complete_third_row_to_rot(r3) + R = complete_third_row_to_rot(r3) # Assert that first rotation is the identity matrix. assert np.allclose(R[0], np.eye(3, dtype=dtype)) @@ -640,10 +642,6 @@ def _gt_cl_c2(n_theta, rots_gt): U = Ri.T @ g @ Rj c1 = np.array([-U[1, 2], U[0, 2]]) c2 = np.array([U[2, 1], -U[2, 0]]) - clmatrix_gt[idx, i, j] = CLSymmetryC3C4.cl_angles_to_ind( - c1[np.newaxis, :], n_theta - ) - clmatrix_gt[idx, j, i] = CLSymmetryC3C4.cl_angles_to_ind( - c2[np.newaxis, :], n_theta - ) + clmatrix_gt[idx, i, j] = cl_angles_to_ind(c1[np.newaxis, :], n_theta) + clmatrix_gt[idx, j, i] = cl_angles_to_ind(c2[np.newaxis, :], n_theta) return clmatrix_gt From 89dfbdb02d72ccb249855d501186f634bc0c9028 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 25 Aug 2025 11:29:18 -0400 Subject: [PATCH 3/4] migrate syncmatrix_ij_vote_3n to SyncVotingMixin. Remove C3C4 implimentation. --- src/aspire/abinitio/commonline_c3_c4.py | 29 --------------------- src/aspire/abinitio/commonline_sync3n.py | 31 ----------------------- src/aspire/abinitio/sync_voting.py | 32 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 60 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index bd9522e865..af74dc6f6c 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -369,35 +369,6 @@ def _estimate_all_Rijs_c3_c4(self, clmatrix): return Rijs - def _syncmatrix_ij_vote_3n(self, clmatrix, i, j, k_list, n_theta): - """ - Compute the (i,j) rotation block of the synchronization matrix using voting method - - Given the common lines matrix `clmatrix`, a list of images specified in k_list - and the number of common lines n_theta, find the (i, j) rotation block Rij. - - :param clmatrix: The common lines matrix - :param i: The i image - :param j: The j image - :param k_list: The list of images for the third image for voting algorithm - :param n_theta: The number of points in the theta direction (common lines) - :return: The (i,j) rotation block of the synchronization matrix - """ - _, good_k = self._vote_ij(clmatrix, n_theta, i, j, k_list) - - rots = self._rotratio_eulerangle_vec(clmatrix, i, j, good_k, n_theta) - - if rots is not None: - rot_mean = np.mean(rots, 0) - - else: - # This is for the case that images i and j correspond to the same - # viewing direction and differ only by in-plane rotation. - # We set to zero as in the Matlab code. - rot_mean = np.zeros((3, 3)) - - return rot_mean - def _local_J_sync_c3_c4(self, Rijs, Riis): """ Estimate viis and vijs. In order to estimate vij = vi @ vj.T, it is necessary for Rii, Rjj, diff --git a/src/aspire/abinitio/commonline_sync3n.py b/src/aspire/abinitio/commonline_sync3n.py index 841f9dfceb..39f57ab978 100644 --- a/src/aspire/abinitio/commonline_sync3n.py +++ b/src/aspire/abinitio/commonline_sync3n.py @@ -969,37 +969,6 @@ def _estimate_all_Rijs_host(self, clmatrix): return Rijs - def _syncmatrix_ij_vote_3n(self, clmatrix, i, j, k_list, n_theta): - """ - Compute the (i,j) rotation block of the synchronization matrix using voting method - - Given the common lines matrix `clmatrix`, a list of images specified in k_list - and the number of common lines n_theta, find the (i, j) rotation block Rij. - - :param clmatrix: The common lines matrix - :param i: The i image - :param j: The j image - :param k_list: The list of images for the third image for voting algorithm - :param n_theta: The number of points in the theta direction (common lines) - :return: The (i,j) rotation block of the synchronization matrix - """ - alphas, good_k = self._vote_ij(clmatrix, n_theta, i, j, k_list, sync=True) - - angles = np.zeros(3) - - if alphas is not None: - angles[0] = clmatrix[i, j] * 2 * np.pi / n_theta + np.pi / 2 - angles[1] = np.mean(alphas) - angles[2] = -np.pi / 2 - clmatrix[j, i] * 2 * np.pi / n_theta - rot = Rotation.from_euler(angles).matrices - - else: - # This is for the case that images i and j correspond to the same - # viewing direction and differ only by in-plane rotation. - # We set to zero as in the Matlab code. - rot = np.zeros((3, 3)) - - return rot ####################################### # Secondary Methods for Global J Sync # diff --git a/src/aspire/abinitio/sync_voting.py b/src/aspire/abinitio/sync_voting.py index 651ba35c5e..b866f1e8ff 100644 --- a/src/aspire/abinitio/sync_voting.py +++ b/src/aspire/abinitio/sync_voting.py @@ -13,6 +13,38 @@ class SyncVotingMixin(object): which are shared by CLSynVoting and CLSymmetryC3C4 """ + def _syncmatrix_ij_vote_3n(self, clmatrix, i, j, k_list, n_theta): + """ + Compute the (i,j) rotation block of the synchronization matrix using voting method + + Given the common lines matrix `clmatrix`, a list of images specified in k_list + and the number of common lines n_theta, find the (i, j) rotation block Rij. + + :param clmatrix: The common lines matrix + :param i: The i image + :param j: The j image + :param k_list: The list of images for the third image for voting algorithm + :param n_theta: The number of points in the theta direction (common lines) + :return: The (i,j) rotation block of the synchronization matrix + """ + alphas, good_k = self._vote_ij(clmatrix, n_theta, i, j, k_list, sync=True) + + angles = np.zeros(3) + + if alphas is not None: + angles[0] = clmatrix[i, j] * 2 * np.pi / n_theta + np.pi / 2 + angles[1] = np.mean(alphas) + angles[2] = -np.pi / 2 - clmatrix[j, i] * 2 * np.pi / n_theta + rot = Rotation.from_euler(angles).matrices + + else: + # This is for the case that images i and j correspond to the same + # viewing direction and differ only by in-plane rotation. + # We set to zero as in the Matlab code. + rot = np.zeros((3, 3)) + + return rot + def _rotratio_eulerangle_vec(self, clmatrix, i, j, good_k, n_theta): """ Compute the rotation that takes image i to image j From 06283962f6ea54a094a770f332b6f95f2c779ec5 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 26 Aug 2025 15:01:35 -0400 Subject: [PATCH 4/4] remove unused import --- src/aspire/abinitio/commonline_sync3n.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/aspire/abinitio/commonline_sync3n.py b/src/aspire/abinitio/commonline_sync3n.py index 39f57ab978..25bfd63015 100644 --- a/src/aspire/abinitio/commonline_sync3n.py +++ b/src/aspire/abinitio/commonline_sync3n.py @@ -7,15 +7,7 @@ from scipy.optimize import curve_fit from aspire.abinitio import CLOrient3D, SyncVotingMixin -from aspire.utils import ( - J_conjugate, - Rotation, - all_pairs, - nearest_rotations, - random, - tqdm, - trange, -) +from aspire.utils import J_conjugate, all_pairs, nearest_rotations, random, tqdm, trange from aspire.utils.matlab_compat import stable_eigsh logger = logging.getLogger(__name__) @@ -969,7 +961,6 @@ def _estimate_all_Rijs_host(self, clmatrix): return Rijs - ####################################### # Secondary Methods for Global J Sync # #######################################