Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
151 commits
Select commit Hold shift + click to select a range
68e171f
Initial integration of NUG into abinitio class.
j-c-c Feb 27, 2026
f206b40
isort, black
j-c-c Feb 27, 2026
dfd701c
Use numeric to handle cupy/numpy. Remove duplicate function.
j-c-c Mar 6, 2026
153c3f8
tox
j-c-c Mar 9, 2026
6097ffc
build full pf
j-c-c Mar 12, 2026
32ec134
compute_fejer_weights method. Still need to optimize (very slow)
j-c-c Apr 7, 2026
5b0250c
vectorize Wigner matrix comps. Vectorize fejer_weights comp.
j-c-c Apr 8, 2026
4558b19
Add SO3 grid generation
j-c-c Apr 16, 2026
d32fcb9
tox
j-c-c Apr 17, 2026
6f0006c
use aspire symmetry parsing
j-c-c Apr 20, 2026
3c630e0
Use aspire ZYZ rotation convention
j-c-c Apr 20, 2026
f5caebd
vectorize psd_projection, transform_block, transform_back_block
j-c-c Apr 21, 2026
f134b01
Vectorize update_S. 2x speedup!
j-c-c Apr 22, 2026
864a2ba
vectorize print update component.
j-c-c Apr 22, 2026
a1022b8
intial add of proximal_refine
j-c-c Apr 24, 2026
b680ff9
Update base class. Fix Proximal Refinement bug. Add PR update diagnos…
j-c-c Apr 30, 2026
22831d6
Add euler_est_Dm
j-c-c May 5, 2026
d700d69
Use in-house polarFT and Sinogram. Add symmetry handling and logs
j-c-c May 13, 2026
e1b4f37
cleanup
j-c-c May 13, 2026
1f5b6b8
Use saff-kuijlaars method for S2 grid
j-c-c May 14, 2026
fef88c8
initial test file
j-c-c May 15, 2026
e04864e
clean up estimate_rotations
j-c-c May 15, 2026
a8b8f7f
Explicit dtypes. Add dtype test. Add estimate_rotations test.
j-c-c May 19, 2026
7e73f7f
Support shifts
j-c-c May 22, 2026
48dfa14
test shifts
j-c-c May 26, 2026
063350a
Test Dn symmetry
j-c-c May 26, 2026
ecdef9a
move compare_rots_sym to utils
j-c-c May 29, 2026
d2fe08e
Add generalized g_sync
j-c-c Jun 4, 2026
a0fceff
tox
j-c-c Jun 4, 2026
7f46984
Add g_sync test
j-c-c Jun 8, 2026
5046b3f
Use new g_sync in D2 test. Remove unused method
j-c-c Jun 8, 2026
7ea89f3
remove unused imports
j-c-c Jun 8, 2026
c63fe4a
remove fast_radon_transform and wemd code path
j-c-c Jun 9, 2026
7fede11
cleanup
j-c-c Jun 9, 2026
50f1d4d
Add function docstrings
j-c-c Jun 10, 2026
1ce900e
Add param descrips for init
j-c-c Jun 15, 2026
7ec0207
more params. cleanup.
j-c-c Jun 15, 2026
0692245
remove commented out code
j-c-c Jun 15, 2026
b947228
Test g_sync for clean case
j-c-c Jun 17, 2026
0d813ab
xp in admm
j-c-c Jun 23, 2026
77b23d6
raise on unsupported symmetry
j-c-c Jun 23, 2026
9e3c27c
mark tests expensive. Add smoke test.
j-c-c Jun 23, 2026
36a5dbb
tunable proximal refinement iterations
j-c-c Jun 24, 2026
d1de07f
code coverage for proximal refinement and print_update computations.
j-c-c Jun 25, 2026
ecfbfb9
test compare_rots_sym
j-c-c Jun 25, 2026
5c2a915
test symmetry handling log messages
j-c-c Jun 25, 2026
552ee51
remove singleton handling for block transformations
j-c-c Jun 25, 2026
87bcbff
fij loss funtion docstring
j-c-c Jun 25, 2026
30c307a
Add Defaults to param descriptions
j-c-c Jun 29, 2026
8e4990b
add vectorized computation. some cleanup.
j-c-c Jun 29, 2026
4de983b
fix test docstring
j-c-c Jun 29, 2026
efa5599
remove time
j-c-c Jun 29, 2026
1aea6c3
cleanup index creation
j-c-c Jun 29, 2026
4b02090
docstrings and code comments for main ADMM step
j-c-c Jun 30, 2026
d69e204
docstring for compare_rots_sym
j-c-c Jun 30, 2026
fe5911b
compute_coeff code comments
j-c-c Jul 1, 2026
1e52681
proximal_refine code commentary
j-c-c Jul 1, 2026
2db60f4
some cleanup
j-c-c Jul 1, 2026
bf1030a
missing xp's in print_update
j-c-c Jul 28, 2026
eac5698
add reference for g_sync_finite_group method
j-c-c Jul 29, 2026
6b5f16e
Initial port of C1 admm
j-c-c Aug 13, 2026
897c213
build clmatrix from X_est for C1 euler est
j-c-c Aug 14, 2026
ed19b76
populate upper tri with clmatrix loop. Port scalar rotratio_eulerangle.
j-c-c Aug 18, 2026
1d41afc
tox
j-c-c Aug 18, 2026
eabd1d6
fix symmetry log message/test
j-c-c Aug 19, 2026
984a51f
port of matlab cryo_syncmatrix
j-c-c Aug 19, 2026
2687459
tox
j-c-c Aug 19, 2026
6a7323d
move syncrotations to sync_voting.py
j-c-c Aug 20, 2026
8b80350
add syncmatrix_from_X1
j-c-c Aug 24, 2026
6d2f216
Add form_ground_truth_X
j-c-c Aug 24, 2026
d68e8b8
Add C1 NUG to testing
j-c-c Aug 25, 2026
92a7e3c
remove yutong implementation
j-c-c Aug 25, 2026
97683ad
fix smoke test
j-c-c Aug 26, 2026
e7edb08
supress expected gimbal-lock warning
j-c-c Aug 26, 2026
2984df3
Remove yutong rounding procedure and associated methods
j-c-c Aug 26, 2026
d0ec0f4
cleanup/rename rotation recovery procedure
j-c-c Aug 26, 2026
a5c7d46
de-duplicate euler estimation code with helper functions
j-c-c Aug 26, 2026
1a241cd
dosctrings for helper functions
j-c-c Aug 26, 2026
987a687
Add code comments to euler_est_Cm
j-c-c Aug 26, 2026
b15e452
code comments for euler_est_Dm
j-c-c Aug 26, 2026
7a429eb
code comments for _estimate_gamma
j-c-c Aug 27, 2026
d420689
use k for degree everywhere
j-c-c Aug 31, 2026
8150028
helper function _project_representation_blocks
j-c-c Aug 31, 2026
7825d95
organize helper function sections
j-c-c Aug 31, 2026
502cf18
Add NUG dataset to downloader. Add gallery experiment reproducing pap…
j-c-c Sep 9, 2026
7658c6d
uncomment admm random update order
j-c-c Sep 9, 2026
d298cab
disable shift search space in gallery
j-c-c Sep 10, 2026
e1bc5dc
tox
j-c-c Sep 10, 2026
bc2285a
Add gallery for D3 experiment. Add D3 dataset to downloader.
j-c-c Sep 16, 2026
eecfaa3
remove unused import
j-c-c Sep 16, 2026
7073929
handle no proximal refine for C1 in init. Add test.
j-c-c Sep 17, 2026
fa5596a
test docstring
j-c-c Sep 17, 2026
dfb4ffc
update init docstring
j-c-c Sep 17, 2026
b1ea62f
_largest_eigenvalue docs
j-c-c Sep 17, 2026
53e8fd5
psd_projection docs
j-c-c Sep 17, 2026
bcd70cf
vec/mat_block docs
j-c-c Sep 17, 2026
7662262
transform_block/transform_back_block docs
j-c-c Sep 17, 2026
5873113
docs construct_AEq, WD, Wd
j-c-c Sep 18, 2026
bf57387
Fix indexing and variable naming for AE and bE construction
j-c-c Sep 18, 2026
2a1bdc8
fix more indexing. Use k for wigner degree and k_idx for python indexing
j-c-c Sep 18, 2026
8ebe202
cleanup indexing fun_AE/AET
j-c-c Sep 21, 2026
6e7ce0f
cleanup indexing update_S
j-c-c Sep 21, 2026
155640f
cleanup indexing update_yE
j-c-c Sep 21, 2026
fd962ad
cleanup indexing in print_updates
j-c-c Sep 21, 2026
885c6de
typos, cleanup
j-c-c Sep 21, 2026
31e9380
cleanup indexing asymmetric admm
j-c-c Sep 21, 2026
9e02ccf
Fix indexing bug in Xnorm. Expose missing volumes in downloader init …
j-c-c Sep 29, 2026
22ac4cd
Indexing for AI_mat_(off)diag computation loops
j-c-c Sep 29, 2026
9098a82
Indexing for compute_rank
j-c-c Sep 29, 2026
c95a9a3
update indexing in compute_coeff
j-c-c Sep 29, 2026
596c874
Indexing in compute_fejer_weights
j-c-c Sep 29, 2026
443112f
indexing in proximal_refine
j-c-c Sep 29, 2026
fe8eac0
Roundtrip test for mat_block/vec_block
j-c-c Oct 1, 2026
5c3cf4f
Add transform_block roundtrip test.
j-c-c Oct 1, 2026
4d97176
Row-wise ADMM block packing
j-c-c Oct 1, 2026
56a6d50
update docstrings column-wise -> row-wise
j-c-c Oct 1, 2026
fb7d81c
remove unnecessary transposes in eye().T
j-c-c Oct 1, 2026
d49fd41
remove unecessary xp.vdot and xp.ones call. replace with xp.sum
j-c-c Oct 6, 2026
b7cb276
host-scalar cleanup
j-c-c Oct 6, 2026
94166c8
keep compute_rank on CPU
j-c-c Oct 6, 2026
fdc1cb7
cleanup right-hand side, bE, to remove small xp.eye calls
j-c-c Oct 6, 2026
726f62b
cleanup asymmetric bE0/1 construction.
j-c-c Oct 6, 2026
2c98d3c
Remove unecessary xp.copy()
j-c-c Oct 6, 2026
892f0c6
tox
j-c-c Oct 6, 2026
039c8e2
cleanup AEk construction. Remove xp.eye()
j-c-c Oct 6, 2026
9d45718
cleanup bEq construction. remove xp.eye().
j-c-c Oct 6, 2026
68a74b8
keep small array operations on cpu in permutek
j-c-c Oct 6, 2026
baf69d4
permutek_back, keep small array operations on cpu.
j-c-c Oct 7, 2026
6a800aa
remove unecessary use of transform_coeff on identity matrices
j-c-c Oct 7, 2026
3c8c43f
Add trange to ADMM loop. Use f-strings for all logging.
j-c-c Oct 8, 2026
ca7befc
add progress bars to coefficient computation
j-c-c Oct 8, 2026
cf3d19b
Gallery example for C1 experiment
j-c-c Oct 8, 2026
7b1230a
add gallery print error to tox.ini
j-c-c Oct 8, 2026
5aba7b9
Clean up gallery. Use full size simulation with noise, then downsample.
j-c-c Oct 12, 2026
c379885
black
j-c-c Oct 12, 2026
405c78d
Add dataset references
j-c-c Oct 12, 2026
c5b4f5e
staticmethods
j-c-c Oct 13, 2026
78b7dcc
Make helpers private
j-c-c Oct 13, 2026
60f2d51
Rename remaining helpers as private
j-c-c Oct 13, 2026
de1fbe3
return preprocessing variables as dict and unpack with descriptions
j-c-c Oct 14, 2026
1900546
remove explicit reference to manuscript equation numbers
j-c-c Oct 14, 2026
cc58640
remove unused variable
j-c-c Oct 15, 2026
4527bc5
build ADMM permutation matrics on CPU
j-c-c Oct 15, 2026
5f64cab
Replace dense ADMM permutation matrices with index-based grouping
j-c-c Oct 15, 2026
1e8b668
Replace dense inverse ADMM permutation matrices with index-based reor…
j-c-c Oct 16, 2026
01da9dd
Improve docstrings for _permutek/_permutek_back
j-c-c Oct 16, 2026
ff808d1
Replace _largest_eigenvalue power method with eigvalsh. Use 3.58GiB l…
j-c-c Oct 16, 2026
bb3bf5d
vectorize pairwise loss function (fij)
j-c-c Oct 19, 2026
e73f7bb
extract nested functions from rotation recovery main methods.
j-c-c Oct 19, 2026
386362a
replace complex_type() with np.complex128
j-c-c Oct 19, 2026
258cf44
typo
j-c-c Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions gallery/experiments/experimental_nug_pipeline_10081.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
"""
Symmetric NUG Pipeline
======================

This notebook demonstrates reproducing simliar results to the
experiment found in:

.. admonition:: Publication

| Shi, Y., Singer, A., Wang, J., & Yang, R.
| Orientation estimation of cryo-EM images for molecules with
| arbitrary symmetry by semidefinite programming

This pipeline begins with 17 class averages that were generated (as described
in the publication) from the EMPIAR 10081 picked particle data, available here:

https://www.ebi.ac.uk/empiar/EMPIAR-10081

First, orientation estimation is performed using the Non-Unique Games
algorithm adapted for handling symmetric particles. A volume is then
reconstructed from the estimated orientations and saved to an output
file for inspection. The reconstructed volume can be compared with the
EMPIAR-10081 associated reconstruction found here:

https://www.ebi.ac.uk/emdb/EMD-8511
"""

# %%
# Imports
# -------
# First import the necessary utilities and ASPIRE
# classes used throughout the notebook.
import logging

from aspire.abinitio import CommonlineNUG
from aspire.downloader import nug_10081
from aspire.reconstruction import MeanEstimator
from aspire.source import ArrayImageSource, OrientedSource

logger = logging.getLogger(__name__)


# %%
# Setup
# -----

# Note: Use of GPU is recommended for the NUG algorithm.
# Below we setup some output files to save off orientation
# and reconstruction results.
oriented_fn = "nug_10081_oriented.star"
volume_output_fn = "nug_10081_abinitio.mrc"


# %%
# Load the Dataset
# ----------------
#
# The 17 class averages can be accessed as an ASPIRE ``Image`` object
# via the built-in downloader utility.

logger.info("Load Precomputed Class Averages")
ims = nug_10081()
avgs = ArrayImageSource(ims, pixel_size=1.3, symmetry_group="C4")


# %%
# Orientation Estimation
# ----------------------
#
# Create an orientation estimation object for the ``avgs``.
# The ``CommonlineNUG`` algorithm will detect the symmetry from
# the ``avgs`` metadata and use the approriate NUG ADMM-solver to estimate
# orientations. To replicate the original experiment as closely as possible,
# we use the 15 iterations of proximal refinement and disable searching over
# the commonline shift space.

logger.info("Begin Orientation Estimation")
orient_est = CommonlineNUG(avgs, max_shift=0, pr_iters=15)

# Create an ``OrientedSource`` class instance that performs orientation
# estimation in a lazy fashion upon request of images or rotations.
oriented_src = OrientedSource(avgs, orient_est)

# Estimate orientations and override the estimated shifts.
# In the case of insufficient number of images, estimating shifts fails.
oriented_src.rotations
oriented_src = oriented_src.update(offsets=0)

# Confirm offsets have been set to zero.
logger.info(f"Offsets: {oriented_src.offsets}")

# Save oriented source.
oriented_src.save(oriented_fn)

# %%
# Volume Reconstruction
# ----------------------
#
# Using the oriented source, attempt to reconstruct a volume. Since
# this is a C4 symmetric molecule the ``symmetry_group`` source attribute
# will flow through the pipeline to ``oriented_src``. Then each image will be
# repeated for each of the 3 additional rotations during
# back-projection. In general, this boosts the effective number of images used in
# the reconstruction from ``n_imgs`` to ``4 * n_imgs``, or from 17 to 68 in this case.

# Setup an estimator to perform the back projection.
logger.info("Begin Volume reconstruction")
estimator = MeanEstimator(oriented_src)

# Perform the estimation and save the reconstructed volume.
est_vol = estimator.estimate()
est_vol.save(volume_output_fn)
111 changes: 111 additions & 0 deletions gallery/experiments/experimental_nug_pipeline_12036.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
Dn-Symmetric NUG Pipeline
=========================

This notebook demonstrates reproducing simliar results to the
experiment found in:

.. admonition:: Publication

| Shi, Y., Singer, A., Wang, J., & Yang, R.
| Orientation estimation of cryo-EM images for molecules with
| arbitrary symmetry by semidefinite programming

This pipeline begins with 93 class averages that were generated (as described
in the publication) from the EMPIAR 12036 picked particle data, available here:

https://www.ebi.ac.uk/empiar/EMPIAR-12036

First, orientation estimation is performed using the Non-Unique Games
algorithm adapted for handling symmetric particles. A volume is then
reconstructed from the estimated orientations and saved to an output
file for inspection. The reconstructed volume can be compared with the
EMPIAR-12036 associated reconstruction found here:

https://www.ebi.ac.uk/emdb/EMD-14803
"""

# %%
# Imports
# -------
# First import the necessary utilities and ASPIRE
# classes used throughout the notebook.
import logging

from aspire.abinitio import CommonlineNUG
from aspire.downloader import nug_12036
from aspire.reconstruction import MeanEstimator
from aspire.source import ArrayImageSource, OrientedSource

logger = logging.getLogger(__name__)


# %%
# Setup
# -----

# Note: Use of GPU is recommended for the NUG algorithm.
# Below we setup some output files to save off orientation
# and reconstruction results.
oriented_fn = "nug_12036_oriented.star"
volume_output_fn = "nug_12036_abinitio.mrc"


# %%
# Load the Dataset
# ----------------
#
# The 93 class averages can be accessed as an ASPIRE ``Image`` object
# via the built-in downloader utility.
logger.info("Load Precomputed Class Averages")
ims = nug_12036()
avgs = ArrayImageSource(ims, symmetry_group="D3")


# %%
# Orientation Estimation
# ----------------------
#
# Create an orientation estimation object for the ``avgs``.
# The ``CommonlineNUG`` algorithm will detect the symmetry from
# the ``avgs`` metadata and use the approriate NUG ADMM-solver to estimate
# orientations. To replicate the original experiment as closely as possible,
# we use the 5 iterations of proximal refinement and disable searching over
# the commonline shift space.

logger.info("Begin Orientation Estimation")
orient_est = CommonlineNUG(avgs, max_shift=0, pr_iters=5)

# Create an ``OrientedSource`` class instance that performs orientation
# estimation in a lazy fashion upon request of images or rotations.
oriented_src = OrientedSource(avgs, orient_est)

# Estimate orientations and override the estimated shifts.
# In the case of insufficient number of images, estimating shifts fails.
oriented_src.rotations
oriented_src = oriented_src.update(offsets=0)

# Confirm offsets have been set to zero.
logger.info(f"Offsets: {oriented_src.offsets}")

# Save oriented source.
oriented_src.save(oriented_fn)

# %%
# Volume Reconstruction
# ----------------------
#
# Using the oriented source, attempt to reconstruct a volume. Since
# this is a D3 symmetric molecule the ``symmetry_group`` source attribute
# will flow through the pipeline to ``oriented_src``. Then each image will be
# repeated for each of the 5 additional symmetry induced rotations during
# back-projection. In general, this boosts the effective number of images used in
# the reconstruction from ``n_imgs`` to ``6 * n_imgs``, or from 93 to 558 in this case.

# Setup an estimator to perform the back projection.
logger.info("Begin Volume reconstruction")
estimator = MeanEstimator(oriented_src)

# Perform the estimation and save the reconstructed volume.
est_vol = estimator.estimate()
est_vol.save(volume_output_fn)
120 changes: 120 additions & 0 deletions gallery/experiments/experimental_nug_simulated_pipeline_10028.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
C1 NUG Rotation Accuracy on Simulated EMDB-2660 Images
======================================================

Estimate orientations of noisy, simulated projections of EMDB-2660 using
the asymmetric (C1) NUG method. Compare the estimated rotations with the
known simulation rotations at several signal-to-noise ratios (SNRs). A
volume is reconstructed and saved off for comparison against the generating
volume.

EMDB-2660 is the volume entry reconstructed from the experimental dataset
EMPIAR 10028, both available here:

https://www.ebi.ac.uk/emdb/EMD-2660
https://www.ebi.ac.uk/empiar/EMPIAR-10028

This experiment is based on:

.. admonition:: Reference

A. S. Bandeira, Y. Chen, R. R. Lederman, and A. Singer,
"Non-unique games over compact groups and orientation estimation
in cryo-EM," Inverse Problems 36(6), 064002 (2020).
https://doi.org/10.1088/1361-6420/ab7d2c

The ASPIRE-Python implementation of NUG differs from the original MATLAB
implementation, so this example is intended to reproduce the experimental
workflow rather than its exact numerical results.
"""

# %%
# Imports
# -------
import logging

import numpy as np

from aspire.abinitio import CommonlineNUG
from aspire.downloader import emdb_2660
from aspire.noise import WhiteNoiseAdder
from aspire.reconstruction import MeanEstimator
from aspire.source import OrientedSource, Simulation
from aspire.utils import Rotation

logger = logging.getLogger(__name__)


# %%
# Experiment settings
# -------------------
# Start with 100 images. The paper also reports results for 500 images and
# SNR ranging from 1 to 1/128, which requires substantially more computation.
SEED = 1980
RESOLUTION = 129
N_IMAGES = (100,)
SNR_VALUES = (1, 1 / 2, 1 / 4, 1 / 8, 1 / 16)

# Download volume once; every SNR uses the same underlying volume.
volume = emdb_2660().astype(np.float64)

# %%
# Estimate rotations at each SNR
# ------------------------------
results = []

for n_images in N_IMAGES:
for snr in SNR_VALUES:
logger.info(f"Estimating rotations: n={n_images}, SNR={snr}")
# Keeping the simulation seed fixed gives corresponding ground-truth
# orientations across SNR conditions.
source = (
Simulation(
n=n_images,
vols=volume,
offsets=0,
amplitudes=1,
seed=SEED,
noise_adder=WhiteNoiseAdder.from_snr(snr=snr),
)
.downsample(RESOLUTION)
.cache()
)

# Initialize the NUG orientation estimator, matching paramters
# to the referenced experiment.
estimator = CommonlineNUG(
source,
symmetry="C1",
max_shift=0,
verbose=False,
)

oriented_source = OrientedSource(source, estimator)
estimated_rotations = oriented_source.rotations

# Rotation.mse registers the estimates to ground truth before
# computing the mean squared error.
mse = Rotation(estimated_rotations).mse(Rotation(source.rotations))
results.append((n_images, snr, mse))

print(f"n={n_images} SNR={snr} rotation MSE={mse}", flush=True)

# The simulation has zero offsets. Use the estimated orientations
# with zero shifts to reconstruct from the cached noisy images.
oriented_source = oriented_source.update(offsets=0)

snr_tag = f"{snr:g}"
volume_filename = f"nug_2660_c1_n{n_images}_snr{snr_tag}_recon.mrc"

logger.info(f"Reconstructing and saving {volume_filename}")
estimated_volume = MeanEstimator(oriented_source).estimate()
estimated_volume.save(volume_filename, overwrite=True)

# %%
# Summary
# -------
print("\nC1 NUG rotation accuracy")
print(f"{'Images':>6} {'SNR':>10} {'MSE':>14}")
for n_images, snr, mse in results:
print(f"{n_images:6d} {snr:10g} {mse:14.6g}")
4 changes: 4 additions & 0 deletions src/aspire/abinitio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@
from .J_sync import JSync
from .commonline_utils import (
build_outer_products,
compare_rots_sym,
g_sync,
g_sync_finite_group,
saff_kuijlaars,
)
from .commonline_base import Orient3D
from .commonline_matrix import CLOrient3D
from .commonline_nug import CommonlineNUG
from .commonline_sdp import CommonlineSDP
from .commonline_lud import CommonlineLUD
from .commonline_irls import CommonlineIRLS
Expand Down
Loading
Loading