From 995a44557eb4befde83d4ee8e73fed31cc063cb5 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 25 Oct 2022 16:09:11 -0400 Subject: [PATCH 001/424] compute_cls_inds --- src/aspire/abinitio/commonline_cn.py | 117 +++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/aspire/abinitio/commonline_cn.py diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py new file mode 100644 index 0000000000..892bacf27b --- /dev/null +++ b/src/aspire/abinitio/commonline_cn.py @@ -0,0 +1,117 @@ +import logging + +import numpy as np +from numpy.linalg import eigh, norm, svd +from tqdm import tqdm + +from aspire.abinitio import CLSymmetryC3C4, SyncVotingMixin +from aspire.utils import ( + J_conjugate, + Rotation, + all_pairs, + all_triplets, + anorm, + cyclic_rotations, + pairs_to_linear, +) +from aspire.utils.random import randn + +logger = logging.getLogger(__name__) + + +class CLSymmetryCn(CLSymmetryC3C4): + def __init__( + self, + src, + symmetry=None, + n_rad=None, + n_theta=None, + max_shift=0.15, + shift_step=1, + epsilon=1e-3, + max_iters=1000, + degree_res=1, + n_points_sphere=500, + seed=None, + ): + + super().__init__( + src, + symmetry=symmetry, + n_rad=n_rad, + n_theta=n_theta, + max_shift=max_shift, + shift_step=shift_step, + epsilon=epsilon, + max_iters=max_iters, + degree_res=degree_res, + seed=seed, + ) + + self.n_points_sphere = n_points_sphere + + # def estimate_relative_viewing_directions_cn(self): + + def compute_cls_inds(self, Ris_tilde, R_theta_ijs): + n_theta = self.n_theta + n_points_sphere = self.n_points_sphere + n_theta_ijs = R_theta_ijs.shape[0] + cij_inds = np.zeros( + (n_points_sphere, n_points_sphere, n_theta_ijs, 2), dtype=np.uint16 + ) + + with tqdm(total=n_points_sphere) as pbar: + for i in range(n_points_sphere): + for j in range(n_points_sphere): + R_cands = Ris_tilde[i].T @ R_theta_ijs @ Ris_tilde[j] + + c1s = np.array((-R_cands[:, 1, 2], R_cands[:, 0, 2])).T + c2s = np.array((-R_cands[:, 2, 1], R_cands[:, 2, 0])).T + + c1s = self.cl_angles_to_ind(c1s, n_theta) + c2s = self.cl_angles_to_ind(c2s, n_theta) + + inds = np.where(c1s >= n_theta // 2) + c1s[inds] -= n_theta // 2 + c2s[inds] += n_theta // 2 + c2s[inds] = np.mod(c2s[inds], n_theta) + + cij_inds[i, j, :, 0] = c1s + cij_inds[i, j, :, 1] = c2s + pbar.update() + return cij_inds + + def generate_cand_rots(self): + # Construct candidate rotations, Ris_tilde. + vis = self.generate_cand_rots_third_rows(self.n_points_sphere) + Ris_tilde = np.array([self._complete_third_row_to_rot(vi) for vi in vis]) + + # Construct all in-plane rotations, R_theta_ijs + theta_ij = np.arange(0, 360, self.degree_res) * np.pi / 180 + R_theta_ijs = Rotation.about_axis("z", theta_ij).matrices + + return Ris_tilde, R_theta_ijs + + def generate_cand_rots_third_rows(self, legacy=True): + n_points_sphere = self.n_points_sphere + + if legacy: + # Genereate random points on the sphere + third_rows = randn(n_points_sphere, 3) + third_rows /= anorm(third_rows, axes=(-1,))[:, np.newaxis] + else: + # Use Fibonocci sphere points + third_rows = np.zeros((n_points_sphere, 3)) + phi = np.pi * (3.0 - np.sqrt(5.0)) # golden angle in radians + + for i in range(n_points_sphere): + y = 1 - (i / float(n_points_sphere - 1)) * 2 # y goes from 1 to -1 + radius = np.sqrt(1 - y * y) # radius at y + + theta = phi * i # golden angle increment + x = np.cos(theta) * radius + z = np.sin(theta) * radius + + third_rows[i] = x, y, z + + return third_rows From c827b98b11c3779edd17baf47e4d7eea12879043 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 27 Oct 2022 15:54:52 -0400 Subject: [PATCH 002/424] symmetry check. compute candidate self-common-lines. --- src/aspire/abinitio/__init__.py | 1 + src/aspire/abinitio/commonline_c3_c4.py | 11 +++-- src/aspire/abinitio/commonline_cn.py | 60 ++++++++++++++++++++----- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/aspire/abinitio/__init__.py b/src/aspire/abinitio/__init__.py index 4adc025ba0..1dc13455a7 100644 --- a/src/aspire/abinitio/__init__.py +++ b/src/aspire/abinitio/__init__.py @@ -4,6 +4,7 @@ # isort: off from .commonline_sync import CLSyncVoting from .commonline_c3_c4 import CLSymmetryC3C4 +from .commonline_cn import CLSymmetryCn # isort: on from .orientation_src import OrientEstSource diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 73a8f71e14..bdd2cdf602 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -74,6 +74,13 @@ def __init__( shift_step=shift_step, ) + self._check_symmetry(symmetry) + self.epsilon = epsilon + self.max_iters = max_iters + self.degree_res = degree_res + self.seed = seed + + def _check_symmetry(self, symmetry): if symmetry is None: raise NotImplementedError( "Symmetry type not supplied. Please indicate C3 or C4 symmetry." @@ -85,10 +92,6 @@ def __init__( f"Only C3 and C4 symmetry supported. {symmetry} was supplied." ) self.order = int(symmetry[1]) - self.epsilon = epsilon - self.max_iters = max_iters - self.degree_res = degree_res - self.seed = seed def estimate_rotations(self): """ diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 892bacf27b..1d57a60f39 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -1,19 +1,10 @@ import logging import numpy as np -from numpy.linalg import eigh, norm, svd from tqdm import tqdm -from aspire.abinitio import CLSymmetryC3C4, SyncVotingMixin -from aspire.utils import ( - J_conjugate, - Rotation, - all_pairs, - all_triplets, - anorm, - cyclic_rotations, - pairs_to_linear, -) +from aspire.abinitio import CLSymmetryC3C4 +from aspire.utils import Rotation, anorm, cyclic_rotations from aspire.utils.random import randn logger = logging.getLogger(__name__) @@ -50,8 +41,53 @@ def __init__( self.n_points_sphere = n_points_sphere - # def estimate_relative_viewing_directions_cn(self): + def _check_symmetry(self, symmetry): + if symmetry is None: + raise NotImplementedError( + "Symmetry type not supplied. Please indicate C3 or C4 symmetry." + ) + else: + symmetry = symmetry.upper() + if not symmetry[0] == "C": + raise NotImplementedError( + f"Only Cn symmetry supported. {symmetry} was supplied." + ) + self.order = int(symmetry[1]) + + def compute_scls_inds(self, Ri_cands): + """ + Compute self-common-lines indices induced by candidate rotations. + + :param Ri_cands: An array of size n_candsx3x3 of candidate rotations. + :return: An n_cands x (order-1)//2 x 2 array holding the indices of the (order-1)//2 + non-collinear pairs of self-common-lines for each candidate rotation. + """ + order = self.order + n_theta = self.n_theta + n_scl_pairs = (order - 1) // 2 + n_cands = Ri_cands.shape[0] + scls_inds = np.zeros((n_cands, n_scl_pairs, 2), dtype=np.uint16) + rots_symm = cyclic_rotations(order, dtype=self.dtype).matrices + + for i_cand, Ri_cand in enumerate(Ri_cands): + Riigs = Ri_cand.T @ rots_symm[1 : n_scl_pairs + 1] @ Ri_cand + + c1s = np.array((-Riigs[:, 1, 2], Riigs[:, 0, 2])).T + c2s = np.array((-Riigs[:, 2, 1], Riigs[:, 2, 0])).T + + c1s_inds = self.cl_angles_to_ind(c1s, n_theta) + c2s_inds = self.cl_angles_to_ind(c2s, n_theta) + + inds = np.where(c1s_inds >= (n_theta // 2)) + c1s_inds[inds] -= n_theta // 2 + c2s_inds[inds] += n_theta // 2 + c2s_inds[inds] = np.mod(c2s_inds[inds], n_theta) + + scls_inds[i_cand, :, 0] = c1s_inds + scls_inds[i_cand, :, 1] = c2s_inds + return scls_inds + # TODO: cache def compute_cls_inds(self, Ris_tilde, R_theta_ijs): n_theta = self.n_theta n_points_sphere = self.n_points_sphere From dd197583083b8e3594b1452c650001c183563c47 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 8 Dec 2022 20:26:26 -0500 Subject: [PATCH 003/424] initial intel conda changes --- environment.yml | 6 ++++++ setup.py | 6 +++--- src/aspire/config_default.yaml | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/environment.yml b/environment.yml index 73ec284af5..dbd8f5f31e 100644 --- a/environment.yml +++ b/environment.yml @@ -8,6 +8,12 @@ dependencies: - fftw - pip - python=3.8 + - numpy=1.21.5 + - pandas=1.3.5 + - scipy=1.7.3 + - scikit-learn + - scikit-image + - libblas=*=*mkl - pip: - . diff --git a/setup.py b/setup.py index ce591f9931..d33110d9f8 100644 --- a/setup.py +++ b/setup.py @@ -30,15 +30,15 @@ def read(fname): "joblib", "matplotlib>=3.2.0", "mrcfile", - "numpy==1.21.5", + "numpy>=1.21.5", "packaging", - "pandas==1.3.5", + "pandas>=1.3.5", "psutil", "pyfftw", "PyWavelets", "pillow", "ray", - "scipy==1.7.3", + "scipy>=1.7.3", "scikit-learn", "scikit-image", "setuptools>=0.41", diff --git a/src/aspire/config_default.yaml b/src/aspire/config_default.yaml index 4d3edacdbb..8206fdd12b 100644 --- a/src/aspire/config_default.yaml +++ b/src/aspire/config_default.yaml @@ -3,7 +3,7 @@ common: # numeric module to use - one of numpy/cupy numeric: numpy # fft backend to use - one of pyfftw/scipy/cupy - fft: pyfftw + fft: scipy logging: # Set log_dir to a relative or absolute directory From 6fdd476dc8b63b560983c725ee8ea43bb555ce17 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 8 Dec 2022 21:05:06 -0500 Subject: [PATCH 004/424] pyfftw must be installed via conda or bugs --- environment.yml | 1 + src/aspire/config_default.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index dbd8f5f31e..7c437f2628 100644 --- a/environment.yml +++ b/environment.yml @@ -6,6 +6,7 @@ channels: dependencies: - fftw + - pyfftw - pip - python=3.8 - numpy=1.21.5 diff --git a/src/aspire/config_default.yaml b/src/aspire/config_default.yaml index 8206fdd12b..4d3edacdbb 100644 --- a/src/aspire/config_default.yaml +++ b/src/aspire/config_default.yaml @@ -3,7 +3,7 @@ common: # numeric module to use - one of numpy/cupy numeric: numpy # fft backend to use - one of pyfftw/scipy/cupy - fft: scipy + fft: pyfftw logging: # Set log_dir to a relative or absolute directory From e3fbc73040e4e59a345f4e66de2b1ee3f9d4b9dc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 8 Dec 2022 21:41:10 -0500 Subject: [PATCH 005/424] extend alternative envs --- environment-intel.yml | 22 ++++++++++++++++++++++ environment-openblas.yml | 20 ++++++++++++++++++++ environment.yml | 1 - 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 environment-intel.yml create mode 100644 environment-openblas.yml diff --git a/environment-intel.yml b/environment-intel.yml new file mode 100644 index 0000000000..95c4d57262 --- /dev/null +++ b/environment-intel.yml @@ -0,0 +1,22 @@ +name: 'aspire' + +channels: + - defaults + - conda-forge + +dependencies: + - fftw + - pyfftw + - pip + - python=3.8 + - numpy=1.21.5 + - pandas=1.3.5 + - scipy=1.7.3 + - scikit-learn + - scikit-learn-intelex + - scikit-image + - mkl_fft + - libblas=*=*mkl + - pip: + - . + diff --git a/environment-openblas.yml b/environment-openblas.yml new file mode 100644 index 0000000000..c19e73e572 --- /dev/null +++ b/environment-openblas.yml @@ -0,0 +1,20 @@ +name: 'aspire' + +channels: + - defaults + - conda-forge + +dependencies: + - fftw + - pyfftw + - pip + - python=3.8 + - numpy=1.21.5 + - pandas=1.3.5 + - scipy=1.7.3 + - scikit-learn + - scikit-image + - libblas=*=*openblas + - pip: + - . + diff --git a/environment.yml b/environment.yml index 7c437f2628..e728827fde 100644 --- a/environment.yml +++ b/environment.yml @@ -14,7 +14,6 @@ dependencies: - scipy=1.7.3 - scikit-learn - scikit-image - - libblas=*=*mkl - pip: - . From fc801efe234d1152dc5b317d3244b0f93aaf0642 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 8 Dec 2022 22:42:40 -0500 Subject: [PATCH 006/424] adds MKL FFT seems much slower, use pyfftw/scipy(mkl) --- src/aspire/config_default.yaml | 2 +- src/aspire/numeric/__init__.py | 2 ++ src/aspire/numeric/mkl_fft.py | 57 ++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/aspire/numeric/mkl_fft.py diff --git a/src/aspire/config_default.yaml b/src/aspire/config_default.yaml index 4d3edacdbb..929f4e0347 100644 --- a/src/aspire/config_default.yaml +++ b/src/aspire/config_default.yaml @@ -2,7 +2,7 @@ version: 0.10.1 common: # numeric module to use - one of numpy/cupy numeric: numpy - # fft backend to use - one of pyfftw/scipy/cupy + # fft backend to use - one of pyfftw/scipy/cupy/mkl fft: pyfftw logging: diff --git a/src/aspire/numeric/__init__.py b/src/aspire/numeric/__init__.py index e9190c2f0c..b1087d8454 100644 --- a/src/aspire/numeric/__init__.py +++ b/src/aspire/numeric/__init__.py @@ -27,6 +27,8 @@ def fft_object(which): from .cupy_fft import CupyFFT as FFTClass elif which == "scipy": from .scipy_fft import ScipyFFT as FFTClass + elif which == "mkl": + from .mkl_fft import MKL_FFT as FFTClass else: raise RuntimeError(f"Invalid selection for fft class: {which}") return FFTClass() diff --git a/src/aspire/numeric/mkl_fft.py b/src/aspire/numeric/mkl_fft.py new file mode 100644 index 0000000000..2cf2558216 --- /dev/null +++ b/src/aspire/numeric/mkl_fft.py @@ -0,0 +1,57 @@ +import mkl_fft +import numpy as np + +from aspire.numeric.base_fft import FFT + + +class MKL_FFT(FFT): + """ + Define a unified wrapper class for MKL FFT functions + + To be consistent with Pyfftw, not all arguments are included. + """ + + # Note MKL does use the "workers" argument + def fft(self, x, axis=-1, workers=-1): + return mkl_fft.fft( + x, + axis=axis, + ) + + def ifft(self, x, axis=-1, workers=-1): + return mkl_fft.ifft( + x, + axis=axis, + ) + + def fft2(self, x, axes=(-2, -1), workers=-1): + return mkl_fft.fft2( + x, + axes=axes, + ) + + def ifft2(self, x, axes=(-2, -1), workers=-1): + return mkl_fft.ifft2( + x, + axes=axes, + ) + + def fftn(self, x, axes=None, workers=-1): + return mkl_fft.fftn( + x, + axes=axes, + ) + + def ifftn(self, x, axes=None, workers=-1): + return mkl_fft.ifftn( + x, + axes=axes, + ) + + def fftshift(self, x, axes=None): + # N/A in mkl_fft, use np + return np.fft.fftshift(x, axes=axes) + + def ifftshift(self, x, axes=None): + # N/A in mkl_fft, use np + return np.fft.ifftshift(x, axes=axes) From abb0994299916b6c4d6b4751b67a7b6d64013f70 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 20 Dec 2022 14:20:52 -0500 Subject: [PATCH 007/424] initial change set --- tests/test_image.py | 480 ++++++++++++++++++++++++-------------------- 1 file changed, 263 insertions(+), 217 deletions(-) diff --git a/tests/test_image.py b/tests/test_image.py index 6c7d6eaa3d..38712bca7d 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -3,231 +3,277 @@ from unittest import TestCase import numpy as np +import pytest from parameterized import parameterized_class from pytest import raises from scipy import misc from aspire.image import Image -from aspire.utils import powerset +from aspire.utils import powerset, utest_tolerance DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") logger = logging.getLogger(__name__) +params = [(0, np.float32), (1, np.float32), (0, np.float64), (1, np.float64)] -@parameterized_class(("parity",), [(0,), (1,)]) -class ImageTestCase(TestCase): - def setUp(self): - self.dtype = np.float64 - self.n = 3 - self.size = 768 - self.parity - # numpy array for top-level functions that directly expect it - self.im_np = misc.face(gray=True).astype(self.dtype)[ - np.newaxis, : self.size, : self.size - ] - # Independent Image object for testing Image methods - self.im = Image( - misc.face(gray=True).astype(self.dtype)[: self.size, : self.size] - ) - - # Construct a simple stack of Images - self.ims_np = np.empty((self.n, *self.im_np.shape[1:]), dtype=self.dtype) - for i in range(self.n): - self.ims_np[i] = self.im_np * (i + 1) / float(self.n) - - # Independent Image stack object for testing Image methods - self.ims = Image(self.ims_np) - - # Multi dimensional stack Image object - self.mdim = 2 - self.mdim_ims_np = np.concatenate([self.ims_np] * self.mdim).reshape( - self.mdim, *self.ims_np.shape - ) - self.mdim_ims = Image(self.mdim_ims_np) - - def tearDown(self): - pass - - def testRepr(self): - r = repr(self.mdim_ims) - logger.info(f"Image repr:\n{r}") - - def testNonSquare(self): - """Test that an irregular Image array raises.""" - with raises(ValueError, match=r".* square .*"): - _ = Image(np.empty((4, 5), dtype=self.dtype)) - - def testImShift(self): - # Note that the _im_translate method can handle float input shifts, as it - # computes the shifts in Fourier space, rather than performing a roll - # However, NumPy's roll() only accepts integer inputs - shifts = np.array([100, 200]) - - # test built-in - im0 = self.im.shift(shifts) - # test explicit call - im1 = self.im._im_translate(shifts) - # test that float input returns the same thing - im2 = self.im.shift(shifts.astype(np.float64)) - # ground truth numpy roll - im3 = np.roll(self.im_np[0, :, :], -shifts, axis=(0, 1)) - - self.assertTrue(np.allclose(im0.asnumpy(), im1.asnumpy())) - self.assertTrue(np.allclose(im1.asnumpy(), im2.asnumpy())) - self.assertTrue(np.allclose(im0.asnumpy()[0, :, :], im3)) - - def testImShiftStack(self): - # test stack of shifts (same number as Image.num_img) - # mix of odd and even - shifts = np.array([[100, 200], [203, 150], [55, 307]]) - - # test built-in - im0 = self.ims.shift(shifts) - # test explicit call - im1 = self.ims._im_translate(shifts) - # test that float input returns the same thing - im2 = self.ims.shift(shifts.astype(np.float64)) - # ground truth numpy roll - im3 = np.array( - [ - np.roll(self.ims_np[i, :, :], -shifts[i], axis=(0, 1)) - for i in range(self.n) - ] - ) - self.assertTrue(np.allclose(im0.asnumpy(), im1.asnumpy())) - self.assertTrue(np.allclose(im1.asnumpy(), im2.asnumpy())) - self.assertTrue(np.allclose(im0.asnumpy(), im3)) - - def testImageShiftErrors(self): - # test bad shift shape - with self.assertRaisesRegex(ValueError, "Input shifts must be of shape"): - _ = self.im.shift(np.array([100, 100, 100])) - # test bad number of shifts - with self.assertRaisesRegex(ValueError, "The number of shifts"): - _ = self.im.shift(np.array([[100, 200], [100, 200]])) - - def testImageSqrt(self): - self.assertTrue(np.allclose(self.im.sqrt().asnumpy(), np.sqrt(self.im_np))) - self.assertTrue(np.allclose(self.ims.sqrt().asnumpy(), np.sqrt(self.ims_np))) - - def testImageTranspose(self): - # test method and abbreviation - self.assertTrue( - np.allclose(self.im.T.asnumpy(), np.transpose(self.im_np, (0, 2, 1))) - ) - self.assertTrue( - np.allclose( - self.im.transpose().asnumpy(), np.transpose(self.im_np, (0, 2, 1)) - ) - ) - - # Check individual imgs in a stack - for i in range(self.ims_np.shape[0]): - self.assertTrue(np.allclose(self.ims.T[i], self.ims_np[i].T)) - self.assertTrue(np.allclose(self.ims.transpose()[i], self.ims_np[i].T)) - - def testImageFlip(self): - for axis in powerset(range(1, 3)): - if not axis: - # test default - result_single = self.im.flip().asnumpy() - result_stack = self.ims.flip().asnumpy() - axis = 1 - else: - result_single = self.im.flip(axis).asnumpy() - result_stack = self.ims.flip(axis).asnumpy() - # single image - self.assertTrue(np.allclose(result_single, np.flip(self.im_np, axis))) - # stack - self.assertTrue( - np.allclose( - result_stack, - np.flip(self.ims_np, axis), - ) - ) - - # test error for axis 0 - axes = [0, (0, 1)] - for axis in axes: - with self.assertRaisesRegex(ValueError, "stack axis"): - _ = self.im.flip(axis) - - def testShape(self): - self.assertEqual(self.ims.shape, self.ims_np.shape) - self.assertEqual(self.ims.stack_shape, self.ims_np.shape[:-2]) - self.assertEqual(self.ims.stack_ndim, 1) - - def testMultiDimShape(self): - self.assertEqual(self.mdim_ims.shape, self.mdim_ims_np.shape) - self.assertEqual(self.mdim_ims.stack_shape, self.mdim_ims_np.shape[:-2]) - self.assertEqual(self.mdim_ims.stack_ndim, self.mdim) - self.assertEqual(self.mdim_ims.n_images, self.mdim * self.ims.n_images) - - def testBadKey(self): - with self.assertRaisesRegex(ValueError, "slice length must be"): - _ = self.mdim_ims[tuple(range(self.mdim_ims.ndim + 1))] - - def testMultiDimGets(self): - for X in self.mdim_ims: - self.assertTrue(np.allclose(self.ims_np, X)) - - # Test a slice - self.assertTrue(np.allclose(self.mdim_ims[:, 1:], self.ims[1:])) - - def testMultiDimSets(self): - self.mdim_ims[0, 1] = 123 - # Check the values changed - self.assertTrue(np.allclose(self.mdim_ims[0, 1], 123)) - # and only those values changed - self.assertTrue(np.allclose(self.mdim_ims[0, 0], self.ims_np[0])) - self.assertTrue(np.allclose(self.mdim_ims[0, 2:], self.ims_np[2:])) - self.assertTrue(np.allclose(self.mdim_ims[1, :], self.ims_np)) - - def testMultiDimSetsSlice(self): - # Test setting a slice - self.mdim_ims[0, 1:] = 456 - # Check the values changed - self.assertTrue(np.allclose(self.mdim_ims[0, 1:], 456)) - # and only those values changed - self.assertTrue(np.allclose(self.mdim_ims[0, 0], self.ims_np[0])) - self.assertTrue(np.allclose(self.mdim_ims[1, :], self.ims_np)) - - def testMultiDimReshape(self): - # Try mdim reshape - X = self.mdim_ims.stack_reshape(*self.mdim_ims.stack_shape[::-1]) - self.assertEqual(X.stack_shape, self.mdim_ims.stack_shape[::-1]) - # Compare with direct np.reshape of axes of ndarray - shape = (*self.mdim_ims_np.shape[:-2][::-1], *self.mdim_ims_np.shape[-2:]) - self.assertTrue(np.allclose(X.asnumpy(), self.mdim_ims_np.reshape(shape))) - - def testMultiDimFlattens(self): - # Try flattening - X = self.mdim_ims.stack_reshape(self.mdim_ims.n_images) - self.assertEqual(X.stack_shape, (self.mdim_ims.n_images,)) - - def testMultiDimFlattensTrick(self): - # Try flattening with -1 - X = self.mdim_ims.stack_reshape(-1) - self.assertEqual(X.stack_shape, (self.mdim_ims.n_images,)) - - def testMultiDimReshapeTuples(self): - # Try flattening with (-1,) - X = self.mdim_ims.stack_reshape((-1,)) - self.assertEqual(X.stack_shape, (self.mdim_ims.n_images,)) - - # Try mdim reshape - X = self.mdim_ims.stack_reshape(self.mdim_ims.stack_shape[::-1]) - self.assertEqual(X.stack_shape, self.mdim_ims.stack_shape[::-1]) - - def testMultiDimBadReshape(self): - # Incorrect flat shape - with self.assertRaisesRegex(ValueError, "Number of images"): - _ = self.mdim_ims.stack_reshape(8675309) - - # Incorrect mdin shape - with self.assertRaisesRegex(ValueError, "Number of images"): - _ = self.mdim_ims.stack_reshape(42, 8675309) - - def testMultiDimBroadcast(self): - X = self.mdim_ims + self.ims - self.assertTrue(np.allclose(X[0], 2 * self.ims.asnumpy())) +n = 3 +mdim = 2 + + +def get_images(parity=0, dtype=np.float32): + size = 768 - parity + im_np = misc.face(gray=True).astype(dtype) + # numpy array for top-level functions that directly expect it + im_np = misc.face(gray=True).astype(dtype)[np.newaxis, :size, :size] + # Independent Image object for testing Image methods + im = Image(misc.face(gray=True).astype(dtype)[:size, :size]) + return im_np, im + + +def get_stacks(parity=0, dtype=np.float32): + im_np, im = get_images(parity, dtype) + + # Construct a simple stack of Images + ims_np = np.empty((n, *im_np.shape[1:]), dtype=dtype) + for i in range(n): + ims_np[i] = im_np * (i + 1) / float(n) + + # Independent Image stack object for testing Image methods + ims = Image(ims_np) + + return ims_np, ims + + +def get_mdim_images(parity=0, dtype=np.float32): + ims_np, im = get_stacks(parity, dtype) + # Multi dimensional stack Image object + mdim = 2 + mdim_ims_np = np.concatenate([ims_np] * mdim).reshape(mdim, *ims_np.shape) + mdim_ims = Image(mdim_ims_np) + + return mdim_ims_np, mdim_ims + + +def testRepr(): + # don't need to parametrize this test + _, mdim_ims = get_mdim_images() + r = repr(mdim_ims) + logger.info(f"Image repr:\n{r}") + + +def testNonSquare(): + # don't need to parametrize this test + """Test that an irregular Image array raises.""" + with raises(ValueError, match=r".* square .*"): + _ = Image(np.empty((4, 5))) + + +@pytest.mark.parametrize("parity,dtype", params) +def testImShift(parity, dtype): + im_np, im = get_images(parity, dtype) + # Note that the _im_translate method can handle float input shifts, as it + # computes the shifts in Fourier space, rather than performing a roll + # However, NumPy's roll() only accepts integer inputs + shifts = np.array([100, 200]) + + # test built-in + im0 = im.shift(shifts) + # test explicit call + im1 = im._im_translate(shifts) + # test that float input returns the same thing + im2 = im.shift(shifts.astype(dtype)) + # ground truth numpy roll + im3 = np.roll(im_np[0, :, :], -shifts, axis=(0, 1)) + + assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=1e-3) + assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=1e-3) + assert np.allclose(im0.asnumpy()[0, :, :], im3, atol=1e-3) + + +@pytest.mark.parametrize("parity,dtype", params) +def testImShiftStack(parity, dtype): + ims_np, ims = get_stacks(parity, dtype) + # test stack of shifts (same number as Image.num_img) + # mix of odd and even + shifts = np.array([[100, 200], [203, 150], [55, 307]]) + + # test built-in + im0 = ims.shift(shifts) + # test explicit call + im1 = ims._im_translate(shifts) + # test that float input returns the same thing + im2 = ims.shift(shifts.astype(dtype)) + # ground truth numpy roll + im3 = np.array( + [np.roll(ims_np[i, :, :], -shifts[i], axis=(0, 1)) for i in range(n)] + ) + assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=utest_tolerance(dtype)) + assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=utest_tolerance(dtype)) + assert np.allclose(im0.asnumpy(), im3, atol=utest_tolerance(dtype)) + + +def testImageShiftErrors(): + _, im = get_images(0, np.float32) + # test bad shift shape + with pytest.raises(ValueError, match="Input shifts must be of shape"): + _ = im.shift(np.array([100, 100, 100])) + # test bad number of shifts + with pytest.raises(ValueError, match="The number of shifts"): + _ = im.shift(np.array([[100, 200], [100, 200]])) + + +@pytest.mark.parametrize("parity,dtype", params) +def testImageSqrt(parity, dtype): + im_np, im = get_images(parity, dtype) + ims_np, ims = get_stacks(parity, dtype) + assert np.allclose(im.sqrt().asnumpy(), np.sqrt(im_np)) + assert np.allclose(ims.sqrt().asnumpy(), np.sqrt(ims_np)) + + +@pytest.mark.parametrize("parity,dtype", params) +def testImageTranspose(parity, dtype): + im_np, im = get_images(parity, dtype) + ims_np, ims = get_stacks(parity, dtype) + # test method and abbreviation + assert np.allclose(im.T.asnumpy(), np.transpose(im_np, (0, 2, 1))) + assert np.allclose(im.transpose().asnumpy(), np.transpose(im_np, (0, 2, 1))) + + # Check individual imgs in a stack + for i in range(ims_np.shape[0]): + assert np.allclose(ims.T[i], ims_np[i].T) + assert np.allclose(ims.transpose()[i], ims_np[i].T) + + +@pytest.mark.parametrize("parity,dtype", params) +def testImageFlip(parity, dtype): + im_np, im = get_images(parity, dtype) + ims_np, ims = get_stacks(parity, dtype) + for axis in powerset(range(1, 3)): + if not axis: + # test default + result_single = im.flip().asnumpy() + result_stack = ims.flip().asnumpy() + axis = 1 + else: + result_single = im.flip(axis).asnumpy() + result_stack = ims.flip(axis).asnumpy() + # single image + assert np.allclose(result_single, np.flip(im_np, axis)) + # stack + assert np.allclose(result_stack, np.flip(ims_np, axis)) + + # test error for axis 0 + axes = [0, (0, 1)] + for axis in axes: + with pytest.raises(ValueError, match="stack axis"): + _ = im.flip(axis) + + +def testShape(): + ims_np, ims = get_stacks() + assert ims.shape == ims_np.shape + assert ims.stack_shape == ims_np.shape[:-2] + assert ims.stack_ndim == 1 + + +def testMultiDimShape(): + ims_np, ims = get_stacks() + mdim_ims_np, mdim_ims = get_mdim_images() + assert mdim_ims.shape == mdim_ims_np.shape + assert mdim_ims.stack_shape == mdim_ims_np.shape[:-2] + assert mdim_ims.stack_ndim == mdim + assert mdim_ims.n_images == mdim * ims.n_images + + +def testBadKey(): + mdim_ims_np, mdim_ims = get_mdim_images() + with pytest.raises(ValueError, match="slice length must be"): + _ = mdim_ims[tuple(range(mdim_ims.ndim + 1))] + + +def testMultiDimGets(): + ims_np, ims = get_stacks() + mdim_ims_np, mdim_ims = get_mdim_images() + for X in mdim_ims: + assert np.allclose(ims_np, X) + + # Test a slice + assert np.allclose(mdim_ims[:, 1:], ims[1:]) + + +def testMultiDimSets(): + ims_np, ims = get_stacks() + mdim_ims_np, mdim_ims = get_mdim_images() + mdim_ims[0, 1] = 123 + # Check the values changed + assert np.allclose(mdim_ims[0, 1], 123) + # and only those values changed + assert np.allclose(mdim_ims[0, 0], ims_np[0]) + assert np.allclose(mdim_ims[0, 2:], ims_np[2:]) + assert np.allclose(mdim_ims[1, :], ims_np) + + +def testMultiDimSetsSlice(): + ims_np, ims = get_stacks() + mdim_ims_np, mdim_ims = get_mdim_images() + # Test setting a slice + mdim_ims[0, 1:] = 456 + # Check the values changed + assert np.allclose(mdim_ims[0, 1:], 456) + # and only those values changed + assert np.allclose(mdim_ims[0, 0], ims_np[0]) + assert np.allclose(mdim_ims[1, :], ims_np) + + +def testMultiDimReshape(): + # Try mdim reshape + mdim_ims_np, mdim_ims = get_mdim_images() + X = mdim_ims.stack_reshape(*mdim_ims.stack_shape[::-1]) + assert X.stack_shape == mdim_ims.stack_shape[::-1] + # Compare with direct np.reshape of axes of ndarray + shape = (*mdim_ims_np.shape[:-2][::-1], *mdim_ims_np.shape[-2:]) + assert np.allclose(X.asnumpy(), mdim_ims_np.reshape(shape)) + + +def testMultiDimFlattens(): + mdim_ims_np, mdim_ims = get_mdim_images() + # Try flattening + X = mdim_ims.stack_reshape(mdim_ims.n_images) + assert X.stack_shape, (mdim_ims.n_images,) + + +def testMultiDimFlattensTrick(): + mdim_ims_np, mdim_ims = get_mdim_images() + # Try flattening with -1 + X = mdim_ims.stack_reshape(-1) + assert X.stack_shape == (mdim_ims.n_images,) + + +def testMultiDimReshapeTuples(): + mdim_ims_np, mdim_ims = get_mdim_images() + # Try flattening with (-1,) + X = mdim_ims.stack_reshape((-1,)) + assert X.stack_shape, (mdim_ims.n_images,) + + # Try mdim reshape + X = mdim_ims.stack_reshape(mdim_ims.stack_shape[::-1]) + assert X.stack_shape == mdim_ims.stack_shape[::-1] + + +def testMultiDimBadReshape(): + mdim_ims_np, mdim_ims = get_mdim_images() + # Incorrect flat shape + with pytest.raises(ValueError, match="Number of images"): + _ = mdim_ims.stack_reshape(8675309) + + # Incorrect mdin shape + with pytest.raises(ValueError, match="Number of images"): + _ = mdim_ims.stack_reshape(42, 8675309) + + +def testMultiDimBroadcast(): + ims_np, ims = get_stacks() + mdim_ims_np, mdim_ims = get_mdim_images() + X = mdim_ims + ims + assert np.allclose(X[0], 2 * ims.asnumpy()) From 8bf63979fcb628e8ad5b1e97e11c7a489e589d95 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 20 Dec 2022 14:27:21 -0500 Subject: [PATCH 008/424] put in temp measure for f32 shifting tests --- tests/test_image.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/tests/test_image.py b/tests/test_image.py index 38712bca7d..b103ce1360 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,10 +1,8 @@ import logging import os.path -from unittest import TestCase import numpy as np import pytest -from parameterized import parameterized_class from pytest import raises from scipy import misc @@ -86,9 +84,11 @@ def testImShift(parity, dtype): # ground truth numpy roll im3 = np.roll(im_np[0, :, :], -shifts, axis=(0, 1)) - assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=1e-3) - assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=1e-3) - assert np.allclose(im0.asnumpy()[0, :, :], im3, atol=1e-3) + atol = 1e-3 if dtype == np.float32 else utest_tolerance(dtype) + + assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=atol) + assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=atol) + assert np.allclose(im0.asnumpy()[0, :, :], im3, atol=atol) @pytest.mark.parametrize("parity,dtype", params) @@ -108,9 +108,12 @@ def testImShiftStack(parity, dtype): im3 = np.array( [np.roll(ims_np[i, :, :], -shifts[i], axis=(0, 1)) for i in range(n)] ) - assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=utest_tolerance(dtype)) - assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=utest_tolerance(dtype)) - assert np.allclose(im0.asnumpy(), im3, atol=utest_tolerance(dtype)) + + atol = 5e-2 if dtype == np.float32 else utest_tolerance(dtype) + + assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=atol) + assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=atol) + assert np.allclose(im0.asnumpy(), im3, atol=atol) def testImageShiftErrors(): From 007973ed16e4a88330a9b26cae1e5a1bece7b7d9 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Thu, 22 Dec 2022 13:57:43 -0500 Subject: [PATCH 009/424] im_translate change and use utest_tolerance for image shift checks --- src/aspire/image/image.py | 10 +++++----- tests/test_image.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index 5c823657cb..c713b10676 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -341,7 +341,8 @@ def _im_translate(self, shifts): # Note original stack shape and flatten stack stack_shape = self.stack_shape - im = self.stack_reshape(-1)._data + # upcast to np.float64 to preserve numerical stability in FFT ops + im = self.stack_reshape(-1)._data.astype(np.float64) if shifts.ndim == 1: shifts = shifts[np.newaxis, :] @@ -357,9 +358,7 @@ def _im_translate(self, shifts): L = self.resolution im_f = xp.asnumpy(fft.fft2(xp.asarray(im))) - grid_shifted = fft.ifftshift( - xp.asarray(np.ceil(np.arange(-L / 2, L / 2, dtype=self.dtype))) - ) + grid_shifted = fft.ifftshift(xp.asarray(np.ceil(np.arange(-L / 2, L / 2)))) grid_1d = xp.asnumpy(grid_shifted) * 2 * np.pi / L om_x, om_y = np.meshgrid(grid_1d, grid_1d, indexing="ij") @@ -373,7 +372,8 @@ def _im_translate(self, shifts): mult_f = np.exp(-1j * phase_shifts) im_translated_f = im_f * mult_f im_translated = xp.asnumpy(fft.ifft2(xp.asarray(im_translated_f))) - im_translated = np.real(im_translated) + # downcast back to Image's internal dtype + im_translated = np.real(im_translated).astype(self.dtype) # Reshape to stack shape return Image(im_translated).stack_reshape(stack_shape) diff --git a/tests/test_image.py b/tests/test_image.py index b103ce1360..410e2a8d5f 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -84,7 +84,7 @@ def testImShift(parity, dtype): # ground truth numpy roll im3 = np.roll(im_np[0, :, :], -shifts, axis=(0, 1)) - atol = 1e-3 if dtype == np.float32 else utest_tolerance(dtype) + atol = utest_tolerance(dtype) assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=atol) assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=atol) @@ -109,7 +109,7 @@ def testImShiftStack(parity, dtype): [np.roll(ims_np[i, :, :], -shifts[i], axis=(0, 1)) for i in range(n)] ) - atol = 5e-2 if dtype == np.float32 else utest_tolerance(dtype) + atol = utest_tolerance(dtype) assert np.allclose(im0.asnumpy(), im1.asnumpy(), atol=atol) assert np.allclose(im1.asnumpy(), im2.asnumpy(), atol=atol) From 6ecc0cbc35d34b9493f95723c9eee17b4e7880ff Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 3 Jan 2023 09:44:26 -0500 Subject: [PATCH 010/424] AsymmetricVolume as Simulation default. Invoke LegacyVolume in hardcoded tests. --- src/aspire/source/simulation.py | 6 +++--- tests/test_anisotropic_noise.py | 3 +++ tests/test_covar3d.py | 4 +++- tests/test_mean_estimator.py | 6 +++++- tests/test_simulation.py | 32 +++++++++++++++++++++++--------- tests/test_white_noise.py | 5 +++++ 6 files changed, 42 insertions(+), 14 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index b9ce4ded01..dab8f9aaa2 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -18,7 +18,7 @@ vecmat_to_volmat, ) from aspire.utils.random import rand, randi, randn -from aspire.volume import LegacyVolume, Volume +from aspire.volume import AsymmetricVolume, Volume logger = logging.getLogger(__name__) @@ -56,7 +56,7 @@ def __init__( If a `Volume` is provided `L` and `vols.resolution` must agree. :param n: The number of images to generate (integer). :param vols: A `Volume` object representing a stack of volumes. - Default is generated with `volume.volume_synthesis.LegacyVolume`. + Default is generated with `volume.volume_synthesis.AsymmetricVolume`. :param states: A 1d array of n integers in the interval [0, C). The i'th integer indicates the volume stack index used to produce the i'th projection image. Default is a random set. :param unique_filters: A list of Filter objects to be applied to projection images. @@ -84,7 +84,7 @@ def __init__( # If a Volume is not provided we default to the legacy Gaussian blob volume. # If a Simulation resolution or dtype is not provided, we default to L=8 and np.float32. if vols is None: - self.vols = LegacyVolume( + self.vols = AsymmetricVolume( L=L or 8, C=C, seed=self.seed, diff --git a/tests/test_anisotropic_noise.py b/tests/test_anisotropic_noise.py index e249c8a175..76e0bbeea7 100644 --- a/tests/test_anisotropic_noise.py +++ b/tests/test_anisotropic_noise.py @@ -7,6 +7,7 @@ from aspire.operators import RadialCTFFilter from aspire.source import ArrayImageSource, Simulation from aspire.utils.types import utest_tolerance +from aspire.volume import LegacyVolume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -14,8 +15,10 @@ class SimTestCase(TestCase): def setUp(self): self.dtype = np.float32 + self.vol = LegacyVolume(L=8, C=2, dtype=self.dtype, seed=0).generate() self.sim = Simulation( n=1024, + vols=self.vol, unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index 20af1b9405..91a12ebeec 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -15,7 +15,7 @@ from aspire.source.simulation import Simulation from aspire.utils import eigs from aspire.utils.random import Random -from aspire.volume import Volume +from aspire.volume import LegacyVolume, Volume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -24,8 +24,10 @@ class Covar3DTestCase(TestCase): @classmethod def setUpClass(cls): cls.dtype = np.float32 + cls.vols = LegacyVolume(L=8, C=2, seed=0, dtype=cls.dtype).generate() cls.sim = Simulation( n=1024, + vols=cls.vols, unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], diff --git a/tests/test_mean_estimator.py b/tests/test_mean_estimator.py index 945f51057a..422ea780cd 100644 --- a/tests/test_mean_estimator.py +++ b/tests/test_mean_estimator.py @@ -8,6 +8,7 @@ from aspire.operators import RadialCTFFilter from aspire.reconstruction import MeanEstimator from aspire.source.simulation import Simulation +from aspire.volume import LegacyVolume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -16,9 +17,12 @@ class MeanEstimatorTestCase(TestCase): def setUp(self): self.dtype = np.float32 self.resolution = 8 - + self.vols = LegacyVolume( + L=self.resolution, C=2, seed=0, dtype=self.dtype + ).generate() self.sim = sim = Simulation( n=1024, + vols=self.vols, unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 364ad064fa..e62acaa510 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -9,7 +9,7 @@ from aspire.source.relion import RelionSource from aspire.source.simulation import Simulation from aspire.utils.types import utest_tolerance -from aspire.volume import Volume +from aspire.volume import LegacyVolume, Volume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -88,15 +88,28 @@ def testPassthroughFromSim(self): class SimTestCase(TestCase): def setUp(self): + self.n = 1024 + self.L = 8 + self.seed = 0 + self.dtype = np.float32 + + self.vols = LegacyVolume( + L=self.L, + C=2, + seed=self.seed, + dtype=self.dtype, + ).generate() + self.sim = Simulation( - n=1024, - L=8, + n=self.n, + L=self.L, + vols=self.vols, unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], - seed=0, + seed=self.seed, noise_filter=IdentityFilter(), - dtype="single", + dtype=self.dtype, ) def tearDown(self): @@ -134,14 +147,15 @@ def testSimulationImages(self): def testSimulationCached(self): sim_cached = Simulation( - n=1024, - L=8, + n=self.n, + L=self.L, + vols=self.vols, unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], - seed=0, + seed=self.seed, noise_filter=IdentityFilter(), - dtype="single", + dtype=self.dtype, ) sim_cached.cache() self.assertTrue( diff --git a/tests/test_white_noise.py b/tests/test_white_noise.py index ed2e74ac6d..2fe7c2cb90 100644 --- a/tests/test_white_noise.py +++ b/tests/test_white_noise.py @@ -6,17 +6,22 @@ from aspire.noise import WhiteNoiseEstimator from aspire.operators import RadialCTFFilter from aspire.source.simulation import Simulation +from aspire.volume import LegacyVolume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") class SimTestCase(TestCase): def setUp(self): + self.dtype = np.float32 + self.vols = LegacyVolume(L=8, C=2, seed=0, dtype=self.dtype).generate() self.sim = Simulation( n=1024, + vols=self.vols, unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], + dtype=self.dtype, ) def tearDown(self): From 69d45e5d51de96dc4b94d98ab0f5d5bc416d9954 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 3 Jan 2023 12:27:22 -0500 Subject: [PATCH 011/424] normalize image data to fix singles tolerance issue for shifting --- src/aspire/image/image.py | 10 +++++----- tests/test_image.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index c713b10676..5c823657cb 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -341,8 +341,7 @@ def _im_translate(self, shifts): # Note original stack shape and flatten stack stack_shape = self.stack_shape - # upcast to np.float64 to preserve numerical stability in FFT ops - im = self.stack_reshape(-1)._data.astype(np.float64) + im = self.stack_reshape(-1)._data if shifts.ndim == 1: shifts = shifts[np.newaxis, :] @@ -358,7 +357,9 @@ def _im_translate(self, shifts): L = self.resolution im_f = xp.asnumpy(fft.fft2(xp.asarray(im))) - grid_shifted = fft.ifftshift(xp.asarray(np.ceil(np.arange(-L / 2, L / 2)))) + grid_shifted = fft.ifftshift( + xp.asarray(np.ceil(np.arange(-L / 2, L / 2, dtype=self.dtype))) + ) grid_1d = xp.asnumpy(grid_shifted) * 2 * np.pi / L om_x, om_y = np.meshgrid(grid_1d, grid_1d, indexing="ij") @@ -372,8 +373,7 @@ def _im_translate(self, shifts): mult_f = np.exp(-1j * phase_shifts) im_translated_f = im_f * mult_f im_translated = xp.asnumpy(fft.ifft2(xp.asarray(im_translated_f))) - # downcast back to Image's internal dtype - im_translated = np.real(im_translated).astype(self.dtype) + im_translated = np.real(im_translated) # Reshape to stack shape return Image(im_translated).stack_reshape(stack_shape) diff --git a/tests/test_image.py b/tests/test_image.py index 410e2a8d5f..ee4a389ef7 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -21,11 +21,12 @@ def get_images(parity=0, dtype=np.float32): size = 768 - parity - im_np = misc.face(gray=True).astype(dtype) # numpy array for top-level functions that directly expect it im_np = misc.face(gray=True).astype(dtype)[np.newaxis, :size, :size] + denom = np.max(np.abs(im_np)) + im_np = im_np / denom # Normalize test image data to 0,1 # Independent Image object for testing Image methods - im = Image(misc.face(gray=True).astype(dtype)[:size, :size]) + im = Image(im_np.copy()) return im_np, im @@ -54,7 +55,6 @@ def get_mdim_images(parity=0, dtype=np.float32): def testRepr(): - # don't need to parametrize this test _, mdim_ims = get_mdim_images() r = repr(mdim_ims) logger.info(f"Image repr:\n{r}") From c6395850cff66860e959c2aaded9a01766e39783 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 3 Jan 2023 13:53:47 -0500 Subject: [PATCH 012/424] Use LegacyVolume in cov3D tutorial. --- gallery/tutorials/cov3d_simulation.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/gallery/tutorials/cov3d_simulation.py b/gallery/tutorials/cov3d_simulation.py index bef5888e6d..97446277f1 100644 --- a/gallery/tutorials/cov3d_simulation.py +++ b/gallery/tutorials/cov3d_simulation.py @@ -19,7 +19,7 @@ from aspire.source.simulation import Simulation from aspire.utils import eigs from aspire.utils.random import Random -from aspire.volume import Volume +from aspire.volume import LegacyVolume, Volume logger = logging.getLogger(__name__) @@ -31,15 +31,26 @@ img_size = 8 # image size in square num_imgs = 1024 # number of images num_eigs = 16 # number of eigen-vectors to keep +dtype = np.float32 + +# Generate a ``Volume`` object for use in the simulation. +vols = LegacyVolume( + L=img_size, + C=2, # number of volumes + seed=0, + dtype=dtype, +).generate() # Create a simulation object with specified filters sim = Simulation( L=img_size, n=num_imgs, + vols=vols, unique_filters=[RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7)], + dtype=dtype, ) -# By default, a Simulation object is created using 2 volumes. +# The Simulation object was created using 2 volumes. num_vols = sim.C # Specify the normal FB basis method for expending the 2D images From 590ed2d2ea2effc596297b95440481cbaee75bb2 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 4 Jan 2023 12:45:41 -0500 Subject: [PATCH 013/424] LegacyVolume init with default params --- src/aspire/volume/volume_synthesis.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aspire/volume/volume_synthesis.py b/src/aspire/volume/volume_synthesis.py index ff359aef09..377d41169f 100644 --- a/src/aspire/volume/volume_synthesis.py +++ b/src/aspire/volume/volume_synthesis.py @@ -355,6 +355,9 @@ class LegacyVolume(AsymmetricVolume): An asymmetric Volume object used for testing of legacy code. """ + def __init__(self, L, C=2, K=16, seed=0, dtype=np.float64): + super().__init__(L=L, C=C, seed=seed, dtype=dtype) + def generate(self): """ Generates an asymmetric volume composed of random 3D Gaussian blobs. From cc4ef00a9c88fddb96056c8aaffe37a2eb255616 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 4 Jan 2023 13:10:36 -0500 Subject: [PATCH 014/424] Remove explicit paramters from instances of LegacyVolume --- gallery/tutorials/cov3d_simulation.py | 5 ++--- tests/test_anisotropic_noise.py | 2 +- tests/test_covar3d.py | 2 +- tests/test_mean_estimator.py | 4 +--- tests/test_simulation.py | 5 ----- tests/test_white_noise.py | 2 +- 6 files changed, 6 insertions(+), 14 deletions(-) diff --git a/gallery/tutorials/cov3d_simulation.py b/gallery/tutorials/cov3d_simulation.py index 97446277f1..234dc799e1 100644 --- a/gallery/tutorials/cov3d_simulation.py +++ b/gallery/tutorials/cov3d_simulation.py @@ -33,11 +33,10 @@ num_eigs = 16 # number of eigen-vectors to keep dtype = np.float32 -# Generate a ``Volume`` object for use in the simulation. +# Generate a ``Volume`` object for use in the simulation. Here we use a ``LegacyVolume`` which +# by default generates 2 unique random volumes. vols = LegacyVolume( L=img_size, - C=2, # number of volumes - seed=0, dtype=dtype, ).generate() diff --git a/tests/test_anisotropic_noise.py b/tests/test_anisotropic_noise.py index 76e0bbeea7..dc4732732e 100644 --- a/tests/test_anisotropic_noise.py +++ b/tests/test_anisotropic_noise.py @@ -15,7 +15,7 @@ class SimTestCase(TestCase): def setUp(self): self.dtype = np.float32 - self.vol = LegacyVolume(L=8, C=2, dtype=self.dtype, seed=0).generate() + self.vol = LegacyVolume(L=8, dtype=self.dtype).generate() self.sim = Simulation( n=1024, vols=self.vol, diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index 91a12ebeec..cd3b42d576 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -24,7 +24,7 @@ class Covar3DTestCase(TestCase): @classmethod def setUpClass(cls): cls.dtype = np.float32 - cls.vols = LegacyVolume(L=8, C=2, seed=0, dtype=cls.dtype).generate() + cls.vols = LegacyVolume(L=8, dtype=cls.dtype).generate() cls.sim = Simulation( n=1024, vols=cls.vols, diff --git a/tests/test_mean_estimator.py b/tests/test_mean_estimator.py index 422ea780cd..5a71eff0ef 100644 --- a/tests/test_mean_estimator.py +++ b/tests/test_mean_estimator.py @@ -17,9 +17,7 @@ class MeanEstimatorTestCase(TestCase): def setUp(self): self.dtype = np.float32 self.resolution = 8 - self.vols = LegacyVolume( - L=self.resolution, C=2, seed=0, dtype=self.dtype - ).generate() + self.vols = LegacyVolume(L=self.resolution, dtype=self.dtype).generate() self.sim = sim = Simulation( n=1024, vols=self.vols, diff --git a/tests/test_simulation.py b/tests/test_simulation.py index e62acaa510..f4015bc9d3 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -90,13 +90,10 @@ class SimTestCase(TestCase): def setUp(self): self.n = 1024 self.L = 8 - self.seed = 0 self.dtype = np.float32 self.vols = LegacyVolume( L=self.L, - C=2, - seed=self.seed, dtype=self.dtype, ).generate() @@ -107,7 +104,6 @@ def setUp(self): unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], - seed=self.seed, noise_filter=IdentityFilter(), dtype=self.dtype, ) @@ -153,7 +149,6 @@ def testSimulationCached(self): unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], - seed=self.seed, noise_filter=IdentityFilter(), dtype=self.dtype, ) diff --git a/tests/test_white_noise.py b/tests/test_white_noise.py index 2fe7c2cb90..42facd4446 100644 --- a/tests/test_white_noise.py +++ b/tests/test_white_noise.py @@ -14,7 +14,7 @@ class SimTestCase(TestCase): def setUp(self): self.dtype = np.float32 - self.vols = LegacyVolume(L=8, C=2, seed=0, dtype=self.dtype).generate() + self.vols = LegacyVolume(L=8, dtype=self.dtype).generate() self.sim = Simulation( n=1024, vols=self.vols, From 746c3f19b9694eec2a06c95108aa8a033ddbabfa Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 4 Jan 2023 15:44:54 -0500 Subject: [PATCH 015/424] Attempt to patch sklearn with intel ext when installed --- src/aspire/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/aspire/__init__.py b/src/aspire/__init__.py index 00fda26af0..7ca1a98a47 100644 --- a/src/aspire/__init__.py +++ b/src/aspire/__init__.py @@ -54,6 +54,18 @@ sys.excepthook = handle_exception +# When we are using Intel numerical backends, patch scikit to use Intel extensions. +# Note this must occur before any scikit learn imports for the patch to be effective. +try: + from sklearnex import patch_sklearn + + patch_sklearn() +except ModuleNotFoundError: + logging.debug( + "scikit-learn-intelex was not installed, skipping sklearn intel extension patching" + ) + + __all__ = [] for _, modname, _ in pkgutil.iter_modules(aspire.__path__): __all__.append(modname) # Add module to __all_ From 1da5ae5b9165929b924a66d415b7dbf9e8611ee9 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 4 Jan 2023 15:48:38 -0500 Subject: [PATCH 016/424] missing param in super init. --- src/aspire/volume/volume_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/volume/volume_synthesis.py b/src/aspire/volume/volume_synthesis.py index 377d41169f..cdc9ada2ce 100644 --- a/src/aspire/volume/volume_synthesis.py +++ b/src/aspire/volume/volume_synthesis.py @@ -356,7 +356,7 @@ class LegacyVolume(AsymmetricVolume): """ def __init__(self, L, C=2, K=16, seed=0, dtype=np.float64): - super().__init__(L=L, C=C, seed=seed, dtype=dtype) + super().__init__(L=L, C=C, K=K, seed=seed, dtype=dtype) def generate(self): """ From b8f0458a64830f0e0a15f0779c81a3f724ee7b18 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 5 Jan 2023 08:53:07 -0500 Subject: [PATCH 017/424] partial port of relative_viewing_directions_cn --- src/aspire/abinitio/commonline_cn.py | 59 ++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 1d57a60f39..fc1f07f139 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -1,6 +1,7 @@ import logging import numpy as np +from numpy.linalg import norm from tqdm import tqdm from aspire.abinitio import CLSymmetryC3C4 @@ -54,6 +55,64 @@ def _check_symmetry(self, symmetry): ) self.order = int(symmetry[1]) + def estimate_relative_viewing_directions_cn(self): + n_img = self.n_img + # n_rad = self.n_rad + n_theta = self.n_theta + pf = self.pf + max_shift_1d = self.max_shift + shift_step = self.shift_step + + # Generate candidate rotation matrices and the common-line and + # self-common-line indices induced by those rotations. + Ris_tilde, R_theta_ijs = self.generate_cand_rots() + # cijs_inds = self.compute_cls_inds(Ris_tilde, R_theta_ijs) + scls_inds = self.compute_scls_inds(Ris_tilde) + + # Generate shift phases. + r_max = pf.shape[0] + shifts, shift_phases, _ = self._generate_shift_phase_and_filter( + r_max, max_shift_1d, shift_step + ) + n_shifts = len(shifts) + all_shift_phases = shift_phases.T + + # Transpose and reconstruct full polar Fourier for use in correlation. + pf = pf.T + pf_full = np.concatenate((pf, np.conj(pf)), axis=1) + + # Step 1: pre-calculate the likelihood with respect to the self-common-lines. + scores_self_corrs = np.zeros((n_img, n_img), dtype=self.dtype) + for i in range(n_img): + pf_i = pf[i] + pf_full_i = pf_full[i] + + # Generate shifted versions of image. + pf_i_shifted = np.array( + [pf_i * shift_phase for shift_phase in all_shift_phases] + ) + pf_i_shifted = np.reshape(pf_i_shifted, (n_shifts * n_theta // 2, r_max)) + + # Normalize each ray. + pf_full_i /= norm(pf_full_i, axis=1)[..., np.newaxis] + pf_i_shifted /= norm(pf_i_shifted, axis=1)[..., np.newaxis] + + # Compute correlation. + corrs = pf_i_shifted @ np.conj(pf_full_i).T + corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) + corrs_cands = np.array( + [ + np.max( + np.real(corrs[:, scls_inds_cand[:, 0], scls_inds_cand[:, 1]]), + axis=0, + ) + for scls_inds_cand in scls_inds + ] + ) + + scores_self_corrs[i] = np.mean(np.real(corrs_cands), axis=1) + return scores_self_corrs + def compute_scls_inds(self, Ri_cands): """ Compute self-common-lines indices induced by candidate rotations. From 25fd35dc0784b3243cef63060a6f7d426d0f223f Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 5 Jan 2023 10:12:18 -0500 Subject: [PATCH 018/424] merge conflict --- tests/test_simulation.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index ba504a47ac..004914f54c 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -150,14 +150,8 @@ def testSimulationCached(self): unique_filters=[ RadialCTFFilter(defocus=d) for d in np.linspace(1.5e4, 2.5e4, 7) ], -<<<<<<< HEAD - noise_filter=IdentityFilter(), - dtype=self.dtype, -======= - seed=0, noise_adder=WhiteNoiseAdder(var=1), - dtype="single", ->>>>>>> origin/develop + dtype=self.dtype, ) sim_cached.cache() self.assertTrue( From ea3fc8b06408a4caef6cd939d61bf4d96bbf1cb1 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Thu, 5 Jan 2023 10:49:47 -0500 Subject: [PATCH 019/424] small review changes --- tests/test_image.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_image.py b/tests/test_image.py index ee4a389ef7..1cebafc800 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -24,7 +24,8 @@ def get_images(parity=0, dtype=np.float32): # numpy array for top-level functions that directly expect it im_np = misc.face(gray=True).astype(dtype)[np.newaxis, :size, :size] denom = np.max(np.abs(im_np)) - im_np = im_np / denom # Normalize test image data to 0,1 + im_np /= denom # Normalize test image data to 0,1 + # Independent Image object for testing Image methods im = Image(im_np.copy()) return im_np, im @@ -40,7 +41,6 @@ def get_stacks(parity=0, dtype=np.float32): # Independent Image stack object for testing Image methods ims = Image(ims_np) - return ims_np, ims @@ -49,8 +49,9 @@ def get_mdim_images(parity=0, dtype=np.float32): # Multi dimensional stack Image object mdim = 2 mdim_ims_np = np.concatenate([ims_np] * mdim).reshape(mdim, *ims_np.shape) - mdim_ims = Image(mdim_ims_np) + # Independent multidimensional Image stack object for testing Image methods + mdim_ims = Image(mdim_ims_np) return mdim_ims_np, mdim_ims From ce642c37fe3b9a4561db39ce004298019618dc7d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 5 Jan 2023 14:31:35 -0500 Subject: [PATCH 020/424] Update scipy and numpy library versions for python >= 3.7 --- environment-intel.yml | 4 ++-- environment-openblas.yml | 4 ++-- environment.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/environment-intel.yml b/environment-intel.yml index 95c4d57262..bac3938bb8 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -9,9 +9,9 @@ dependencies: - pyfftw - pip - python=3.8 - - numpy=1.21.5 + - numpy=1.23.5 - pandas=1.3.5 - - scipy=1.7.3 + - scipy=1.9.3 - scikit-learn - scikit-learn-intelex - scikit-image diff --git a/environment-openblas.yml b/environment-openblas.yml index c19e73e572..ba62d4a9ea 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -9,9 +9,9 @@ dependencies: - pyfftw - pip - python=3.8 - - numpy=1.21.5 + - numpy=1.23.5 - pandas=1.3.5 - - scipy=1.7.3 + - scipy=1.9.3 - scikit-learn - scikit-image - libblas=*=*openblas diff --git a/environment.yml b/environment.yml index e728827fde..1374dfad16 100644 --- a/environment.yml +++ b/environment.yml @@ -9,9 +9,9 @@ dependencies: - pyfftw - pip - python=3.8 - - numpy=1.21.5 + - numpy=1.23.5 - pandas=1.3.5 - - scipy=1.7.3 + - scipy=1.9.3 - scikit-learn - scikit-image - pip: From fb31142ff9bd9d35184b90971821486574517fb2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 5 Jan 2023 14:32:25 -0500 Subject: [PATCH 021/424] Add 'accelerate' conda optimized backend for M1 --- docs/source/installation.rst | 48 +++++++++++++++++++++++++++++++++--- environment-accelerate.yml | 20 +++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 environment-accelerate.yml diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 6e7d95457b..ae6e4af7e5 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -43,7 +43,7 @@ Finally, we install the ``aspire`` package inside the activated environment. Thi Alternative Developer Installations -************************************ +*********************************** Developers are expected to be able to manage their own code and environments. However, for consistency and newcomers, we recommend the following procedure using `conda`. @@ -65,8 +65,8 @@ you would probably want to keep that in a seperate environment. # Activate the environment conda activate aspire_dev - # Command to install the aspire package in a locally editable way: - pip install -e . + # Command to install the aspire package, along with developer extensions, in a locally editable way: + pip install -e ".[dev]" We recommend using ``conda`` or a ``virtualenv`` environment managing solutions because ASPIRE may have conflicts or change installed versions of Python packages on your system. @@ -149,3 +149,45 @@ Sphinx Documentation of the source (a local copy of what you're looking at right make html The built html files can be found at ``/path/to/git/clone/folder/docs/build/html`` + + +Optimized Numerical Backends +**************************** + +Conda provides optimized numerical backends that can provide significant +performance improvements on appropriate machines. The backends accelerate +the performance of ``numpy``, ``scipy``, and ``scikit`` packages. +ASPIRE ships several ``environment*.yml`` files which define tested package +versions along with these optimized numerical installations. + +The default ``environment.yml`` does not force a specific backend, +instead relying on ``conda`` to select something reasonable. +In the case of an Intel machine, the default ``conda`` install +will automatically install some optimizations for you. +However, these files can be used to specify a specific setup +or as the basis for your own customized ``conda`` environment. + +.. list-table:: Suggested Conda Environments + :widths: 25 25 + :header-rows: 1 + + * - Architecture + - Recommended Environment File + * - Default + - environment.yml + * - Intel x86_64 + - environment-intel.yml + * - AMD x86_64 + - environment-openblas.yml + * - Apple M1 + - environment-accelerate.yml + +Using any of these environments follows the same pattern outlined above in the developer's section. +As an example to specify using the ``accelerate`` backend on an M1 laptop: + +:: + + cd ASPIRE-Python + conda env create -f environment-accelerate.yml --name aspire_acc + conda activate aspire_acc + pip install -e ".[dev]" diff --git a/environment-accelerate.yml b/environment-accelerate.yml new file mode 100644 index 0000000000..063447cc4a --- /dev/null +++ b/environment-accelerate.yml @@ -0,0 +1,20 @@ +name: 'aspire' + +channels: + - defaults + - conda-forge + +dependencies: + - fftw + - pyfftw + - pip + - python=3.8 + - numpy=1.23.5 + - pandas=1.3.5 + - scipy=1.9.3 + - scikit-learn + - scikit-image + - libblas=*=*accelerate + - pip: + - . + From b3bf4e9b82c6f0d200bfe09b9f629351965bdc19 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 6 Jan 2023 16:16:43 -0500 Subject: [PATCH 022/424] first draft of relative_viewing_directions --- src/aspire/abinitio/commonline_cn.py | 146 +++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 7 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index fc1f07f139..0533a0b16a 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -5,7 +5,7 @@ from tqdm import tqdm from aspire.abinitio import CLSymmetryC3C4 -from aspire.utils import Rotation, anorm, cyclic_rotations +from aspire.utils import Rotation, anorm, cyclic_rotations, trange from aspire.utils.random import randn logger = logging.getLogger(__name__) @@ -57,7 +57,6 @@ def _check_symmetry(self, symmetry): def estimate_relative_viewing_directions_cn(self): n_img = self.n_img - # n_rad = self.n_rad n_theta = self.n_theta pf = self.pf max_shift_1d = self.max_shift @@ -66,8 +65,10 @@ def estimate_relative_viewing_directions_cn(self): # Generate candidate rotation matrices and the common-line and # self-common-line indices induced by those rotations. Ris_tilde, R_theta_ijs = self.generate_cand_rots() - # cijs_inds = self.compute_cls_inds(Ris_tilde, R_theta_ijs) + cijs_inds = self.compute_cls_inds(Ris_tilde, R_theta_ijs) scls_inds = self.compute_scls_inds(Ris_tilde) + n_cands = len(Ris_tilde) + n_theta_ijs = len(R_theta_ijs) # Generate shift phases. r_max = pf.shape[0] @@ -82,7 +83,7 @@ def estimate_relative_viewing_directions_cn(self): pf_full = np.concatenate((pf, np.conj(pf)), axis=1) # Step 1: pre-calculate the likelihood with respect to the self-common-lines. - scores_self_corrs = np.zeros((n_img, n_img), dtype=self.dtype) + scores_self_corrs = np.zeros((n_img, n_cands), dtype=self.dtype) for i in range(n_img): pf_i = pf[i] pf_full_i = pf_full[i] @@ -93,6 +94,10 @@ def estimate_relative_viewing_directions_cn(self): ) pf_i_shifted = np.reshape(pf_i_shifted, (n_shifts * n_theta // 2, r_max)) + # Ignore dc-component. + pf_full_i[:, 0] = 0 + pf_i_shifted[:, 0] = 0 + # Normalize each ray. pf_full_i /= norm(pf_full_i, axis=1)[..., np.newaxis] pf_i_shifted /= norm(pf_i_shifted, axis=1)[..., np.newaxis] @@ -111,7 +116,132 @@ def estimate_relative_viewing_directions_cn(self): ) scores_self_corrs[i] = np.mean(np.real(corrs_cands), axis=1) - return scores_self_corrs + + # Remove candidates that are equator images. + # TODO: Should the threshold be parameter-dependent instead of set to 10 degrees? + cii_equators_inds = np.array( + [ + ind + for (ind, Ri_tilde) in enumerate(Ris_tilde) + if abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) < 10 * np.pi / 180 + ] + ) + scores_self_corrs[:, cii_equators_inds] = 0 + + # Step 2: Compute likelihood with respect to pairwise images. + logger.info("Computing pairwise likelihood") + n_vijs = n_img * (n_img - 1) // 2 + vijs = np.zeros((n_vijs, 3, 3), dtype=self.dtype) + viis = np.zeros((n_img, 3, 3), dtype=self.dtype) + rots_symm = cyclic_rotations(self.order, self.dtype).matrices + c = 0 + e1 = [1, 0, 0] + min_ii_norm = min_jj_norm = float("inf") + for i in trange(n_img): + pf_i = pf[i] + + # Generate shifted versions of the images. + pf_i_shifted = np.array( + [pf_i * shift_phase for shift_phase in all_shift_phases] + ) + pf_i_shifted = np.reshape(pf_i_shifted, (n_shifts * n_theta // 2, r_max)) + + # Ignore dc-component. + pf_i_shifted[:, 0] = 0 + + # Normalize each ray. + pf_i_shifted /= norm(pf_i_shifted, axis=1)[..., np.newaxis] + + for j in range(i + 1, n_img): + pf_full_j = pf_full[j] + + # Ignore dc-component. + pf_full_j[:, 0] = 0 + + # Normalize each ray. + pf_full_j /= norm(pf_full_j, axis=1)[..., np.newaxis] + + # Compute correlation. + corrs_ij = pf_i_shifted @ np.conj(pf_full_j).T + + # Max out over shifts. + corrs_ij = np.max( + np.reshape(np.real(corrs_ij), (n_shifts, n_theta // 2, n_theta)), + axis=0, + ) + + # Arrange correlation based on common lines induced by candidate rotations. + corrs = corrs_ij[cijs_inds[..., 0], cijs_inds[..., 1]] + corrs = np.reshape(corrs, (-1, self.order, n_theta_ijs // self.order)) + corrs = np.mean( + corrs, axis=1 + ) # take the mean over all symmetric common lines + corrs = np.reshape( + corrs, + ( + self.n_points_sphere, + self.n_points_sphere, + n_theta_ijs // self.order, + ), + ) + + # Self common-lines are invariant to n_theta_ijs (i.e., in-plane rotation angles) so max them out. + opt_theta_ij_ind_per_sphere_points = np.argmax(corrs, axis=-1) + corrs = np.max(corrs, axis=-1) + + # Maximum likelihood while taking into consideration both cls and scls. + corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j]) + + # Extract the optimal candidates. + opt_sphere_i, opt_sphere_j = np.unravel_index( + np.argmax(corrs), corrs.shape + ) + opt_theta_ij = opt_theta_ij_ind_per_sphere_points[ + opt_sphere_i, opt_sphere_j + ] + + opt_Ri_tilde = Ris_tilde[opt_sphere_i] + opt_Rj_tilde = Ris_tilde[opt_sphere_j] + opt_R_theta_ij = R_theta_ijs[opt_theta_ij] + + # Compute the estimate of vi*vi.T as given by j. + vii_j = np.mean( + np.array( + [opt_Ri_tilde.T @ rot @ opt_Ri_tilde for rot in rots_symm] + ), + axis=0, + ) + + _, svals, _ = np.linalg.svd(vii_j) + if np.linalg.norm(svals - e1, 2) < min_ii_norm: + viis[i] = vii_j + + # Compute the estimate of vj*vj.T as given by i. + vjj_i = np.mean( + np.array( + [opt_Rj_tilde.T @ rot @ opt_Rj_tilde for rot in rots_symm] + ), + axis=0, + ) + + _, svals, _ = np.linalg.svd(vjj_i) + if np.linalg.norm(svals - e1, 2) < min_jj_norm: + viis[j] = vjj_i + + # Compute the estimate of vi*vj.T. + vijs[c] = np.mean( + np.array( + [ + opt_Ri_tilde.T @ rot @ opt_R_theta_ij @ opt_Rj_tilde + for rot in rots_symm + ] + ), + axis=0, + ) + + c += 1 + + return viis, vijs def compute_scls_inds(self, Ri_cands): """ @@ -178,11 +308,13 @@ def compute_cls_inds(self, Ris_tilde, R_theta_ijs): def generate_cand_rots(self): # Construct candidate rotations, Ris_tilde. - vis = self.generate_cand_rots_third_rows(self.n_points_sphere) + vis = self.generate_cand_rots_third_rows() Ris_tilde = np.array([self._complete_third_row_to_rot(vi) for vi in vis]) # Construct all in-plane rotations, R_theta_ijs - theta_ij = np.arange(0, 360, self.degree_res) * np.pi / 180 + # The number of R_theta_ijs must be divisible by the symmetric order. + n_theta_ij = 360 - (360 % self.order) + theta_ij = np.arange(0, n_theta_ij, self.degree_res) * np.pi / 180 R_theta_ijs = Rotation.about_axis("z", theta_ij).matrices return Ris_tilde, R_theta_ijs From f28e3744eb1329252bfcfcfa1984ef2d510ec1aa Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 9 Jan 2023 08:36:30 -0500 Subject: [PATCH 023/424] Change ubuntu 20.04 azure job to be intel conda env, adds azure openblas env job --- azure-pipelines.yml | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e7be12645a..f266683d39 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,7 +32,7 @@ jobs: displayName: pytest - job: - displayName: ubuntu-20.04 + displayName: ubuntu-20.04-openblas pool: vmImage: 'ubuntu-20.04' strategy: @@ -47,16 +47,40 @@ jobs: # - bash: conda update -q -y conda # displayName: Update conda - - bash: conda env create --name myEnvironment --file environment.yml + - bash: conda env create --name openblas_env --file environment-openblas.yml displayName: Create Anaconda environment - bash: | - source activate myEnvironment + source activate openblas_env pip install -e .[dev] pip freeze --all pytest --durations=50 --random-order displayName: pytest +- job: + displayName: ubuntu-20.04-intel + pool: + vmImage: 'ubuntu-20.04' + strategy: + matrix: + Python38: + python.version: '3.8' + + steps: + - bash: echo "##vso[task.prependpath]$CONDA/bin" + displayName: Add conda to PATH + + - bash: conda env create --name intel_env --file environment-intel.yml + displayName: Create Anaconda environment + + - bash: | + source activate intel_env + pip install -e .[dev] + pip freeze --all + pytest --durations=50 --random-order + displayName: pytest + + - job: displayName: macOS-latest pool: From 82cad7548719b50d87539652b44f3c9a011e46ad Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 9 Jan 2023 08:37:16 -0500 Subject: [PATCH 024/424] Remove commented conda update line --- azure-pipelines.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f266683d39..868fdbdb34 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -44,9 +44,6 @@ jobs: - bash: echo "##vso[task.prependpath]$CONDA/bin" displayName: Add conda to PATH -# - bash: conda update -q -y conda -# displayName: Update conda - - bash: conda env create --name openblas_env --file environment-openblas.yml displayName: Create Anaconda environment From 78fe1b22352126d58f7373990571b667e947cace Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 9 Jan 2023 12:05:14 -0500 Subject: [PATCH 025/424] Always numpy.show_config() in azure pipelines allows visual inspection/confirmation of backend --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 868fdbdb34..edb7da0a2e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,6 +28,7 @@ jobs: source activate myEnvironment pip install -e .[dev] pip freeze --all + python -c "import numpy; numpy.show_config()" pytest --durations=50 --random-order displayName: pytest @@ -51,6 +52,7 @@ jobs: source activate openblas_env pip install -e .[dev] pip freeze --all + python -c "import numpy; numpy.show_config()" pytest --durations=50 --random-order displayName: pytest @@ -74,6 +76,7 @@ jobs: source activate intel_env pip install -e .[dev] pip freeze --all + python -c "import numpy; numpy.show_config()" pytest --durations=50 --random-order displayName: pytest @@ -106,6 +109,7 @@ jobs: source activate myEnvironment pip install -e .[dev] pip freeze --all + python -c "import numpy; numpy.show_config()" pytest --durations=50 -s displayName: pytest @@ -137,6 +141,7 @@ jobs: source activate myEnvironment pip install -e .[dev] pip freeze --all + python -c "import numpy; numpy.show_config()" pytest --durations=50 -s displayName: pytest @@ -163,5 +168,6 @@ jobs: call activate myEnvironment pip install -e .[dev] pip freeze --all + python -c "import numpy; numpy.show_config()" pytest --durations=50 --random-order displayName: pytest From ceb36edc235ee7a83763133abac46a169a51edff Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 9 Jan 2023 12:47:35 -0500 Subject: [PATCH 026/424] Test we're using specfici backend in Azure pipelines. --- azure-pipelines.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index edb7da0a2e..5140038cd3 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -33,7 +33,7 @@ jobs: displayName: pytest - job: - displayName: ubuntu-20.04-openblas + displayName: ubuntu-20.04-open pool: vmImage: 'ubuntu-20.04' strategy: @@ -45,14 +45,15 @@ jobs: - bash: echo "##vso[task.prependpath]$CONDA/bin" displayName: Add conda to PATH - - bash: conda env create --name openblas_env --file environment-openblas.yml + - bash: conda env create --name open_env --file environment-openblas.yml displayName: Create Anaconda environment - bash: | - source activate openblas_env + source activate open_env pip install -e .[dev] pip freeze --all - python -c "import numpy; numpy.show_config()" + python -c "import numpy; numpy.show_config()" + python -c "import numpy; numpy.show_config()" | grep -qi openblas pytest --durations=50 --random-order displayName: pytest @@ -77,6 +78,7 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" + python -c "import numpy; numpy.show_config()" | grep -qi mkl pytest --durations=50 --random-order displayName: pytest From 02e2ca7f1943301942487536f1d8cb1701abefb3 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 9 Jan 2023 16:05:55 -0500 Subject: [PATCH 027/424] remove unnecessary list comprehension. add logger messages. add comments. --- src/aspire/abinitio/commonline_cn.py | 54 +++++++++++----------------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 0533a0b16a..917d50ad53 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -84,7 +84,8 @@ def estimate_relative_viewing_directions_cn(self): # Step 1: pre-calculate the likelihood with respect to the self-common-lines. scores_self_corrs = np.zeros((n_img, n_cands), dtype=self.dtype) - for i in range(n_img): + logger.info("Computing likelihood wrt self common-lines.") + for i in trange(n_img): pf_i = pf[i] pf_full_i = pf_full[i] @@ -117,18 +118,20 @@ def estimate_relative_viewing_directions_cn(self): scores_self_corrs[i] = np.mean(np.real(corrs_cands), axis=1) - # Remove candidates that are equator images. + # Remove candidates that are equator images. Equator candidates induce collinear + # self common-lines, which always have perfect correlation. # TODO: Should the threshold be parameter-dependent instead of set to 10 degrees? cii_equators_inds = np.array( [ ind for (ind, Ri_tilde) in enumerate(Ris_tilde) - if abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) < 10 * np.pi / 180 + if abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) < 5 * np.pi / 180 ] ) scores_self_corrs[:, cii_equators_inds] = 0 - # Step 2: Compute likelihood with respect to pairwise images. + # Step 2: Compute the likelihood for each pair of candidate matrices with respect + # to the common-lines they induce. logger.info("Computing pairwise likelihood") n_vijs = n_img * (n_img - 1) // 2 vijs = np.zeros((n_vijs, 3, 3), dtype=self.dtype) @@ -136,7 +139,7 @@ def estimate_relative_viewing_directions_cn(self): rots_symm = cyclic_rotations(self.order, self.dtype).matrices c = 0 e1 = [1, 0, 0] - min_ii_norm = min_jj_norm = float("inf") + min_ii_norm = float("inf") * np.ones(n_img) for i in trange(n_img): pf_i = pf[i] @@ -173,9 +176,8 @@ def estimate_relative_viewing_directions_cn(self): # Arrange correlation based on common lines induced by candidate rotations. corrs = corrs_ij[cijs_inds[..., 0], cijs_inds[..., 1]] corrs = np.reshape(corrs, (-1, self.order, n_theta_ijs // self.order)) - corrs = np.mean( - corrs, axis=1 - ) # take the mean over all symmetric common lines + # Take the mean over all symmetric common lines. + corrs = np.mean(corrs, axis=1) corrs = np.reshape( corrs, ( @@ -205,43 +207,29 @@ def estimate_relative_viewing_directions_cn(self): opt_R_theta_ij = R_theta_ijs[opt_theta_ij] # Compute the estimate of vi*vi.T as given by j. - vii_j = np.mean( - np.array( - [opt_Ri_tilde.T @ rot @ opt_Ri_tilde for rot in rots_symm] - ), - axis=0, - ) + vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) - _, svals, _ = np.linalg.svd(vii_j) - if np.linalg.norm(svals - e1, 2) < min_ii_norm: + svals = np.linalg.svd(vii_j, compute_uv=False) + if np.linalg.norm(svals - e1, 2) < min_ii_norm[i]: viis[i] = vii_j + min_ii_norm[i] = np.linalg.norm(svals - e1, 2) # Compute the estimate of vj*vj.T as given by i. - vjj_i = np.mean( - np.array( - [opt_Rj_tilde.T @ rot @ opt_Rj_tilde for rot in rots_symm] - ), - axis=0, - ) + vjj_i = np.mean(opt_Rj_tilde.T @ rots_symm @ opt_Rj_tilde, axis=0) - _, svals, _ = np.linalg.svd(vjj_i) - if np.linalg.norm(svals - e1, 2) < min_jj_norm: + svals = np.linalg.svd(vjj_i, compute_uv=False) + if np.linalg.norm(svals - e1, 2) < min_ii_norm[j]: viis[j] = vjj_i + min_ii_norm[j] = np.linalg.norm(svals - e1, 2) # Compute the estimate of vi*vj.T. vijs[c] = np.mean( - np.array( - [ - opt_Ri_tilde.T @ rot @ opt_R_theta_ij @ opt_Rj_tilde - for rot in rots_symm - ] - ), - axis=0, + opt_Ri_tilde.T @ rots_symm @ opt_R_theta_ij @ opt_Rj_tilde, axis=0 ) c += 1 - return viis, vijs + return viis, vijs def compute_scls_inds(self, Ri_cands): """ @@ -307,6 +295,7 @@ def compute_cls_inds(self, Ris_tilde, R_theta_ijs): return cij_inds def generate_cand_rots(self): + logger.info("Generating candidate rotations.") # Construct candidate rotations, Ris_tilde. vis = self.generate_cand_rots_third_rows() Ris_tilde = np.array([self._complete_third_row_to_rot(vi) for vi in vis]) @@ -321,7 +310,6 @@ def generate_cand_rots(self): def generate_cand_rots_third_rows(self, legacy=True): n_points_sphere = self.n_points_sphere - if legacy: # Genereate random points on the sphere third_rows = randn(n_points_sphere, 3) From b5503d99d9177c0ffced7172d78b1b0f50a5602d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 10 Jan 2023 09:00:38 -0500 Subject: [PATCH 028/424] rollback the scikit intelex --- environment-intel.yml | 1 - src/aspire/__init__.py | 13 ------------- 2 files changed, 14 deletions(-) diff --git a/environment-intel.yml b/environment-intel.yml index bac3938bb8..695bc413ea 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -13,7 +13,6 @@ dependencies: - pandas=1.3.5 - scipy=1.9.3 - scikit-learn - - scikit-learn-intelex - scikit-image - mkl_fft - libblas=*=*mkl diff --git a/src/aspire/__init__.py b/src/aspire/__init__.py index 61e7f7656c..bca0d39dfd 100644 --- a/src/aspire/__init__.py +++ b/src/aspire/__init__.py @@ -65,19 +65,6 @@ sys.excepthook = handle_exception -# When we are using Intel numerical backends, patch scikit to use Intel extensions. -# Note this must occur before any scikit learn imports for the patch to be effective. -try: - from sklearnex import patch_sklearn - - patch_sklearn() - -except ModuleNotFoundError: - logging.debug( - "scikit-learn-intelex was not installed, skipping sklearn intel extension patching" - ) - - __all__ = [] for _, modname, _ in pkgutil.iter_modules(aspire.__path__): __all__.append(modname) # Add module to __all_ From 890db85ac66f632e64d273a7f5071347b60f9a4f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 11 Jan 2023 09:50:57 -0500 Subject: [PATCH 029/424] Set OMP threads to 2 for azure pipelines Azure states linux machines receive 2 vcpu --- azure-pipelines.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5140038cd3..732499320f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -54,6 +54,7 @@ jobs: pip freeze --all python -c "import numpy; numpy.show_config()" python -c "import numpy; numpy.show_config()" | grep -qi openblas + export OMP_NUM_THREADS=2 pytest --durations=50 --random-order displayName: pytest @@ -79,6 +80,7 @@ jobs: pip freeze --all python -c "import numpy; numpy.show_config()" python -c "import numpy; numpy.show_config()" | grep -qi mkl + export OMP_NUM_THREADS=2 pytest --durations=50 --random-order displayName: pytest From 851ec50273698b573383c470d8cd30f9086b5d0d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 11 Jan 2023 10:21:03 -0500 Subject: [PATCH 030/424] Set OMP threads to 1 for azure pipelines --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 732499320f..2c9c2f4782 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -54,7 +54,7 @@ jobs: pip freeze --all python -c "import numpy; numpy.show_config()" python -c "import numpy; numpy.show_config()" | grep -qi openblas - export OMP_NUM_THREADS=2 + export OMP_NUM_THREADS=1 pytest --durations=50 --random-order displayName: pytest @@ -80,7 +80,7 @@ jobs: pip freeze --all python -c "import numpy; numpy.show_config()" python -c "import numpy; numpy.show_config()" | grep -qi mkl - export OMP_NUM_THREADS=2 + export OMP_NUM_THREADS=1 pytest --durations=50 --random-order displayName: pytest From 0e2c38ad860bf5c1f41a8513d4e4fd2f1a5c27c9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 11 Jan 2023 15:23:34 -0500 Subject: [PATCH 031/424] Increase timeout for debugging and make legacy jobs parallel pytest --- azure-pipelines.yml | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2c9c2f4782..5f15c29c32 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,7 +29,7 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest --durations=50 --random-order + pytest -n 2 --durations=50 --random-order displayName: pytest - job: @@ -40,20 +40,24 @@ jobs: matrix: Python38: python.version: '3.8' + timeoutInMinutes: 120 # how long to run the job before automatically cancelling steps: - bash: echo "##vso[task.prependpath]$CONDA/bin" displayName: Add conda to PATH - - bash: conda env create --name open_env --file environment-openblas.yml - displayName: Create Anaconda environment - - bash: | + conda env create --name open_env --file environment-openblas.yml source activate open_env pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" python -c "import numpy; numpy.show_config()" | grep -qi openblas + + displayName: Create Anaconda environment + + - bash: | + source activate open_env export OMP_NUM_THREADS=1 pytest --durations=50 --random-order displayName: pytest @@ -66,20 +70,23 @@ jobs: matrix: Python38: python.version: '3.8' + timeoutInMinutes: 120 # how long to run the job before automatically cancelling steps: - bash: echo "##vso[task.prependpath]$CONDA/bin" displayName: Add conda to PATH - - bash: conda env create --name intel_env --file environment-intel.yml - displayName: Create Anaconda environment - - bash: | + conda env create --name intel_env --file environment-intel.yml source activate intel_env pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" python -c "import numpy; numpy.show_config()" | grep -qi mkl + displayName: Create Anaconda environment + + - bash: | + source activate intel_env export OMP_NUM_THREADS=1 pytest --durations=50 --random-order displayName: pytest @@ -114,7 +121,7 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest --durations=50 -s + pytest -n 3 --durations=50 -s displayName: pytest - job: @@ -146,7 +153,7 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest --durations=50 -s + pytest -n 3 --durations=50 -s displayName: pytest - job: @@ -173,5 +180,5 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest --durations=50 --random-order + pytest -n 2 --durations=50 --random-order displayName: pytest From 07d59c4da2d8be571b15397c45a2df1bbabe74fa Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 11 Jan 2023 15:31:27 -0500 Subject: [PATCH 032/424] Flake8 ignore sphinx gallery build --- tox.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tox.ini b/tox.ini index 4676a9c5d3..f37b2826d4 100644 --- a/tox.ini +++ b/tox.ini @@ -69,6 +69,9 @@ extend-ignore = E203, E501 per-file-ignores = __init__.py: F401 gallery/tutorials/configuration.py: T201, E402 + # Ignore Sphinx gallery builds + docs/build/html/_downloads/*/*.py: T201, E402 + docs/source/auto*/*.py: T201, E402 [isort] default_section = THIRDPARTY From 4f8bacc62908385bf7e982063126d692a48bde51 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 12 Jan 2023 13:15:10 -0500 Subject: [PATCH 033/424] Misplaced negative sign! Use aspire tqdm. --- src/aspire/abinitio/commonline_cn.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 917d50ad53..16043f4c94 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -2,10 +2,9 @@ import numpy as np from numpy.linalg import norm -from tqdm import tqdm from aspire.abinitio import CLSymmetryC3C4 -from aspire.utils import Rotation, anorm, cyclic_rotations, trange +from aspire.utils import Rotation, anorm, cyclic_rotations, tqdm, trange from aspire.utils.random import randn logger = logging.getLogger(__name__) @@ -103,7 +102,7 @@ def estimate_relative_viewing_directions_cn(self): pf_full_i /= norm(pf_full_i, axis=1)[..., np.newaxis] pf_i_shifted /= norm(pf_i_shifted, axis=1)[..., np.newaxis] - # Compute correlation. + # Compute correlation of pf_i with itself over all shifts. corrs = pf_i_shifted @ np.conj(pf_full_i).T corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) corrs_cands = np.array( @@ -132,7 +131,7 @@ def estimate_relative_viewing_directions_cn(self): # Step 2: Compute the likelihood for each pair of candidate matrices with respect # to the common-lines they induce. - logger.info("Computing pairwise likelihood") + logger.info("Computing pairwise likelihood.") n_vijs = n_img * (n_img - 1) // 2 vijs = np.zeros((n_vijs, 3, 3), dtype=self.dtype) viis = np.zeros((n_img, 3, 3), dtype=self.dtype) @@ -250,7 +249,7 @@ def compute_scls_inds(self, Ri_cands): Riigs = Ri_cand.T @ rots_symm[1 : n_scl_pairs + 1] @ Ri_cand c1s = np.array((-Riigs[:, 1, 2], Riigs[:, 0, 2])).T - c2s = np.array((-Riigs[:, 2, 1], Riigs[:, 2, 0])).T + c2s = np.array((Riigs[:, 2, 1], -Riigs[:, 2, 0])).T c1s_inds = self.cl_angles_to_ind(c1s, n_theta) c2s_inds = self.cl_angles_to_ind(c2s, n_theta) @@ -279,7 +278,7 @@ def compute_cls_inds(self, Ris_tilde, R_theta_ijs): R_cands = Ris_tilde[i].T @ R_theta_ijs @ Ris_tilde[j] c1s = np.array((-R_cands[:, 1, 2], R_cands[:, 0, 2])).T - c2s = np.array((-R_cands[:, 2, 1], R_cands[:, 2, 0])).T + c2s = np.array((R_cands[:, 2, 1], -R_cands[:, 2, 0])).T c1s = self.cl_angles_to_ind(c1s, n_theta) c2s = self.cl_angles_to_ind(c2s, n_theta) @@ -312,7 +311,7 @@ def generate_cand_rots_third_rows(self, legacy=True): n_points_sphere = self.n_points_sphere if legacy: # Genereate random points on the sphere - third_rows = randn(n_points_sphere, 3) + third_rows = randn(n_points_sphere, 3, seed=self.seed) third_rows /= anorm(third_rows, axes=(-1,))[:, np.newaxis] else: # Use Fibonocci sphere points From 01797b43abf3a534fb9da28f533089aec7f7b293 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 12 Jan 2023 13:26:23 -0500 Subject: [PATCH 034/424] Use same name for _estimate_relative_viewing_directions so estimate_rotations() is called from super. --- src/aspire/abinitio/commonline_c3_c4.py | 6 +++--- src/aspire/abinitio/commonline_cn.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 363ede7655..1514996459 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -96,12 +96,12 @@ def _check_symmetry(self, symmetry): def estimate_rotations(self): """ - Estimate rotation matrices for molecules with C3 or C4 symmetry. + Estimate rotation matrices for molecules with Cn symmetry. :return: Array of rotation matrices, size n_imgx3x3. """ logger.info(f"Estimating relative viewing directions for {self.n_img} images.") - vijs, viis = self._estimate_relative_viewing_directions_c3_c4() + vijs, viis = self._estimate_relative_viewing_directions() logger.info("Performing global handedness synchronization.") vijs, viis = self._global_J_sync(vijs, viis) @@ -118,7 +118,7 @@ def estimate_rotations(self): # Primary Methods # ########################################### - def _estimate_relative_viewing_directions_c3_c4(self): + def _estimate_relative_viewing_directions(self): """ Estimate the relative viewing directions vij = vi*vj^T, i Date: Thu, 12 Jan 2023 13:33:44 -0500 Subject: [PATCH 035/424] prefer conda-forge channel and try to force GNU OMP --- azure-pipelines.yml | 16 ++++++++++------ environment-accelerate.yml | 3 ++- environment-intel.yml | 3 ++- environment-openblas.yml | 3 ++- environment.yml | 3 ++- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5f15c29c32..4fe0135ed4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,8 +28,10 @@ jobs: source activate myEnvironment pip install -e .[dev] pip freeze --all + export MKL_THREADING_LAYER=GNU + export OMP_NUM_THREADS=2 python -c "import numpy; numpy.show_config()" - pytest -n 2 --durations=50 --random-order + pytest --durations=50 --random-order displayName: pytest - job: @@ -58,7 +60,8 @@ jobs: - bash: | source activate open_env - export OMP_NUM_THREADS=1 + export MKL_THREADING_LAYER=GNU + export OMP_NUM_THREADS=2 pytest --durations=50 --random-order displayName: pytest @@ -87,7 +90,8 @@ jobs: - bash: | source activate intel_env - export OMP_NUM_THREADS=1 + export MKL_THREADING_LAYER=GNU + export OMP_NUM_THREADS=2 pytest --durations=50 --random-order displayName: pytest @@ -121,7 +125,7 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest -n 3 --durations=50 -s + pytest --durations=50 -s displayName: pytest - job: @@ -153,7 +157,7 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest -n 3 --durations=50 -s + pytest --durations=50 -s displayName: pytest - job: @@ -180,5 +184,5 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - pytest -n 2 --durations=50 --random-order + pytest --durations=50 --random-order displayName: pytest diff --git a/environment-accelerate.yml b/environment-accelerate.yml index 063447cc4a..a9de126750 100644 --- a/environment-accelerate.yml +++ b/environment-accelerate.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw diff --git a/environment-intel.yml b/environment-intel.yml index 695bc413ea..ae0025ed09 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw diff --git a/environment-openblas.yml b/environment-openblas.yml index ba62d4a9ea..a8c3fa11db 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw diff --git a/environment.yml b/environment.yml index 1374dfad16..2fc81f4237 100644 --- a/environment.yml +++ b/environment.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw From fe4a22067079d913fa5b20e27fbfb72532bcb4cd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 12 Jan 2023 13:41:32 -0500 Subject: [PATCH 036/424] conda-forge lib names --- azure-pipelines.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4fe0135ed4..97d81f2fe5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -54,8 +54,6 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - python -c "import numpy; numpy.show_config()" | grep -qi openblas - displayName: Create Anaconda environment - bash: | @@ -85,7 +83,6 @@ jobs: pip install -e .[dev] pip freeze --all python -c "import numpy; numpy.show_config()" - python -c "import numpy; numpy.show_config()" | grep -qi mkl displayName: Create Anaconda environment - bash: | From 6dd1b7f0138aec90e9630e3d0ba152b8d3a5cd41 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 12 Jan 2023 13:59:30 -0500 Subject: [PATCH 037/424] return order --- src/aspire/abinitio/commonline_cn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 30a7b13c72..5d2ed829b0 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -228,7 +228,7 @@ def _estimate_relative_viewing_directions(self): c += 1 - return viis, vijs + return vijs, viis def compute_scls_inds(self, Ri_cands): """ From b864250e00788be8adc286876beca0689dda238a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 12 Jan 2023 14:19:48 -0500 Subject: [PATCH 038/424] conda-forge only? --- environment-intel.yml | 2 -- environment-openblas.yml | 1 - 2 files changed, 3 deletions(-) diff --git a/environment-intel.yml b/environment-intel.yml index ae0025ed09..f8416ff54d 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -2,8 +2,6 @@ name: 'aspire' channels: - conda-forge - - defaults - dependencies: - fftw diff --git a/environment-openblas.yml b/environment-openblas.yml index a8c3fa11db..74668c1bf2 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -2,7 +2,6 @@ name: 'aspire' channels: - conda-forge - - defaults dependencies: From 6a66e4acd854d75799f38f74cac7eef65a08887f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 12 Jan 2023 14:59:15 -0500 Subject: [PATCH 039/424] revert. set max parallel --- azure-pipelines.yml | 6 ++++++ environment-accelerate.yml | 3 +-- environment-intel.yml | 1 + environment-openblas.yml | 2 +- environment.yml | 3 +-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 97d81f2fe5..cd1ffd3111 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ jobs: pool: vmImage: 'ubuntu-22.04' strategy: + maxParallel: 4 matrix: Python38: python.version: '3.8' @@ -39,6 +40,7 @@ jobs: pool: vmImage: 'ubuntu-20.04' strategy: + maxParallel: 4 matrix: Python38: python.version: '3.8' @@ -68,6 +70,7 @@ jobs: pool: vmImage: 'ubuntu-20.04' strategy: + maxParallel: 4 matrix: Python38: python.version: '3.8' @@ -98,6 +101,7 @@ jobs: pool: vmImage: 'macOS-latest' strategy: + maxParallel: 4 matrix: Python38: python.version: '3.8' @@ -130,6 +134,7 @@ jobs: pool: vmImage: 'macOS-11' strategy: + maxParallel: 4 matrix: Python38: python.version: '3.8' @@ -162,6 +167,7 @@ jobs: pool: vmImage: 'windows-latest' strategy: + maxParallel: 4 matrix: Python38: python.version: '3.8' diff --git a/environment-accelerate.yml b/environment-accelerate.yml index a9de126750..063447cc4a 100644 --- a/environment-accelerate.yml +++ b/environment-accelerate.yml @@ -1,9 +1,8 @@ name: 'aspire' channels: - - conda-forge - defaults - + - conda-forge dependencies: - fftw diff --git a/environment-intel.yml b/environment-intel.yml index f8416ff54d..695bc413ea 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -1,6 +1,7 @@ name: 'aspire' channels: + - defaults - conda-forge dependencies: diff --git a/environment-openblas.yml b/environment-openblas.yml index 74668c1bf2..ba62d4a9ea 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -1,9 +1,9 @@ name: 'aspire' channels: + - defaults - conda-forge - dependencies: - fftw - pyfftw diff --git a/environment.yml b/environment.yml index 2fc81f4237..1374dfad16 100644 --- a/environment.yml +++ b/environment.yml @@ -1,9 +1,8 @@ name: 'aspire' channels: - - conda-forge - defaults - + - conda-forge dependencies: - fftw From a3632e059ee09e41ae50bba1d54fb551a03a244b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 13 Jan 2023 09:05:48 -0500 Subject: [PATCH 040/424] Change custom noise adder to use filter directly, and test with sample previously was using sample for adder --- src/aspire/noise/noise.py | 32 ++++++++------------------------ tests/test_noise.py | 11 +++++++++++ 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index 4f5097987e..5fc8206563 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -60,6 +60,9 @@ def noise_var(self): """ Concrete implementations are expected to provide a method that returns the noise variance for the NoiseAdder. + + Authors of `NoiseAdder`s are encouraged to consider any theoretically + superior methods of calculating noise variance directly, """ @@ -69,34 +72,15 @@ class CustomNoiseAdder(NoiseAdder): """ @property - def noise_var(self): - """ - Return noise variance. - - CustomNoiseAdder will estimate noise_var by taking a sample of the noise. - - If you require tuning the noise_var sampling, see `get_noise_var`. - """ - return self.get_noise_var() - - def get_noise_var(self, sample_n=100, sample_res=128): + def noise_var(self, res=512): """ Return noise variance. - CustomNoiseAdder will estimate noise_var by taking a sample of the noise. - - It is highly encouraged that authors of `CustomNoiseAdder`s consider - any theoretically superior methods of calculating noise variance, - or test that this method's default values are satisfactory for their - implementation. - - :sample_n: Number of images to sample. - :sample_res: Resolution of sample (noise) images. - :returns: Noise Variance. + CustomNoiseAdder will estimate noise_var using the `noise_filter`. + :param res: Resolution to use when evaluating noise filter, default 512. """ - im_zeros = Image(np.zeros((sample_n, sample_res, sample_res))) - im_noise_sample = self._forward(im_zeros, range(sample_n)) - return np.var(im_noise_sample.asnumpy()) + # Take mean of user provided _noise_filter, before the PowerFilter is applied. + return np.mean(self._noise_filter.evaluate_grid(res)) class WhiteNoiseAdder(NoiseAdder): diff --git a/tests/test_noise.py b/tests/test_noise.py index a773d77677..8f1a769098 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -5,6 +5,7 @@ import numpy as np import pytest +from aspire.image import Image from aspire.noise import CustomNoiseAdder, WhiteNoiseAdder, WhiteNoiseEstimator from aspire.operators import FunctionFilter, ScalarFilter from aspire.source.simulation import Simulation @@ -130,3 +131,13 @@ def pinkish_spectrum(x, y): # Check we are achieving an estimate near the target logger.debug(f"Estimated Noise Variance {estimated_noise_var}") assert np.isclose(estimated_noise_var, target_noise_variance, rtol=0.1) + + # Check sampling yields an estimate near target. + sample_n = 16 + sample_res = 32 + im_zeros = Image(np.zeros((sample_n, sample_res, sample_res))) + im_noise_sample = sim_fixture.noise_adder._forward(im_zeros, range(sample_n)) + sampled_noise_var = np.var(im_noise_sample.asnumpy()) + + logger.debug(f"Sampled Noise Variance {sampled_noise_var}") + assert np.isclose(sampled_noise_var, target_noise_variance, rtol=0.1) From e96b7a87c6bb24f36f7f2b6dcebc7531b9911fd6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 13 Jan 2023 12:02:06 -0500 Subject: [PATCH 041/424] Adds PSNR and associated unit test --- src/aspire/source/simulation.py | 44 +++++++++++++++++++++++++++++++++ tests/test_noise.py | 30 +++++++++++++++++++++- 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 49014e5827..b49672868d 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -3,6 +3,7 @@ import numpy as np from scipy.linalg import eigh, qr +from sklearn.metrics import mean_squared_error from aspire.image import Image from aspire.noise import NoiseAdder @@ -13,6 +14,7 @@ ainner, anorm, make_symmat, + trange, uniform_random_angles, vecmat_to_volmat, ) @@ -468,3 +470,45 @@ def eval_coords(self, mean_vol, eig_vols, coords_est): corr = inner / (norm_true * norm_est) return {"err": err, "rel_err": rel_err, "corr": corr} + + def estimate_psnr(self, sample_n=None, batch_size=512): + """ + Estimate Peak SNR in decibels as 10*Log(max(signal)^2 / MSE), + where MSE is computed between `projections` + and the resulting simulated `images`. + PSNR is computed along the stack axis and an average + decibel value across the sample is returned. + Note that PSNR is inherently a poor metric for identical images. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :returns: Estimated peak signal to noise ratio in decibels. + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_psnr` sample_n > Simulation.n: {sample_n} > Simulation.n." + " Accuracy may be impaired." + ) + + peaksq = np.empty(sample_n, dtype=self.dtype) + mse = np.empty(sample_n, dtype=self.dtype) + for start in trange(0, sample_n, batch_size): + end = min(start + batch_size, sample_n) + + signal = self.projections[start:end].asnumpy() + images = self.images[start:end].asnumpy() + peaksq[start:end] = np.square(signal).max(axis=(-1, -2)) # max per image + + # Reshape and Transpose for Scikit metrics, + # which expect a 2d (samples, outputs) + mse[start:end] = mean_squared_error( + signal.reshape(sample_n, -1).T, + images.reshape(sample_n, -1).T, + multioutput="raw_values", + ) + + return np.mean(10 * np.log10(peaksq / mse)) diff --git a/tests/test_noise.py b/tests/test_noise.py index 8f1a769098..82536aa1e4 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -9,7 +9,8 @@ from aspire.noise import CustomNoiseAdder, WhiteNoiseAdder, WhiteNoiseEstimator from aspire.operators import FunctionFilter, ScalarFilter from aspire.source.simulation import Simulation -from aspire.volume import AsymmetricVolume +from aspire.utils import grid_3d +from aspire.volume import AsymmetricVolume, Volume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -38,6 +39,7 @@ def sim_fixture(request): return Simulation( vols=AsymmetricVolume(L=resolution, C=1, dtype=dtype).generate(), n=16, + amplitudes=1, offsets=0, dtype=dtype, ) @@ -141,3 +143,29 @@ def pinkish_spectrum(x, y): logger.debug(f"Sampled Noise Variance {sampled_noise_var}") assert np.isclose(sampled_noise_var, target_noise_variance, rtol=0.1) + + +@pytest.mark.parametrize("res", RESOLUTIONS, ids=lambda param: f"res={param}") +@pytest.mark.parametrize( + "target_noise_variance", VARS, ids=lambda param: f"var={param}" +) +@pytest.mark.parametrize("dtype", DTYPES, ids=lambda param: f"dtype={param}") +def test_psnr(res, target_noise_variance, dtype): + + vol = np.ones((res,) * 3, dtype=dtype) + g = grid_3d(res, normalized=False) + mask = g["r"] > res // 2 + vol[mask] = 0 + + sim = Simulation( + vols=Volume(vol), + n=16, + amplitudes=1, + offsets=0, + dtype=dtype, + noise_adder=WhiteNoiseAdder(var=target_noise_variance), + ) + + psnr = sim.estimate_psnr() + logger.debug(f"PSNR target={target_noise_variance} L={sim.L} {sim.dtype} {psnr}") + assert np.isclose(psnr, -10 * np.log10(target_noise_variance), rtol=0.01) From edab50da3483a335f73f48cfba6bb63fc9f3385e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 13 Jan 2023 13:53:47 -0500 Subject: [PATCH 042/424] Adds the `from_snr` methods and test still not liking the signal var estimates --- src/aspire/source/simulation.py | 97 ++++++++++++++++++++++++++++++++- tests/test_noise.py | 37 +++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index b49672868d..a3ed674529 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -6,13 +6,14 @@ from sklearn.metrics import mean_squared_error from aspire.image import Image -from aspire.noise import NoiseAdder +from aspire.noise import NoiseAdder, WhiteNoiseAdder from aspire.source import ImageSource from aspire.source.image import _ImageAccessor from aspire.utils import ( acorr, ainner, anorm, + grid_2d, make_symmat, trange, uniform_random_angles, @@ -512,3 +513,97 @@ def estimate_psnr(self, sample_n=None, batch_size=512): ) return np.mean(10 * np.log10(peaksq / mse)) + + @classmethod + def from_snr( + cls, + target_snr, + *args, + sample_n=100, + support_radius=1, + **kwargs, + ): + """ + Generates a Simulation source with a WhiteNoiseAdder + configured to produce a target signal to noise ratio. + :param target_snr: Desired signal to noise ratio of + the returned source. + :returns: Simulation source. + """ + + # Create a Simulation + sim = cls(*args, **kwargs) + + # Assert NoiseAdder has not been provided + if "noise_adder" in kwargs or sim.noise_adder is not None: + raise RuntimeError( + "Cannot provide 'noise_adder' when using {cls.__name__}.from_snr." + ) + + # Estimate the required noise variance + signal_var = sim.estimate_signal_var( + sample_n=sample_n, support_radius=support_radius + ) + noise_var = signal_var / target_snr + + # Assign the noise_adder + sim.noise_adder = WhiteNoiseAdder(var=noise_var) + logger.info(f"Appended {sim.noise_adder} to generation pipeline") + + return sim + + def estimate_signal_var(self, sample_n=None, support_radius=1): + """ + Estimate the signal variance of `sample_n` projections. + :param sample_n: Number of projections used for estimate. + :returns: Estimated signal variance. + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_signal_var` sample_n > Simulation.n: {sample_n} > Simulation.n." + " Accuracy may be impaired." + ) + + g2d = grid_2d(self.L, indexing="yx", normalized=False, dtype=self.dtype) + mask = g2d["r"] < self.L // 2 + + # TODO, batch this + # Note, for simulation we are assuming `sample_n` is random + # estimated_var = np.var(self.projections[:sample_n].asnumpy()[..., mask]) + estimated_var = np.var(self.clean_images[:sample_n].asnumpy()[..., mask]) + + logger.info(f"Estimated signal var {estimated_var}") + return estimated_var + + def estimate_snr(self, sample_n=None, support_radius=1): + """ + Estimate the SNR of the simulated data set using + estimated signal variance / noise variance. + :param sample_n: Number of projections used for estimate. + :returns: Estimated signal to noise ratio. + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_snr` sample_n > Simulation.n: {sample_n} > Simulation.n." + " Accuracy may be impaired." + ) + + # For clean images return infinite SNR. + # Note, relationship with CTF and other sim corrupotions still isn't clear to me... + if self.noise_adder is None: + return np.inf + + noise_var = self.noise_adder.noise_var + signal_var = self.estimate_signal_var( + sample_n=sample_n, support_radius=support_radius + ) + + return signal_var / noise_var diff --git a/tests/test_noise.py b/tests/test_noise.py index 82536aa1e4..c2dcd1fa92 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -169,3 +169,40 @@ def test_psnr(res, target_noise_variance, dtype): psnr = sim.estimate_psnr() logger.debug(f"PSNR target={target_noise_variance} L={sim.L} {sim.dtype} {psnr}") assert np.isclose(psnr, -10 * np.log10(target_noise_variance), rtol=0.01) + + +@pytest.mark.parametrize( + "target_noise_variance", VARS, ids=lambda param: f"var={param}" +) +def test_from_snr_white(sim_fixture, target_noise_variance): + """ + Test that prescribing noise directly by var and by `from_snr`, + are close for a variety of paramaters. + """ + + # First add an explicit amount of noise to the base simulation, + sim_fixture.noise_adder = WhiteNoiseAdder(var=target_noise_variance) + # and compute the resulting snr of the sim. + target_snr = sim_fixture.estimate_snr() + + # print("noise_var", sim_fixture.noise_adder.noise_var) + # print("signal_var", sim_fixture.estimate_signal_var()) + # print("snr", target_snr) + + # Attempt to create a new simulation at this `target_snr` + sim_from_snr = Simulation.from_snr( + target_snr, + # vols=AsymmetricVolume(L=sim_fixture.L, C=1, dtype=sim_fixture.dtype).generate(), + vols=sim_fixture.vols, # but, this only tests that garrett can do division + n=sim_fixture.n, + offsets=0, + amplitudes=1.0, + ) + # print("from_snr noise_var", sim_from_snr.noise_adder.noise_var) + # print("from_snr signal_var", sim_from_snr.estimate_signal_var()) + # print("from_snr snr", sim_from_snr.estimate_snr()) + + # Check we're within 10% + assert np.isclose( + sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.1 + ) From 027a6de774417565642e13a01abef12ec7816289 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 17 Jan 2023 14:11:36 -0500 Subject: [PATCH 043/424] remove equator candidates from testing --- src/aspire/abinitio/commonline_cn.py | 2 +- tests/test_orient_symmetric.py | 78 ++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 12 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 5d2ed829b0..3b909e41c0 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -124,7 +124,7 @@ def _estimate_relative_viewing_directions(self): [ ind for (ind, Ri_tilde) in enumerate(Ris_tilde) - if abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) < 5 * np.pi / 180 + if abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) < 10 * np.pi / 180 ] ) scores_self_corrs[:, cii_equators_inds] = 0 diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 68b071d7b7..e17afe97c1 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -3,7 +3,7 @@ from numpy import pi, random from numpy.linalg import det, norm -from aspire.abinitio import CLSymmetryC3C4 +from aspire.abinitio import CLSymmetryC3C4, CLSymmetryCn from aspire.source import Simulation from aspire.utils import Rotation from aspire.utils.coor_trans import ( @@ -16,7 +16,7 @@ from aspire.volume import CnSymmetricVolume # A set of these parameters are marked expensive to reduce testing time. -param_list = [ +param_list_c3_c4 = [ (44, 3, np.float32), (45, 4, np.float64), pytest.param(44, 4, np.float32, marks=pytest.mark.expensive), @@ -27,6 +27,10 @@ pytest.param(45, 3, np.float64, marks=pytest.mark.expensive), ] +param_list_cn = [ + (44, 5, np.float32), +] + # Method to instantiate a Simulation source and orientation estimation object. def source_orientation_objs(L, n_img, order, dtype): @@ -48,26 +52,50 @@ def source_orientation_objs(L, n_img, order, dtype): seed=123, ) - orient_est = CLSymmetryC3C4( + if order in [3, 4]: + CLclass = CLSymmetryC3C4 + else: + CLclass = CLSymmetryCn + orient_est = CLclass( src, symmetry=f"C{order}", n_theta=360, max_shift=1 / L, # set to 1 pixel + seed=1, ) return src, orient_est -@pytest.mark.parametrize("L, order, dtype", param_list) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4 + param_list_cn) def testEstimateRotations(L, order, dtype): n_img = 24 + if order > 4: + n_img = 32 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Estimate rotations. cl_symm.estimate_rotations() rots_est = cl_symm.rotations - # g-synchronize ground truth rotations. + # Ground truth rotations. rots_gt = src.rotations + + # For order>4 we cannot expect estimates from equator images to be accurate. + # So we exclude those from testing. + if order > 4: + equator_inds = np.array( + [ + ind + for (ind, rot_gt) in enumerate(rots_gt) + if abs(np.arccos(rot_gt[2, 2]) - np.pi / 2) < 10 * np.pi / 180 + ] + ) + + # Exclude equator estimates and ground truths. + rots_est = np.delete(rots_est, equator_inds, axis=0) + rots_gt = np.delete(rots_gt, equator_inds, axis=0) + + # g-synchronize ground truth rotations. rots_gt_sync = cl_symm.g_sync(rots_est, order, rots_gt) # Register estimates to ground truth rotations and compute MSE. @@ -79,7 +107,7 @@ def testEstimateRotations(L, order, dtype): assert mse_reg < 0.005 -@pytest.mark.parametrize("L, order, dtype", param_list) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) def testRelativeRotations(L, order, dtype): # Simulation source and common lines estimation instance # corresponding to volume with C3 or C4 symmetry. @@ -119,7 +147,7 @@ def testRelativeRotations(L, order, dtype): assert mean_angular_distance < 5 -@pytest.mark.parametrize("L, order, dtype", param_list) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) def testSelfRelativeRotations(L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. @@ -154,11 +182,13 @@ def testSelfRelativeRotations(L, order, dtype): assert mean_angular_distance < 5 -@pytest.mark.parametrize("L, order, dtype", param_list) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) def testRelativeViewingDirections(L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. n_img = 24 + if order > 4: + n_img = 32 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Calculate ground truth relative viewing directions, viis and vijs. @@ -178,7 +208,7 @@ def testRelativeViewingDirections(L, order, dtype): vijs_gt[idx] = np.outer(vi, vj) # Estimate relative viewing directions. - vijs, viis = cl_symm._estimate_relative_viewing_directions_c3_c4() + vijs, viis = cl_symm._estimate_relative_viewing_directions() # Since ground truth vijs and viis are rank 1 matrices they span a 1D subspace. # We use SVD to find this subspace for our estimates and the ground truth relative viewing directions. @@ -215,6 +245,30 @@ def testRelativeViewingDirections(L, order, dtype): (theta_vii, theta_vii_J, np.pi - theta_vii, np.pi - theta_vii_J), axis=0 ) + # For order>4 we cannot expect estimates from equator images to be accurate. + # So we exclude those from testing. + if order > 4: + equator_inds = np.array( + [ + ind + for (ind, rot_gt) in enumerate(rots_gt) + if abs(np.arccos(rot_gt[2, 2]) - np.pi / 2) < 10 * np.pi / 180 + ] + ) + + # Exclude ii estimates and ground truths. + min_theta_vii = np.delete(min_theta_vii, equator_inds, axis=0) + sii = np.delete(sii, equator_inds, axis=0) + + # Exclude ij estimates and ground truths. + pairwise_equator_inds = [] + for pair in pairs: + for ind in equator_inds: + if ind in pair: + pairwise_equator_inds.append(pairs.index(pair)) + min_theta_vij = np.delete(min_theta_vij, pairwise_equator_inds, axis=0) + sij = np.delete(sij, pairwise_equator_inds, axis=0) + # Calculate the mean minimum angular distance. angular_dist_vijs = np.mean(min_theta_vij) angular_dist_viis = np.mean(min_theta_vii) @@ -230,11 +284,13 @@ def testRelativeViewingDirections(L, order, dtype): # Check that the mean angular difference is within 2 degrees. angle_tol = 2 * np.pi / 180 + if order > 4: + angle_tol = 6 * np.pi / 180 assert angular_dist_vijs < angle_tol assert angular_dist_viis < angle_tol -@pytest.mark.parametrize("L, order, dtype", param_list) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) def testSelfCommonLines(L, order, dtype): n_img = 24 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) @@ -277,7 +333,7 @@ def testSelfCommonLines(L, order, dtype): assert np.allclose(detection_rate, 1.0) -@pytest.mark.parametrize("L, order, dtype", param_list) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) def testCommonLines(L, order, dtype): n_img = 24 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) From 824d7d0be36010fbdf29f97e9b3946390ae4196b Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 17 Jan 2023 14:37:24 -0500 Subject: [PATCH 044/424] rotations tolerance for order>4 --- src/aspire/abinitio/commonline_cn.py | 10 ++-------- tests/test_orient_symmetric.py | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 3b909e41c0..ec54de31ad 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -105,14 +105,8 @@ def _estimate_relative_viewing_directions(self): # Compute correlation of pf_i with itself over all shifts. corrs = pf_i_shifted @ np.conj(pf_full_i).T corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) - corrs_cands = np.array( - [ - np.max( - np.real(corrs[:, scls_inds_cand[:, 0], scls_inds_cand[:, 1]]), - axis=0, - ) - for scls_inds_cand in scls_inds - ] + corrs_cands = np.max( + np.real(corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 ) scores_self_corrs[i] = np.mean(np.real(corrs_cands), axis=1) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index e17afe97c1..f8a18f6b08 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -104,7 +104,10 @@ def testEstimateRotations(L, order, dtype): mse_reg = get_rots_mse(regrot, rots_gt_sync) # Assert mse is small. - assert mse_reg < 0.005 + if order > 4: + assert mse_reg < 0.015 + else: + assert mse_reg < 0.005 @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) From a028dd85097921ea15686ea28b4e1aa213fd3f8a Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 17 Jan 2023 15:07:52 -0500 Subject: [PATCH 045/424] lift fourier ray normalize from loop. tqdm progress bar. --- src/aspire/abinitio/commonline_cn.py | 175 ++++++++++++++------------- 1 file changed, 88 insertions(+), 87 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index ec54de31ad..04ad5f0263 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -79,6 +79,7 @@ def _estimate_relative_viewing_directions(self): # Transpose and reconstruct full polar Fourier for use in correlation. pf = pf.T + pf /= norm(pf, axis=2)[..., np.newaxis] # Normalize each ray. pf_full = np.concatenate((pf, np.conj(pf)), axis=1) # Step 1: pre-calculate the likelihood with respect to the self-common-lines. @@ -98,10 +99,6 @@ def _estimate_relative_viewing_directions(self): pf_full_i[:, 0] = 0 pf_i_shifted[:, 0] = 0 - # Normalize each ray. - pf_full_i /= norm(pf_full_i, axis=1)[..., np.newaxis] - pf_i_shifted /= norm(pf_i_shifted, axis=1)[..., np.newaxis] - # Compute correlation of pf_i with itself over all shifts. corrs = pf_i_shifted @ np.conj(pf_full_i).T corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) @@ -133,94 +130,98 @@ def _estimate_relative_viewing_directions(self): c = 0 e1 = [1, 0, 0] min_ii_norm = float("inf") * np.ones(n_img) - for i in trange(n_img): - pf_i = pf[i] - - # Generate shifted versions of the images. - pf_i_shifted = np.array( - [pf_i * shift_phase for shift_phase in all_shift_phases] - ) - pf_i_shifted = np.reshape(pf_i_shifted, (n_shifts * n_theta // 2, r_max)) - - # Ignore dc-component. - pf_i_shifted[:, 0] = 0 - - # Normalize each ray. - pf_i_shifted /= norm(pf_i_shifted, axis=1)[..., np.newaxis] - - for j in range(i + 1, n_img): - pf_full_j = pf_full[j] - - # Ignore dc-component. - pf_full_j[:, 0] = 0 - # Normalize each ray. - pf_full_j /= norm(pf_full_j, axis=1)[..., np.newaxis] + with tqdm(total=n_vijs) as pbar: + for i in range(n_img): + pf_i = pf[i] - # Compute correlation. - corrs_ij = pf_i_shifted @ np.conj(pf_full_j).T - - # Max out over shifts. - corrs_ij = np.max( - np.reshape(np.real(corrs_ij), (n_shifts, n_theta // 2, n_theta)), - axis=0, - ) - - # Arrange correlation based on common lines induced by candidate rotations. - corrs = corrs_ij[cijs_inds[..., 0], cijs_inds[..., 1]] - corrs = np.reshape(corrs, (-1, self.order, n_theta_ijs // self.order)) - # Take the mean over all symmetric common lines. - corrs = np.mean(corrs, axis=1) - corrs = np.reshape( - corrs, - ( - self.n_points_sphere, - self.n_points_sphere, - n_theta_ijs // self.order, - ), - ) - - # Self common-lines are invariant to n_theta_ijs (i.e., in-plane rotation angles) so max them out. - opt_theta_ij_ind_per_sphere_points = np.argmax(corrs, axis=-1) - corrs = np.max(corrs, axis=-1) - - # Maximum likelihood while taking into consideration both cls and scls. - corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j]) - - # Extract the optimal candidates. - opt_sphere_i, opt_sphere_j = np.unravel_index( - np.argmax(corrs), corrs.shape + # Generate shifted versions of the images. + pf_i_shifted = np.array( + [pf_i * shift_phase for shift_phase in all_shift_phases] ) - opt_theta_ij = opt_theta_ij_ind_per_sphere_points[ - opt_sphere_i, opt_sphere_j - ] - - opt_Ri_tilde = Ris_tilde[opt_sphere_i] - opt_Rj_tilde = Ris_tilde[opt_sphere_j] - opt_R_theta_ij = R_theta_ijs[opt_theta_ij] - - # Compute the estimate of vi*vi.T as given by j. - vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) - - svals = np.linalg.svd(vii_j, compute_uv=False) - if np.linalg.norm(svals - e1, 2) < min_ii_norm[i]: - viis[i] = vii_j - min_ii_norm[i] = np.linalg.norm(svals - e1, 2) - - # Compute the estimate of vj*vj.T as given by i. - vjj_i = np.mean(opt_Rj_tilde.T @ rots_symm @ opt_Rj_tilde, axis=0) - - svals = np.linalg.svd(vjj_i, compute_uv=False) - if np.linalg.norm(svals - e1, 2) < min_ii_norm[j]: - viis[j] = vjj_i - min_ii_norm[j] = np.linalg.norm(svals - e1, 2) - - # Compute the estimate of vi*vj.T. - vijs[c] = np.mean( - opt_Ri_tilde.T @ rots_symm @ opt_R_theta_ij @ opt_Rj_tilde, axis=0 + pf_i_shifted = np.reshape( + pf_i_shifted, (n_shifts * n_theta // 2, r_max) ) - c += 1 + # Ignore dc-component. + pf_i_shifted[:, 0] = 0 + + for j in range(i + 1, n_img): + pf_full_j = pf_full[j] + + # Ignore dc-component. + pf_full_j[:, 0] = 0 + + # Compute correlation. + corrs_ij = pf_i_shifted @ np.conj(pf_full_j).T + + # Max out over shifts. + corrs_ij = np.max( + np.reshape( + np.real(corrs_ij), (n_shifts, n_theta // 2, n_theta) + ), + axis=0, + ) + + # Arrange correlation based on common lines induced by candidate rotations. + corrs = corrs_ij[cijs_inds[..., 0], cijs_inds[..., 1]] + corrs = np.reshape( + corrs, (-1, self.order, n_theta_ijs // self.order) + ) + # Take the mean over all symmetric common lines. + corrs = np.mean(corrs, axis=1) + corrs = np.reshape( + corrs, + ( + self.n_points_sphere, + self.n_points_sphere, + n_theta_ijs // self.order, + ), + ) + + # Self common-lines are invariant to n_theta_ijs (i.e., in-plane rotation angles) so max them out. + opt_theta_ij_ind_per_sphere_points = np.argmax(corrs, axis=-1) + corrs = np.max(corrs, axis=-1) + + # Maximum likelihood while taking into consideration both cls and scls. + corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j]) + + # Extract the optimal candidates. + opt_sphere_i, opt_sphere_j = np.unravel_index( + np.argmax(corrs), corrs.shape + ) + opt_theta_ij = opt_theta_ij_ind_per_sphere_points[ + opt_sphere_i, opt_sphere_j + ] + + opt_Ri_tilde = Ris_tilde[opt_sphere_i] + opt_Rj_tilde = Ris_tilde[opt_sphere_j] + opt_R_theta_ij = R_theta_ijs[opt_theta_ij] + + # Compute the estimate of vi*vi.T as given by j. + vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) + + svals = np.linalg.svd(vii_j, compute_uv=False) + if np.linalg.norm(svals - e1, 2) < min_ii_norm[i]: + viis[i] = vii_j + min_ii_norm[i] = np.linalg.norm(svals - e1, 2) + + # Compute the estimate of vj*vj.T as given by i. + vjj_i = np.mean(opt_Rj_tilde.T @ rots_symm @ opt_Rj_tilde, axis=0) + + svals = np.linalg.svd(vjj_i, compute_uv=False) + if np.linalg.norm(svals - e1, 2) < min_ii_norm[j]: + viis[j] = vjj_i + min_ii_norm[j] = np.linalg.norm(svals - e1, 2) + + # Compute the estimate of vi*vj.T. + vijs[c] = np.mean( + opt_Ri_tilde.T @ rots_symm @ opt_R_theta_ij @ opt_Rj_tilde, + axis=0, + ) + + c += 1 + pbar.update() return vijs, viis From 638853edb80cc86addb6e603a0a8e753fd1e9cd1 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 17 Jan 2023 15:50:16 -0500 Subject: [PATCH 046/424] mark test expensive --- tests/test_orient_symmetric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index f8a18f6b08..6d280ca95d 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -28,7 +28,7 @@ ] param_list_cn = [ - (44, 5, np.float32), + pytest.param(44, 5, np.float32, marks=pytest.mark.expensive), ] From 330e4f800b66337251964907a5e7ae27526f8064 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 18 Jan 2023 15:10:09 -0500 Subject: [PATCH 047/424] logger message --- src/aspire/abinitio/commonline_cn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 04ad5f0263..371dc614e7 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -266,7 +266,7 @@ def compute_cls_inds(self, Ris_tilde, R_theta_ijs): cij_inds = np.zeros( (n_points_sphere, n_points_sphere, n_theta_ijs, 2), dtype=np.uint16 ) - + logger.info("Computing common-line indices induced by candidate rotations.") with tqdm(total=n_points_sphere) as pbar: for i in range(n_points_sphere): for j in range(n_points_sphere): From 9fed6d1aa103ef1d1b0b3ef7ddac9d368599c5ee Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 19 Jan 2023 11:37:05 -0500 Subject: [PATCH 048/424] fix order bug --- src/aspire/abinitio/commonline_cn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 371dc614e7..cfd1b3708b 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -44,7 +44,7 @@ def __init__( def _check_symmetry(self, symmetry): if symmetry is None: raise NotImplementedError( - "Symmetry type not supplied. Please indicate C3 or C4 symmetry." + "Symmetry type not supplied. Please indicate symmetry." ) else: symmetry = symmetry.upper() @@ -52,7 +52,7 @@ def _check_symmetry(self, symmetry): raise NotImplementedError( f"Only Cn symmetry supported. {symmetry} was supplied." ) - self.order = int(symmetry[1]) + self.order = int(symmetry[1:]) def _estimate_relative_viewing_directions(self): n_img = self.n_img From 25b9fc215c101a2adff33da9cd166c54f2c8b95e Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 19 Jan 2023 11:47:16 -0500 Subject: [PATCH 049/424] docstring for compute_cls_inds() --- src/aspire/abinitio/commonline_cn.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index cfd1b3708b..fcd4804891 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -260,6 +260,14 @@ def compute_scls_inds(self, Ri_cands): # TODO: cache def compute_cls_inds(self, Ris_tilde, R_theta_ijs): + """ + Compute the common-lines indices induced by the candidate rotations. + + :param Ris_tilde: An array of size n_candsx3x3 of candidate rotations. + :param R_theta_ijs: An array of size n_theta_ijsx3x3 of inplane rotations. + :return: An array of size n_cands x n_cands x n_theta_ijs x 2 holding common-lines + indices induced by the supplied rotations. + """ n_theta = self.n_theta n_points_sphere = self.n_points_sphere n_theta_ijs = R_theta_ijs.shape[0] From f3630dd6f827b863be869aa899473b689ee491ef Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 19 Jan 2023 14:31:22 -0500 Subject: [PATCH 050/424] move check that n_theta is even above polar fft. --- src/aspire/abinitio/commonline_base.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/aspire/abinitio/commonline_base.py b/src/aspire/abinitio/commonline_base.py index 9ef9119dd6..361d156af7 100644 --- a/src/aspire/abinitio/commonline_base.py +++ b/src/aspire/abinitio/commonline_base.py @@ -26,7 +26,7 @@ def __init__( :param src: The source object of 2D denoised or class-averaged imag :param n_rad: The number of points in the radial direction. If None, n_rad will default to the ceiling of half the resolution of the source. - :param n_theta: The number of points in the theta direction. + :param n_theta: The number of points in the theta direction. This value must be even. Default is 360. :param n_check: For each image/projection find its common-lines with n_check images. If n_check is less than the total number of images, @@ -59,6 +59,10 @@ def _build(self): self.n_rad = math.ceil(0.5 * self.n_res) if self.n_check is None: self.n_check = self.n_img + if self.n_theta % 2 == 1: + msg = "n_theta must be even" + logger.error(msg) + raise NotImplementedError(msg) imgs = self.src.images[:] @@ -69,11 +73,6 @@ def _build(self): self.pf = self.basis.evaluate_t(imgs) self.pf = self.pf.reshape(self.n_img, self.n_theta, self.n_rad).T # RCOPT - if self.n_theta % 2 == 1: - msg = "n_theta must be even" - logger.error(msg) - raise NotImplementedError(msg) - n_theta_half = self.n_theta // 2 # The first two dimension of pf is of size n_rad x n_theta. We will convert pf From 064f66e7e169f01573078756a3db5debf99101e9 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 23 Jan 2023 09:32:25 -0500 Subject: [PATCH 051/424] Change polar Fourier to row major. Make necessary downstream changes. --- src/aspire/abinitio/commonline_base.py | 37 ++++++++++++------------- src/aspire/abinitio/commonline_c3_c4.py | 16 ++++------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/aspire/abinitio/commonline_base.py b/src/aspire/abinitio/commonline_base.py index 9ef9119dd6..ad024e3e89 100644 --- a/src/aspire/abinitio/commonline_base.py +++ b/src/aspire/abinitio/commonline_base.py @@ -67,7 +67,7 @@ def _build(self): (self.n_res, self.n_res), self.n_rad, self.n_theta, dtype=self.dtype ) self.pf = self.basis.evaluate_t(imgs) - self.pf = self.pf.reshape(self.n_img, self.n_theta, self.n_rad).T # RCOPT + self.pf = self.pf.reshape(self.n_img, self.n_theta, self.n_rad) if self.n_theta % 2 == 1: msg = "n_theta must be even" @@ -76,18 +76,18 @@ def _build(self): n_theta_half = self.n_theta // 2 - # The first two dimension of pf is of size n_rad x n_theta. We will convert pf - # into an array of size (n_rad-1) x n_theta/2, that is, take half of ray + # The last two dimension of pf is of size n_theta x n_rad. We will convert pf + # into an array of size (n_theta/2) x (n_rad-1), that is, take half of each ray # through the origin except the DC part, and also take the angles only up to PI. # This is due to the fact that the original images are real, and thus each ray # is conjugate symmetric. We therefore gain nothing by taking longer correlations - # (of length 2*n_rad-1 instead of n_rad-1). In the Matlab version, pf is convert to - # the size of (2*n_rad-1) x n_theta/2 but most of calculations of build_clmatrix - # and estimate_shifts below only use the size of (n_rad-1) x n_theta/2. In the - # Python version we will use the size of (n_rad-1) x n_theta/2 directly and make - # sure every part is using it. By taking shorter correlation we can speed the + # (of length 2*n_rad-1 instead of n_rad-1). In the Matlab version, pf is converted to + # the size of (n_theta/2) x (2*n_rad-1) but most of the calculations of build_clmatrix + # and estimate_shifts below only use the size of (n_theta/2) x (n_rad-1). In the + # Python version we will use the size of (n_theta/2) x (n_rad-1) directly and make + # sure every part is using it. By taking shorter correlations we can speed the # computation by a factor of two. - self.pf = np.flip(self.pf[1:, n_theta_half:], 0) + self.pf = np.flip(self.pf[:, n_theta_half:, 1:], 2) def estimate_rotations(self): """ @@ -148,7 +148,7 @@ def build_clmatrix(self): shifts_1d = np.zeros((n_img, n_img)) # Prepare the shift phases to try and generate filter for common-line detection - r_max = pf.shape[0] + r_max = pf.shape[2] shifts, shift_phases, h = self._generate_shift_phase_and_filter( r_max, max_shift, shift_step ) @@ -156,10 +156,7 @@ def build_clmatrix(self): # Apply bandpass filter, normalize each ray of each image # Note that only use half of each ray - pf = self._apply_filter_and_norm("ijk, i -> ijk", pf, r_max, h) - - # change dimensions of axes to (n_img, n_rad/2, n_theta/2) - pf = pf.transpose((2, 1, 0)) + pf = self._apply_filter_and_norm("ijk, k -> ijk", pf, r_max, h) # Search for common lines between [i, j] pairs of images. # Creating pf and building common lines are different to the Matlab version. @@ -306,7 +303,7 @@ def _get_shift_equations_approx(self, equations_factor=1, max_memory=4000): # is also applied to the radial direction for easier detection. max_shift = self.max_shift shift_step = self.shift_step - r_max = pf.shape[0] + r_max = pf.shape[2] _, shift_phases, h = self._generate_shift_phase_and_filter( r_max, max_shift, shift_step ) @@ -326,7 +323,7 @@ def _get_shift_equations_approx(self, equations_factor=1, max_memory=4000): c_ij, c_ji = self._get_cl_indices(rotations, i, j, n_theta_half) # Extract the Fourier rays that correspond to the common line - pf_i = pf[:, c_ij, i] + pf_i = pf[i, c_ij] # Check whether need to flip or not Fourier ray of j image # Is the common line in image j in the positive @@ -334,9 +331,9 @@ def _get_shift_equations_approx(self, equations_factor=1, max_memory=4000): # negative direction (is_pf_j_flipped=True). is_pf_j_flipped = c_ji >= n_theta_half if not is_pf_j_flipped: - pf_j = pf[:, c_ji, j] + pf_j = pf[j, c_ji] else: - pf_j = pf[:, c_ji - n_theta_half, j] + pf_j = pf[j, c_ji - n_theta_half] # perform bandpass filter, normalize each ray of each image, pf_i = self._apply_filter_and_norm("i, i -> i", pf_i, r_max, h) @@ -520,7 +517,7 @@ def _apply_filter_and_norm(self, subscripts, pf, r_max, h): # Note if we'd rather not have the dtype and casting args, # we can control h.dtype instead. np.einsum(subscripts, pf, h, out=pf, dtype=pf.dtype, casting="same_kind") - pf[r_max - 1 : r_max + 2] = 0 - pf /= np.linalg.norm(pf, axis=0) + pf[..., r_max - 1 : r_max + 2] = 0 + pf /= np.linalg.norm(pf, axis=-1)[..., np.newaxis] return pf diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 9cc4be6e16..03253d5799 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -279,7 +279,7 @@ def _estimate_inplane_rotations(self, vis): # Step 3: Compute the correlation over all shifts. # Generate shifts. - r_max = pf.shape[0] + r_max = pf.shape[-1] shifts, shift_phases, _ = self._generate_shift_phase_and_filter( r_max, max_shift_1d, shift_step ) @@ -291,10 +291,8 @@ def _estimate_inplane_rotations(self, vis): # 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) - # Transpose pf and reconstruct the full polar Fourier for use in correlation. - # self.pf only consists of rays in the range [180, 360) and is in column major order, - # ie. self.pf has shape (n_rad-1, n_theta//2, n_img). - pf = pf.T + # 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 = np.concatenate((pf, np.conj(pf)), axis=1) # Normalize rays. @@ -435,17 +433,15 @@ def _self_clmatrix_c3_c4(self): # Compute the correlation over all shifts. # Generate Shifts. - r_max = pf.shape[0] + 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) all_shift_phases = shift_phases.T - # Transpose pf and reconstruct the full polar Fourier for use in correlation. - # self.pf only consists of rays in the range [180, 360) and is in column major order, - # ie. self.pf has shape (n_rad-1, n_theta//2, n_img). - pf = pf.T + # 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_full = np.concatenate((pf, np.conj(pf)), axis=1) # The self-common-lines matrix holds two indices per image that represent From 0322985c6f052e05787ede1047ee77342d89b0a5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 23 Jan 2023 12:11:00 -0500 Subject: [PATCH 052/424] batched estimate signal, update `from_snr` unit test, some docstrings cleanup --- src/aspire/source/simulation.py | 80 ++++++++++++++++++++++++--------- tests/test_noise.py | 16 +++---- 2 files changed, 64 insertions(+), 32 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index a3ed674529..1d3144f8c5 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -491,9 +491,10 @@ def estimate_psnr(self, sample_n=None, batch_size=512): if sample_n > self.n: logger.warning( - f"`estimate_psnr` sample_n > Simulation.n: {sample_n} > Simulation.n." - " Accuracy may be impaired." + f"`estimate_psnr` sample_n > Simulation.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" ) + sample_n = self.n peaksq = np.empty(sample_n, dtype=self.dtype) mse = np.empty(sample_n, dtype=self.dtype) @@ -519,8 +520,9 @@ def from_snr( cls, target_snr, *args, - sample_n=100, - support_radius=1, + sample_n=None, + support_radius=None, + batch_size=512, **kwargs, ): """ @@ -528,6 +530,11 @@ def from_snr( configured to produce a target signal to noise ratio. :param target_snr: Desired signal to noise ratio of the returned source. + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. :returns: Simulation source. """ @@ -552,10 +559,14 @@ def from_snr( return sim - def estimate_signal_var(self, sample_n=None, support_radius=1): + def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512): """ Estimate the signal variance of `sample_n` projections. - :param sample_n: Number of projections used for estimate. + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. :returns: Estimated signal variance. """ @@ -564,26 +575,52 @@ def estimate_signal_var(self, sample_n=None, support_radius=1): if sample_n > self.n: logger.warning( - f"`estimate_signal_var` sample_n > Simulation.n: {sample_n} > Simulation.n." - " Accuracy may be impaired." + f"`estimate_signal_var` sample_n > Simulation.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" ) + sample_n = self.n - g2d = grid_2d(self.L, indexing="yx", normalized=False, dtype=self.dtype) - mask = g2d["r"] < self.L // 2 + if support_radius is None: + support_radius = self.L // 2 + elif not 0 < support_radius <= self.L * np.sqrt(2): + raise ValueError( + "`estimate_signal_var`'s support_radius should be" + f" `(0, L*sqrt(2)={self.L*np.sqrt(2)}]`," + f" passed {support_radius}." + ) - # TODO, batch this - # Note, for simulation we are assuming `sample_n` is random - # estimated_var = np.var(self.projections[:sample_n].asnumpy()[..., mask]) - estimated_var = np.var(self.clean_images[:sample_n].asnumpy()[..., mask]) + g2d = grid_2d(self.L, indexing="yx", normalized=False, dtype=self.dtype) + mask = g2d["r"] < support_radius + + # Var is estimated batch-wise, compare with numpy + # np_estimated_var = np.var(self.clean_images[:sample_n].asnumpy()[..., mask]) + # Note, for simulation we are implicitly assuming taking `sample_n` is random, + # but this does not need to be the case. We can add a `random_shuffle` param. + first_moment = 0.0 + second_moment = 0.0 + _denom = sample_n * np.sum(mask) + for i in trange(0, sample_n, batch_size): + # Gather this batch of images and mask off area outside support_radius + images_masked = self.clean_images[i : i + batch_size].asnumpy()[..., mask] + # Accumulate first and second moments + first_moment += np.sum(images_masked) / _denom + second_moment += np.sum(images_masked**2) / _denom + + # E[X**2] - E[X]**2 + estimated_var = second_moment - first_moment**2 + logger.info(f"Simulation estimated signal var: {estimated_var}") - logger.info(f"Estimated signal var {estimated_var}") return estimated_var - def estimate_snr(self, sample_n=None, support_radius=1): + def estimate_snr(self, sample_n=None, support_radius=None, batch_size=512): """ Estimate the SNR of the simulated data set using estimated signal variance / noise variance. - :param sample_n: Number of projections used for estimate. + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. :returns: Estimated signal to noise ratio. """ @@ -592,18 +629,19 @@ def estimate_snr(self, sample_n=None, support_radius=1): if sample_n > self.n: logger.warning( - f"`estimate_snr` sample_n > Simulation.n: {sample_n} > Simulation.n." - " Accuracy may be impaired." + f"`estimate_snr` sample_n > Simulation.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" ) + sample_n = self.n # For clean images return infinite SNR. - # Note, relationship with CTF and other sim corrupotions still isn't clear to me... + # Note, relationship with CTF and other sim corruptions still isn't clear to me... if self.noise_adder is None: return np.inf noise_var = self.noise_adder.noise_var signal_var = self.estimate_signal_var( - sample_n=sample_n, support_radius=support_radius + sample_n=sample_n, support_radius=support_radius, batch_size=batch_size ) return signal_var / noise_var diff --git a/tests/test_noise.py b/tests/test_noise.py index c2dcd1fa92..78194de600 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -185,24 +185,18 @@ def test_from_snr_white(sim_fixture, target_noise_variance): # and compute the resulting snr of the sim. target_snr = sim_fixture.estimate_snr() - # print("noise_var", sim_fixture.noise_adder.noise_var) - # print("signal_var", sim_fixture.estimate_signal_var()) - # print("snr", target_snr) - # Attempt to create a new simulation at this `target_snr` + # For unit testing, we will use `sim_fixture`'s volume, + # but the new Simulation instance should yield different projects. sim_from_snr = Simulation.from_snr( target_snr, - # vols=AsymmetricVolume(L=sim_fixture.L, C=1, dtype=sim_fixture.dtype).generate(), - vols=sim_fixture.vols, # but, this only tests that garrett can do division + vols=sim_fixture.vols, # Force the previously generated volume. n=sim_fixture.n, offsets=0, amplitudes=1.0, ) - # print("from_snr noise_var", sim_from_snr.noise_adder.noise_var) - # print("from_snr signal_var", sim_from_snr.estimate_signal_var()) - # print("from_snr snr", sim_from_snr.estimate_snr()) - # Check we're within 10% + # Check we're within 1% assert np.isclose( - sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.1 + sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 ) From c7253e9360bad4e7dd469b31a08888bf78181dbc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 23 Jan 2023 13:15:55 -0500 Subject: [PATCH 053/424] migrate linux and osx tests towards GitHub Actions --- .github/workflows/workflow.yml | 50 +++++++-- azure-pipelines.yml | 121 --------------------- environment.yml => environment-default.yml | 0 tox.ini | 6 +- 4 files changed, 43 insertions(+), 134 deletions(-) rename environment.yml => environment-default.yml (100%) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 48301b5cc8..9fe71c60a3 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -15,7 +15,7 @@ jobs: python-version: '3.7' - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip # check still needed pip install tox tox-gh-actions - name: Run Tox Check run: tox -e check @@ -26,15 +26,15 @@ jobs: strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] - pyenv: [stable, dev] + pyenv: ['pip'] exclude: - # Exclude 3.7-stable so we can add pre and post tasks to that environment. + # Exclude 3.7-pip so we can add pre/post tasks to that environment. - python-version: '3.7' - pyenv: stable + pyenv: pip include: - # Re-include 3.7-stable with additional tox tasks. + # Re-include 3.7 with additional tox tasks. - python-version: '3.7' - pyenv: stable,docs + pyenv: pip,docs steps: @@ -45,13 +45,45 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip # check still needed pip install tox tox-gh-actions - name: Test with tox run: tox --skip-missing-interpreters false -e py${{ matrix.python-version }}-${{ matrix.pyenv }} - name: Upload Coverage to CodeCov uses: codecov/codecov-action@v3 + conda-build: + needs: check + runs-on: ${{ matrix.os }} + strategy: + matrix: + #os: [ubuntu-latest, windows-latest, macOS-latest] + #backend: [default, intel, openblas] + os: [ubuntu-latest, macOS-latest] + backend: [default] + python-version: ['3.8'] + + steps: + - uses: actions/checkout@v3 + - name: Set up Conda ${{ matrix.os }} Python ${{ matrix.python-version }} + uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + activate-environment: aspire + # TODO update yaml docs to -default` + environment-file: environment-${{ matrix.backend }}.yml + auto-activate-base: false + - name: Complete Install and Log Environment + run: | + conda info + conda list + pip install -e ".[dev]" + pip freeze + python -c "import numpy; numpy.show_config()" + - name: Execute Tests + run: | + pytest --durations=50 -s docs: if: github.ref == 'refs/heads/master' @@ -64,7 +96,7 @@ jobs: python-version: '3.7' - name: Install Dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip # check still needed pip install -e ".[dev]" - name: Run Sphinx doc build script env: @@ -81,7 +113,7 @@ jobs: - uses: actions/checkout@v3 - name: Install dependencies run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip # check still needed pip install -e ".[dev,gpu_11x]" - name: Customize config run: | diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cd1ffd3111..4f80f9e9b4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -8,127 +8,6 @@ schedules: always: true # Always run the Daily Build jobs: -- job: - displayName: ubuntu-22.04 - pool: - vmImage: 'ubuntu-22.04' - strategy: - maxParallel: 4 - matrix: - Python38: - python.version: '3.8' - - steps: - - bash: echo "##vso[task.prependpath]$CONDA/bin" - displayName: Add conda to PATH - - - bash: conda env create --name myEnvironment --file environment.yml - displayName: Create Anaconda environment - - - bash: | - source activate myEnvironment - pip install -e .[dev] - pip freeze --all - export MKL_THREADING_LAYER=GNU - export OMP_NUM_THREADS=2 - python -c "import numpy; numpy.show_config()" - pytest --durations=50 --random-order - displayName: pytest - -- job: - displayName: ubuntu-20.04-open - pool: - vmImage: 'ubuntu-20.04' - strategy: - maxParallel: 4 - matrix: - Python38: - python.version: '3.8' - timeoutInMinutes: 120 # how long to run the job before automatically cancelling - - steps: - - bash: echo "##vso[task.prependpath]$CONDA/bin" - displayName: Add conda to PATH - - - bash: | - conda env create --name open_env --file environment-openblas.yml - source activate open_env - pip install -e .[dev] - pip freeze --all - python -c "import numpy; numpy.show_config()" - displayName: Create Anaconda environment - - - bash: | - source activate open_env - export MKL_THREADING_LAYER=GNU - export OMP_NUM_THREADS=2 - pytest --durations=50 --random-order - displayName: pytest - -- job: - displayName: ubuntu-20.04-intel - pool: - vmImage: 'ubuntu-20.04' - strategy: - maxParallel: 4 - matrix: - Python38: - python.version: '3.8' - timeoutInMinutes: 120 # how long to run the job before automatically cancelling - - steps: - - bash: echo "##vso[task.prependpath]$CONDA/bin" - displayName: Add conda to PATH - - - bash: | - conda env create --name intel_env --file environment-intel.yml - source activate intel_env - pip install -e .[dev] - pip freeze --all - python -c "import numpy; numpy.show_config()" - displayName: Create Anaconda environment - - - bash: | - source activate intel_env - export MKL_THREADING_LAYER=GNU - export OMP_NUM_THREADS=2 - pytest --durations=50 --random-order - displayName: pytest - - -- job: - displayName: macOS-latest - pool: - vmImage: 'macOS-latest' - strategy: - maxParallel: 4 - matrix: - Python38: - python.version: '3.8' - - steps: - - bash: | - echo "##vso[task.prependpath]$CONDA/bin" - sudo chown -R $USER $CONDA - displayName: Add conda to PATH - - - bash: conda update -q -y conda - displayName: Update conda - - - bash: conda clean -i -t -y - displayName: Removing conda cached package tarballs. - - - bash: conda env create --name myEnvironment --file environment.yml - displayName: Create Anaconda environment - - - bash: | - source activate myEnvironment - pip install -e .[dev] - pip freeze --all - python -c "import numpy; numpy.show_config()" - pytest --durations=50 -s - displayName: pytest - - job: displayName: macOS-11 pool: diff --git a/environment.yml b/environment-default.yml similarity index 100% rename from environment.yml rename to environment-default.yml diff --git a/tox.ini b/tox.ini index 4676a9c5d3..9b7b67a5a9 100644 --- a/tox.ini +++ b/tox.ini @@ -4,12 +4,11 @@ envlist = clean check docs - py{3.7,3.8,3.9,3.10}-{stable,dev} + py{3.7,3.8,3.9,3.10} minversion = 3.8.0 -[testenv] +[testenv:pip] changedir = tests -allowlist_externals = sh deps = parameterized pytest @@ -17,7 +16,6 @@ deps = Cython>=0.23 commands = python -V - py{3.7,3.8,3.9,3.10}-dev: sh -c "pip freeze | cut -d@ -f1 | cut -d= -f1 | xargs -n1 pip install -U" pip freeze --all python -c "import numpy; numpy.show_config()" # --cov should generate `Coverage` data From 5457c7e2aea0c490826f7330dcffa23b8e668e81 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 23 Jan 2023 13:47:02 -0500 Subject: [PATCH 054/424] untabify --- .github/workflows/workflow.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9fe71c60a3..81c71b2cd0 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -26,11 +26,11 @@ jobs: strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] - pyenv: ['pip'] + pyenv: pip exclude: # Exclude 3.7-pip so we can add pre/post tasks to that environment. - python-version: '3.7' - pyenv: pip + pyenv: pip include: # Re-include 3.7 with additional tox tasks. - python-version: '3.7' @@ -57,10 +57,10 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - #os: [ubuntu-latest, windows-latest, macOS-latest] - #backend: [default, intel, openblas] + #os: [ubuntu-latest, windows-latest, macOS-latest] + #backend: [default, intel, openblas] os: [ubuntu-latest, macOS-latest] - backend: [default] + backend: default python-version: ['3.8'] steps: @@ -71,15 +71,15 @@ jobs: auto-update-conda: true python-version: ${{ matrix.python-version }} activate-environment: aspire - # TODO update yaml docs to -default` + # TODO update yaml docs to -default` environment-file: environment-${{ matrix.backend }}.yml auto-activate-base: false - name: Complete Install and Log Environment run: | conda info conda list - pip install -e ".[dev]" - pip freeze + pip install -e ".[dev]" + pip freeze python -c "import numpy; numpy.show_config()" - name: Execute Tests run: | From 9f7920bccd38bc8e8150ca05d43c6e41415a9570 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 23 Jan 2023 13:50:52 -0500 Subject: [PATCH 055/424] GHA lint --- .github/workflows/workflow.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 81c71b2cd0..55c75bf27f 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] - pyenv: pip + pyenv: [pip] exclude: # Exclude 3.7-pip so we can add pre/post tasks to that environment. - python-version: '3.7' @@ -60,20 +60,20 @@ jobs: #os: [ubuntu-latest, windows-latest, macOS-latest] #backend: [default, intel, openblas] os: [ubuntu-latest, macOS-latest] - backend: default + backend: [default] python-version: ['3.8'] steps: - uses: actions/checkout@v3 - name: Set up Conda ${{ matrix.os }} Python ${{ matrix.python-version }} uses: conda-incubator/setup-miniconda@v2 - with: - auto-update-conda: true - python-version: ${{ matrix.python-version }} - activate-environment: aspire - # TODO update yaml docs to -default` - environment-file: environment-${{ matrix.backend }}.yml - auto-activate-base: false + with: + auto-update-conda: true + python-version: ${{ matrix.python-version }} + activate-environment: aspire + # TODO update yaml docs to -default` + environment-file: environment-${{ matrix.backend }}.yml + auto-activate-base: false - name: Complete Install and Log Environment run: | conda info From 2c25e2087aeb4c81e67e0c253046f7776ddd4d08 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 23 Jan 2023 15:45:56 -0500 Subject: [PATCH 056/424] Default to bash for minicondabater --- .github/workflows/workflow.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 55c75bf27f..68b38bd73f 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -55,6 +55,9 @@ jobs: conda-build: needs: check runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash -el {0} strategy: matrix: #os: [ubuntu-latest, windows-latest, macOS-latest] @@ -74,14 +77,14 @@ jobs: # TODO update yaml docs to -default` environment-file: environment-${{ matrix.backend }}.yml auto-activate-base: false - - name: Complete Install and Log Environment + - name: Complete Install and Log Environment ${{ matrix.os }} Python ${{ matrix.python-version }} run: | conda info conda list pip install -e ".[dev]" pip freeze python -c "import numpy; numpy.show_config()" - - name: Execute Tests + - name: Execute Pytest Conda ${{ matrix.os }} Python ${{ matrix.python-version }} run: | pytest --durations=50 -s From a38bdf6942e27986756bc2c8a974e2bfe16058ce Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 24 Jan 2023 09:01:33 -0500 Subject: [PATCH 057/424] Add from_snr tests that cover iso pinkish noise and aniso noise --- tests/test_noise.py | 136 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/tests/test_noise.py b/tests/test_noise.py index 78194de600..ef9f392202 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -6,7 +6,12 @@ import pytest from aspire.image import Image -from aspire.noise import CustomNoiseAdder, WhiteNoiseAdder, WhiteNoiseEstimator +from aspire.noise import ( + AnisotropicNoiseEstimator, + CustomNoiseAdder, + WhiteNoiseAdder, + WhiteNoiseEstimator, +) from aspire.operators import FunctionFilter, ScalarFilter from aspire.source.simulation import Simulation from aspire.utils import grid_3d @@ -196,7 +201,136 @@ def test_from_snr_white(sim_fixture, target_noise_variance): amplitudes=1.0, ) + # Check we're within 1% of explicit target + logger.info( + "sim_from_snr.noise_adder.noise_var, target_noise_variance =" + f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" + ) + assert np.isclose( + sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 + ) + + # Compare with WhiteNoiseEstimator consuming sim_from_snr + noise_estimator = WhiteNoiseEstimator(sim_from_snr, batchSize=512) + est_noise_variance = noise_estimator.estimate() + logger.info( + "est_noise_variance, target_noise_variance =" + f" {est_noise_variance}, {target_noise_variance}" + ) + + # Check we're within 1% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.01) + + +@pytest.mark.parametrize( + "target_noise_variance", VARS, ids=lambda param: f"var={param}" +) +def test_from_snr_pink_iso(sim_fixture, target_noise_variance): + """ + Test that prescribing noise directly by var and by `from_snr`, + are close for a variety of paramaters. + """ + + # Create the custom noise function and associated Filter + def pinkish_spectrum(x, y): + s = x[-1] - x[-2] + f = 2 * s / (np.hypot(x, y) + s) + m = np.mean(f) + return f * target_noise_variance / m + + custom_filter = FunctionFilter(f=pinkish_spectrum) + + # Create the CustomNoiseAdder + sim_fixture.noise_adder = CustomNoiseAdder(noise_filter=custom_filter) + + # and compute the resulting snr of the sim. + target_snr = sim_fixture.estimate_snr() + + # Attempt to create a new simulation at this `target_snr` + # For unit testing, we will use `sim_fixture`'s volume, + # but the new Simulation instance should yield different projects. + sim_from_snr = Simulation.from_snr( + target_snr, + vols=sim_fixture.vols, # Force the previously generated volume. + n=sim_fixture.n, + offsets=0, + amplitudes=1.0, + ) + + # Check we're within 1% of explicit target + logger.info( + "sim_from_snr.noise_adder.noise_var, target_noise_variance =" + f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" + ) + assert np.isclose( + sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 + ) + + # TODO, potentially remove or change to Isotropic after #842 + # Compare with AnisotropicNoiseEstimator consuming sim_from_snr + noise_estimator = AnisotropicNoiseEstimator(sim_from_snr, batchSize=512) + est_noise_variance = noise_estimator.estimate() + logger.info( + "est_noise_variance, target_noise_variance =" + f" {est_noise_variance}, {target_noise_variance}" + ) + # Check we're within 1% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.01) + + +@pytest.mark.parametrize( + "target_noise_variance", VARS, ids=lambda param: f"var={param}" +) +def test_from_snr_aniso(sim_fixture, target_noise_variance): + """ + Test that prescribing noise directly by var and by `from_snr`, + are close for a variety of paramaters. + """ + + # Create the custom noise function and associated Filter + def aniso_spectrum(x, y): + s = x[-1] - x[-2] + f = 4 * s / (np.hypot(x, 2 * y) + s) + m = np.mean(f) + return f * target_noise_variance / m + + custom_filter = FunctionFilter(f=aniso_spectrum) + + # Create the CustomNoiseAdder + sim_fixture.noise_adder = CustomNoiseAdder(noise_filter=custom_filter) + + # and compute the resulting snr of the sim. + target_snr = sim_fixture.estimate_snr() + + # Attempt to create a new simulation at this `target_snr` + # For unit testing, we will use `sim_fixture`'s volume, + # but the new Simulation instance should yield different projects. + sim_from_snr = Simulation.from_snr( + target_snr, + vols=sim_fixture.vols, # Force the previously generated volume. + n=sim_fixture.n, + offsets=0, + amplitudes=1.0, + ) + + # Check we're within 1% of explicit target + logger.info( + "sim_from_snr.noise_adder.noise_var, target_noise_variance =" + f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" + ) assert np.isclose( sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 ) + + # TODO, potentially remove after #842 + # Compare with AnisotropicNoiseEstimator consuming sim_from_snr + noise_estimator = AnisotropicNoiseEstimator(sim_from_snr, batchSize=512) + est_noise_variance = noise_estimator.estimate() + logger.info( + "est_noise_variance, target_noise_variance =" + f" {est_noise_variance}, {target_noise_variance}" + ) + + # Check we're within 1% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.01) From ebd992cc69a7737bb3001ce58a1d90e421e32e2d Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 24 Jan 2023 12:27:26 -0500 Subject: [PATCH 058/424] ntheta check in PolarBasis2D. Test for polar2d ntheta error. --- src/aspire/basis/polar_2d.py | 5 +++++ tests/test_PolarBasis2D.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/aspire/basis/polar_2d.py b/src/aspire/basis/polar_2d.py index 1bdcdf7c65..5f29689030 100644 --- a/src/aspire/basis/polar_2d.py +++ b/src/aspire/basis/polar_2d.py @@ -51,6 +51,11 @@ def _build(self): # try to use the same number as Fast FB basis self.ntheta = 8 * self.nrad + if self.ntheta % 2 == 1: + msg = "Only even values for ntheta are supported." + logger.error(msg) + raise NotImplementedError(msg) + self.count = self.nrad * self.ntheta self._sz_prod = self.sz[0] * self.sz[1] diff --git a/tests/test_PolarBasis2D.py b/tests/test_PolarBasis2D.py index 9fd0f1d8fd..c431390c43 100644 --- a/tests/test_PolarBasis2D.py +++ b/tests/test_PolarBasis2D.py @@ -2,6 +2,7 @@ from unittest import TestCase import numpy as np +from pytest import raises from aspire.basis import PolarBasis2D from aspire.image import Image @@ -26,6 +27,16 @@ def setUp(self): def tearDown(self): pass + def testPolarBasis2DThetaError(self): + """ + Test that PolarBasis2D when instantiated with odd value for `ntheta` + gives appropriate error. + """ + + # Test we raise with expected error. + with raises(NotImplementedError, match=r"Only even values for ntheta*"): + _ = PolarBasis2D(size=self.L, ntheta=143, dtype=self.dtype) + def testPolarBasis2DEvaluate_t(self): x = Image( np.array( From ed3169d66fe87e3de3750e44975c0b31c13d0477 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 24 Jan 2023 12:39:35 -0500 Subject: [PATCH 059/424] n_theta error test for CLSyncVoting. --- tests/test_orient_sync.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/test_orient_sync.py b/tests/test_orient_sync.py index 5900edcb9f..36e2c5a61c 100644 --- a/tests/test_orient_sync.py +++ b/tests/test_orient_sync.py @@ -5,6 +5,7 @@ import numpy as np from click.testing import CliRunner +from pytest import raises from aspire.abinitio import CLSyncVoting from aspire.commands.orient3d import orient3d @@ -42,9 +43,11 @@ def setUp(self): ) vols = vols.downsample(L) - sim = Simulation(L=L, n=n, vols=vols, unique_filters=filters, dtype=self.dtype) + self.sim = Simulation( + L=L, n=n, vols=vols, unique_filters=filters, dtype=self.dtype + ) - self.orient_est = CLSyncVoting(sim, L // 2, 36) + self.orient_est = CLSyncVoting(self.sim, L // 2, 36) def tearDown(self): pass @@ -89,6 +92,16 @@ def testEstShifts(self): np.allclose(results, self.est_shifts, atol=utest_tolerance(self.dtype)) ) + def testThetaError(self): + """ + Test that CLSyncVoting when instantiated with odd value for `n_theta` + gives appropriate error. + """ + + # Test we raise with expected error. + with raises(NotImplementedError, match=r"n_theta must be even*"): + _ = CLSyncVoting(self.sim, 16, 35) + def testCommandLine(self): # Ensure that the command line tool works as expected runner = CliRunner() From 762665350ceb890764dfe3feaa2b540718720f54 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 24 Jan 2023 15:40:11 -0500 Subject: [PATCH 060/424] Loosen utest for fast math on OSX GHA CI --- tests/test_array_image_source.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_array_image_source.py b/tests/test_array_image_source.py index c3b163a7b4..8c08759d90 100644 --- a/tests/test_array_image_source.py +++ b/tests/test_array_image_source.py @@ -143,7 +143,8 @@ def testArrayImageSourceMeanVol(self): logger.info(f"Simulation vs ArrayImageSource estimates MRSE: {delta}") # Estimate RMSE should be small. - self.assertTrue(delta <= utest_tolerance(self.dtype)) + # Loosened by factor of two to accomodate fast math on OSX CI. + self.assertTrue(delta <= 2 * utest_tolerance(self.dtype)) # And the estimate themselves should be close (virtually same inputs). # We should be within same neighborhood as generating sim_est multiple times... self.assertTrue( From afff8ae67a4727f857d16ee1ca9dadef4553d4a5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 24 Jan 2023 15:50:21 -0500 Subject: [PATCH 061/424] revert tox tweak --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 9b7b67a5a9..4c4e32b4fd 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,10 @@ envlist = clean check docs - py{3.7,3.8,3.9,3.10} + py{3.7,3.8,3.9,3.10}-{pip} minversion = 3.8.0 -[testenv:pip] +[testenv] changedir = tests deps = parameterized From fd23d1bd398a58da8adc50ecc8ccfb2bf2750723 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 24 Jan 2023 16:08:15 -0500 Subject: [PATCH 062/424] Update Azure environment-default.yml files --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4f80f9e9b4..c41402534e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,7 +30,7 @@ jobs: - bash: conda clean -i -t -y displayName: Removing conda cached package tarballs. - - bash: conda env create --name myEnvironment --file environment.yml + - bash: conda env create --name myEnvironment --file environment-default.yml displayName: Create Anaconda environment - bash: | @@ -55,7 +55,7 @@ jobs: - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" displayName: Add conda to PATH - - script: conda env create --name myEnvironment --file environment.yml + - script: conda env create --name myEnvironment --file environment-default.yml displayName: Create Anaconda environment - script: conda update -q -y conda From 752f0032878cb8bd89dbc59a84e880c039ec59e6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 25 Jan 2023 08:30:53 -0500 Subject: [PATCH 063/424] Try defaulting to conda-forge to avoid pyfftw bug --- environment-accelerate.yml | 3 ++- environment-default.yml | 2 +- environment-intel.yml | 3 ++- environment-openblas.yml | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/environment-accelerate.yml b/environment-accelerate.yml index 063447cc4a..a9de126750 100644 --- a/environment-accelerate.yml +++ b/environment-accelerate.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw diff --git a/environment-default.yml b/environment-default.yml index 1374dfad16..54c8ad8616 100644 --- a/environment-default.yml +++ b/environment-default.yml @@ -1,8 +1,8 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults dependencies: - fftw diff --git a/environment-intel.yml b/environment-intel.yml index 695bc413ea..ae0025ed09 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw diff --git a/environment-openblas.yml b/environment-openblas.yml index ba62d4a9ea..a8c3fa11db 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -1,8 +1,9 @@ name: 'aspire' channels: - - defaults - conda-forge + - defaults + dependencies: - fftw From e12ed3932ef57911ef7bb8934be6e6b9f2edacd5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 25 Jan 2023 08:52:50 -0500 Subject: [PATCH 064/424] Expand conda matrix, removes azure, conditionally only run on pull_requests --- .github/workflows/workflow.yml | 13 +++++-- azure-pipelines.yml | 70 ---------------------------------- 2 files changed, 9 insertions(+), 74 deletions(-) delete mode 100644 azure-pipelines.yml diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 68b38bd73f..93a5fe2751 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -55,16 +55,21 @@ jobs: conda-build: needs: check runs-on: ${{ matrix.os }} + # Only run on pull_requests + if: ${{ github.event_name == 'pull_request' }} defaults: run: shell: bash -el {0} strategy: matrix: - #os: [ubuntu-latest, windows-latest, macOS-latest] - #backend: [default, intel, openblas] - os: [ubuntu-latest, macOS-latest] - backend: [default] + os: [ubuntu-latest, ubuntu-20.04, macOS-latest, macOS-11, windows-latest] + backend: [default, openblas] python-version: ['3.8'] + include: + - os: ubuntu-latest + backend: intel + - os: macOS-latest + backend: accelerate steps: - uses: actions/checkout@v3 diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index c41402534e..0000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,70 +0,0 @@ -schedules: -- cron: "0 0 * * *" # cron syntax defining a schedule - displayName: Daily Build - branches: - include: - - master - - develop - always: true # Always run the Daily Build - -jobs: -- job: - displayName: macOS-11 - pool: - vmImage: 'macOS-11' - strategy: - maxParallel: 4 - matrix: - Python38: - python.version: '3.8' - - steps: - - bash: | - echo "##vso[task.prependpath]$CONDA/bin" - sudo chown -R $USER $CONDA - displayName: Add conda to PATH - - - bash: conda update -q -y conda - displayName: Update conda - - - bash: conda clean -i -t -y - displayName: Removing conda cached package tarballs. - - - bash: conda env create --name myEnvironment --file environment-default.yml - displayName: Create Anaconda environment - - - bash: | - source activate myEnvironment - pip install -e .[dev] - pip freeze --all - python -c "import numpy; numpy.show_config()" - pytest --durations=50 -s - displayName: pytest - -- job: - displayName: windows-latest - pool: - vmImage: 'windows-latest' - strategy: - maxParallel: 4 - matrix: - Python38: - python.version: '3.8' - - steps: - - powershell: Write-Host "##vso[task.prependpath]$env:CONDA\Scripts" - displayName: Add conda to PATH - - - script: conda env create --name myEnvironment --file environment-default.yml - displayName: Create Anaconda environment - - - script: conda update -q -y conda - displayName: Update conda - - - script: | - call activate myEnvironment - pip install -e .[dev] - pip freeze --all - python -c "import numpy; numpy.show_config()" - pytest --durations=50 --random-order - displayName: pytest From c840abea07e1e49f720a08b2bc452b759937f0e5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 25 Jan 2023 08:56:43 -0500 Subject: [PATCH 065/424] enforce two OMP threads and untabify --- .github/workflows/workflow.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 93a5fe2751..a504db9452 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -69,7 +69,7 @@ jobs: - os: ubuntu-latest backend: intel - os: macOS-latest - backend: accelerate + backend: accelerate steps: - uses: actions/checkout@v3 @@ -91,6 +91,7 @@ jobs: python -c "import numpy; numpy.show_config()" - name: Execute Pytest Conda ${{ matrix.os }} Python ${{ matrix.python-version }} run: | + export OMP_NUM_THREADS=2 pytest --durations=50 -s docs: From 7cc3860c42913e61ec12ad732060e992dd9cd560 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 25 Jan 2023 13:40:49 -0500 Subject: [PATCH 066/424] utest tolerance for tickled osx test --- tests/test_orient_symmetric.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 68b071d7b7..2cc193c4b7 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -5,7 +5,7 @@ from aspire.abinitio import CLSymmetryC3C4 from aspire.source import Simulation -from aspire.utils import Rotation +from aspire.utils import Rotation, utest_tolerance from aspire.utils.coor_trans import ( get_aligned_rotations, get_rots_mse, @@ -398,7 +398,7 @@ def testCompleteThirdRow(dtype): assert np.allclose(Rz, np.eye(3, dtype=dtype)) # Assert that R is orthogonal with determinant 1. - assert np.allclose(R @ R.T, np.eye(3, dtype=dtype)) + assert np.allclose(R @ R.T, np.eye(3, dtype=dtype), atol=utest_tolerance(dtype)) assert np.allclose(det(R), 1) From 44b33b18029a29c8f58d62795a4420989e37ee9b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 25 Jan 2023 13:46:25 -0500 Subject: [PATCH 067/424] Potential upstream pyfftw bug --- environment-default.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/environment-default.yml b/environment-default.yml index 54c8ad8616..e956edde64 100644 --- a/environment-default.yml +++ b/environment-default.yml @@ -6,7 +6,9 @@ channels: dependencies: - fftw - - pyfftw + # There is a potential pyFFTW bug + # https://github.com/pyFFTW/pyFFTW/issues/294 + - pyfftw=0.12 - pip - python=3.8 - numpy=1.23.5 From 21a283585f1e7934906cb78ea12bfd641af49c70 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 25 Jan 2023 16:02:32 -0500 Subject: [PATCH 068/424] update docs towards default --- .github/workflows/workflow.yml | 1 - README.md | 2 +- docs/source/installation.rst | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index a504db9452..58349cecc8 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -79,7 +79,6 @@ jobs: auto-update-conda: true python-version: ${{ matrix.python-version }} activate-environment: aspire - # TODO update yaml docs to -default` environment-file: environment-${{ matrix.backend }}.yml auto-activate-base: false - name: Complete Install and Log Environment ${{ matrix.os }} Python ${{ matrix.python-version }} diff --git a/README.md b/README.md index b468479d18..e2280b26dc 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ After cloning this repo, the simplest option is to use Anaconda 64-bit for your cd /path/to/git/clone/folder # Creates the conda environment and installs base dependencies. -conda env create -f environment.yml --name aspire_dev +conda env create -f environment-default.yml --name aspire_dev # Enable the environment conda activate aspire_dev diff --git a/docs/source/installation.rst b/docs/source/installation.rst index ae6e4af7e5..61d22be3ca 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -1,7 +1,7 @@ Installation ============ -ASPIRE comes with an ``environment.yml`` for reproducing a working Conda environment based on Python 3.8 to run the package. +ASPIRE comes with an ``environment-default.yml`` for reproducing a working Conda environment based on Python 3.8 to run the package. The package is tested on Linux/Windows/Mac OS X. Pre-built binaries are available for all platform-specific components. No manual compilation should be needed. @@ -60,7 +60,7 @@ you would probably want to keep that in a seperate environment. cd ASPIRE-Python # Create's the conda environment and installs base dependencies. - conda env create -f environment.yml --name aspire_dev + conda env create -f environment-default.yml --name aspire_dev # Activate the environment conda activate aspire_dev @@ -160,7 +160,7 @@ the performance of ``numpy``, ``scipy``, and ``scikit`` packages. ASPIRE ships several ``environment*.yml`` files which define tested package versions along with these optimized numerical installations. -The default ``environment.yml`` does not force a specific backend, +The default ``environment-default.yml`` does not force a specific backend, instead relying on ``conda`` to select something reasonable. In the case of an Intel machine, the default ``conda`` install will automatically install some optimizations for you. @@ -174,7 +174,7 @@ or as the basis for your own customized ``conda`` environment. * - Architecture - Recommended Environment File * - Default - - environment.yml + - environment-default.yml * - Intel x86_64 - environment-intel.yml * - AMD x86_64 From 1b0190a2eff5137a3a9d8df64bcebd0cf897c6de Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 26 Jan 2023 07:53:08 -0500 Subject: [PATCH 069/424] Pin pyfftw to 0.12 for all conda envs --- environment-accelerate.yml | 4 +++- environment-default.yml | 2 +- environment-intel.yml | 4 +++- environment-openblas.yml | 4 +++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/environment-accelerate.yml b/environment-accelerate.yml index a9de126750..e4ebd6f42d 100644 --- a/environment-accelerate.yml +++ b/environment-accelerate.yml @@ -7,7 +7,9 @@ channels: dependencies: - fftw - - pyfftw + # There is a potential pyFFTW bug, pin to 0.12 + # https://github.com/pyFFTW/pyFFTW/issues/294 + - pyfftw=0.12 - pip - python=3.8 - numpy=1.23.5 diff --git a/environment-default.yml b/environment-default.yml index e956edde64..dc72c57a53 100644 --- a/environment-default.yml +++ b/environment-default.yml @@ -6,7 +6,7 @@ channels: dependencies: - fftw - # There is a potential pyFFTW bug + # There is a potential pyFFTW bug, pin to 0.12 # https://github.com/pyFFTW/pyFFTW/issues/294 - pyfftw=0.12 - pip diff --git a/environment-intel.yml b/environment-intel.yml index ae0025ed09..751b3bfb86 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -7,7 +7,9 @@ channels: dependencies: - fftw - - pyfftw + # There is a potential pyFFTW bug, pin to 0.12 + # https://github.com/pyFFTW/pyFFTW/issues/294 + - pyfftw=0.12 - pip - python=3.8 - numpy=1.23.5 diff --git a/environment-openblas.yml b/environment-openblas.yml index a8c3fa11db..f57d0f37e8 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -7,7 +7,9 @@ channels: dependencies: - fftw - - pyfftw + # There is a potential pyFFTW bug, pin to 0.12 + # https://github.com/pyFFTW/pyFFTW/issues/294 + - pyfftw=0.12 - pip - python=3.8 - numpy=1.23.5 From da85fff8516e9ce04f13852e3f873e1b8ef61c34 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 26 Jan 2023 10:17:15 -0500 Subject: [PATCH 070/424] Restrict to review ready PR --- .github/workflows/workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 58349cecc8..4ef42bb7ec 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -55,8 +55,8 @@ jobs: conda-build: needs: check runs-on: ${{ matrix.os }} - # Only run on pull_requests - if: ${{ github.event_name == 'pull_request' }} + # Only run on review ready pull_requests + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false }} defaults: run: shell: bash -el {0} From e64825770471183cfa14be1e2653ca2d393e4667 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 26 Jan 2023 10:24:15 -0500 Subject: [PATCH 071/424] Restrict all jobs to review ready PR should ease the CI queue --- .github/workflows/workflow.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 4ef42bb7ec..0e20d2c783 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -23,6 +23,8 @@ jobs: build: needs: check runs-on: ubuntu-latest + # Run on every code push, but only on review ready PRs + if: ${{ github.event_name == 'push' || github.event.pull_request.draft == false }} strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10'] @@ -117,6 +119,8 @@ jobs: ampere_gpu: needs: check runs-on: self-hosted + # Run on every code push, but only on review ready PRs + if: ${{ github.event_name == 'push' || github.event.pull_request.draft == false }} steps: - uses: actions/checkout@v3 - name: Install dependencies From 76ba6f2b2a72db69f24e9384d52362651a7638ce Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 08:25:10 -0500 Subject: [PATCH 072/424] try removing pip upgrades --- .github/workflows/workflow.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 0e20d2c783..1ced1324c9 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -15,7 +15,6 @@ jobs: python-version: '3.7' - name: Install dependencies run: | - python -m pip install --upgrade pip # check still needed pip install tox tox-gh-actions - name: Run Tox Check run: tox -e check @@ -47,7 +46,6 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | - python -m pip install --upgrade pip # check still needed pip install tox tox-gh-actions - name: Test with tox run: tox --skip-missing-interpreters false -e py${{ matrix.python-version }}-${{ matrix.pyenv }} @@ -106,7 +104,6 @@ jobs: python-version: '3.7' - name: Install Dependencies run: | - python -m pip install --upgrade pip # check still needed pip install -e ".[dev]" - name: Run Sphinx doc build script env: @@ -125,7 +122,6 @@ jobs: - uses: actions/checkout@v3 - name: Install dependencies run: | - python -m pip install --upgrade pip # check still needed pip install -e ".[dev,gpu_11x]" - name: Customize config run: | From a1704fc2872772e6a5e0cb1a341d44fcb8c034b8 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 08:26:09 -0500 Subject: [PATCH 073/424] use xdist for conda pytest --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1ced1324c9..b2884decc5 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -91,7 +91,7 @@ jobs: - name: Execute Pytest Conda ${{ matrix.os }} Python ${{ matrix.python-version }} run: | export OMP_NUM_THREADS=2 - pytest --durations=50 -s + pytest -n2 --durations=50 -s docs: if: github.ref == 'refs/heads/master' From eabaf4ad882a1211894fc49a7aeb07b545beaf1a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 11:20:13 -0500 Subject: [PATCH 074/424] Code up the median sign group mean discussed for Cn out product estimation --- src/aspire/abinitio/commonline_cn.py | 96 ++++++++++++++++++++++++++++ tests/test_orient_symmetric.py | 60 +++++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 371dc614e7..33bdda4caa 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -324,3 +324,99 @@ def generate_cand_rots_third_rows(self, legacy=True): third_rows[i] = x, y, z return third_rows + + +class VeeOuterProductEstimator: + """ + Incrementally accumulate outer product entries of unknown conjugation. + """ + + # These arrays are small enough to just use doubles. + # Then we can probably avoid numerical summing concerns without precomputing denom + dtype = np.float64 + + # conjugation + J = np.array([[0, 0, -1], [0, 0, -1], [-1, -1, 0]], dtype=np.float64) + + # Create a mask selecting elements unchanged by J + mask = J == 0 + mask_inverse = ~mask + + def __init__(self): + # Create storage for non_negative (index 0) and negative_entries (index 1) + self.V_estimates = np.zeros((2, 3, 3), dtype=self.dtype) + self.counts = np.zeros((2, 3, 3), dtype=int) + # Might as well gather the second moment for var in case you need it later + self.V_estimates_moment2 = self.V_estimates.copy() + + def push(self, V): + """ + Given V, accumulate entries into two running averages. + """ + + self.V_estimates[:, self.mask] += V[self.mask] + + # Parens are important here + non_negative_entries = (V >= 0) & self.mask_inverse + negative_entries = (V < 0) & self.mask_inverse + + self.V_estimates[0][non_negative_entries] += V[non_negative_entries] + self.V_estimates[1][negative_entries] += V[negative_entries] + + self.counts[:, self.mask] += 1 + self.counts[0][non_negative_entries] += 1 + self.counts[1][negative_entries] += 1 + + self.V_estimates_moment2[..., self.mask] += V[self.mask] ** 2 + self.V_estimates_moment2[0][non_negative_entries] += ( + V[non_negative_entries] ** 2 + ) + self.V_estimates_moment2[1][negative_entries] += V[negative_entries] ** 2 + + def mean(self): + """ + Running mean. + """ + # note double sum and double count for `mask` elements cancel out + return np.sum(self.V_estimates, axis=0) / np.sum(self.counts, axis=0) + + def second_moment(self): + """ + Running second moment. + """ + # note double sum and double count for `mask` elements cancel out + return np.sum(self.V_estimates_moment2, axis=0) / np.sum(self.counts, axis=0) + + def variance(self): + """ + Running variance. + """ + return self.second_moment() - self.mean() ** 2 + + def median_sign_mean_estimate(self): + """ + Return the mean for the group of entries in V containing + the median value. + + Seperately computes running metrics (mean) for the group of + non_negative and negative entries. Keeps seperate counts + so we can compute an effective median sign estimate. + + """ + + # Find whether non negative or negative had the most entries + # This should effectively give the the group which has the same + # sign as median. + # Note on tie this code will return non_negative. + # Technically the effective median would be mean(group_means) in that case, + # but I don't think that logic is necessary yet. If needed we can add easily. + group_ind = np.argmax(self.counts, axis=0) + group_sum = np.take_along_axis(self.V_estimates, group_ind[np.newaxis], axis=0) + group_count = np.take_along_axis(self.counts, group_ind[np.newaxis], axis=0) + group_mean = group_sum / group_count + # group_moment2 = np.take_along_axis( + # self.V_estimates_moment2, group_ind[np.newaxis], axis=0 + # ) + # group_var = group_moment2 / group_count - group_mean**2 # might be interesting... + + return group_mean diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 6d280ca95d..d4f7367f0d 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -4,6 +4,7 @@ from numpy.linalg import det, norm from aspire.abinitio import CLSymmetryC3C4, CLSymmetryCn +from aspire.abinitio.commonline_cn import VeeOuterProductEstimator from aspire.source import Simulation from aspire.utils import Rotation from aspire.utils.coor_trans import ( @@ -519,3 +520,62 @@ def buildOuterProducts(n_img, dtype): viis[i] = np.outer(gt_vis[i], gt_vis[i]) return vijs, viis, gt_vis + + +def test_vee_estimator_simple(): + """ + Manully run VeeOuterProductEstimator for prebaked inputs. + """ + + est = VeeOuterProductEstimator() + + est.push(np.full((3, 3), -2, dtype=np.float64)) + est.push(np.full((3, 3), 2, dtype=np.float64)) + + assert np.allclose(est.mean(), np.full((3, 3), 0, dtype=np.float64)) + assert np.allclose(est.variance(), np.full((3, 3), 4, dtype=np.float64)) + assert np.allclose( + est.median_sign_mean_estimate(), np.array([[0, 0, 2], [0, 0, 2], [2, 2, 0]]) + ) + + est.push(np.full((3, 3), -2, dtype=np.float64)) + est.push(np.full((3, 3), -2, dtype=np.float64)) + assert np.allclose( + est.median_sign_mean_estimate(), + np.array([[-1, -1, -2], [-1, -1, -2], [-2, -2, -1]]), + ) + + +def test_vee_estimator_stat(): + """ + Tests incremental VeeOuterProductEstimator using random data, + comparing to global numpy arithmetic. + """ + + est = VeeOuterProductEstimator() + + n = 1000 + # Mix of pos and negative centers + centers = np.array([(i % 2) * 2 - 1 for i in range(1, 10)]) + V = np.array([np.random.normal(loc=c, scale=4, size=n) for c in centers]) + V = V.reshape(3, 3, n) + + for v in np.transpose(V, (2, 0, 1)): + est.push(v) + + assert np.allclose(est.mean(), np.mean(V, axis=2)) + assert np.allclose(est.variance(), np.var(V, axis=2)) + + res = np.empty((3, 3)) + # Find the mean of the entries matching sign of median + for i, j in [(0, 2), (1, 2), (2, 0), (2, 1)]: + entries = V[i, j] + group_selection = np.sign(np.median(entries)) == np.sign(entries) + res[i, j] = np.mean(entries[group_selection]) + + # These entries should match a global mean (unaffected by J) + for i, j in [(0, 0), (0, 1), (1, 0), (1, 1), (2, 2)]: + entries = V[i, j] + res[i, j] = np.mean(entries) + + assert np.allclose(est.median_sign_mean_estimate(), res) From bc00151b62d5fa7c08726f57c541a8f92a71ff26 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 11:56:37 -0500 Subject: [PATCH 075/424] Trigger on undraft of PR --- .github/workflows/workflow.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index b2884decc5..1fa2b1d9b3 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -2,7 +2,10 @@ name: ASPIRE Python Pip CI on: - push + # //docs.github.com/en/actions/reference/events-that-trigger-workflows - pull_request + types: [opened, synchronize, reopened, ready_for_review] + jobs: check: From cd4cb39fd15cc06283f94bb2652ab322949011e0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 11:59:31 -0500 Subject: [PATCH 076/424] Trigger on undraft of PR, linting --- .github/workflows/workflow.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1fa2b1d9b3..8213f864f8 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,10 +1,9 @@ name: ASPIRE Python Pip CI on: - - push - # //docs.github.com/en/actions/reference/events-that-trigger-workflows - - pull_request + pull_request: types: [opened, synchronize, reopened, ready_for_review] + push: jobs: From fca2243c45ab43c5e837b9c0e674dac0f41c8552 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 12:57:40 -0500 Subject: [PATCH 077/424] incorporate both mean and var signal power methods --- src/aspire/source/simulation.py | 124 ++++++++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 7 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 1d3144f8c5..12289693e0 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -523,11 +523,13 @@ def from_snr( sample_n=None, support_radius=None, batch_size=512, + signal_power_method="estimate_signal_var", **kwargs, ): """ Generates a Simulation source with a WhiteNoiseAdder configured to produce a target signal to noise ratio. + :param target_snr: Desired signal to noise ratio of the returned source. :param sample_n: Number of images used for estimate. @@ -535,6 +537,9 @@ def from_snr( :param support_radius: Pixel radius used for masking signal support. Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. + :param signal_power_method: Method used for computing signal energy. + Defaults to mean via `estimate_signal_mean`. + Can use variance method via `estimate_signal_var`. :returns: Simulation source. """ @@ -548,8 +553,10 @@ def from_snr( ) # Estimate the required noise variance - signal_var = sim.estimate_signal_var( - sample_n=sample_n, support_radius=support_radius + signal_var = sim.estimate_signal_power( + sample_n=sample_n, + support_radius=support_radius, + signal_power_method=signal_power_method, ) noise_var = signal_var / target_snr @@ -559,9 +566,59 @@ def from_snr( return sim + def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=512): + """ + Estimate the signal mean of `sample_n` projections. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :returns: Estimated signal mean + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_signal_mean` sample_n > Simulation.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + ) + sample_n = self.n + + if support_radius is None: + support_radius = self.L // 2 + elif not 0 < support_radius <= self.L * np.sqrt(2): + raise ValueError( + "`estimate_signal_mean`'s support_radius should be" + f" `(0, L*sqrt(2)={self.L*np.sqrt(2)}]`," + f" passed {support_radius}." + ) + + g2d = grid_2d(self.L, indexing="yx", normalized=False, dtype=self.dtype) + mask = g2d["r"] < support_radius + + # mean is estimated batch-wise, compare with numpy + # Note, for simulation we are implicitly assuming taking `sample_n` is random, + # but this does not need to be the case. We can add a `random_shuffle` param. + first_moment = 0.0 + _denom = sample_n * np.sum(mask) + for i in trange(0, sample_n, batch_size): + # Gather this batch of images and mask off area outside support_radius + images_masked = self.clean_images[i : i + batch_size].asnumpy()[..., mask] + # Accumulate first and second moments + first_moment += np.sum(images_masked) / _denom + + logger.info(f"Simulation estimated signal mean: {first_moment}") + + return first_moment + def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512): """ Estimate the signal variance of `sample_n` projections. + :param sample_n: Number of images used for estimate. Defaults to all images in source. :param support_radius: Pixel radius used for masking signal support. @@ -612,15 +669,63 @@ def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512 return estimated_var - def estimate_snr(self, sample_n=None, support_radius=None, batch_size=512): + def estimate_signal_power( + self, + sample_n=None, + support_radius=None, + batch_size=512, + signal_power_method="estimate_signal_mean", + ): + """ + Estimate the signal energy of `sample_n` projections using prescribed method. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :param signal_power_method: Method used for computing signal energy. + Defaults to mean via `estimate_signal_mean`. + Can use variance method via `estimate_signal_var`. + + :returns: Estimated signal variance. + """ + + try: + signal_estimate_method = getattr(self, signal_power_method) + except AttributeError as e: + raise ValueError( + f"Cannot find signal_power_method={signal_power_method}." + " Try the default 'estimate_signal_mean' or 'estimate_signal_var'" + ) + + signal_power = signal_estimate_method( + sample_n=sample_n, support_radius=support_radius, batch_size=batch_size + ) + if signal_estimate_method == "estimate_signal_mean": + signal_power = signal_power**2 # mean**2 + + return signal_power + + def estimate_snr( + self, + sample_n=None, + support_radius=None, + batch_size=512, + signal_power_method="estimate_signal_var", + ): """ Estimate the SNR of the simulated data set using estimated signal variance / noise variance. + :param sample_n: Number of images used for estimate. Defaults to all images in source. :param support_radius: Pixel radius used for masking signal support. Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. + :param signal_power_method: Method used for computing signal energy. + Defaults to mean via `estimate_signal_mean`. + Can use variance method via `estimate_signal_var`. :returns: Estimated signal to noise ratio. """ @@ -639,9 +744,14 @@ def estimate_snr(self, sample_n=None, support_radius=None, batch_size=512): if self.noise_adder is None: return np.inf - noise_var = self.noise_adder.noise_var - signal_var = self.estimate_signal_var( - sample_n=sample_n, support_radius=support_radius, batch_size=batch_size + noise_power = self.noise_adder.noise_var + signal_power = self.estimate_signal_power( + sample_n=sample_n, + support_radius=support_radius, + batch_size=batch_size, + signal_power_method=signal_power_method, ) - return signal_var / noise_var + # For `estimate_signal_mean` we yield: signal_mean**2 / noise_variance + # `estimate_signal_var` we yield: signal_variance / noise_variance + return signal_power / noise_power From 98b5f155559b506042eb3b2c079cf3cf861e13aa Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 13:02:35 -0500 Subject: [PATCH 078/424] Change default to be mean based signal power --- src/aspire/source/simulation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 12289693e0..4b5af42bf0 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -523,7 +523,7 @@ def from_snr( sample_n=None, support_radius=None, batch_size=512, - signal_power_method="estimate_signal_var", + signal_power_method="estimate_signal_mean", **kwargs, ): """ @@ -712,7 +712,7 @@ def estimate_snr( sample_n=None, support_radius=None, batch_size=512, - signal_power_method="estimate_signal_var", + signal_power_method="estimate_signal_mean", ): """ Estimate the SNR of the simulated data set using From ca8b088d6f33f2515c5e60642e17bb9206398820 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 27 Jan 2023 13:55:01 -0500 Subject: [PATCH 079/424] Automatically randomize projects when users bring their own angles to Simulation source --- src/aspire/source/simulation.py | 17 ++++++++++++++++- tests/test_noise.py | 7 +++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 4b5af42bf0..be5ffa3951 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -136,8 +136,10 @@ def __init__( states = randi(self.C, n, seed=seed) self.states = states + self._uniform_random_angles = False if angles is None: angles = uniform_random_angles(n, seed=seed, dtype=self.dtype) + self._uniform_random_angles = True self.angles = angles if unique_filters is None: @@ -546,6 +548,15 @@ def from_snr( # Create a Simulation sim = cls(*args, **kwargs) + # When a user supplies angles and requests `sample_n` subset of images + # randomize the rotations for them to avoid as much bias as possible. + shuffle = (not sim._uniform_random_angles) and (sample_n is not None) + if shuffle: + # Store the original rotations. + _rots = sim.rotations.copy() + # Shuffle the Sim rotations + sim.rotations = sim.rotations[np.random.permutation(sim.n)] + # Assert NoiseAdder has not been provided if "noise_adder" in kwargs or sim.noise_adder is not None: raise RuntimeError( @@ -564,6 +575,10 @@ def from_snr( sim.noise_adder = WhiteNoiseAdder(var=noise_var) logger.info(f"Appended {sim.noise_adder} to generation pipeline") + # Restore the original rotations when previously shuffled. + if shuffle: + sim.rotations = _rots + return sim def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=512): @@ -693,7 +708,7 @@ def estimate_signal_power( try: signal_estimate_method = getattr(self, signal_power_method) - except AttributeError as e: + except AttributeError: raise ValueError( f"Cannot find signal_power_method={signal_power_method}." " Try the default 'estimate_signal_mean' or 'estimate_signal_var'" diff --git a/tests/test_noise.py b/tests/test_noise.py index ef9f392202..ec88b1feca 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -14,7 +14,7 @@ ) from aspire.operators import FunctionFilter, ScalarFilter from aspire.source.simulation import Simulation -from aspire.utils import grid_3d +from aspire.utils import grid_3d, uniform_random_angles from aspire.volume import AsymmetricVolume, Volume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -43,7 +43,7 @@ def sim_fixture(request): # ie, clean centered projections. return Simulation( vols=AsymmetricVolume(L=resolution, C=1, dtype=dtype).generate(), - n=16, + n=128, amplitudes=1, offsets=0, dtype=dtype, @@ -199,6 +199,9 @@ def test_from_snr_white(sim_fixture, target_noise_variance): n=sim_fixture.n, offsets=0, amplitudes=1.0, + # Excercise the `sample_n` and shuffle branches + angles=uniform_random_angles(sim_fixture.n, dtype=sim_fixture.dtype), + sample_n=sim_fixture.n - 1, ) # Check we're within 1% of explicit target From 862f1af5efaa298163c4cb0c6131d187f99ab5e4 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 30 Jan 2023 08:07:31 -0500 Subject: [PATCH 080/424] Add dB unit util and make snr methods consistent --- src/aspire/source/simulation.py | 31 +++++++++++++++++++++++++------ src/aspire/utils/__init__.py | 1 + src/aspire/utils/units.py | 22 ++++++++++++++++++++++ tests/test_noise.py | 2 +- 4 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 src/aspire/utils/units.py diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index be5ffa3951..feca2b1173 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -15,6 +15,7 @@ anorm, grid_2d, make_symmat, + ratio_to_decibel, trange, uniform_random_angles, vecmat_to_volmat, @@ -474,18 +475,19 @@ def eval_coords(self, mean_vol, eig_vols, coords_est): return {"err": err, "rel_err": rel_err, "corr": corr} - def estimate_psnr(self, sample_n=None, batch_size=512): + def estimate_psnr(self, sample_n=None, batch_size=512, units=None): """ - Estimate Peak SNR in decibels as 10*Log(max(signal)^2 / MSE), + Estimate Peak SNR as max(signal)^2 / MSE, where MSE is computed between `projections` and the resulting simulated `images`. PSNR is computed along the stack axis and an average - decibel value across the sample is returned. + value across the sample is returned. Note that PSNR is inherently a poor metric for identical images. :param sample_n: Number of images used for estimate. Defaults to all images in source. - :returns: Estimated peak signal to noise ratio in decibels. + :param units: Optionally, convert from default ratio to log scale (`dB`). + :returns: Estimated peak signal to noise ratio. """ if sample_n is None: @@ -515,7 +517,14 @@ def estimate_psnr(self, sample_n=None, batch_size=512): multioutput="raw_values", ) - return np.mean(10 * np.log10(peaksq / mse)) + psnr = peaksq / mse + if units == "dB": + psnr = ratio_to_decibel(psnr) + elif units is not None: + raise ValueError("Units should be `None` or `dB`.") + + # Return the mean psnr of the stack. + return np.mean(psnr) @classmethod def from_snr( @@ -728,6 +737,7 @@ def estimate_snr( support_radius=None, batch_size=512, signal_power_method="estimate_signal_mean", + units=None, ): """ Estimate the SNR of the simulated data set using @@ -741,6 +751,7 @@ def estimate_snr( :param signal_power_method: Method used for computing signal energy. Defaults to mean via `estimate_signal_mean`. Can use variance method via `estimate_signal_var`. + :param units: Optionally, convert from default ratio to log scale (`dB`). :returns: Estimated signal to noise ratio. """ @@ -769,4 +780,12 @@ def estimate_snr( # For `estimate_signal_mean` we yield: signal_mean**2 / noise_variance # `estimate_signal_var` we yield: signal_variance / noise_variance - return signal_power / noise_power + snr = signal_power / noise_power + + # Perform any unit conversion + if units == "dB": + snr = ratio_to_decibel(psnr) + elif units is not None: + raise ValueError("Units should be `None` or `dB`.") + + return snr diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index d9e9045a68..8d0bbd37a9 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -64,3 +64,4 @@ from .random import Random, choice, rand, randi, randn, random from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance +from .units import ratio_to_decibel diff --git a/src/aspire/utils/units.py b/src/aspire/utils/units.py new file mode 100644 index 0000000000..16e951cf66 --- /dev/null +++ b/src/aspire/utils/units.py @@ -0,0 +1,22 @@ +""" +Miscellaneous utilities for common unit conversions. +""" + +import logging + +import numpy as np + +logger = logging.getLogger(__name__) + + +def ratio_to_decibel(p): + """ + Convert a ratio of powers to decibel (log) scale. + + Follows numpy broadcasting rules. + + :param p: Power ratio. + :returns: Power ratio in log scale + """ + + return 10 * np.log10(p) diff --git a/tests/test_noise.py b/tests/test_noise.py index ec88b1feca..c454ce597a 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -171,7 +171,7 @@ def test_psnr(res, target_noise_variance, dtype): noise_adder=WhiteNoiseAdder(var=target_noise_variance), ) - psnr = sim.estimate_psnr() + psnr = sim.estimate_psnr(units="dB") logger.debug(f"PSNR target={target_noise_variance} L={sim.L} {sim.dtype} {psnr}") assert np.isclose(psnr, -10 * np.log10(target_noise_variance), rtol=0.01) From 3f33f1adfc9b051a41dc28a6a48a83e246e3f9e5 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 30 Jan 2023 10:19:08 -0500 Subject: [PATCH 081/424] add J-synchronized mean method --- src/aspire/abinitio/commonline_cn.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 44835bd020..eeec650c60 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -354,6 +354,11 @@ def __init__(self): # Create storage for non_negative (index 0) and negative_entries (index 1) self.V_estimates = np.zeros((2, 3, 3), dtype=self.dtype) self.counts = np.zeros((2, 3, 3), dtype=int) + + # Create storage for J-synchronized estimates. + self.V_estimates_sync = np.zeros((3,3), dtype=self.dtype) + self.count = 0 + # Might as well gather the second moment for var in case you need it later self.V_estimates_moment2 = self.V_estimates.copy() @@ -381,6 +386,17 @@ def push(self, V): ) self.V_estimates_moment2[1][negative_entries] += V[negative_entries] ** 2 + # Accumulate synchronized entries to compute synchronized mean. + if self.count == 0: + self.V_estimates_sync += V + else: + if (np.sign(V) == np.sign(self.V_estimates_sync)).all(): + self.V_estimates_sync += V + else: + self.V_estimates_sync += J_conjugate(V) + + self.count += 1 + def mean(self): """ Running mean. @@ -388,6 +404,12 @@ def mean(self): # note double sum and double count for `mask` elements cancel out return np.sum(self.V_estimates, axis=0) / np.sum(self.counts, axis=0) + def synchronized_mean(self): + """ + Calculate the mean of synchronized outer product estimates. + """ + return self.V_estimate_sync / self.count + def second_moment(self): """ Running second moment. From 441202cb8f1c31222ba713e434c7d3e4472ace97 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 30 Jan 2023 10:32:15 -0500 Subject: [PATCH 082/424] Refactor snr supporting code into base ImageSource, breakout shared utils/mask --- src/aspire/source/image.py | 208 ++++++++++++++++++++++++- src/aspire/source/simulation.py | 261 +++++--------------------------- src/aspire/utils/__init__.py | 1 + src/aspire/utils/misc.py | 20 +++ 4 files changed, 265 insertions(+), 225 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 5b9d8821af..5d70165f5e 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -17,6 +17,7 @@ Multiply, Pipeline, ) +from aspire.noise import AnisotropicNoiseEstimator from aspire.operators import ( CTFFilter, IdentityFilter, @@ -24,7 +25,7 @@ PowerFilter, ) from aspire.storage import MrcStats, StarFile -from aspire.utils import Rotation, grid_2d, trange +from aspire.utils import Rotation, grid_2d, ratio_to_decibel, support_mask, trange logger = logging.getLogger(__name__) @@ -146,6 +147,8 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): # Instantiate the accessor for the `images` property self._img_accessor = _ImageAccessor(self._images, self.n) + # For base ImageSource, signal is estimated from `images` + self._signal_images = self.images logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") @@ -791,6 +794,209 @@ def save_images( im = self.images[i_start:i_end] im.save(mrcs_filepath, overwrite=overwrite) + def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=512): + """ + Estimate the signal mean of `sample_n` projections. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :returns: Estimated signal mean + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_signal_mean` sample_n > Source.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + ) + sample_n = self.n + + mask = support_mask(self.L, support_radius, dtype=self.dtype) + + # mean is estimated batch-wise, compare with numpy + # Note, for simulation we are implicitly assuming taking `sample_n` is random, + # but this does not need to be the case. We can add a `random_shuffle` param. + first_moment = 0.0 + _denom = sample_n * np.sum(mask) + for i in trange(0, sample_n, batch_size): + # Gather this batch of images and mask off area outside support_radius + images_masked = self._signal_images[i : i + batch_size].asnumpy()[..., mask] + # Accumulate first and second moments + first_moment += np.sum(images_masked) / _denom + + logger.info(f"Source estimated signal mean: {first_moment}") + + return first_moment + + def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512): + """ + Estimate the signal variance of `sample_n` projections. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :returns: Estimated signal variance. + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_signal_var` sample_n > Source.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + ) + sample_n = self.n + + mask = support_mask(self.L, support_radius, dtype=self.dtype) + + # Var is estimated batch-wise, compare with numpy + # np_estimated_var = np.var(self._signal_images[:sample_n].asnumpy()[..., mask]) + # Note, for simulation we are implicitly assuming taking `sample_n` is random, + # but this does not need to be the case. We can add a `random_shuffle` param. + first_moment = 0.0 + second_moment = 0.0 + _denom = sample_n * np.sum(mask) + for i in trange(0, sample_n, batch_size): + # Gather this batch of images and mask off area outside support_radius + images_masked = self._signal_images[i : i + batch_size].asnumpy()[..., mask] + # Accumulate first and second moments + first_moment += np.sum(images_masked) / _denom + second_moment += np.sum(images_masked**2) / _denom + + # E[X**2] - E[X]**2 + estimated_var = second_moment - first_moment**2 + logger.info(f"Source estimated signal var: {estimated_var}") + + return estimated_var + + def estimate_signal_power( + self, + sample_n=None, + support_radius=None, + batch_size=512, + signal_power_method="estimate_signal_mean", + ): + """ + Estimate the signal energy of `sample_n` projections using prescribed method. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :param signal_power_method: Method used for computing signal energy. + Defaults to mean via `estimate_signal_mean`. + Can use variance method via `estimate_signal_var`. + + :returns: Estimated signal variance. + """ + + try: + signal_estimate_method = getattr(self, signal_power_method) + except AttributeError: + raise ValueError( + f"Cannot find signal_power_method={signal_power_method}." + " Try the default 'estimate_signal_mean' or 'estimate_signal_var'" + ) + + signal_power = signal_estimate_method( + sample_n=sample_n, support_radius=support_radius, batch_size=batch_size + ) + if signal_estimate_method == "estimate_signal_mean": + signal_power = signal_power**2 # mean**2 + + return signal_power + + def estimate_noise_power( + self, + sample_n=None, + support_radius=None, + batch_size=512, + noise_estimator=AnisotropicNoiseEstimator, + ): + """ + Estimate the noise energy of `sample_n` images using prescribed estimator. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :param noise_estimator: Method used for estimating noise. + Defaults to AnisotropicNoiseEstimator. + + :returns: Estimated noise energy (variance). + """ + + est = noise_estimator(src=self, bgRadius=support_radius, batchSize=batch_size) + + return est.estimate() + + def estimate_snr( + self, + sample_n=None, + support_radius=None, + batch_size=512, + noise_power=None, + signal_power_method="estimate_signal_mean", + units=None, + ): + """ + Estimate the SNR of the simulated data set using + estimated signal variance / noise variance. + + :param sample_n: Number of images used for estimate. + Defaults to all images in source. + :param support_radius: Pixel radius used for masking signal support. + Default of None will compute inscribed circle, `self.L // 2`. + :param batch_size: Images per batch, defaults 512. + :param signal_power_method: Method used for computing signal energy. + Defaults to mean via `estimate_signal_mean`. + Can use variance method via `estimate_signal_var`. + :param units: Optionally, convert from default ratio to log scale (`dB`). + :returns: Estimated signal to noise ratio. + """ + + if sample_n is None: + sample_n = self.n + + if sample_n > self.n: + logger.warning( + f"`estimate_snr` sample_n > Source.n: {sample_n} > {self.n}." + f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + ) + sample_n = self.n + + if noise_power is None: + noise_power = self.estimate_noise_power() + + signal_power = self.estimate_signal_power( + sample_n=sample_n, + support_radius=support_radius, + batch_size=batch_size, + signal_power_method=signal_power_method, + ) + + # For `estimate_signal_mean` we yield: signal_mean**2 / noise_variance + # `estimate_signal_var` we yield: signal_variance / noise_variance + snr = signal_power / noise_power + + # Perform any unit conversion + if units == "dB": + snr = ratio_to_decibel(snr) + elif units is not None: + raise ValueError("Units should be `None` or `dB`.") + + return snr + class ArrayImageSource(ImageSource): """ diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index feca2b1173..77a20e27b5 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -13,7 +13,6 @@ acorr, ainner, anorm, - grid_2d, make_symmat, ratio_to_decibel, trange, @@ -170,6 +169,8 @@ def __init__( self._projections_accessor = _ImageAccessor(self._projections, self.n) self._clean_images_accessor = _ImageAccessor(self._clean_images, self.n) + # For Simulation, signal can be computed directly from clean_images. + self._signal_images = self.clean_images def _populate_ctf_metadata(self, filter_indices): # Since we are not reading from a starfile, we must construct @@ -475,56 +476,17 @@ def eval_coords(self, mean_vol, eig_vols, coords_est): return {"err": err, "rel_err": rel_err, "corr": corr} - def estimate_psnr(self, sample_n=None, batch_size=512, units=None): + def estimate_snr(self, *args, **kwargs): """ - Estimate Peak SNR as max(signal)^2 / MSE, - where MSE is computed between `projections` - and the resulting simulated `images`. - PSNR is computed along the stack axis and an average - value across the sample is returned. - Note that PSNR is inherently a poor metric for identical images. - - :param sample_n: Number of images used for estimate. - Defaults to all images in source. - :param units: Optionally, convert from default ratio to log scale (`dB`). - :returns: Estimated peak signal to noise ratio. + See ImageSource.estimate_snr() for documentation. """ + # For clean images return infinite SNR. + # Note, relationship with CTF and other sim corruptions still isn't clear to me... + if self.noise_adder is None: + return np.inf - if sample_n is None: - sample_n = self.n - - if sample_n > self.n: - logger.warning( - f"`estimate_psnr` sample_n > Simulation.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" - ) - sample_n = self.n - - peaksq = np.empty(sample_n, dtype=self.dtype) - mse = np.empty(sample_n, dtype=self.dtype) - for start in trange(0, sample_n, batch_size): - end = min(start + batch_size, sample_n) - - signal = self.projections[start:end].asnumpy() - images = self.images[start:end].asnumpy() - peaksq[start:end] = np.square(signal).max(axis=(-1, -2)) # max per image - - # Reshape and Transpose for Scikit metrics, - # which expect a 2d (samples, outputs) - mse[start:end] = mean_squared_error( - signal.reshape(sample_n, -1).T, - images.reshape(sample_n, -1).T, - multioutput="raw_values", - ) - - psnr = peaksq / mse - if units == "dB": - psnr = ratio_to_decibel(psnr) - elif units is not None: - raise ValueError("Units should be `None` or `dB`.") - - # Return the mean psnr of the stack. - return np.mean(psnr) + # For SNR, Use the noise variance known from the noise_adder. + return super().estimate_snr(noise_power=self.noise_adder.noise_var) @classmethod def from_snr( @@ -590,169 +552,19 @@ def from_snr( return sim - def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=512): - """ - Estimate the signal mean of `sample_n` projections. - - :param sample_n: Number of images used for estimate. - Defaults to all images in source. - :param support_radius: Pixel radius used for masking signal support. - Default of None will compute inscribed circle, `self.L // 2`. - :param batch_size: Images per batch, defaults 512. - :returns: Estimated signal mean - """ - - if sample_n is None: - sample_n = self.n - - if sample_n > self.n: - logger.warning( - f"`estimate_signal_mean` sample_n > Simulation.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" - ) - sample_n = self.n - - if support_radius is None: - support_radius = self.L // 2 - elif not 0 < support_radius <= self.L * np.sqrt(2): - raise ValueError( - "`estimate_signal_mean`'s support_radius should be" - f" `(0, L*sqrt(2)={self.L*np.sqrt(2)}]`," - f" passed {support_radius}." - ) - - g2d = grid_2d(self.L, indexing="yx", normalized=False, dtype=self.dtype) - mask = g2d["r"] < support_radius - - # mean is estimated batch-wise, compare with numpy - # Note, for simulation we are implicitly assuming taking `sample_n` is random, - # but this does not need to be the case. We can add a `random_shuffle` param. - first_moment = 0.0 - _denom = sample_n * np.sum(mask) - for i in trange(0, sample_n, batch_size): - # Gather this batch of images and mask off area outside support_radius - images_masked = self.clean_images[i : i + batch_size].asnumpy()[..., mask] - # Accumulate first and second moments - first_moment += np.sum(images_masked) / _denom - - logger.info(f"Simulation estimated signal mean: {first_moment}") - - return first_moment - - def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512): - """ - Estimate the signal variance of `sample_n` projections. - - :param sample_n: Number of images used for estimate. - Defaults to all images in source. - :param support_radius: Pixel radius used for masking signal support. - Default of None will compute inscribed circle, `self.L // 2`. - :param batch_size: Images per batch, defaults 512. - :returns: Estimated signal variance. - """ - - if sample_n is None: - sample_n = self.n - - if sample_n > self.n: - logger.warning( - f"`estimate_signal_var` sample_n > Simulation.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" - ) - sample_n = self.n - - if support_radius is None: - support_radius = self.L // 2 - elif not 0 < support_radius <= self.L * np.sqrt(2): - raise ValueError( - "`estimate_signal_var`'s support_radius should be" - f" `(0, L*sqrt(2)={self.L*np.sqrt(2)}]`," - f" passed {support_radius}." - ) - - g2d = grid_2d(self.L, indexing="yx", normalized=False, dtype=self.dtype) - mask = g2d["r"] < support_radius - - # Var is estimated batch-wise, compare with numpy - # np_estimated_var = np.var(self.clean_images[:sample_n].asnumpy()[..., mask]) - # Note, for simulation we are implicitly assuming taking `sample_n` is random, - # but this does not need to be the case. We can add a `random_shuffle` param. - first_moment = 0.0 - second_moment = 0.0 - _denom = sample_n * np.sum(mask) - for i in trange(0, sample_n, batch_size): - # Gather this batch of images and mask off area outside support_radius - images_masked = self.clean_images[i : i + batch_size].asnumpy()[..., mask] - # Accumulate first and second moments - first_moment += np.sum(images_masked) / _denom - second_moment += np.sum(images_masked**2) / _denom - - # E[X**2] - E[X]**2 - estimated_var = second_moment - first_moment**2 - logger.info(f"Simulation estimated signal var: {estimated_var}") - - return estimated_var - - def estimate_signal_power( - self, - sample_n=None, - support_radius=None, - batch_size=512, - signal_power_method="estimate_signal_mean", - ): - """ - Estimate the signal energy of `sample_n` projections using prescribed method. - - :param sample_n: Number of images used for estimate. - Defaults to all images in source. - :param support_radius: Pixel radius used for masking signal support. - Default of None will compute inscribed circle, `self.L // 2`. - :param batch_size: Images per batch, defaults 512. - :param signal_power_method: Method used for computing signal energy. - Defaults to mean via `estimate_signal_mean`. - Can use variance method via `estimate_signal_var`. - - :returns: Estimated signal variance. - """ - - try: - signal_estimate_method = getattr(self, signal_power_method) - except AttributeError: - raise ValueError( - f"Cannot find signal_power_method={signal_power_method}." - " Try the default 'estimate_signal_mean' or 'estimate_signal_var'" - ) - - signal_power = signal_estimate_method( - sample_n=sample_n, support_radius=support_radius, batch_size=batch_size - ) - if signal_estimate_method == "estimate_signal_mean": - signal_power = signal_power**2 # mean**2 - - return signal_power - - def estimate_snr( - self, - sample_n=None, - support_radius=None, - batch_size=512, - signal_power_method="estimate_signal_mean", - units=None, - ): + def estimate_psnr(self, sample_n=None, batch_size=512, units=None): """ - Estimate the SNR of the simulated data set using - estimated signal variance / noise variance. + Estimate Peak SNR as max(signal)^2 / MSE, + where MSE is computed between `projections` + and the resulting simulated `images`. + PSNR is computed along the stack axis and an average + value across the sample is returned. + Note that PSNR is inherently a poor metric for identical images. :param sample_n: Number of images used for estimate. Defaults to all images in source. - :param support_radius: Pixel radius used for masking signal support. - Default of None will compute inscribed circle, `self.L // 2`. - :param batch_size: Images per batch, defaults 512. - :param signal_power_method: Method used for computing signal energy. - Defaults to mean via `estimate_signal_mean`. - Can use variance method via `estimate_signal_var`. :param units: Optionally, convert from default ratio to log scale (`dB`). - :returns: Estimated signal to noise ratio. + :returns: Estimated peak signal to noise ratio. """ if sample_n is None: @@ -760,32 +572,33 @@ def estimate_snr( if sample_n > self.n: logger.warning( - f"`estimate_snr` sample_n > Simulation.n: {sample_n} > {self.n}." + f"`estimate_psnr` sample_n > Source.n: {sample_n} > {self.n}." f" Accuracy may be impaired, settting sample_n=self.n={self.n}" ) sample_n = self.n - # For clean images return infinite SNR. - # Note, relationship with CTF and other sim corruptions still isn't clear to me... - if self.noise_adder is None: - return np.inf + peaksq = np.empty(sample_n, dtype=self.dtype) + mse = np.empty(sample_n, dtype=self.dtype) + for start in trange(0, sample_n, batch_size): + end = min(start + batch_size, sample_n) - noise_power = self.noise_adder.noise_var - signal_power = self.estimate_signal_power( - sample_n=sample_n, - support_radius=support_radius, - batch_size=batch_size, - signal_power_method=signal_power_method, - ) + signal = self.projections[start:end].asnumpy() + images = self.images[start:end].asnumpy() + peaksq[start:end] = np.square(signal).max(axis=(-1, -2)) # max per image - # For `estimate_signal_mean` we yield: signal_mean**2 / noise_variance - # `estimate_signal_var` we yield: signal_variance / noise_variance - snr = signal_power / noise_power + # Reshape and Transpose for Scikit metrics, + # which expect a 2d (samples, outputs) + mse[start:end] = mean_squared_error( + signal.reshape(sample_n, -1).T, + images.reshape(sample_n, -1).T, + multioutput="raw_values", + ) - # Perform any unit conversion + psnr = peaksq / mse if units == "dB": - snr = ratio_to_decibel(psnr) + psnr = ratio_to_decibel(psnr) elif units is not None: raise ValueError("Units should be `None` or `dB`.") - return snr + # Return the mean psnr of the stack. + return np.mean(psnr) diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 8d0bbd37a9..b3589a32b2 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -27,6 +27,7 @@ pairs_to_linear, powerset, sha256sum, + support_mask, fuzzy_mask, ) diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index b801873049..754dc2379c 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -363,3 +363,23 @@ def cyclic_rotations(order, dtype=np.float64): rots_symm = Rotation.from_euler(angles) return rots_symm + + +# Potentially cache this in the future. +def support_mask(L, support_radius=None, dtype=np.float64): + if support_radius is None: + support_radius = L // 2 + elif support_radius == -1: + # Disables mask, here to reduce code duplication. + return np.full((L, L), fill_value=True, dtype=bool) + elif not 0 < support_radius <= L * np.sqrt(2): + raise ValueError( + "support_radius should be" + f" `(0, L*sqrt(2)={L*np.sqrt(2)}]` or -1 to disable." + f" passed {support_radius}." + ) + + g2d = grid_2d(L, indexing="yx", normalized=False, dtype=dtype) + mask = g2d["r"] < support_radius + + return mask From 30a652cb59503eda941eb8ae76ba281416c984d3 Mon Sep 17 00:00:00 2001 From: Chris Langfield <34426450+chris-langfield@users.noreply.github.com> Date: Mon, 30 Jan 2023 12:56:04 -0500 Subject: [PATCH 083/424] Drop `parameterized_class` from `FBBasis2D` and `FFBBasis2D` (#818) * initial changeset * make changes to UniversalBasisMixin * steerable basis mixin * fix some typos and add descriptive test names * cleanup * FFB2d * workaround for other non-parametrized bases * period * move basis test parameters to basis_util and add some comments * first pass at parametrizing fb3d with pytest.skip * added 3d params * basis params 3d * ffb3d, and removed skips from fb3d as params stored in basis_params_3d * pswf changes * pswf_params_2d --- tests/_basis_util.py | 177 +++--- tests/test_FBbasis2D.py | 111 ++-- tests/test_FBbasis3D.py | 1127 ++++++++++++++++++------------------ tests/test_FFBbasis2D.py | 109 ++-- tests/test_FFBbasis3D.py | 708 +++++++++++----------- tests/test_FPSWFbasis2D.py | 30 +- tests/test_PSWFbasis2D.py | 31 +- tests/test_PolarBasis2D.py | 20 + 8 files changed, 1150 insertions(+), 1163 deletions(-) diff --git a/tests/_basis_util.py b/tests/_basis_util.py index 17bcedad02..fc4cea74a2 100644 --- a/tests/_basis_util.py +++ b/tests/_basis_util.py @@ -1,6 +1,5 @@ -from unittest.case import SkipTest - import numpy as np +import pytest from aspire.image import Image from aspire.utils import gaussian_2d, utest_tolerance @@ -8,13 +7,44 @@ from aspire.utils.random import randn from aspire.volume import Volume +# Parameter combinations for testing 2D bases +# Each tuple represents (resolution in pixels, datatype of basis) +basis_params_2d = [ + (8, np.float32), + (8, np.float64), + (16, np.float32), + (16, np.float64), + (32, np.float32), + (32, np.float64), +] + +basis_params_3d = [ + (8, np.float32), + (8, np.float64), +] + +pswf_params_2d = [ + (8, np.float64), +] + + +def show_basis_params(basis): + # print descriptive test name for parametrized test + # run pytest with option -rA to see explicitly + return f"{basis.nres}-{basis.dtype}" + class Steerable2DMixin: - def testIndices(self): - ell_max = self.basis.ell_max - k_max = self.basis.k_max + """ + Inheriting Test class will expect all Steerable2DMixin functions to take a Basis object + as a parameter. + """ + + def testIndices(self, basis): + ell_max = basis.ell_max + k_max = basis.k_max - indices = self.basis.indices() + indices = basis.indices() i = 0 @@ -26,24 +56,26 @@ def testIndices(self): for sgn in sgns: for k in range(k_max[ell]): - self.assertTrue(indices["ells"][i] == ell) - self.assertTrue(indices["sgns"][i] == sgn) - self.assertTrue(indices["ks"][i] == k) + assert indices["ells"][i] == ell + assert indices["sgns"][i] == sgn + assert indices["ks"][i] == k i += 1 - def testGaussianExpand(self): + def testGaussianExpand(self, basis): # Offset slightly x0 = 0.50 y0 = 0.75 + L = basis.nres + # Want sigma to be as large as possible without the Gaussian # spilling too much outside the central disk. - sigma = self.L / 8 - im1 = gaussian_2d(self.L, mu=(x0, y0), sigma=sigma, dtype=self.dtype) + sigma = L / 8 + im1 = gaussian_2d(L, mu=(x0, y0), sigma=sigma, dtype=basis.dtype) - coef = self.basis.expand(im1) - im2 = self.basis.evaluate(coef) + coef = basis.expand(im1) + im2 = basis.evaluate(coef) if isinstance(im2, Image): im2 = im2.asnumpy() @@ -51,121 +83,126 @@ def testGaussianExpand(self): # For small L there's too much clipping at high freqs to get 1e-3 # accuracy. - if self.L < 32: + if L < 32: atol = 1e-2 else: atol = 1e-3 - self.assertTrue(im1.shape == im2.shape) - self.assertTrue(np.allclose(im1, im2, atol=atol)) + assert im1.shape == im2.shape + assert np.allclose(im1, im2, atol=atol) - def testIsotropic(self): - sigma = self.L / 8 - im = gaussian_2d(self.L, sigma=sigma, dtype=self.dtype) + def testIsotropic(self, basis): + L = basis.nres + sigma = L / 8 + im = gaussian_2d(L, sigma=sigma, dtype=basis.dtype) - coef = self.basis.expand(im) + coef = basis.expand(im) - ells = self.basis.indices()["ells"] + ells = basis.indices()["ells"] energy_outside = np.sum(np.abs(coef[ells != 0]) ** 2) energy_total = np.sum(np.abs(coef) ** 2) energy_ratio = energy_outside / energy_total - self.assertTrue(energy_ratio < 0.01) + assert energy_ratio < 0.01 - def testModulated(self): - if self.L < 32: - raise SkipTest + def testModulated(self, basis): + L = basis.nres + if L < 32: + pytest.skip() ell = 1 - sigma = self.L / 8 - im = gaussian_2d(self.L, sigma=sigma, dtype=self.dtype) + sigma = L / 8 + im = gaussian_2d(L, sigma=sigma, dtype=basis.dtype) - g2d = grid_2d(self.L) + g2d = grid_2d(L) for trig_fun in (np.sin, np.cos): im1 = im * trig_fun(ell * g2d["phi"]) - coef = self.basis.expand(im1) + coef = basis.expand(im1) - ells = self.basis.indices()["ells"] + ells = basis.indices()["ells"] energy_outside = np.sum(np.abs(coef[ells != ell]) ** 2) energy_total = np.sum(np.abs(coef) ** 2) energy_ratio = energy_outside / energy_total - self.assertTrue(energy_ratio < 0.10) + assert energy_ratio < 0.10 - def testEvaluateExpand(self): - coef1 = randn(self.basis.count, seed=self.seed) - coef1 = coef1.astype(self.dtype) + def testEvaluateExpand(self, basis): + coef1 = randn(basis.count, seed=self.seed) + coef1 = coef1.astype(basis.dtype) - im = self.basis.evaluate(coef1) + im = basis.evaluate(coef1) if isinstance(im, Image): im = im.asnumpy() - coef2 = self.basis.expand(im)[0] + coef2 = basis.expand(im)[0] - self.assertTrue(coef1.shape == coef2.shape) - self.assertTrue(np.allclose(coef1, coef2, atol=utest_tolerance(self.dtype))) + assert coef1.shape == coef2.shape + assert np.allclose(coef1, coef2, atol=utest_tolerance(basis.dtype)) - def testAdjoint(self): - u = randn(self.basis.count, seed=self.seed) - u = u.astype(self.dtype) + def testAdjoint(self, basis): + u = randn(basis.count, seed=self.seed) + u = u.astype(basis.dtype) - Au = self.basis.evaluate(u) + Au = basis.evaluate(u) if isinstance(Au, Image): Au = Au.asnumpy() - x = Image(randn(*self.basis.sz, seed=self.seed), dtype=self.dtype) + x = Image(randn(*basis.sz, seed=self.seed), dtype=basis.dtype) - ATx = self.basis.evaluate_t(x) + ATx = basis.evaluate_t(x) Au_dot_x = np.sum(Au * x.asnumpy()) u_dot_ATx = np.sum(u * ATx) - self.assertTrue(Au_dot_x.shape == u_dot_ATx.shape) - self.assertTrue(np.isclose(Au_dot_x, u_dot_ATx)) + assert Au_dot_x.shape == u_dot_ATx.shape + assert np.isclose(Au_dot_x, u_dot_ATx) class UniversalBasisMixin: - def getClass(self): - if self.basis.ndim == 2: + """ + Inheriting Test class will expect all UniversalBasisMixin functions to take a Basis object + as a parameter. + """ + + def getClass(self, basis): + if basis.ndim == 2: return Image - elif self.basis.ndim == 3: + elif basis.ndim == 3: return Volume - def testEvaluate(self): + def testEvaluate(self, basis): # evaluate should take a NumPy array of type basis.coefficient_dtype # and return an Image/Volume - _class = self.getClass() - result = self.basis.evaluate( - np.zeros((self.basis.count), dtype=self.basis.coefficient_dtype) - ) - self.assertTrue(isinstance(result, _class)) + _class = self.getClass(basis) + result = basis.evaluate(np.zeros((basis.count), dtype=basis.coefficient_dtype)) + assert isinstance(result, _class) - def testEvaluate_t(self): + def testEvaluate_t(self, basis): # evaluate_t should take an Image/Volume and return a NumPy array of type # basis.coefficient_dtype - _class = self.getClass() - result = self.basis.evaluate_t( - _class(np.zeros((self.L,) * self.basis.ndim, dtype=self.dtype)) + _class = self.getClass(basis) + result = basis.evaluate_t( + _class(np.zeros((basis.nres,) * basis.ndim, dtype=basis.dtype)) ) - self.assertTrue(isinstance(result, np.ndarray)) - self.assertEqual(result.dtype, self.basis.coefficient_dtype) + assert isinstance(result, np.ndarray) + assert result.dtype == basis.coefficient_dtype - def testExpand(self): - _class = self.getClass() + def testExpand(self, basis): + _class = self.getClass(basis) # expand should take an Image/Volume and return a NumPy array of type # basis.coefficient_dtype - result = self.basis.expand( - _class(np.zeros((self.L,) * self.basis.ndim, dtype=self.dtype)) + result = basis.expand( + _class(np.zeros((basis.nres,) * basis.ndim, dtype=basis.dtype)) ) - self.assertTrue(isinstance(result, np.ndarray)) - self.assertEqual(result.dtype, self.basis.coefficient_dtype) + assert isinstance(result, np.ndarray) + assert result.dtype == basis.coefficient_dtype - def testInitWithIntSize(self): + def testInitWithIntSize(self, basis): # make sure we can instantiate with just an int as a shortcut - self.assertEqual((self.L,) * self.basis.ndim, self.basis.__class__(self.L).sz) + assert (basis.nres,) * basis.ndim == basis.__class__(basis.nres).sz diff --git a/tests/test_FBbasis2D.py b/tests/test_FBbasis2D.py index 7b016bde7c..9b60c0e788 100644 --- a/tests/test_FBbasis2D.py +++ b/tests/test_FBbasis2D.py @@ -1,8 +1,7 @@ import os.path -from unittest import TestCase import numpy as np -from parameterized import parameterized_class +import pytest from pytest import raises from scipy.special import jv @@ -12,52 +11,40 @@ from aspire.utils.coor_trans import grid_2d from aspire.utils.random import randn -from ._basis_util import Steerable2DMixin, UniversalBasisMixin +from ._basis_util import ( + Steerable2DMixin, + UniversalBasisMixin, + basis_params_2d, + show_basis_params, +) DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +# Create a test Basis object for each combination of parameters we want to test +test_bases = [FBBasis2D(L, dtype=dtype) for L, dtype in basis_params_2d] -# NOTE: Default class values (ie. L=8, dtype=np.float32) are listed here and below to -# to be picked up by TestCase. This means the default values are tested twice by the Mixins. -@parameterized_class( - ("L", "dtype"), - [ - (8, np.float32), - (8, np.float64), - (16, np.float32), - (16, np.float64), - (32, np.float32), - (32, np.float64), - ], -) -class FBBasis2DTestCase(TestCase, Steerable2DMixin, UniversalBasisMixin): - L = 8 - dtype = np.float32 - - def setUp(self): - self.basis = FBBasis2D((self.L, self.L), dtype=self.dtype) - self.seed = 9161341 - def tearDown(self): - pass +@pytest.mark.parametrize("basis", test_bases, ids=show_basis_params) +class TestFBBasis2D(UniversalBasisMixin, Steerable2DMixin): + seed = 9161341 - def _testElement(self, ell, k, sgn): + def _testElement(self, basis, ell, k, sgn): # This is covered by the isotropic test. assert ell > 0 - indices = self.basis.indices() + indices = basis.indices() ells = indices["ells"] sgns = indices["sgns"] ks = indices["ks"] - g2d = grid_2d(self.L, dtype=self.dtype) + g2d = grid_2d(basis.nres, dtype=basis.dtype) mask = g2d["r"] < 1 - r0 = self.basis.r0[ell][k] + r0 = basis.r0[ell][k] - im = np.zeros((self.L, self.L), dtype=self.dtype) + im = np.zeros((basis.nres, basis.nres), dtype=basis.dtype) im[mask] = jv(ell, g2d["r"][mask] * r0) - im *= np.sqrt(2**2 / self.L**2) + im *= np.sqrt(2**2 / basis.nres**2) im *= 1 / (np.sqrt(np.pi) * np.abs(jv(ell + 1, r0))) if sgn == 1: @@ -65,85 +52,85 @@ def _testElement(self, ell, k, sgn): else: im *= np.sqrt(2) * np.sin(ell * g2d["phi"]) - coef_ref = np.zeros(self.basis.count, dtype=self.dtype) + coef_ref = np.zeros(basis.count, dtype=basis.dtype) coef_ref[(ells == ell) & (sgns == sgn) & (ks == k)] = 1 - im_ref = self.basis.evaluate(coef_ref) + im_ref = basis.evaluate(coef_ref) - coef = self.basis.expand(im) + coef = basis.expand(im) # TODO: These tolerances should be tighter. - self.assertTrue(np.allclose(im, im_ref.asnumpy(), atol=1e-4)) - self.assertTrue(np.allclose(coef, coef_ref, atol=1e-4)) + assert np.allclose(im, im_ref.asnumpy(), atol=1e-4) + assert np.allclose(coef, coef_ref, atol=1e-4) - def testElements(self): + def testElements(self, basis): ells = [1, 1, 1, 1] ks = [1, 2, 1, 2] sgns = [-1, -1, 1, 1] for ell, k, sgn in zip(ells, ks, sgns): - self._testElement(ell, k, sgn) + self._testElement(basis, ell, k, sgn) - def testComplexCoversion(self): - x = Image(randn(*self.basis.sz, seed=self.seed), dtype=self.dtype) + def testComplexCoversion(self, basis): + x = Image(randn(*basis.sz, seed=self.seed), dtype=basis.dtype) # Express in an FB basis - v1 = self.basis.expand(x) + v1 = basis.expand(x) # Convert real FB coef to complex coef, - cv = self.basis.to_complex(v1) + cv = basis.to_complex(v1) # then convert back to real coef representation. - v2 = self.basis.to_real(cv) + v2 = basis.to_real(cv) # The round trip should be equivalent up to machine precision - self.assertTrue(np.allclose(v1, v2)) + assert np.allclose(v1, v2) - def testComplexCoversionErrorsToComplex(self): - x = randn(*self.basis.sz, seed=self.seed) + def testComplexCoversionErrorsToComplex(self, basis): + x = randn(*basis.sz, seed=self.seed) # Express in an FB basis - v1 = self.basis.expand(x.astype(self.dtype)) + v1 = basis.expand(x.astype(basis.dtype)) # Test catching Errors with raises(TypeError): # Pass complex into `to_complex` - _ = self.basis.to_complex(v1.astype(np.complex64)) + _ = basis.to_complex(v1.astype(np.complex64)) # Test casting case, where basis and coef don't match - if self.basis.dtype == np.float32: + if basis.dtype == np.float32: test_dtype = np.float64 - elif self.basis.dtype == np.float64: + elif basis.dtype == np.float64: test_dtype = np.float32 # Result should be same precision as coef input, just complex. result_dtype = complex_type(test_dtype) - v3 = self.basis.to_complex(v1.astype(test_dtype)) - self.assertTrue(v3.dtype == result_dtype) + v3 = basis.to_complex(v1.astype(test_dtype)) + assert v3.dtype == result_dtype # Try 0d vector, should not crash. - _ = self.basis.to_complex(v1.reshape(-1)) + _ = basis.to_complex(v1.reshape(-1)) - def testComplexCoversionErrorsToReal(self): - x = randn(*self.basis.sz, seed=self.seed) + def testComplexCoversionErrorsToReal(self, basis): + x = randn(*basis.sz, seed=self.seed) # Express in an FB basis - cv1 = self.basis.to_complex(self.basis.expand(x.astype(self.dtype))) + cv1 = basis.to_complex(basis.expand(x.astype(basis.dtype))) # Test catching Errors with raises(TypeError): # Pass real into `to_real` - _ = self.basis.to_real(cv1.real.astype(np.float32)) + _ = basis.to_real(cv1.real.astype(np.float32)) # Test casting case, where basis and coef precision don't match - if self.basis.dtype == np.float32: + if basis.dtype == np.float32: test_dtype = np.complex128 - elif self.basis.dtype == np.float64: + elif basis.dtype == np.float64: test_dtype = np.complex64 # Result should be same precision as coef input, just real. result_dtype = real_type(test_dtype) - v3 = self.basis.to_real(cv1.astype(test_dtype)) - self.assertTrue(v3.dtype == result_dtype) + v3 = basis.to_real(cv1.astype(test_dtype)) + assert v3.dtype == result_dtype # Try a 0d vector, should not crash. - _ = self.basis.to_real(cv1.reshape(-1)) + _ = basis.to_real(cv1.reshape(-1)) diff --git a/tests/test_FBbasis3D.py b/tests/test_FBbasis3D.py index 970c475ab7..251adbc80d 100644 --- a/tests/test_FBbasis3D.py +++ b/tests/test_FBbasis3D.py @@ -1,374 +1,361 @@ import os.path -from unittest import TestCase import numpy as np +import pytest from aspire.basis import FBBasis3D from aspire.utils import utest_tolerance from aspire.volume import Volume -from ._basis_util import UniversalBasisMixin +from ._basis_util import UniversalBasisMixin, basis_params_3d, show_basis_params DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +test_bases = [FBBasis3D(L, dtype=dtype) for L, dtype in basis_params_3d] -class FBBasis3DTestCase(TestCase, UniversalBasisMixin): - def setUp(self): - self.L = 8 - self.dtype = np.float32 - self.basis = FBBasis3D((self.L, self.L, self.L), dtype=self.dtype) - def tearDown(self): - pass +@pytest.mark.parametrize("basis", test_bases, ids=show_basis_params) +class TestFBBasis3D(UniversalBasisMixin): + def testFBBasis3DIndices(self, basis): + indices = basis.indices() - def testFBBasis3DIndices(self): - indices = self.basis.indices() - - self.assertTrue( - np.allclose( - indices["ells"], - [ - 0.0, - 0.0, - 0.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - ], - ) + assert np.allclose( + indices["ells"], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + ], ) - self.assertTrue( - np.allclose( - indices["ms"], - [ - 0.0, - 0.0, - 0.0, - -1.0, - -1.0, - -1.0, - 0.0, - 0.0, - 0.0, - 1.0, - 1.0, - 1.0, - -2.0, - -2.0, - -2.0, - -1.0, - -1.0, - -1.0, - 0.0, - 0.0, - 0.0, - 1.0, - 1.0, - 1.0, - 2.0, - 2.0, - 2.0, - -3.0, - -3.0, - -2.0, - -2.0, - -1.0, - -1.0, - 0.0, - 0.0, - 1.0, - 1.0, - 2.0, - 2.0, - 3.0, - 3.0, - -4.0, - -4.0, - -3.0, - -3.0, - -2.0, - -2.0, - -1.0, - -1.0, - 0.0, - 0.0, - 1.0, - 1.0, - 2.0, - 2.0, - 3.0, - 3.0, - 4.0, - 4.0, - -5.0, - -4.0, - -3.0, - -2.0, - -1.0, - 0.0, - 1.0, - 2.0, - 3.0, - 4.0, - 5.0, - -6.0, - -5.0, - -4.0, - -3.0, - -2.0, - -1.0, - 0.0, - 1.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - -7.0, - -6.0, - -5.0, - -4.0, - -3.0, - -2.0, - -1.0, - 0.0, - 1.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - 7.0, - ], - ) + assert np.allclose( + indices["ms"], + [ + 0.0, + 0.0, + 0.0, + -1.0, + -1.0, + -1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + -2.0, + -2.0, + -2.0, + -1.0, + -1.0, + -1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 2.0, + 2.0, + 2.0, + -3.0, + -3.0, + -2.0, + -2.0, + -1.0, + -1.0, + 0.0, + 0.0, + 1.0, + 1.0, + 2.0, + 2.0, + 3.0, + 3.0, + -4.0, + -4.0, + -3.0, + -3.0, + -2.0, + -2.0, + -1.0, + -1.0, + 0.0, + 0.0, + 1.0, + 1.0, + 2.0, + 2.0, + 3.0, + 3.0, + 4.0, + 4.0, + -5.0, + -4.0, + -3.0, + -2.0, + -1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + -6.0, + -5.0, + -4.0, + -3.0, + -2.0, + -1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + -7.0, + -6.0, + -5.0, + -4.0, + -3.0, + -2.0, + -1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + ], ) - self.assertTrue( - np.allclose( - indices["ks"], - [ - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0, - ], - ) + assert np.allclose( + indices["ks"], + [ + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0, + ], ) - def testFBBasis3DNorms(self): - norms = self.basis.norms() - self.assertTrue( - np.allclose( - norms, - [ - 1.80063263231421, - 0.900316316157109, - 0.600210877438065, - 1.22885897287928, - 0.726196138639673, - 0.516613361675378, - 0.936477951517100, - 0.610605075148750, - 0.454495363516488, - 0.756963071176142, - 0.527618747123993, - 0.635005913075500, - 0.464867421846148, - 0.546574142892508, - 0.479450758110826, - 0.426739123914569, - ], - ) + def testFBBasis3DNorms(self, basis): + norms = basis.norms() + assert np.allclose( + norms, + [ + 1.80063263231421, + 0.900316316157109, + 0.600210877438065, + 1.22885897287928, + 0.726196138639673, + 0.516613361675378, + 0.936477951517100, + 0.610605075148750, + 0.454495363516488, + 0.756963071176142, + 0.527618747123993, + 0.635005913075500, + 0.464867421846148, + 0.546574142892508, + 0.479450758110826, + 0.426739123914569, + ], ) - def testFBBasis3DEvaluate(self): + def testFBBasis3DEvaluate(self, basis): coeffs = np.array( [ 1.07338590e-01, @@ -470,238 +457,232 @@ def testFBBasis3DEvaluate(self): -9.82705453e-04, 6.46337066e-05, ], - dtype=self.dtype, + dtype=basis.dtype, ) - result = self.basis.evaluate(coeffs) + result = basis.evaluate(coeffs) - self.assertTrue( - np.allclose( - result.asnumpy(), - np.load(os.path.join(DATA_DIR, "hbbasis_evaluation_8_8_8.npy")).T, - atol=utest_tolerance(self.dtype), - ) + assert np.allclose( + result.asnumpy(), + np.load(os.path.join(DATA_DIR, "hbbasis_evaluation_8_8_8.npy")).T, + atol=utest_tolerance(basis.dtype), ) - def testFBBasis3DEvaluate_t(self): + def testFBBasis3DEvaluate_t(self, basis): v = np.load(os.path.join(DATA_DIR, "hbbasis_coefficients_8_8_8.npy")).T - v = Volume(v.astype(self.dtype)) - result = self.basis.evaluate_t(v) + v = Volume(v.astype(basis.dtype)) + result = basis.evaluate_t(v) - self.assertTrue( - np.allclose( - result, - [ - 1.07338590e-01, - 1.23690941e-01, - 6.44482039e-03, - -5.40484306e-02, - -4.85304586e-02, - 1.09852144e-02, - 3.87838396e-02, - 3.43796455e-02, - -6.43284705e-03, - -2.86677145e-02, - -1.42313328e-02, - -2.25684091e-03, - -3.31840727e-02, - -2.59706174e-03, - -5.91919887e-04, - -9.97433028e-03, - 9.19123928e-04, - 1.19891589e-03, - 7.49154982e-03, - 6.18865229e-03, - -8.13265715e-04, - -1.30715655e-02, - -1.44160603e-02, - 2.90379956e-03, - 2.37066082e-02, - 4.88805735e-03, - 1.47870707e-03, - 7.63376018e-03, - -5.60619559e-03, - 1.05165081e-02, - 3.30510143e-03, - -3.48652120e-03, - -4.23228797e-04, - 1.40484061e-02, - 1.42914291e-03, - -1.28129504e-02, - 2.19868825e-03, - -6.30835037e-03, - 1.18524223e-03, - -2.97855052e-02, - 1.15491057e-03, - -8.27947006e-03, - 3.45442781e-03, - -4.72868856e-03, - 2.66615329e-03, - -7.87929790e-03, - 8.84126590e-04, - 1.59402808e-03, - -9.06854048e-05, - -8.79119004e-03, - 1.76449039e-03, - -1.36414673e-02, - 1.56793855e-03, - 1.44708445e-02, - -2.55974802e-03, - 5.38506357e-03, - -3.24188673e-03, - 4.81582945e-04, - 7.74260101e-05, - 5.48772082e-03, - 1.92058500e-03, - -4.63538896e-03, - -2.02735133e-03, - 3.67592386e-03, - 7.23486969e-04, - 1.81838422e-03, - 1.78793284e-03, - -8.01474060e-03, - -8.54007528e-03, - 1.96353845e-03, - -2.16254252e-03, - -3.64243996e-05, - -2.27329863e-03, - 1.11424393e-03, - -1.39389189e-03, - 2.57787159e-04, - 3.66918811e-03, - 1.31477774e-03, - 6.82220128e-04, - 1.41822851e-03, - -1.89476924e-03, - -6.43966255e-05, - -7.87888465e-04, - -6.99459279e-04, - 1.08918981e-03, - 2.25264584e-03, - -1.43651015e-04, - 7.68377620e-04, - 5.05955256e-04, - 2.66936132e-06, - 2.24934884e-03, - 6.70529439e-04, - 4.81121742e-04, - -6.40789745e-05, - -3.35915672e-04, - -7.98651783e-04, - -9.82705453e-04, - 6.46337066e-05, - ], - atol=utest_tolerance(self.dtype), - ) + assert np.allclose( + result, + [ + 1.07338590e-01, + 1.23690941e-01, + 6.44482039e-03, + -5.40484306e-02, + -4.85304586e-02, + 1.09852144e-02, + 3.87838396e-02, + 3.43796455e-02, + -6.43284705e-03, + -2.86677145e-02, + -1.42313328e-02, + -2.25684091e-03, + -3.31840727e-02, + -2.59706174e-03, + -5.91919887e-04, + -9.97433028e-03, + 9.19123928e-04, + 1.19891589e-03, + 7.49154982e-03, + 6.18865229e-03, + -8.13265715e-04, + -1.30715655e-02, + -1.44160603e-02, + 2.90379956e-03, + 2.37066082e-02, + 4.88805735e-03, + 1.47870707e-03, + 7.63376018e-03, + -5.60619559e-03, + 1.05165081e-02, + 3.30510143e-03, + -3.48652120e-03, + -4.23228797e-04, + 1.40484061e-02, + 1.42914291e-03, + -1.28129504e-02, + 2.19868825e-03, + -6.30835037e-03, + 1.18524223e-03, + -2.97855052e-02, + 1.15491057e-03, + -8.27947006e-03, + 3.45442781e-03, + -4.72868856e-03, + 2.66615329e-03, + -7.87929790e-03, + 8.84126590e-04, + 1.59402808e-03, + -9.06854048e-05, + -8.79119004e-03, + 1.76449039e-03, + -1.36414673e-02, + 1.56793855e-03, + 1.44708445e-02, + -2.55974802e-03, + 5.38506357e-03, + -3.24188673e-03, + 4.81582945e-04, + 7.74260101e-05, + 5.48772082e-03, + 1.92058500e-03, + -4.63538896e-03, + -2.02735133e-03, + 3.67592386e-03, + 7.23486969e-04, + 1.81838422e-03, + 1.78793284e-03, + -8.01474060e-03, + -8.54007528e-03, + 1.96353845e-03, + -2.16254252e-03, + -3.64243996e-05, + -2.27329863e-03, + 1.11424393e-03, + -1.39389189e-03, + 2.57787159e-04, + 3.66918811e-03, + 1.31477774e-03, + 6.82220128e-04, + 1.41822851e-03, + -1.89476924e-03, + -6.43966255e-05, + -7.87888465e-04, + -6.99459279e-04, + 1.08918981e-03, + 2.25264584e-03, + -1.43651015e-04, + 7.68377620e-04, + 5.05955256e-04, + 2.66936132e-06, + 2.24934884e-03, + 6.70529439e-04, + 4.81121742e-04, + -6.40789745e-05, + -3.35915672e-04, + -7.98651783e-04, + -9.82705453e-04, + 6.46337066e-05, + ], + atol=utest_tolerance(basis.dtype), ) - def testFBBasis3DExpand(self): + def testFBBasis3DExpand(self, basis): v = np.load(os.path.join(DATA_DIR, "hbbasis_coefficients_8_8_8.npy")).T - v = Volume(v.astype(self.dtype)) - result = self.basis.expand(v) + v = Volume(v.astype(basis.dtype)) + result = basis.expand(v) - self.assertTrue( - np.allclose( - result, - [ - +0.10743660, - +0.12346847, - +0.00684837, - -0.05410818, - -0.04840195, - +0.01071116, - +0.03878536, - +0.03437083, - -0.00638332, - -0.02865552, - -0.01425294, - -0.00223313, - -0.03317134, - -0.00261654, - -0.00056954, - -0.00997264, - +0.00091569, - +0.00123042, - +0.00754713, - +0.00606669, - -0.00043233, - -0.01306626, - -0.01443522, - +0.00301968, - +0.02375521, - +0.00477979, - +0.00166319, - +0.00780333, - -0.00601982, - +0.01052385, - +0.00328666, - -0.00336805, - -0.00070688, - +0.01409127, - +0.00127259, - -0.01289172, - +0.00234488, - -0.00630249, - +0.00117541, - -0.02974037, - +0.00108834, - -0.00823955, - +0.00340772, - -0.00471875, - +0.00266391, - -0.00789639, - +0.00093529, - +0.00160710, - -0.00011925, - -0.00817443, - +0.00046713, - -0.01357463, - +0.00145920, - +0.01452459, - -0.00267202, - +0.00535952, - -0.00322100, - +0.00092083, - -0.00075300, - +0.00509418, - +0.00193687, - -0.00483399, - -0.00204537, - +0.00338492, - +0.00111248, - +0.00194841, - +0.00174416, - -0.00814324, - -0.00839777, - +0.00199974, - -0.00196156, - -0.00014695, - -0.00245317, - +0.00109957, - -0.00146145, - +0.00015149, - +0.00415232, - +0.00121810, - +0.00066095, - +0.00166167, - -0.00231911, - -0.00025819, - -0.00086808, - -0.00074656, - +0.00110445, - +0.00285573, - -0.00014959, - +0.00093241, - +0.00051144, - +0.00004805, - +0.00250166, - +0.00059104, - +0.00066592, - +0.00019188, - -0.00079074, - -0.00068995, - -0.00087668, - +0.00052913, - ], - atol=utest_tolerance(self.dtype), - ) + assert np.allclose( + result, + [ + +0.10743660, + +0.12346847, + +0.00684837, + -0.05410818, + -0.04840195, + +0.01071116, + +0.03878536, + +0.03437083, + -0.00638332, + -0.02865552, + -0.01425294, + -0.00223313, + -0.03317134, + -0.00261654, + -0.00056954, + -0.00997264, + +0.00091569, + +0.00123042, + +0.00754713, + +0.00606669, + -0.00043233, + -0.01306626, + -0.01443522, + +0.00301968, + +0.02375521, + +0.00477979, + +0.00166319, + +0.00780333, + -0.00601982, + +0.01052385, + +0.00328666, + -0.00336805, + -0.00070688, + +0.01409127, + +0.00127259, + -0.01289172, + +0.00234488, + -0.00630249, + +0.00117541, + -0.02974037, + +0.00108834, + -0.00823955, + +0.00340772, + -0.00471875, + +0.00266391, + -0.00789639, + +0.00093529, + +0.00160710, + -0.00011925, + -0.00817443, + +0.00046713, + -0.01357463, + +0.00145920, + +0.01452459, + -0.00267202, + +0.00535952, + -0.00322100, + +0.00092083, + -0.00075300, + +0.00509418, + +0.00193687, + -0.00483399, + -0.00204537, + +0.00338492, + +0.00111248, + +0.00194841, + +0.00174416, + -0.00814324, + -0.00839777, + +0.00199974, + -0.00196156, + -0.00014695, + -0.00245317, + +0.00109957, + -0.00146145, + +0.00015149, + +0.00415232, + +0.00121810, + +0.00066095, + +0.00166167, + -0.00231911, + -0.00025819, + -0.00086808, + -0.00074656, + +0.00110445, + +0.00285573, + -0.00014959, + +0.00093241, + +0.00051144, + +0.00004805, + +0.00250166, + +0.00059104, + +0.00066592, + +0.00019188, + -0.00079074, + -0.00068995, + -0.00087668, + +0.00052913, + ], + atol=utest_tolerance(basis.dtype), ) diff --git a/tests/test_FFBbasis2D.py b/tests/test_FFBbasis2D.py index 156906652a..a81962a882 100644 --- a/tests/test_FFBbasis2D.py +++ b/tests/test_FFBbasis2D.py @@ -1,9 +1,8 @@ import logging import os.path -from unittest import TestCase import numpy as np -from parameterized import parameterized_class +import pytest from scipy.special import jv from aspire.basis import FFBBasis2D @@ -12,54 +11,43 @@ from aspire.utils.misc import grid_2d from aspire.volume import Volume -from ._basis_util import Steerable2DMixin, UniversalBasisMixin +from ._basis_util import Steerable2DMixin, UniversalBasisMixin, basis_params_2d logger = logging.getLogger(__name__) DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +# Create a test Basis object for each combination of parameters we want to test +test_bases = [FFBBasis2D(L, dtype=dtype) for L, dtype in basis_params_2d] -# NOTE: Default class values (ie. L=8, dtype=np.float32) are listed here and below to -# to be picked up by TestCase. This means the default values are tested twice by the Mixins. -@parameterized_class( - ("L", "dtype"), - [ - (8, np.float32), - (8, np.float64), - (16, np.float32), - (16, np.float64), - (32, np.float32), - (32, np.float64), - ], -) -class FFBBasis2DTestCase(TestCase, Steerable2DMixin, UniversalBasisMixin): - L = 8 - dtype = np.float32 - - def setUp(self): - self.basis = FFBBasis2D((self.L, self.L), dtype=self.dtype) - self.seed = 9161341 - - def tearDown(self): - pass - - def _testElement(self, ell, k, sgn): - indices = self.basis.indices() + +def show_basis_params(basis): + # print descriptive test name for parametrized test + # run pytest with option -rA to see explicitly + return f"{basis.nres}-{basis.dtype}" + + +@pytest.mark.parametrize("basis", test_bases, ids=show_basis_params) +class TestFFBBasis2D(Steerable2DMixin, UniversalBasisMixin): + seed = 9161341 + + def _testElement(self, basis, ell, k, sgn): + indices = basis.indices() ells = indices["ells"] sgns = indices["sgns"] ks = indices["ks"] - g2d = grid_2d(self.L, dtype=self.dtype) + g2d = grid_2d(basis.nres, dtype=basis.dtype) mask = g2d["r"] < 1 - r0 = self.basis.r0[ell][k] + r0 = basis.r0[ell][k] # TODO: Figure out where these factors of 1 / 2 are coming from. # Intuitively, the grid should go from -L / 2 to L / 2, not -L / 2 to # L / 4. Furthermore, there's an extra factor of 1 / 2 in the # definition of `im` below that may be related. - r = g2d["r"] * self.L / 4 + r = g2d["r"] * basis.nres / 4 - im = np.zeros((self.L, self.L), dtype=self.dtype) + im = np.zeros((basis.nres, basis.nres), dtype=basis.dtype) im[mask] = ( (-1) ** k * np.sqrt(np.pi) @@ -73,29 +61,28 @@ def _testElement(self, ell, k, sgn): else: im *= np.sqrt(2) * np.sin(ell * g2d["phi"]) - coef_ref = np.zeros(self.basis.count, dtype=self.dtype) + coef_ref = np.zeros(basis.count, dtype=basis.dtype) coef_ref[(ells == ell) & (sgns == sgn) & (ks == k)] = 1 - im_ref = self.basis.evaluate(coef_ref).asnumpy()[0] + im_ref = basis.evaluate(coef_ref).asnumpy()[0] - coef = self.basis.expand(im) + coef = basis.expand(im) # NOTE: These tolerances are expected to be rather loose since the # above expression for `im` is derived from the analytical formulation # (eq. 6 in Zhao and Singer, 2013) and does not take into account - # discretization and other approximations. - self.assertTrue(np.allclose(im, im_ref, atol=1e-1)) - self.assertTrue(np.allclose(coef, coef_ref, atol=1e-1)) + assert np.allclose(im, im_ref, atol=1e-1) + assert np.allclose(coef, coef_ref, atol=1e-1) - def testElements(self): + def testElements(self, basis): ells = [1, 1, 1, 1] ks = [1, 2, 1, 2] sgns = [-1, -1, 1, 1] for ell, k, sgn in zip(ells, ks, sgns): - self._testElement(ell, k, sgn) + self._testElement(basis, ell, k, sgn) - def testRotate(self): + def testRotate(self, basis): # Now low res (8x8) had problems; # better with odd (7x7), but still not good. # We'll use a higher res test image. @@ -103,7 +90,7 @@ def testRotate(self): # Use a real data volume to generate a clean test image. v = Volume( np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")).astype( - self.dtype + basis.dtype ) ) src = Simulation(L=v.resolution, n=1, vols=v, dtype=v.dtype) @@ -132,15 +119,15 @@ def testRotate(self): y4 = basis.evaluate(v4) # Rotate 90 - self.assertTrue(np.allclose(y1[0], y2[0], atol=1e-5)) + assert np.allclose(y1[0], y2[0], atol=1e-5) # 2*pi Identity - self.assertTrue(np.allclose(x1[0], y3[0], atol=1e-5)) + assert np.allclose(x1[0], y3[0], atol=1e-5) # Refl (flipped using flipud) - self.assertTrue(np.allclose(np.flipud(x1[0]), y4[0], atol=1e-5)) + assert np.allclose(np.flipud(x1[0]), y4[0], atol=1e-5) - def testRotateComplex(self): + def testRotateComplex(self, basis): # Now low res (8x8) had problems; # better with odd (7x7), but still not good. # We'll use a higher res test image. @@ -148,7 +135,7 @@ def testRotateComplex(self): # Use a real data volume to generate a clean test image. v = Volume( np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")).astype( - self.dtype + basis.dtype ) ) src = Simulation(L=v.resolution, n=1, vols=v, dtype=v.dtype) @@ -177,15 +164,15 @@ def testRotateComplex(self): y4 = basis.evaluate(v4) # Rotate 90 - self.assertTrue(np.allclose(y1[0], y2[0], atol=1e-5)) + assert np.allclose(y1[0], y2[0], atol=1e-5) # 2*pi Identity - self.assertTrue(np.allclose(x1[0], y3[0], atol=1e-5)) + assert np.allclose(x1[0], y3[0], atol=1e-5) # Refl (flipped using flipud) - self.assertTrue(np.allclose(np.flipud(x1[0]), y4[0], atol=1e-5)) + assert np.allclose(np.flipud(x1[0]), y4[0], atol=1e-5) - def testShift(self): + def testShift(self, basis): """ Compare shifting using Image with shifting provided by the Basis. @@ -198,31 +185,31 @@ def testShift(self): # Construct some synthetic data v = Volume( np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")).astype( - self.dtype + basis.dtype ) - ).downsample(self.L) + ).downsample(basis.nres) - src = Simulation(L=self.L, n=n_img, vols=v, dtype=self.dtype) + src = Simulation(L=basis.nres, n=n_img, vols=v, dtype=basis.dtype) # Shift images using the Image method directly shifted_imgs = src.images[:n_img].shift(test_shift) # Convert original images to basis coefficients - f_imgs = self.basis.evaluate_t(src.images[:n_img]) + f_imgs = basis.evaluate_t(src.images[:n_img]) # Use the basis shift method - f_shifted_imgs = self.basis.shift(f_imgs, test_shift) + f_shifted_imgs = basis.shift(f_imgs, test_shift) # Compute diff between the shifted image sets - diff = shifted_imgs.asnumpy() - self.basis.evaluate(f_shifted_imgs).asnumpy() + diff = shifted_imgs.asnumpy() - basis.evaluate(f_shifted_imgs).asnumpy() # Compute mask to compare only the core of the shifted images - g = grid_2d(self.L, indexing="yx", normalized=False) - mask = g["r"] > self.L / 2 + g = grid_2d(basis.nres, indexing="yx", normalized=False) + mask = g["r"] > basis.nres / 2 # Masking values outside radius to 0 diff = np.where(mask, 0, diff) # Compute and check error rmse = np.sqrt(np.mean(np.square(diff), axis=(1, 2))) logger.info(f"RMSE shifted image diffs {rmse}") - self.assertTrue(np.allclose(rmse, 0, atol=1e-5)) + assert np.allclose(rmse, 0, atol=1e-5) diff --git a/tests/test_FFBbasis3D.py b/tests/test_FFBbasis3D.py index 4732fc3daf..5b3ce0d602 100644 --- a/tests/test_FFBbasis3D.py +++ b/tests/test_FFBbasis3D.py @@ -1,373 +1,360 @@ import os.path -from unittest import TestCase import numpy as np +import pytest from aspire.basis import FFBBasis3D from aspire.volume import Volume -from ._basis_util import UniversalBasisMixin +from ._basis_util import UniversalBasisMixin, basis_params_3d, show_basis_params DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +test_bases = [FFBBasis3D(L, dtype=dtype) for L, dtype in basis_params_3d] -class FFBBasis3DTestCase(TestCase, UniversalBasisMixin): - def setUp(self): - self.L = 8 - self.dtype = np.float32 - self.basis = FFBBasis3D((self.L, self.L, self.L), dtype=self.dtype) - def tearDown(self): - pass +@pytest.mark.parametrize("basis", test_bases, ids=show_basis_params) +class TestFFBBasis3D(UniversalBasisMixin): + def testFFBBasis3DIndices(self, basis): + indices = basis.indices() - def testFFBBasis3DIndices(self): - indices = self.basis.indices() - - self.assertTrue( - np.allclose( - indices["ells"], - [ - 0.0, - 0.0, - 0.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 1.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 2.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 3.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 4.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 5.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 6.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - 7.0, - ], - ) + assert np.allclose( + indices["ells"], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 1.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 2.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 3.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 4.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 5.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 6.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + 7.0, + ], ) - self.assertTrue( - np.allclose( - indices["ms"], - [ - 0.0, - 0.0, - 0.0, - -1.0, - -1.0, - -1.0, - 0.0, - 0.0, - 0.0, - 1.0, - 1.0, - 1.0, - -2.0, - -2.0, - -2.0, - -1.0, - -1.0, - -1.0, - 0.0, - 0.0, - 0.0, - 1.0, - 1.0, - 1.0, - 2.0, - 2.0, - 2.0, - -3.0, - -3.0, - -2.0, - -2.0, - -1.0, - -1.0, - 0.0, - 0.0, - 1.0, - 1.0, - 2.0, - 2.0, - 3.0, - 3.0, - -4.0, - -4.0, - -3.0, - -3.0, - -2.0, - -2.0, - -1.0, - -1.0, - 0.0, - 0.0, - 1.0, - 1.0, - 2.0, - 2.0, - 3.0, - 3.0, - 4.0, - 4.0, - -5.0, - -4.0, - -3.0, - -2.0, - -1.0, - 0.0, - 1.0, - 2.0, - 3.0, - 4.0, - 5.0, - -6.0, - -5.0, - -4.0, - -3.0, - -2.0, - -1.0, - 0.0, - 1.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - -7.0, - -6.0, - -5.0, - -4.0, - -3.0, - -2.0, - -1.0, - 0.0, - 1.0, - 2.0, - 3.0, - 4.0, - 5.0, - 6.0, - 7.0, - ], - ) + assert np.allclose( + indices["ms"], + [ + 0.0, + 0.0, + 0.0, + -1.0, + -1.0, + -1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + -2.0, + -2.0, + -2.0, + -1.0, + -1.0, + -1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 1.0, + 1.0, + 2.0, + 2.0, + 2.0, + -3.0, + -3.0, + -2.0, + -2.0, + -1.0, + -1.0, + 0.0, + 0.0, + 1.0, + 1.0, + 2.0, + 2.0, + 3.0, + 3.0, + -4.0, + -4.0, + -3.0, + -3.0, + -2.0, + -2.0, + -1.0, + -1.0, + 0.0, + 0.0, + 1.0, + 1.0, + 2.0, + 2.0, + 3.0, + 3.0, + 4.0, + 4.0, + -5.0, + -4.0, + -3.0, + -2.0, + -1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + -6.0, + -5.0, + -4.0, + -3.0, + -2.0, + -1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + -7.0, + -6.0, + -5.0, + -4.0, + -3.0, + -2.0, + -1.0, + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + ], ) - self.assertTrue( - np.allclose( - indices["ks"], - [ - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 2.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0, - ], - ) + assert np.allclose( + indices["ks"], + [ + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 2.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0, + ], ) - def testFFBBasis3DNorms(self): - norms = self.basis.norms() - self.assertTrue( - np.allclose( - norms, - [ - 1.80063263231421, - 0.900316316157109, - 0.600210877438065, - 1.22885897287928, - 0.726196138639673, - 0.516613361675378, - 0.936477951517100, - 0.610605075148750, - 0.454495363516488, - 0.756963071176142, - 0.527618747123993, - 0.635005913075500, - 0.464867421846148, - 0.546574142892508, - 0.479450758110826, - 0.426739123914569, - ], - ) + def testFFBBasis3DNorms(self, basis): + norms = basis.norms() + assert np.allclose( + norms, + [ + 1.80063263231421, + 0.900316316157109, + 0.600210877438065, + 1.22885897287928, + 0.726196138639673, + 0.516613361675378, + 0.936477951517100, + 0.610605075148750, + 0.454495363516488, + 0.756963071176142, + 0.527618747123993, + 0.635005913075500, + 0.464867421846148, + 0.546574142892508, + 0.479450758110826, + 0.426739123914569, + ], ) - def testFFBBasis3DEvaluate(self): + def testFFBBasis3DEvaluate(self, basis): coeffs = np.array( [ 1.07338590e-01, @@ -469,32 +456,33 @@ def testFFBBasis3DEvaluate(self): -9.82705453e-04, 6.46337066e-05, ], - dtype=self.dtype, + dtype=basis.dtype, ) - result = self.basis.evaluate(coeffs) + + result = basis.evaluate(coeffs) ref = np.load( os.path.join(DATA_DIR, "ffbbasis3d_xcoeff_out_8_8_8.npy") ).T # RCOPT - self.assertTrue(np.allclose(result, ref, atol=1e-2)) + assert np.allclose(result, ref, atol=1e-2) - def testFFBBasis3DEvaluate_t(self): + def testFFBBasis3DEvaluate_t(self, basis): x = np.load(os.path.join(DATA_DIR, "ffbbasis3d_xcoeff_in_8_8_8.npy")).T # RCOPT - x = x.astype(self.dtype, copy=False) - result = self.basis.evaluate_t(Volume(x)) + x = x.astype(basis.dtype, copy=False) + result = basis.evaluate_t(Volume(x)) ref = np.load(os.path.join(DATA_DIR, "ffbbasis3d_vcoeff_out_8_8_8.npy"))[..., 0] - self.assertTrue(np.allclose(result, ref, atol=1e-2)) + assert np.allclose(result, ref, atol=1e-2) - def testFFBBasis3DExpand(self): + def testFFBBasis3DExpand(self, basis): x = np.load(os.path.join(DATA_DIR, "ffbbasis3d_xcoeff_in_8_8_8.npy")).T # RCOPT - x = x.astype(self.dtype, copy=False) - result = self.basis.expand(x) + x = x.astype(basis.dtype, copy=False) + result = basis.expand(x) ref = np.load(os.path.join(DATA_DIR, "ffbbasis3d_vcoeff_out_exp_8_8_8.npy"))[ ..., 0 ] - self.assertTrue(np.allclose(result, ref, atol=1e-2)) + assert np.allclose(result, ref, atol=1e-2) diff --git a/tests/test_FPSWFbasis2D.py b/tests/test_FPSWFbasis2D.py index 7e63b76048..2ca6e3e213 100644 --- a/tests/test_FPSWFbasis2D.py +++ b/tests/test_FPSWFbasis2D.py @@ -1,45 +1,39 @@ import os.path -from unittest import TestCase import numpy as np +import pytest from aspire.basis import FPSWFBasis2D from aspire.image import Image -from ._basis_util import UniversalBasisMixin +from ._basis_util import UniversalBasisMixin, pswf_params_2d, show_basis_params DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +test_bases = [FPSWFBasis2D(L, dtype=dtype) for L, dtype in pswf_params_2d] -class FPSWFBasis2DTestCase(TestCase, UniversalBasisMixin): - def setUp(self): - self.L = 8 - self.dtype = np.float64 - self.basis = FPSWFBasis2D((self.L, self.L), 1.0, 1.0, dtype=self.dtype) - def tearDown(self): - pass - - def testFPSWFBasis2DEvaluate_t(self): +@pytest.mark.parametrize("basis", test_bases, ids=show_basis_params) +class TestFPSWFBasis2D(UniversalBasisMixin): + def testFPSWFBasis2DEvaluate_t(self, basis): img_ary = np.load( os.path.join(DATA_DIR, "ffbbasis2d_xcoeff_in_8_8.npy") ).T # RCOPT images = Image(img_ary) - result = self.basis.evaluate_t(images) + result = basis.evaluate_t(images) coeffs = np.load( os.path.join(DATA_DIR, "pswf2d_vcoeffs_out_8_8.npy") ).T # RCOPT # make sure both real and imaginary parts are consistent. - self.assertTrue( - np.allclose(np.real(result), np.real(coeffs)) - and np.allclose(np.imag(result) * 1j, np.imag(coeffs) * 1j) + assert np.allclose(np.real(result), np.real(coeffs)) and np.allclose( + np.imag(result) * 1j, np.imag(coeffs) * 1j ) - def testFPSWFBasis2DEvaluate(self): + def testFPSWFBasis2DEvaluate(self, basis): coeffs = np.load( os.path.join(DATA_DIR, "pswf2d_vcoeffs_out_8_8.npy") ).T # RCOPT - result = self.basis.evaluate(coeffs) + result = basis.evaluate(coeffs) images = np.load(os.path.join(DATA_DIR, "pswf2d_xcoeff_out_8_8.npy")).T # RCOPT - self.assertTrue(np.allclose(result.asnumpy(), images)) + assert np.allclose(result.asnumpy(), images) diff --git a/tests/test_PSWFbasis2D.py b/tests/test_PSWFbasis2D.py index 18d49336e3..8908d3173b 100644 --- a/tests/test_PSWFbasis2D.py +++ b/tests/test_PSWFbasis2D.py @@ -1,48 +1,41 @@ import os.path -from unittest import TestCase import numpy as np +import pytest from aspire.basis import PSWFBasis2D from aspire.image import Image -from ._basis_util import UniversalBasisMixin +from ._basis_util import UniversalBasisMixin, pswf_params_2d, show_basis_params DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +test_bases = [PSWFBasis2D(L, dtype=dtype) for L, dtype in pswf_params_2d] -class PSWFBasis2DTestCase(TestCase, UniversalBasisMixin): - def setUp(self): - self.basis = PSWFBasis2D((8, 8), 1.0, 1.0) - self.L = 8 - self.dtype = np.float64 - self.basis = PSWFBasis2D((self.L, self.L), 1.0, 1.0, dtype=self.dtype) - def tearDown(self): - pass - - def testPSWFBasis2DEvaluate_t(self): +@pytest.mark.parametrize("basis", test_bases, ids=show_basis_params) +class TestPSWFBasis2D(UniversalBasisMixin): + def testPSWFBasis2DEvaluate_t(self, basis): img_ary = np.load( os.path.join(DATA_DIR, "ffbbasis2d_xcoeff_in_8_8.npy") ).T # RCOPT images = Image(img_ary) - result = self.basis.evaluate_t(images) + result = basis.evaluate_t(images) coeffs = np.load( os.path.join(DATA_DIR, "pswf2d_vcoeffs_out_8_8.npy") ).T # RCOPT # make sure both real and imaginary parts are consistent. - self.assertTrue( - np.allclose(np.real(result), np.real(coeffs)) - and np.allclose(np.imag(result) * 1j, np.imag(coeffs) * 1j) + assert np.allclose(np.real(result), np.real(coeffs)) and np.allclose( + np.imag(result) * 1j, np.imag(coeffs) * 1j ) - def testPSWFBasis2DEvaluate(self): + def testPSWFBasis2DEvaluate(self, basis): coeffs = np.load( os.path.join(DATA_DIR, "pswf2d_vcoeffs_out_8_8.npy") ).T # RCOPT - result = self.basis.evaluate(coeffs) + result = basis.evaluate(coeffs) images = np.load(os.path.join(DATA_DIR, "pswf2d_xcoeff_out_8_8.npy")).T # RCOPT - self.assertTrue(np.allclose(result.asnumpy(), images)) + assert np.allclose(result.asnumpy(), images) diff --git a/tests/test_PolarBasis2D.py b/tests/test_PolarBasis2D.py index c431390c43..78c10c8596 100644 --- a/tests/test_PolarBasis2D.py +++ b/tests/test_PolarBasis2D.py @@ -516,3 +516,23 @@ def testPolarBasis2DAdjoint(self): ) self.assertTrue(np.isclose(lhs, rhs, atol=utest_tolerance(self.dtype))) + + # The following functions of UniversalBasisMixin expect a `basis` + # arg to be passed in. When PolarBasis2D tests are parametrized + # over size and dtype, this will be possible by passing in a basis + # automatically via @pytest.mark.parametrize() decorator on the test class + # + # See: test_FBBasis2D and test_FFBBasis2D + # + # for now, pass in the basis we are using + def testEvaluate(self): + super().testEvaluate(self.basis) + + def testEvaluate_t(self): + super().testEvaluate_t(self.basis) + + def testExpand(self): + super().testExpand(self.basis) + + def testInitWithIntSize(self): + super().testInitWithIntSize(self.basis) From 150266f8707a94c6db9685c7b48ed085492827ba Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 30 Jan 2023 11:12:10 -0500 Subject: [PATCH 084/424] Update pipeline_demo to use `from_snr` --- gallery/tutorials/pipeline_demo.py | 42 +++++++++++++++++++++--------- src/aspire/noise/noise.py | 2 ++ src/aspire/source/image.py | 12 +++++++-- tests/test_noise.py | 9 ++++--- 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 579a5d5bbd..aa86a38dad 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -13,6 +13,7 @@ # We begin by downloading a high resolution volume map of the 80S Ribosome, sourced from # EMDB: https://www.ebi.ac.uk/emdb/EMD-2660. +import logging import os import numpy as np @@ -67,19 +68,13 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Noise and CTF Filters # ^^^^^^^^^^^^^^^^^^^^^ -# Let's start by creating noise and CTF filters. The ``operators`` package contains a collection -# of filter classes that can be supplied to a ``Simulation``. We use ``ScalarFilter`` to create -# Gaussian white noise and ``RadialCTFFilter`` to generate a set of CTF filters with various defocus values. +# Let's start by CTF filters. The ``operators`` package contains a collection +# of filter classes that can be supplied to a ``Simulation``. +# We use ``RadialCTFFilter`` to generate a set of CTF filters with various defocus values. -# Create noise and CTF filters -from aspire.noise import WhiteNoiseAdder +# Create CTF filters from aspire.operators import RadialCTFFilter -# Gaussian noise filter. -# Note, the value supplied to the ``WhiteNoiseAdder``, chosen based on other parameters -# for this quick tutorial, can be changed to adjust the power of the noise. -noise_adder = WhiteNoiseAdder(var=1e-5) - # Radial CTF Filter defocus_min = 15000 # unit is angstroms defocus_max = 25000 @@ -95,21 +90,30 @@ def download(url, save_path, chunk_size=1024 * 1024): # Initialize Simulation Object # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # We feed our ``Volume`` and filters into ``Simulation`` to generate the dataset of images. +# When controlled white Gaussian noise is desired, ``Simulation.from_snr()`` +# can be used to generate a simulation data set around a specific SNR. +# +# Alternatively, users can bring their own images using an ``ArrayImageSource``, +# or define their own custom noise functions via ``Simulation(..., noise_adder=CustomNoiseAdder(...))``. from aspire.source import Simulation # set parameters res = 41 n_imgs = 2500 +# SNR target for white gaussian noise +# Note, the SNR value was chosen based on other parameters for this quick tutorial, +# and can be changed to adjust the power of the noise. +snr = 1.8 # For this ``Simulation`` we set all 2D offset vectors to zero, # but by default offset vectors will be randomly distributed. -src = Simulation( +src = Simulation.from_snr( + target_snr=snr, # desired SNR L=res, # resolution n=n_imgs, # number of projections vols=vol, # volume source offsets=np.zeros((n_imgs, 2)), # Default: images are randomly shifted - noise_adder=noise_adder, unique_filters=ctf_filters, ) @@ -186,6 +190,20 @@ def download(url, save_path, chunk_size=1024 * 1024): # work because we used ``TopClassSelector`` to classify our images. src.images[0:10].show() +# %% + +# Compare the estimated SNR before and after Class Averaging. +src_snr = src.estimate_snr() +avgs_snr = avgs.estimate_snr() +logging.info( + f"Simulation SNR (includes additonal offsets, amplitude, and CTF corruptions): {src_snr}" +) +logging.info(f"Class Averaged SNR: {avgs_snr}") +# Alternatively, estimate power in the areas outside the signal support, which should be noise. +src_noise_power = src.estimate_noise_power() +avgs_noise_power = avgs.estimate_noise_power() +logging.info(f"Simulation noise power: {src_noise_power}") +logging.info(f"Class Averaged noise power: {avgs_noise_power}") # %% # Orientation Estimation diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index 5fc8206563..ae75d275cb 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -127,6 +127,8 @@ def __init__(self, src, bgRadius=1, batchSize=512): :param src: A Source object which can give us images on demand :param bgRadius: The radius of the disk whose complement is used to estimate the noise. + Radius is relative proportion, where `1` represents + the radius of disc inscribing a `(src.L, src.L)` image. :param batchSize: The size of the batches in which to compute the variance estimate """ diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 5d70165f5e..df2bc3512d 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -910,7 +910,7 @@ def estimate_signal_power( signal_power = signal_estimate_method( sample_n=sample_n, support_radius=support_radius, batch_size=batch_size ) - if signal_estimate_method == "estimate_signal_mean": + if signal_power_method == "estimate_signal_mean": signal_power = signal_power**2 # mean**2 return signal_power @@ -936,7 +936,15 @@ def estimate_noise_power( :returns: Estimated noise energy (variance). """ - est = noise_estimator(src=self, bgRadius=support_radius, batchSize=batch_size) + if support_radius is None: + support_radius_proportion = 1 + else: + # Note, noise_estimator expects radius as proportion. + support_radius_proportion = support_radius / (self.L // 2) + + est = noise_estimator( + src=self, bgRadius=support_radius_proportion, batchSize=batch_size + ) return est.estimate() diff --git a/tests/test_noise.py b/tests/test_noise.py index c454ce597a..de41d16f64 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -204,13 +204,14 @@ def test_from_snr_white(sim_fixture, target_noise_variance): sample_n=sim_fixture.n - 1, ) - # Check we're within 1% of explicit target + # Check we're within 2% of explicit target + # Note achieved 1% when using var power method. logger.info( "sim_from_snr.noise_adder.noise_var, target_noise_variance =" f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" ) assert np.isclose( - sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 + sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.02 ) # Compare with WhiteNoiseEstimator consuming sim_from_snr @@ -221,8 +222,8 @@ def test_from_snr_white(sim_fixture, target_noise_variance): f" {est_noise_variance}, {target_noise_variance}" ) - # Check we're within 1% - assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.01) + # Check we're within 2% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.02) @pytest.mark.parametrize( From f28cda392796b315d0c67a5ff7052898939d8ba2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 30 Jan 2023 15:24:07 -0500 Subject: [PATCH 085/424] Change not very useful log messages to debug --- src/aspire/source/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index df2bc3512d..28e7a07358 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -829,7 +829,7 @@ def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=51 # Accumulate first and second moments first_moment += np.sum(images_masked) / _denom - logger.info(f"Source estimated signal mean: {first_moment}") + logger.debug(f"Source estimated signal mean: {first_moment}") return first_moment @@ -873,7 +873,7 @@ def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512 # E[X**2] - E[X]**2 estimated_var = second_moment - first_moment**2 - logger.info(f"Source estimated signal var: {estimated_var}") + logger.debug(f"Source estimated signal var: {estimated_var}") return estimated_var From bbfb2c87c8b6ea14b89dfc0ef414d1327dcbb76a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 30 Jan 2023 17:14:11 -0500 Subject: [PATCH 086/424] Set a good example SNR --- gallery/tutorials/pipeline_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index aa86a38dad..788e7010cf 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -104,7 +104,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # SNR target for white gaussian noise # Note, the SNR value was chosen based on other parameters for this quick tutorial, # and can be changed to adjust the power of the noise. -snr = 1.8 +snr = 1e-4 # For this ``Simulation`` we set all 2D offset vectors to zero, # but by default offset vectors will be randomly distributed. From cc734487010ece75d3709ca076c1156e6604d804 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 31 Jan 2023 09:00:12 -0500 Subject: [PATCH 087/424] Some review touchs up --- gallery/tutorials/pipeline_demo.py | 8 +++--- src/aspire/ctf/ctf_estimator.py | 3 +-- src/aspire/noise/noise.py | 4 +-- src/aspire/operators/__init__.py | 1 - src/aspire/operators/filters.py | 39 +----------------------------- src/aspire/source/simulation.py | 6 +++-- src/aspire/utils/__init__.py | 2 +- src/aspire/utils/misc.py | 19 +++++++++++++++ src/aspire/utils/units.py | 37 ++++++++++++++++++++++++++++ tests/test_imaging.py | 2 +- tests/test_noise.py | 5 ++-- 11 files changed, 73 insertions(+), 53 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 788e7010cf..18d9b105d8 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -95,6 +95,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # # Alternatively, users can bring their own images using an ``ArrayImageSource``, # or define their own custom noise functions via ``Simulation(..., noise_adder=CustomNoiseAdder(...))``. +# Examples can be found in ``tutorials/class_averaging.py`` and ``experiments/simulated_abinitio_pipeline.py``. from aspire.source import Simulation @@ -103,7 +104,7 @@ def download(url, save_path, chunk_size=1024 * 1024): n_imgs = 2500 # SNR target for white gaussian noise # Note, the SNR value was chosen based on other parameters for this quick tutorial, -# and can be changed to adjust the power of the noise. +# and can be changed to adjust the power of the additive noise. snr = 1e-4 # For this ``Simulation`` we set all 2D offset vectors to zero, @@ -196,10 +197,11 @@ def download(url, save_path, chunk_size=1024 * 1024): src_snr = src.estimate_snr() avgs_snr = avgs.estimate_snr() logging.info( - f"Simulation SNR (includes additonal offsets, amplitude, and CTF corruptions): {src_snr}" + f"Simulation SNR (includes any additonal offset, amplitude, and CTF corruptions): {src_snr}" ) logging.info(f"Class Averaged SNR: {avgs_snr}") -# Alternatively, estimate power in the areas outside the signal support, which should be noise. +# Alternatively, estimate power in the areas outside the signal support, +# which should be predominantly noise. src_noise_power = src.estimate_noise_power() avgs_noise_power = avgs.estimate_noise_power() logging.info(f"Simulation noise power: {src_noise_power}") diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 1f2f151fb7..752ad3f5b0 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -19,9 +19,8 @@ from aspire.basis.ffb_2d import FFBBasis2D from aspire.image import Image from aspire.numeric import fft -from aspire.operators import voltage_to_wavelength from aspire.storage import StarFile -from aspire.utils import abs2, complex_type, grid_1d, grid_2d +from aspire.utils import abs2, complex_type, grid_1d, grid_2d, voltage_to_wavelength logger = logging.getLogger(__name__) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index ae75d275cb..df31dd7bb4 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -61,8 +61,8 @@ def noise_var(self): Concrete implementations are expected to provide a method that returns the noise variance for the NoiseAdder. - Authors of `NoiseAdder`s are encouraged to consider any theoretically - superior methods of calculating noise variance directly, + Authors of `NoiseAdder`s are encouraged to consider any relevant + methods of calculating noise variance from theory. """ diff --git a/src/aspire/operators/__init__.py b/src/aspire/operators/__init__.py index 8a1e9b7a1d..27789eaae8 100644 --- a/src/aspire/operators/__init__.py +++ b/src/aspire/operators/__init__.py @@ -14,6 +14,5 @@ ScaledFilter, ZeroFilter, evaluate_src_filters_on_grid, - voltage_to_wavelength, ) from .wemd import wemd_embed, wemd_norm diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index bc677207e9..cbd7cc50c7 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -1,52 +1,15 @@ import inspect import logging -import math import numpy as np from scipy.interpolate import RegularGridInterpolator -from aspire.utils import grid_2d +from aspire.utils import grid_2d, voltage_to_wavelength from aspire.utils.filter_to_fb_mat import filter_to_fb_mat logger = logging.getLogger(__name__) -def voltage_to_wavelength(voltage): - """ - Convert from electron voltage to wavelength. - - :param voltage: float, The electron voltage in kV. - :return: float, The electron wavelength in angstroms. - """ - # We use de Broglie's relativistic formula for wavelength given by: - # wavelength = h / np.sqrt(2 * m * q * V * (1 + q * V / (2 * m * c**2))), - # where - # h = float(6.62607015e-34) is Planck's constant - # q = float(1.602176634e-19) is elementary charge - # m = float(9.1093837015e-31) is electron mass - # c = float(299792458) is speed of light - - # We precalculate a = 1e10 * a / np.sqrt(2*m*q) and b = 1e6 * q / (2*m*c^2). - # 1e10 and 1e6 are conversions from meters to angstroms and volts to kilovolts, respectively. - a = float(12.264259661581491) - b = float(0.9784755917869367) - - return a / math.sqrt(voltage * 1e3 + b * voltage**2) - - -def wavelength_to_voltage(wavelength): - """ - Convert from electron voltage to wavelength. - - :param wavelength: float, The electron wavelength in angstroms. - :return: float, The electron voltage in kV. - """ - a = float(12.264259661581491) - b = float(0.9784755917869367) - - return (-1e3 + math.sqrt(1e6 + 4 * a**2 * b / wavelength**2)) / (2 * b) - - def evaluate_src_filters_on_grid(src): """ Given an ImageSource object, compute the source's unique filters diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 77a20e27b5..da63abaedb 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -485,7 +485,8 @@ def estimate_snr(self, *args, **kwargs): if self.noise_adder is None: return np.inf - # For SNR, Use the noise variance known from the noise_adder. + # For SNR of Simulations, use the theoretical noise variance + # known from the noise_adder instead of deriving from PSD. return super().estimate_snr(noise_power=self.noise_adder.noise_var) @classmethod @@ -521,6 +522,7 @@ def from_snr( # When a user supplies angles and requests `sample_n` subset of images # randomize the rotations for them to avoid as much bias as possible. + # The original rotations stored in `_rots` will be restored later. shuffle = (not sim._uniform_random_angles) and (sample_n is not None) if shuffle: # Store the original rotations. @@ -558,7 +560,7 @@ def estimate_psnr(self, sample_n=None, batch_size=512, units=None): where MSE is computed between `projections` and the resulting simulated `images`. PSNR is computed along the stack axis and an average - value across the sample is returned. + value across the stack sample is returned. Note that PSNR is inherently a poor metric for identical images. :param sample_n: Number of images used for estimate. diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index b3589a32b2..7d818a8c10 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -65,4 +65,4 @@ from .random import Random, choice, rand, randi, randn, random from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance -from .units import ratio_to_decibel +from .units import ratio_to_decibel, voltage_to_wavelength, wavelength_to_voltage diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index 754dc2379c..78dad7c84c 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -367,11 +367,30 @@ def cyclic_rotations(order, dtype=np.float64): # Potentially cache this in the future. def support_mask(L, support_radius=None, dtype=np.float64): + """ + Return a mask selecting values within `support_radius`. + + This mask is hard cutoff, boolean type. + For a soft cutoff, see `fuzzy_mask` + + Use for selecting signal. + Alternatively the mask inverse (~) can be used to select background. + Combinations can be used to create bands. + + :param L: Resolution in pixels. + :param support_radius: Radius of mask in pixels. + Defaults to L // 2. + :param dtype: Dtype used for mask construction and comparison. + :return: Boolean mask as (L,L) array. + """ + if support_radius is None: support_radius = L // 2 + elif support_radius == -1: # Disables mask, here to reduce code duplication. return np.full((L, L), fill_value=True, dtype=bool) + elif not 0 < support_radius <= L * np.sqrt(2): raise ValueError( "support_radius should be" diff --git a/src/aspire/utils/units.py b/src/aspire/utils/units.py index 16e951cf66..fd40864795 100644 --- a/src/aspire/utils/units.py +++ b/src/aspire/utils/units.py @@ -3,6 +3,7 @@ """ import logging +import math import numpy as np @@ -20,3 +21,39 @@ def ratio_to_decibel(p): """ return 10 * np.log10(p) + + +def voltage_to_wavelength(voltage): + """ + Convert from electron voltage to wavelength. + + :param voltage: float, The electron voltage in kV. + :return: float, The electron wavelength in angstroms. + """ + # We use de Broglie's relativistic formula for wavelength given by: + # wavelength = h / np.sqrt(2 * m * q * V * (1 + q * V / (2 * m * c**2))), + # where + # h = float(6.62607015e-34) is Planck's constant + # q = float(1.602176634e-19) is elementary charge + # m = float(9.1093837015e-31) is electron mass + # c = float(299792458) is speed of light + + # We precalculate a = 1e10 * a / np.sqrt(2*m*q) and b = 1e6 * q / (2*m*c^2). + # 1e10 and 1e6 are conversions from meters to angstroms and volts to kilovolts, respectively. + a = float(12.264259661581491) + b = float(0.9784755917869367) + + return a / math.sqrt(voltage * 1e3 + b * voltage**2) + + +def wavelength_to_voltage(wavelength): + """ + Convert from electron voltage to wavelength. + + :param wavelength: float, The electron wavelength in angstroms. + :return: float, The electron voltage in kV. + """ + a = float(12.264259661581491) + b = float(0.9784755917869367) + + return (-1e3 + math.sqrt(1e6 + 4 * a**2 * b / wavelength**2)) / (2 * b) diff --git a/tests/test_imaging.py b/tests/test_imaging.py index 0f2e8db047..cc39e480b8 100644 --- a/tests/test_imaging.py +++ b/tests/test_imaging.py @@ -1,7 +1,7 @@ from random import random from unittest import TestCase -from aspire.operators.filters import voltage_to_wavelength, wavelength_to_voltage +from aspire.utils import voltage_to_wavelength, wavelength_to_voltage class ImagingTestCase(TestCase): diff --git a/tests/test_noise.py b/tests/test_noise.py index de41d16f64..8f7708aa63 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -192,7 +192,7 @@ def test_from_snr_white(sim_fixture, target_noise_variance): # Attempt to create a new simulation at this `target_snr` # For unit testing, we will use `sim_fixture`'s volume, - # but the new Simulation instance should yield different projects. + # but the new Simulation instance should yield different projections. sim_from_snr = Simulation.from_snr( target_snr, vols=sim_fixture.vols, # Force the previously generated volume. @@ -205,7 +205,6 @@ def test_from_snr_white(sim_fixture, target_noise_variance): ) # Check we're within 2% of explicit target - # Note achieved 1% when using var power method. logger.info( "sim_from_snr.noise_adder.noise_var, target_noise_variance =" f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" @@ -309,7 +308,7 @@ def aniso_spectrum(x, y): # Attempt to create a new simulation at this `target_snr` # For unit testing, we will use `sim_fixture`'s volume, - # but the new Simulation instance should yield different projects. + # but the new Simulation instance should yield different projections. sim_from_snr = Simulation.from_snr( target_snr, vols=sim_fixture.vols, # Force the previously generated volume. From 18af2a6ea0a449d6a0c36add092a42225962a26b Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 31 Jan 2023 09:59:56 -0500 Subject: [PATCH 088/424] use synchronized_mean for outer product estimate. fix pf RCO. --- src/aspire/abinitio/commonline_cn.py | 122 +++++---------------------- tests/test_orient_symmetric.py | 63 +++----------- 2 files changed, 33 insertions(+), 152 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index eeec650c60..2f42437ff4 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -4,7 +4,7 @@ from numpy.linalg import norm from aspire.abinitio import CLSymmetryC3C4 -from aspire.utils import Rotation, anorm, cyclic_rotations, tqdm, trange +from aspire.utils import J_conjugate, Rotation, anorm, cyclic_rotations, tqdm, trange from aspire.utils.random import randn logger = logging.getLogger(__name__) @@ -70,15 +70,14 @@ def _estimate_relative_viewing_directions(self): n_theta_ijs = len(R_theta_ijs) # Generate shift phases. - r_max = pf.shape[0] + 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) all_shift_phases = shift_phases.T - # Transpose and reconstruct full polar Fourier for use in correlation. - pf = pf.T + # Reconstruct full polar Fourier for use in correlation. pf /= norm(pf, axis=2)[..., np.newaxis] # Normalize each ray. pf_full = np.concatenate((pf, np.conj(pf)), axis=1) @@ -125,11 +124,15 @@ def _estimate_relative_viewing_directions(self): logger.info("Computing pairwise likelihood.") n_vijs = n_img * (n_img - 1) // 2 vijs = np.zeros((n_vijs, 3, 3), dtype=self.dtype) - viis = np.zeros((n_img, 3, 3), dtype=self.dtype) + viis_sync = np.zeros((n_img, 3, 3), dtype=self.dtype) rots_symm = cyclic_rotations(self.order, self.dtype).matrices c = 0 - e1 = [1, 0, 0] - min_ii_norm = float("inf") * np.ones(n_img) + + # List of MeanOuterProductEstimator instances. + # + mean_est = [] + for _ in range(n_img): + mean_est.append(MeanOuterProductEstimator()) with tqdm(total=n_vijs) as pbar: for i in range(n_img): @@ -200,19 +203,11 @@ def _estimate_relative_viewing_directions(self): # Compute the estimate of vi*vi.T as given by j. vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) - - svals = np.linalg.svd(vii_j, compute_uv=False) - if np.linalg.norm(svals - e1, 2) < min_ii_norm[i]: - viis[i] = vii_j - min_ii_norm[i] = np.linalg.norm(svals - e1, 2) + mean_est[i].push(vii_j) # Compute the estimate of vj*vj.T as given by i. vjj_i = np.mean(opt_Rj_tilde.T @ rots_symm @ opt_Rj_tilde, axis=0) - - svals = np.linalg.svd(vjj_i, compute_uv=False) - if np.linalg.norm(svals - e1, 2) < min_ii_norm[j]: - viis[j] = vjj_i - min_ii_norm[j] = np.linalg.norm(svals - e1, 2) + mean_est[j].push(vjj_i) # Compute the estimate of vi*vj.T. vijs[c] = np.mean( @@ -223,7 +218,9 @@ def _estimate_relative_viewing_directions(self): c += 1 pbar.update() - return vijs, viis + viis_sync[i] = mean_est[i].synchronized_mean() + + return vijs, viis_sync def compute_scls_inds(self, Ri_cands): """ @@ -334,7 +331,7 @@ def generate_cand_rots_third_rows(self, legacy=True): return third_rows -class VeeOuterProductEstimator: +class MeanOuterProductEstimator: """ Incrementally accumulate outer product entries of unknown conjugation. """ @@ -343,49 +340,16 @@ class VeeOuterProductEstimator: # Then we can probably avoid numerical summing concerns without precomputing denom dtype = np.float64 - # conjugation - J = np.array([[0, 0, -1], [0, 0, -1], [-1, -1, 0]], dtype=np.float64) - - # Create a mask selecting elements unchanged by J - mask = J == 0 - mask_inverse = ~mask - def __init__(self): - # Create storage for non_negative (index 0) and negative_entries (index 1) - self.V_estimates = np.zeros((2, 3, 3), dtype=self.dtype) - self.counts = np.zeros((2, 3, 3), dtype=int) - # Create storage for J-synchronized estimates. - self.V_estimates_sync = np.zeros((3,3), dtype=self.dtype) + self.V_estimates_sync = np.zeros((3, 3), dtype=self.dtype) self.count = 0 - - # Might as well gather the second moment for var in case you need it later - self.V_estimates_moment2 = self.V_estimates.copy() def push(self, V): """ - Given V, accumulate entries into two running averages. + Given V, accumulate entries into a running sum of J-synchronized entries. """ - self.V_estimates[:, self.mask] += V[self.mask] - - # Parens are important here - non_negative_entries = (V >= 0) & self.mask_inverse - negative_entries = (V < 0) & self.mask_inverse - - self.V_estimates[0][non_negative_entries] += V[non_negative_entries] - self.V_estimates[1][negative_entries] += V[negative_entries] - - self.counts[:, self.mask] += 1 - self.counts[0][non_negative_entries] += 1 - self.counts[1][negative_entries] += 1 - - self.V_estimates_moment2[..., self.mask] += V[self.mask] ** 2 - self.V_estimates_moment2[0][non_negative_entries] += ( - V[non_negative_entries] ** 2 - ) - self.V_estimates_moment2[1][negative_entries] += V[negative_entries] ** 2 - # Accumulate synchronized entries to compute synchronized mean. if self.count == 0: self.V_estimates_sync += V @@ -396,57 +360,9 @@ def push(self, V): self.V_estimates_sync += J_conjugate(V) self.count += 1 - - def mean(self): - """ - Running mean. - """ - # note double sum and double count for `mask` elements cancel out - return np.sum(self.V_estimates, axis=0) / np.sum(self.counts, axis=0) def synchronized_mean(self): """ Calculate the mean of synchronized outer product estimates. """ - return self.V_estimate_sync / self.count - - def second_moment(self): - """ - Running second moment. - """ - # note double sum and double count for `mask` elements cancel out - return np.sum(self.V_estimates_moment2, axis=0) / np.sum(self.counts, axis=0) - - def variance(self): - """ - Running variance. - """ - return self.second_moment() - self.mean() ** 2 - - def median_sign_mean_estimate(self): - """ - Return the mean for the group of entries in V containing - the median value. - - Seperately computes running metrics (mean) for the group of - non_negative and negative entries. Keeps seperate counts - so we can compute an effective median sign estimate. - - """ - - # Find whether non negative or negative had the most entries - # This should effectively give the the group which has the same - # sign as median. - # Note on tie this code will return non_negative. - # Technically the effective median would be mean(group_means) in that case, - # but I don't think that logic is necessary yet. If needed we can add easily. - group_ind = np.argmax(self.counts, axis=0) - group_sum = np.take_along_axis(self.V_estimates, group_ind[np.newaxis], axis=0) - group_count = np.take_along_axis(self.counts, group_ind[np.newaxis], axis=0) - group_mean = group_sum / group_count - # group_moment2 = np.take_along_axis( - # self.V_estimates_moment2, group_ind[np.newaxis], axis=0 - # ) - # group_var = group_moment2 / group_count - group_mean**2 # might be interesting... - - return group_mean + return self.V_estimates_sync / self.count diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index d4f7367f0d..4b221de476 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -4,7 +4,7 @@ from numpy.linalg import det, norm from aspire.abinitio import CLSymmetryC3C4, CLSymmetryCn -from aspire.abinitio.commonline_cn import VeeOuterProductEstimator +from aspire.abinitio.commonline_cn import MeanOuterProductEstimator from aspire.source import Simulation from aspire.utils import Rotation from aspire.utils.coor_trans import ( @@ -35,11 +35,12 @@ # Method to instantiate a Simulation source and orientation estimation object. def source_orientation_objs(L, n_img, order, dtype): + seed = 1 vol = CnSymmetricVolume( L=L, C=1, order=order, - seed=0, + seed=seed, dtype=dtype, ).generate() @@ -50,7 +51,7 @@ def source_orientation_objs(L, n_img, order, dtype): dtype=dtype, vols=vol, C=1, - seed=123, + seed=seed, ) if order in [3, 4]: @@ -62,7 +63,7 @@ def source_orientation_objs(L, n_img, order, dtype): symmetry=f"C{order}", n_theta=360, max_shift=1 / L, # set to 1 pixel - seed=1, + seed=seed, ) return src, orient_est @@ -186,7 +187,7 @@ def testSelfRelativeRotations(L, order, dtype): assert mean_angular_distance < 5 -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) +@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4 + param_list_cn) def testRelativeViewingDirections(L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. @@ -290,6 +291,7 @@ def testRelativeViewingDirections(L, order, dtype): angle_tol = 2 * np.pi / 180 if order > 4: angle_tol = 6 * np.pi / 180 + assert angular_dist_vijs < angle_tol assert angular_dist_viis < angle_tol @@ -522,60 +524,23 @@ def buildOuterProducts(n_img, dtype): return vijs, viis, gt_vis -def test_vee_estimator_simple(): +def test_mean_estimator_simple(): """ - Manully run VeeOuterProductEstimator for prebaked inputs. + Manully run MeanOuterProductEstimator for prebaked inputs. """ - est = VeeOuterProductEstimator() + est = MeanOuterProductEstimator() + # Push two matrices with opposite signs. est.push(np.full((3, 3), -2, dtype=np.float64)) est.push(np.full((3, 3), 2, dtype=np.float64)) - assert np.allclose(est.mean(), np.full((3, 3), 0, dtype=np.float64)) - assert np.allclose(est.variance(), np.full((3, 3), 4, dtype=np.float64)) + # synchronized_mean will J-conjugate the second entry prior to averaging. assert np.allclose( - est.median_sign_mean_estimate(), np.array([[0, 0, 2], [0, 0, 2], [2, 2, 0]]) + est.synchronized_mean(), np.array([[0, 0, -2], [0, 0, -2], [-2, -2, 0]]) ) est.push(np.full((3, 3), -2, dtype=np.float64)) est.push(np.full((3, 3), -2, dtype=np.float64)) - assert np.allclose( - est.median_sign_mean_estimate(), - np.array([[-1, -1, -2], [-1, -1, -2], [-2, -2, -1]]), - ) - - -def test_vee_estimator_stat(): - """ - Tests incremental VeeOuterProductEstimator using random data, - comparing to global numpy arithmetic. - """ - - est = VeeOuterProductEstimator() - - n = 1000 - # Mix of pos and negative centers - centers = np.array([(i % 2) * 2 - 1 for i in range(1, 10)]) - V = np.array([np.random.normal(loc=c, scale=4, size=n) for c in centers]) - V = V.reshape(3, 3, n) - - for v in np.transpose(V, (2, 0, 1)): - est.push(v) - - assert np.allclose(est.mean(), np.mean(V, axis=2)) - assert np.allclose(est.variance(), np.var(V, axis=2)) - - res = np.empty((3, 3)) - # Find the mean of the entries matching sign of median - for i, j in [(0, 2), (1, 2), (2, 0), (2, 1)]: - entries = V[i, j] - group_selection = np.sign(np.median(entries)) == np.sign(entries) - res[i, j] = np.mean(entries[group_selection]) - - # These entries should match a global mean (unaffected by J) - for i, j in [(0, 0), (0, 1), (1, 0), (1, 1), (2, 2)]: - entries = V[i, j] - res[i, j] = np.mean(entries) - assert np.allclose(est.median_sign_mean_estimate(), res) + assert np.allclose(est.synchronized_mean(), np.full((3, 3), -1, dtype=np.float64)) From 06a8d859e91ac1951f5df0544bb09a92e1e3b3a8 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 31 Jan 2023 11:11:08 -0500 Subject: [PATCH 089/424] tolerance adjustment. parametrization clarifying comments. --- tests/test_orient_symmetric.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 4b221de476..ca1d4db961 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -17,6 +17,7 @@ from aspire.volume import CnSymmetricVolume # A set of these parameters are marked expensive to reduce testing time. +# Each tuple holds the parameters (resolution "L", cyclic order "order", dtype). param_list_c3_c4 = [ (44, 3, np.float32), (45, 4, np.float64), @@ -28,8 +29,10 @@ pytest.param(45, 3, np.float64, marks=pytest.mark.expensive), ] +# For testing Cn methods where n>4. param_list_cn = [ pytest.param(44, 5, np.float32, marks=pytest.mark.expensive), + pytest.param(45, 6, np.float64, marks=pytest.mark.expensive), ] @@ -282,9 +285,9 @@ def testRelativeViewingDirections(L, order, dtype): # ie. check that the svd is close to [1, 0, 0]. error_ij = np.linalg.norm(np.array([1, 0, 0], dtype=dtype) - sij, axis=1) error_ii = np.linalg.norm(np.array([1, 0, 0], dtype=dtype) - sii, axis=1) - assert np.max(error_ij) < 0.2 - assert np.max(error_ii) < 1e-5 - assert np.mean(error_ij) < 0.002 + assert np.max(error_ij) < 2e-1 + assert np.max(error_ii) < 5e-2 + assert np.mean(error_ij) < 2e-3 assert np.mean(error_ii) < 1e-5 # Check that the mean angular difference is within 2 degrees. From 57a914eba378f17c4a42a5b6ac178d65e51b0a69 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 1 Feb 2023 14:45:30 -0500 Subject: [PATCH 090/424] black 23.1.0 formatting updates. --- src/aspire/abinitio/commonline_c3_c4.py | 4 ++-- src/aspire/apple/picking.py | 1 - src/aspire/basis/ffb_3d.py | 1 - src/aspire/basis/fspca.py | 7 ++----- src/aspire/basis/steerable.py | 2 -- src/aspire/classification/averager2d.py | 2 -- src/aspire/covariance/covar2d.py | 1 - src/aspire/denoising/class_avg.py | 1 - src/aspire/image/image.py | 1 - src/aspire/numeric/complex_pca/validation.py | 1 - src/aspire/numeric/numpy.py | 1 - src/aspire/numeric/pyfftw_fft.py | 2 -- src/aspire/operators/filters.py | 1 - src/aspire/operators/wemd.py | 2 +- src/aspire/source/image.py | 2 -- src/aspire/utils/cell.py | 1 - tests/test_BlkDiagMatrix.py | 6 ++---- tests/test_adaptive_support.py | 1 - tests/test_apple.py | 1 - tests/test_averager2d.py | 7 ------- tests/test_mrc.py | 2 -- tests/test_orient_symmetric.py | 2 +- 22 files changed, 8 insertions(+), 41 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 03253d5799..b8325ff409 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -750,7 +750,7 @@ def _signs_times_v(self, vijs, vec): # condition. Finally, we the multiply the 'edge_signs' by the cooresponding entries of 'vec'. v = vijs new_vec = np.zeros_like(vec) - for (i, j, k) in triplets: + for i, j, k in triplets: ij = pairs_to_linear(n_img, i, j) jk = pairs_to_linear(n_img, j, k) ik = pairs_to_linear(n_img, i, k) @@ -846,7 +846,7 @@ def g_sync(rots, order, rots_gt): pairs = all_pairs(n_img) - for (i, j) in pairs: + for i, j in pairs: Ri = rots[i] Rj = rots[j] Rij = Ri.T @ Rj diff --git a/src/aspire/apple/picking.py b/src/aspire/apple/picking.py index 3dd6c04753..b28ded0b29 100644 --- a/src/aspire/apple/picking.py +++ b/src/aspire/apple/picking.py @@ -52,7 +52,6 @@ def __init__( response_thresh_norm_factor=20, conv_map_nthreads=4, ): - self.particle_size = int(particle_size / 2) self.max_size = int(max_size / 2) self.min_size = int(min_size / 2) diff --git a/src/aspire/basis/ffb_3d.py b/src/aspire/basis/ffb_3d.py index 0a4654c5db..6362a9a703 100644 --- a/src/aspire/basis/ffb_3d.py +++ b/src/aspire/basis/ffb_3d.py @@ -235,7 +235,6 @@ def _evaluate(self, v): sgns = (1, -1) for sgn in sgns: - end = np.size(u_even, 0) u_m_even = u_even[end - n_even_ell : end, :, self.ell_max + sgn * m, :] end = np.size(u_odd, 0) diff --git a/src/aspire/basis/fspca.py b/src/aspire/basis/fspca.py index f01cd704bb..31a926472a 100644 --- a/src/aspire/basis/fspca.py +++ b/src/aspire/basis/fspca.py @@ -193,7 +193,6 @@ def _compute_spca(self): eigval_index = 0 basis_inds = [] for angular_index, C_k in enumerate(self.covar_coef_est): - # # Eigen/SVD, covariance block C_k should be symmetric. eigvals_k, eigvecs_k = np.linalg.eigh(C_k) @@ -249,7 +248,6 @@ def _compute_spca(self): # Apply Data matrix batchwise num_batches = (self.src.n + self.batch_size - 1) // self.batch_size for i in range(num_batches): - # Compute the coefficients for this batch start = i * self.batch_size finish = min((i + 1) * self.batch_size, self.src.n) @@ -296,7 +294,6 @@ def _compute_spca(self): # on the correspong block. blk_spca_coef = np.empty_like(batch_coef) for angular_index, a_blk in enumerate(A): - # To compute new expansion coefficients using spca basis # we combine the basis coefs using the eigen decomposition. # Note image stack slow moving axis, otherwise this is just a @@ -392,7 +389,7 @@ def _get_compressed_indices(self): # instead of the larger component. # Note, that we only use OrderedDict for its key's (ie as an OrderedSet) ordered_components = OrderedDict() - for (k, q) in unsigned_components: + for k, q in unsigned_components: ordered_components.setdefault((k, q)) # inserts when not exists yet # Select the top n (k,q) pairs @@ -402,7 +399,7 @@ def _get_compressed_indices(self): pos_mask = self.basis._indices["sgns"] == 1 neg_mask = self.basis._indices["sgns"] == -1 compressed_indices = [] - for (k, q) in top_components: + for k, q in top_components: # Compute the locations of coefs we're interested in. k_maps = self.angular_indices == k q_maps = self.radial_indices == q diff --git a/src/aspire/basis/steerable.py b/src/aspire/basis/steerable.py index 8aaa67bd4e..3363f1cdf5 100644 --- a/src/aspire/basis/steerable.py +++ b/src/aspire/basis/steerable.py @@ -101,14 +101,12 @@ def calculate_bispectrum( logger.info(f"Calculating bispectrum matrix with shape {B.shape}.") for ind1 in range(self.complex_count): - k1 = angular_indices[ind1] if freq_cutoff and k1 > freq_cutoff: continue coef1 = complex_coef[ind1] for ind2 in range(self.complex_count): - k2 = angular_indices[ind2] if freq_cutoff and k2 > freq_cutoff: continue diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index ec3da077f0..f761b93cc2 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -312,7 +312,6 @@ def align(self, classes, reflections, basis_coefficients): correlations = np.empty(classes.shape, dtype=self.dtype) def _innerloop(k): - _correlations = np.empty((n_nbor, self.n_angles)) # Get the coefs for these neighbors if basis_coefficients is None: @@ -629,7 +628,6 @@ def average( b_avgs = np.empty((n_classes, self.composite_basis.count), dtype=self.src.dtype) def _innerloop(i): - # Get coefs in Composite_Basis if not provided as an argument. if coefs is None: # Retrieve relevant images directly from source. diff --git a/src/aspire/covariance/covar2d.py b/src/aspire/covariance/covar2d.py index 00f34355f8..d0b1507009 100644 --- a/src/aspire/covariance/covar2d.py +++ b/src/aspire/covariance/covar2d.py @@ -280,7 +280,6 @@ def identity(x): M = BlkDiagMatrix.zeros_like(ctf_fb[0]) for k in np.unique(ctf_idx[:]): - coeff_k = coeffs[ctf_idx == k].astype(self.dtype) weight = coeff_k.shape[0] / coeffs.shape[0] diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 681b834148..54672aa75d 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -17,7 +17,6 @@ def __init__(self, img_src, class_index): pass def class_averaging(self): - pass def output_images(self): diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index 5c823657cb..a337c37418 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -446,7 +446,6 @@ def show(self, columns=5, figsize=(20, 10), colorbar=True): # Create a context manager for altering warnings with catch_warnings(): - # Filter off specific warning. # sphinx-gallery overrides to `agg` backend, but doesn't handle warning. filterwarnings( diff --git a/src/aspire/numeric/complex_pca/validation.py b/src/aspire/numeric/complex_pca/validation.py index 1186379ab8..4b1b6993e3 100644 --- a/src/aspire/numeric/complex_pca/validation.py +++ b/src/aspire/numeric/complex_pca/validation.py @@ -176,7 +176,6 @@ def check_array( estimator=None, allow_complex=False, ): - """Input validation on an array, list, sparse matrix or similar. By default, the input is checked to be a non-empty 2D array containing diff --git a/src/aspire/numeric/numpy.py b/src/aspire/numeric/numpy.py index 77bc455561..3237c2c3ad 100644 --- a/src/aspire/numeric/numpy.py +++ b/src/aspire/numeric/numpy.py @@ -2,7 +2,6 @@ class Numpy: - asnumpy = staticmethod(lambda x: x) def __getattr__(self, item): diff --git a/src/aspire/numeric/pyfftw_fft.py b/src/aspire/numeric/pyfftw_fft.py index 2291189db3..c1b183108a 100644 --- a/src/aspire/numeric/pyfftw_fft.py +++ b/src/aspire/numeric/pyfftw_fft.py @@ -113,7 +113,6 @@ def ifft2(self, a, axes=(-2, -1), workers=-1): return b def fftn(self, a, axes=None, workers=-1): - axes = axes or tuple(range(a.ndim)) comp_type = complex_type(a.dtype) @@ -131,7 +130,6 @@ def fftn(self, a, axes=None, workers=-1): return b def ifftn(self, a, axes=None, workers=-1): - axes = axes or tuple(range(a.ndim)) # FFTW_BACKWARD requires complex input array, cast as needed. diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index bc677207e9..0727257440 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -320,7 +320,6 @@ def __init__(self, xfer_fn_array): self.xfer_fn_array = xfer_fn_array def _evaluate(self, omega): - _input_pts = tuple(np.linspace(1, x, x) for x in self.xfer_fn_array.shape) # TODO: This part could do with some documentation - not intuitive! diff --git a/src/aspire/operators/wemd.py b/src/aspire/operators/wemd.py index 06680fb68f..45db7203bb 100644 --- a/src/aspire/operators/wemd.py +++ b/src/aspire/operators/wemd.py @@ -52,7 +52,7 @@ def wemd_embed(arr, wavelet="coif3", level=None): assert len(detail_coefs) == level weighted_coefs = [] - for (j, details_level_j) in enumerate(detail_coefs): + for j, details_level_j in enumerate(detail_coefs): multiplier = 2 ** ((level - 1 - j) * (1 + (dimension / 2.0))) for coefs in details_level_j.values(): weighted_coefs.append(multiplier * coefs.flatten()) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 5b9d8821af..859a87b441 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -711,7 +711,6 @@ def save_metadata(self, starfile_filepath, batch_size=512, save_mode=None): def save_images( self, starfile_filepath, filename_indices=None, batch_size=512, overwrite=False ): - """ Save an ImageSource to MRCS files @@ -751,7 +750,6 @@ def save_images( mrc_mode=2, overwrite=overwrite, ) as mrc: - stats = MrcStats() # Loop over source setting data into mrc file for i_start in np.arange(0, self.n, batch_size): diff --git a/src/aspire/utils/cell.py b/src/aspire/utils/cell.py index ad3a789589..384747071f 100644 --- a/src/aspire/utils/cell.py +++ b/src/aspire/utils/cell.py @@ -12,7 +12,6 @@ class Cell2D: """ def __init__(self, rows, cols, dtype=np.float32): - self.dtype = np.dtype(dtype) self.dtype = dtype self.rows = rows diff --git a/tests/test_BlkDiagMatrix.py b/tests/test_BlkDiagMatrix.py index b7307981ff..261efae29b 100644 --- a/tests/test_BlkDiagMatrix.py +++ b/tests/test_BlkDiagMatrix.py @@ -9,7 +9,6 @@ class BlkDiagMatrixTestCase(TestCase): def setUp(self): - self.num_blks = 10 self.blk_partition = [(i, i) for i in range(self.num_blks, 0, -1)] @@ -57,7 +56,7 @@ def tearDown(self): def allallfunc(self, A, B, func=np.allclose): """Checks assertTrue(func()) as it iterates through A, B.""" - for (a, b) in zip(A, B): + for a, b in zip(A, B): self.assertTrue(func(a, b)) def allallid(self, A, B_ids, func=np.allclose): @@ -368,7 +367,6 @@ class IrrBlkDiagMatrixTestCase(TestCase): """ def setUp(self): - partition = [[4, 5], [2, 3], [1, 1]] self.X = X = [(1 + np.arange(np.prod(p))).reshape(p) for p in partition] self.XT = XT = [x.T for x in X] @@ -378,7 +376,7 @@ def setUp(self): def allallfunc(self, A, B, func=np.allclose): """Checks assertTrue(func()) as it iterates through A, B.""" - for (a, b) in zip(A, B): + for a, b in zip(A, B): self.assertTrue(func(a, b)) def testAdd(self): diff --git a/tests/test_adaptive_support.py b/tests/test_adaptive_support.py index 8c0c2af810..e6d98c8aa9 100644 --- a/tests/test_adaptive_support.py +++ b/tests/test_adaptive_support.py @@ -16,7 +16,6 @@ class AdaptiveSupportTest(TestCase): def setUp(self): - self.size = 1025 self.sigma = 16 self.n_disc = 10 diff --git a/tests/test_apple.py b/tests/test_apple.py index fbad560825..f865019310 100644 --- a/tests/test_apple.py +++ b/tests/test_apple.py @@ -491,7 +491,6 @@ def testFileCorruption(self): # Create a tmp dir for this test output with tempfile.TemporaryDirectory() as tmpdir_name: - # Instantiate an Apple instance apple_picker = Apple(particle_size=42, output_dir=tmpdir_name) diff --git a/tests/test_averager2d.py b/tests/test_averager2d.py index f89f5499ed..26bee9ab7b 100644 --- a/tests/test_averager2d.py +++ b/tests/test_averager2d.py @@ -74,7 +74,6 @@ class Averager2DBase: averager = Averager2D def setUp(self): - self.vols = Volume( np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")) ).downsample(64) @@ -104,7 +103,6 @@ def _getSrc(self): return None def testTypeMismatch(self): - # Work around ABC, which won't let us test the unimplemented base case. self.averager.__abstractmethods__ = set() @@ -155,7 +153,6 @@ class AligningAverager2DBase(Averager2DBase): num_procs = 1 if xfail_ray_dev() else 2 def setUp(self): - super().setUp() # We'll construct our Rotations now @@ -202,7 +199,6 @@ def _getSrc(self): class BFRAverager2DTestCase(AligningAverager2DBase, TestCase): - averager = BFRAverager2D n_search_angles = 360 @@ -244,7 +240,6 @@ def testAverager(self): class BFSRAverager2DTestCase(BFRAverager2DTestCase): - averager = BFSRAverager2D def setUp(self): @@ -295,7 +290,6 @@ def testAverager(self): class ReddyChatterjiAverager2DTestCase(BFSRAverager2DTestCase): - averager = ReddyChatterjiAverager2D num_procs = 1 if xfail_ray_dev() else 2 @@ -332,5 +326,4 @@ def testAverager(self): class BFSReddyChatterjiAverager2DTestCase(ReddyChatterjiAverager2DTestCase): - averager = BFSReddyChatterjiAverager2D diff --git a/tests/test_mrc.py b/tests/test_mrc.py index 45739a58a2..363db73a6c 100644 --- a/tests/test_mrc.py +++ b/tests/test_mrc.py @@ -72,7 +72,6 @@ def testUpdate(self): with mrcfile.new_mmap( files[0], shape=(self.n, self.n), mrc_mode=2, overwrite=True ) as mrc: - mrc.data[:, :] = self.a mrc.update_header_from_data() self.stats.update_header(mrc) @@ -82,7 +81,6 @@ def testUpdate(self): with mrcfile.new_mmap( files[1], shape=(self.n, self.n), mrc_mode=2, overwrite=True ) as mrc: - mrc.set_data(self.a.astype(np.float32)) mrc.header.time = epoch mrc.header.label[0] = label diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 68b071d7b7..20f2c3dfb2 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -293,7 +293,7 @@ def testCommonLines(L, order, dtype): pairs = all_pairs(n_img) within_1_degree = 0 within_5_degrees = 0 - for (i, j) in pairs: + for i, j in pairs: a_ij_s = np.zeros(order) a_ji_s = np.zeros(order) # Convert common-line indices to angles. Use angle of common line in [0, 180). From 66c77f788c45f7b552d72ba8c9f7209cc914e96c Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 1 Feb 2023 15:51:14 -0500 Subject: [PATCH 091/424] remove parameterized_class. Use pytest. --- tests/test_synthetic_volume.py | 243 ++++++++++++++++----------------- 1 file changed, 118 insertions(+), 125 deletions(-) diff --git a/tests/test_synthetic_volume.py b/tests/test_synthetic_volume.py index ba8fc51a4b..a6d89c3cf0 100644 --- a/tests/test_synthetic_volume.py +++ b/tests/test_synthetic_volume.py @@ -1,7 +1,7 @@ -from unittest import TestCase +import itertools import numpy as np -from parameterized import parameterized_class +import pytest from aspire.source.simulation import Simulation from aspire.utils import Rotation, grid_3d @@ -14,127 +14,120 @@ TSymmetricVolume, ) - -class Base: - def setUp(self): - self.dtype = np.float32 - self.C = 1 - self.seed = 0 - vol_kwargs = dict( - L=self.L, - C=self.C, - seed=self.seed, - dtype=self.dtype, - ) - if hasattr(self, "order"): - vol_kwargs["order"] = self.order - - self.vol_obj = self.vol_class(**vol_kwargs) - self.vol = self.vol_obj.generate() - - def testVolumeRepr(self): - """Test Synthetic Volume repr""" - self.assertTrue( - repr(self.vol_obj).startswith(f"{self.vol_obj.__class__.__name__}") +# dtype fixture to pass into volume fixture. +DTYPES = [np.float32, pytest.param(np.float64, marks=pytest.mark.expensive)] + + +@pytest.fixture(params=DTYPES) +def dtype_fixture(request): + dtype = request.param + return dtype + + +# Parameter combinations for testing SyntheticVolumes with cyclic and dihedral symmetry. +# Each tuple represents (volume class, resolution in pixels, cyclic order) +PARAMS_Cn_Dn = [ + (CnSymmetricVolume, 20, 2), + (CnSymmetricVolume, 21, 2), + (CnSymmetricVolume, 30, 3), + (CnSymmetricVolume, 31, 3), + (CnSymmetricVolume, 40, 4), + (CnSymmetricVolume, 41, 4), + (CnSymmetricVolume, 52, 5), + (CnSymmetricVolume, 53, 5), + (CnSymmetricVolume, 64, 6), + (CnSymmetricVolume, 65, 6), + (DnSymmetricVolume, 20, 2), + (DnSymmetricVolume, 21, 2), + (DnSymmetricVolume, 40, 3), + (DnSymmetricVolume, 41, 3), + (DnSymmetricVolume, 42, 4), + (DnSymmetricVolume, 43, 4), + (DnSymmetricVolume, 55, 5), + (DnSymmetricVolume, 56, 5), + (DnSymmetricVolume, 64, 6), + (DnSymmetricVolume, 65, 6), +] + + +# Parameters for tetrahedral, octahedral, asymmetric, and legacy volumes. +# These volumes do not have an `order` parameter. +VOL_CLASSES = [TSymmetricVolume, OSymmetricVolume, AsymmetricVolume, LegacyVolume] +RESOLUTIONS = [20, 21] +PARAMS = list(itertools.product(VOL_CLASSES, RESOLUTIONS)) + + +def vol_fixture_id(params): + vol_class = params[0] + res = params[1] + if len(params) > 2: + order = params[2] + return f"{vol_class.__name__}, res={res}, order={order}" + else: + return f"{vol_class.__name__}, res={res}" + + +# Create SyntheticVolume fixture for the set of parameters. +@pytest.fixture(params=PARAMS_Cn_Dn + PARAMS, ids=vol_fixture_id) +def vol_fixture(request, dtype_fixture): + params = request.param + vol_class = params[0] + res = params[1] + vol_kwargs = dict( + L=res, + C=1, + seed=0, + dtype=dtype_fixture, + ) + if len(params) > 2: + vol_kwargs["order"] = params[2] + + return vol_class(**vol_kwargs) + + +# SyntheticVolume tests: +def testVolumeRepr(vol_fixture): + """Test Synthetic Volume repr""" + assert repr(vol_fixture).startswith(f"{vol_fixture.__class__.__name__}") + + +def testVolumeGenerate(vol_fixture): + """Test that a volume is generated""" + _ = vol_fixture.generate() + + +def testSimulationInit(vol_fixture): + """Test that a Simulation initializes provided a synthetic Volume.""" + _ = Simulation(L=vol_fixture.L, vols=vol_fixture.generate()) + + +def testCompactSupport(vol_fixture): + """Test that volumes have compact support.""" + if not isinstance(vol_fixture, LegacyVolume): + # Mask to check support + g_3d = grid_3d(vol_fixture.L, dtype=vol_fixture.dtype) + inside = g_3d["r"] < (vol_fixture.L - 1) / vol_fixture.L + outside = g_3d["r"] > 1 + vol = vol_fixture.generate() + + # Check that volume is zero outside of support and positive inside. + assert vol[0][outside].all() == 0 + assert (vol[0][inside] > 0).all() + + +def testVolumeSymmetry(vol_fixture): + """Test that volumes have intended symmetry.""" + vol = vol_fixture.generate() + + # Rotations in symmetry group, excluding the Identity. + rots = vol_fixture.symmetry_group.matrices[1:] + + for rot in rots: + # Rotate volume by an element of the symmetric group. + rot_vol = vol.rotate(Rotation(rot), zero_nyquist=False) + + # Check that correlation is close to 1. + corr = np.dot(rot_vol[0].flatten(), vol[0].flatten()) / np.dot( + vol[0].flatten(), vol[0].flatten() ) - - def testVolumeGenerate(self): - """Test that a volume is generated""" - _ = self.vol - - def testSimulationInit(self): - """Test that a Simulation initializes provided a synthetic Volume.""" - _ = Simulation(L=self.L, vols=self.vol) - - def testCompactSupport(self): - """Test that volumes have compact support.""" - if self.vol_class != LegacyVolume: - # Mask to check support - g_3d = grid_3d(self.L, dtype=self.dtype) - inside = g_3d["r"] < (self.L - 1) / self.L - outside = g_3d["r"] > 1 - - # Check that volume is zero outside of support and positive inside. - self.assertTrue(self.vol[0][outside].all() == 0) - self.assertTrue((self.vol[0][inside] > 0).all()) - - def testVolumeSymmetry(self): - """Test that volumes have intended symmetry.""" - vol = self.vol - - # Rotations in symmetry group, excluding the Identity. - rots = self.vol_obj.symmetry_group.matrices[1:] - - for rot in rots: - # Rotate volume by an element of the symmetric group. - rot_vol = vol.rotate(Rotation(rot), zero_nyquist=False) - - # Check that correlation is close to 1. - corr = np.dot(rot_vol[0].flatten(), vol[0].flatten()) / np.dot( - vol[0].flatten(), vol[0].flatten() - ) - self.assertTrue(abs(corr - 1) < 1e-5) - - -@parameterized_class( - ("L", "order"), - [ - (21, 2), - (30, 3), - (31, 3), - (40, 4), - (41, 4), - (52, 5), - (53, 5), - (64, 6), - (65, 6), - ], -) -class CnSymmetricVolumeCase(Base, TestCase): - vol_class = CnSymmetricVolume - L = 20 - order = 2 - - -@parameterized_class( - ("L", "order"), - [ - (21, 2), - (40, 3), - (41, 3), - (42, 4), - (43, 4), - (55, 5), - (56, 5), - (64, 6), - (65, 6), - ], -) -class DnSymmetricVolumeCase(Base, TestCase): - vol_class = DnSymmetricVolume - L = 20 - order = 2 - - -@parameterized_class(("L"), [(21,)]) -class TSymmetricVolumeCase(Base, TestCase): - vol_class = TSymmetricVolume - L = 20 - - -@parameterized_class(("L"), [(21,)]) -class OSymmetricVolumeCase(Base, TestCase): - vol_class = OSymmetricVolume - L = 20 - - -@parameterized_class(("L"), [(21,)]) -class AsymmetricVolumeCase(Base, TestCase): - vol_class = AsymmetricVolume - L = 20 - - -@parameterized_class(("L"), [(21,)]) -class LegacyVolumeCase(Base, TestCase): - vol_class = LegacyVolume - L = 20 + assert abs(corr - 1) < 1e-5 From 12cdad418668a37f28494351938553a4c4435c3f Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 1 Feb 2023 16:02:43 -0500 Subject: [PATCH 092/424] CamelCase --> snake_case --- tests/test_synthetic_volume.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_synthetic_volume.py b/tests/test_synthetic_volume.py index a6d89c3cf0..489d942856 100644 --- a/tests/test_synthetic_volume.py +++ b/tests/test_synthetic_volume.py @@ -86,22 +86,22 @@ def vol_fixture(request, dtype_fixture): # SyntheticVolume tests: -def testVolumeRepr(vol_fixture): +def test_volume_repr(vol_fixture): """Test Synthetic Volume repr""" assert repr(vol_fixture).startswith(f"{vol_fixture.__class__.__name__}") -def testVolumeGenerate(vol_fixture): +def test_volume_generate(vol_fixture): """Test that a volume is generated""" _ = vol_fixture.generate() -def testSimulationInit(vol_fixture): +def test_simulation_init(vol_fixture): """Test that a Simulation initializes provided a synthetic Volume.""" _ = Simulation(L=vol_fixture.L, vols=vol_fixture.generate()) -def testCompactSupport(vol_fixture): +def test_compact_support(vol_fixture): """Test that volumes have compact support.""" if not isinstance(vol_fixture, LegacyVolume): # Mask to check support @@ -115,7 +115,7 @@ def testCompactSupport(vol_fixture): assert (vol[0][inside] > 0).all() -def testVolumeSymmetry(vol_fixture): +def test_volume_symmetry(vol_fixture): """Test that volumes have intended symmetry.""" vol = vol_fixture.generate() From bd7672679d46e3f78df2285dc4578f144f89b57e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 09:46:56 -0500 Subject: [PATCH 093/424] revert mean enery back to second moment --- gallery/tutorials/pipeline_demo.py | 2 +- src/aspire/source/image.py | 28 ++++++++++++++-------------- src/aspire/source/simulation.py | 4 ++-- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 18d9b105d8..c3b5b530de 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -105,7 +105,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # SNR target for white gaussian noise # Note, the SNR value was chosen based on other parameters for this quick tutorial, # and can be changed to adjust the power of the additive noise. -snr = 1e-4 +snr = 0.5 # For this ``Simulation`` we set all 2D offset vectors to zero, # but by default offset vectors will be randomly distributed. diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 28e7a07358..d5e37f1e0e 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -794,7 +794,9 @@ def save_images( im = self.images[i_start:i_end] im.save(mrcs_filepath, overwrite=overwrite) - def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=512): + def estimate_signal_mean_energy( + self, sample_n=None, support_radius=None, batch_size=512 + ): """ Estimate the signal mean of `sample_n` projections. @@ -811,7 +813,7 @@ def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=51 if sample_n > self.n: logger.warning( - f"`estimate_signal_mean` sample_n > Source.n: {sample_n} > {self.n}." + f"`estimate_signal_mean_energy` sample_n > Source.n: {sample_n} > {self.n}." f" Accuracy may be impaired, settting sample_n=self.n={self.n}" ) sample_n = self.n @@ -821,17 +823,17 @@ def estimate_signal_mean(self, sample_n=None, support_radius=None, batch_size=51 # mean is estimated batch-wise, compare with numpy # Note, for simulation we are implicitly assuming taking `sample_n` is random, # but this does not need to be the case. We can add a `random_shuffle` param. - first_moment = 0.0 + s = 0.0 _denom = sample_n * np.sum(mask) for i in trange(0, sample_n, batch_size): # Gather this batch of images and mask off area outside support_radius images_masked = self._signal_images[i : i + batch_size].asnumpy()[..., mask] # Accumulate first and second moments - first_moment += np.sum(images_masked) / _denom + s += np.sum(images_masked**2) / _denom - logger.debug(f"Source estimated signal mean: {first_moment}") + logger.debug(f"Source estimated signal mean energy: {s}") - return first_moment + return s def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512): """ @@ -882,7 +884,7 @@ def estimate_signal_power( sample_n=None, support_radius=None, batch_size=512, - signal_power_method="estimate_signal_mean", + signal_power_method="estimate_signal_mean_energy", ): """ Estimate the signal energy of `sample_n` projections using prescribed method. @@ -893,7 +895,7 @@ def estimate_signal_power( Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. :param signal_power_method: Method used for computing signal energy. - Defaults to mean via `estimate_signal_mean`. + Defaults to mean via `estimate_signal_mean_energy`. Can use variance method via `estimate_signal_var`. :returns: Estimated signal variance. @@ -904,14 +906,12 @@ def estimate_signal_power( except AttributeError: raise ValueError( f"Cannot find signal_power_method={signal_power_method}." - " Try the default 'estimate_signal_mean' or 'estimate_signal_var'" + " Try the default 'estimate_signal_mean_energy' or 'estimate_signal_var'" ) signal_power = signal_estimate_method( sample_n=sample_n, support_radius=support_radius, batch_size=batch_size ) - if signal_power_method == "estimate_signal_mean": - signal_power = signal_power**2 # mean**2 return signal_power @@ -954,7 +954,7 @@ def estimate_snr( support_radius=None, batch_size=512, noise_power=None, - signal_power_method="estimate_signal_mean", + signal_power_method="estimate_signal_mean_energy", units=None, ): """ @@ -967,7 +967,7 @@ def estimate_snr( Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. :param signal_power_method: Method used for computing signal energy. - Defaults to mean via `estimate_signal_mean`. + Defaults to mean via `estimate_signal_mean_energy`. Can use variance method via `estimate_signal_var`. :param units: Optionally, convert from default ratio to log scale (`dB`). :returns: Estimated signal to noise ratio. @@ -993,7 +993,7 @@ def estimate_snr( signal_power_method=signal_power_method, ) - # For `estimate_signal_mean` we yield: signal_mean**2 / noise_variance + # For `estimate_signal_mean_energy` we yield: mean(signal**2) / noise_variance # `estimate_signal_var` we yield: signal_variance / noise_variance snr = signal_power / noise_power diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index da63abaedb..60e11bf9fc 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -497,7 +497,7 @@ def from_snr( sample_n=None, support_radius=None, batch_size=512, - signal_power_method="estimate_signal_mean", + signal_power_method="estimate_signal_mean_energy", **kwargs, ): """ @@ -512,7 +512,7 @@ def from_snr( Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. :param signal_power_method: Method used for computing signal energy. - Defaults to mean via `estimate_signal_mean`. + Defaults to mean via `estimate_signal_mean_energy`. Can use variance method via `estimate_signal_var`. :returns: Simulation source. """ From 5d4ec8a33799226523bd13c0a85120fe2b634d55 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 10:21:55 -0500 Subject: [PATCH 094/424] black updates --- tests/test_noise.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_noise.py b/tests/test_noise.py index 8f7708aa63..0f01ff57af 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -156,7 +156,6 @@ def pinkish_spectrum(x, y): ) @pytest.mark.parametrize("dtype", DTYPES, ids=lambda param: f"dtype={param}") def test_psnr(res, target_noise_variance, dtype): - vol = np.ones((res,) * 3, dtype=dtype) g = grid_3d(res, normalized=False) mask = g["r"] > res // 2 From 7b2035c3c6778c5d269c4468caeea38e9d10acec Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 11:21:56 -0500 Subject: [PATCH 095/424] loosen utest tolerance for decentralized signal energy --- tests/test_noise.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_noise.py b/tests/test_noise.py index 0f01ff57af..92896a0787 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -203,13 +203,13 @@ def test_from_snr_white(sim_fixture, target_noise_variance): sample_n=sim_fixture.n - 1, ) - # Check we're within 2% of explicit target + # Check we're within 5% of explicit target logger.info( "sim_from_snr.noise_adder.noise_var, target_noise_variance =" f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" ) assert np.isclose( - sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.02 + sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.05 ) # Compare with WhiteNoiseEstimator consuming sim_from_snr @@ -220,8 +220,8 @@ def test_from_snr_white(sim_fixture, target_noise_variance): f" {est_noise_variance}, {target_noise_variance}" ) - # Check we're within 2% - assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.02) + # Check we're within 5% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.05) @pytest.mark.parametrize( From 121e331073d31c6118ff5d85f181919b5cd1b076 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 2 Feb 2023 12:14:34 -0500 Subject: [PATCH 096/424] private methods --- src/aspire/abinitio/commonline_cn.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 2f42437ff4..740150f1aa 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -63,9 +63,9 @@ def _estimate_relative_viewing_directions(self): # Generate candidate rotation matrices and the common-line and # self-common-line indices induced by those rotations. - Ris_tilde, R_theta_ijs = self.generate_cand_rots() - cijs_inds = self.compute_cls_inds(Ris_tilde, R_theta_ijs) - scls_inds = self.compute_scls_inds(Ris_tilde) + Ris_tilde, R_theta_ijs = self._generate_cand_rots() + cijs_inds = self._compute_cls_inds(Ris_tilde, R_theta_ijs) + scls_inds = self._compute_scls_inds(Ris_tilde) n_cands = len(Ris_tilde) n_theta_ijs = len(R_theta_ijs) @@ -129,7 +129,7 @@ def _estimate_relative_viewing_directions(self): c = 0 # List of MeanOuterProductEstimator instances. - # + # Used to keep a running mean of J-synchronized estimates for vii. mean_est = [] for _ in range(n_img): mean_est.append(MeanOuterProductEstimator()) @@ -222,7 +222,7 @@ def _estimate_relative_viewing_directions(self): return vijs, viis_sync - def compute_scls_inds(self, Ri_cands): + def _compute_scls_inds(self, Ri_cands): """ Compute self-common-lines indices induced by candidate rotations. @@ -256,7 +256,7 @@ def compute_scls_inds(self, Ri_cands): return scls_inds # TODO: cache - def compute_cls_inds(self, Ris_tilde, R_theta_ijs): + def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): """ Compute the common-lines indices induced by the candidate rotations. @@ -293,10 +293,10 @@ def compute_cls_inds(self, Ris_tilde, R_theta_ijs): pbar.update() return cij_inds - def generate_cand_rots(self): + def _generate_cand_rots(self): logger.info("Generating candidate rotations.") # Construct candidate rotations, Ris_tilde. - vis = self.generate_cand_rots_third_rows() + vis = self._generate_cand_rots_third_rows() Ris_tilde = np.array([self._complete_third_row_to_rot(vi) for vi in vis]) # Construct all in-plane rotations, R_theta_ijs @@ -307,7 +307,7 @@ def generate_cand_rots(self): return Ris_tilde, R_theta_ijs - def generate_cand_rots_third_rows(self, legacy=True): + def _generate_cand_rots_third_rows(self, legacy=True): n_points_sphere = self.n_points_sphere if legacy: # Genereate random points on the sphere From 70c2ffe7cf234d47265bb52380159942127f082f Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 2 Feb 2023 12:21:02 -0500 Subject: [PATCH 097/424] CamelCase --> snake_case --- tests/test_orient_symmetric.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index ca1d4db961..576c6578a3 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -72,7 +72,7 @@ def source_orientation_objs(L, n_img, order, dtype): @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4 + param_list_cn) -def testEstimateRotations(L, order, dtype): +def test_estimate_rotations(L, order, dtype): n_img = 24 if order > 4: n_img = 32 @@ -116,7 +116,7 @@ def testEstimateRotations(L, order, dtype): @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def testRelativeRotations(L, order, dtype): +def test_relative_rotations(L, order, dtype): # Simulation source and common lines estimation instance # corresponding to volume with C3 or C4 symmetry. n_img = 24 @@ -156,7 +156,7 @@ def testRelativeRotations(L, order, dtype): @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def testSelfRelativeRotations(L, order, dtype): +def test_self_relative_rotations(L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. n_img = 24 @@ -191,7 +191,7 @@ def testSelfRelativeRotations(L, order, dtype): @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4 + param_list_cn) -def testRelativeViewingDirections(L, order, dtype): +def test_relative_viewing_directions(L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. n_img = 24 @@ -300,7 +300,7 @@ def testRelativeViewingDirections(L, order, dtype): @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def testSelfCommonLines(L, order, dtype): +def test_self_commonlines(L, order, dtype): n_img = 24 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) n_theta = cl_symm.n_theta @@ -310,7 +310,7 @@ def testSelfCommonLines(L, order, dtype): # Compute ground truth self-common-lines matrix. rots = src.rotations - scl_gt = buildSelfCommonLinesMatrix(n_theta, rots, order) + scl_gt = build_self_commonlines_matrix(n_theta, rots, order) # Since we search for self common lines whose angle differences fall # outside of 180 degrees by a tolerance of 2 * (360 // L), we must exclude @@ -343,7 +343,7 @@ def testSelfCommonLines(L, order, dtype): @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def testCommonLines(L, order, dtype): +def test_commonlines(L, order, dtype): n_img = 24 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) n_theta = cl_symm.n_theta @@ -399,14 +399,14 @@ def testCommonLines(L, order, dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def testGlobalJSync(dtype): +def test_global_J_sync(dtype): L = 16 n_img = 20 order = 3 # test not dependent on order _, orient_est = source_orientation_objs(L, n_img, order, dtype) # Build a set of outer products of random third rows. - vijs, viis, _ = buildOuterProducts(n_img, dtype) + vijs, viis, _ = build_outer_products(n_img, dtype) # J-conjugate some of these outer products (every other element). vijs_conj, viis_conj = vijs.copy(), viis.copy() @@ -429,14 +429,14 @@ def testGlobalJSync(dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def testEstimateThirdRows(dtype): +def test_estimate_third_rows(dtype): L = 16 n_img = 20 order = 3 # test not dependent on order _, orient_est = source_orientation_objs(L, n_img, order, dtype) # Build outer products vijs, viis, and get ground truth third rows. - vijs, viis, gt_vis = buildOuterProducts(n_img, dtype) + vijs, viis, gt_vis = build_outer_products(n_img, dtype) # Estimate third rows from outer products. # Due to factorization of V, these might be negated third rows. @@ -449,7 +449,7 @@ def testEstimateThirdRows(dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def testCompleteThirdRow(dtype): +def test_complete_third_row(dtype): # Complete third row that coincides with z-axis z = np.array([0, 0, 1], dtype=dtype) Rz = CLSymmetryC3C4._complete_third_row_to_rot(z) @@ -468,7 +468,7 @@ def testCompleteThirdRow(dtype): @pytest.mark.parametrize("dtype", [np.float32, np.float64]) -def testDtypePassThrough(dtype): +def test_dtype_pass_through(dtype): L = 16 n_img = 20 order = 3 # test does not depend on order @@ -476,7 +476,7 @@ def testDtypePassThrough(dtype): assert src.dtype == cl_symm.dtype -def buildSelfCommonLinesMatrix(n_theta, rots, order): +def build_self_commonlines_matrix(n_theta, rots, order): # Construct rotatation matrices associated with cyclic order. rots_symm = cyclic_rotations(order, rots.dtype).matrices @@ -502,7 +502,7 @@ def buildSelfCommonLinesMatrix(n_theta, rots, order): return scl_gt -def buildOuterProducts(n_img, dtype): +def build_outer_products(n_img, dtype): # Build random third rows, ground truth vis (unit vectors) gt_vis = np.zeros((n_img, 3), dtype=dtype) for i in range(n_img): From e964933b1b1755ddb2a1878310725896a2a0732e Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 2 Feb 2023 14:40:11 -0500 Subject: [PATCH 098/424] blank line --- src/aspire/abinitio/commonline_cn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 740150f1aa..7858f2bfaa 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -25,7 +25,6 @@ def __init__( n_points_sphere=500, seed=None, ): - super().__init__( src, symmetry=symmetry, From a5d2a12d860404d55881f6c86671173f3c82599d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 14:42:09 -0500 Subject: [PATCH 099/424] CTF NB content add --- gallery/tutorials/ctf.py | 360 ++++++++++++++++++++++++++++++++ src/aspire/operators/filters.py | 11 +- 2 files changed, 368 insertions(+), 3 deletions(-) create mode 100644 gallery/tutorials/ctf.py diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py new file mode 100644 index 0000000000..94331b76b3 --- /dev/null +++ b/gallery/tutorials/ctf.py @@ -0,0 +1,360 @@ +""" +================================ +CTF: Contrast Transfer Function +================================ + +This tutorial demonstrates the CTF and corresponds to +lecture notes from MATH586. +""" + +import matplotlib.pyplot as plt + +plt.rcParams["image.cmap"] = "gray" +import numpy as np +from scipy.ndimage import gaussian_filter + +import aspire + +# Resolution to use throughout the demo. +RESOLUTION = 512 + +# %% +# Visualizing the CTF +# ------------------- +# ASPIRE can be used create and visualize example CTFs. + +# %% +# Radially Symmetric CTF +# ^^^^^^^^^^^^^^^^^^^^^^ +# The ``RadialCTFFilter`` is used to create a radially symmetric +# ``Filter`` object for use with ASPIRE. +# This object can also be used to calculate the transfer function as a numpy array. + +from aspire.image import Image +from aspire.operators import CTFFilter, RadialCTFFilter + +radial_ctf_filter = RadialCTFFilter( + pixel_size=1, # Angstrom + voltage=200, # kV + defocus=10000, # Angstrom, 10000A = 1um + Cs=2.26, # Spherical aberration constant + alpha=0.07, # Amplitude contrast phase in radians + B=0, # Envelope decay in inverse square angstrom (default 0) +) + +# The CTF filter can be visualized as an image once it is evaluated at a specific resolution. +# More specifically the following code will return a transfer function as an array, +# which is then plotted. +rctf_fn = radial_ctf_filter.evaluate_grid(RESOLUTION) +plt.imshow(rctf_fn) +plt.colorbar() +plt.show() + +# %% +# Asymmetric CTF +# ^^^^^^^^^^^^^^ +# For the general ``CTFFilter``, we provide defocus u and v seperately, +# along with a defocus angle. + +ctf_filter = CTFFilter( + pixel_size=1, # Angstrom + voltage=200, # kV + defocus_u=15000, # Angstrom, 10000A = 1um + defocus_v=10000, + defocus_ang=np.pi / 4, # Radians + Cs=2.26, # Spherical aberration constant + alpha=0.07, # Amplitude contrast phase in radians + B=0, # Envelope decay in inverse square angstrom (default 0) +) + +# Again, we plot it, and note the difference from the RadialCTFFilter. +plt.imshow(ctf_filter.evaluate_grid(RESOLUTION)) +plt.colorbar() +plt.show() + +# %% +# Phase Flipping +# ^^^^^^^^^^^^^^ +# A common technique used with CTF corruped data is to apply a transformation +# based on the sign of the (estimated) CTF. +# We can easily visuallize this in an idealized way by taking the sign of the +# array returned by ASPIRE's ``CTFFilter.evaluate_grid``. + + +ctf_sign = np.sign(radial_ctf_filter.evaluate_grid(RESOLUTION)) +plt.imshow(ctf_sign) +plt.colorbar() +plt.show() + + +# %% +# Applying CTF Directly to Images +# ------------------------------- +# Various defocus values and phase flippig correction will be +# applied directly to images using a mix of ASPIRE and Numpy. + + +# %% +# Generate example image +# ^^^^^^^^^^^^^^^^^^^^^^ +def genExImg(L): + """ + Generates data similar to the MATH586 lecture notes. + + :param L: Resolution in pixels + :return: Image as np array. + """ + # Empty Array + img = np.zeros((L, L)) + + # Make central square + center = L // 2 + half_width = L // 6 + lb, ub = center - half_width, center + half_width + img[lb:ub, lb:ub] = 1 + + # Remove the outer corners + g2d = aspire.utils.grid_2d(L, normalized=False) + disc = g2d["r"] > (1.2 * half_width) + img[disc] = 0 + + # Remove center circle + disc = g2d["r"] < half_width // 4 + img[disc] = 0 + + # Smooth gaussian + img = gaussian_filter(img, sigma=7) + + # Add noise + img += np.random.normal(0, 0.1, size=(L, L)) + + return img + + +img = genExImg(RESOLUTION) +plt.imshow(img) +plt.colorbar() +plt.show() + +# %% +# Apply CTF and Phase Flipping +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# First CTF filters for a range of defocus values are created +# and used to corrupt the example image. +# Then phase flipping will use the actual CTF paramaters to attempt idealized corrections. +# ASPIRE has built in tools for performing these tasks which are discussed towards the end, +# but here the methods are demonstrated directly. + +from aspire.numeric import fft + +# Construct a range of CTF filters. +defoci = [2500, 5000, 10000, 20000] +ctf_filters = [ + RadialCTFFilter(pixel_size=1, voltage=200, defocus=d, Cs=2.26, alpha=0.07, B=0) + for d in defoci +] + +# %% +# Generate images corrupted by progressively increasing defocus. + +# For each defocus, apply filter to the base image. +imgs = np.empty((len(defoci), RESOLUTION, RESOLUTION)) +for i, ctf in enumerate(ctf_filters): + imgs[i] = Image(img).filter(ctf)[0] +Image(imgs).show() + +# %% +# Generate phase flipped images. + +# Construct the centered 2D FFT of the images. +imgs_f = fft.centered_fft2(imgs) + +# Manually apply phase flipping transformation. +phase_flipped_imgs_f = np.empty_like(imgs_f) +for i, ctf in enumerate(ctf_filters): + # Compute the signs of this CTF + # In practice, this would be an estimated CTF, + # but in the demo we have the luxury of using the model CTF that was applied. + signs = np.sign(ctf.evaluate_grid(RESOLUTION)) + # Apply to the image in Fourier space. + phase_flipped_imgs_f[i] = signs * imgs_f[i] + +# Construct the centered 2D FFT of the images. +phase_flipped_imgs = fft.centered_ifft2(phase_flipped_imgs_f).real +Image(phase_flipped_imgs).show() + +# %% +# .. note:: +# Centering the signal FFT is critical when the CTF is centered in Fourier space. + + +# %% +# Validating CTFFilter +# -------------------- +# The forward modeling of CTF can be validated by passing a corrupted image +# through CTF estimators and comparing the resulting defocus value(s). + +# %% +# Importance of correct CTF Estimation +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Phase flipping with incorrect CTF estimates can further corrupt signal. +# Here we plot a ray of the ``radial_ctf_filter`` modeled earlier, +# along with an erroneous CTF filter. + +bad_est_ctf_filter = RadialCTFFilter( + pixel_size=1, + voltage=200, + defocus=14000, # Modeled CTF was 10000 + Cs=2.26, + alpha=0.07, + B=0, +) +# Evaluate Filter, returning a numpy array. +bad_ctf_fn = bad_est_ctf_filter.evaluate_grid(RESOLUTION) + +c = RESOLUTION // 2 + 1 +plt.plot(rctf_fn[c, c:], label="Model CTF") # radial_ctf_filter +plt.plot(bad_ctf_fn[c, c:], label="Incorrect CTF Estimate") +plt.legend() +plt.show() + +# %% +# Compare the idealized CTF phase flipping correction +# with phase flipping an incorrect CTF. + +idealized_flipped_fn = rctf_fn * np.sign(rctf_fn) +incorrect_flipped_fn = rctf_fn * np.sign(bad_ctf_fn) +fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True) +fig.suptitle("Application of Phase Flipping") +ax1.plot(idealized_flipped_fn[c, c:]) +ax1.set_title("Idealized") +ax2.plot(incorrect_flipped_fn[c, c:]) +ax2.set_title("Incorrect") +plt.show() + + +# %% +# ASPIRE CtfEstimator +# ^^^^^^^^^^^^^^^^^^^ +from aspire.ctf import estimate_ctf + +# Using our radial_ctf_filter from earlier, corrupt an image. +test_img = Image(img).filter(radial_ctf_filter) +plt.imshow(test_img.asnumpy()[0]) +plt.colorbar() +plt.show() +test_img.save("test_img.mrc", overwrite=True) + + +radial_ctf_est = estimate_ctf( + data_folder=".", + pixel_size=radial_ctf_filter.pixel_size, + cs=radial_ctf_filter.Cs, + amplitude_contrast=radial_ctf_filter.alpha, + voltage=radial_ctf_filter.voltage, + psd_size=512, + num_tapers=2, + dtype=np.float64, +) + +# We'll use these estimates next. +print(radial_ctf_est) + +# %% +# Using ASPIRE's CTF Estimate +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Previously we computed some radial CTF estimates in ``radial_ctf_est``. +# Here we will view the application of these estimates in phase flipping +# our example image. + +# Get the relevant estimate. +est = radial_ctf_est["test_img.mrc"] + +# Take an average defocus for radial case. +defocus = (est["defocus_u"] + est["defocus_v"]) / 2.0 +print(f"defocus = {defocus}") + +# Create a filter and evaluate. +est_ctf = RadialCTFFilter( + pixel_size=est["pixel_size"], + voltage=est["voltage"], + defocus=defocus, # Modeled CTF was 10000 + Cs=est["cs"], + alpha=est["amplitude_contrast"], + B=0, +) +est_ctf_fn = est_ctf.evaluate_grid(RESOLUTION) + +# Compare the model CTF with the estimated CTF. +c = RESOLUTION // 2 + 1 +plt.plot(rctf_fn[c, c:], label="Model CTF") +plt.plot(est_ctf_fn[c, c:], label="Estimated CTF") +plt.legend() +plt.show() + +# %% +# Compare the idealized CTF phase flipping correction +# with phase flipping the estimated CTF. + +estimated_flipped_fn = rctf_fn * np.sign(est_ctf_fn) +fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True) +fig.suptitle("Application of Phase Flipping") +ax1.plot(idealized_flipped_fn[c, c:]) +ax1.set_title("Idealized") +ax2.plot(estimated_flipped_fn[c, c:]) +ax2.set_title("Estimated") +plt.show() + +# %% +# .. note:: +# At the time of writing, asymmetric CTF estimation is under going development and validation. + + +# %% +# ASPIRE Sources: CTF and Phase Flipping +# -------------------------------------- +# The most common uses of CTF simulation and corection are +# implemented behind the scenes in ASPIRE's Source classes. +# For simulations, users are expected to provide their own +# reasonable CTF parameters. When in doubt they should refer +# EMDB or EMPIAR for related datasets that may have CTF estimates. +# ASPIRE also commonly uses some reasonable values for the 10028 dataset +# in examples. + +# %% +# Simulation Source +# ^^^^^^^^^^^^^^^^^ +# The following code demonstrates adding the previous list of +# CTFs to a Simulation and performing phase flipping. +# No calculations beyond defining the CTF parameters is required. + +from aspire.source import Simulation + +# Create the Source +src = Simulation(L=64, n=5, unique_filters=ctf_filters) +src.images[:5].show() + +# Phase flip +src.phase_flip() +src.images[:5].show() + +# %% +# Beyond Simulations: Experimental Data Sources +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# When loading experimental data, +# CTF params in the starfile should be loaded automatically. + +from aspire.source import RelionSource + +# Load an example Relion starfile +src = RelionSource( + "data/sample_relion_data.star", + pixel_size=1.338, + max_rows=10000, +) +src.downsample(64) # easier to visualize +src.images[:5].show() + +# Phase flip +src.phase_flip() +src.images[:5].show() diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index bc677207e9..44834d6b39 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -320,7 +320,6 @@ def __init__(self, xfer_fn_array): self.xfer_fn_array = xfer_fn_array def _evaluate(self, omega): - _input_pts = tuple(np.linspace(1, x, x) for x in self.xfer_fn_array.shape) # TODO: This part could do with some documentation - not intuitive! @@ -451,14 +450,20 @@ def _evaluate(self, omega): defocus = np.zeros_like(om_x) defocus[ind_nz] = self.defocus_mean + self.defocus_diff * np.cos(2 * angles_nz) - c2 = -np.pi * self.wavelength * defocus - c4 = 0.5 * np.pi * (self.Cs * 1e7) * self.wavelength**3 + # f0 = 1 / (512 * self.pixel_size) ## XXX 512 + c2 = -np.pi * self.wavelength * defocus # * f0**2 + c4 = 0.5 * np.pi * (self.Cs * 1e7) * self.wavelength**3 # *f0**4 r2 = om_x**2 + om_y**2 r4 = r2**2 gamma = c2 * r2 + c4 * r4 h = np.sqrt(1 - self.alpha**2) * np.sin(gamma) - self.alpha * np.cos(gamma) + # For historical reference, below is a translated forumla from the legacy MATLAB code. + # The two implementations seem to agree for odd images, but the original MATLAB code + # behaves differently for even image sizes. + # h = np.sin(c2*r2 + c4*r2*r2 - self.alpha) + if self.B: h *= np.exp(-self.B * r2) From b5cbb2d90bdd61733b7a885fc7de8019c2b1d5fb Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 2 Feb 2023 16:14:08 -0500 Subject: [PATCH 100/424] rename variable --- src/aspire/abinitio/commonline_cn.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 7858f2bfaa..9313c30fa1 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -123,7 +123,7 @@ def _estimate_relative_viewing_directions(self): logger.info("Computing pairwise likelihood.") n_vijs = n_img * (n_img - 1) // 2 vijs = np.zeros((n_vijs, 3, 3), dtype=self.dtype) - viis_sync = np.zeros((n_img, 3, 3), dtype=self.dtype) + viis = np.zeros((n_img, 3, 3), dtype=self.dtype) rots_symm = cyclic_rotations(self.order, self.dtype).matrices c = 0 @@ -217,9 +217,9 @@ def _estimate_relative_viewing_directions(self): c += 1 pbar.update() - viis_sync[i] = mean_est[i].synchronized_mean() + viis[i] = mean_est[i].synchronized_mean() - return vijs, viis_sync + return vijs, viis def _compute_scls_inds(self, Ri_cands): """ From 5ae0bb5b36d2597d1118504bff21873c8e61816f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 16:16:37 -0500 Subject: [PATCH 101/424] Continue to add CTF validation content --- docs/source/conf.py | 2 + gallery/tutorials/ctf.py | 56 ++++++++++++--- .../data/ctffind4/diagnostic_output.png | Bin 0 -> 1641138 bytes .../data/ctffind4/diagnostic_output.txt | 6 ++ .../data/ctffind4/diagnostic_output_avrot.png | Bin 0 -> 448139 bytes gallery/tutorials/data/ctffind4/input.txt | 68 ++++++++++++++++++ src/aspire/ctf/ctf_estimator.py | 10 ++- 7 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 gallery/tutorials/data/ctffind4/diagnostic_output.png create mode 100644 gallery/tutorials/data/ctffind4/diagnostic_output.txt create mode 100644 gallery/tutorials/data/ctffind4/diagnostic_output_avrot.png create mode 100644 gallery/tutorials/data/ctffind4/input.txt diff --git a/docs/source/conf.py b/docs/source/conf.py index 08c65393e8..a99fe4ef9d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -50,6 +50,8 @@ 'gallery_dirs': ['auto_tutorials', 'auto_experiments'], # path to where to save gallery generated output 'download_all_examples': False, 'plot_gallery': 'True', + 'remove_config_comments': True, + 'notebook_images': True, 'within_subsection_order': ExampleTitleSortKey, 'filename_pattern': r'/tutorials/.*\.py', # Parse all gallery python files, but only execute tutorials. } diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 94331b76b3..cda609954f 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -7,8 +7,9 @@ lecture notes from MATH586. """ -import matplotlib.pyplot as plt +# sphinx_gallery_thumbnail_path = '../../../gallery/tutorialsdata/ctffind4/diagnostic_output.png' +import matplotlib.pyplot as plt plt.rcParams["image.cmap"] = "gray" import numpy as np from scipy.ndimage import gaussian_filter @@ -297,12 +298,8 @@ def genExImg(L): # with phase flipping the estimated CTF. estimated_flipped_fn = rctf_fn * np.sign(est_ctf_fn) -fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True) -fig.suptitle("Application of Phase Flipping") -ax1.plot(idealized_flipped_fn[c, c:]) -ax1.set_title("Idealized") -ax2.plot(estimated_flipped_fn[c, c:]) -ax2.set_title("Estimated") +plt.title("Application of estimated CTF Phase Flipping") +plt.plot(estimated_flipped_fn[c, c:]) plt.show() # %% @@ -353,8 +350,49 @@ def genExImg(L): max_rows=10000, ) src.downsample(64) # easier to visualize -src.images[:5].show() +src.images[:3].show() # Phase flip src.phase_flip() -src.images[:5].show() +src.images[:3].show() + + +# %% +# CTFFIND4: External Validation +# ----------------------------- +# CTFFIND4 is often used by other CryoEM distributions, +# and was used to confirm the forward CTF filter model of ASPIRE. +# For transparency, an example run using the ``test_img.mrc`` +# generated earlier is documented. +# +# CTFFIND4 estimated 9982.5, within 0.2% of the modeled defocus=10000 +# for the radially symmetric case. +# + +# %% +# Interactive session +# +# .. literalinclude:: ../../../gallery/tutorials/data/ctffind4/input.txt +# +# + +# %% +# Diagnostic Output Text +# +# .. literalinclude:: ../../../gallery/tutorials/data/ctffind4/diagnostic_output.txt +# +# + +# %% +# CTFFIND4 Diagnostic Output PDF (rendering of diagnostic_output_avrot.pdf). +# +# .. image:: ../../../gallery/tutorials/data/ctffind4/diagnostic_output_avrot.png +# :alt: diagnostic_output_avrot.png +# + +# %% +# CTFFIND4 Diagnostic Output MRC (rendering of diagnostic_output.mrc). +# +# .. image:: ../../../gallery/tutorials/data/ctffind4/diagnostic_output.png +# :alt: diagnostic_output.png +# diff --git a/gallery/tutorials/data/ctffind4/diagnostic_output.png b/gallery/tutorials/data/ctffind4/diagnostic_output.png new file mode 100644 index 0000000000000000000000000000000000000000..960f339c3332492536c1bd048d4d1900bf822983 GIT binary patch literal 1641138 zcmeFZ1yq~e*6)iHhvF0|QlxlsC>mS~1cw5pxVvj`EpElVxV2bY+@&}a3M~{UUfcqN zzk_6_Sg)=hAWJG5OtAa)y3*A9$=nSFgZ!tzt?^f z@t93x*zI~2HaP=v9vRbl2yO_Af105%SbZ8L(4NjGtQ=uW;H}JOW~S2|*``3Ry+DnW z#-=O(wDLehDWWX8t{uY_)BkwEHF1-E|dRy7(Y+Cl=w5TEb-zujhsMltO{Y0*u0OvjK? zr390jJidDNn(QUJbYr+Bp~-g3l~BA*o&nHDOg;c&LW30_kc6E{-zdlxBJBb2?dH`V zC4B?DoB}Xlh($cdmkluj8-4?*i0p7uh=ov&0NT$?iMxH=O|{}-{&&Hb)sd(I)0FXU z-3%($-6`bqxJ>dyeK84bSTxbM8`H-WxH+N|gMk>QU^{gmWg8|jdL!m0LMQcJJE*L?!b!{8ROHtJvJy)ws0!w<7upE{u!xv1|%g<(idz5Qq+DZm;3&?Vh|vXIcWd~=@3w>8kM&V8-X@A3qeJP) z!$UOa$QVPlxe0X*a2Gy4eu2ipae&~T?9K1v_koM61y4RFj)}3 zBZ|ec45tp43DjHO0rwVwJ`+u!b3{ z^r*)u@2HnD>LyWH-i*q8$&waoMa-PTo{^n!bEIFaU*e6fiprSsn4bNc4B2YMY9sru zuLiYIFKKOV_?am)1@qsPa+V#G3QRlAuFULui28Ah$LFh?DAyMk6aosbZ1|dtn|+t0 z4}_OUmztOKnoM?q9uA(*MTkAIJx4tr?itPO&Xw&f?YenhfBN9b?t#C*GI#LRd?$V? zZhmamu(;LmD0js@Fr_mu=xrwu6ap2zBfd$bAn(iWvBV@lqgbXWCqF=!MfFF`muiu! z?0FSN7WPagP)5DaqR%#xG*SU~`7QZu!if16Zzc~uw=*~3ArDnA1wG|^AI0$`c9|6_ zJOOM$B8qhK1HMmA=hJU3A6w;`xA(S9fIG3A)-8AkEviPIrfNOSlfjF;!6(J#r0{#a zL#;J8H^RT-5xFZk8~5&q)s_{Z74HX6E9NgE<5^ohTi>>-#}l)?^|?7vI2p;RITH-p zUsJq)USp&yyw6xYHZ#XNqiOqb{{6uFh0ztZ5xe|(iR#<0+V(A;?=I~2t@fW$;p9vU z@;O#H9xiG+d}}dVZf>!5SY7_MT(-#BRQTU$b>Obcc=FD$ z*B96CJ3imAJWt(-M0K7UyUFKuqV_@5Noh8`#S zB`)Mo7XxqP>3J%@$1CAW+DeMEaJdjj4fim^YeMo8p{h zQAD4-odHZSl$<*Ltas8&m6vIyB=(G3_eQi z;^_(MZNl1qn*HKSo{vS!1S7OGY&ZNo?fl1X0k{#O9ORA8WiBJ18O*NF?!$^pR!8O) zjUvk~H_402t7j!Ki9P%>eg4TqqRbdkCZiF*k^SGpzsJ8FxtqCrZJlqv`*k?RuwAxI zow29lUdqgzqnFmBp#jomBV-M65Ela4E+zMygStks5Y2dU}MA%H9dXk25=!PWV33beK*2c{n>&KRJH zTi}Fh(sKx#?CHMLy|ImXJ#tN}0MUnZX9x3AsF zuMM*2gbK|&2re6ay51lg$uKlzX!)wUq?6&u;KaUo>EVBR$p>fzRV|smFYy@K&YQ_| z{Jid+S94lfwdDB8-$3AHZO!2#U(n=cN0I-&y^&qZq4^>E5By8^t1htc-8L0Q@k4%E z3)-6Nfy+$1DDo&}yX2+aW@FFwPc1idH(&co9tsJ8g&yC`Uo7OsXT*~#;3jg2DvN*m zX|vkcYyLy^{kw?`gCSZWb-PY~v)?iH%tN^j!la&izju#knI~K(9*a`Bw4CIwe(c-+ zw!Qz%$E2cT$Ma;$`BfLS-HB6W$1nFv==-D1$gjz@3iS_+g@Xjn_jc{u?H|<*898?( zdu)6iKM`7X!g3bx%JaXy$O{$i@QynqJWT(-&|z}feJ-_ml+bl#f9525%6+~zpn#Ql zC_yF`bS-=GOY1hY{Jh+9k|SS7+#9sANVwv9J<#^(fXkB0Uo;Ljb!*p@3`; z)HNC()Nj_`u5kF;L%g<4@<9ULL!JPi%&WSqK&3?2$vXVs_|_8d0s_8@UJ&nUU0V-L z;+Yh8e%~M4-%mZ+gg{G>(5bSsu)1^dkfyYdUw898@XWn=w#XHI16@k&2KDaUnsxc0 z0#QZhdGePZbO$Z|j-?a&t)l?$e;w@SgDV)%!YlrL-iY6anEw3yqC)^Rcn+!C+SkP7 zXt1+$a@n+@71<`cxHGIO9PMrNde_LwElmae@q^VX7|IQGvQy?S zf@kp6N35m3l9j3|5*z#&6X^jmB@!C^2pRsAK&JY~u{<&h66&9F6eOf5TcihnwNZn= zA|6TbC&K3b6*VOi2?PF)2>$dbMESe*17IQQ-^Zv4@H!+ZEg2;x_^Xz=o28|byN$EQ zahdNUcmbA+qJcXS5*ZWXiL9i_cnt4jM z;D?Tu9;S3Yjt)-lB0l2ue_DvZj}gUO^mKokc-V{6>#G9kWSreB=>(r~KjWsCz^0?4 z6LYh$648{E|H~ZyPMqGx!^1^{i_6>F`c7E?!~F{$76>|7+!6hGJZZul_GnaX-(0O5qbNfi1@Mj~`6} zo1{MT2mE(@Y%8m-4S$87Wrzpz5d06z{VV(!=XkX<@z@>-NfJp(R!Z9k`F96SgVm5L z%~u#~)&J_S!+Z6Jx#9C7nq;s~Sh!-cnPOIpTue}MN>x9Lj_Ux^kXqHcwt;ipdvk>t z%^0RkEnNd$GxiKQkqqMG6vc1avQqSl!E|tAuHZn^N4G&g{I5Epu*n6!a(_RlFznla zdUR-r1fv>g_Q$NdT0wrku=`fm^4>$bb@`3y;E#Z{x!eeQ;{Cg!*jZAa;BmW-ae|n;*A~}sEvC!jnBCtII8C~rb zUVM?$ACEQe!%pcIl8(S`4}5OtlE>yd)MjiN-xRqCjlu{udE-vPb_@>75rdxQW){nc z<{=BrQ0i6?faNQIAwva21y_K6$vr}0LF)QYUH9kgilJ50dKHq(s*jylZ+hNY*l`IpSE5Ta*^ra|%r#(f%eIcYtD;Q{|!YLTpXQ!aHZjkl5sMt`m+O*Ne@AeRs z2si@q(9T&$uMsDS1dec&4Tv~S0`fs(Fdr!EhlpQ2F|8FO~}9NG63ClKN@ zsldqN>&c&=>%dAC7PBcY@#5rW&yGZSVDG3|h%kf{1;UYh(B8cik~&Z(inhZ${z%Ba zaY`lHBpBR!1ib>MLbWd5NH%3k+FaMzTWVenR(?f4hHl?Y-WHrwFHbgQmO|fgplP1q zY1HEVM3i0ub|rKsF=hQ@t^gEA^Gw+NuFlbRj!5?(R`IIRPo8c`MGV%idZu0-#mRDA zRqMNh68D-MlZ^E%kf(^A+o5-}t9$)G;YikmA`uH$8+%VFSR_Av!KN_w7J%nN16qM0 zzO^ZSzk{7yRkvi>>oa@m$fK9? zqIx3qncyj97P+FTA%XV5tFEB}FNPzKZ8)%;5%?>P5hdjTLlcp*M#Y{llohgjvVFqL zmsaCEAWg`XmEgf$v;qso=%JUSjPkqP__+o5F6V46!@`T+UVzWo@ePN#lG&GB4aY=tJ#c7D#C1 zKzy1jK$gXrhv9p*;+DzMUo&)3!?&~$gXeXpDw`sIpCM}=z*M#>4h;q>APq~TUx*$< z1IOQWClIYvbAj8h!wy6dE0QPF1)vQS3Vu$nz^We3&N=G~E)F?-TYoCLbk@*{xrjK8jzBt5 zwtGSxRKLyPgQwTH+K+ zV5wo|`YYVMB09CzIX9*777%E80>;Th>sh-~cUNduHS6Zn!7!_{V2oquNd*!zPXY#6 zvHYMX5C#)paag29$i&*UkVSNoW*_Uj>0eoFA+iP>`bIHX+{@bq5>POBjJoJ~0OaCi zVaP-@YWvw~xhOOnEp3sukDO`TDuShTTlx#`S&C+B?ef?9zO3;Ypxq-g=heHTlc7BB zF9IDfG`+N#l91HRv>Ko;TmSu)ij_Cv5%GS5-{rkTI_ao##3H}lYlFs+)Nfy~Ez6`G zCm1zc)i={*d**dbmY&F2i-qh&_(d z3Y0R5r_sBKX@f6~mfg(7lVd#J>El%`>atPJ+ZXI{14Mx%%dPojT%TaIdS+juo!k1~ z;v*cR0eN`EPpvl@Y*r3C74q7r5Oe?R=c8wd7s|L4CwQ6UXKU9(XX<3=PuRdDC1tS` zR3cS`>0+?O{HF=dEg?7wWY6Z4}x= z*bc@K>nYzmiqQom(^4^~fE*iH#;-km=3|#%i}P!vE6Y zMtY`(ydsXx2Mw4Yu8P#lpJahdo@vs*Ht=&d;10M;0(N{IH3Let|2;1rh+K+d= z6o-Uc@5pA?zf5>pw%0;QTufdmFa_?U4POa5m#n@04qIy;+^9E|yo4@Hg^0nrg_|oQ z@}3s;wdwxxoDU)q9v(AniKIx$qOIxG`n7`b_O-X7%Qbjo1OI&#!tK-H?hO%65XunS zC^lYP02Qhp)+VSc=>yi7@}ATxghWTY?{Fb`vj#D0X2a65(GgE(A!r1qKf-ke_!JIu z30K2QD@1f9Nuu>D6r%=Ji^$D+(Co_S{G{g_f)P!V{cewC-Ksv7O>liYs!HLGc#1hshLMrw^rAvR_(G#6@egZe!cC z`roGiM@`eHPy|{U4e_P{P@CK(%u}Vvkh^9xx@6R4K$CY^X*0aa3p=tE9yIP0uO+ZC zh2?6i4d2m<0iRn;-Ol(&vS*}7sj)Htf_lUHP~{|jAb5UspKRJnlt<{8(}}BIvXY0@ zjxkWy=_{iPSLPWd*M%(Sdn^OwAZGe}Jq9^pf2_?=X*uQ!ZONr#2r!A_NGV;4&OtRrgs>rMQytqaLgZPa<@6Fo zD2Ub_p~3REk0sj}u+j1X5CkVR82ADuUo#qu`C#M0FM+F+A;f-oA zX3x|CYgw>IZIG&}3#Ei(Wwn+_t4QUfDDWgbN%>h6l5LbfOE(v@u#_NI)PQ(5Hp#A$p{4bq}ur7m>?Q|*;&4XZ&?f15Db)gDa$QCnnis?{wE^6r(u zgR+T?DsmdTZz-*`?jLVygBn6KWc5m>Y^cN|utW1XeIdS(6C?Wma6DXM4MIWe_?Bs{ zif*`bXju7c@wQ!%MC@S3O(D=LUov+Hg<87fkJw#aLOCb3A4BKDo?1WFgg=e$SzchNiJpQ!)yLtYL$otS?cN zx!Mrz@I17eX?n)hr{1R872P0C-_kzVQ4!;nl*K! zOT4h02twMGzuSZ_gzaNVld)9AYiWF8XV4)~@NfBz0mNLr(QW|w@2SbwX zFbsT)o{-QXTCSe25UiLl8Y{3lYHa>h+)yKgk1T+2jk|;5Fz6BE(oS~4BOTITAZdD9 zxfziYBoob;qK}b?_y-I&k20bKF&WTlfgu~50O|K z&8QRABxa`3*^UN2#pWxI*=JJ{Is#8*Pjw=cnaS9`5C~iWu1xpQ_Me?-{wk|b8Med7 z)no1NB@kGCe=N%^UN)>$`!vmiU7M0s`|T6El}-RC5wwis;GO)6$hUHV=|2vT>Sz0_ zRF_!!6DeCf38Hi7)!VMC|H}4&3(y%cT!!Z& z65)<|DlN#|cp4oDIXM{`GC9PtGSj$kdySDx2w6MBr;^aI3LM*7)y0RQtKW46nCaH= zPmyZlFQ;wfWo%0i@KyqWx|s%B~3$?c^5}r_!pb#9jVRMXZ;;HhCIi}FCA4Bt&0I_(HX3FA z`zpcvs{7SO*>gfhZLu_)H2sfeXvL1?$b-BE*T>+vpkuI#E4^Zc56d)5quangM!H|U zWUk950Z)L%8fKfZq|q&=56H&?w)!@>ZhAJj24d)n){A6I`bSCG2^{+{xihZ6AXY;% zT97idG)*D5H;Jfy&tKx{iBwe;@@8KOzWX5@CbAL~)-@np*yFt39=jz=8~4=>#z58Y zL6@%*Qt>KQl9VmT(cmrVc+9QIcZRwP@EQ0F`p7TL3lD8ZDf={j8tbCeP0k&@!uZbM zboR$Js_)<}36Ll{HBxZfbmjVU`s}%SKUXiJ4(pf0jQkv0teRoIDdUPBr(WlaXwMC9V z*ssB1()NkaqGJFgLSg1nbFX+v@_R+;u%-MYmG4J&8h1TA&5u>L(e_QdvjDwVrR0lOj9pTpgT!D%Y5QkKK!BG)D zucP5B%!idO=#Mo&uuFcoL}kHk%qUVuk{V{F1qDI^?ca2LQ}>a!NN|hQFhTQil9Wxh zS4Q6wkY|a3V|>|vWBmW!#0aWLTVy73eqBt zEGr!8VksM=eUbl)_nUt7pIp{*z1fHo_1BO1L;SzPj;n@1E8ChLY15hZ5AjFnup-|G zL|}_S#h~r5C+M1q!>;pvxQ*GQpCd^bL!860No_vIY#bPxkPqQ|Up8R>DQbhQ>Cl4C zBr=Oj-gFwd`zWF-aa+`|YX3xBns@j-Nq({k)ihSo^EHbCHFQ{iU_V?R_|^BSYx48B zw#`f0;Fj!x;z*Fz;|z;(-HyTI2S(*w84S&oWN{gPEn)Pm;?kao!Em?yMoOgz^q3HJ z?+}KkA?H4GT3+N1j1x|$J*Yx_PWxJZFtZTLLFyE&2Jq3z~BUMsgYT)2b zb~zV7;tGz>kHwhs9{NL2P3h!{w&aMYc3+P#esIPN+tPS;&PQ!4X~UFO40NVVh>_re z>_XKUEXWKSfnfI8WF+F3SY<488~6z);Ef%Y^$`T5&MMN|?MtD5RRcVWeVWaZUJRT+ z1G7VR|0p3{9My}`(_{su!e2(K1oBvZYh{V<+mf4GCyPX?GPs4!Ix&8u#P;h@T`9U|sZO^0?3%9g~(aBKB&i z#sRL#11psxe7qM9SJW|Tj^bnwvEev=6nIbeSAwp;YV8YMb@i)}8l#@*Z!}S6EJoAk zio>rZbUkXjjX{JObOc&QreYz}JEhD3bH}<|-mfM+DirN+@#4_`CKK>E&(4<2hdvvbdprMeopXMs3aI$#^yz{>v&VYkpCR8 z#c-vNCKof1OgvFUueQ?ZsvLRRLPEE`|DGFkOH6o3#{7MFMU`Z0p< z(}E5j=YJk9I0W010nI?T=4t#nq{ZL!ad`N?s3`xKRCc%%$%QleGOs6yK#~UZ9jni1 z_SVEjF6%kva|5*z1ik-#A0ABYzR+V4RYPUo*ZTh}s{fx9B4Qnn-!mluD<-64*?@cI zp>Lsz4mAVKoI+oTv-GWF=L=&U%ZfERE-N3NeLzib58SssDZX9xV;Xz|j#POAj~vgS zng3lZk2w7;ObLoZ)p=f4y=^Qiq11qfDhIafER%q*>^v6zLPlm^qW)-a|2?(f1wmlr(`wS~-6@yk)#7q-T7!;NkQvMUyLlc9Jp|t6P6zW@Y=^lh7LVhX3FM$Wf> zg;dY?sry8#m0nkER_IhYRr5TFO@4_Qe{?IgbD)4f_i6Hu_LaRDloH+I2!uyHe1@N^ zmGg4ZmPq7{JzSE3lfu^sA0EeDp-Ko>VpaB;@6KOzjo?=1{^y!M!f zKO~aG&;;MJbZ=@8oECmT{Az|`#t#neS(b4WddMdxbr6hQcb+%~f#-dDjxWP7>>w#0 z5YWEL98DIk1;l#Td?$(JA=4_<#V%&n#>0t*HzloX#aCx6uQJ?&q}I;@cat9^4C7MVKmXtC=V;{q5pSS4{C%A zdIwjB_1{GP5Of6)13m~JS}nvVj*AqnO2c*26fUYt59L|>Bv!l@TV|1zI*R3ci?I9- z`w!hAqSv1x^mOPY>e~O14F1Gvh-ehpRinWaEyN59r)KdzO-=zia-seK^$KuM|MN3) z1|W!jeOTa%4W-DaRZN>QIO9$6@!Qk2gU>KR92(FPY3Zzzdd!`yvNVJDF@oS*%ptS^ z#O;~@X9d>8?gNX0AfQG2Gf2X}5 zJiFQ4JRx>p#21ACh?Jkrd}4tYf9;=UeM2S)7c{vImm9CHXhE>ED|o@TYwPA98%D$k3|gg4>6hV zZuGPOcI6^9DJQYj)jTPUDYd~armdI^u%xJznd1hsXO1}U>Bs>094ceiiZVETZ+S95!`3jU$mKi)?nyq~ z&bJhJej4p0lk|(lZf-(S)4$L^+&LicDSwb!=-ZRV?s4Ap`lY8xt ze&I@@h1%M3|AOdmUF60*N`JOeZX09_jHHGlqyUdH9Oi>yO$}1cF*6+?LD{%S)$d8# zy5sGFVx~gvImyO z&Z;Z>ls}&TVtA;abpuRH20SH5!Z41IdMaCVvzFuVS*a(5HM>)&Tb-0-lX4{pm;VZM zEJ^m5#RqaijgumEDH5!j5_RjUu3CgRNrWPbx+Qwqw=c&foz zON@Walun*){}%@H8ooHuOF`p}m;|h7!YL#w)8x<7CTuo%{{9CY(%NB_J}GHk(Vy+g zPsk!GzGXx(i)@WI@^bDjEFe~cC)o_Qo)3&^7I3(hddlmaByvD2BH(i(MS#a;Bg0e;S}NQ(nzZps3Wa zXhspy`PpR$%pdk&*9eF)s{GCY+Ebqr|g> z=UydZo`Ly(;8OS9u1dZjztWyM!#5(iiC3yYd-KLckax1nMgI6t?g(Ob>#0qi3(;{| zxD&A_F5aW`&Cb_P$?n@0fVwGqXeD3`Yg(3HTOFW|7st-WbFeXHs{v(HW2uVPTm9b&c6Wqa$-=0d>oJ<5sNflHWhyWR8=k6gvZdWzr{zZxw@<#M8wGN zH=2Ks)&u`S>$iaa7IlZ`AjJ|(Ne{@x$j79!>(VVn629g*%fe6W+QCTdB4G{lFC|$$8HT{mx!;t`yOfnw zVY8LkI-4)$@l|Xb2!RcAQ7Iu$C#r4K&KqqwuK@3*USO<$B#GWD!_+QXU&x3sn9gm3 z4T!J5M5G}efr^A|7JzhlrL3#uc|dhGJ$`05BoSX1ymyV^D}}}~As#8Q4^QpKdTVUd z#9-0>{mu7Y)7NmTzyX=*;x?QS0~0Sr+JXG3fg<%g`M&pjxgJELB>ien1aX~BQqwQ; zPLeFGpYJlJu@(|AMl5oFhaJM~8X6kS>HGg`3&cd&-)CpQA^wnw4`ZMP`*&h;);LD~ z3JQeLy%XSDbcE+YG30|{8;0SPPyg=nPyXt|tyMeZfd{Wi8=0)R)6DtLXn~bMl?H6M zloTBH@qe>?ME-Es6PdHu%#TG|3|NHdIc34^I>#5g76dcudtSrJ+xkzdE<8E9t zGogi{rw+e1&{2^Z(b#-Y`0JizcsoI3a9Exi%G!{#8q*mT5pXxm*NQGN=WDZ=o<>|Bw#Bpb9;5Ep4XHvCM-sTeNdB7Lqh=G z@Lpd7gTrKhBGSiraDDWnF(NSwKs!m;*4Cup(A%zkoQ-kX$R=sXZ6D|$Nu%g zne!Kn3wR}*2t$lz)@3W+JiS){?xp;VjY)4Wh>|Ff5&s9>JPCTz-HLjKS!k$Tk6lE1 z*-v!CFY}00eOl(H(=zGSC>mTJ;a3|0>f26RMV4XFuXq{o+CSlp89^c&FUmAa$ndGb z?5NqeklWWKq~NeU);P^PWU;KuEUv-eLt-daEtYKybjc0<&n>%VZ%K>UdJs zjEwLO*B?d!wKOe-zp8a9vuzWo^dswwUJYrPB1NE+3;sMPYKCivx0fSbU@Ud~Nv)aPh9Lj}x+aO; zE5^p;hhN`_-$ruV&}>QrW|Pswb){LcFD}>K?ws`&9K!getr|&jBD#2~vAB!Ufw9u! z(4XXqfP@74{B}5yPNncmm*8MsAHWu<@t{}1xd=Lhkkz7=+7aMIAEm@(LG{QiS0^gQ3b3+j8{BLVMQ=v$1R-{rmBJa3wP zgExkL` zePZbLn+@bauUA$4?Cl$`>n{p4i5g@~WD07}qhg=?-olgoKfz}8GLB>+;wSyY=g=#V zknv|hbv{16b7H^=@p&>n=WG3-=69*+&vkhDamT)V~JXOek=~v z`n{#Ja0`3wg`0m0!4UF+UY&4F=BCI6KyD`zKPaC{L|T+SsDkM zyi>X#pG_cg{_Xxh$@!N!?!lj-1wgpt6XT~dVLPTbbx|~<>oCxD0Vo-Im=O<&dO!omwr6k782vwA89hN8}F5SzsM^Pck-qA^l$;4pST@X?92IA_8s_`|E|*V97u zm&EYR4PbV!!hWh+fJ)ng$`hrzgc+9!lGNINZX57H&lY6E_pT5d6`o%2--xz*4@4MJqc6whaSQ>-fNoDK~s^2;i1Zr>S)&r?0tSZ z_6qDvsl-Pny8D#~7;>ii7Iq8fOEN42KcoU7woa7ukoWU6E2pgR=6Go0}*I`d%NYKugA7H=O}1Z*lO54`$~Bz8t4bbSsprdE_^cLd_3iF z5nTN4Z9=?cq@Yg}-ZBmbs>bPMwe_P8bK|~6`2HqqKegY08E-QzYuVVVuAdmoaB%BV zh*-KH66F@QX?b5QGVY$$9}Pq7KQ5QKTTezZA0K{RQBAFWT3!AmOc;=MTEXkX`Z__| z(#X*G6rla2UPptWuk3=z*~EC^C#((Ca!2b8T%fV^Ut=NfyT0FPQRPRkL1$LP#znpo zbSxQf;`T?1@vOPutPzP+y&o7+tmat_2kbjoe{aILv7U$a_R`nB8pnDsM|%rRoZbdp z33rqtp+EB&&!))E%PWaGRhi}wnR?F?SqTqNz$9I}vEw~h`LS8dh%$y$( zDgi~MO*GI#S8CCE>?4lj<%ZnV)cE5aLqJWXHkgOmku?MzGo}GM44&y!u<;xp7qJA* z2ce6|t;nrgjVLpz*K4BSO`X*z0=^+R(vKiC=RnAv+5~{+ZmHz_r#}sUAkZ2@nwTm= z-ggn=l31_yD9v&0uJanF9v0l!)i;s+6Mk` zClv^ISy^7t`2@BP7#bwDkXU@RL1;Cj(I;uuZ`Q}oPV#bIT6Ow?-c}!+H<0@YstF~{ zn@`^XJVuZno`R{?sUOlvw8P{8xZ*i#)^ycF#aCTV2+1D*x^s^9ZifxfY+qa3Y{j6Y zdf-}QZPU_pt_WXWO%F8$y8+06jNZ60j}J9-JtJA~_ef|9=yH$7MYHvhs{Gj;;b+}U z>s9zmh>NMHY$K}#pL0jw6n>0H+1BT{jmXgMrNXl$*Oke+U>$#APf<1hPIRG}rm8 zP(Kw_-z)n287e5}sct=<*RtVq)5VX4iXVqFGc(VPKl92jBs5ie$JCdr+aKCbPX=6- zc$6)_1KsU8dhD)~IDT`BAj=%wX=eVad3Et@S~jjF=nCa7%)e8P`uVQjt7Rp1vGyF@ z3F*NfHfR|Qr2tRE9Er=$?H#U~f-=3#n{zJwRjKm)w?&7-zth(|zcmVa3bxqTd1t=+ z$~gn}$rOAsJ`44HWLviSjtdZoGj1wg+4X`yNGs>iZ-?Y=C!vJ@$KjN@yW@@br6WHl588sOT6uF{D3^~fF8;E$iM$D zfE~}9{<@NLe`~K9b5KK$-x=qU&nk-Qi}RgxJw2@>?B5gJLKwGAxZg&z$l{%kW~sdk zig!;){UC;)ca~HdJ_qlwEg$vY4rYO_WbSl0m##z5ZW@=nloE}8$imieFiw8^%`Hv6 zYrOE0wLY$wG~S=>Um-gPykdI!!7t?5gen@Jxw)~3ubKF`_@nyFeV2AmuJ!8{SJl=F z+kL10{`CjOIeCRyo;Xdm(J4C|scGknqRUq&H z7c%DZbbkHS;#(|jYL)Pcs*qBsr|W<0={!`x^VwN zk%8aQ;l??5B+moV==sF!W$N4Z9+M87Ho&6u+I92S?$+boxfPl~2-JF~gYWE|OYQrM zD3e9$9Pja&#P6q%sSV#2uilL2HMu;fir$#uce;377jjGFaC~>QlOg8DM6v0L0mc75JEa-Eh3yXmbK5+%kODc|Cx1k z2)x4n?vFf4dO76vEEYpB0*NDwb@3+0;yx?qc~KyYraOP ztNXa8eS+ug37ZCD%!jEzb`npDI1SXE3jz{fYN}y4Q=vIe)k2V@u;AzPkKeyfG zuXAjPt3%51QG+=6=^dMk200yb#;^7B#6Ty!7jk62GpP5sKcKeiU0BN|@TUg**jZr@-^f|md8upyo?ad5R|3UgQPpe1LFuS$) zH(DEWc@*VPKggFy=2}!6YE*%#1vqtVzl>;RKkvA8xQMs4#*NK=d{1)tak4So-oPO0 zdzbIy>xc5mu%U0Q=52g!`LxK}te%=v@(Zl2o8%k9!sJeE&&?pPuXX_F8#r+}x)(Q9TO{rm>%v z^)&3a$p@(tNWarMCEQV+V%^x>^4A@3Ea~@l^e~Ste~jm`s~`A~A0k2fU1G1M8~;E{ z#b$$T)MbkD``BUD>jHP)A;p5DC;b#l!avFy7hG09#63yyH%z*1S?^2^^ZC6NFzU`k zL;Sg1&*t%h+P3xd0>S?B*9Sky0z_LP&2vGI-}uX!7zoGg2YH5wWLj0WT4nover-c_ zNcb_&owM1MdaNzstiqKX`rK9cyDVtz<=zi5C3F8|y@ej{ooaTM`B5~ZeWj*E`vVi% zsTxC7_S>&Oe+;v4SntF7j_6#z{j8X#gjfu||)o|A#h595vDAs??cXEl7WL+@{}dWL6-#!~G)1^(PED`JF$+%((ZowaF?L@!$i z%Wb>*j~uHC_x{WZ5jSCea zcRuCr)mTfiX7_6URj47w@Y64b>c!hsc&deFJo*^;__FVVPwE{l8E{>u?y=F?luttf zmq%`k&v4~KF{+RGB1kF=8)i@!Tt!({=V0Bh{?LONC)P!~IG9=0L2FgGPpe%T&zu+f6r02P{Kx=Qjkm!@~~8 zSR6p{$|h5-gr^-6{-`$Iap|&K>~7Im{ikJ>3heS+@Mb2{sjRH;ZG&_~eOKb6u=v&a zvc%O}+`OK+E!u!bPwfdtm6K6JtT=Pa@s(y47G_Q>YO|dDl0#n@ z#TVb_zBPcYRU2Xx8fKlBZ--|hnM75yTDQx8)AI|Mf8(M!-R2{vQv!3HM!0^(ED?iC zGK$_@N+ku)FR3<@)4^50cYYD@6}1eZx=DjCZ2l@extUoYBf9P&&u)1imH>ATIONl7 zF+ij1}z!KLFT9NXf;sZ(0tjf!6|;^FL)S8-U?*fp$mpL;Y0WR~IX5 zXPnBS$BwUoZED3)TSoy@bWl6Y9e%(qO6YMxf1us_x%o%RfcA=JK7+gP+)@3rv$=nF zVC&?cuqc#4XAql2pmEkUEC`(0-T6@rM0pdfn?~ngP0c^$l)LEF;hEX$1$hZI@G474 za|*F2s_Y6XDjRf>Ti6D!GBf^CELdv^<7A}$=PN%x#r2MM_qM4b_6BPBhe$;2C$LKg zv(eFu1hqV3Mf0rfX;XhQI6j&Mhl@GjzE2Rd&MhT{`~l7MQir0#N{cM|vnR8nk;_>mI9YhsmzD=`D%vIN!OAA|zWL2FWkw&O&1` zS@5&6rJkllD&>F}%;5$gA!iS|Z+2h@2_c>WWG?I(8%tv;9P#N4qa2R55~_k(TDuN* ztq`9GU`X8u)NjU(Aeh^#q>7HAJ<^Hr;ZNKRN7eljxEy;ws;-Eh5^eE1<9X=o0+;Sy z939k@E4ki$ow!UJ6miiM5PwF))I)NcBe|zg*qyCB>?|WXABTKjcH$&b6u@B$} zc|H%9RBWLysZ)C&qDX`o!t_&CV8^vpUB_H9C~@ouXu4xFWO5~ zFiPk!LLE-mW!2(<#q(SV_+mQyyHeLUz>_NbHbu;HUEI*Pmg#UD{a(Z*=wPyq#=QMc z2;Hy1U0MVdfgY?QzZu<=7mRLHt&JUjxKVJ~a{j<@yO+-Ndf*Q4NV1rYWao)Ug%8W! zcfNH&l<58-pbpB+lp(9uF{$s6z}=ol%vbS(K-=8J4f&F4r!9-A1Hs8?ic+|2T%O;_ zu@NDPC)Am~9o-d$pf4uES<^m1M*EtE0e6rP23jrw^~qZl=B~ZI5fQEXhp>*r_)nH9 zz=w+{M4~c5`-|+I&e7voho`o|5I5%GG-`*BD?hry=D`8OO?if{xoCI|Iijv>Uc$9`+AO% z(bdYm(LB28!@Z_?;}#*SwLys2b9cyxf!k~*(YugRcbnmfRp)hLK$XaOfn)?u@i`Br zgL<@Q(9(A0Cnn=Y)VeKUIJeo(ojH@N8DhIM$+s~pi^}uQG$W(>Y6v&ng$3Q{^{JC} z4m;~|JE*2}yw$l3@77*S0u4e*(igsyn~TdQQA_$WH`17N|ANrOC7g(wQ^Zh$jS&Ot zNldSO@>C$n7dvz}wHyF0`~FgY4kEZOCFDcwb+}dI^B8KD(#UISG$&Z;v@|V?)J8;p z;mDju0Uu}`zuQoHB4e(%obC=w%sCT9;4*TpnpbGnZV$Jox$lMZi%hoJ6+>g0fYL+o zL^lY^@f9QI*MV4ENAV#Rj+JqddWN(|0guzzpuSmVvy2#t=u-ocXUA$vN^?|xk)up( zNu#g{bxv}CIkQ_)QCeCmLdW|?NQBG&BNg(O1?4WnB_t1?rC9Bw54FdN6uw?LJ1Z=q zVl0Zo`eJ=lM2ofAMGD&*pNn5BdiY}p%YKd5|61JqO}aGW&jiLI(O!8>&k*OE3QYP?lud}qKN;==E?hvUc0*g3$%6kz2!B)tDr zSRiaMG>{{VsR==b^n66F#*AL=iVgfzaafHh<9WbG@!wC z+L5NSIB*qc3yIFPEZM|-U+f~}{3+hH6|yzFv?(L9xuv2xcmu3X|L<1WpV+))1TG8(>i6N&ulBSYwE>gLGpc zA0}#Tkr7UuCiVc19ifd%H+2!5u+4Xg{$R$nc zIkN$n+Nfg zSBEl*Oe{Zw(1C(37~D5iXH#({wRCX4{-h`bul51@Ay*uiZ7nWXpF&cZbmwi07gT1o z$M@^B4Y+h+#)&5u7c2lTRB7aKpS! zGu;M{G%icr@DK&){Y6#!?}nSXLNh0FdG(B#(#V}qih-w>^i!%CIXF=)!#}suNqq`r zl^Zocw>{o-=y8IFd%&i;F2J7qsq*Ldk|TCNpL;Z9B)H|EpSrlDcq2F#Jop3!@41Z1 ze-N5<=y#^QY4ssn%4AoBkUO5YGJ39o7&w~|JgQj@bGc|SwqY4ZS`zmVBLubKkht0f z`RH_($o$O)hAnbCK{Mbyt9no?g8Vel?|q)Zc0%|qGZ+A3@7E=DD1-Hr@7ke-XBI%+ z4|pmmFNT5WoqHcpJEL-1Q-*SX|67JI$hT-=GP^U_6mDWUC=YF7klvf&Dl>^nFx5rU3}aVa(hPb&#OWM-61Y;6ktT z>k0rwPSykR>K&mZFxcdO+W9u-+6~2)I?VzjP4=FQ1sK+QoV)5PH-zd{JZ1SlZ*pqC&U_ zb1XL>q%P_?Jl7R<%*5)eI+nKWhx!N@b3&LV@%Bw@B0jo1yh^bnQp$BbH~8<3yQ%BO zsyiw-C}Y~%TGs_Uz@(zbPaTQ)r}Fz<4*Xq$<~T8@G2+`@R}b(;SD!B&p&^{2J;`HY zt89agza#;j4!*U+X)4L295=VZSa7gS#m%v@x*hsAw<7k6G{5Jk4t7BY8}DTyquv|f z#~XQgL(P3Hu{5YlO#N3`;?(Au$j*^ylCHZkd74e`ADg}kD!N`9n3N9o00VVnYfkTt z8*WxO4nds(FJ;1jwogDu8c9(P!Z-7mciLgt7~ab-X5LjxoP&Q@qgH*-*n*y?&m{l{ z(ik1%m`dzb?K6?GK`!4_Y1XzeL)lk(`0Z5dqhvjZ?<+sFZcdaki%cd0IA-p?=ILH; zU5XT0p3VXvMjiq2#?Ty7xJLE zlg*$QTVQYIbZ@$H9N{ohR7HeBbZ+NZeo8efe8h3=vfJ~n7IqjTSyu@vxF(kikJ41qXikWatFJj4T56pO>JiU(uw=1)Pnfy4nhE=?Z@A>b6B8$FC zsH|XCBr&P&(0-7mxzUR>jxF8>+B*-B=m?`onU9jPSb_}!#6Bj+GIP&I#Yo_vS`rHO zJ#konM&)5QywlHTtrP^IIkUCDdLc4v|1mMR^Rn^7yxm0F)2#~)nut-Q_bAmte{F*; z?GIvpHmu}*p2sBH3G`7<|M@0%aqer;p?Hj+pyIMWKe`W6v9C+@#`7>YGRHl0aWG_j z9bz`d79?oT{T-MP(Z!L*y&S1t5TcI16nz7yVhlu)*xmaYPB1T#DgmvQxg~=hr{SSR zLb#o!f_Ob1Lnl=KmU-Gf=Ms5>sBhRJ7a0inc=T^nYfd39sOiH1=hLu|rc=*?3QW-A zVBiH5$ChnRm5#AumLsbH(l^%;%}%@2=pR62lSUy+mZ?vAdZ(VgpA9~MG$c-fOq z-OU4{dqX8kyBJ!7?R~@Wy_jw$$+H~l(#+=rg%~m%xHsYrV-Z$DuH)Sy0g)CVwa}TGDLra507?olg!{1(5TK153 znFS*aSe1KZ+L?wBWPX2lCfbKxnhty)cWIbr7<&us^MyAG6R7U8%PJdg5jKfA+8+yo zho3S-=Qi46?ucivW!I@;wb&nlmXv~G+*Va$>Me`yZfJ5 z?)5dK6Lc4kj00GYhb5$Wt1mG3M_KG?aSg}+8->;VzjCn6HL!?)W0F=zGuGQ|+h^{_UB;Sc!8(5sK_O)MrjS?LmJx$iH&XSqYzL87y~ z*~9iLivkXFnXs((KQR-Ijqh>%oeT#HJKHs7}d9|D2%B2o^xoMLMLSgxZv<{eMaM{A+ppbY3 z&q+jFP=S-3(sxg4RUpw9W{P0QxmlBHP*>f6S0Xjq1+WkRC-sszzE%uWib`z?{#x(AG_h`>H{fH4Ce*U6+x{Xxi_8&g$>`D2%O3mqr2oh9SE+ zFnBwG8lY@eJUBsu?j;1u+--M(%gQg@52-m+o|_f_b68%-rC2V%%ldxG{q#JIKT5;* zp4ec)ZslImX5a!dXe_rdG2>kt+M4xZb=h?CmP%wnvL|YW{h8iY4+^doPGwt`W^N>p zrQr8O`7tskhB#1QIoB3S|G|Q^LXh`6+nT7crN{Z3%^JYle{9};jZ11f)#ru}*R6=x zvOU5E%6H}>TQu(fh5lGm=Z2&MQSqDpn1bQ<2c{I4$pd!kD{cf9M0H}etXHJIVM zj?|bz89?gJ%t`5*hA+h9osLeLFYUUI3LtR>m|MD=xx6HakI-0xnf)Z!>qC!F2HReMFtVRfw^_9ZEdbM;Vy0$oeG`Oz!&hjn|DaaZM8W1i~By6 z5yad&lFIIMwpiY~MMAx}vqq<&aUyAVcYQ(%mB#^9K4l?y(sl8fImoJoZIt~>-^ewl z9Y21^_EuIido&2U>2W#y2VmYYYZAPg;RvTuvY&;C>+arJCo%mB;Yt0PJTT)OjhG(k z`ib)V%2Ykl*z7#<{i205t=6oG}z=@HuqJ$N$Z^a>%uXzRTa1jZC> z2)NM(0sW&=RWGm`gs?#>TYHqtD<>&59u;V}JGKo`enoSHW+(_wh%Y|g9u~j!AFc8} zoX?*^DXi8SxA@>?-7+5Ip|^|=9BuA08a6eXXZ|HK{ZDH)gr(z(KXk+QcQ12fiz>@Q zOnVVsXXWmO+Z=P8XLs%{2fZo?yzXRn73`_Aau)pe-297wT%R0)?LsgqAet^br3V{c zT#2Ps^0ytBI0iTc&(`ED&7tTSgMW)TFpWuWbe>SCw4aJRT)Pt=)XX?ZW&yU1Zyr7D zo`vUnS^iALs06V*jPM38%pWsLSRTfa!;L!Y`^L$)@H;{jmhb=!*E@x~&Y+At@|dN~8E(!9Xk-7Hao3^wHz4 zaYbf)6db%Q01D|C($sW1Chl>8(Ek9Sv?&!t&`+8z_1tC#o-Q@UIfg2T@LUPZN>oYk zU(C3Eu(EWYgNHQX0_-C|o|}2G_m0?cDk{v_bMsMn7G91$xzdXe?#iDuo~;PK6&w6; zRMvpu`z?ToM!Lh&(_U+0Ao3y}IAG|Hn4eMlxe6)&Ll)5w3{;Pf58sT}COs}3`a1UE zUJ+s*4X3Loy%}?oz)B}E1zV!&xG|wKeCdY>2iC6iFvxOWmu2e?A!`TkvDy})U=%mB zos&^EM1q+7&-Z3s=_rmgJbzbC!1@NIIrDPjhKQ>Yx`lXY1{l8MGNDQp8goGtR32d9 zfZQiz)RzCYbnZp}1b(uNjK0-1FWk|7G^D;f96LOt8s(=v0-WoJFCMlpaG?gFdb{X2 zd;UvEraI??G5}gHi1SXhQRIP&P()c)aTTJzo+IjUv){rKq+$7O93>z<9uw^{z4Uvx z7+m;gLlE7EkZ=`_x{lr8ixip9^d55B8g|KK8q#M@`==HQnP{gSDqYBynw?Yc?vYX~ z5y$qu!Q#Q&3M1#*)^v>j-s1bfv@E;2n#|5`Ug+xN`<6|iKFi%j4yiFd{>mCU(Y}9? z`^Vl_7+pb}28388K^L!|?2ud#*rE?P`XR^KPppR2K=>oK-Np_#dZ-#Y$D|9QGe=uh zqJkO6?Y~##<{?APt8I^#QnjhmPilWVdf};Ho*t0Fr_35leCesQ^e%(#OHo^Y5fV>Z zNUhLPb{Gqa?pD2BfUQ=h-B5ke)MA&DjDFuHGoy1ze{Ar`Ia?E9x5TDs9LjPfZn&X9 zzO`|7aE7_R|KU4UC@4`mSoXN&XtRZ3Dal6dpf{qI4H#)_G>m7wY!l%5D9u%BvDdkf zzg0cyWQ!<7y=vlOJ%`2)HV~`FlqTBdUHQAHc0h*}*7BKn;9G1%G1=ky{5x*#as`G0AO|I35Y&=yGp%GsXvcQ8O3XQ`-F z_oOqa>*+xe_hy)I+TG<>10axd7!jR{bR-5P9Tc^-eV*eN5+e{ycdyh!2&&x)_YO@_ zgH=VLc1Jju*}kj`v+2AVeR3mEY^AFUaA@35QQ==l8?$5FUM`PMpEyO^_?u;UDTF-c z%pciqpbTm1k4_~$-cdOAQi$zm)z9KQo{i%iGM%guzPvYtz0Bble)~mW)I5FLhU3^S zY7Cwt1hTPC(2>X_{pT{#3FnC0$&CINrZ<=1y|qUSs6$(aZpVRmr~AjU`>DVSlKdxV4H-f#2!Y9QD^+?1I!8pIHz<^K zhnD_`B3AOnMA>vE9d!LzXVRD=P-*gK5j(~GZ0$E}52F|+1o($WZ{Ry>732ViB_v*3 z6XnC=`#9=l91XmPQn~GHTRWkc`|R(Iww^8XPVtLKbdp`4SII@kQTvi{#SjOV;mt7%#RyN)NThq){4w3P ztQ;BJmX#hXnN3G}koY8b^w5YbxhyU<&t;Q$4p1=KT?487yiw)uCQr;EHK;~&)O_(BN%+c zVH(SY^NCA6cB@Bv?49@P;94uspHJMI_C}kE%;}V-cl0WMe5sYpOQF-9mak9Y5tw!~ zl%#v}RY>U-|BK=J(KHWTuannpVXzK5f~F5!WO!W3SKV^Gu+We`&E}0f!My(_a~ldm zZZ_^W!h0W-;Mj*51z*?Qp9F8f$AzivqO%h$_QlHoEKZE_x5xf#_`m=OOtp%O6Z6*j zlFs$;id3!?xMDlB_XObd(x|1QoVM%QI;(hH^WJF(+3@UUY(SZ)IGNSb{Z_$JsLKmW+fIS_b0 zboxW!;A4jU#F8p_A|)lBca){^;2pgYqkjnGOu9$kNu99Kg23p}=jT!^6Dhzl>5LL8I*|cT^1e#KQ~C%N z2J!Gu>}76QH_eQEya`u)_rj6T+t*ljhDgu8Ye8FR9ih>WSU=m1hB z*CDIh%VEXW{lJ&Frs{5NY6Lh{sS0||yju!Uy0OUrQZjM0G8p_EB=BnTnEvxUrm_(WJS6zR6qAwfvwNGF&WF~cWivcsy3VPPibfL6KR|T`b8J; zOhhtYXTj~)trOp81g&v3N{$5)%vE8j1+5MGY z$N8(i$3H)8^={T&zeuMRXhtgwey?o%qS4Q&>u%c3l(wM-TdjIqgX${2)w2z`&O008 zJqrzxd8Bg%e;5FuHPDkYXg%Be9j0D-&fC$XrHb0>x@yqGuu{}-e1h=229s9y2u9m6L|w{8&kGtmHX1>yUSKUXHqFs;K61g~HI5#3 z<0VU{ead*n_|@w{p(R}j%IMb|HMpF7jFUY{F1rGmJ&ak7lIO!Ufyf3B*2cGheS=5i zEgkvTT{j6KiHH8wm*tL=XC7}|uJV?yhyGlUl#(nGK7|&9h@PjAFJyrbUrCrteC+z?~j`UQ_83Z-NS`WX`XuAmaI8t5P zWqxE;k+>rp&)k2BD`wEQ`P7~` z0{JeOLN)EXv_>A^Z`-awR*Z?P=ur86(#~$Qf7he=XKwHr+vVaYiG8DJ2E7ogQ^hyS z5+0>C9APk5k3rm{v&da=Zg{rxuE0J3w9^q147HA+y~z3q&==4<$5XvZH8h6JU~NIak1zuKJKH^T@(LYK#jN9R ziPRK8q)H({pEm+U9C&7%axjzTZN))&(?P-0(8`ITF@(Pyj9m{-72bs5%D-p6_Zm>V z9*e+Lo3T0N`(oGE$McR!->M_Cu^vRiZfKSZaMqV$*U8&yHIemv0-QJjTnH!$XJk|iWYjY=>kK@N9G~clta92Zm%IP zEngq7OMhLuKU(2|y;Hztp#Mbwbj%gQ*jG{*%O^=D^y80*)m^N6ER|zgkf2&3o@bG& z_S^4^L-UrQu1b+H71Y$~3^)w>Ui0+HPS*N-$+kGjv?R35Q|Gxl49d-VJ#1!&klf%Z zPKG{ucxTyU^NYS{i)eZlj$8p}mv=WxurcP^_mT_hy~ZqG6?XFR^3-XdGBcfxSa{*H z&~p`KSg5}L%b2X!lExv_9wcif*-1aPbeOO>+i95H71cH^kYRLkofYU1n{egyLiQP+ zl(Ijb4-xFiq4@I6U_i~5aOIDFmjPw9kBcC!Sm0txq*{q>tM*+X%>l!Q4)qN~@_3(D zYVW!YtXPI(x$fj83|o2)nkVv@`UM$Yp6|%VzxkB@m2f=KX+zRL`^x`y!czfOJ>put zt7{8rZ`I4OYn%!}d$4h5{}Z8-caeRg-$vj1dg5D~P_@#=_B06xpY^78_j9F zvFC?^KjjTD_3|INi5-HP+vz~8;t~ZB#DVV%CQKIOQuUx!uU7bSg-8j!1|W0-jinaU zsQ9DsOoclvt^9lIty^By@8=&9(Z^hKszoF76qBs6IAjKhC5iP*| z@9kM%Y!f@-a9zXOV8@pxiaOg>?KN!zjAtt!3iuRV^~GsT^}W(@IKGI*fL|3mtAYQk zlJd$lx*6xY?#J8nRMbtF#|lL@+66hK{%7%c=F`Sr)mOdJdBGC)NPRm+26Nz#XEMJg zB;2t(Mb7$#w!K7sicEXqZG&1dzHyWPS$h=cys=>}pMK^5k}MflTGH`la%VJ12V*LG zH8q$8{|OH2QMH=LW1g$sJ!49{pS5R&?__SRjNxZ(9UcGE5P!4(>iq9%6<9V+$HLSY zKKFO#OMFK4<191UI=03CDTOaX6W+G}-%0_7#9-4&XOvW0qrPLh?>@Bw%gSG0WqiS2 zA3mPxS!h05`U}gr>E1wY+`ztLu|W@KD{!!WF~1lIXd8eYMjg9gSJ;aTh$D1-+5Vky zDF>&gJfD$2T^*3&)rSEmnwYH~QGK7K>7ovipZPXp<33( zr^!e&a}=(|H#d*E}Wz6?yVwr&oe!l_J`V&PQTj94pSh7UA#;m1&Y>9Q&CUoGRCD z9nJoNY122ImW#B1Ptzig{hMCm+>$$qpXHx3!>)F=RIN=KcGz>4mrQ-iR^eHT9Qb)6 zAwPNAZqJz3rd0U5In^a)W8{>WaVKREz8kq=CQ<&htoqGPG1Xa$8TcfrNHgp+ivDtu zr@bkV^TY>6@X)9S(n1zF36M{Do<80hqnXi8`Ji4lTU-9@K6p)S=wmq(Zor7!>3fPD zpUXceMz#n)AYK8z8AHX}7hFa)v+cD#p-Xz`L*9mAI+8fKj}Ty7+^k$wTU#ssf3zkI zAX`TcE2^(M8(EZ4rvkAN)`f!Iy^9BV%HcULnDn$Flas5fzaF)t93VPji+8lDIMo#} zTm^;iGI4@ri7aU_W4mWO0a3mooGU`Z;Ebh|x}gFB4DQh+jKV6|2qLSS>xn;4h(Gew@QbCw(#C@(;+!~ zLhaV0V(wqPJ;`!Q+#Rv7c{*b-WOxpXvXhBAj{^*6;}9%`+z9g9DIIW*uA8fy>}0I` zZcojIy*m|+qu2L|xVzhL_jzah8{hCnvkHrO8vtK}YhqPZU(g#*Sjggq9F$Y*bo1OG zIQ5}ggAczn++IvI>>$9yEiYrKN_LpF{6Qk=!a{$I(Y|_2^5C?f632sT*XP%59f@%1 z{r2!OMPoo{`iALz4^#A+VSnl!`YHpd1e0gG$c`?(nbtCcpUB@kdW#ggSeHcvb7-#g z!>29@``*&8n{?(1(|<6oVLaBZdFf9>0I!ql`SeB|V})aH<#rJ|?((DvLd^9X#zDGB zMl1N)ST^R7dY%^WpetZT`q9{JzUG&~eQ)(_xKe{NEAj_hbD@UPIEi9UvCFUgOt{jZ zc#!hBF~0nHG_cU7XaAQ;qmjn@KGItTHly+?Lvl<_g{gG`s)#=(zxA7I%_V80(L46g zf_fsOZ$4pq;BF|kgKr6c2H872`rHWfWcN*kK3|D?0r@+5nDae(35l#EjDTNFMlAsj zxW@{Tbdo5}GBpDWTrNt1bU0rEXoO1!lfyv z_;#!em0vgvYArXIa;TwCA&D*Ga2w6Js@Ykk631C9A2Lh?@% z^*hkM=z>`GfmAudkOZ$^lP2`amJ!1rEVcI_AxA zlPe2|k927CrGx$w{1u*pP_LIDj=X98xotFk(XYQ&&}3jN>zgS<@uElZi|}WgI_!(* zdPIoYS8SS3`5g^6bt;Pac$Q4s%HLk7u5i}BxK@}-{z?$)kSjCxtdHPnk-(8%MUL|6 zfOO)OiJgGZ#`Q1Bt^AL}u|Fa419i7XvC3Fte33>vtsme;n7$#XV+PFM0qYOC#NXPwf# zWzn*~cB|x4RQ6g0_ zR;w8=#P{oFFlmNX7V;l$u44uoeq}ukWda?3sjY?~4~pi0H~i{_A=RxR_ZY0)Um))1 zpZZ1(2YW;o{vSC5Jl0^Oi=p;c&HSY&Ki3USsZ6tb_FO$vyUrS9F8%nv*RVyc{}TaT zcd%?jRTda}U(Q}rdRVRpa}{#j1T_nn)xS!^{wsy9#*P@m%~2i2_Z_G;(7ktqt+%}8 z!jLfChF`1R?b~$3DE^cGM#8cCGdx<+`CRctspjy9U(3DN_s^11f)v(M6UL8}Lrh z?+Q1tCFeE0Zt;e?@kWu=gP)BlXZTF+G=!#0Z@vrak=@~SPUHTRBwK0mN#px=xUt$Z z+%wRPNyIrOM?B6ewHVkeXM6iy6$gjWJ6`PdBL;kjbjUI0Ko4D6-Y|6WZ!}UaYS%TN zPa$2};<*EDsNtSksjm8)(zd)#Bl&zy-+xiw)#%AD8A&OHxc*Mp^0BnB7k9iyA4>N( zRrQNHcy%zwvzlAJH>U_{>oj2RnO6FA+X#GyGxaD(X{S{$iyQnRoK)oJSt6xBVK&=m zCEP~JEYn@^gPFZj3$*gWCRdm%*M^@wnzcrcn?!fcwB$rdmK{s}(dsneoiZ*vsyLl$ zNXl+SJ6tNOjE(u}H&UY!o3D~eQDmg8D?#W$>2ft&7P~BbvmnIC^M-Q`@DePIl#!z1 z$mKw8&}0FV%!y6rmv7EZ`@G{Hzu1lW?~%DH^%u_Z^-f|w=GpqLa3U2#?J&k(9<;yg zS#*=he)p2jZ>ItEZuf$^PlJ&!Lg6fT!xkVz1&%I|xGIiOGo`~ZeUx;Me5;ZH_<$T& z;tT5pz^H`h^mK=(kFGMlsF5CY_AoPq`_}&I|`xj>k&Ep8{I#Wekq8OsnulpaBaSRqFE4O_Dv~B#rV-PH@VoP2c6DH;mDAg zk4L^U`@+#=-D3uBp2ARx0mmGiEwEC8#X60_6-{B=S#i2((Sk&R)2|7wz&vF``3>&X zMNAUjG9=EHy!mCXyeQew5y^cxoth&N4iQj1{Kh)2W}Ti_Q;V!jMb5L}g>mOZ!;gie z4>e=?*N0rs<+UpW1rrIu#wHzF3Wf`KCv%<*y%7TE4!YhN@y~Am9~JpV z^i>6*asd_A@pG2801?Z$sys#9u)Jl(&aA`*{+`x)SO8r;D}r z?k|6(*p`aLOslILS6kAf>D_7RivS1km@)EN3sV z#$i>I>5-pj@D%@>?{H+T(8Uqf;fz1J{*vy$in6{sSQqmRCtDh_hSK*mFXPgkAUt>% zS5_L=$iehh?Ap@s4<`|og<40sA@}D3b#*%Siz-gazf%fEINXuENRDC`^F7;$S?1Qe zeR+(zSazc9E0i}c+1EOrertA7G0Au7Mnvzw0t6*F7{Pz^OA7LVVJ zm`rA8vSZB-WQ+El?;zXyE8u`v(^9=*aa(Cw0uFcrTa*g0xUDDm(;e2*P@PZHE&yQ4 zz4924;|Yl|E{>qaUL#nK@TWIRqrI4x!cyV0#udz0?(|o033gsvRD$1_lL^j_Sw7O+ z&J(Kqnf^&fYBJ(61K26=o92b!hvUL8=?r%@N^^KZlK4k+LG#d5JyHzAt5&I(G!MKh zly&$T53)_+21 zEIk}kR0b%Qe&m3ZO8?$tEF}3nf|?(Aefu_&M65K|!XVQRvf6V8pRF)in83rPHol;6 zvX*|Caa%GiHBU(f&UBO+WDuMQWm5qjSc5z#CfU??9{D%S4hQx)NZ!3f9&$4vLfE}O%;D6Ki;%{mId9(i6#v_- zdefuuaXg;Msp(s{dPi7U_rkG8GufuVhu57X=Ftt=$Hbd1Ar8@=n%U%wmQ1Y;xsNkID%n4&?C!iQJY?wHiIqnsiWW-q@NVltk z>+^%_;5CcO218$8;c#I2&P7U?aRn9aBHdQ`3u)nn^)^?6$NHLHgW}vfMxoROhASnH z4t$D&^_auV`ks;mTR$c$`5;QT>*^~o_x?j@{%F!gX{5qKABi~S9OtFJhNf9t@HRe? zF*p3LSLfsCv|{ZE;)@>vo%@=VtNOOojoeVnPuw4IxkZO~{6=XdUtTDjhZ08PUR(`p zZoFECv3?>YY z)eF4FGvEfTyvx)nnl&ZPyGimj(d`wx5Sc2UzZH!xITIN?uN~m`>g>~`%!9<}4@Sm| zCsyWC2o>`!5BPn;Sc>&?NrcU-PlkSjzvX-doz^*QCblR&WMF)yH$0x?P^KJh_&HyK&w0WM0UWc9eN2 zx_BW_wHGrgL&|D<|MlYP4<*dqcs&y2`OdPwMCQw2R@)*dBjyMHE6u5%e_N~WdIQxa z;h^&7Wc|H=9fwTS^zP5Lb>EJtGH@Mo>dOC0no`B(cJAtL`#mO1zwY<1^SPogAv&~% zi`g6BLGZ@+5cIh)N-i|ksG}{VrCC+=cFb^uSQQaP5A8tRU@A|R>nFyhm3nD@!?-Z+#LfhGHj@h$rE34!K z!^mF>YfvcJF%VE|g0EGAlzOmWZ}m zuQ@sy&NdZN!4}DKKJALv{Nxg8%}U;F)KPXdnB zUH2IiKE*uHA?d;m-dUmN)2N|e8cG{$rOCv(M*nUZDh9yHn~}f|(5Bl=6xticn&x8R zYm;Pe*+R9rxxMTP8<@r?JN8^=o`2=sVM2P;9xWxa^xs$f1k~33%*c>OI0R7{g^uJZ zQFHoL>XK*k1%WoXM{>r#6j>qU=6wt0?njt%AMKv{~*)q{|B{wVe5=ozW zkOFs`kovkPX9u0^4EGXBQexA6iA$RLfGC|IPxPLiAfU-q`KVXr_v zaEcHY&w^K;Ot1SzY0mYV+_v68&w?~c9Jd1GcV7bjss2F0DiIUAjvi54#U?3!B;28W zJ%3h>y@W)1P*sxR``&xakQEThGUDyM{kpkFig)H|#`bP*r_pZ$s<$*H2W!~Mv4%DM zs7FI^dn&=cw(B{H?lg%kZ%t-UOxT>69-jzM#Ql$Fqw6vKb z-$@M*$glHdeC-PJgqOX%z4@I8O`=ysJTL-C~~C; z!9Vc6b>Q>2k|Dro9T!a7!j=6}ZkevAdp7UYs5cnjTElcvq0S)~URB_9Cm*UE;<-lo zp(9updNipZ`7*Mjw%XCEN}pb^ki5CvM{!E-SQ;lsXmP6L>>jMl3GDD(Ea5|P0w{+R z(JT*q1Bq>=>Yv>fXEK9_o0)=k=ng&5a~x%_eNSs@GUb&v=*tXTNogLf0Ip@E3+?Vrmo>z_aw8p5ps=k^9**L-BD653H zYLIoo<>wHu=$E3hDtOkiJUqiz>i+7W7*_vvLcGFFx<1D&g?)>#Sp{KtUOK= zV|FRktrM9^+&%Nmlm)1GAQONYhjmqV2F%$l{#AAs|2c5KS#V-%6();f-D){%8VGwf z0&3`2#G-=&2zgL)JIZdO#6gIW3;ZW*aa8BSC+N|Uxo`HHs|L%gTej%>bf%B(z<@&{ z>wuiqER!3+G5foSZhi>13XJu*31jK|Parm!gDODuM=a%^Se1R23E@LBYCU~9*oS<| zsDXr`QMVKT=0jy(K#PLnBZn>`pyjA=v-l^5Kd5X^J{#h;FAw|?!)$f$xsI`(ySs1JyI zLFv3--2%qrIu%U7O!$to4dF#zf1O8`Td{f13|bAPaVONyRr35|R%PFTPn8TZ()dg@lMyhikTrlJ)m_!q38O!FfCT!4srC>PQ>jLlj{ zJxpwuk0F0)G)j@6mT0a*gcUNmW(cB@hP!wH&!}gUANoQoB&HrOvl4k_V~GXu52S`5 z*GiPA@)OVn4c#h++jN(qW+FNizlL%w$g87>H;6sa68faB_CDpu18@-U+_Jb>Oy}Vd z)1+X+YhTI!;D<%lhkM2}t+Wk4A}1K|#IK^ureu(B@~;;=8{nNQj}Q6$VPsk5U7?sa z=j>B+7-}*25~N$gg@kn|iL^59K9kHWzlmQoO8S~C`;Q>F8TR_K*0$JE{XYqUw1zsS zN(ViDZMhDg#>X^XhUITDu-J|FwWRW_r=Z5$^18HGJiOr9`Tx$?yW1cO7V?ucEXTaY zUvu*uQ9ozc>fu`9X0xE!_$u93u3L7sw~~9Vy1(o{=0lh6*?ETj%Ra7J{LWhTsQmV* zm=4AEr{i&2$d-ru13#elJp7J4jJH2OCZ@E{?q6EmGpxR3zKetMrcjWOw_B25=QDeP z06^%G)l(Z{f)SN~@WzAS7vE!vs4OA${a+{$zDJbi2W{_-;j+-^J)V?cEo>B*5fpgM z$t)y{{bC{k0`FzH;fCdw5o{DmOs3)tkn0Qr6C<%-c4rm@TP}F>izmnSxzQgP@N6!3 z>0yr}FAhS&YNNGXnq>$j>uugJwIhqA$i-xv%&f4Ay`ZTgf^dKB)?Us9U&Pj2C+yMZJmk!=*DH)tSTzxR-zY%_wfAl(YK=$h4w;J{i>sSew6*jNH51DF=jLLK2(!KLpKP_ zH4shgnDO&6q1EoM-jB{^{ETvUo8ZRboheh@-Y@+oe@X%$8o%q#m)X?tK# z`NEVn!<_Qx(b*Otk_F0d@Tn2z=uH<~RKCW`o_R_U4NTDXCSJ@)mHRY)_@yH&nPQ)b zrTAN-+`MtZ&dP09-W&B`lO;O`qH-o{+Li(F3VP`dr1e9$$k>`DEYmEtu zJ~8TiWOmn&URe<3SX_FGh{5I_X++=kS9dIKkKT%-Gc+O4^=NPp}t1D-R$OpH&YMe*UnZ)~O-u!X+;`5>gjLKV#d?F6*Jwk_LZedaH z_8l^BCD(qZTVkLnw>x1w3KAH+Zg-bJ+?fibKSvv@Y=`-=1o-8K74W0kMFP{w0=o|r zht)%E`CqkNZU9A&P0+`}KXxJlI~MmSW*7h~_Z+6nz(%xCT#MPQ>?#iHiET#xb6pL) zFmtu|b*(;5@q!E%VzHW-L)_m1Bt-gv>lHtyy#a;1z8gmQVh}ySuf2b*ZjdDCE`g7K zW$L(8qE=PVb9k;#KojbdOl^Mo2eEZ-bGT{<_Mxw%1*j*(2g2m~rm0pV@9{p-4d?ql z(blE^6zXAdCYJA9!_r$n+E=;q5@3LBJb|Ndh0 zwQyGe&tL)>&~|}mF+YHG1V&e$dz6^S88~?Xhoaf|KG)Kn-V>Ugze1r6shWSkoK^yJy!q@M;un%wgPNZyGMB!e{%OYMfY}LgP#fk!ZPd{x^W{Pgofk*$plI zEANC>PR>>Xhc3t==dOLZo)Ar!q|OE0#R}w5rFW=}*RT_&^H&_zHQm-p=iz{xL{Sf_ z$E{f{z*KdeOEUjozEtRyY4B(mz!he#Z@W1Y9_(ln9KU|&<=1$B7clev&+!VD3XG^z z59ON z3U7ro3m>F(1!-QG%eUJRZWy9lE-I@_lktE*5UCF`;|B%@QcP0vI1RB?R;{MKJQZu8 z&K!E!O=)EO^cY+f3WaYy0K67XL}C)lHzk5M)t(005Hfz4y)?w8>LG1X3PS^39>{mZ zl}V1nK2pw`MOG-p8;=|GD-wA^n5}@uWL*wp#JcOV;SGRK(F?YshYbHu0ajiUO;%-W z(esS*bbgdyKvZaiTU$KsQxOFDXLunCr=w;zkTE1xo|W3?H{I?t`zjsHJ193x%JWL~ z*fRiGs&vRqi!^|VtBV?qxwIZaF~5o4w*fkcgmG3UkxJ20>+9aFNK0WJg9pOg02Cy3 zY9oA14#co2^Tynt6*?J@uG(VErwtrVWSio1Io4BJ5F^@oYeCfO6_R3tIp3{6Ox_6J za4?~hDXE`YETaP5y*DRe74n&0G22Y|3o#&Zk77JvUbjOvGhti?l&tCp#H~uuE~OzQ z-jL(?Nx3WjuB4)5c*`5_9F@F2z~> zh4PU7fMrK^jGEsL0LLS=C`N-L+)UI9b+_5CcIz$C3`Hq!+8o2WacVD&^coQ(GJO9P1Im9y(5k$naqP{?;=WrF`sK&mH7TSHI!P4 z21pDngE--EzLmFvHBy)@PW~)Sz%zBi?L{DBQv)oOcsrSPBtIIycZ?Z%<2i3RPERo! zSjg5_xLE3-6Ovjd*!=aB*pecmJHj$KIsfVN_AEHjj!oEx48{w|1vF`SH1e?yNASn1 zK3sg4+hadUzjC$L^OF}ixBEQMPO!oybR03d5c{-ib$|L$l~@|_*ZKgntUv!-G3qtJ zmAb$)XC2HzJ~MNNvj5cv)$=Brk^&h0c;l2kBD{L>_|HVI^@%<6Y*wr>< zDpIH!%I&J>&^ylW&*T2MnY`ZsB@jiPNMZSh)_f7`NaAxgB z>$Z~{w$Ue8zo7aRVGehE1-UYK+rM^C-XgR5#0rZ@c*>r*48=^jsh(0-oeh0?l$ZO?^~kJw{IMboQ1YFiEitbMHxhp61x? z@2);?6F;1b~C*|*66+2olLiO$)rHl=}uPJ`;cGRHq5h%cMDjI&{^fZxwa z39W1L{vk@vh)8-}`>YlB@U)0u69;b}bHeG#DZY?th88rkaQE&?BCdbo)rl|ol9#?e zr|Zq<_O2+KBSJl2B^f$^&!{pK+pVlvM^2S>` zevpk!qgp>#1t{zqw=X?H*Il~OjM4j8HkN^6(+SDI{C$<30nv58CZF@=EZD1_2G_>b zvubqiZ%p|KtivjXJ3NHrY~LqObg-Xp92ru1W+>#JKN9kdlWf)W|BZ{Zs{*9t-48B9 zHbjmf65zlmV1mqh1(HmrS*Cwv81M<%y>d+M)$j@Vj>DhL)U=w}y-W(f0 zeQf*Q^|`Xp#jf*}ApyX#bN#D>*^RkQDXCkL2yvtJ@a%`vXD*{<7>(s62yjs>QTiCC zLa4KZbiD7cU*|!eB}in)naQ!ZToy$h9S$l!*%#fEeLyNc}YH78ga_( zy2LdjV>jq`AWaSJLxt~w_(bb!YGN7u`p9d&f$hizpoIyQvYXt%Kh2w&`uiN4lF?2(|Gx6zVFZ zRTG_-KF3~|b9z8S-@jO9r)G1S-^!cy@s>hFyo5v}jFa0`Ik%CF52Zy755WyihHs0% zPAFdIEBOSWz%t$K3;0bfY`qLQfDTQA2R6`IDJFNxa5r0`NBa*qcFZv@Iu%nA)pc_0 z28o%jj!%cjBeo}Pg@LX2_#GY3HkO3cX4KXY-8R91)ct#dQW6ZQRcV^LrJl)Z&wjd@ zyx&`$65CROM7jzirPPX-L2#g&JvfDdrgN1O&#pT(Cod9K`X1$|{+D2kdq-w*&DF!{ z4u+T#A(eP8XZs5iIvEz_9R#7RS9Q^K5ReNp!C;e8Q%(oHzs@gmA{^q+qhv;SL9|xU z1ssE^G)!!<1CY=v5kBs2f=A*;BsnJspF=1wH@$L*6p-nyZ@I>4oc&o3 z=RitaL_lk6p-nm2AO30NKtKN`p1iXQedR{%Qa&@_Hky5>VV$rlmu6g^yUXqAl2Qy~ zPA>m#&}7OjNj?FhND-Lw%KoQvSd~PIMdR6h>$Xz}=Hm1LwB1)24Y!)2K%~tq5Zx^3 zm%drI#0yP}zdKG~6 z{CP0n?rrqboBmeulGwy@#*ayh=|$?UEX>pO-@UrU258%Ai$DJ4DkqZyqQO-(v2^iR zj57itq|HGk(3NauhE$uQVe5M<KEh~7(%pTyDfVF3|^&S1^aKx`Nhrl+#6 zIMuBagP0Lm&v633K;)chOef52S+Z4H0Vs(mA!!IR0pFBbM5{~nAZD_NvsRZ_`sDne zZfS7QQ$&l{4N>epX~xvwWa`4Fh-XptvjU*dVd=}AMFE~lH#ApRA%zYhSC_+VmbBxI ze^~TDXr%*?vQz@3jS<0#>oKmC(x6SGdRMrY1!y{0eRamf zh(nwrk$WXH{`@Qd+>GgJF8L!qTb#ING(b2V=4fROac5RziQ45qZ$=H}e&bxaCS#@d-K(AgL~ z{#*Z{GC58^^KD}ChuJtOS_xYO@)&t*qk{~3oRM*`XM)W->Rnk?+AVg$Kc;|XgbWfq z>MO!bma4iKjiU*+H;@2UFU9%L%1K&4*qC*o+`L{T(%U%dtu}_Iq5|hEZ+=kIm&SxfOw<$i={vM&1{CD{!%bzZ;7P&V2(B5~18DCbwivdz`Da^dx7@I2EM64^5 z*WMZv53`Hxst5JSIL;9!pg)4+<6{2i{8=c+)0&Ku_qP#(!`4*M$YqX*FkG|}meACN z&J^o5PvTx<_;?UhL6QSJqQ2%I+58hCpPDohBBLg{wXk>J@kB7q5S`yoyV zkCD)Qt{j5NqU8`84;|ICAHj%Q&G=me9k10R*Qv1;F%`8P$kJaN)=@yJ?YnF4$!@HD z@f^ji5R`(sCbngC7cwU80!SP8q8FJbclDp-K5~&jNUf9_l`|S+==i0Gornj6(3iQ7 zJm>e&5rdc?B0GMU>qvutv5=op5bxUQ0na;e@j;s6WqwScuiLByA0ZjOCsu%B73jcH z2^h;hsjPDd5F}yH)XC5>%^Ad>tR1%tH_ZepH_M1U5{-RJ z&y=vQjJ^;2%pU}-T4$Umf{@5^;_5=So5HejTOK@w=k0_&{OyABJ=4=~6haW_FF`v< zpgKwxnX@|daxMxK-~Ap!9i?9i{pGEGghvibjEfxpiGP0c5WPHGzQ5-&%Z`3SF5NH# z2WH@gui`M7^bm}xFt@q=8L$ZBk`17wU-bVjbHSo->fRIvy_Xe+k813ruW~jJh=UX( zoY{QC2#nSi_Rx(`-Oh*4B_JXNX6IDm7$(Vp5!Ec-$r5}2@C@l(tFq%^M1WiPgR58A zX7vasv{~hiuIfy*BE6zJo&2k~E*VFyV5==IQE~(8PB&{|8G~asm*~?8g6?D(pS+JY z!)$2F_p8oH9>wx%-xR_{sv(GRLUqJv5$;z}MJ9zTJKkLD6iL|{t%_Ly<45|ZnEEJ)s zBooF)Pe~)3uGpB3S4~MX)zWK-J6Xyu;Vq^k0krJQI=Ec@%_!TB&ksA5T|cQ^&@)e! z#7H$q&uwF_cMJ8Ir_i|qAr7QxXO0q#N4fp099+QXANVMFbMDuPD)==xf8*v;{0?*= zl*0Yke%o~zOk7%e#*{u4^n6V`-UuLD0kbePw2fTWzX-?Gj|=4Is?R%*sGRAa{s#rq zThg5tBby{(S8uUmpW||nh~?XRd|o?s68kv290{}m{chAorn$y4ZKYe=QS7YpGEK!1 zn$4@)s60=p@fZdMR zWIQCy#G$U{X;|d81(Y7ohz*rX_ehlutbUCJ?*w~N&A?P?!S;~~nB5(`1K0X8RB@7W zQ8TvDC!w~*-=#3FZxUl*?@~vNaG*{#i;>fO-l^- zR17<{=7Qjf2&%2Ol7(PB&ky*B6K0Zf%Omxx3#*n-7{wBZR0`+FOZnZ(8W3h+RuWh? zT}kuebWSQ)udeSXu#LqkK+hzV%4(isnwjAPTD z8H92-M`$U+%;(WuIQVLC7_Su-YxjZ#!h)C|5x%99cvNN_V;gt7X4dapHbVo*uGNNY zDL&G!<$Xh;e@E2NGea3G1>&Gw5tI>JS1QlpN5qEI)5Q`nQ7}QJv1k2D87;Y$jgy@6 zidZVTTLfQMoUQatu>%-}a4Q+GarnOSf*}0_f+LC}wAlFnxqTPMcqOP}pENotNGVd) zN`{d5!z3JqH8$fGKrle&0H#WrC13AkT0Dz1h-&H@yNKx4m)-+wA z7RPSoF;7KE3z&caq$g*fgCLvzJP5vM;s7*;e$N_p8DbE(3Oe*`6YFC$g7x7dcwlpDXq3q{0Wh( ze)+M>qsNfhFj^c$9a_o8W@2cCRLb*l&mW~%f<`S1~(|MHtVfVkcOs_~w& zy^T`J?TeC>(S%m;Pz>)CMq-)nvQA$elk zre_O?L;mcr@g)IcqRYAK`R@O@dfb1qtd2gQo^k2T;v9mgPIhS~Z1fM$}^P<JE|wzLVp!ag^}2`mZ?lEzFuk2yUTrs29oCRZ>!!Rt?FG$9zZi<}oK{u?9fAuvIfpi+$no!f^7Q@ikkj#~kJ}eL z!YTgwA;ae6a%+3*T2Q~spPdl?L$9>K(S>E`JsI4s2N@S@yAEARa2>UEZ8rcM1By4J>CQ9Qe0D}xkI8b*9rF0`i?@UHqYLy@Eg{Ptp%jU5 zDSBxzj8e}qF<%}pLt8^}2H%-pO8F?<%d%FaG+M2On6KLr;8q?;4+`j-cSnrS;tZ*n zh}yz#yO<;hil4QX()2=~w~+Fqd+I+}On+OyFCGy^M9);u7%fQasVPzdCCkyu_i4x} z)~f9|iE&F6!d}@>$MZLQX$XhvcJ1(QK5Cfcw|trzwJmRJDu>N%lZeD=gIKsP-vdC>=G-iwwI+Slxa;{3I@T>gwAmGaTXxD*D72Z5_kePP8 z(Sj{_yMyQSsjlI3W|Q%jQ(T>Rc6L!iHmXO|hJo~mG}0n}H9#aU<8vN~_PyiG0d=*E zTj0%sC=fALF>aP&mc^d{s<60IrbLk=-4}XSj>d!4Pj&7$6i8LncPRlKM401Ef>)>) zblM1lS{0NNpI-6_xb9?vPc;zJ5>sDr8(g2%@ebWff1e-g4J`6ML-4Vn8=74t6e0wd z9G2M58)E~(#XBwBR6;}^W_QRCR7g`aTt@^OMwlFV*ayhH-Yfl3=N6GlDJj{jM#{+| z75Ta{ZOLpC#gwHDn2hf_TYUDLjP}F$67PBHFFeM}h?)^_j0(rv+t#&--S)bG{+C3# zAN(PDGgRp7bcu)8u>F%)VcpfYb`~m%IyOiOHyd-1587r2%iPtjb_W}%)iLjGafq4|pYGJn{f-@fAs-a$#{G?SDHr&90 zr*X*UP;B%~;+ydM1tb@JWHVjX*YxUf_u+GcYChPGd7yoeE1E{oE5S*!w zxmaYI^X9a8{{Yy5;pOiNBi_OakIU9RNJpEPoa4wg7xaj)?3P8LF;zpHuh%>)Fz5w% z+g*+rUUWD?Pz;xzp#v%JW%=h8^&j2qAZZ%1^1fx<*cSCOEQq%OH#H7P;OLz~`O>VM zSGxu*)a7{dly5=RGzrNCkw_kdthw8K6S9Z=y0?d~^fz9gfTn5?{m%nq^Q7})+4B2W z##Q=vOkKz6IwZKkRjSx}vIM2vVg^l7wqV5{<_WBr>38Mniu&cNazQ_WXtFkr@zdOh z4UbCl30@=o*sKNAEG36w*au7y;NSDD-`>TYUr`%ab&u%kX8>PtC~jF>1f> zy)2BUtOPS-5r$Zk-(j2LHWdYgjGnVLI^R$V!A>Fu{-r4dR`QhvNK}~J{y9t#z{GScm)!f1GC~vACHcYmSo4pQflxc?PxH^*$bDWi%Wp=@u`U-9c z`I4As_)Z)ih|bjehEIil5d%OXib-FjA<>R~K7CF-tO$V9HQ1f+r*F8JA^|9sNV zP0Sx>=z}q3KGRM}};B#1CUYM@#Mn@Iv&cPlSvZ^f}c{O7uAHkYKar78N7w#hmDB zM6zgNvdIErw*}+$zaLR6z?Ta^RqKb*Xz8=%uE%GVT*5YK1ice&n3$_Q3@w?5m;kqE zw+AmB!}y?FWowkXp*!nsqqUlDaJ6(nM_Q1jLK`gweaKhgpYW3O47YrJmPu&^1hT}8y~cGiejR`si>}f% zQR?uI&R6=YqtgBVM2@Sk><I!?VW>=J3(-hF}x&M7|@b-?r~O5GhFhAjqO_ zQ6TnyT=R*J!p*bCzb>&ici#T%bOOrchAoJMrL@q9-5|4LY%j*}UxN$BD~G~R3GJqn zS`WrKwHv=;WPZQxkrL)BmKM|JCl*okdW~`!^cgHRO`jdLDlEli$mFiBt9y=kHUVlV zq6gSRQvNvtgyH1`H54??T4X6|IiKy>kB!2W_TYamjsk@IjwBrHGehaq=kXIYwL%65 z5S=4>0uZB1xd4u8cqIgeX~;H>`=Q{qg~Et}UH;C=;xl~Itw(n= zZC7tGKZRm3Afr)-AfFWfsmq_?bI+G?sE2?L)xrzPgf~_HTJe(Ukz0Y6Mlym!wk& zn8qP(-5yhe@kD+E`E7DHZ`7HbH)BNstz6M}$|=lcv*NuVsT^{F=zF}ElFb{P0Z6zZ zDj^tT19Z9)IO%IK*IQqGIQZ{zgR=+2uDI`Yfvh(|BO*SRbIs4=SWjfCi5hul)oF;w z($KUDe&;^#b@XI6ueOpt`+Jx7(Q|F~aRqR4PA|q9uOmua-?cyBtAU{{+gH5BpRpywTr)#?MWZw_0}kZZ2~jlHW=- z-bDEYXX<;Tr35^xDKMnj#)zFgVJnWUz6(JObyafwr@`Ll!TtZW_+O$1CYa_f0?pu%&{2=i0 z_PHq$trIGjCYT_^YsF4?a2sxG$gcZw^b+9IefG%I@%+qCPfzc_0`@DC5;W^{wBDKF zUs6OK5t`7Agjd`)|;N+8W_!z+mW!KyaWL`u7lY35!Ux);`;P>cKy)9^r7Nk5yM!z?N^=WAUh)~WX3I|A3jFN8_c?M~@ z2o=yi@B5UmCGY8kddW@Jg=m$Kg$SHq5iqY+M?vwH_4HLW^7gCUI!AdzlD0wYvhG{s%!-<3=HPy+|Q!0g!jRJwcmIh8T7swE4m`u(@1`Ud) zsJI+qW<@CKOI|$MS!gn$kNoq8URe%{@_m~K|8mZ9KRD{niBa!u4dm|?jdQ1snz?D# zq^58y1tlgk?hq$8f~V6O@<8+tS|#xDWE&ne!|aJx;Pn(ACcUOE{-GJc1=5czx?u^_I1;4EYNdK?LqM8 zYV9$=y#c{k0Li4fZ!H!8xihLCM0q|eCgnmoLT?ZhZ6;hh-Rm%@?`pE+z(DD)?(en6`hsL{`-tX*ss9&A47K>XbCuu!& z?YR=+QoDX#l#vXr!6Edh&wr0d=uQv(C%^lTZuP(yu&MSs$8LH8|Ei5SpjP~vMc4)! zU%Y0?`0t2bbvhnpws;QxH@6`8`@>-6rlygdGLG4{ZAL)(IO zG9lC|kFlV^Ep3R2Y0M~Gk|KG?=>x1*;c3V6hGu$6v8df!DMT^GfiE1nvj0%Nz=TMA zR__AJe^2|};d>wY-8PRevAmy+%w-OFQw~IZ=+H~#6+uigb#HoJKO=1POW2#*EY{xI z9o-Ie!lkD5^j`2fh{Wewcz$F!z*9fK>s`hGM95P`So0*_S!v5xkO{{h*qeRxeqZ_w ztb}sxjwGIpo1A2#X_TgwD(T0C8PfZZ%Yf|kzxwvbY#i1w2wHWFBlsC=v`TQ+uG zr?_@zESo={4&TolpI}`D_}fswvDvf%eL&ND$#(g2JoOy7X<<}_#iBiClyV#k&1TSE zN8$HW;cfiM28z)Uzi5_F8Gc>ywLic(3MF8X7*>Ew8a7pLdWS~$rB@M zAwBJNViJ+HFbwG9kl|6ooa_g=>n}GUe`Grh!z236^sGc@qPmSfJeoZJI~l;ChL8Nv zsUFVntpj4_{Pe?Q+B@6cO5cg^uMAS<@oN-7Q3l7*MlptX`)*cU_A;Ot8DZAi2l|;(ea9(G@zw?xZKG}dMLSnJsJ1(%6h80zq74MT`8Mm{Z63EzCf z^nspPNsU*T^CL9z=1i&Iih^Rz)Quz@?{lQ(uDUWd@p&>u4rn!+>k9RvKAt64 zSL2AK#+ESBuNg)Cakp_81^AKgEZP!}uwlqN3^#`?f|L?vv)ZJK>`udZQpAnF9tcxc zHq^o(Ymlq?7d%I8Hu4dnY!0s4SRMq%mVAm=QbDO3gV=u z`{}jtQzD<>Zq3N+#Gn;5SKQnHscI}2Hs%HDU74xyyPctY#n236VRf=G!hs-1*cjm= zqDpjeCH3;RN(VB-D&vCZBD~9{b#jATqp;#P3bp;W^CN1K4d4*eCrNAP{r|rjX09uTwxM7 zvnW-z#%Ti$bg*&Ow1LuiE|E%NS)A)qc402P;DrX;-<`lici5(!wDiZ8xOcAQ4#byu z+XT)}r))~5SM@r^5*IV{VZ-T`IPivtvXUMG(G@JisNX#pIAlzO_1Ju5AQV%IOp&wk z(ijQFS%ooerQEPDsO3qyNjpd%)6G+mC5Sc!i1G8fVuyL%svxpIxVO=aTOIL+bb$S+ zF8C*vlNKPGf`OpR@WL1YEF)swBi(w8J!Wi`pLZ{ln_0yt)auv8(AngfgJM83 z)M-8-mYQra>@kipz@0u}w0=ljfe8KCp&fS0{ZwDs=-c^YlR7&1xjnk;bI^ELh~7}X)Gww#~GLnaJ8j5vFTix zA*QG9`c`RJiHJ{|IsZ1mG{b%`W*hhklL?zOcuWLAJVb5vO2S<8==PRw&Tp3C_^K*Z zR?R9457?K<=&+PfNdg)1i~8iSMO-%5N(^GB2uKG7GDFx3jt8Kodi^v85Kh_u%*|$# zsnrmf&qf(fLew!@I`2&%*Ov|Ovcg;jbj3k)0+?BR~g!BAZzu6{zu~9TH5> z^D!R&5XvqU&Y-Sc8-KHk5k1K7PVFsoqf>OlL_Xv@$Ci7E>l0fYz!n46b{&hiaW}dm zA|E2~3~w0IZMCpx5t+JFI`VhFj?K(!0;t`Hs5x8_*}6YEA3QG3*Y%&llB+jVIxea| z?F{Uz4#Hr)Y$&=Po}}k^88~NrXIh6A^n5W zb*Cai`wh79c!Y|)wp>n&eH5n< z^D&Kgrc}J4nkpc1qy3u3byBRjP-VAkiN@zVe1Y!hYb<6BvPWLF8=*uU9mTfguUy~?6NR|p%3~hbL-Q$ z8KKZJIY~lGiJ^1ErVCa0GNbLl0we?uYhW8IT?ikg;-I^)DUX2LHtmfA!v*R4oqUoq zA$W7;`g(PhC$4@MV(Q+wDEr*+`RvpaN+2dLqaPCp+>)P^gCTlDmgT2^>iD`qwVPd6JBzO|P3&6ilVK1D(Ei~Cc;!&4A=}T0$r+d?H1fRiU0y}kkJ_IBlec^| zGwHCA?0~@pGEiQP>Pg%U(-QPtgiGH9UU3uncVAg=h-7_j9A4bqwh=J%%dc3Oa<1xU zBTh*gT&cpxKMXF6Q5Kn~?H9)ctK05$U*GO#_~Dd5f#~`p1GnSdx-SANG6w5T?qSHp z#ouR>9R`y((Vihd@QbFmJMxaTsbPCK>PWg-+gYpksm#r>S$_!Ax_^#({+(I#s^j!ax zRq77kb|djKU3bI}CwW+y{hUpgVcj`MEqq}1j9U`7!=Gs<7ivv?w1qMUb{w z<{rM6X;w%EfhHJ@%9w5v^I&JQJ(e_K6@%4-9Nx7?kk)hz%9uQh2Ox@^cFOxD{X9Iy zgv;FnuT+RtB_~QNHr0H9`BI6+&8ro4D$q;G$&t#(KBg^fbD1sO(5JLP_0>_G`r@Q_ z)Z>-^Tn!-cyjA$9nnlN zuDF!A1N#L|0H@nv#$U~!Lj(-ZpDwG&*@xx;M;-MaVrkX+i>8T#tUKqC!n*xnc% zQ%V?(Mm{T&ZHA@o7i&gYF8o-mTI<;pCBqX2p<2-^>qIz;JRG1~uLiC**8&Uc{i;z1 zALAzH>q@A5fh=nzzt2lB_~)GY(NqYQva|-FKAAY*FzSgh$sa@4QT0ea(}X92epV`) z6+lQWe?q?e3gDyJM^@{l9}DM^VFacYL1s%cR8wzzfL#k(hR}wJ~l8v;fGC{&cEVU<%`w zrBJ(G(}a^k#ei-7N8C$4s|iyvglBT1Pb`7qdO^A`lA;^ zo=g-6iBu)86(xFfwgk5qIl%nn{yI%AAuZlTEyZ63fiI7jrjiAJPs0|cTBQbnjIlJ@ zok*UT;$9Ci^->G~CKeJvKu3}7f7urO#9yOIYNIJ|&-7ydU=M(t_vrt1QY`!IO`Dmw zd?+b|Nt?z(hdkvfopr*5J>d&LeAZVYw1#v=QCt)oai=At!f<8c1tOUy$4+?mQN_g7 zO-1i3^|)vK&1mL|X^WvtCj{CS-LoMi#8JpFN!Gf4@@x-)3NFC;iTrt_B0Jr|8k&Ng zUt`}G{pqp{ci@KYNPY)<-HnGF{@T0+u3U8ovGhJp9FR(fz~M{tY+7N7INy;2Sx^W+ z`j=0M4ZmK=5t}GAkPrYT>)5Pf+nZg=fj`Q6HI1P1N26k1DTDfzIkApqhfhwA@yTu^ z>SEm|s~sd^Zo~3~nmFo0tm`+O;TMRd6^{X!5JeYfRST+YO8<@- z@W)a^u}*Po_G^J)>AR#i;}-G7YN%y*z;^n>!s>1u6RAOvwX4NQEztFlHGsNCx725p z@oEe9CmX@`Yezm%Efjk=q>KoSaGg$Y+KT(%qC)3k_Qbs zBA(6s@#o|-yQl~=KnO<|>**MHZ8+UVP|))Jv#T*+Bzk*wS^HV_>M@|QlxO|C=(v*b z-oaZXSowWT%Kzq?uaWR#u0OZ{At&wULjMO1liRsCO?L7a1xj8U)DAffBr2P&4icjb@7JpO`Yi& znlz6T^M;vtoc(=9ct_3=?1Fg}jLvor>!)!(4)dmUI^Ow}gR_B}%-FXeB&3S$oZk3H z0s1y=_epn0PrM2o9M!4<7Q2Ftn9XAY&e7?>`F%9bg*tWdIrZ*oaE$S3Y3_SMd-mjm zu(QyU^;K2_LYoz|7ugeqjv0XnLgS<7S{wWqI0kyN+?&0)pdWL)_K`8{e2PnP7XbV` zzJ2ZXaxWZ z;aqPqSvQu=1L8nqWjSvu+~=sTfHQX2k^uq5%FM!+v*FPf)E%NADkNndUw>EHtVreI zABF}i6^lJv1O!)>V=6eU&@|DSB`%aFH25N`SY$yO6l z*&cE=Nm|h1Jfad8aB#@ zZK|1gxHeCX91js{vNSO*JfVmm@B0ogi)6odU=c;&Da;qHjNRtVxqr6F$op2C__#V$ zXa1VhHTOX7^Wim&N~9WQL@qhzfrV>%y+n89=0HuL!>O2qy}SfIoM+1IC<@hhf7n(z zO)q)aZX@zlFqDq1<}xL}1Iw^9!<+^+Lx z{u~vhDyn&Ke?Nso8g6nZ%-A_A2p4Ge8}Q*V*ogH{eR_%;Lf(IeV2%N?Y1_0aOUnt*+^C@OPrpk71S(){`src(+J5#eceT?I z_SV~Jj>HOCJ22#G2R9Pi+PiJu^u&-+O+dlY~MH;ufEWq^IGPeKfGr7 zPpwGFvhXDTQXaQGcy6|fLnRezzGeN?h8lH7`8l@k99P`IHO>jPaL#!&&yyd)FF}|D zRZI`)mQImPi?UQyu_H}UFh*Y`{5CH&R!crP`-5SN1r;Xof<2n~7%e433$s4xC{`dYqv6n>1?hdK`N(eMy9GQLi@wWsD9!im7nm0niI7u7 z(Jufp6E! zC^`5XsbX5Rw3|-W&l0B0hM@PysG4xNB4png)n^Avyc^+mnS$^8HpkEZb+f|-B#CuOu z$uM1w&{ICz9<2Z*P51^gLRlH}XU#6-B;XDHPK?&Kv=eil0hlO0%kj_0F(M$o}ee=V|e1W_1snT~uFeU9OhWuhEF zu-gYih9xG!UFHf|M3r*@MT0O==Un^~_s4$Xnmvm~#2r z+nmZPdA{M|%Hhhv-S<;b4#w3QS$n)(!wp}JDVI;b&8fVS=Nm4r9IhO!ym$4?-S;=v zMMK5^!Ik|7-H_)SF0LG|9IUDTF{1FK6InXMd;a{-65xe}ncboG9hK}jIcbyBN&n_2?@1Ow_2fd zbyaueeCy_%b2|4Vzu!9d+`83Ojl?|6V*_=%>z?qZ{qMd1z1RBox4w0eRr2*4Y{y8& zX0S+W$hsb!vJ*8UHoR73Z*(`>az91@h7OB-=?MvgM=O6}DDwpfbQX|NWDeHAI;;V) zK!U8E<>7djBEl-Wn(424R5!G3a4o0dw3^G7{ zrGww(%vU3bi*Tci?VHySVAb_7Y-OlwWj~93_mh(JpSShc0tU@*T|QzXuQ1uO%H8c8(v;kY&zdDZx7cF+R2F~yZKtV8<^6h2$fP- zILexI-`;K&)q3;Cf=E=s~VNyk@_8^mS`yb?7Xs zoPR#rW(#Z7-A2S_2lFgC!1eJOLN(HXiCS0Vggsh02Gb;If7pY5%QuxempW#pKT=N_ zu#G*egS1Lpntz$;gG0T?k+$0n^wf1_^-1 zgUy5X0I7Yi;Y2^%+i8 zVw<1LvGqZW6oFbC62^FX_^APVjC=U*Oub#a+CXneSyRiLhe-F{D7Tes7%J>5<5KThXy=vTRSa+@x9p_H_4do4!K4{1gXZ%V(Up5G)^0Q!OWfy32r%jqUUHlWo`RC<2wL z;@=%Fvlq^_pC}pGKFmzoVld# zMmtC$P^Inb_CFjxYmX3aa07$(-<~-}AHaI1o_z%l15^&8R_CP=qLh)!-s2$@x&}sk zR%x7Yx(#cop0zeKSH{*0>`YH1>=ytiK)1sWjM_))`e2R}*q=Us1o340CSXVD@tFP4 z;mh{JrNsKvB+mdk{1M=mv|UP@R;>Xb^4hgpvRl#IfD{V9DcR2lK!KIvEn#I1UFr)s zkJ*h$D+X}dgqgBD%F5+9y$FL=j*FgG$QCfFFw|iOX+_jmSn2Kp01L{Zwm6aNfrz*RPSTd?1gIvV_&OEKqLJOm`Q8Sfbcc@TPR_9Ev%dQt^hZka=l&I?Dsf?hx9u;c_-n#sy`fd z2UpldhpYS0tJ7a_4AeMU8TO4Ti>^b56ca-_KXQ(P4k*yi1XuvPXw37}#oAwDunKd* zS$8&zgq8VEtF$VZ%A^#=E(9xJ1;8mupB6(zz>0&7JZoC@))=ojdi$giu1llzw#t3StL2LRZI)B*m5mFb4G$U3LjV*x;dr!eMxfFS8?Ih>0z&%r+G zCJo9^PQvL@j0>bp1B?Jjkrsk5kO-jUIvRrAv)Lu0u2PvtBeAfCG0I`f(#u0Z=i0-_-KXYLaC<0xQ2+Vp`AF{@1{!kxK zSv<=TOcya@f=YJ`0GT4BCoaH4%pvv-Q*B;Hv}+Q;B{&RVFuq7%#nG=*s1R9tsyUS6 zIN(Ts^(;Mu*jK}_XK25qLc|cQRrq!$1-ANijD@qrp!8E1{Lvjrl>jCIq9oB}k*<{3 zKkCR&zOP|0h%1Nf8cnjJvO)v$S2Yl|P9hLPb*+(B%`eFf24S*`mwdy{8acPvPdp|9r4!tiLVh9_#w`l#_2p~7=i}1Qa7F`#LisPeWnOpr z$v64$J2q~A-2ULhGDX{d*KxPD`dQxe=Pq0C@{=Frzwg+%{c-!l_O)*Pa_{=gbDAc< zS4F6bzgM-<;WXjuWN+F#{mZR0-0e&|f6MHD`}@v4c7D$6b?^H3>i_Rg|J#{`=lDl{ z+49;;t?8j9+f_a7tLRa9)XH2k3RQl7G~XneDNsU&ZLr*I-$PUO@s4i7aw7IT6XK`8 zh0ez%o*|4x>2_pQGr?g@g>a}FSeY-*A#=>ddRsluIe6rXA57WLJaoohnyR-m-)z8% zVKOhph+=}th%;)UEGLtSCd$=$CU&$fau7I`j>P5w;{mIL+L*U5U@d!Q(T_ueW4grV z1`BYMDGLUe^aMh_iihq&0R|Q4C@6_qNA-*~lM46DWUXC)rOM)nhDsBp>dWhQrhTGcJ*Mh2JBgw7T*}`v?Z7*3L8>bOnfw5Ud3kDJ-BTD)1h;=j}_FD zI0c`2^|&ow!g$5F5c-gijsteK41EddMQX5dad@){hfx~mH9`|K&=)nVqd}Jdph3q5 z2RTw@w)fbiov0ecSH|GyzZMt@n1yy>xx)HLcRe*tAG(f#hNG(bb6UPuZfWbBx}-gF zU!Q#l*1~y0g8uaEVOzMo#hhVZF$26@++$XU*-t<9y8RG_%8O_VeEQY<0eB)--?c>O z*MeQRP72>w*?$YTk~1lzI=~8cPG`!hVEu$hue}T)xHiW%F#6=+gwbS?7qSKmCC)v$ zwhX6{Bb433Vk`F?i$?q&6rPl#Plc%?IURER>sptrk!(np`s;1>H5@!_LU+~#Ox226 zld#stNqmO5b&tS`8pf!^SX29pJ}-x)uw~PBwM^n41nugT8e6%6gM(wGOg9oHm6MPL zh1efwUJiIs$+}e&&hmKmu)UM*A=K>F2@wA@DWqR~rQM?A+`pVgt|QRO87%3a-03Mf zh|Y{V=?R!Zu)+u%QkxujV9fqubFZ{G?NjHD+4Pw_LhVxaLyugv_m?ig_6XY_o` zY!2SgmnF`qtOy=fLSOd#$EfzPf_jcLz<8^JwZ!wHm2!8!=mSav9%ltw&W^yutTO5g zV>voS-ElrS!#V9}$JDRbD$m%$MI1n$nK1G6#cj~B^I}$BylU^P@3Z~6QTsAXvDbT> zt*ms#ezdLIqCDgO@$3lM7;*cw-%XW^OcO03tKPM2>x2z1 z^>Hoq0R%8t*Dc!2Orb3gWSO!wz4zg(_JgH!_TT=Wk9vJj`P4o4tx<}1fp7j@POH9a zJ6p9`-!uDZ{Lyy-+p04;+Y(0nt0{{Y45WLcPG*0Q6mBS=D zz4Q{&dMjL1j1>T1eK%ozw5gbt0y4#fbEb;M6%|u&JRd2%MU}~&X@Ne}x5^;~QUEX| zpt)ofAcZn{cncYUJiJ8ps!TsH7EBBY3B*d%M0zG<wt;w8EsNL9TAMLnJ`2 zc`bxvAD9(Ogo;*QRfvv`lb9=jg~ZK+DlhYa+C(9t(z7@k>IkSU_V2N@8mL)0( z>r|Ky0I&mFfTtO;-2@Do02yOK<1j`PrUi>4-kz7vKqKHz{h3>L&}oK!M*y70=l~l$ zP-2)U)v(wUB|KN`OKH3uVweQ_75+md1B|EYOD#_LO&HKXtV)47D(@yBMN+I4&KVHH zg%Bh$Oi}<9T5QssE&y5#AlW_PUc#Z~0F|42%xST;skMIbUsw#rEV{79hX+iTbJL(%3Y|NE3xHBDy7@ z5nzyXW|$AcfL}M(gLltH4IbD=c-{XbftO~&O>Tw7-fmBJVIdU zYxDzw(VD-+N=jYcwD{$l_R7G2qE+Y?04VhhJ3@H)O)vGe^qJG|Z}ppgzw?zjT3dF0 z&g@A!eBMrfzxy8duKzb(|F;+@nRfU--?tlx544+ijN6*dci7&GODN}z@VD(=!ls?SYtNnA(ntPv{%#-5hyO47-!Kf6=Eg?9ixWTh zajSl)&{|nVZ!ej#Ctw0#YufLRG~4O#HnC!zc9gX`;Hf$b(0*uGG>JBj!4??lEwQ!B z+`U%sFjkx2y<(5H^xHSDb=uSmqzL4^~g5;m-f3#*6Lg z&+YQ?3e|^-fv5zP(7Lam1Z?If|0372qUKWp_Dzmzt4u3 zORVP#;*mHe(nHaToF?s>V=H!l*=mrjWZAjcnL(giIsQaptv&V^6DTz7)hHJq=v*z|$o1V?Ld7LcOvsepa zooqnckF$Cr1IgJHoHGa;q2WR5xvkH7$)IzY6?lPMy1q`D#2GV;S6@}fnJ7o>^QVrS zw*zE_`Qj|uP)_c)=pcqBySdZDM6Lo!k3vSQjL`xZD9!b<+{}9WDs2^Jjf-Q_HbF!< zWq6h}tXWt6q_yP(_ORbLtdpL(3Y(qAz!R8;!4xGWv@?8It`2=cdMJx+vv5@R8@95~U50;_)kCpWA`rGW%1v2qq z^hhVd`vq;uUC=KuaxtD{{CV0-SR_r4FWX0P!lz@|_7_+75N^gwny!UGBj7-N%LIx5 zMwB(K+3vattIb=rZn6b+_f`RFVC>@r$k3KSrf{N24euP|h8NF?Q`c2)R-$Ay=Qbv- z0;l_-hC%x<<<7@@dl^UU(d43l71uyN$Jf6rfm+>26Acz z0LWVAfBlm9Skc+h$$t8DTt zSsE~a4;&qH#>mwR)wa<`zraC>U|@^xoli<{vE-&n7rk;5020T%Y;V*Wsuy4&<+|W+ z39KecXOk0%7UGCk(f$IEd-|(vwHGZ7`it~bv~sG|c`=+6cBtD~zD7AE#>tAjKqY0` z5i?H4yH?Dl6(JhD03k7)ly+O;Y^&eEu@^|e^DpO3|D^{EeNR`D4> zu5;+KvmG>+a`XDGF+%`=+F9tFw!2ugzH{%=FL6MFmHwdiMNh&*RrJpOT+z$U;Ns;= zh{aK$4oMjRG+k$f@lYXyE)J{bL+zn^(LGW&J_(!A-B50U0F-0P@m1yyjsGri4ycmW zG@)-R6i4+h6oYF)-SoLo8YD@s8z{udr+;Qbaa3;-Kz7hcomW5sylT#p1WMXZ#7 zhxwpMA{L1NB+j2w3S;yYl)~7-6tOp$LtOho_rnAhgH95T!sCtA#Nn06-+2m4tLwGC&Fo9v<35o2Va%B_}Y`07N7h z8$|F{$0lLbBr9;{#fYFToLRzYv`t!H37l|$rVw)t0ssY<*1%Y}SA`?h(-FirQ+WXl z1hi!UC;_p`UK1i)PmpXqdO%Zv9C3gY&A%QB#MrGS7YL|lHLh`ou%564ZPyqF1PqYj z(8OS2J_^eTr|qR-Dxe^Y=K3d!di?8v1f*8d$D0)tt4v+HErOWYIDCg>YB$PK0?`Hm;{GGieQZ; z>dCy*OeinUf{$geJ~ZA5n1Rx0VdngQy7i=McDEue3YZM%-ECKSxItoc~3`2;y!21DXf&_v?H@U}Nb|yPdVpoMF zI{=?pVAOb`LBzwQ5RH|vYzo#^f^^D}22O!3DErmx3cG0Y%(T7s+`scj$+Pg(YbQNe zoX1~VpSOS7{;;+EulKfnG#_L#X0F0FPNcHyZF?`i?EGEV+PO{N|9Y?Oqxtan`T8)# znKHL(KV2!?RdcJ3{ihwjZ`bwRe%s#YAMV(LA9hwaHj+Px+Bv=Njw3Q3{XO$#=lSpU z+s^HobME+!L+)Mw)&qC1{{MFNKhMI`)aYa6lfUz?ET_2Cq6>f@`{wOa`)|P7joHUu zJ8l!-LF5Yu$&GpDI2pB-M>edfYR;})f;N8{>p^kk7#>>rlVeYG?l zJeiSJR%$yoZLtR<3uEy^AG%^cU&DmR!!#I++RyyQM-dX`JWMPS-O8zoTXpS<9jqR; z{BYc!yGbYrVMKC<#i-ZgucJxh^NwvG*w=64v!P~lKq zH^B04q%~lVy`$fblCJkpZtP>_9kDiyT^ClQdgVCC$yb<@?pe;4GQE_Stk}k8mQ4>M zj>$dA;KST!U&K7o`Qgd|G*$fK%s6+E(cj3JCt`yt(;RgJfq&>N-$Lh1z?2El!)_WSjiT;rTt0Kkz z8v_{I06FUa$`m7G*)z!qJc0F7a42by9qF-sgjj_zkjh}>tZ>|C#56gH^F!TNJQ%eP z9=vLUt0ngQx7#e%L!DAQInjD1(jXnzP@U_JbW<3os()pQC_NOjeXZlRfL_rO|327$ z&EAWlt7`t_P>sFt$ITwPpirCKh7=;pX~07nqogwuUj!f>=X`{Da0n~FT=Q<%*O-H2 zDmyQ^qCP;srGKfAN^h(_nb!(ys%p(v=CTp8W%VB5C511faF#HDOxuYCJ5fJmXD8}y zu(yn`n5Z3X9k%JU0=s&t&Z5(dM}QO|`l?cnE1OSQ&6+JOouPLERzWn|Wpd38kGQQCZ!S-v`g5 z_E${5vac8{0Iz?ZEv3OtYqW6uC56B78wdJ!)toAT|Mk(^O@H+Ach!$TgUoL{3)-FU za#{p%cnE-Y=W|k^0Ph}(065_*-n>btMB|jR7PMXcfalM_NICNWb^;00%oT0tGY+J{ z7iAq&h6|P3`*Ig}E%Z%=d%CWO`ZEBzsQCg0l{rT26@|dKNG#xh zLV%*%X44 z;<^qPilriU3JgK9Mo2Bjwds%AABK!{Q&M?2k*q@N8O)?GPKT^;Z2%l^2bEFhrjIyi z17P79chv60kp}7G9!A5tRYwYk5Hm&R6?;SH^(-)qnSKwd|E>CTc7XaL&xMDoN}mL= z*brb!YF!`(S4uXHNhMde$8JVgNu{X^;ajTQt50Vad37 zEgC1DzL?A=pd)=10p){Q$h3nSn2Cy5_j_QJDM)p9$nK zS9#iFh3KTg7uUQ{_v1blis8cZ!yGdUfgun{{ zFL?hv56KD`8-O*MV;tb|{WWee28hit#`z}zT>y6kj3fxXNdh={*fDLO=UDZtwo_!0~eOurbcyihN8&lF)U`VAeIB;iUyn69!L#I(`#u{p{m z33U;OqJ9`h3^pkM$3Z&I0l*eu?%C8Fe3KBUJ`fO?WYT#F!U_>UB_@i(@M3_*u^!kX zH=uVa6>}*eIBkF}Vyw_dTz^evp?>21>_>WK6Hr!*0Bx%nHR*a6*dNMCqTP?AW&Z zD$`+5rFuT8TlQ1Pk9ETU8>c*I@uYUcxMZFRW-~s;=^NDl%D|$X`s^qCygSnv>-hB5 ze3O^W$LY*hcE}9t>3w$`k@@KFnJ-%h`rF-p+qvB@cklW;tAr~1o2-98e~+l$d5ImL zGYyn^-`bSlF^bh{2&5k!)Y!rv;ferhG<7ez^<1O~a)Ab(CP<|X^ z0AqLh6qY3uRO#E|ge1(^#h22b*TC_Iw151qJd9k4+Dc%aMrE`oI6qzIUa zF(=C5knvb}zuBjrdWFR*CRW<$S1up4-fywWfx+XerMfe0Xsw{Nd|=uBmqQoq4YUJ3 z`<3Ii*^L1zhk%z1dMVJ?9a^!9!ezTg_{4e-j#w48Ts7eyh+rzson9#>@@ytqpU|1> zl~OfaL<(D&AIj9?9->w<)r&YpdJs8nIIZX{tf3{bc8zN)wqE0n(mMJ{IVbgU;Jz_? zcg+A{FQxXaQ(d-tmT^t?KmRllOSG+M4+60J`s_z42=(9se>Pof&z~l>?-&$$+C^a+ zzVb-9nx0y=e|hj6vKK)+xsqoS8-#%!ha zzCxW-xaz~KMq$uTp2R@HSPr)Z?1S&QiXE1;XU|ba9MZhTxV5*Umd^oWuuc|-avTKl z*nU=}3U;${loz*UW!@2}!g*cv5eG(wXM|P1O$-_tTLK}(j*t;1kjk|uxR?B9+8g#s zLH@ec6fL7C7_^?jDqHC0oReC4Rz*_l^s^AL-dT0@H|%KXaEMuw3)m#%c6FGrrg7CN z<>30B;*tI2(8+l!Zi(7II(p9D!79@VOG95>D6+4-whv|tMjHK3MsggGQ3kJ!YQGs= zy!3qKt?o`o;emv;P$vSjltt+6HG_n2$###~Z})cCg=b*R%u)tyT}^E++{yD%`^cL8 zG+|apFd7w>_3h=bed*d>G&f*D;k?U%iLfdc!thdvfwDV^ndJAc6)!o2^m*02>N#hB z(5l;3Y-T9m*17@6sN46w^Q!I6Td>bxJ7iN=^I;jqV4}^~&Fj_P=iD*FdC8*x%aKor z4S``Q0MEDa*~?3DT<}vsA^wXTOO2gnZ%f)I-gc4&*P#7IUyEHjS8w&5^Y(A{Ux9rS zu#a8bZ+%|}l+<%T-%~~h9hscdd3e`X?mZ}HQdO)vqW=eQ=A0>ov#PKV9hX&0*F}gm z0gGj0M9ul+GEFVT4NsB55}8pl9cCR z^#f@0tBW`=)NdB&&dH4t5|`~dGFpXI7@+DlCI*hm%vhI0h+bjcrmZa^X52%oOCXtj zxGkD&tG;v(I0RvgZx?iAC{?(C^cp0hE1*FPzhEBECUq>3QY+4`uK-A)ds2NQ={M>0 z-Sll3P)X^9P?w<|(y+>@=KfP&aa^BQP}leG6o?9-3vp7eL0br*WOvoRmsQ(UN4}3@ zqxh^t#SY;#Y~y&{7;WPRvWoA0K_CI+jE|`}i)vR7rxCE?;Vqm*K#Rs2^;3LnfqUMr z`i(l&IPIY|>Q5>Z7nif6YlS!#T$KA`llIkC^*6?*q(WTegwO{aaMSPV|Eg25bnwj$ zP(wntTH*IJ#?+JUM~-bkIwk98Tn ztGQB4mpRTMvDg)!5dlB6SrXPxs$Az{?4zH_Y0QLQa5-N&7fVjSgooxZKZxC-@E!+` zlue0HnIt+X2AC}^GqD<_F{KVBA=V6#vK`uk=&Pv=YIp#rlo%-rFA|ud=Y}y@eT2Jk z21gR7KduzPfH2`1`oDl6>L5YJo+KH2Trd_UgUanU&#k%GJolYM*CjN)rK_U0kv0sv zB}qUCPe%-B)JCXBvFK#UHRq6GFr0}D)mHWG&Sw87MKnyo`Mct{V| zncyK;<|T!Lh>b$ni;K+WrR&1m1BX31K^+c>F+!(RsG1lQ2+W3*KA6P{ft2ch3QdYr z-^SPuk#V;>RQ7UOoLsTB~zWegAk&{E>LandIT30I16h|cM( znMqF4FD8c$qQ5E&4r0Cm)d6#&7KAkDc(@CiAxd-Xpb%j{sb=n_nlTdqPUC~ZUBqk& zUE`i71blEGo#>coKQUHxE}l)XK4L>+q5uSuUfC#{L}LY_h+Qf$HMWWt3rv)758%fb zOq3k7ak^k&?hadaHP_=MQTk27kwOY_8bQkh;5E?(69s{N0gD=I6PmMFu-MMdBQ2S| zOr8kKdOI*SDtu9F6;dt|0!0`SnmUQD9Of_jCeOQ`$@Q51}*2c_N-tP$E z$!yy`&zpXD(^uckf4j?uoj+~I@^Zw^-?y)`_2G_fZ~Ea)U+-N%Q=fOKn4Le}tA7Tn ztp{mmz5H$Mf6hQ5gh#I*`TRe#s38D54eGCnfeB`{j|mkAN-N^KpO~@Q!bN-iLaVLd zAZX&tg6h7>BWnNZfm3#r zRhB-B&kkZ;Uls|MRS>P-dS{ zFy?aikS6)asFlHBxj0;JQ|DMI@-Pir87D)=u>@2j^-Pl3f^m!~IX#-JHL*vv+Qcf4 zoO11#)d9l-U|?gKRXPk3Q9flPY_MvR*wQ3X8dGJt$tuT4Lg-WCWm$zuYigadI)ON2 zRW^1SDlg8GUz-U%g6>C2Wz>FvaEK3+3V4HA_ut-Zv$J2riv&E8LmRG(#KynCz?CQ8Vb#}EbW<^FNr0vH`G?g?As*uD{a0v(d4M_cX8({&gH z7-xWD*;Rz#jABT^xF|dru>bI$ml5knF`gC2-#K+0ZJI37P_J7Z8F!X9a&7Q>scqb# z?kGp~6YKUf2hLj|>8U^8yT@+4NCqQTx%@H0De*WlIe20Z;0(zV$YLcvrT<~3_4)$f6#8EeU1$H`CGjrAP+N>7)KUBG|^ED`%kdJ{3R14gN9 zu50w2kn{uOz2G#-c~VF~2!pS%DQ3BOn=EhwDrU#*;nqRhM~Kr`Mrbd<6UEnS#XEp8 z=jo-zjyVvt-+KQ`wilL)Cgo+U-~X+j5V~g?ErC%hA6KjPS}pf*968H+N_f5mQlpcQYVqk5oVq609wC|olc=-l+3t-9F zqc-+j9xH?aYlhjgr)tVh_cq$xWeiB_Rjbh+OCqP3_V*QNoNlqzDt4_R?290BFTdkd zm+Xt84MZwpWn`islpNi(@Q9 z7;pU-NW0yivZA9Yd-r{Wb8QsZsq;;=A0bERudGdG*~U#O5%9C({!J?@TlLVUXfJg| zdrOEhHb!~np!1fZP5fQ$L7$dWBLB}rk7yQ&CrTqm0;(EMg4C7M$kmrQFsD)A;D*Jc zAmR7FrL*)g#=mUFw2p&wwy=^*+W~0e6iLJ}r%??KICz!*fwAl3 zfXc-`y&PPl91a@t9A!2*up=N*2M7(I?sOkwc&M+bBPmo(=k(AR!T}ZXq~ir-1vtjf zEdWRR>NxdW_Th}_7kww)Z*d|7*ibP_SuB7+8C{fF$5}4?<}4QVV~q=P_Idub&Dl2s z@Aywq7YYZGljHz{#tiC9^N$!C4ru7gdRzoNs6Pl;;n_?|FN^0k0Jst$1CE0$IJJn= zu%GTh*Q0D=3y6sUGex=-DRfLyJP)p=MOdb5GXSPI=&EO*@_74mynrt`&uKt~>k8~+ zYz}fgBitpZ@FT`eX=-pgX^@F|P}fQ+vyIqP03QM7V0Xs3Z-ut$0&*&~kdUC?ssFgX zm!47jMo3_aLSY1cFt`d7N}vxST@pX1R`Ti3VuEP=R6TlGRcG{jX=`~{4tgm8Pd`jW zZQy9LV?a71FhY`q&-u8D!1hQ3DXJGLkjupxqa<|eX@4hS zjY?ldS&hhKq_Cc3l|pA=AHaa~zN4N*8;Nm|goP7;2^yFstVfI#0Z_#_(q!HV@EnVk zsT8~d+MN*u7~yOJLUcmo00#miU?wCbOzR;!T!;DIQxa1=0j}{`>O}y?AixOPD12jo z#OTo^3E@BsP({I?9cf#NkdSTSKn+F)V3Gqu4QPnKw1`M|i@xE{HqV>FbTl`F0bK&5 z8WuPgYbhp656(NR5l_KOm(Y9}7ih#-7$&`La2c>kS!U`<3?T3aTgtU>=obP#92o8c zT!IxrYBf?96XpziCL&!F>^}7|Z!>@ciOy%C@sUz{l`==8ixMQ&vA_e>Te_|}Cn47n zbWu`>!9r`b1fsbCwDLR)AJ3DRb7Gk!1$=Zf$*iu^c8I|$BnH+_0B}*}nDj?tFqIWL z<7tl3A4#^CSrP_L7IUveb(Nt{XAkW_`wH|E&?071pj%?VXzW$ekb4Qe!nRO7txW)+{N^Xr=gQ6wqPuMdzx@_tVi~*cy8Q2o46Fsu16L!MxL9dRJS6Xd> z)WCA$JdGgR1-i%)J<>da-5s!3F0|Xy1y0QTAAx~U+yc#(qhmM;c}1Icq-oT0LNR;h zHCPO9P&FJAWR>dBdnWB)qj90SJC9K1=gu9qsTWxZ(`p{Z*Rl7FS~bj+XP@5f0;y$> zCG3BF8R^d$btFMk6y6@1@rdhv39b#6q1pSbi7MBHrJhNn}f84oCPNA zU>y$M!wYtxX53!xZbdAUl`B>cwfg8ONM(FuR8%~PsO5>*@tFel?_WG&iznqIQ9l@! z$`s_rETvYe--jRXv(l_hd+|mK;*S`PoI}@2u!1G+7p)p+*D+U>-0qL0ae2P*y2tW!I{em8{u7PZ>-b z46W*v{oDuNunzz{=>ES6knwx39EZ`PI^!;>3mJkk9t_7s>=OYK`YuLL$9;45&c=RQ zgOczJ;ag*;F(L_bac7SD(qyVWQ_vN+uFgsCU*ijePhgOj6%n$rk!53-i!Itm{a{cj zt4ahTTA7B#M#-;?em zC<1V?9$__%j7Mdg_WlFc?A-{F%Afr6xkCHm^M`B|u+W)J>LhYzGeZgAvm#kloscvf-%Arlz$68tkjGiO1_VWv*?PsV5tK*3Kubq4Tn!?0QH zcP}5ZMKaK|x6fEMp?sIl*HMoFvU|*7per1YlSO<;!{ zM(Gy?Xh($`DjmABX9OzHj|*szy2fSF4l7(MV0#)TtP5SKS-{1t8T>-0YKe>tCOXWyI8+MsdR3#H%nd21hm zBYMX5-E}zBo%LNBOFA#@n+8+ZkIzAY5dsO+2{=}nbp*Oli(A*{c!4f#7GUQpwE{s1 zu@R8&Octp8DQAXkfrff01SqJ^(cAIk_(|ZJ9Ah~|dI|g&(5O{kg$IejqB08DaLobU zB|1qAAoB{FX={Z51*I{C1GNpX17}OlF%Ecubk5Ek((1MZH_=Q<0i-za0Xszw zv!^fCJ?r7u{m}PZfR^%0*xkcg6;g=4P!Iq?nQl_L=@2Xqm0db4h|h`z;%pbr>0vos zhAK>lL{Ua*Nu@YX3ScG#xDXPMLC6YMu30ELGJ2^^dOq~Q7~fn1bRS|SPQ zs|dto@=;i^>cyX1fe(!9a{L8WL?$VSLTBj19+IStIF&fFjN{V&&`qnq#$j$H#Vp~t z&;&NhB4rg*L}5hbjQ{?szVCv0eJCe1BOq$E*D`}03PNs4{@Rn)vjqx5`d90%0%WNfse@==0D1# zut$lkiWQ|ktxPonqk|)2Hwa0h{VHL3G$XnX;342hq%di&sGq1D!ErLyOra%0MI_^fH@f6C`r5G+91r};zZpV($^Cp1OuP;Dj044`IG0KpXtRn;& zfFUH%i|0eEG0(y`K$}Wj7O*N%ge}%AofYaBfLm-I7ujWjD%PJCdi8s+VG*Mput`F^ z0zPO1fim$)%HV*-6qUiV(_U`Y1t0-aCX4V$r9+Ov;9S2BAVY{vfcrIot5|!!XH9gG zWBPztRB@O;VP)qT1?|eBUG|k*Xjc}CN8BUVbLR6H*B(d<%Xf??^ZU-v_pa|XaF;6i ze(OICpxkkmyPh$9PIUl1rFVJlM0vJ$X0NS%{gmnR>aSk6pC!F5y=CX;^e_I`59H6i z>+h(PTOaS$KZDzL-|)^t<^0|3eN2#)8j6VvvA#vbb?W%ILV_M|m7u>X{b*0l;)bt^iYaHh%B z9N{hiEV{3f#x>iFhHdV390(}YTGh_QdCM-xkRantQ|-L{Ky5#GRKWi1+I|~3xs^_t z$=%Zv$_SOCLU}57t=o_9zhL_Sjh>z>x2Ip+YtgG1irCXyh16u71)Ig01sFDkWgAw7 z81M?(D8oI)h-LzCC?o*KFiU^{As}5aG*&kwcKJrFt=^=J7?imHF>7_&zSrFvz)%AS30x5Z#62rO(pIuBEs>{S=Z761q# zfQ_TwevtzIk;g}EZ`m}$v=vrB>caN}Mj?Y}zjO7l4W2AArI}RfO94I6elD(6Y^}uO zx1{70szmCafG4?iNjrF8%zl`@vC94aao-*ryh@5}K)?_HK`xAnTvFdk8)XHiPz(n} zj*~)DqB1VKF`zNP-IL}1LYN$hM94KOGw8Oa{E zuV+NzC^#otVfT7q7p?r3w5(eRY?QgV0$ZdE3bBclQb&X&%4t{sDdR7@;|=|wjEhs)RDeP`+n}s5nG$ZVIVvy3{y*4vee1FuhO5m6aM*6 z+$a6dzxZGO-8cJJ_H!gdZ|S4;vpeg43XY{7HE#06^GDye>PAL6m-52}vgosbB{^|8 z6w)Bk_W+KTfE5Q?09^#U=r^%IJY_NCw2sm5{+=nE24hy$MVU~x9rgPZKt_^x={%(b zzDZ=0am_<}1f;0#U@AE<$f07hD4fs7Kh+MWk#M7fMU2}5lO;B;XWhYb#-D&d4#q37 zQ{+&~@d@Z&bUuY75uTWOq7Wa?cqhk|IYWTG&gY;HV^>-|M!gBIC zo#t^dF$ClYtoG{@X5%T89Y~=L1*E`cNqT4xY`iS|#^#W+JDBF|5Qqoy|A~@!n>(#Gldpf+HU9n3{ znQ5i~UdRxmz8VAQFc=tG@X>u~T+#FAz>BUiO1YGh7+^%9O351LaseZ-ES$YUJ!ml{ zAz%b1th7-=WcCS&5mZRXY73QJ&%OdtH1AX2V#hdZgiL}#0FNLn4`s=TcMxtO<_fln z@eHW12yoNBfgu1c7!`W{64l&iy)xFM%b-52$`KeQhDNAYX?>L`2XLXS#H>YZm^A3a zjsOgt6JhTlp*lfjEn;l-l*rhIS}=0P2lb$PiVt$Wo3MALV7Ewnq_qSV3~jR2juHB} zgY1KVH>48|Ori5q0(%7DBUlf>&wdJX)41XS@YkuI-Z=m<7?N!jmfDpKTaqym)~}xX zP3F94FI`Jalu6D7`z5{`5QVb2=&pve4ax;8G}s3iJxDk*?U?8+x4;2F6c&Qi<_h78 zF?R&eY6+2@M*_NW!h(Vgw6y>>s@TiK!i35l?4M@5qP}#V4gf0JfRgiUaV^5V!)U7r z>{Pa(zB@uHmGVfn6)dTnh2>CF%1&$f@V zW1BW>{NLX8rmN_y9+-RA{~N9U9RNzYF>b5&+rFf?@BDkmW^W(=lkN`bk2??fo}X_! z=WSo^JoJvuem(zl@A_2N*7I^_#r+k(+*YTzeYx|XJ2rdO`=5LD|6jTOcZTph^U6Ez zQ|I4hYv&n20VL|6{+Gih7#%Hk?Fy~#WKz3g#^GEr5isc(j&l%&^NuHG?H6}lvDa4% z?GK+j3@d^O1!E|J5Lk8@t1dVrb)?_ys+_V*6SdZdR*5ti%5 ze(J;-oUnv_=Hek6dl_*##SHJ590IIj)jhIihZ{%i;z+%XzC=ia3@HD>z62_025Yt8lPCPx3%@z(q@j`m}a$_vX|tq*&LwI@P#tl?8gAcAj)dO;K4ZC zoRfje++GLsg!H>PkxiSPDY6Ap*v45^Q+f0EnLT#A#~vx3vPG`>)uB%7hwZXC#a00y ztX9f*a$Nwqj_&x-FQA|2vjTr)?}!~Pov>Zmaq*%Uwwweqo9ILvZ4+%6Y zhBeSszhL8(Vdf&MemFI?glfH?)%MfVb$0O;bantDL^IB7iUSD5t*~%|3{VRmKGHA1 zWF=zxg_{Tf$8D07+iSzJoTx{ftRx_q>y=>piymKeG-2;Le8V0qp0#f;RMMV=wv1p* zLDQG&|0YIp0)WR?tSO5eM_DQ%LW|z>zH9b_fFD{6A4hlLI$53m^86kLzO>q&-GafG zhbGP}?TS$w!ar$AC|+s z`WMD6w!Lcuan7t}jwfVmbp$H%fV>B;pzGm4Dbr0>4dEgHY&nH-t0HUA{&D~T(tR($ zD*5(!i`~3f?d+wjs-%_HuGuO~i=~_LEO{0%HU)01-k^eTLbG>;t>w^+jr5h;>g#Od zI$3~BSuk=0ym71czz>bsFSZQYHx>%)PriG|HBJ8Yqp#T!R)J5U@$mcC58BKaP6jKJ z8^c@%BV0gFcB7vT!Bd#qvox{zTn? z9nOg&s}ryuoS#<#)y5HwUSZYilj3_7VCCv$jZF?=JaeuZ7%L^{ z`;1H$+xjx?Sh0?X@QRI$m)b&qj^)+H?H3+7Wsjhlu}o&9F9E(j52z%q77uHo4=i5i z*`YZF2I-}H@Q2j<6LrP|tMO4=wTqr#|DbBn&TR7+W2yHCeq+?-A20r-&#I4n*>MeD zS>+fxa_pXF)$vuKKLQQZ|M=e>W04~C?CV)^4HKRb2PXu!=y_Ft-^Ag^(Q}YR&Ob~R z*AGe4_52z>&I70fr7r-WqRcu!#<(SKnO698zVvaE8i{Zfdt^OE5 zXGz)0oH?UM#A{Rcu4{2C;2W-MA~0TBB#X?IV#1)E5`YEc6^AqDtNMT`u5BXbg@Y-K zSFGAA#7Ej(4w8wzK$(*~D-t2sGap>!Tq^(={Bv-MI`i-rKoF%4mbQ?yIaFR8QwM6* zj?$IE*;7U!Wv>c2BZcuCr%$0du5lyX5Cm9LVs;2f5hx*62=zkYnX>_7VBL6TGUnFM zjDQrLFX~Y3l`JAOsGNtf0w0oXdg7ucO)=4FLf=_Me{)9i{dGf4Rmne}wtuq%>;vPkR1!67-=Od1|a!!;!A zDj{2i0Wmz z(QxiKz)@J@wEz#!rl_Y;+P2s?)RF84FZT>LKv<15IXo1pkcADxbOHhvumL^ep!Vgm z2^@aUs58Dm9|1f=TsGMPcw5DA&wNC8p`Bp00uBXS4432pw11)+JC6k|v1SB(=y{W_ zNPL7c0x}qxgw!U<2DIx4C6Y}hwxGbsB*OPGK$*}Lm@Ue@BjBTx%sUGE0izQ0Op8Xb z8Tu*OB9R+R+6*+YR*;IWY|F02{^Z#g)hM=k>7EHer z=oIXwk|oBwr_6%1f$T*Mq*_c6Fr`$_hHg(1P%756YOntGC%o?u4{fC}W?w%kZ{P1r zhv=oU=vw~rFI%5?yxpu&=n zJ8nDOaCfVCcJkGp?Ou^SN(V5EZU36C>dZEM(`UUn>DJFXw`xoJ`&(?(4tlZq-t~9Z zbfy;X)&I^~+3qacJyP|g*WaD~m&wATyZQ(J`5)WROU-__m5;~l$*ykuPIm_k5N0wN z-Aq)PL^Ys>poP{QTefxd6qW~bET=MNj~?h_p)YCQBsK2pC2EQNq*0Ouol>h?5_+tt zTD3f!h`|x4^;fWHd6nO{Zg21ECGBmQoqe_rEfEY<88*y;n@liTeb=O#4YOf$90P<9 z4pB&d{KMw~E0*n7pL@`jPGLY`7-TnN*Ro2GAQm;JGhmND)`P>7YiFK=4a0=3XJ3wi z02UdYdHVy_xqlLU!7Ljl`^$PiZOiq1Wt!r-<_W+?Oa>3<)nx6fJs6cT`sL(?xxeb; z>-LWip0y(w&DSvQe*J}q09Y`3waN;pBL`I(LPUAaYa^5d03eF?i{fv!;wA%3_pOyX z8ODVNaL)2K$@)V0!7TewneIUAJlGpr#gWA#Fea}#W$$|My8To&D=-xCFBY=xOXm;z zD!m)%a-1n|s0I+|=#tfyEZVg}GU{Bz@#5Z!0A`d%cI_I5E~||)dcsX%YkOeU{^{;Z zwkJEs#>kNLvDfamsc%s!TqI2W%lU~(@Q$L0RUw0Cqn|HatL*Wl{rW?Qhyq@GcB0xY z4H3oxz#&jW!gF#`#Bxw}qTCu-O;zjG1T$#@hjX~6l=>jlgA7dBWUraOo_1o@b`k_E z-b=Vn$AmppF-CL=Y@7=RZTd7TnR0TdJJG@wnk1o3cRp}vb*w)7$}|8Omaq^y>t+O|-H{1Ma ztU@_IHS~9wBheuaR2xwqv5gWNAu?6*+~A-*`2JzrT{3Nd`U32Mi}+aw%s%+&6&qbH zv{SFcvYDbTDUO^5r_xh?rOWg_BybPMFuT>Pp=;3&*NsuftM&?MjIW%n^%c$s9v?)U zdC0znlmEwGKV;DX>KcbjS}U%LB8LdWQ;w(WSO`cE0}RJifRXfP9_Ab$tr>K!%0C}# zwVu%mL{u?yar)vbA)ZUh#;UnkQUaZn%_@dbsW1~~HSxSUO97`ds~!EMqtmv$o^7M& zid;A8XP-Q6hjKUVH?QxrKGG(C@ZDGJ=PFn5pAmw-*=$!X)LC^afC3>w-GDXgJ!(tJ zna#5+jRl?qclO0R5OYA*yV_ZU4*&bUI6XnMM^gBD3PVzAXffgPYtvzKN=+Lf%u2J~ z#tM!Y`$=f{SjPxjIJ4GG_Mw-rp!YNay&5p7x_Qwg20TMXrXUX55%znybkb%ucB~iJ z1BB=t$f0g1%>Of6WPkDMep?%*jw!1|7X?-bqmXV?W!s|dtDf@ye&afV&I9y2&h6R~ z^grE;gq1@u&(>j;YyuuCjHCR(hPBqs+WcysjSUd0M!(7?<3?^VVGb~iCcn;w1TqNt zP(5%xjZyk9ZS(LOFX0?63d?7KHy%dC0ScQ*vnI3yCy>L>$IL&K-(RY0w&}T0xqZCk zCylGVi>}EBom=hXV2m0??ZiKCE-Hjycr3ZDKo-UXu_*N1dEXI;p)nQ*Ree)T6=$yi z+?qb~{0YQxy%szc&&;E1%5itmS_%kwkwnA95QI$<{-SV1@9T_va%SbsDebSyPfK|p z5f~-lAV3=n=+QIb3=jH;XKxbF!M~vT`Wj#dpbGJ7l$*O!N5rvDDGUeph(czh$0g8K z;?5z3{ooiYO?pV>R1Hw?x}NUa!)I6|@HENNB4N%5E>ZxoQluq9UFqJ$z(_ia1c!4A zOmP;80~fRb_uzUO94e>U0T6}uuz(V@zO&$>j$n@kvxSQwgLB)Xhj2W$YSPmNNh zaabFKv?K+LR1${Q2wQsE;Xkdpg!E8Hh4hgW^S!_y>7rmSg$D?!nc*LRK)e}^IJD0MEiOE=g!jvL)N?L5aZ&fKq6#LVp5eED8HrQ3$nguILQ*AwC~W{A*dWf;T}or0`jO`FAmt7X&!$;ZyGxx7 z6ads=RPeB_6k%84K|meCW5tVl?>@S_154Ya7e$V{;78^5r zvH70=o38(Km2A})mAdoS+qdbP_S5TD#b=I8|NdWI|Jwjc=0tzh`)wE9QKs}U>8<|v zwjFPs@hyJ7wZr%LbldUzq8D|Nex`U-Y_n{d7HSjSTnd zpDuA*N7x8D@fF zr6-?N^NppjN!V2eT2WifTANwThb1yNQEoFgSOJ0IAhw2Q4B&3%)Ryc{3vy0gK}mf8 zBalfdZ#PB)6X!-Bb%LQS@!86{6&t--WSc`c;H@}4uq!5RvZ8y9!4dEy%*34Hx{7y> z04VH#c#@Ef6?^H8Hkdb@2jjM2KOrroFF=h3T@hQXEDd=0zY1;JXcfN;M4QYFX z3?q9lSp;L}moFW#zQ1JEg?kVsS80Jar`eJJ>yNM4dpob$i{mYJ{iRBbI?ju+s`9&W z$SQ7DOUP>XFIjQ!1{!*ZSw?75! zm<_MJ&|;f6u?8@%^V%sV^|U#|{;bHnaA+eba;$o)ooGR2I8(Uj3qMI zpyjbdC`KGnTQ!iUi^PG534Fm2lp&tmp0wRvQ+5=+kE*Pw_2Vp`ov5?f*#ZPV34wvR z;;U{PqU-}&%~H6EJA!}<#m5o=L~AxXQQ!{yp0;t5PD}p_^bv_KvqC9x;LS<;CE#7b zejMfc6`Qz9I7<&kKkP204BuQ2+59=21llT0-Ia93tO$l!2%~wPw9!j_+y@|EUPsD0 zyQXborqCu|V--}+4$p6XCq}fwnE)ePhXDsNt2JmR9vigU{AK&j^-i04Ba1Z8X20?S zFWU*I+7t1p{fjq_xnQY8m)+s=n&SR=`FDIj0b5#mb~-z3A33R^N`B{A6XPkTG;T#@ z>$V#K*&nX~^ohueg!vwg+HVlD(n-k84_8jgzqN17m)JM2?y_~ljW)(HRL5k2V{URu zZ2olr-ztcnKd1i#nz?QOfS$9$0OtfU$?=NnenrWmU+?&*%Nn0@n=p{Cpzv%mNsI_VFh56JJpBMnnwU-^nAs?j3Xj2f&aXPbdV}Z7Cpb{_vF?C zA)rKkUYi|^Vjus^wH0cq1L!-?vP4`RxL_Yn>7Uc#G<;@X zu|;&8t|=$a$98`6bjw?1V-HHmJ}QR_FEE96Q^bf0CdQoZQO=~Y=m_wVu88!ATvtlk zC0p#3AizpWDU7!&L1pt)%P={TfLzi9(O8nw_(I#bo(g?c!rtn$st*Ay8k@wRaYhE? zk$?^lT@uK_^%X9oxj{^;&!LNm-eq+Ry9R+bLwYBf z@Fy22p5(d;of!h1$)l}1i@0ZHKBCP4+B8OJvB^P{Za@vj_k@@z9oe2PI06`@@h?h! zYY``O6;{jS7OQ0!b)vQbfXo1BN!mR)NI&U=LBeyE=s;NZ5a+7ocgC{VGHpSb0^O87 z!$j#Mr7~cQfKma_0@j>q)DK8RSWrOWO6`;zMviPC4SmwaF|>ZdJw#jUzVpJgX|$N-|JWVqIYD1~IM*cd>ekpzJy{bQ!9O z+dCHRUCq6AaiPM_yxeRXy_|?i-(#%-9`sTck$vx)v|~6lCnp-w zuPA3l2;)F2FTx)^i@7!;}H-Z5yxINx$Sw9+5WLx_@)r(*OQu6Ebh zB8-**_YrQ97ER1kt8Q*WOGRimItx1h+O{kIV|&IXn7C#1v6@#(YGO^~bLTN^xtET2 zE!nT`J8#b|m)IAcK17OXj9?raU&&NiaK>`00J`QQwlPZEQ||ITtfCMiFntjK3noth zM=q^9gHK0D&4=&5Z0{&uw$BVV*(usct8vPrqxz2y(B{$;pe@64s&UN3Mk*m|Pd+S! zg@rus0b_}kq!=Kfg##7K{&8(-ye~ zwzO7PIGDEN9R`fE73c!tTC_&gJE#JTpqn;4*NMDb3&SDiP8(^vc zf!7KEz`fyrEdZB4ICTge4JugxkNO3mhuTq&v5Z|`g=6(kz>);~irb=A zSOjQ;zDh~ns?7sZ_FX6;CY$4_cj*wgFfI1HoYLF_Fpx-jy)cBa#mZnV0NDQhqX^<= zTmMyrYGu#KP^CVU=2>A6K>-#x3u4}QJ)qCv4lB>Do;j^b$~e?2VQPz(A1X@OiTj7_ zV~u0@>>o*PEbD!m+*{XY%1ll;`#GSqW05dV|rW~9u!DU`gN$we*xe(2>Wb#g_MqfftB>5 zIG{xTV1+H7$3etl;;NeN)vD=Fa!geY`eFG4G5hG@i)5P0vv0h(2hbDTa9_fnI(*$O zP1o3s?^fVAk;& zTw8@Y5%@?=Z5h{`i+U8WtM<{o>3{Z0J89JA_6uWNeIvlQ;QPD%!*AL}zvFnS-$za_ zayTdR7u)$i^EvagU&jYCZPy7@zgySR@BRzFaSd&8Fohy(7uSB!M}aGB_iQ>UC*Kv$ z;qTOekW<$tQGcOtsElbahD}tGQ|}pTbPZL9-ki;%d*QpcwtiFzXzNT!rJjGWA^ZT| zrIX-5CGScD?ZA>i75bn;OoAFK#8iPLp)jBnh`YvruVBW86wkEf0&2h(k&cQ|7fT#g z;WnNLhpwuA?m81HC#QA6QmWV;EiUN{6`oxWFV$E8XeRxXAo?i^n-OzFObOMa98(i} zgy%I$=!pYjJj)8p(>NgnN)T|yfe4&VwTWjS5*WceXXsamk)n)q z%0Q>~a#n~g%{g2@36R!cm4xT02v*W*wzQLr2y2q?nm`$2KMN>gQHInf=!{|;c@`Wo zS{Q?au8~5R4fT~$7*+`&jKC1FQ3WFC`dj0V<`s9)1wf0<0wAIk!5aFL1(aKYxm9Aq zP!EiA3hPn3sjWO~jes99MF3!w5y`Td2g(tERTdp(-hq|m+8ErHu9cE4fkPbzxCl%G z8qk*v;3ZCGB9jvj*dta)I`m0(%e`S^q?D;=6qXGcZ`Ag2gjEf#6ESmLGlpkFeSR(vuc~<=onr zMiB7eDVxW!1^NJU=E>5NBMlW046^h{Y*is(0x~4VHqkBkuCw_U#$HQXe|+rL!_5D@@&EVz(qoz4 zpZez0TONCB$2-q`@A_0CHTO4I|19umNPUZ!8YA<%>rPv(^;Z@sbCSEBz&qUCw{YRi zp(?{&ce;0dub6xF|5pr1nND=q4)nLS|80lxaC?vb>c`0xQVeKK zgfxA%7{*5B1DjS=v1C2xDsA(+PC$7v9`r6_QL(&xpP05EYq?H{e$bwutFue}wTQuD zn2d9NtwOTmUvW5U_0{NlOc#?KgBzoc^XgbZ!I%|W!0(YsSQBaMV~h6Q<{PYLXWO&a zx@?ZH73s@lG55U>8DF;?g2AUjYd!zQggIBGSEH-}xtEKz(3K&2-j(xdjcR*O(%WI}C zv2f#nBoY*^LlifQj4H#ErKD9xCt?!A3}ae)BWdOi{SN~qs~}+q2$%R^?U1b^1p1|s zPV2s01!%!AZvX_ybQ=77fkN4Zbk|}#>y3wv&uCw(a z?N2}BdRobsqbuhrg0bF-5b(!#T|+-7XfH2E?Br~X)#WXtJ&|KCo@`I+&v0&O?#Pi( z(MAFTD{4uxjaJ7RjH}$*q#Zgq!s2MqdWNbj8*LT=b2lf@#z2oJ!OE>xqC>QC9H7Kk z(h12bzh9!SQJWuUAB^|x5`<}U5iX};;;=TaieT60!ONu9Lk7mMvnFC8Fb0rfzA z$N^JWh3Zr%46|~W1L!D2JBDE#Dn++r?}~$9gM?j0``KTs_86e*lVWUTHv+yjuCU4( zwhe&33Phx(6BH!W@A6EwO)tRg0C-p#CJPY;tyZ9OJISh4v#|w?aZdaj3Fmq!2ZoCr zZD&IOMrd{P$a}}_m)m<-#7o(4T{~!FJw;a8vSLT-Mr~rP&;}>V(D6&yeT~C*0H7p~ z&+rpBCV~kYM`U*Kb#7kHI6D*^pdJABR=WYYaG=!Nq?@9ZR@YpT zu&R?wAMhe&ZAJ;PdQ^tRDGi1i@qxsIW~>YXY{_d^v-jw1IXa7+L~ zh`ub{n<&7jRz~$qNvF!4U34zI-{NG&uF&>6k4maP^ijRL&FdE0)<$h(SA~S=8PQ-W zmJVCpo_A^=-xU_)&xcm{`RAY&yQhy!pUcHbxgw?JMY;8#?omG|9!6sYE4XQpg-Z!# zVr&<%k_K6{-_~b93{Tms`eZkK7Wm|$Kz!Fpb?;g^Pu~vb);;i_R~~)NQ_a#29%3oS zgtqi?Lf{uHhX5c)003W(nnYpU8Rpbp2Esu+Z80nru{y+35wK$Tn*wdsE(-M!P$-=r z4{xB3G=2!&k<+F5#K9E&TGzeedSZJh+$XR>XBLR2G7C&mm=D*H!|zNO=BRWw60V1H zsqxR#maG5c9LgEfy(nB!BGCcn1lKn~lSJaIo}G+m&_hiKiBVXNvgK&(7PG>Ib-520 zH09Y&12dEpM^LGU5xX*Jj5w%3`!hfT%KX3Vy$6(C*>#?|U#gsQPKbp@?gkni*~~+c zvS^tivjb_9-6Wgc>;#~@5gG-c z3Y8-&=k(Hi|Gsr!6&h$*UTczNyl&LH@7{CoJ^AeYpS}N$g$B=XvRA$xNRfihqrLx8 zB#n1PcTwb>h=33x=O~o0cRUnbagHc*Ps9mlwK>53%-PhU*H@Y~Um#}|ASU2QVWAQ5 zC2pHCfC|8n07zlJ1l$nNB+Dd?u{650=5a;riO%y*6m`vCS(NomHDHPWAP0fSFJo2x z(k&WT3uRRoy^VE%fFE3%GO9D&dNQ&`!MY?4=p1a+@DAEj?^1Z-7+fq;yfwC5*{>5f z3h4nx1oZeO2;47nJMz%V0!D5FGyvdbQP$n!MQ$U2O0pQzM{|JIak=SuNMc#9&<1JT zTGGvIz#w;M)55_pfHhg-q$im7asN^H;wXJNQ;WqgJT2ld5%Ay^Nc0=eJMPdX0%ovoVJ=G0-{iKXXlohB{o~_+74ulF03fCnI=MNB!sJ{sG)fY@BR$Ak z*UPikHvz!P0YBh`p&dlf>L4cR#0Qgw?)>?UnG!(gihL;+|K-$eu{{GWbCg|N{v0F>qyj`g9!{i6|c_eWRneT56*t-9@=iFWShOLOWyljI)q z_E+zH<-z-NRd=8I4=hmLwiSa5;pOmK&-2e+C*J=2ok@3og(t)Bo!fW6f(zm0@H=B8^agTsU1 zLU=j+4zK=~{QglaP}*ACeAfJH|N3WD1*`*>t{}v)2v?p+T4U3K zO-w)rIi7Hiih2~rx!EFHxJVjy-4r?HWAD057}gbAWI?#RUTiN;wblnA-W zxV428Gzo|_gk*RvL@9&^kII9fT7E8VKYaGGeYgTMdZeY#&Q?3w=gL)9B!ovP^_6F` z_IICng~${!Ho%hhj|Pa2bFsm;2Uq|Rn4MgWHC>9G6ZNt4peO_0hqPMg#ro!v5)SgH zis}v)RPr^`S8N+Mr8uakun;0Oh-96s`Nvcjo+C}}_P$R)@jx9kFz82|3ZQM-Ku^3M^K zfpYS{Ou_TpBi3{ajv2V^6SBh;?ZXHqvrJP_v;npVr2Q|kx?C(8to{g!9{o(C;`}X$^*p>rAMNFQaH&{NYGkEE>UMuXSsN1wQAf4v&33?#DO9_q9= zUTC3yP)aa?_BKQi5g3<3_;bq}T(uN#Rva_3oGQi*=cDH?WB8P?&wuBrZC=&8xR*AK zU{)_*??NnQ)P9P{(E&fo&vfbd-R{EDm#z4+z zygUiv?nmk-?LXiW^7U7{U5I}AR6kuWZO@9Nb}D8it?aJSx0Ww6UZiYL|04EvLO(zh zSx9~OU2ob^2wVSTSU?dV-WQa5L6+on83H>%=qc?~WG=#8=LMBj9)?rel^IG8c zX-+%AY;73>jzabJ*W2<<1Xcvh6VFW8Pqy8%7qLM4Q+PVI=WuHxx>j?;q7C#`+v;WN z1${zw1NIZNJ4#p45&B36Akq@ za3C}9lkc0b54H^1m&ZD+2OgLteIU+8uPlHBXygGv)j`lIqU4G3Dy&0L26wpm#e6tNu@WQ^T|$V@7%9H%AVrh~L7&5#Fb;+5#yRy~4*M z;H#`zlF0ID#w(jk~jkkAe{>U%7GPGwCI?A=AnX_gET)nZwY-`bCsL?t4~NN(KjGURBgerTexT}?_^y? zQ`y~0guJLdrJ$NDI!!Ee5m0g)KXazxK?>l6SSxAt($6!OS=q3AwNPKvdK#;t=X1p$zv zcVz{7(5H-kENcP*H8BBh6hVJH;tuPG0kF!&M-~zh+;QaQ6Tw}=30jqwXUcuZxkizH(Ek;ID2Td)MF=FeFfTjcBkPF>)-5Tb0SQQvHKE)v;?##i z#qxIQm-|UBiw9ZcMTp!Jok6)@WzehdU?F3;aip-;h=}7t`Ejs;e5$T>FL{vLNdzz_ zHbXfS5|{dk$hxLVWVPJuB(6aj;*>!a5gxGzw@Y!B!~}>e@-SKcsQk(|p^r^gFyz~# zrH~bI26;3Z-j|(5*(R@12wHutQ~?GMt;gg>B9MdkXfsLSsO2Up7vRVw{1Ir#S?ahy!-mGVhSPQ8N}J05x&a2b?qHpS~xa4q1Xk zaOsHxx~N|Y#LD8*lf|Mj&fMbRj%l9EIv@?!4X~M`;KOwy>mMv|(rq-d+;rFsh++XK zt3z4jX32Y2Ehb1ni?mJlQ1uP-A zIgby0Co7N$Yf(k6iB9lLz|nLoz)UCPvTWkWxkEr9MvB)lfD2i?- zYe=pANh~NceJM)}(1~z4N()432SkBfR^uQ5r z*CDLE)1C0wFxCai3l!d5ShSb^*H6(|?(M=8^eDf~pPgUf$?!Y8ocj(ggqOqbw_MKs z%pKg7F!#otBs&Shbm4dI0pUS#A-o)ZhgZX5I4f3%Bhq;35^o?@(?yknh6oxG;5`V?Nk`i@%}Q*ZCN1L} zgq)*=Ry*69d9|qC002M$Nkl9Y*<) zxiE{CcC+dqUbp}5v41qY&8nL<~>(te>9VlC{ z(pb_4*2?YTNUKd>BmCef@1-uJAS3`4nvO5qzWP~P1c(@!#N~nfd1MueWkg8_y1s@{R~zp zzcXHM&po>z;fyj-x5|h7$s0}*&bWTYZV>fmX1K&kYPak-TqJ!{v@?7;;&c(mLKML| zh9cQ|X4!rLccB`}`EHR|COCgpkQWX|Qk97}OS$xZ01MHibF10+uKbOHPGcgR=@tc&Nb`$SNVH+-H z@V+Fmu+grw2>HtQZ(B>_qP?qW$eu1r*?APRe{tbqTe_7G{|!-S%2T#7fQ1G3$fYQ+ zNo7Z{#+oTXoi`s_M2F1UM1Q%r^~axj1JH2WewQ}>#+MGk0m6N>ffN=2Co$Ss=D`91 zRF}RRj)Ec!hZt5TkAGm$eyVxI>*e=`yX>`>TYP0!B&-Bx&iRG7e_;(XJz4_UGYU8s zQ*u|xZ)NC7c%PtQsGFGhf~0(^U!$}Z?$0uQ0UWR(5`Ro>^NO8m9<@_gb(DZ8T;3?P zFI_ug)2}n8<-!Dzs5wDOl{hM73E-KMsNjk(-%@P3tUs<4qu@C?E#*nHz}wQoSey(Z z1u5!I&Dkw0g1=_$e5nhp`P~tF{K-M*DY^9RX7a9fA@4j#fT`L|mQo^?)Jvos3RvZB zoTOazOP!a`z7+C!JN!cr0k+__Gez{VHQdR>OQZ4SJJICZt-Mf%W%jOJbeVn2Dq;IJ z{RG9z=!A}CMjv;#9}c}R)Q?-R@J<0LULNx5;e_d%4gk?s z0*SMHM`bBOq#XxU6m>`WqAap}$;jmgUAXue1kbtF25Os&KDTR&k5v9LLDRqUD`BH$7T?g+4`2p;+ueJZUu6~IeFTNp`Bs>ZoODz!Hg1s; z04aVy94T_`aqc8^Uw2^wAX5Jp(51Q2=N%`ErG7koGVLI;TO$5s1j4ZSAh#I@n3R3h zEt@b&I8`EYnV9B2$a39nOL-QzOAVGH%CkTK^6b_D^iOw5kt-)|G4WX{5?3ae5(A!CUG=%hSm<@N)wp8B`eMR!9be~Es82HcafaIB;NZ)Axg`9R>5 zz;S^xX@M<$Y*gW@lWIr!?BboWoYp((&rU20h?3|9nc0B9rnMUG6w)9Y%AH4TAWNF$ zC|#R~IT6TtJ;d=o$aZN%wONVd&YaH{~Ku-3|LY;$$lUiyc>p{5EfP?}n} z(SN?}pJ0{={@yFm-B;dvX7~5*(aw|ZI&t?Kf+TOB@a`*bJ+u3J_vpW@_alqCA?TjA z4yz*kzPsXeCU?~ja=-Q@4$tXZAN3}Vweas8hA$0Mda~S;?>rk^+x?rn>Ib=By9w@i zj!r*#|AYFcpZq21e=-wnZ)CcMyW~%ZtVKe|+gAoDr;uEM{)C zg3zo*Db6l(5r9Dv(OMR#3vB%w3lz$YAp{hkjM&NJqgZIiZD^v#mWigZJ=e*JYjJKFbJu&N7dTB9|s9haz^BQf2Ix7ad0N z!{sE+&P+wahCR_SV3UNoef8xI+Z3?FvlI{mkLn0P1|ewwGjqs^X}kIzLLcKNoqs55-H+V1>4j38e1RP` z+iA z)J5ye%d=N#li$B|%2sDF8HVu9mEu8w6BLu8Zmbj<)b;>MjVtyeSbl5*I{i~we(+3@ z8H!J3dsZP0dA^hx?Euq$VSmuBoWkO2ewJ&G102++lvhP3kneN`#ljUE`kItsnVjqT z`#;ocKhrR4Lntu6(|gRu2MH$#kRVgx3<^Zp{yf};`zQijK0=e+qROi`?4jl<>nK{b zYgnH2Pu1Gy2EZHluMeVRA(U&y(0jE)3XGD%Eo+w;JDRoi%l3)=*R38`rvGi|kcW6K zrY;T>uKMP1gU!8&B1PXSIH>+c^}floP*Aii$^2KAVKM(*)6@#1Kqu54yzT6(9AiAQgxeee$p*p2F zD*A@V{6d#LnPtlwCfJcB3QSqbPT*p7x?;+{GJMebUPMTRcSzwirD&u9yVNf3aw3Zo z#-Cf6Q6BLPxS2FUG zC&GCi96gDA05|$%8X#GBQ=RltZq}3NN-5?EeV4UtTX)BlEkicjcdd@F(O6xzC+%S8 zG+-wok0GGV#|5jrWCIJB3R}9$Gk8|naa_I{R%{t7zxBn0mDkgkTBgWq*5&|7D=BAN z@sjmHYWv#jt+p`)NZ4lfp{K6ddn)GbA6z+UBmaRiQ^$?(+p>S~$OZeUKY7Z_5HzHE z9%be~eJF15;isSBkLtvu@#rG&^>&g~f<8(|R^8>2j^TYn=XJ2N{z-wC5~AN3z!VRo ztIPCJ)ujUu`X-$$6r^bApIb}m9Kc$X^D2X)*m#s3fEMkJD~$+oWnmZ91N0hYN=={+ zDEAitlZf1H_?Kl)5I~uwk9ov0=30Ggo_27KTT~wPGp_*htn#bavcT{>>zg@U3asWb z0V#?q7KI>IfQsSf;hZLVgcN20JhB`q5vZbf(^pt4q<9IOm32l8#Z%yl&s)s>n&(`h zWPX>cPZpp-prIn}#0WR+p^`U+BJd9G&A`v$E>KO`-{GBVt#$P9gvp$vO~m z0SS~vppY!8vRGFNND?>^BT`Rv3UGq4WU|D;8YPNLrQ{#y2O({eIWz-65uMlu^jHpt zDZDOeDe(eySOx?kJL$vwb`tD7 zVeglo>R0_B_iIm*@SMK&*?H!J_vfl2sKB6wc58ZXz3rY2>TvgOn92c^-A9C{cN6N! z1;}8vrLpOl;boGC?Gf~Xd*2@>_h<1ZcYp3ecwhLPyWj^8-cPMRsDJu`rtJs)Pvd87 zY#b60kdP+xG~I0LLR2DlElfC?T-q1kr5RZJwDZwas}<&kwHH4;;K<*H^0S z`EPWYxC?}!mmI=WAG7lPa_ieSyTEo-e`u<>bxk!>Oy zwJ20^)~>yZn^GS_Ek)McsrFO3i%v)F?88Io9|`N9sI?>(7uwZXpF!BAT=@tV3CL;J z0lK8@9DjVmezfJ5eRaOUzWLQd>^LECvXD#Y+Zv%BM9d1$N7chg{BakY+;f^Ru1kY( zmdJ$$#ZY0?)-)uOGJ6nS^tnqyU!|ud;*4vt^oJ zdKMs|X~m-r)!=gSftCS+Rb=e5g9q)_CCV^E$xvcsrtT%BP4XX#Lfngx2+*=HRfN)t z;E4i|53y`=4J(uDlpEns{Y5~73-YvklpSMPQk6Gu*c!ye$c^^M!6AE?5VYqP>g_^L ztE~-S@qp!kcBEy!kk<}L9YXEKD02q=?a;et?Z4@|VTD*-oW~7jXq9lXnYi8Pt+N$4 zWn|GI*B%L$8T{qdKpr-be3bF-(#j26UkA)1f=_kxn*Gd?OLhbW@SkJF^UZG_aDg=f zXr(?TH{mo{PeVSRW0D^vmc%j9*aWK_4K0El`h-mYc0+* zqTRfXQww&u7Vu%Szy@&Jn}<^-wMgA-%o4?`u$XX@J2%QKih6NdYm<_OA& z9w0X+SsS>kk1Ss_Ry9|UR}txIw^!~T0!&*NQ^)J3>F`t4JP>PBg}CTv1sX>}~C90yQzOahARJLGzUUsp2z`EwlP@E6hX z_1xfH95BW69CUa+K$c%j5#HbATHqES^gXvm5|D?ICgq*V3xF2p{ixh)_!odI`Ucp< z-65*ys(@r#B({nqqwg$HI^6w6{axUSI36??xPneLm_xMF?m!x4^Sn}S&lufEbEb#x zMKP5%g+LSs{RqqLErr8NjOIJdYqDGr5aI8l9U_2rvPQ}5xabJPQUoP+ zd0Eb8z!tKUE5`;P&3WeL@rY9~sM zgD8{>P*0XVipZ1__%lGB75#@XpKhdJ`vSfd$tlW!EJs9$8yObp!85T$%G5I-x#bLf z1~5b8UjU>Cdb2~g*7Wl{0UAxnExbF5HDSDrd5V6eh(B9^-;sVSR>lD@22 z-*j*!n5J`-aEL5c7MD2-vLm98^YVQu4z#YzjKcZ6XZ_Z=OC9h zcW|G?!KE-|_}=h4cVn0!cj7(=xwN^1`y>u7g(<`LhTpjx!vwh#_c_R=%^ln)ad0V2 z8NN6C&fOR$$ep;)K`w3X;690iOJU0Jz2SH6#xOzd#C;BOX>$knNgP}XQ-<#izjHT+ z334aybC64$JGf8c;8K_}d~f)jyD?0VJ8_?bT-w~heG&(k!j$2A!|&XUVFFH!VS&=x z+UAqT<3Il^xbCp{pcJ`Tzjn9S{VxR=Ieuu2&Ap7hezVni=RZt~v-|lA@*5@eb`e*LJVj-#Bsw z{?D}iODq9~Um(mYyH74yQwXIiI+*e3C<`Mf@cld=`NMfUw1`W{lLZ7ja&ZXVR46w^ zMsU|oiG;y4CczPe2bn}yo=w`*5B1y3X0g3_qlxgl2-XleKHwBF>u0)2(Lx?ND4P<_@i0BRC+BA|Pu%vjXwuxLq7=vC;2RK!ieBxD?_NvH&NC1SMHw z$cn;udMR)bZP3~=Yx95>(^uGif>_TLW6B&sfv-KgVfz~Ataqf=mWLn^ePrG~edIbD zz#0457az5mXOVhU*X)G3vcb>F{hL-_hjIpw&GKagc5)~|M?0JfC#k-W=)#qyZHs71 z_&yh>?601>W*@2Cv@f9O{?V6DxoN9HENUk>QRJO5+BP~*0S^ngervY<`5_OC72<~cU2NPBADE&ELKuR)WL;znPm{S~q ztfVQe-?9%pbi;n2a#ixP{j-TWdmeCS8?Z(oO9H{VkUBYptKNx<+xF^gvt7H^WFqo? z{P=(!B4qJ*CfngCOxVY|u3Hs=)n_g+hOXq<6CWJ0Uv8UZ>Pp+kpFL|!-^0{>2=2AELL&=HgL4)^XlbxTe(EOHE&5l!un@pkn+*L)EDv@Yic{~ zWIA|3f0xCa2t^}9?6mWrTY~6e-hrqNMy-48Xi^K0)xl^ZDNas$sfY2~IbL*LNmVhL z=J?W2S!1a`2~^WqaUxZH^Bxxt1;~&e&zDYf?7)@3SI^{{p2XPKDfJgAp~|Kd6lF

zS7c3+!NMiWy5_DNtnVT})<0PnW#Q{lxs@-41NO*0vM2#aQY4^AK3pSZa+7&W7-9OV zx`o;~Ae&_zW3?HZ0|ddUL_m?slu>jdx#uW)4jUkvZ$i)^O&hBW&aZ;ygutMfNBklG zSk$By4X2hD;o9S2g5{n;9mujS4WKR{#c=D1-o~Acn5=kJ0CPj4U3Esc z7Fi(AaFIl(=v!D5$W0`JwMeEOzzI$cccbC8YB#sk13VBQArjsA6cvU0mt4KlHB7dk zs{t@Vz0u#4UT#C;FA>*CY(l^U>7}$c@KC^xL=}C6exm1#dnHYNzE+;0@g zC{u%l$^L)`B*EOHaq8SJ$Sm1$-amw!i-#qqF0e$22!sF_P^f8AvwqT+xE{rC@m?bI zWFVo<>{GNHak4O%1EMIBP!gcpMg-mn5CcT0qU^0$wg7y%TNceJuqkz$yuwiu$5KXM zgu5mo-)WOfZa)Hg#5a{5V1w!g^|-bUK!O~*A2*!(;5s8C0D|Ps)3eBC43-~mA%i6f z?v%2S(cCAnN-pa$=S1Os<>U!#l_*xD?j}fOIM9Nda9kkD#2kPokqIBFuy_|h3-yp; z%*pC1O`0ggxeiVVXz4B^d^G)_rJJ_ot9xr6&84lac$AH4rBUjKIhDEF`7uzGj8 z1V6*Y`Cj)0sdw*Dnmf*gt#q%O?|E+bS$@Qy2k(FTp?ODL@6_iV=N{C5FiP&(*Y0&P zs6AcrBmVq<+5g7(aG*T)w|>Rq>%jjA5DE({;zObPQgpjcFW4iEWA@#_P8<6ceybwF zli-c=6@@=R3s7De!WqKW`WQRZV-yVGMP-w9%E=E-*^lG?@g_d~fArjGTffA01c~A! z5j%8j(u(2;Dae5`S>J-V^P8_9wxyS}a#K+Vv*KeYI+(WO-D7sHYRWDx*Vqfs?gK<2 zA3F-X1c=QE7AF={7XUGv_1z(a6jipZ$~a; zms;*T&Y6IcAi-89oPi|-f)Bz`{yr>fJH)fH37KxGtqjT1gZCpSOPLn2m@CuNsZ3t; zo4B7!QCafqL3k8Yexw5^{NvFj9FM5aRTkSn_7<^Oug=oZO%gjHIv%u*f1v z#|iIPdwks^!|QZ!u{Zd%lmElb)JucAXk&(6K3a54%&Nb$L$+4t@ivkJ1sRt1yL7rEy~IY zf!#>8qX3kegB#Y_FlXFAF;L6FvQf7IS@(fZZSX>mr4^dhJJ3k730j?e8MOzT`7TR=Ai6s#% zJ?ypGnRh9ILIR*=Yl8NsUE`e*`{WOuw{F}be)asLHv3w>hr0dXi5~iNmyJJ*kgNKo z-xsu?kWM4yY1fVZ>|q(Hk3!ta9yu~%&s0y~9)o~HKmFQxr@i^DdaMI@KktrWi6O8~ zRtyF?WnzEKo_Tk_Jy|vBmR;YPZno<~jkbE5U8(t?u4N4s!}ncNsqc16nWB#MTh=0O zaY6;j55K5`-+W{qUs9d4n(7ssTPU%)>nH;B<3ueNxleVb@+h=&A&Pr~vAKnXlT5N* zfusHfu!y@tyQw9Ft!`_cx0CS9$j|@9u?`!#MntZ09w!A(76s^m8f&r`aHUn_t{w2D z7SMDQMG@heLOM4=#Zh*<0bDm%<5pNo6s3J*-j61?^6X0khi&3|nMpW*&*__Xu5#A? z&A=hMjys^NqzVoQBq8$6EfgOqFpOjQqdNeqHlZ&UV7--IiP{!@-1`%AL<9wu$%A=^ zIv{TXeLQLer$vxhT!)Z~GP!&UB@YEKuifnZ@9eejtDds4?Swry+hVJXgTlORn_n%l ziBYWNP-v@Lh*DI&WJ6QcHrIoaiBeyFaN7>GPpi1_uizqwCB^)Fu`ONa*_5H|WXj%k ztk13!2KwgLssUyhiwuK;gXD>}-W;K-P!94N0Bb5ya+zDajuGOw=zAVfi=xQ_$^lX- zz_KEcBI^=0$8xRXdF2S*^Zl5U_tD>8la@pKXh89;v>XK=I;bvjk^gnTH3|>{E3zi@ zXG`1F{nVS^uLs20Syg)aKvw7o^(#IMSr%&iJFvnjfiJT35O}CDF5UzOT==Q}fYI}mH!U1|eAkX#$kI$ot>$p;@@CoLm$gwwpb0>W+Fh=o9?6Ph z2R7segwm^j4y5v2mBAEgM|lQ-aQ7R=i1!_=Exq5MT*?ANV_$iet6ESuWR1MK3ywfL z`jR50WdX!9PDV>QfhjAzm-#t#8&P%1ZN}dPuZaUV0KslWMqiGwcFO8U%Cg3_0~S2n z0S5s!0&E;iA)WJ)Ft)Hlkfn~d9l(XV_$YKQfUmpv(D$9&gmUV6YIwIgU`~>Isk>$} z7smj=#V;Zd#H*b)^++_;JX5YcfGGkKy*}uRvR=|U=-gXU5@n4dFa*+8S(Ic|09m%k zB1K>fJSXnL0vMBvfI~aF`-?0$1UM|P6F<%UfKY00MKN(8MPP`46vnRREVYH!Gw0@z zJ0@0|0z{%q3?$qyBUrKsNXf{OMG7|eNx|mL!TVU3Q*59_iP$4@TUm0@J1q^#ZkFgz*|_kq+4ScSoG~n7 z6c$;bj;)e4wX4`-vc9aKjVObszKG*=6_62ZLqQ=(XN z#AeuO0q{$9m05%cJ=$o}_)iHCWAja}!--MAmK7{(#K+Qw#fsX`0W|7{{<1wuD?-p3 zhp09y64xe>Lbza@&jxwTt$Hb9N+3!P+$n3DSm-E<50)r^OCITnW^$J>WSGPa^0Wj% zQdb2bEFg;Njz>C(!aR~<{f^zlVoA|?D#;TTB--q8phf*jV{(lC*9*z+ICCyQOX?x` z+5k}k7c|lcXrzC~2jtGP>;O?pRxh|Ti4V|;%GFl}08y|+iNL$F)?8?>Joj7bw9bLj zOcWk^3BSnv&L{Y_b50Yw=IX!jXZKlt#2-zyZ})^C_3k}-%^l|+y#N26`e)+P+~;lS z6?|u%zomKadvQKd66~8|fJm2Htj7c*}&er^CJFaL+?>hq(h! zyZ6_Bj{R?(C_F7#ps2#m{`fCjex}S9?J^>#l$CGV!VC-l2CfH$6_u&_>{zj_4a)t4 zTM#@YIK&Wc;w+qL7CO0G1iRL(MIupSaVw}x+rtly+7CC5xT5k;FP^r!?;%K1gGGlU zcI?!Ioj{2ZiRwZ!-(H?*v%Z($QNTsS1z61Kwcv>Jq^Wz!K6;?X@BRJfABB{Wry$S? zU`YT@2yjWU3__$FD51+Mwg6>F|s zwEpp0%%&hPfzbg7 z!~ICu>)7_Fbdfm($zQStL^#TE}OfCDJY7kTLd6L%lbm(r$trUxY6X><`9`j zm?~_idJ8kQaU0$m1S97ap-J7M2t}plscrjv=Pp?bjc%=~qY~U;@G3NkmaDqcwmtU1_(ZS)D|~ zil1YBmT(FRbMiXTFdK`39puIKPh>Z;+f4J#@n zV$02P$UP}1#msMkZ=;JBF~}DNQKFG^rC=2{r)+Z?QrQ9e1;9e0Giq&T;DnhhvDIE& zZw?X0`j2=w+DBy6;`G=C99vrh3=_b&1n@CUA*t6A+NGhH`kE`T z`SZ*Pw4d5ryQC66r39z}G}rk1ac7YGgSY}zhuUG5S$IJ;fFfmDpT;$conUb_HSJ$S z4_LP)_(XUWu0ks|yH;xbufiWRNL``CR-A%|q;?gnbigH)^a|1|?f4|*!M9KZ=^Jr& zg5z{!ncd4pO@OLE%QVjh+=6QiH-f_8I zesM9%0O_kZN~93-0P@Elxw&YkTK!xg3~ebe!#P8ge}3yR0$()#bzRmmo)MDzfnKg6 zPzIG>bH0FZfnDNKaexEs6af#KbDX%=K@IvAu0P`NP<_biK?QMlB_~-0Jjf^Sa=#I< zLBA5%uD++S?v@>D7s@IiL3wq{6oyiSxlp_60x?Be?LZ3dK88C^lyM(frz1%NR)xyN zNY*3bO~E}c3%7|tdykGoorG2g(KvmGGOE7S51b?w5KRDG6!0Um3{Zp8>EVl&M(v@9 zJ6M4zbg?mxL=NrTCalr21`kB8b-W06tdCvlIpxw;Y8( zch?-|d9Mq~FKZl?IYa-`xF?P{Kng&N2xkRa$*P4A#Mx>pO=~#MP;@MJ4N()S&vL&< zwP9U}a=*d}M=6t+MQ%3r05PoX@tm~*bx3Cl`A1R^dq!C61i-K`1oIU96&V2{QdD`b zKoo@|c7+wC-APoLTl{^v&xqGXd@h-C-X|*--sd0(_h=k?T?sfq*^fce8lB-i2s;_V z-lnidi8RWxg8HRyoEJ*K0QHg8#?2I)Bzgy%335S6w}>=V^II^cH6QrKj4UVwy12Uv z>FN6!ELY?L6rmj2%yCW+`n%+V^fc8#RG%m=Flv8x6Cjw~;W}aNbbykMlVq+Hv9?@= zB2$Dq7N?LnOaM`$xc#X;{9OQovhK+UaDn3^G6x6&VQ+Fjpa+&C7$(#1l!;=96UTXC z#;{ts%@4+VrWLTF6)q6o?N(4!oxmimZ85CkqGRxV2G5={Y-T6?>xnJSD?C17g2ovYN?|U(qiW=R0 zdhRfH;OX}MdhmYQf~N60*;_GtYBV^!`y%p{Rx&2U-73H9w!J5Ubi1kjX!mC>>E2U& zk8=rf2f@|*r^zMW$>(lrKid77OSt#c-s4 z-mS^-D7bq6G-29sYS>NfN4r0B3HP4bdz?#Rwwd7;8COtd0X6Tt*UW$H#5E;hCD-L!4ER^&>QMxjwet+VB`bCG0+ zIZb-)UIhJ&G*TK^Xp0W9=A&q?^&{Y8=Bo3OQeAjQDa&g{F>Mc`_pG8|&JVz`!J;h1 zNfV^_S|SeUWbM#nv-TUfWt6h(b{RqDm%f04EWQ>#5d@UxM_285%Y;?Ip)iNL$>3rY z$|liIh^VoSVxK|45Fn9vFlx`dqu-i~m*ETPc8&xIAlj|TV+Tk=*7hRl5qK48rQ+bG zeduV9J&95%m-!nTg?4SJ&R&1B*;ZepolqnsKsP?IVJF(hZJbCR12=1Jy_cdQfIA0( z1TE^!$=?v5;-t@O40b4G?p@GCDCVvOTi?vL@e8hVR7$h$dJ}hx+ZPa&Z&McTOLPD*w54rh4lo1I zL5%i=&4lu;*>LjCgH<#3SlPT?-zc_!`vPRPukN%Fc`|Y62s|OL5$dGt9gB9hY1CdC zX}94Q%U!8YG$Z_1VBs`KZvxPeuporYxppXF9!m|)m1=L7sFYbWw^~i)hRXv6gNyIL|`*OJ- z{*h^R^QgxdK%f9fx!^hQs5aPXjgYs&P{y?LSHiRQ5!U$>Rx7KSxLp`;vnfT~0<_dl zU0KtnHPtNf+>DJ(RM}!LX5_T3#-tP=mBrnNP*_x-GM865wCh~dMIBS_^?p(b+#{_k z-D)<@i{w^ZHVPB{ZumfI0UqDPHS7F*oh@w?SzGynorBk=3xyP;bi#gP^^TRuHKJTS z-@gy5C6pifj=R7i%uA`0GN)ZuDZp~e5CGx>p0;wgAX!c^PMAf4`UTJx2Zh>53cr9H z5ljkj5)YJwZ~1t`eMN!B2mN2Oepw_<69QocNb z(#3|btf}-{B+3E{n;&1XlP%-+oeKwpklM7z@efSfPG`eb)}E`s=oMk(13Oq zcHna!-#-7T|LmOe`&7pg>2!}k7TV1F7{^inO=4Mr@P9gDMH8p$ zO?3r`>7WI`i3YusngX^&_@_4ZGy+?s^i!t_Uo4j&S%;`EyUUB7#yOvlX#(|kO05=@OqFqcDBiksfMBMR6Kq&m;$IzHoKleQ5p;XKLM&zvm#aOG{ zWzF*?#g6wg=0Z0m)s>f-_7$)flCa7WP40aniH)wJ^a28A6w!tTi2^QV*q!(Hl<+>l zG681chJipP2bajQ6kWNoXfbCN(5t8lHVDhFu914GzhS(3>0QJWc> z7u_O7?Vt|9m`Lea{4>B(4HSh#jwseN&F+3;Z5o`k29FJh5zEAmSQXeNiZctw3 zniIvUC5mg0$u$Q{91{l(p@ap-82wcILQaH>E1cF68LhZ$5PGr5r=x%lvTpG3z*-ap zhyZM46v`HhR}T@)qtqXrXjb3@8yr#oMF3J_>zZ81OF7_6EeWw^@Q66fNzPk>E0U~E zeWQT&KQc?5i64df5OJ6Oc|OaAM{IJ(s)KbuO++CP#yTK@ zRh_%&P%j$)a&VZa|3dRNqUahPP=A!Ka`o~tpL0Voq_Njla|kN6`H0fUe%R;2Mk%B{#dWo+EF zQZZN_%KcTAEwZ3WP4Up1fDI!M!qPQT01}D{B>uTL^P=Wq$u1t6_!?!U4l_+sax&{@X}ZnS-taD^~BV29uk z>>08bZ~ilG3|x4c;U}b2{^3;ac<0ofui)za)9faHYij>}kDK?N+IzhFr0^)XdjB+G zS}z`D*-h<7yFYUY_nz8&oJ){92(I2gO)l}yn|D+D(eBS&!o8>V9_JF|4uY%qPm@c0 z*Zcn?EpYCSua_w4Xm_jjl%Q9 z&;L&-YUNhgkhULw{E~g7DruwJ+xDxkJ!})-#n)ClUo2+Dhcb5P(3G{7EL+cPy^UUH zM|VbxAi@YjT?7H?eIMI$kl;WN@0)7yOUN?vEjT1Ndzq^e;W&V zw%Ugfh;XM}CYis+ixDzd)E_=RYM*Q#W1})^e=t*SU;5e+TxWQXE)*b0)a_rkXF3M0HnC}cK6uD(eXlYw<42%ZnLK== z9*d$SYZ8&J)|ImGvjER67khp^%FbeeEnP<7#bqELQ05cgf61Ppo&NsSQ&^B7NZ@*< z9d7MnYS%UbcvZZQW6GJt-9izBN-81goX>MhBh{A+VM;HVUsFHz?>j@tH${LH4Mfh_ z#4YGH;gXY65*o;_c1TtG;(fu|M0^tsxSM>ewbvd-Nd4E-CH93cAGPfvc5t*~Qv$d) zLc6LDAqW*6C2t6un-jbXp{)|YsHJX!jZNHq;6_ zJiHVg)_0xgP@HeF_Tg*!5Ba-F2w?2K8xliSC@`` zbJkw5V10A7HcYf5MGbK94T(@L1B&{gm-?J>;uh)RMO9x>b+*vP85bg$r-QI^*H`aho7S z@M~AwY<-x#lHZbMtZN`n-RR{R=o(Ux^1FC2%KPSMFh5nD*|v8c=?Azgu$Nxxw9Q@s zuQm|}PugR+WnGwSw7y~06BbG(@Yk&7+hPw&7a)fOKe>;{LLx^xDrJscNV%95w`vdiN=~|W4I@7(SMeiPx?pdn2+FWz&b4^*9{57GNorukzg)s z4)uEnOq5@g7B`L8oWf+Gc|h}wyYrDwoJEBl9B@t6H7ZB#p;hZ9BzThXATr!l#%2@( z(bDoQIDZIjO?|H{pv3ULn)(&2Wgs@*#DXRnw~|84)gjQHm@T)ZF~A%C2Y;0yM*_g}N0{r6A%jyYNO@kfP`C)&p=+5d2bk4~su^l3k< z8~hvx^oM}Qhf{zhnkN+ASoPtY8o1CoP{MDoBhBYhW&t2DUh?_fU20_Qq2GKvsIpBv zvIFjx7<_4d(0`PSL_AwAK~%n!tc+W#@EcG?z)3EAG4*a(iHL~R!376ccozLfAeR7P z2ehQU`A5%Ie`Zc%Y^XhDQ6R-mps7Ge&HoCsE#OhnY6Pr+^pk+R={W*<@)#GgQUFZJ zU|As!E$1fE`yqF_`9%7(EC&mQdV=8 zDIRf$axzD`8w#LJ5UQDbWwk^(yiBZN3P~^bF1Oy2C5bFkWJy9@JO7I;N!+~$07Ndg ztbH28BG`;D#soMC#0UT%zGcP7S{l={;0uY6N3TwClvMDnDqK-mH#Fxs>8!?zx*6pZ zhl{K}(~K*T%|^v@vH}>RK0qC208%n_+^}PjqENCPmYMQrWCcdu$f89XF;Sxa#IW9o z&=#uyOeJ7~!WWA$7N8=g`5WL=bDlOEhTNr5OqUL)dE!E(m)DQgsg9C8Pga!$8MEmQwkz8Ksk@r9lGA-!9p5b{{- zK@9o8K@q?Kxh19nYQ&`yb1oHGudsGuF=EUc?$UxR5uu+6K$S(A*0tD(++=86!e*!H z2p=q$6@UVPwlUVLz;cD>vk4O;)Nu5+01xUy1g~(ecvKrUTO!OOZY{*xlo^EsWQ=FZ zRfxJtVG$GQgr9{uSM{m+AhSyJGF(!lV{o~Qkr~>~IYkcP`olUUo)66f;-gAU^U5Jy zP$qDPQ5!avnv=n*e_+8Q*B`B!+Wd<2GpCO(_&kf zML0?T&J;mN7C5pV3B98S{p2m@cW=lY-4pM7H~;`Z07*naRGH$z`-6twsh&G3 zhaV5>|3Uv_+>MToTU%Qzog{0|eC8K12B>18%GzIf=9+z?X4$?4KgaKW{w!vZ2*W6P z`JKqKD7y(1nEc|TZE$^U6y;FhgoH?*sm15fcMRDtVEMo@ZX?Mp`@3I%!d5Ra?IZZ* zqwK!-y}kBBb<;$}%i6EM@vx06WG*s^^5M}_kWm}ny=|X4d%-*KZ(KWWqvweX#O{x* zV0`^x;Yl1M`oytCti&_6xLjzf3S}&%QUWDkicV2~X3a@TZx5IGjgkOA$ir_CQg++EHr%OFzy5uf>_FkF{qf*o zo4Q?&a)sbWz9(;#2GNTU{3YyZqLi7qENlupsehExBvv@`BbLyr1vOCx=mc1^2Kb?f zG2(dHM?{l)q6b}kquti7`eS^@OA5RM=Xht-PCq?vrFolnbE?kg<5EDZQ9}8yUgW(} zI;dw^n-~g!%3pd6_afYB<{*9DT1!|>qr%N@5iP0KwysisDaDk(s2e%Caot*K7p#Ra zzb%DpSn4EgV7>$Y0^X_J#Y%W8j&_b?9Yn}q6zjDV!nV2=3Zz2P&dinC;!vS&Pf#Xt8|>773; zKP{#mWZE{4i%k(=VtL($b=1xI4ssvikk=sKZRVMeHx1hhqb1a%Tq^V%B_oP*EYQ<=D-g;RHA`+r0xy`{Vt(2ojBaAjj$Z%v^LD?hwXq{ec;5Km7*Cst2H6!K8p1LT=57Hi;q6Ya$_ zR2LGQWuYRD9l1eBDNXP$nUITjApd~bFaE$wb`A@M|9z&|KK}*KRBnIghx+Z4O%sf> z2n57c_CjB$N7>oeI&B|p9I~dowEY>;fG!NSIbTav743{-IX#S0#5ixkh4EzLxQ#4V z*u;FfJ<&O2AA)eVQc^gX`{)-Qb5{k;2b9LgxJ=t!pQqw=mYVQuY6zFV)J&XF+5B4- zwexW|J-^0v<|5@=KlM-d`X9!HlW1zL0u**j5P%nVQ)2$eqA+NT1pjzHK#IWYD1%LN zWz?^8BWpv%H3_n!O4swrlv}H)c(=pR{>Lg&g^B<}zqF{18daar>ASHvASSPYk( z45YJB+gL;=83xaVZ+xIr#9at~sj@%AF4;%E_xt_b9_fY;GE zfg9}Ji}M9AAp`%2cupd+C}53~YliYJej`O45_gpmzBoFg`vSsPg~846K7}uK@>j;W z?i0wN^&~|;1=_@>03zUWF}Ot(g4T(3#i_zZhj`7LSBCl)AeNrzURjq+pj<=9D{CFk zn?Mw-Fg$PCxKY=Z0!?|`rAGv_a&y`t?v=%eIK|vWN^L5O41p+e$;7oK2GAss5R>S{bG@Y+%U&>8jje*mGk~QA^`36WokbiatY>b~!B9>K_!wcR<3=Rn-mJ)P0h;7SBsVG5 zzknlI>evkcnPD1%e5BjS8}E#(PXI~?V3h?)Y7hXwcZD{E6QvPOD=bk2?8_xqV3W7Y zAVAnPp0iFFTV;t-$fi!f!{q=8fYR0|;75ENaVA5xbsA2 zu~w^D`7qvN=(B znp-K1e(|2*KtJyJxibatB)#9N)OTDA@~n$$L#DJnpF0xnx$iCK_uLU4-kIXT`~Oqw zKd?Z#vj)R@z3V$n7JTphyEkc=Dmb^>9J@z*Q~U9qx7>9(JP{__`{wXmm}XDU*?DwW z9S`0g*2HdI?Ht`*iFYN@i7?rN`qz}Ww-)wRS6EBIx!qp3d$c#TAK!V)U6;cXVKRMB zjDd2uws91(r+)dDEWZpQEEcJTu4St)U$Gkln8aU35=Fog;bB2#lC_*gSGzteWLs09 z?*cWDYxCRLiLYbf!g52Qa*ynvw0*cuT$pOM0fh(EP6=)Whu=M8pE`5{Hx70U5SU)T zn&eae_6bW3Afbj+KEh4qxwL)w)SK2;ux|fu=#ce3PpCI`eLVm8jwk9MhHOn)9UKg; zSaA|cRM8uF(05@BNCy1_A7mT#`y9RdMcwakh{m1&Xo zoodQbP|HHj4&@4NM*@K)H2&ZxdhC-8D^Mh4?1#Q|&emQbUkH@imC=rkhYn*bbm+{qeFRID{>@VR7VZ}FqquL$RCbnn6&a@ri}2%?5wxLPKH8pMvEMv?$!G@q z{r+xuAu23OT2P^g>5q1RvN&qZt+p>=4Is159pB*d2PalD1bbHgjx=?Lb zryFf{7OtOZl95+g)b6;OK2wk4=_qSUS}R1brFpmji9mM0ivTbz!~L<%SlxtFcn$8G zRpBF-nKy{>no{a^O7+ z0COIGTjuTv@Bv5~yx&8f^9*-$VjSnU^FscY1)Y@0^6r$KIXukc(sqgHSJT(oDMk=4 zJG@O~sUABC$@QDO_nZ9(ZGH+RlDs&$gd(o0bn--jQuN~={r=bN16Z?tdZ5!T{1NC1 zpL2g>(mu6s47a^xoUHDoviZEVyk0h;o-?a7g z*W2c7!X7<0WFKi7vERCM+QuM0u6jIW|LPs*=@JPW+TOCC{==sn9MISZKORC`h2)eg zxE!dSHI8JpCQv|?;P>cQ|NH$MsGsUG=_1~F^$`89Kf!VM9swsh?VyWarX4vaOvJ$# zjUidL2&L4R*Z9{r>xkBpC?JOEALkUZ*ue`avMA2l1(yOXfQ1x%lwZCCq`<-8p^K&1 z16XAQsL&rA)R3Y?_YiTf&r{4*S;{1!O>36-3h$ew<5kL&fH47U4ss|zC`S=oZz9Zp zF%g}HQZzyH*1dcH&Sm}D+F>H<#u-*KsfOh zDYUy=rYN$FxHDc^K02L%EbAD)R7NK_m! zB8mmT$Y9|U0sK+K903>+k;Rfv=YP;#&K%*EMp(eP<&1XfnRn%y6I-C8i+2TUlL#PB z1~-{B79LqyUFe^eThAchw70wV3XEY6jmYH#D-RgUu~b6I$KuGU10cqTgdtL3sJMr5n>2hn~-C3+*vNg<+4S*ZWGup|QdkfPzeTh2IeOS^(nDd>p zWSM-cF9ZA}0aCOPAy6cNa;y4FBeN)~ob$Kc7HEO8ERaKWQHl~@OQI_Bh-HNoUJpkM zP~;(o^$vj&({vPpgOK2+8;DLL<+zf%&^$zERliCz2TNXxLr81lBw>JL#IMl+e~CCa z014#2rFl_+wcKm7M6`*GqL{1i$29oO3 z!7||(BElWx-Ah=HRLBKK`2;`!Cy;xi=6ScSxrNmSAVLOrGsAi$CCd=pViM)_ea2)G z3m<_I(Oa^rA+w5>)CywL3{a08dyKmh++MC*s@4V%%%i)PI+1@vY=fX59C!-+ubN8*gN4bmP!JW5+XYcwBlLg;< z|L#p1rV7sOzIFF#Z)!ij^On0VhbO{hd*2)YDB)B53WX0-hIi_OX1LvY$z9odpw8%v zX% zVOLBfVICG)idlQsP97h#|F)Uk)&vVYgZ)#(4fee+w&PpO`y^yy$}izI#)4Ybk%sg# zX|ufuKE4~wLc-2j+0m3WwJcd_(WWh}77>XArt~cqYfO@*c&%OMIV@N)gpzlDc+lQo zGirZ`mB)?e>)bU)grAcCvsf}zJ(;o}d*ltPVK@7WlO1+zxXv~fA-kmf8Px#-odhR! z9I^OI2vHbcDY08qoR-Ujc6kw!wfhzVDRNWNZe?Iu!Fd)@?I3E`uN-CXx1M~(I`Z=E z#@3eo4_|!Bwk|U{^Bj>XIxs@r$gQa6Ly);f>Rvss(A-t%>C#iF{@j*0~9w93Q(5*~}OWD#S0f+#o#+NQ0ebjiMT1Ad4X zk>I54BN!B*2s%(Ui~>Ws^Y)YX4g?z>BG-Z~On5z_gL)A1kX#}1+M?EUV#ONDmu;44 zCQC%hk%X~L`>fr<99e=7dC+dHM>wL+v?i3Ejo44W^Hu96r14i4YV6gaR$E13dn-7#*x4%VXxpY z)=%&34moABCTk+X1>evK7^@+Y^CJglTj8nAAnXqFY^+NP_A`_r23ABUk6y)? zi%ZEW=W#qw{_jP#+qlfF6L~TpfQDUS`d9)mNm}muIN6@lI79E*w`sLC2)DFFT*@!s z!~KNSev`bhQ=Uh=wjRYQYPyI>Qh{ZOcGxwJM1-sNNnlnVb;Vt6N%1N6WFSWv|Ei0$KH*vdJ z$MR=w63~Y+q21@MM;GjP{iHp2W53NjM@QiGHBY4N9Y=53G+aDadWiZnfc2I7CQ5(l zLs>h0e8g^!)!Wqf7&8FN`B-B$A6v5Nky3XJsy;%btEM@-@-nC{(*>vE9^+%wJ za}?nUU~-{DNsR(xxU!nW(!k3blt-?P(U4aK4iiX|0&qohjVwd}QdHL-frK^7K@{dJ zjnlwIN^^|zq<+FN{m}sxfuNcbL^y>m7Q5onHyC zLm;4&jMC>d4!yeR$I7)^rD%);mxfGkP`Y{?o20@gGV zOeCkE)PXojnB%3C%Zehig_Rzg9%;F@U^ODw7A8QfK_HlI!E!}Ju&hav zo3a92>MN^FFD%-{+`-KLr$e}_vX4)`xO7v-(^*V9@>)$hGhGM_lk{GI0n=kETirBU-!_$E)W=N-Y}p1*spJ$OHr;q~#K zR{yxbvh3VZkzvCGi^QGnu_vJ)?)e?w_SWyalI%UP^Nziza`!)YzZU5S^-qV$^^e@a z&bd9mbN%Km2X`ged*VOe{x>!{<}N%c&C|d3%a#WRigrtqD4-t6g&q5%BZL=*7orfw zb~_cvJv?p;xNj`H$qtZ|VU%zQUq!7%dO;z|&r7oFoo9=L3r&q8ARz#G)RBECLe1=A zNVs7km7Kl>@5agv77;)QeWD0od5v6OMbNm-y*oSqt`s1Qi}0+JU1BNB#~_K+=PP{& zZ0vhw?zinG+ESF*L3CFi1lFpj({{9L(%wK}S-ucl(}Q}w&^CP&e%upVLJKyb7`N?AhQ|7`Jg+12p3BM7(wTR0l z!F_h+c0|64P?9?L&stF)0xSwj89V&?ynd_I7G6V0V+T?KetrkaKZ@k`3ep7i)Y`pZ zkG722t))tP{e>2kRD>IL01FRL{iP}1i=u#_T1fcchI$rnJ`-c*@R*>`&5$1+>pQRP zbmY}TYFWQ+c>rP?t3<(BlfuMxl$nhYtVj^sCw@n}xYzTOFpqczy7E>Ql;++D7g$u6&`?MA>=FZ_L`0kM!G5 zHBNyaMeU!AH`+@tcbND*#9iZjHrho-C{j7Ia>KzAqv~|hvX!gs3dvnZ6{tEx2Z|wR z*B)E9_NIATg`+|wwx{YQ?0xX0sGEHQu;;m}2W*Y9%A{GOr@kA{JGJ{(Mt`lS+OXS$ z5HVA~q;Q^;`hCkLcblalln?q%KGs|D;Z9af z#!hsP!m|ZFgw%hzVF;Nz4zXMK`*=53UJxP`YcINltfc5VWc>ZV>z-E$Ff`^ z#};kTiEPsnWj$7xWtopiQEVc`A})8i1;An#iw?{H(|hmrw*3C*n>%w+n8@qUaV*?j z%>C|H-tX=2Isf;6&avw^8fb4RAM~ASgtZj}O!X54Yqh69PDQv8^TeM=m6E(Ixlka|U{>QGHkcLwBsXK?Q{|W*&^FsyEk1jl`BHEIfU0*BDTHC)eWwoP%urE#qL+A)#@NnB z<21#1QakfBnb5b6XgiGys=~)Ub)+`Y8253`HSNBq{G0yAT;xe1+wrSMc3giz7FV`e^fT*Nkw3e@F+Lbv!Zed5O5zR z=$mN=Q+Sp#yQ5MDVi?D;#FJ*OAOR`%Vu+};l;8cTpajcdt>#x}s!*>XnR2SXY0aR& zi2a~>-^<`-RT8{kV2Zv00Uh8WKMqXN|2=FmbB4cP<}&y+@%d4;jJZk@6E|!Z375;e z1>&GD$$TwLzf(PV#1z%bPT2+CrU8+}AQ3Ap#B?48OmP+ryY>#!bHDt1xS0zDS|S}X8JbCa8HV%{VFB%Yppr8tN|-M~mmmh;V8E~coP_o!}^miiIx z;Ne?gWI2EfFd4zTMq!XmWk#`2$}|UNh)jF5mJ2x0282gxq7IW{hwK2;4(KQ}u)q+p zlH6p5@dw+(*G@nLF)X~F(WYXF=vg`4mU&KuP{biU%RHOJ!Dm`55jGqGAcc3bMhpD% zNI_ECWo{$09!yt!ZkL}EoXnfeuaO8qjlv8Id`s5B z1i@rT4rZgQ9mcx6%4B; zKopre(XJ}1#7%66Ks_dR@v^s&bh4L!9(}|z3-On6aV@3&-}`)ppT~;Bp#gDYv!yH?3N|C^Zxv1 z?#;fwd%Sy)c`oaO9|phmSD(Fpw|8IWDDxfMnf*7ol6`&mc=sT4f7S^<41VjcJ`dj? z)I+9C^(#|jneWUUd*6F?n5nXUhX@2z`BSx*Kx@BZCOBlG<3 zy}JjQo3l>%Vengj^~pTH_tw3mtS5t;cmM9Ck$Hai-ra-D%~>b>F!-&%`edHpd+XlO z2n>`KXP|`ali&KVot=2hmad_UvlAwpau>poOQrnh)#Ll3b;aTUA`?W?*&1L$Vn?~+ z*oM^-3RFHwHr8XVn8)!yF@F=qa00;!Wh%Fe@USS13Trw;h}Y%YEQSIfq^)FIHM&oM%z$eD{#v zpE4C`i&_^^cFOZM zU>!rr<(XtEV&m)vuMwhFHo{;q)D7|kd8-0AC@n@&#zE?`oL@?b*Fy-|5*l(_3E7MT z&(k#{_KlG)>%V|Pi4arNijan|w{)F4K{zZsld_+A%Q>q>F#A;RVY~f}3Iy+v)f`{9 z%@u&Dvm}Z@tWcFTPiztr8BhxCr?Ya-x(XNUB0eH+_0>E0qU>!AP|c!Hqp*~c_B)Q< zW+OLZcV?>tbCDrTRR(DbDIuhp-_8P$5U|z9(*lAJ`lBrVs@voQ-*Vtf7F3fGD%3U# z-6{~I_3*sapm_G-!($pi!L0FD0#ImoNo<-Q zKXS#oQ2cMA=%1a#td0J#xLy!M#GzlwAB$K!zH^R{n2W+*(!OOmuzOZ<6dDIOkiVQ0 zE#r_g5-?>NZM?VC!8n0E8gEj5V(b!1=-Z~AnECn^n&ZGg9i20%Z7g8G{=t|Mm4!4xl7(!?{QIonGr>z(u zZ+5c)CPmCDVDiYP(8c~{n|l=r9^W)+#-+mMx?+ca0pA%WEh$>6JefPF|HyYrEi#h)&V0NC}XUv+1#&;YD)?xzjaQ)ndYA|fU*R>Zq&X7gb^25GLM;? zcKh=>i3HlTVe7P&!YmipZCf3tfs-@Euxf~y!TeELkFT7i99!;T2N)e9#LjSpy7~17|6n5$Nm6LkiOc`CJk;-XnidPm03R_+A_%;~(hCg+|-B$~Xf+De1C$q|CqsBFKy+0}LwMYT04HY9e55YOHck^eVzXlZ|FoYQ{g_pZyGJ z?0l=w=pJP-Q1*3ea5Z>}dxfZEo)ZFt0_kQvbe_O32`KFh&uv1QUX&jLzNj{ zL_6i`=ky~1O)9CXUI3#2n>5N_lC<5|4r4KdkB+dS2MMfE9;j3KV*!l^z#st>?}*rs zq^)v@mFXY^kEl<{zlhi%o>!UWDD<#QhRCPb4QayHrZ87Y0j9|5Y!v2b2xf;fN>qE8 z(5R2fiD(*|$i=Z?w4(r@l0XwBGr%M8a`q z4E<5>PvfXoW;2T5=An-TJkTe^{uEfKs3j>jU80jbr-(hO=X{>Q_eY9|HQM-aXSu3V zp3~SA5U({mG6t#wv&0!R0BUKOAt_vNE#sNFHSov5y|C@Xd`YvB6O#Fmo{^s#OiILH zamTcP0%Fu@{!({!a}a=~kmR0mKm-9)0N`>0YxUHH03N_!jWqQc0q2V96CR@+IKGt+ zkrXCF$tDU+bQv)+Vx+CjhU7Z=LGf0|+vqs{I&hL3DiT1$y_oc9J`Wfs)D0kt`w1BV zSitv*~k@Z&aNm+^g+Y(@4u zfXPo2-CE{64B2fq95#FCM^{$>5LYlSf@QJ~2fLV(X+CqXmoAxxwG+LA(_H+=Bsyy? zbOgYL{;a|~(4>tmz^m90zz=m4Ka9yw>8hRm#sikj34dYmpRIO`{ld-1Xr@J?;h_SWFE*m;fKL*{nh8;`-6JOZl_F*Wxn?S zl>6@u&SdVq>w4zo&iCD4+3(v)gByHN&$^UJB9m_B``+^p{GB1W~$!5cW=nl9p8H+X{T;8H|>1i{q?Z^Gu4x+*zZIC8yy|nHQ~8@?%noJ z2A;9$Kf?hS3qh=b24j_!Qs%o}2zogP`7BxPdqLjVn@=p+Upa6MJv(e)9_qBAfnb+e zi|zIpk0Ze5V#3gMat3qP6&prDo*XT<)jJ4i?CxlHP$5wjW>C)Gw3{8p_O$uYWt+#e zVfl4dESWE$NEUV?;9*0rs2&|e09itT9e9yNkX;{rv>U5kbl+t|AT2z?;!m`TRm?_` z2ttZHAV-l(PXeXF$mSp*Y$23L8FQrzW`lO@wGb8@4cX}@`)vll79(TjZWbfqf0hua zzB7Vgo!b=%TvEuxY0|2kiNqT{in10yI^cyB|fkkti(-F&HW+h$snOF5aeoqMaT^km>50 zvG+9%*a(XFm(CrrmCKSgP$t+Rbp?bk33(y5y|>1tP^RspAG~58uf|J0P4cnl9j5?^F!>aG*eFY^Z!;MLeL5q;%!grmebh6*HHZ&G$izCi!>1v)BG- z%r7S6+xGX*KVefZ1nn>9ivkfOSc&Ez!||MyNYRRA>h)7NQsxczOV8ma=OTRxf$zxM zrqFegcI|vUK7F{ThSyD)7)Y^G^dp%t$y7%QY*9y?h(447aBo3Tniy;H=hFK0vi!bU3P0loSQD}WkYW@LW$mviX8J?pwJ**{}*_CwSn45mPaVwas#28W- zWKq^7xW^}XKXoL)A%-ws(!On_IGSBs&2zswTd!yW^tY1%z6EyZfUJ4`bV>PS9`ze_ zum9$ocJ_NToiZRjWVx5_o#!>~ESLkmQ>+gGC9af9i{&^? zI~n~^CJt%FpX$NSt1iI+1Zq2*h4N`W_B!Xh`ku^-{I`mec^6~Gvmus>@=3ltnhfou zevl>#mOwv+5Dv*PtpAfg6EcirPA@MBc(`7`1DX6JF?*01sZ4%kf}{L;-(oIR#29A* zF+RzHx4*zBH}zp2@JKtXF*3#Rh;*v}3o?=OaLK$+{ZS7_0S*;u2jD}Yi*@XMT8sm7c%|nV5$7r_jS;nbmM!{X7jIp$Il&lPImgVXNASH24UF)p&GfivW6nLI-0O z0S)B4My5StwTR`x`1a^`uy3?eue=HflL9Zl81Afg29p-~Dv_@i`ilcOMC?h!4w2%m zG3=3uc()XF&%2nbB9j&BO#OoXpN54K zC;zEYSQ%pg6mzRVd%Jl{zy=WmN$-1>>S z>R8TTVSgoLsv=V&FbT~Kk^?*(GV{M8?}TL*Q(kD>`%5j#21KlsI%ll7gVu5KIs$Nn zLtTxZWS#Pc)7UENQF&wRC1xphA8eXCLg5)JfFG$=IRFmYQy_}ufy4kZ<}GBUH9`vG z6D8FNuptnIylc#D&r^5(w8c%*!F(&(h0p@iCbxlMOB*Y4)y52Lksf@jT%+>}AZe>K z>%gRnexTv4I+aPa0U||j&v1gaI9y}l1Kgu^g+7u*j#1>dXdiud5~dJNg4dh3?EHWD z=iYuJ!#I&`2D!<6G|@fOf4w%S39o03&)^_aNju;7o`2x)%*mbayT53_d(!?s)<5%> zQV%|R@7+7fT+4j>mHRGdoyf{;<{5ow-I0CzuH(IX_KxnlA(PI1m$ObheE;rX&91ww z8p=L>*YVyxdq;QOzz@H2-{q_m59|NGNd1qDWf&-Czxd4`vTwd}n$T`YY{(zEo$SaI z1Oj**bAb3L3%8VVS}&(7Y)`*^$lhBuVz>#l?@Tw^js6B(y3Im~KqIAHjVz(5>cp0v z-ZxIm*+hR-o?8r~D5NXF=GV4j$69;GTuaa+*SD>XLd*wB-aN#pUp;vup59L%s zhlC$Zm;!fXn=4wbvrbS>_J3f>YD$*v(zQke2NvK8Li4hlyv1&T!feV(o6J{|>?|pb zB6h*!&{Nl-%ctZ#Q#+&Vp6Qwdb-g3E48;YdT|P#%6E2WPyKa=wN}n|QnYW(9RYBCg zgo9c|HaXrfW@Br`cJbSRnT-TRIq56hmvpD9&(;px#gz*C(u;>|;|f)z>ryNva8qI0 zbvgA{C+&al7+}*nV$UI5efm33*vjkN$CwBs%;vYE43=+MSM#iWxPHL)p(ysExP5J^ zl{ToegP2hd zB?&6pbz?_zYXYGa>U5xmQ9rb6l(w!qv~G>nOU^8rf@P$BRoAj)CyAPZN@3>)T5b9| z<`!<2riw*)LpT>ZD5n;Gh^<5!!VF|~uE17CphBY*Ylm9C9da7ecKpdPdtdF4{pnPj zUHEE)r5OWa*5sf#`%WOfFkIOeAl06fRz%og zHwn5y8Uhs9C5|CXDtx2bC=4K=T{G32Opngc?}`Cj{s?ExeP=77gQKvs<67RmX|+{2 zGR7=#8Ais^C}7k8X^M@bA~M_r!a0M0JV+svsh%j!G4fGewqo7YGqwmL=f+4q?VGY^ zyKmaFWn1>y`6B!Lw~yIoKlLl$2GqTnH3DVauMpsXvkAzvJNK>A7z89>7cd1a+d*5Z zH%i|u0$326X91_h3QwtQxzkG6I@-N2ZrHLX_6=DB5v``T@@)#xa~rnF!fK(-;;TeL zvBtENBE3WQ=(+irByD5YJ}QTV8sDTUrJu6P=Y}ioq%8o!0XD{;6lzyG08}IdYfJ`A z4eCl2MqMeytC$h4L~9;FI4^78v;zFph@rPc-`Ej)-zX)U9ux_h_gI>HO zpc3VezHsE-)Akr9V;6C5JU&-qmE~(zgrnd)Blv0p$g4lFWPh{!sx467PyUCeUD5Fh zQhxNIAN$;*L$d8=cjVj6=yY8mgwFdn8RA%>cRdXa6a99rh5T}l@DfA$b$~^H5z32L z1*!`HFF`ot;9H?|HHP(m1{D8uAIh=&D$%!?Ahd@AE9} G7O(L4-mN8p7RZZcOC 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%0DVt$vb=mkWJCW(S6K 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%0Dn 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�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

    FeP*G?5=q0=TAAX9Sm?D1h6!a3s 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
    (;s6b%S8 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%0Dp&(n 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%0A{7 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%0D14kNkU!V*NJQuWwFSxjzQa_Me~D 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%0Dd)NR}^Pk^pU1kDfDV5`i~O1El%Rs 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

    0fXQ^);33$q_gEKI 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

    7LO(8UhL5_T-H8LbW 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 literal 0 HcmV?d00001 diff --git a/gallery/tutorials/data/ctffind4/diagnostic_output.txt b/gallery/tutorials/data/ctffind4/diagnostic_output.txt new file mode 100644 index 0000000000..3f975cca12 --- /dev/null +++ b/gallery/tutorials/data/ctffind4/diagnostic_output.txt @@ -0,0 +1,6 @@ +# Output from CTFFind version 4.1.14, run on 2023-02-02 15:13:07 +# Input file: test_img.mrc ; Number of micrographs: 1 +# Pixel size: 1.000 Angstroms ; acceleration voltage: 200.0 keV ; spherical aberration: 2.26 mm ; amplitude contrast: 0.07 +# Box size: 1024 pixels ; min. res.: 30.0 Angstroms ; max. res.: 5.0 Angstroms ; min. def.: 5000.0 um; max. def. 50000.0 um +# Columns: #1 - micrograph number; #2 - defocus 1 [Angstroms]; #3 - defocus 2; #4 - azimuth of astigmatism; #5 - additional phase shift [radians]; #6 - cross correlation; #7 - spacing (in Angstroms) up to which CTF rings were fit successfully +1.000000 9982.502930 9982.502930 0.000000 0.035256 0.565683 1.980663 diff --git a/gallery/tutorials/data/ctffind4/diagnostic_output_avrot.png b/gallery/tutorials/data/ctffind4/diagnostic_output_avrot.png new file mode 100644 index 0000000000000000000000000000000000000000..1028ac7f40c17b35d5557be5adfee56257cbe6f6 GIT binary patch literal 448139 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¥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 z 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%0Dl_CBg)wh+`9?UA{EIRIiU!@C^#J6pS}u-S^I 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𝔇;y>Obl#wfAtp& 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%0Drjg= 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

    wV$ZVKT+#n$bsGvCh7ub!q0~qdp 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�O}U|(K}A6X3jlkbi* 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

    s zGH^6Bv-@=8v1GiQR$W|4uZ^Gb+ySjkmwZlbjXy<00C-sH*m+1)&iS>X(jwdbbMyVG zdq(2b@+!-<%GPW3mvcRhO{_R$u-R;qn%=&pE4SbuE`-Pt&GC#CkUjqb^z?J&4&jY- zczdFS4_bSrZGQlP`|IK)PITXHt0$ML0(R{=8eY$_aY;$!?3laV{g+nyTv4vV_&+2y zNiA|TI?f$3A5$&&ueE$yChD_43k$Tp@>q?gF_x#c#((1Oo)DR0UI-Y*BCOb1XDrjr zj1Mj&N8Jl7+m$n&c~iDcyU-)avQmZ&doL3VV%ZM>myn`*6i$w1j$?+VZ-Bgo$nFW( zTpt;l%0(+_Cz)*M;nk5$p zn~)eW6DfX$X*LZ|G;K>sw|!t|ZQ42ArRw<1}2tdui5 zk2sOKx@1LMiUX-Xv(ifOebNP>bQsWVOnn~^+u((b|eNPk5G7SkXaW7xh9MS;fGI7NTO5N#{GXdl&u=b2$4ls~t2 zueCM^vo82OqS`_%Wm85+;#P-aadQ-oQxV3^BDm}&J2dQqBi}h&luyfMKK1LAe3o6A z$%{nuPsvReO~IT91O|(#xzaa35V@O7tX$AE9n$=Cvyw7o72;8*XPS0M21ihLc0c*V zj4Akk(h|-}>G83Gs`*714^#k!L#j^pD0yG}wZ?ecmNvli&WvV8!}R~42PBbA5loT5 z#u^Kd!85Nh5wmytn{1n(R_U$sY6@-~yI z`A*ZFplePy@UK5+y;M(UFzL5eSR@+Wpx ze}iJ)7_Tewp+f#toa90v!CSJWJ!h*dnU$ZmApcnwI}O3p?w!2U_P8< z$DIEKG)GL-tK2^2y7<3Y0Jc@=shVqCnHCTFr7J2;3pu53h88c?TK-ysd)O89n?IVF zQsORpd@HDNK)^24J#Z_C`+zLp`R5u05I%3q-OEdWzk8~5j3~$?F4j=GT+0t;3W?Y8SfTgVFVddm!amSb+DVEfj~+o;(P<;ElmQ*uV0sR zgmXQ6wRE=xNM=>2kD!-c?AB2I- zqKfsK2(+gVc*<�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 literal 0 HcmV?d00001 diff --git a/gallery/tutorials/data/ctffind4/input.txt b/gallery/tutorials/data/ctffind4/input.txt new file mode 100644 index 0000000000..1d5baf42f9 --- /dev/null +++ b/gallery/tutorials/data/ctffind4/input.txt @@ -0,0 +1,68 @@ +(aspire_ctf_valid) ➜ bin ./ctffind + + + ** Welcome to Ctffind ** + + Version : 4.1.14 + Compiled : Jan 31 2023 + Mode : Interactive + +Input image file name [test_img.mrc] : +Output diagnostic image file name +[diagnostic_output.mrc] : +Pixel size [1] : +Acceleration voltage [200] : +Spherical aberration [2.26] : +Amplitude contrast [0.07] : +Size of amplitude spectrum to compute [1024] : +Minimum resolution [30.0] : +Maximum resolution [5.0] : +Minimum defocus [5000.0] : +Maximum defocus [50000.0] : +Defocus search step [100.0] : +Do you know what astigmatism is present? [yes] : yes +Slower, more exhaustive search? [no] : yes +Known astigmatism [0] : 0 +Known astigmatism angle [0] : 0 +Find additional phase shift? [no] : yes +Minimum phase shift (rad) [0.0] : +Maximum phase shift (rad) [3.15] : +Phase shift search step [0.1] : 0.1 +Do you want to set expert options? [yes] : yes +Resample micrograph if pixel size too small? [no] : yes +Do you already know the defocus? [no] : no +Desired number of parallel threads [1] : +File name: test_img.mrc +File type: MRC +Dimensions: X = 512 Y = 512 Z = 1 +Number of slices: 1 +Working on micrograph 1 of 1 +OpenMP is not available - will not use parallel threads. + + 100% [=================] done! (0h:00m45s) + DFMID1 DFMID2 ANGAST CC + 10000.00 10000.00 0.00 0.56503 + +Timings + Initialization : 00:00:00 + Spectrum computation : 00:00:00 + Parameter search : 00:00:45 + Diagnosis : 00:00:03 + Total : 00:00:48 + + +Estimated defocus values : 9982.50 , 9982.50 Angstroms +Estimated azimuth of astigmatism: 0.00 degrees +Additional phase shift : 2.020 degrees (0.035 radians) (0.011 PIf) +Score : 0.56568 +Pixel size for fitting : 1.400 Angstroms +Thon rings with good fit up to : 2.0 Angstroms +Did not detect CTF aliasing + + +Summary of results : diagnostic_output.txt +Diagnostic images : diagnostic_output.mrc +Detailed results, including 1D fit profiles : diagnostic_output_avrot.txt +Use this command to plot 1D fit profiles : ctffind_plot_results.sh diagnostic_output_avrot.txt + + diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 1f2f151fb7..23702670ab 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -144,8 +144,14 @@ def micrograph_to_blocks(self, micrograph, block_size): # verify block_size is even assert block_size % 2 == 0 - size_x = micrograph.shape[1] - size_y = micrograph.shape[0] + if micrograph.ndim == 3: + assert ( + micrograph.shape[0] == 1 + ), f"micrograph should be 2D or stack of 1 2D image: {micrograph.shape}" + micrograph = micrograph[0] + + size_x = micrograph.shape[-1] + size_y = micrograph.shape[-2] step_size = block_size // 2 range_y = size_y // step_size - 1 From efaa11f34a81364ebba94ffd16f677fc66c3c38f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 16:17:49 -0500 Subject: [PATCH 102/424] Tox ignores --- gallery/tutorials/ctf.py | 1 + tox.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index cda609954f..be6d4e82c3 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -10,6 +10,7 @@ # sphinx_gallery_thumbnail_path = '../../../gallery/tutorialsdata/ctffind4/diagnostic_output.png' import matplotlib.pyplot as plt + plt.rcParams["image.cmap"] = "gray" import numpy as np from scipy.ndimage import gaussian_filter diff --git a/tox.ini b/tox.ini index f37b2826d4..695d334e47 100644 --- a/tox.ini +++ b/tox.ini @@ -69,6 +69,7 @@ extend-ignore = E203, E501 per-file-ignores = __init__.py: F401 gallery/tutorials/configuration.py: T201, E402 + gallery/tutorials/ctf.py: T201, E402 # Ignore Sphinx gallery builds docs/build/html/_downloads/*/*.py: T201, E402 docs/source/auto*/*.py: T201, E402 From a4161441365bf9256add4eb5c115e3510ad2cd65 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Feb 2023 18:04:06 -0500 Subject: [PATCH 103/424] Fix CTF gallery thumbnail path typo --- gallery/tutorials/ctf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index be6d4e82c3..fdb6bcf698 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -7,7 +7,7 @@ lecture notes from MATH586. """ -# sphinx_gallery_thumbnail_path = '../../../gallery/tutorialsdata/ctffind4/diagnostic_output.png' +# sphinx_gallery_thumbnail_path = '../../gallery/tutorials/data/ctffind4/diagnostic_output.png' import matplotlib.pyplot as plt From 4aa6aae9777ffad7197f4ff1e45fe05df634ae55 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Feb 2023 08:31:50 -0500 Subject: [PATCH 104/424] Change MKL_FFT to MKLFFT --- src/aspire/numeric/__init__.py | 2 +- src/aspire/numeric/mkl_fft.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/numeric/__init__.py b/src/aspire/numeric/__init__.py index b1087d8454..d298f131e4 100644 --- a/src/aspire/numeric/__init__.py +++ b/src/aspire/numeric/__init__.py @@ -28,7 +28,7 @@ def fft_object(which): elif which == "scipy": from .scipy_fft import ScipyFFT as FFTClass elif which == "mkl": - from .mkl_fft import MKL_FFT as FFTClass + from .mkl_fft import MKLFFT as FFTClass else: raise RuntimeError(f"Invalid selection for fft class: {which}") return FFTClass() diff --git a/src/aspire/numeric/mkl_fft.py b/src/aspire/numeric/mkl_fft.py index 2cf2558216..57dfff1e0d 100644 --- a/src/aspire/numeric/mkl_fft.py +++ b/src/aspire/numeric/mkl_fft.py @@ -4,7 +4,7 @@ from aspire.numeric.base_fft import FFT -class MKL_FFT(FFT): +class MKLFFT(FFT): """ Define a unified wrapper class for MKL FFT functions From 83c208cffb320cadceb0fb9e1cdf989bdd8da6ba Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Feb 2023 08:38:41 -0500 Subject: [PATCH 105/424] cleanup conda yaml files whitespace, remove pip install line --- environment-accelerate.yml | 4 ---- environment-default.yml | 3 --- environment-intel.yml | 4 ---- environment-openblas.yml | 4 ---- 4 files changed, 15 deletions(-) diff --git a/environment-accelerate.yml b/environment-accelerate.yml index e4ebd6f42d..f3f3e9c22b 100644 --- a/environment-accelerate.yml +++ b/environment-accelerate.yml @@ -4,7 +4,6 @@ channels: - conda-forge - defaults - dependencies: - fftw # There is a potential pyFFTW bug, pin to 0.12 @@ -18,6 +17,3 @@ dependencies: - scikit-learn - scikit-image - libblas=*=*accelerate - - pip: - - . - diff --git a/environment-default.yml b/environment-default.yml index dc72c57a53..ec4ba68b93 100644 --- a/environment-default.yml +++ b/environment-default.yml @@ -16,6 +16,3 @@ dependencies: - scipy=1.9.3 - scikit-learn - scikit-image - - pip: - - . - diff --git a/environment-intel.yml b/environment-intel.yml index 751b3bfb86..b06af37b36 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -4,7 +4,6 @@ channels: - conda-forge - defaults - dependencies: - fftw # There is a potential pyFFTW bug, pin to 0.12 @@ -19,6 +18,3 @@ dependencies: - scikit-image - mkl_fft - libblas=*=*mkl - - pip: - - . - diff --git a/environment-openblas.yml b/environment-openblas.yml index f57d0f37e8..3eb19c22bf 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -4,7 +4,6 @@ channels: - conda-forge - defaults - dependencies: - fftw # There is a potential pyFFTW bug, pin to 0.12 @@ -18,6 +17,3 @@ dependencies: - scikit-learn - scikit-image - libblas=*=*openblas - - pip: - - . - From a821ab033d2f3f35c9643e447fe926e2678bdad0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Feb 2023 08:40:33 -0500 Subject: [PATCH 106/424] Add comment about xdist in workflow --- .github/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 8213f864f8..8de9cbb9ce 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -93,6 +93,7 @@ jobs: - name: Execute Pytest Conda ${{ matrix.os }} Python ${{ matrix.python-version }} run: | export OMP_NUM_THREADS=2 + # -n runs test in parallel using pytest-xdist pytest -n2 --durations=50 -s docs: From 29df2e4698f9fd05b9045669323c783c4fa31885 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Feb 2023 09:20:20 -0500 Subject: [PATCH 107/424] Use tmpdir for tutorial MRC. --- gallery/tutorials/ctf.py | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index fdb6bcf698..b7bc5a8c85 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -9,6 +9,10 @@ # sphinx_gallery_thumbnail_path = '../../gallery/tutorials/data/ctffind4/diagnostic_output.png' +# Get some common imports out of the way. +import os +from tempfile import TemporaryDirectory + import matplotlib.pyplot as plt plt.rcParams["image.cmap"] = "gray" @@ -234,10 +238,12 @@ def genExImg(L): ax2.set_title("Incorrect") plt.show() - # %% # ASPIRE CtfEstimator # ^^^^^^^^^^^^^^^^^^^ +# Here we will use ASPIRE's ``CtfEstimator`` on a CTF corrupted image +# so we can compare with the modeled corruption parameters. + from aspire.ctf import estimate_ctf # Using our radial_ctf_filter from earlier, corrupt an image. @@ -245,19 +251,21 @@ def genExImg(L): plt.imshow(test_img.asnumpy()[0]) plt.colorbar() plt.show() -test_img.save("test_img.mrc", overwrite=True) - - -radial_ctf_est = estimate_ctf( - data_folder=".", - pixel_size=radial_ctf_filter.pixel_size, - cs=radial_ctf_filter.Cs, - amplitude_contrast=radial_ctf_filter.alpha, - voltage=radial_ctf_filter.voltage, - psd_size=512, - num_tapers=2, - dtype=np.float64, -) + +# Create the image file in a tmp dir +with TemporaryDirectory() as d: + test_img.save(os.path.join(d, "test_img.mrc")) + + radial_ctf_est = estimate_ctf( + data_folder=d, + pixel_size=radial_ctf_filter.pixel_size, + cs=radial_ctf_filter.Cs, + amplitude_contrast=radial_ctf_filter.alpha, + voltage=radial_ctf_filter.voltage, + psd_size=512, + num_tapers=2, + dtype=np.float64, + ) # We'll use these estimates next. print(radial_ctf_est) From b1caa4f0dfbc8cd99ed65e6a8a6b69d68c6071c0 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 6 Feb 2023 12:01:28 -0500 Subject: [PATCH 108/424] loosen tolerance for testCovar3D --- tests/test_covar3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index cd3b42d576..eb6036c7b7 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -148,7 +148,7 @@ def testCovar3D(self): ] ), covar_est[:, :, 4, 4, 4, 4], - atol=1e-4, + atol=1e-3, ) ) From fde3a229081e1c6cd6e10262e50ec0010ca575c2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Feb 2023 09:27:10 -0500 Subject: [PATCH 109/424] use Warning instead of note --- gallery/tutorials/ctf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index b7bc5a8c85..4c6cb2552c 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -190,7 +190,7 @@ def genExImg(L): Image(phase_flipped_imgs).show() # %% -# .. note:: +# .. warning:: # Centering the signal FFT is critical when the CTF is centered in Fourier space. From e4ed59981822bad2e6ac23af8aec167f1e6cbb0c Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Feb 2023 09:27:45 -0500 Subject: [PATCH 110/424] rm debug comments --- src/aspire/operators/filters.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index 44834d6b39..146c8c8986 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -450,9 +450,8 @@ def _evaluate(self, omega): defocus = np.zeros_like(om_x) defocus[ind_nz] = self.defocus_mean + self.defocus_diff * np.cos(2 * angles_nz) - # f0 = 1 / (512 * self.pixel_size) ## XXX 512 - c2 = -np.pi * self.wavelength * defocus # * f0**2 - c4 = 0.5 * np.pi * (self.Cs * 1e7) * self.wavelength**3 # *f0**4 + c2 = -np.pi * self.wavelength * defocus + c4 = 0.5 * np.pi * (self.Cs * 1e7) * self.wavelength**3 r2 = om_x**2 + om_y**2 r4 = r2**2 From 25e8fe7178dc99de4ded38255ff9c4ca2047b9b8 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Feb 2023 09:36:42 -0500 Subject: [PATCH 111/424] minor ctf gallery updates, typos --- gallery/tutorials/ctf.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 4c6cb2552c..801dc0f2cd 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -81,7 +81,7 @@ # %% # Phase Flipping # ^^^^^^^^^^^^^^ -# A common technique used with CTF corruped data is to apply a transformation +# A common technique used with CTF corrupted data is to apply a transformation # based on the sign of the (estimated) CTF. # We can easily visuallize this in an idealized way by taking the sign of the # array returned by ASPIRE's ``CTFFilter.evaluate_grid``. @@ -103,11 +103,12 @@ # %% # Generate example image # ^^^^^^^^^^^^^^^^^^^^^^ -def genExImg(L): +def genExImg(L, noise_variance=0.1): """ Generates data similar to the MATH586 lecture notes. - :param L: Resolution in pixels + :param L: Resolution in pixels. + :param noise_variance: Variance passed to random normal noise generator. :return: Image as np array. """ # Empty Array @@ -128,11 +129,11 @@ def genExImg(L): disc = g2d["r"] < half_width // 4 img[disc] = 0 - # Smooth gaussian + # Smooth hard edges with some Gaussian blur img = gaussian_filter(img, sigma=7) # Add noise - img += np.random.normal(0, 0.1, size=(L, L)) + img += np.random.normal(0, noise_variance, size=(L, L)) return img From e88dd48459b2d6058cd4f92fcf3ebb04422cadcc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Feb 2023 09:44:52 -0500 Subject: [PATCH 112/424] Change ctf demo pixel size --- gallery/tutorials/ctf.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 801dc0f2cd..5fce6e8c59 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -157,10 +157,16 @@ def genExImg(L, noise_variance=0.1): # Construct a range of CTF filters. defoci = [2500, 5000, 10000, 20000] ctf_filters = [ - RadialCTFFilter(pixel_size=1, voltage=200, defocus=d, Cs=2.26, alpha=0.07, B=0) + RadialCTFFilter(pixel_size=1/2, voltage=200, defocus=d, Cs=2.26, alpha=0.07, B=0) for d in defoci ] +# %% +# .. note:: +# Pixel size was chosen to demonstrate effects similar to lecture notes, +# but at a higher resolution. + + # %% # Generate images corrupted by progressively increasing defocus. From 482f0e3a4f830e684d083c1f6ea0af99c36a27f0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Feb 2023 10:19:28 -0500 Subject: [PATCH 113/424] tox linting --- gallery/tutorials/ctf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 5fce6e8c59..19b36dc3aa 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -157,7 +157,7 @@ def genExImg(L, noise_variance=0.1): # Construct a range of CTF filters. defoci = [2500, 5000, 10000, 20000] ctf_filters = [ - RadialCTFFilter(pixel_size=1/2, voltage=200, defocus=d, Cs=2.26, alpha=0.07, B=0) + RadialCTFFilter(pixel_size=1 / 2, voltage=200, defocus=d, Cs=2.26, alpha=0.07, B=0) for d in defoci ] From 7543b05a76b1f82c2162ccb58108220044d51cc0 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 8 Feb 2023 10:04:38 -0500 Subject: [PATCH 114/424] long running workflow. xfail failing testCovar3D. --- .github/workflows/long_workflow.yml | 37 +++++++++++++++++++++++++++++ tests/test_covar3d.py | 2 ++ 2 files changed, 39 insertions(+) create mode 100644 .github/workflows/long_workflow.yml diff --git a/.github/workflows/long_workflow.yml b/.github/workflows/long_workflow.yml new file mode 100644 index 0000000000..2501165ee5 --- /dev/null +++ b/.github/workflows/long_workflow.yml @@ -0,0 +1,37 @@ +name: ASPIRE Python Long Running Test Suite + +on: push + +jobs: + expensive_tests: + runs-on: self-hosted + steps: + - uses: actions/checkout@v3 + with: + ref: develop + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + - name: Customize config + run: | + echo "Setup tmp dirs and chmod so others can cleanup." + CI_TMP_DIR=/var/ci/tmp + mkdir -p ${CI_TMP_DIR} + chmod g+rwx ${CI_TMP_DIR} + echo "Create and assign a unique temp dir to hold our config." + WORK_DIR=$(mktemp -d -p "${CI_TMP_DIR}") + echo "WORK_DIR=${WORK_DIR}" + echo "Stash the WORK_DIR to GitHub env so we can clean it up later." + echo "WORK_DIR=${WORK_DIR}" >> $GITHUB_ENV + echo -e "ray:\n temp_dir: ${WORK_DIR}\n" > ${WORK_DIR}/config.yaml + echo -e "nufft:\n backends: [finufft, pynfft]\n" >> ${WORK_DIR}/config.yaml + echo "Log the config: ${WORK_DIR}/config.yaml" + cat ${WORK_DIR}/config.yaml + - name: Run + run: | + ASPIREDIR=${{ env.WORK_DIR }} python -c \ + "import aspire; print(aspire.config['ray']['temp_dir'])" + ASPIREDIR=${{ env.WORK_DIR }} pytest -m "expensive" --durations=0 + - name: Cleanup + run: rm -rf ${{ env.WORK_DIR }} diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index eb6036c7b7..063faa0b93 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -55,6 +55,8 @@ def setUpClass(cls): def tearDown(self): pass + # This test is currently failing under the prescribed tolerance. + @pytest.mark.xfail @pytest.mark.expensive def testCovar3D(self): covar_est = self.covar_estimator_with_preconditioner.estimate( From 83c9564b1bf02809ef2003649b4839ad18bb3a10 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 8 Feb 2023 11:43:02 -0500 Subject: [PATCH 115/424] switch pytest marker order --- tests/test_covar3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index 063faa0b93..b68678b83e 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -56,8 +56,8 @@ def tearDown(self): pass # This test is currently failing under the prescribed tolerance. - @pytest.mark.xfail @pytest.mark.expensive + @pytest.mark.xfail def testCovar3D(self): covar_est = self.covar_estimator_with_preconditioner.estimate( self.mean_est, self.noise_variance From b9d5b2bf88b0c04e669b5f36efceb4f3fe8cdcf8 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 8 Feb 2023 12:30:48 -0500 Subject: [PATCH 116/424] checkout this branch for workflow validation. --- .github/workflows/long_workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/long_workflow.yml b/.github/workflows/long_workflow.yml index 2501165ee5..fc8d895a76 100644 --- a/.github/workflows/long_workflow.yml +++ b/.github/workflows/long_workflow.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - ref: develop + ref: long_running_tests_814 - name: Install dependencies run: | python -m pip install --upgrade pip From 09e35f009d85a5b633c86167a96c8831ab265293 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Feb 2023 08:05:46 -0500 Subject: [PATCH 117/424] Add test for source slicing bug --- tests/test_load_images.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_load_images.py b/tests/test_load_images.py index ca82ba5b6d..2292806f93 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -100,13 +100,33 @@ def testRelionSourceNegIndex(self): from_mrc = self.getParticlesFromIndices([self.n - 1]) self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) - def testRelionSourceNegSlice(self): + def testRelionSourceNegPosSlice(self): from_src = self.src.images[-100:100] from_mrc = self.getParticlesFromIndices( [i for i in range(900, 1000)] + [i for i in range(0, 100)] ) self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) + def testRelionSourceNegNegSlice(self): + from_src = self.src.images[-200:-100] + from_mrc = self.getParticlesFromIndices([i for i in range(800, 900)]) + self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) + + def testRelionStrideSlice(self): + from_src = self.src.images[0:100:10] + from_mrc = self.getParticlesFromIndices(list(range(0, 100, 10))) + self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) + + def testRelionStrideNegSlice(self): + from_src = self.src.images[-100:] + from_mrc = self.getParticlesFromIndices(list(range(900, 1000))) + self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) + + def testRelionStrideNegNegSlice(self): + from_src = self.src.images[-200:-100:10] + from_mrc = self.getParticlesFromIndices(list(range(800, 900, 10))) + self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) + def testRelionSourceMaxStop(self): from_src = self.src.images[5:2000] from_mrc = self.getParticlesFromIndices([i for i in range(5, self.n)]) From 5a9c695ee594ee5b10f2ef776c3e01ee0a29c62d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Feb 2023 08:06:01 -0500 Subject: [PATCH 118/424] add source slice fix and some comments --- src/aspire/source/image.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 859a87b441..6a176c688e 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -56,15 +56,28 @@ def __getitem__(self, indices): indices = np.array([indices]) elif isinstance(indices, slice): start, stop, step = indices.start, indices.stop, indices.step + + # X[:end], slice(None, e, None) -> slice(0, e, 1) if not start: start = 0 - if not stop or stop > self.num_imgs: + + # Special case for X[-10:] + if start < 0 and stop is None: + # slice(-10, None, None) -> slice(-10, *0* ,1) + stop = 0 + # All other cases, limit to num_imgs + # slice(s, None, None) -> slice(s, num_imgs, 1) + # slice(s, 10**10, None) -> slice(0, num_imgs, 1) + elif not stop or stop > self.num_imgs: stop = self.num_imgs + if not step: step = 1 + if not all(isinstance(i, (int, np.integer)) for i in [start, stop, step]): raise TypeError("Non-integer slice components.") indices = np.arange(start, stop, step) + if not isinstance(indices, np.ndarray): raise KeyError( "Key for .images must be a slice, 1-D NumPy array, or iterable yielding integers." From fa3e6e9dae2f4f18ec23d134c23feb28e8526522 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 9 Feb 2023 08:39:34 -0500 Subject: [PATCH 119/424] run job when PR is ready for review --- .github/workflows/long_workflow.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/long_workflow.yml b/.github/workflows/long_workflow.yml index fc8d895a76..2cafac783e 100644 --- a/.github/workflows/long_workflow.yml +++ b/.github/workflows/long_workflow.yml @@ -1,6 +1,9 @@ name: ASPIRE Python Long Running Test Suite -on: push +on: + pull_request_target: + types: + - ready_for_review jobs: expensive_tests: @@ -8,7 +11,7 @@ jobs: steps: - uses: actions/checkout@v3 with: - ref: long_running_tests_814 + ref: develop - name: Install dependencies run: | python -m pip install --upgrade pip From 4ee6a8b544977a029068c314177f5c1dc2d8abbe Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 9 Feb 2023 08:48:09 -0500 Subject: [PATCH 120/424] white space --- tests/test_covar3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index b68678b83e..7cf6933364 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -57,7 +57,7 @@ def tearDown(self): # This test is currently failing under the prescribed tolerance. @pytest.mark.expensive - @pytest.mark.xfail + @pytest.mark.xfail def testCovar3D(self): covar_est = self.covar_estimator_with_preconditioner.estimate( self.mean_est, self.noise_variance From 5f8fcbe4dd131156728b11029bf9a8c11b32d56a Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 9 Feb 2023 08:50:25 -0500 Subject: [PATCH 121/424] revert tolerance value --- tests/test_covar3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_covar3d.py b/tests/test_covar3d.py index 7cf6933364..1d3dadbc71 100644 --- a/tests/test_covar3d.py +++ b/tests/test_covar3d.py @@ -150,7 +150,7 @@ def testCovar3D(self): ] ), covar_est[:, :, 4, 4, 4, 4], - atol=1e-3, + atol=1e-4, ) ) From 4c20d7f647ca0c93e9be58440d34e0690df3b0a9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Feb 2023 09:05:19 -0500 Subject: [PATCH 122/424] name oversight --- tests/test_load_images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_load_images.py b/tests/test_load_images.py index 2292806f93..2376cc2bd0 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -117,7 +117,7 @@ def testRelionStrideSlice(self): from_mrc = self.getParticlesFromIndices(list(range(0, 100, 10))) self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) - def testRelionStrideNegSlice(self): + def testRelionNegSlice(self): from_src = self.src.images[-100:] from_mrc = self.getParticlesFromIndices(list(range(900, 1000))) self.assertTrue(np.array_equal(from_src.asnumpy(), from_mrc.asnumpy())) From b3f6d41aa1deaea3229b9d49ab73e80457a4ae06 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 9 Feb 2023 10:21:06 -0500 Subject: [PATCH 123/424] run on push to master or develop --- .github/workflows/long_workflow.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/long_workflow.yml b/.github/workflows/long_workflow.yml index 2cafac783e..6854a0cabd 100644 --- a/.github/workflows/long_workflow.yml +++ b/.github/workflows/long_workflow.yml @@ -1,9 +1,10 @@ name: ASPIRE Python Long Running Test Suite on: - pull_request_target: - types: - - ready_for_review + push: + branches: + - 'master' + - 'develop' jobs: expensive_tests: From 8c5bae02c3827447503726c9c8f4850b055a83cf Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Feb 2023 14:26:39 -0500 Subject: [PATCH 124/424] additional review cleanup mainly ctf area --- gallery/tutorials/ctf.py | 73 ++++++++++++++++++--------------- src/aspire/operators/filters.py | 2 +- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 19b36dc3aa..0be73c8774 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -22,12 +22,12 @@ import aspire # Resolution to use throughout the demo. -RESOLUTION = 512 +IMG_SIZE = 512 # %% # Visualizing the CTF # ------------------- -# ASPIRE can be used create and visualize example CTFs. +# ASPIRE can be used to create and visualize example CTFs. # %% # Radially Symmetric CTF @@ -51,13 +51,13 @@ # The CTF filter can be visualized as an image once it is evaluated at a specific resolution. # More specifically the following code will return a transfer function as an array, # which is then plotted. -rctf_fn = radial_ctf_filter.evaluate_grid(RESOLUTION) +rctf_fn = radial_ctf_filter.evaluate_grid(IMG_SIZE) plt.imshow(rctf_fn) plt.colorbar() plt.show() # %% -# Asymmetric CTF +# Elliptical CTF # ^^^^^^^^^^^^^^ # For the general ``CTFFilter``, we provide defocus u and v seperately, # along with a defocus angle. @@ -74,7 +74,7 @@ ) # Again, we plot it, and note the difference from the RadialCTFFilter. -plt.imshow(ctf_filter.evaluate_grid(RESOLUTION)) +plt.imshow(ctf_filter.evaluate_grid(IMG_SIZE)) plt.colorbar() plt.show() @@ -83,11 +83,11 @@ # ^^^^^^^^^^^^^^ # A common technique used with CTF corrupted data is to apply a transformation # based on the sign of the (estimated) CTF. -# We can easily visuallize this in an idealized way by taking the sign of the +# We can easily visualize this in an idealized way by taking the sign of the # array returned by ASPIRE's ``CTFFilter.evaluate_grid``. -ctf_sign = np.sign(radial_ctf_filter.evaluate_grid(RESOLUTION)) +ctf_sign = np.sign(radial_ctf_filter.evaluate_grid(IMG_SIZE)) plt.imshow(ctf_sign) plt.colorbar() plt.show() @@ -96,14 +96,14 @@ # %% # Applying CTF Directly to Images # ------------------------------- -# Various defocus values and phase flippig correction will be +# Various defocus values and phase flipping correction will be # applied directly to images using a mix of ASPIRE and Numpy. # %% -# Generate example image +# Generate Example Image # ^^^^^^^^^^^^^^^^^^^^^^ -def genExImg(L, noise_variance=0.1): +def gen_ex_img(L, noise_variance=0.1): """ Generates data similar to the MATH586 lecture notes. @@ -138,7 +138,7 @@ def genExImg(L, noise_variance=0.1): return img -img = genExImg(RESOLUTION) +img = gen_ex_img(IMG_SIZE) plt.imshow(img) plt.colorbar() plt.show() @@ -146,14 +146,12 @@ def genExImg(L, noise_variance=0.1): # %% # Apply CTF and Phase Flipping # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# First CTF filters for a range of defocus values are created +# First, CTF filters for a range of defocus values are created # and used to corrupt the example image. # Then phase flipping will use the actual CTF paramaters to attempt idealized corrections. # ASPIRE has built in tools for performing these tasks which are discussed towards the end, # but here the methods are demonstrated directly. -from aspire.numeric import fft - # Construct a range of CTF filters. defoci = [2500, 5000, 10000, 20000] ctf_filters = [ @@ -168,19 +166,22 @@ def genExImg(L, noise_variance=0.1): # %% +# Generate CTF corrupted Images +# """"""""""""""""""""""""""""" # Generate images corrupted by progressively increasing defocus. # For each defocus, apply filter to the base image. -imgs = np.empty((len(defoci), RESOLUTION, RESOLUTION)) +imgs = np.empty((len(defoci), IMG_SIZE, IMG_SIZE)) for i, ctf in enumerate(ctf_filters): imgs[i] = Image(img).filter(ctf)[0] Image(imgs).show() # %% -# Generate phase flipped images. +# Generate Phase Flipped Images +# """"""""""""""""""""""""""""" # Construct the centered 2D FFT of the images. -imgs_f = fft.centered_fft2(imgs) +imgs_f = aspire.numeric.fft.centered_fft2(imgs) # Manually apply phase flipping transformation. phase_flipped_imgs_f = np.empty_like(imgs_f) @@ -188,12 +189,12 @@ def genExImg(L, noise_variance=0.1): # Compute the signs of this CTF # In practice, this would be an estimated CTF, # but in the demo we have the luxury of using the model CTF that was applied. - signs = np.sign(ctf.evaluate_grid(RESOLUTION)) + signs = np.sign(ctf.evaluate_grid(IMG_SIZE)) # Apply to the image in Fourier space. phase_flipped_imgs_f[i] = signs * imgs_f[i] # Construct the centered 2D FFT of the images. -phase_flipped_imgs = fft.centered_ifft2(phase_flipped_imgs_f).real +phase_flipped_imgs = aspire.numeric.fft.centered_ifft2(phase_flipped_imgs_f).real Image(phase_flipped_imgs).show() # %% @@ -202,7 +203,7 @@ def genExImg(L, noise_variance=0.1): # %% -# Validating CTFFilter +# Validating ``CTFFilter`` # -------------------- # The forward modeling of CTF can be validated by passing a corrupted image # through CTF estimators and comparing the resulting defocus value(s). @@ -223,9 +224,9 @@ def genExImg(L, noise_variance=0.1): B=0, ) # Evaluate Filter, returning a numpy array. -bad_ctf_fn = bad_est_ctf_filter.evaluate_grid(RESOLUTION) +bad_ctf_fn = bad_est_ctf_filter.evaluate_grid(IMG_SIZE) -c = RESOLUTION // 2 + 1 +c = IMG_SIZE // 2 + 1 plt.plot(rctf_fn[c, c:], label="Model CTF") # radial_ctf_filter plt.plot(bad_ctf_fn[c, c:], label="Incorrect CTF Estimate") plt.legend() @@ -300,10 +301,10 @@ def genExImg(L, noise_variance=0.1): alpha=est["amplitude_contrast"], B=0, ) -est_ctf_fn = est_ctf.evaluate_grid(RESOLUTION) +est_ctf_fn = est_ctf.evaluate_grid(IMG_SIZE) # Compare the model CTF with the estimated CTF. -c = RESOLUTION // 2 + 1 +c = IMG_SIZE // 2 + 1 plt.plot(rctf_fn[c, c:], label="Model CTF") plt.plot(est_ctf_fn[c, c:], label="Estimated CTF") plt.legend() @@ -320,16 +321,16 @@ def genExImg(L, noise_variance=0.1): # %% # .. note:: -# At the time of writing, asymmetric CTF estimation is under going development and validation. +# At the time of writing, elliptical CTF estimation is under going development and validation. # %% # ASPIRE Sources: CTF and Phase Flipping # -------------------------------------- -# The most common uses of CTF simulation and corection are +# The most common uses of CTF simulation and correction are # implemented behind the scenes in ASPIRE's Source classes. # For simulations, users are expected to provide their own -# reasonable CTF parameters. When in doubt they should refer +# reasonable CTF parameters. When in doubt they should refer to # EMDB or EMPIAR for related datasets that may have CTF estimates. # ASPIRE also commonly uses some reasonable values for the 10028 dataset # in examples. @@ -339,17 +340,23 @@ def genExImg(L, noise_variance=0.1): # ^^^^^^^^^^^^^^^^^ # The following code demonstrates adding the previous list of # CTFs to a Simulation and performing phase flipping. -# No calculations beyond defining the CTF parameters is required. +# No calculations beyond defining the CTF parameters are required. from aspire.source import Simulation -# Create the Source -src = Simulation(L=64, n=5, unique_filters=ctf_filters) -src.images[:5].show() +# Create the Source. ``ctf_filters`` are re-used from earlier section. +src = Simulation(L=64, n=4, unique_filters=ctf_filters) +src.images[:4].show() -# Phase flip +# %% +# Phase flip the images. src.phase_flip() -src.images[:5].show() +src.images[:4].show() + +# %% +# Compare corrupted and phase flipped images to those without corruption. +src.projections[:4].show() + # %% # Beyond Simulations: Experimental Data Sources diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index 146c8c8986..29713894a8 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -458,7 +458,7 @@ def _evaluate(self, omega): gamma = c2 * r2 + c4 * r4 h = np.sqrt(1 - self.alpha**2) * np.sin(gamma) - self.alpha * np.cos(gamma) - # For historical reference, below is a translated forumla from the legacy MATLAB code. + # For historical reference, below is a translated formula from the legacy MATLAB code. # The two implementations seem to agree for odd images, but the original MATLAB code # behaves differently for even image sizes. # h = np.sin(c2*r2 + c4*r2*r2 - self.alpha) From 939dd14914ea935e853ff4e6b0cea79c7ac38c67 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 10 Feb 2023 09:06:26 -0500 Subject: [PATCH 125/424] remove develop checkout --- .github/workflows/long_workflow.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/long_workflow.yml b/.github/workflows/long_workflow.yml index 6854a0cabd..a7e6dae82b 100644 --- a/.github/workflows/long_workflow.yml +++ b/.github/workflows/long_workflow.yml @@ -11,8 +11,6 @@ jobs: runs-on: self-hosted steps: - uses: actions/checkout@v3 - with: - ref: develop - name: Install dependencies run: | python -m pip install --upgrade pip From 555a411e0b2968a51360690b5f3ca892b6f51c03 Mon Sep 17 00:00:00 2001 From: Chris Langfield <34426450+chris-langfield@users.noreply.github.com> Date: Fri, 10 Feb 2023 14:58:30 -0500 Subject: [PATCH 126/424] RELION 3.0 and 3.1 STAR file support (#806) * break up sample particle starfiles * remove test line from test_starfileio * update relion optics group class * test relion version checking * comments * branch based on version in RelionSource * fixed process for populating particles df from optics groups * adjust parameters in 30 file to match 31 unique filters * one optics group in sample relion particle data * subclass rlnopticsgroup and depend on version * flake * replace old filename * initial refactor * add legacy 3.0 support * progress on micrographs star files * format * populate coord source metadata first * fixed coordinate star file parsing * some test fixes * some metadata adjustments * pixel size adjustments * update existing column in metadata according to its dtype, not object * black * dtype for ctf metadata comparison * revert metadata patch change * cleanup check relion version function * flake8 membership check * cleaned up RelionSource * black * cleanup CoordinateSource * flake check * begin relion data star file testing in test_starfileio * test 3.0 vs 3.1 starfile * variable name and comment clarification * cleaned up apply optics block * vstack instead of tile * test legacy starfile * orient sync starfile * fixed assignment of mrc index and mrc path metadata fields * substitute self.num_micrographs where it improves readability * change back format for mrc index * add relion legacy import ctf test * second level module import relion_interop * relion_metadata_fields to 2nd level import * rename classes * straggler * missing data folder os path join * comment for creating 3.0 relion ctf file * remove ominous comment from test file * add test for bad starfile branch in RelionSource * black update * comments/docstring review * add second optics group to starfile 31 * add second optics group * snake case * merged_data_block * cleanup mrc to particle indices * get data block by name rather than index * access data_block by name * refactor into one class * cleanup * fix: starfile block can be a dict when there is one row of data * cleanup and docstrings * final self-review checks * get_merged_data_block * private methods --- src/aspire/source/coordinates.py | 264 ++++++++---------- src/aspire/source/relion.py | 91 ++---- src/aspire/utils/__init__.py | 1 + src/aspire/utils/relion_interop.py | 186 +++++++++++- ...ta.star => sample_particles_relion30.star} | 18 +- .../sample_particles_relion31.star | 60 ++++ tests/test_coordinate_source.py | 98 +++++-- tests/test_orient_sync.py | 2 +- tests/test_starfile_stack.py | 13 +- tests/test_starfileio.py | 34 ++- 10 files changed, 497 insertions(+), 270 deletions(-) rename tests/saved_test_data/{sample_relion_data.star => sample_particles_relion30.star} (91%) create mode 100644 tests/saved_test_data/sample_particles_relion31.star diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 1a239b7dc3..53c7143a5a 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -2,16 +2,18 @@ import os from abc import ABC, abstractmethod from collections import defaultdict +from collections.abc import Iterable from math import floor import mrcfile import numpy as np +import pandas as pd from aspire.image import Image from aspire.operators import CTFFilter, IdentityFilter from aspire.source.image import ImageSource from aspire.storage import StarFile -from aspire.utils.relion_interop import RlnOpticsGroup +from aspire.utils import RelionStarFile logger = logging.getLogger(__name__) @@ -55,6 +57,7 @@ def __init__(self, files, particle_size, max_rows, B): # keep this list to identify micrograph paths by index rather than # storing many copies of the same string self.mrc_paths = mrc_paths + self.num_micrographs = len(self.mrc_paths) # The internal representation of micrographs and their picked coords # is a list of tuples (index of micrograph, coordinate), where @@ -62,11 +65,23 @@ def __init__(self, files, particle_size, max_rows, B): # [lower left X, lower left Y, size X, size Y]. # The micrograph's filepath can be recovered from self.mrc_paths self.particles = [] - self._populate_particles(len(mrc_paths), coord_paths) + self._populate_particles(coord_paths) # Read shapes of all micrographs self.mrc_shapes = self._get_mrc_shapes() + # map mrc indices to particle indices + # i'th element contains a list of particle indices corresponding to i'th mrc + self.mrc_index_to_particles = [] + for mrc_idx in range(self.num_micrographs): + self.mrc_index_to_particles.append( + [ + particle_idx + for particle_idx, particle in enumerate(self.particles) + if particle[0] == mrc_idx + ] + ) + # get first mrc and coordinate file to report some data first_mrc_index, first_coord = self.particles[0] first_mrc = self.mrc_paths[first_mrc_index] @@ -93,7 +108,7 @@ def __init__(self, files, particle_size, max_rows, B): # total micrographs and particles represented by source (info) logger.info( - f"{self.__class__.__name__} from {os.path.dirname(self.mrc_paths[0])} contains {len(mrc_paths)} micrographs, {len(self.particles)} picked particles." + f"{self.__class__.__name__} from {os.path.dirname(self.mrc_paths[0])} contains {self.num_micrographs} micrographs, {len(self.particles)} picked particles." ) # report different mrc shapes logger.info(f"Micrographs have the following shapes: {*set(self.mrc_shapes),}") @@ -131,13 +146,25 @@ def __init__(self, files, particle_size, max_rows, B): # this can be updated with import_ctf() self.filter_indices = np.zeros(self.n, dtype=int) self.unique_filters = [IdentityFilter()] + self.set_metadata("__filter_indices", np.zeros(self.n, dtype=int)) + + # populate __mrc_filename and __mrc_index + for mrc_index, particle_indices in enumerate(self.mrc_index_to_particles): + self.set_metadata( + "__mrc_index", + mrc_index, + particle_indices, + ) + self.set_metadata( + "__mrc_filepath", self.mrc_paths[mrc_index], particle_indices + ) - def _populate_particles(self, num_micrographs, coord_paths): + def _populate_particles(self, coord_paths): """ All subclasses create mrc_paths and coord_paths lists and pass them to this method. """ - for i in range(num_micrographs): + for i in range(self.num_micrographs): # read in all coordinates for the given mrc using subclass's # method of reading the corresponding coord file self.particles += [ @@ -273,170 +300,103 @@ def _get_mrc_shapes(self): return mrc_shapes - def import_ctf(self, ctf): + def import_aspire_ctf(self, ctf): """ - Given a RELION CTF STAR file, or list of CTF STAR files--one for each micrograph--, this function adds - corresponding CTFFilter objects to the `CoordinateSource`'s unique - filter list, and also populates the metadata. - """ - self._ctf_cols = [ - "_rlnVoltage", - "_rlnDefocusU", - "_rlnDefocusV", - "_rlnDefocusAngle", - "_rlnSphericalAberration", - "_rlnAmplitudeContrast", - ] - - # attributes to be populated by the different CTF's - self._unique_filters = [] - self._filter_indices = np.zeros(self.n, dtype=int) + Import CTF information from STAR file(s) generated by ASPIRE's CTF Estimator. - # select method based on input type - if isinstance(ctf, str): - populate_ctf = self._populate_ctf_from_relion - elif isinstance(ctf, list): - populate_ctf = self._populate_ctf_from_list - else: + :param ctf: A path or iterable of paths to STAR files from ASPIRE's CTF Estimator. + Note that number of files provided must match number of micrographs in this + `CoordinateSource`. + """ + if not isinstance(ctf, Iterable): + ctf = [ctf] + if not len(ctf) == self.num_micrographs: raise ValueError( - "Argument to import_ctf() must be a path or a list of paths" + "Number of CTF STAR files must match number of micrographs." ) - # populate_ctf will update the arrays above - populate_ctf(ctf) - - # populate filters and metadata - self.unique_filters = self._unique_filters - self.filter_indices = self._filter_indices - def _populate_ctf_from_relion(self, ctf_starfile): - """ - Populates CTF filters and metadata based on a .star file with CTF parameters - in RELION format. - :param ctf_starfile: A RELION .star file containing CTF parameters for micrographs. - (Note: number of micrographs must match number of micrographs in CoordinateSource) - """ - # RELION star files store CTF data in two separate blocks - star = StarFile(ctf_starfile) - optics = star["optics"] - micrographs = star["micrographs"] - - # optics groups - optics_groups = [None] # start indexing at 1 - for _, row in optics.iterrows(): - optics_groups.append(RlnOpticsGroup(row)) + # merge DataFrames from CTF files + dfs = [] + for f in ctf: + # ASPIRE's CTF Estimator produces legacy (=< 3.0) STAR files containing one row + star = RelionStarFile(f) + dfs.append(star.data_block) - # micrographs - if not len(micrographs) == len(self.mrc_paths): - raise ValueError( - f"{ctf_starfile} has CTF information for {len(micrographs)}", - f" micrographs but this source has {len(self.mrc_paths)} micrographs.", - ) + df = pd.concat(dfs, ignore_index=True) - for mrc_idx, row in micrographs.iterrows(): - # extract parameters not in the optics groups - params = {} - params["defocus_u"], params["defocus_v"], params["defocus_angle"] = ( - float(row._rlnDefocusU), - float(row._rlnDefocusV), - float(row._rlnDefocusAngle), - ) - # get parameters from corresponding optics group - optics_group = optics_groups[int(row._rlnOpticsGroup)] - # set the rest of the CTF parameters - ( - params["voltage"], - params["cs"], - params["amplitude_contrast"], - params["pixel_size"], - ) = ( - optics_group.voltage, - optics_group.cs, - optics_group.amplitude_contrast, - optics_group.pixel_size, - ) - self._process_each_ctf(params, mrc_idx) + self._extract_ctf(df) - def _populate_ctf_from_list( - self, - ctf_files, - ): + def import_relion_ctf(self, ctf): """ - Populates CTF filters and metadata based on a list of .star files containing CTF parameters.. - :param ctf_files: A list of .star files containing CTF parameters for micrographs. - (Note: number of files must match number of micrographs in CoordinateSource) + Import CTF information Relion micrograph STAR files containing CTF information. + + :param ctf: Path to a Relion micrograph STAR file containing CTF information. + Note that number of files provided must match number of micrographs in this + `CoordinateSource`. """ - if not len(ctf_files) == len(self.mrc_paths): + data_block = RelionStarFile(ctf).get_merged_data_block() + + # data_block is a pandas Dataframe containing the micrographs + if not len(data_block) == self.num_micrographs: raise ValueError( - "Number of CTF STAR files must match number of micrographs." + f"{ctf} has CTF information for {len(data_block)}", + f" micrographs but this source has {self.num_micrographs} micrographs.", ) - for mrc_idx, ctf_file in enumerate(ctf_files): - params = self._read_ctf_star(ctf_file) - self._process_each_ctf(params, mrc_idx) + self._extract_ctf(data_block) - def _process_each_ctf(self, params, mrc_idx): + def _extract_ctf(self, df): """ - Given a unique set of CTF parameters, create a CTFFilter and populate source metadata. - :param params: Dictionary of CTF parameters. - :param mrc_idx: Index of the micrograph corresponding to these CTF parameters. + Receives a flattened DataFrame containing micrograph CTF information, and populates + the Source's CTF Filters, filter indices, and metadata. """ - # find particle indices corresponding to this micrograph - indices = [ - idx for idx, particle in enumerate(self.particles) if particle[0] == mrc_idx + # required CTF params excluding pixel size + CTF_params = [ + "_rlnVoltage", + "_rlnDefocusU", + "_rlnDefocusV", + "_rlnDefocusAngle", + "_rlnSphericalAberration", + "_rlnAmplitudeContrast", + "_rlnMicrographPixelSize", ] - # add CTF filter to unique filters - self._unique_filters.append( + + # get unique ctfs from the data block + # i'th entry of `indices` contains the index of `filter_params` with corresponding CTF params + filter_params, indices = np.unique( + df[CTF_params].astype(self.dtype).values, return_inverse=True, axis=0 + ) + + # convert defocus_ang from degrees to radians + filter_params[:, 3] *= np.pi / 180.0 + + # construct filters + self.unique_filters = [ CTFFilter( - pixel_size=params["pixel_size"], - voltage=params["voltage"], - defocus_u=params["defocus_u"], - defocus_v=params["defocus_v"], - defocus_ang=params["defocus_angle"], - Cs=params["cs"], - alpha=params["amplitude_contrast"], + pixel_size=filter_params[i, 6], + voltage=filter_params[i, 0], + defocus_u=filter_params[i, 1], + defocus_v=filter_params[i, 2], + defocus_ang=filter_params[i, 3], + Cs=filter_params[i, 4], + alpha=filter_params[i, 5], B=self.B, ) - ) + for i in range(len(filter_params)) + ] - # assign filter indices - self._filter_indices[indices] = mrc_idx - # populate CTF metadata - self.set_metadata( - self._ctf_cols, - np.array( - [ - [ - params["voltage"], - params["defocus_u"], - params["defocus_v"], - params["defocus_angle"], - params["cs"], - params["amplitude_contrast"], - ] - ] - * len(indices) - ), - indices, - ) - # other ASPIRE metadata parameters - self.set_metadata("__mrc_filepath", self.mrc_paths[mrc_idx], indices) - self.set_metadata("__mrc_index", mrc_idx, indices) - - def _read_ctf_star(self, ctf_file): - """ - Reads a CTF STAR file generated by Relion or ASPIRE for a single - micrograph and returns a dictionary of CTF parameters and values. - """ - df = StarFile(ctf_file).get_block_by_index(0) - return { - "defocus_u": float(df["_rlnDefocusU"][0]), - "defocus_v": float(df["_rlnDefocusV"][0]), - "defocus_angle": float(df["_rlnDefocusAngle"][0]), - "cs": float(df["_rlnSphericalAberration"][0]), - "amplitude_contrast": float(df["_rlnAmplitudeContrast"][0]), - "voltage": float(df["_rlnVoltage"][0]), - "pixel_size": float(df["_rlnDetectorPixelSize"][0]), - } + # set metadata + for mrc_idx, filter_index in enumerate(indices): + particle_indices_this_mrc = self.mrc_index_to_particles[mrc_idx] + num_particles_this_mrc = len(particle_indices_this_mrc) + self.set_metadata( + CTF_params, + np.vstack((filter_params[filter_index],) * num_particles_this_mrc), + particle_indices_this_mrc, + ) + self.set_metadata( + "__filter_indices", filter_index, particle_indices_this_mrc + ) @staticmethod def _crop_micrograph(data, coord): @@ -599,7 +559,7 @@ def _validate_box_file(self, box_file, global_particle_size): "Particle size must be consistent." ) - def _populate_particles(self, num_micrographs, coord_paths): + def _populate_particles(self, coord_paths): # overrides CoordinateSource._populate_particles because of the # possibility that force_new_particle_size will be called, # which requires self.particles to be populated already. @@ -611,7 +571,7 @@ def _populate_particles(self, num_micrographs, coord_paths): self._validate_box_file(coord_path, global_particle_size) # populate self.particles - super()._populate_particles(num_micrographs, coord_paths) + super()._populate_particles(coord_paths) # if particle size set by user, we have to re-do the coordinates if self.particle_size: @@ -695,7 +655,7 @@ def _validate_starfile(self, coord_file): logger.error(f"Problem with coordinate file: {coord_file}") raise ValueError("STAR file contains non-numeric coordinate values.") - def _populate_particles(self, num_micrographs, coord_paths): + def _populate_particles(self, coord_paths): # overrides CoordinateSource._populate_particles() in order # to validate coordinate files for coord_file in coord_paths: @@ -704,7 +664,7 @@ def _populate_particles(self, num_micrographs, coord_paths): else: # assume text/.coord format self._validate_centers_file(coord_file) - super()._populate_particles(num_micrographs, coord_paths) + super()._populate_particles(coord_paths) def _coords_list_from_file(self, coord_file): """ diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 22a678e807..549511a811 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -10,7 +10,7 @@ from aspire.image import Image from aspire.operators import CTFFilter, IdentityFilter from aspire.source import ImageSource -from aspire.storage import StarFile +from aspire.utils import RelionStarFile logger = logging.getLogger(__name__) @@ -25,44 +25,6 @@ class RelionSource(ImageSource): store, for example, Filter objects added during preprocessing. """ - # The metadata_fields dictionary below specifies default data types - # of certain key fields used in the codebase, - # which are originally read from Relion STAR files. - relion_metadata_fields = { - "_rlnVoltage": float, - "_rlnDefocusU": float, - "_rlnDefocusV": float, - "_rlnDefocusAngle": float, - "_rlnSphericalAberration": float, - "_rlnDetectorPixelSize": float, - "_rlnCtfFigureOfMerit": float, - "_rlnMagnification": float, - "_rlnAmplitudeContrast": float, - "_rlnImageName": str, - "_rlnOriginalName": str, - "_rlnCtfImage": str, - "_rlnCoordinateX": float, - "_rlnCoordinateY": float, - "_rlnCoordinateZ": float, - "_rlnNormCorrection": float, - "_rlnMicrographName": str, - "_rlnGroupName": str, - "_rlnGroupNumber": str, - "_rlnOriginX": float, - "_rlnOriginY": float, - "_rlnAngleRot": float, - "_rlnAngleTilt": float, - "_rlnAnglePsi": float, - "_rlnClassNumber": int, - "_rlnLogLikeliContribution": float, - "_rlnRandomSubset": int, - "_rlnParticleName": str, - "_rlnOriginalParticleName": str, - "_rlnNrOfSignificantSamples": float, - "_rlnNrOfFrames": int, - "_rlnMaxValueProbDistribution": float, - } - def __init__( self, filepath, @@ -90,11 +52,14 @@ def __init__( """ logger.info(f"Creating ImageSource from STAR file at path {filepath}") + self.filepath = filepath + self.data_folder = data_folder self.pixel_size = pixel_size self.B = B self.n_workers = n_workers + self.max_rows = max_rows - metadata = self.populate_metadata(filepath, data_folder, max_rows) + metadata = self.populate_metadata() n = len(metadata) if n == 0: @@ -180,50 +145,44 @@ def __init__( logger.info(f"Populated {self.n_ctf_filters} CTFFilters from '{filepath}'") - def populate_metadata(self, filepath, data_folder=None, max_rows=None): + def populate_metadata(self): """ Relion STAR files may contain a large number of metadata columns in addition to the locations of particles. We read this into a Pandas DataFrame and add some of our own columns for convenience. """ - if data_folder is not None: - if not os.path.isabs(data_folder): - data_folder = os.path.join(os.path.dirname(filepath), data_folder) + if self.data_folder is not None: + if not os.path.isabs(self.data_folder): + self.data_folder = os.path.join( + os.path.dirname(self.filepath), self.data_folder + ) else: - data_folder = os.path.dirname(filepath) - - # Valid Relion STAR files always have their data in the first loop of the first block. - # We are getting the first (and only) block in this StarFile object - df = StarFile(filepath).get_block_by_index(0) - # convert STAR file strings to data type for each field - # columns without a specified data type are read as dtype=object - column_types = { - name: RelionSource.relion_metadata_fields.get(name, str) - for name in df.columns - } - df = df.astype(column_types) + self.data_folder = os.path.dirname(self.filepath) + + metadata = RelionStarFile(self.filepath).get_merged_data_block() # particle locations are stored as e.g. '000001@first_micrograph.mrcs' # in the _rlnImageName column. here, we're splitting this information # so we can get the particle's index in the .mrcs stack as an int - df[["__mrc_index", "__mrc_filename"]] = df["_rlnImageName"].str.split( - "@", 1, expand=True - ) + metadata[["__mrc_index", "__mrc_filename"]] = metadata[ + "_rlnImageName" + ].str.split("@", 1, expand=True) # __mrc_index corresponds to the integer index of the particle in the __mrc_filename stack # Note that this is 1-based indexing - df["__mrc_index"] = pd.to_numeric(df["__mrc_index"]) + metadata["__mrc_index"] = pd.to_numeric(metadata["__mrc_index"]) # Adding a full-filepath field to the Dataframe helps us save time later # Note that os.path.join works as expected when the second argument is an absolute path itself - df["__mrc_filepath"] = df["__mrc_filename"].apply( - lambda filename: os.path.join(data_folder, filename) + metadata["__mrc_filepath"] = metadata["__mrc_filename"].apply( + lambda filename: os.path.join(self.data_folder, filename) ) - if max_rows is None: - return df + # finally, chop off the metadata df at max_rows + if self.max_rows is None: + return metadata else: - max_rows = min(max_rows, len(df)) - return df.iloc[:max_rows] + max_rows = min(self.max_rows, len(metadata)) + return metadata.iloc[:max_rows] def __str__(self): return f"RelionSource ({self.n} images of size {self.L}x{self.L})" diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index d9e9045a68..36921a2e00 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -62,5 +62,6 @@ virtual_core_cpu_suggestion, ) from .random import Random, choice, rand, randi, randn, random +from .relion_interop import RelionStarFile, relion_metadata_fields from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance diff --git a/src/aspire/utils/relion_interop.py b/src/aspire/utils/relion_interop.py index 0215db7bf1..5230a7e822 100644 --- a/src/aspire/utils/relion_interop.py +++ b/src/aspire/utils/relion_interop.py @@ -1,11 +1,179 @@ -class RlnOpticsGroup: - def __init__(self, df_row): +import logging +from collections import OrderedDict + +import numpy as np + +from aspire.storage import StarFile + +logger = logging.getLogger(__name__) + + +# The metadata_fields dictionary below specifies default data types +# of certain key fields used in the codebase, +# which are originally read from Relion STAR files. +relion_metadata_fields = { + "_rlnVoltage": float, + "_rlnDefocusU": float, + "_rlnDefocusV": float, + "_rlnDefocusAngle": float, + "_rlnSphericalAberration": float, + "_rlnDetectorPixelSize": float, + "_rlnCtfFigureOfMerit": float, + "_rlnMagnification": float, + "_rlnAmplitudeContrast": float, + "_rlnImageName": str, + "_rlnOriginalName": str, + "_rlnCtfImage": str, + "_rlnCoordinateX": float, + "_rlnCoordinateY": float, + "_rlnCoordinateZ": float, + "_rlnNormCorrection": float, + "_rlnMicrographName": str, + "_rlnGroupName": str, + "_rlnGroupNumber": str, + "_rlnOriginX": float, + "_rlnOriginY": float, + "_rlnAngleRot": float, + "_rlnAngleTilt": float, + "_rlnAnglePsi": float, + "_rlnClassNumber": int, + "_rlnLogLikeliContribution": float, + "_rlnRandomSubset": int, + "_rlnParticleName": str, + "_rlnOriginalParticleName": str, + "_rlnNrOfSignificantSamples": float, + "_rlnNrOfFrames": int, + "_rlnMaxValueProbDistribution": float, + "_rlnOpticsGroup": int, + "_rlnOpticsGroupName": str, +} + + +def df_to_relion_types(df): + """ + Convert STAR file strings to data type for each field in a DataFrame loaded via + `aspire.storage.StarFile`. Columns without a specified data type are read as + `dtype=object`. + + :param df: A `StarFile` block represented as a Pandas DataFrame. + :return: The data frame with types converted where possible. + """ + column_types = {name: relion_metadata_fields.get(name, str) for name in df.columns} + return df.astype(column_types) + + +class RelionStarFile(StarFile): + """ + A star file generated by RELION representing particles, micrographs, or movies. + """ + + def __init__(self, filepath): + super().__init__(filepath, blocks=None) + + # convert dtypes in star file blocks where possible + self._convert_dtypes() + + # validate star file and detect Relion version (3.0 or >=3.1) + self._validate_and_detect_version() + + def _validate_and_detect_version(self): """ - Creates a simple object to store optics group data given a pandas dataframe row. + Based on the number of StarFile blocks, block names, and column names, + determine whether this star file can be interpreted as coming from RELION. + If so, determine what version of RELION and what kind of data is represented. """ - self.name = df_row._rlnOpticsGroupName - self.number = int(df_row._rlnOpticsGroup) - self.voltage = float(df_row._rlnVoltage) - self.cs = float(df_row._rlnSphericalAberration) - self.amplitude_contrast = float(df_row._rlnAmplitudeContrast) - self.pixel_size = float(df_row._rlnMicrographPixelSize) + + self.data_block_name = "" + self.relion_version = "" + + # validate 3.0 STAR file + if len(self.blocks) == 1: + data_block = self.get_block_by_index(0) + columns = data_block.columns.to_list() + if not any( + col in columns + for col in ["_rlnImageName", "_rlnMicrographName", "_rlnMovieName"] + ): + raise ValueError( + f"{self.filepath} does not contain Relion data columns." + ) + + self.relion_version = "3.0" + self.data_block = data_block + + # validate 3.1 STAR file + if len(self.blocks) == 2: + # must have an optics block + if "optics" not in self.blocks.keys(): + raise ValueError(f"{self.filepath} does not contain an optics block.") + + # find type of data + for name in self.blocks.keys(): + if name in ["particles", "micrographs", "movies"]: + self.data_block_name = name + break + if not self.data_block_name: + raise ValueError( + f"{self.filepath} does not contain a block identifying particle, ", + "micrograph, or movie data.", + ) + + data_block = self[self.data_block_name] + # lastly, data block must contain a column identifying the type of data as well + columns = data_block.columns.to_list() + if not any( + col in columns + for col in ["_rlnImageName", "_rlnMicrographName", "_rlnMovieName"] + ): + raise ValueError( + f"{self.filepath} data block does not contain Relion data columns." + ) + + self.relion_version = "3.1" + self.data_block = data_block + self.optics_block = self["optics"] + + if not self.relion_version: + raise ValueError( + f"Cannot interpret {self.filepath} as a valid RELION STAR file." + ) + + def _convert_dtypes(self): + """ + For data blocks that are Pandas DataFrames, convert known Relion columns + to sensible types. + """ + _blocks = OrderedDict() + for block_name, block in self.blocks.items(): + # generic StarFile blocks can be DataFrame or dict + if not isinstance(block, dict): + _blocks[block_name] = df_to_relion_types(block) + self.blocks = _blocks + + def get_merged_data_block(self): + """ + Return the DataFrame containing particle/micrograph/movie information for this STAR file. + + :return: A Pandas DataFrame containing the data from this RELION STAR file. + """ + + if self.relion_version == "3.0": + # 3.0 star files have the data all in one block, which we return + return self.data_block + + if self.relion_version == "3.1": + # merge the parameters in the optics block as new columns in the data block + # based on the corresponding optics group number (returns a new dataframe) + data_block = self.data_block.copy() + # get a NumPy array of optics indices for each row of data + optics_indices = self.data_block["_rlnOpticsGroup"].astype(int).to_numpy() + for optics_index, row in self.optics_block.iterrows(): + # find row indices with this optics index + # Note optics group number is 1-indexed in Relion + match = np.nonzero(optics_indices == optics_index + 1)[ + 0 + ] # returns 1-tuple + for param in self.optics_block.columns: + data_block.loc[match, param] = getattr(row, param) + + return data_block diff --git a/tests/saved_test_data/sample_relion_data.star b/tests/saved_test_data/sample_particles_relion30.star similarity index 91% rename from tests/saved_test_data/sample_relion_data.star rename to tests/saved_test_data/sample_particles_relion30.star index 35a2b1717d..e961204cce 100644 --- a/tests/saved_test_data/sample_relion_data.star +++ b/tests/saved_test_data/sample_particles_relion30.star @@ -38,12 +38,12 @@ _rlnRandomSubset #29 1390.691438 1897.510113 1 1.014525 146.660060 000006@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 61.885658 96.726858 0.449600 0.949600 0.722937 1.429043e+05 0.031912 39 2 1345.201512 3610.905694 1 0.501888 -71.574285 000007@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 15.143139 102.318684 -0.300400 -0.300400 0.704080 1.421460e+05 0.027706 125 2 2084.817115 3565.328280 1 0.771093 77.227747 000008@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 113.770609 121.174001 0.449600 -0.050400 0.706734 1.422664e+05 0.041991 65 2 - 217.831924 2604.723277 1 1.060216 64.177207 000009@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 36.354616 45.137935 -0.050400 0.949600 0.704381 1.423116e+05 0.031452 101 1 - 2463.361132 3397.441214 1 1.081435 64.481628 000010@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 93.502877 48.210823 0.199600 -0.050400 0.703591 1.420758e+05 0.044352 55 1 - 1261.854215 3200.976735 1 0.682379 -77.534613 000011@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 86.448448 50.529843 -0.300400 -0.300400 0.702564 1.420122e+05 0.037174 61 1 - 2003.399829 2592.530806 1 0.992451 -136.161912 000012@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 -2.063664 91.760648 -0.050400 0.199600 0.696950 1.419016e+05 0.049526 42 2 - 1220.979346 2708.577998 1 0.818903 -30.857234 000013@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 21.933282 53.641952 -0.050400 -0.300400 0.721019 1.427112e+05 0.026381 113 1 - 2604.810763 203.429484 1 0.729488 -157.620976 000014@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 118.314252 125.843505 -0.300400 -0.300400 0.699456 1.418293e+05 0.054729 87 1 - 757.018043 2660.755621 1 0.932745 -92.812197 000015@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 134.333245 92.320564 -0.300400 0.199600 0.706685 1.426995e+05 0.074965 42 1 - 3165.259343 2881.675032 1 0.513467 97.907345 000016@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 128.513119 60.205933 0.449600 -0.050400 0.703292 1.428023e+05 0.094331 33 1 - 916.590156 3057.649585 1 0.601333 -120.014857 000017@sample.mrcs micrograph.mrc 300.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 130.487578 87.229432 0.449600 -0.050400 0.706048 1.425358e+05 0.126520 26 2 + 217.831924 2604.723277 1 1.060216 64.177207 000009@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 36.354616 45.137935 -0.050400 0.949600 0.704381 1.423116e+05 0.031452 101 1 + 2463.361132 3397.441214 1 1.081435 64.481628 000010@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 93.502877 48.210823 0.199600 -0.050400 0.703591 1.420758e+05 0.044352 55 1 + 1261.854215 3200.976735 1 0.682379 -77.534613 000011@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 86.448448 50.529843 -0.300400 -0.300400 0.702564 1.420122e+05 0.037174 61 1 + 2003.399829 2592.530806 1 0.992451 -136.161912 000012@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 -2.063664 91.760648 -0.050400 0.199600 0.696950 1.419016e+05 0.049526 42 2 + 1220.979346 2708.577998 1 0.818903 -30.857234 000013@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 21.933282 53.641952 -0.050400 -0.300400 0.721019 1.427112e+05 0.026381 113 1 + 2604.810763 203.429484 1 0.729488 -157.620976 000014@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 118.314252 125.843505 -0.300400 -0.300400 0.699456 1.418293e+05 0.054729 87 1 + 757.018043 660.755621 1 0.932745 -92.812197 000015@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 134.333245 92.320564 -0.300400 0.199600 0.706685 1.426995e+05 0.074965 42 1 + 3165.259343 2881.675032 1 0.513467 97.907345 000016@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 128.513119 60.205933 0.449600 -0.050400 0.703292 1.428023e+05 0.094331 33 1 + 916.590156 3057.649585 1 0.601333 -120.014857 000017@sample.mrcs micrograph.mrc 200.000000 21186.804688 21363.109375 7.476096 2.700000 0.000000 1.000000 0.000000 0.100000 37369.207031 5.000000 0.096462 199 130.487578 87.229432 0.449600 -0.050400 0.706048 1.425358e+05 0.126520 26 2 diff --git a/tests/saved_test_data/sample_particles_relion31.star b/tests/saved_test_data/sample_particles_relion31.star new file mode 100644 index 0000000000..732cee6996 --- /dev/null +++ b/tests/saved_test_data/sample_particles_relion31.star @@ -0,0 +1,60 @@ +data_optics + +loop_ +_rlnOpticsGroupName #1 +_rlnOpticsGroup #2 +_rlnMtfFileName #3 [Modulation Transfer Function] +_rlnMicrographOriginalPixelSize #4 +_rlnVoltage #5 +_rlnSphericalAberration #6 +_rlnAmplitudeContrast #7 +_rlnImagePixelSize #8 +opticsGroup1 1 mtf_k2_200kV.star 0.885000 300.000000 2.700000 0.100000 1.400000 +opticsGroup2 2 mtf_k2_200kV.star 0.885000 200.000000 2.700000 0.100000 1.400000 + +data_particles + +loop_ +_rlnCoordinateX #1 +_rlnCoordinateY #2 +_rlnAutopickFigureOfMerit #3 +_rlnClassNumber #4 +_rlnAnglePsi #5 +_rlnImageName #6 +_rlnMicrographName #7 +_rlnOpticsGroup #8 +_rlnCtfMaxResolution #9 +_rlnCtfFigureOfMerit #10 +_rlnDefocusU #11 +_rlnDefocusV #12 +_rlnDefocusAngle #13 +_rlnCtfBfactor #14 +_rlnCtfScalefactor #15 +_rlnPhaseShift #16 +_rlnGroupNumber #17 +_rlnAngleRot #18 +_rlnAngleTilt #19 +_rlnOriginXAngst #20 +_rlnOriginYAngst #21 +_rlnNormCorrection #22 +_rlnLogLikeliContribution #23 +_rlnMaxValueProbDistribution #24 +_rlnNrOfSignificantSamples #25 +_rlnRandomSubset #26 +2158.111921 3073.912046 1.065473 1 71.118492 000001@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 109.186640 122.200343 0.199600 0.199600 0.715651 1.421115e+05 0.034399 100 2 +309.284206 1133.439578 0.808805 1 -114.981686 000002@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 89.519294 117.952816 -0.050400 -0.050400 0.710567 1.426479e+05 0.052798 56 2 +1324.729083 580.060987 1.182254 1 -16.985423 000003@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 120.433357 125.122565 0.199600 -0.050400 0.710218 1.425458e+05 0.030029 67 1 +3611.063170 2730.962940 1.290524 1 -82.502051 000004@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 133.968052 95.609104 -0.050400 -0.300400 0.716304 1.425883e+05 0.022589 104 1 +3171.451814 3490.980983 1.470647 1 -132.166404 000005@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 139.258106 91.742660 -0.050400 0.199600 0.735847 1.430873e+05 0.038707 61 1 +1390.691438 1897.510113 1.014525 1 146.660060 000006@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 61.885658 96.726858 0.449600 0.949600 0.722937 1.429043e+05 0.031912 39 2 +1345.201512 3610.905694 0.501888 1 -71.574285 000007@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 15.143139 102.318684 -0.300400 -0.300400 0.704080 1.421460e+05 0.027706 125 2 +2084.817115 3565.328280 0.771093 1 77.227747 000008@sample.mrcs micrograph.mrc 1 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 113.770609 121.174001 0.449600 -0.050400 0.706734 1.422664e+05 0.041991 65 2 +217.831924 2604.723277 1.060216 1 64.177207 000009@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 36.354616 45.137935 -0.050400 0.949600 0.704381 1.423116e+05 0.031452 101 1 +2463.361132 3397.441214 1.081435 1 64.481628 000010@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 93.502877 48.210823 0.199600 -0.050400 0.703591 1.420758e+05 0.044352 55 1 +1261.854215 3200.976735 0.682379 1 -77.534613 000011@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 86.448448 50.529843 -0.300400 -0.300400 0.702564 1.420122e+05 0.037174 61 1 +2003.399829 2592.530806 0.992451 1 -136.161912 000012@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 -2.063664 91.760648 -0.050400 0.199600 0.696950 1.419016e+05 0.049526 42 2 +1220.979346 2708.577998 0.818903 1 -30.857234 000013@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 21.933282 53.641952 -0.050400 -0.300400 0.721019 1.427112e+05 0.026381 113 1 +2604.810763 203.429484 0.729488 1 -157.620976 000014@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 118.314252 125.843505 -0.300400 -0.300400 0.699456 1.418293e+05 0.054729 87 1 +2604.810763 203.429484 0.729488 1 -157.620976 000015@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 118.314252 125.843505 -0.300400 -0.300400 0.699456 1.418293e+05 0.054729 87 1 +3165.259343 2881.675032 0.513467 1 97.907345 000016@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 128.513119 60.205933 0.449600 -0.050400 0.703292 1.428023e+05 0.094331 33 1 +916.590156 3057.649585 0.601333 1 -120.014857 000017@sample.mrcs micrograph.mrc 2 4.809192 0.096462 21186.804688 21363.109375 7.476096 0.000000 1.000000 0.000000 199 130.487578 87.229432 0.449600 -0.050400 0.706048 1.425358e+05 0.126520 26 2 diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 8c1a0178c3..b40abd4406 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -16,7 +16,7 @@ from aspire.noise import WhiteNoiseEstimator from aspire.source import BoxesCoordinateSource, CentersCoordinateSource from aspire.storage import StarFile -from aspire.utils import importlib_path +from aspire.utils import RelionStarFile, importlib_path class CoordinateSourceTestCase(TestCase): @@ -129,6 +129,7 @@ def setUp(self): self.ctf_files = sorted(glob(os.path.join(self.data_folder, "ctf*.star"))) self.relion_ctf_file = self.createTestRelionCtfFile() + self.relion_legacy_ctf_file = self.createTestRelionLegacyCtfFile() def tearDown(self): self.tmpdir.cleanup() @@ -266,7 +267,7 @@ def createTestCtfFiles(self, index): "_rlnSphericalAberration": 700 + index, "_rlnAmplitudeContrast": 600 + index, "_rlnVoltage": 500 + index, - "_rlnDetectorPixelSize": 400 + index, + "_rlnMicrographPixelSize": 400 + index, } blocks = OrderedDict( {"root": DataFrame([params_dict], columns=params_dict.keys())} @@ -316,6 +317,18 @@ def createTestRelionCtfFile(self): star.write(star_fp) return star_fp + def createTestRelionLegacyCtfFile(self): + # create a Relion 3.0 format CTF file by loading the 3.1 file, + # and applying the optics block CTF parameters to the data block, + # creating a single df saved back to a new star file + legacy_star_fp = os.path.join(self.data_folder, "micrographs_ctf_legacy.star") + star = RelionStarFile(self.relion_ctf_file) + df = star.get_merged_data_block() + # save as a new star file containing identical information but in 3.0 format + legacy_star = StarFile(blocks=OrderedDict({"": df})) + legacy_star.write(legacy_star_fp) + return legacy_star_fp + def testLoadFromBox(self): # ensure successful loading from box files BoxesCoordinateSource(self.files_box) @@ -512,17 +525,23 @@ def testWrongNumberCtfFiles(self): # trying to give 3 CTF files to a source with 2 micrographs should error with self.assertRaises(ValueError): src = BoxesCoordinateSource(self.files_box) - src.import_ctf(["badfile", "badfile", "badfile"]) + src.import_aspire_ctf(["badfile", "badfile", "badfile"]) def testImportCtfFromList(self): src = BoxesCoordinateSource(self.files_box) - src.import_ctf(self.ctf_files) + src.import_aspire_ctf(self.ctf_files) self._testCtfFilters(src) self._testCtfMetadata(src) def testImportCtfFromRelion(self): src = BoxesCoordinateSource(self.files_box) - src.import_ctf(self.relion_ctf_file) + src.import_relion_ctf(self.relion_ctf_file) + self._testCtfFilters(src) + self._testCtfMetadata(src) + + def testImportCtfFromRelionLegacy(self): + src = BoxesCoordinateSource(self.files_box) + src.import_relion_ctf(self.relion_legacy_ctf_file) self._testCtfFilters(src) self._testCtfMetadata(src) @@ -534,30 +553,44 @@ def _testCtfFilters(self, src): # based on the arbitrary values we added to the CTF files # note these values are not realistic filter0 = src.unique_filters[0] - self.assertEqual( - (1000.0, 900.0, 800.0, 700.0, 600.0, 500.0, 400.0), - ( - filter0.defocus_u, - filter0.defocus_v, - filter0.defocus_ang, - filter0.Cs, - filter0.alpha, - filter0.voltage, - filter0.pixel_size, - ), + self.assertTrue( + np.allclose( + np.array( + [1000.0, 900.0, 800.0 * np.pi / 180.0, 700.0, 600.0, 500.0, 400.0], + dtype=src.dtype, + ), + np.array( + [ + filter0.defocus_u, + filter0.defocus_v, + filter0.defocus_ang, + filter0.Cs, + filter0.alpha, + filter0.voltage, + filter0.pixel_size, + ] + ), + ) ) filter1 = src.unique_filters[1] - self.assertEqual( - (1001.0, 901.0, 801.0, 701.0, 601.0, 501.0, 401.0), - ( - filter1.defocus_u, - filter1.defocus_v, - filter1.defocus_ang, - filter1.Cs, - filter1.alpha, - filter1.voltage, - filter1.pixel_size, - ), + self.assertTrue( + np.allclose( + np.array( + [1001.0, 901.0, 801.0 * np.pi / 180.0, 701.0, 601.0, 501.0, 401.0], + dtype=src.dtype, + ), + np.array( + [ + filter1.defocus_u, + filter1.defocus_v, + filter1.defocus_ang, + filter1.Cs, + filter1.alpha, + filter1.voltage, + filter1.pixel_size, + ] + ), + ) ) # the first 200 particles should correspond to the first filter # since they came from the first micrograph @@ -598,10 +631,15 @@ def _testCtfMetadata(self, src): "_rlnSphericalAberration", "_rlnAmplitudeContrast", "_rlnVoltage", + "_rlnMicrographPixelSize", ] - ctf_metadata = np.zeros((src.n, len(ctf_cols)), dtype=np.float64) - ctf_metadata[:200] = np.array([1000.0, 900.0, 800.0, 700.0, 600.0, 500.0]) - ctf_metadata[200:400] = np.array([1001.0, 901.0, 801.0, 701.0, 601.0, 501.0]) + ctf_metadata = np.zeros((src.n, len(ctf_cols)), dtype=src.dtype) + ctf_metadata[:200] = np.array( + [1000.0, 900.0, 800.0 * np.pi / 180.0, 700.0, 600.0, 500.0, 400.0] + ) + ctf_metadata[200:400] = np.array( + [1001.0, 901.0, 801.0 * np.pi / 180.0, 701.0, 601.0, 501.0, 401.0] + ) self.assertTrue(np.array_equal(ctf_metadata, src.get_metadata(ctf_cols))) def testCommand(self): diff --git a/tests/test_orient_sync.py b/tests/test_orient_sync.py index 36e2c5a61c..a112c9a508 100644 --- a/tests/test_orient_sync.py +++ b/tests/test_orient_sync.py @@ -108,7 +108,7 @@ def testCommandLine(self): with tempfile.TemporaryDirectory() as tmpdir: # Save the simulation object into STAR and MRCS files starfile_out = os.path.join(tmpdir, "save_test.star") - starfile_in = os.path.join(DATA_DIR, "sample_relion_data.star") + starfile_in = os.path.join(DATA_DIR, "sample_particles_relion31.star") result = runner.invoke( orient3d, [ diff --git a/tests/test_starfile_stack.py b/tests/test_starfile_stack.py index fea11067ba..50b26aaff9 100644 --- a/tests/test_starfile_stack.py +++ b/tests/test_starfile_stack.py @@ -21,7 +21,7 @@ def setUpStarFile(self, starfile_name): def setUp(self): # this method is used by StarFileMainCase but overridden by StarFileOneImage - self.setUpStarFile("sample_relion_data.star") + self.setUpStarFile("sample_particles_relion31.star") def tearDown(self): pass @@ -60,6 +60,17 @@ def testImageDownsample(self): first_image = self.src.images[0][0] self.assertEqual(first_image.shape, (16, 16)) + def testLegacyStarFile(self): + # test setting up a RelionSource from a 3.0 (Legacy) RELION star file + self.setUpStarFile("sample_particles_relion30.star") + + def testInitWithBadStarFile(self): + # test setting up a RelionSource with a STAR file that cannot be interpreted + # as representing particles. in this case, a relion STAR file representing + # something else + with self.assertRaisesRegex(ValueError, "Cannot interpret"): + self.setUpStarFile("sample_data_model.star") + class StarFileSingleImage(StarFileTestCase): def setUp(self): diff --git a/tests/test_starfileio.py b/tests/test_starfileio.py index 9663f93680..1b23b195b8 100644 --- a/tests/test_starfileio.py +++ b/tests/test_starfileio.py @@ -12,7 +12,7 @@ from aspire.image import Image from aspire.source import ArrayImageSource from aspire.storage import StarFile, StarFileError -from aspire.utils import importlib_path +from aspire.utils import RelionStarFile, importlib_path DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -38,7 +38,14 @@ class StarFileTestCase(TestCase): def setUp(self): with importlib_path(tests.saved_test_data, "sample_data_model.star") as path: self.starfile = StarFile(path) - + with importlib_path( + tests.saved_test_data, "sample_particles_relion30.star" + ) as path: + self.particles30 = path + with importlib_path( + tests.saved_test_data, "sample_particles_relion31.star" + ) as path: + self.particles31 = path # Independent Image object for testing Image source methods L = 768 self.im = Image(misc.face(gray=True).astype("float64")[:L, :L]) @@ -175,3 +182,26 @@ def testEmptyInit(self): empty = StarFile() self.assertTrue(isinstance(empty.blocks, OrderedDict)) self.assertEqual(len(empty.blocks), 0) + + def testRelionStarFile(self): + # these starfiles represent Relion particles according to + # the legacy 3.0 format and the current 3.1/4.0 format, respectively + star_legacy = RelionStarFile(self.particles30) + star_current = RelionStarFile(self.particles31) + data_block_legacy = star_legacy.get_merged_data_block() + data_block_current = star_current.get_merged_data_block() + + # in the current format, CTF parameters are stored in the optics group block + # RelionDataStarFile provides a method to flatten all the data into one + # table, representable as ASPIRE metadata + # make sure they were applied correctly + ctf_params = [ + "_rlnVoltage", + "_rlnDefocusU", + "_rlnDefocusV", + "_rlnDefocusAngle", + "_rlnSphericalAberration", + ] + self.assertTrue( + data_block_current[ctf_params].equals(data_block_legacy[ctf_params]) + ) From 7a9f929b3a957f0e146d59c380a2f413e46b8193 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 15 Feb 2023 16:09:23 -0500 Subject: [PATCH 127/424] debug log for repr test. --- tests/test_synthetic_volume.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_synthetic_volume.py b/tests/test_synthetic_volume.py index 489d942856..f78d1f58a6 100644 --- a/tests/test_synthetic_volume.py +++ b/tests/test_synthetic_volume.py @@ -1,4 +1,5 @@ import itertools +import logging import numpy as np import pytest @@ -14,6 +15,8 @@ TSymmetricVolume, ) +logger = logging.getLogger(__name__) + # dtype fixture to pass into volume fixture. DTYPES = [np.float32, pytest.param(np.float64, marks=pytest.mark.expensive)] @@ -89,6 +92,7 @@ def vol_fixture(request, dtype_fixture): def test_volume_repr(vol_fixture): """Test Synthetic Volume repr""" assert repr(vol_fixture).startswith(f"{vol_fixture.__class__.__name__}") + logger.debug(f"Volume object: {repr(vol_fixture)}") def test_volume_generate(vol_fixture): From 42d8b97570553d24eb012183c5d7ba83d331068d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Feb 2023 10:14:53 -0500 Subject: [PATCH 128/424] Remove tasks/homework prompts --- gallery/tutorials/lecture_feature_demo.py | 82 ++++++----------------- 1 file changed, 19 insertions(+), 63 deletions(-) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 3841745ad6..08bf2f6d9e 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -13,8 +13,8 @@ # the ASPIRE package that we will use throughout this tutorial. # %% -# Homework Task 0 -# ^^^^^^^^^^^^^^^ +# Installation +# ^^^^^^^^^^^^ # # Attempt to install ASPIRE on your machine. ASPIRE can generally install on # Linux, Mac, and Windows @@ -25,8 +25,9 @@ # # ASPIRE requires some resources to run, so if you wouldn't run typical data # science codes on your machine (maybe a netbook for example), -# you may use TigerCPU. After logging into TigerCPU, -# ``module load anaconda3/2020.7`` and follow the anaconda instructions for +# you may use Tiger/Adroit/Della etc if you have access. +# After logging into Tiger, ``module load anaconda3/2020.7`` +# and continue to follow the anaconda instructions for # developers in the link above. # Those instructions should create a working environment for tinkering with # ASPIRE code found in this notebook. @@ -83,19 +84,6 @@ # The download was uncompressed in my local directory. The notebook defaults to a small low resolution sample file you may use to sanity check. # Unfortunately real data can be quite large so we do not ship it with the repo. -# %% -# Homework Task 1 -# ^^^^^^^^^^^^^^^ -# -# - Starting from `EMPIAR `_ find a molecule of interest and try to -# find if it has a corresponding volume density map from `EMDB `_. -# -# - Download such a map and use it in the following experiments where I have used 2660. -# -# Helpful friendly hint: -# `mrcfile` will typically open `.map` files provided by EMDB, corresponding to an EMPIAR entry. -# This was not obvious to me, but you may `read more about the format here `_. - # %% # Initialize Volume # ----------------- @@ -106,12 +94,12 @@ v = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) # More interesting data requires downloading locally. -# v = Volume.load("EMD-2660/map/emd_2660.map") +# v = Volume.load("path/to/EMD-2660/map/emd_2660.map") # Downsample the volume to a desired resolution img_size = 64 -# Volume.downsample() Returns a new Volume instance. -# We will use this lower resolution volume later. +# Volume.downsample() returns a new Volume instance. +# We will use this lower resolution volume later, calling it `v2`. v2 = v.downsample(img_size) L = v2.resolution @@ -135,15 +123,6 @@ ax.scatter3D(x, y, z, c=np.log10(v2.flatten()), cmap="Greys_r") plt.show() -# %% -# Homework Task 2 -# ^^^^^^^^^^^^^^^ -# -# Above I have used a simple log transform with a scatter plot to peek at the 3D data. -# This was mainly just to make sure the data was in the neighborhood of what I was looking for. -# More commonly we will want to construct an ``isosurface`` plot. -# Try to create a better plot of the volume (this will probably require more advanced tools than matplotlib). - # %% # ``Rotation`` Class - Generating Random Rotations # ------------------------------------------------ @@ -198,7 +177,7 @@ # Using ``Simulation``, we can generate arbitrary numbers of projections for use in experiments. # Later we will demonstrate additional features which allow us to create more realistic data sources. -num_imgs = 100 # How many images in our source. +num_imgs = 100 # Total images in our source. # Generate a Simulation instance based on the original volume data. sim = Simulation(L=v.resolution, n=num_imgs, vols=v) # Display the first 10 images @@ -291,12 +270,12 @@ ) # %% -# Homework Task 3 -# ^^^^^^^^^^^^^^^ +# Experiment +# ^^^^^^^^^^ # # We confirmed a dramatic change in the eigenspacing when we add a lot of noise. # Compute the SNR in this case using the formula described from class. -# Repeat the experiment with varying levels of SNR to find at what level the character of the eigenspacing changes. +# Repeat the experiment with varying levels of noise to find at what level the character of the eigenspacing changes. # This will require changing the Simulation Source's noise_filter. # How does this compare with the levels discussed in lecture? @@ -364,19 +343,11 @@ def noise_function(x, y): # What do the whitened images look like... sim4.images[:10].show() -# %% -# Homework Task 4 -# ^^^^^^^^^^^^^^^ -# -# Try some other image preprocessing methods exposed by the ``Simulation``/``ImageSource`` classes. -# -# Try some other custom function to add noise or other corruptions to the images. - # %% # Real Experimental Data - ``RelionSource`` # ----------------------------------------- # -# Now that we know our experiment code seems to run, +# Now that we know have some basics, # we can try to replace the simulation with a real experimental data source. # # Lets attempt the same CL experiment, but with a ``RelionSource``. @@ -388,25 +359,17 @@ def noise_function(x, y): max_rows=1024, ) -# Data resides on Tiger Cluster -# Please make sure you are using a compute node once you've installed ASPIRE, not the head node... -# src = RelionSource( -# "/tigress/gbwright/data/cryo-em/CryoEMdata/empiar10028/shiny_2sets.star", data_folder="", pixel_size=5.0, max_rows=100 -# ) src.downsample(img_size) src.images[:10].show() -noise_estimator = WhiteNoiseEstimator(src) -src.whiten(noise_estimator.filter) - orient_est = CLSyncVoting(src, n_theta=36) orient_est.estimate_rotations() rots_est = orient_est.rotations # %% # We can see that the code can easily run with experimental data by subsituting the ``Source`` class. -# However, we have hit the practical limitation that requires class averaging of images.... +# However, we have hit the point where we need denoising algorithms to perform orientation estimation. # %% # CTF Filter @@ -439,23 +402,16 @@ def noise_function(x, y): for d in np.linspace(defocus_min, defocus_max, defocus_ct) ] -sim5 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, unique_filters=filters) -sim5.images[:10].show() - # Here we will combine CTF and noise features to our projections. -sim6 = Simulation( +sim5 = Simulation( L=v2.resolution, n=num_imgs, vols=v2, unique_filters=filters, noise_adder=custom_noise, ) -sim6.images[:10].show() - -# Estimate noise. -aiso_noise_estimator = AnisotropicNoiseEstimator(sim6) - -# Whiten based on the estimated noise -sim6.whiten(aiso_noise_estimator.filter) -sim6.images[:10].show() +# Images with CTF, no noise applied +sim5.clean_images[:10].show() +# Images with both CTF and noise +sim5.images[:10].show() From d63adf12e5257fdcd79bdbd4672ce6bde33fdfcb Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Feb 2023 10:28:51 -0500 Subject: [PATCH 129/424] Remove ugly plots --- gallery/tutorials/lecture_feature_demo.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 08bf2f6d9e..17edd2377b 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -104,25 +104,15 @@ L = v2.resolution # %% -# Contour Plot of Data -# -------------------- +# Plot Data +# --------- # Alternatively, for quick sanity checking purposes we can view as a contour plot. # We'll use three orthographic projections, one per axis. for axis in range(3): - plt.contourf(np.arange(L), np.arange(L), np.sum(v2[0], axis=axis), cmap="gray") + plt.imshow(np.sum(v2[0], axis=axis), cmap="gray") plt.show() -# %% -# Scatter Plot -# ------------ - -# We can attempt a 3d scatter plot, but the results aren't very good. -x, y, z = np.meshgrid(np.arange(L), np.arange(L), np.arange(L)) -ax = plt.axes(projection="3d") -ax.scatter3D(x, y, z, c=np.log10(v2.flatten()), cmap="Greys_r") -plt.show() - # %% # ``Rotation`` Class - Generating Random Rotations # ------------------------------------------------ From 2e019250914e2e2a2053538a053d5a5d566da6b0 Mon Sep 17 00:00:00 2001 From: Chris Langfield <34426450+chris-langfield@users.noreply.github.com> Date: Fri, 17 Feb 2023 10:53:24 -0500 Subject: [PATCH 130/424] FLE Basis (#693) * initial commit * init file * add nodes and some functions * computing bessel zeros * finished computing and thresholding bessel zeros * organized into subfns * more of _precomp added * cleanup * rename variables to sensible names, add dummy tests * add test file * slight change to placeholder evaluate function * format * bandlimit default * frmat * chebyshev * removing comment block description of arrays for now * leftover pdb -- embarrassing * steps 1 and 2 evaluate_t * added some initial constants as well as creating dense matrix B * some cleanup * cleaned up evaluate_t steps * interpolation matrix * step3 and test * add sparse matrix condn * adjust constants * format * bugs in create dense matrix method * bug in evaluate_t and bug in create basis functions * flake * parameterized test * formatting * rename evaluate_t steps * step1 * propagate epsilon=1e-8 thru (a)nufft api * use our nufft with epsilon * all 3 steps) * evaluate * evaluate test closeness * add docstrings * lowpass() * test lowpass * rotate() * fix rotate * rotate() tests * radial convolution * radial convolution test * get rid of numpy complex warnings * ctf sample (small) * create utils file * expand * copy()s in evaluate_t and evaluate * remove large expand() test * cleanup * docstrings and cleanup * comments tests * docstring * remove old comment * private _expand * black * FBBasis2D k_max criterion and optional thresholding * isort * fb comparison test * matching FB option * unused variable * oscar comments * __init__ params * explicit reshapes * remove duplicated code * F ordering and n/ell * fix _expand docstring * remove pointless reshaping * descriptive variable names in util fn * better comments and variable names * black * use grid_2d for cartesian gridpoint. BREAKS TESTS * self.h * catch up with develop * relative error * pointless reshape * dtype in test * remove numsparse < 0 branch and associated get_weights function per paper author (vestigial branch) * better docstring and params for transform_complex_to_real * comment about complex conjugate * barycentric docstring and fixed another docstring * barycentric variable names and docstring * comments in bary interp from paper author * precomp_txform_complex_to_real comments * paper author comments in FLE 2D utils * remove unneeded parameter and var from barycentric * fb compat indices first go * formatting * initial remove parameterized from FLE * remove pdb import (embarrassing) and try out pytest skip if GPU backend * rename test file * match fb indices * flip sign going into evaluate * matching FB2d forward tests * add evaluate_t fb2d matching test * cleanup gpu and resolution error test skips * last paper author comments * docstrings * stub evaluate_t dense test * implement dense evaluate_t test (worked out matrix reshaping etc) * cleaner dense evaluate_t * switch match_fb default, and update docstring with bullet points * adjust tests for new default * compute flip_sign_indices once * naming * float32 in/out * indent * rename get_fb_compat_indices * numsparse/maxitr logic * grid dtype * nufft grid dtypes * internal dtype fixes * remove F order * better comment for nus * docstring fix * 2nd docstring fix * cleaner dtype passthrough * cleanup fle_ell_sign * use Basis.rotate instead of special method * test _rotate * redundant terms in numsparse/maxitr logic * remove re-signing function * rename ctf vars * renamed datafile * _create_dense_matrix and tests * remove _expand and _rotate * radial convolve * not because they are easy, but because they are hard. * Odd resolution grid patch. Co-authored-by: Chris Langfield * And the another odd resolution grid patch * remove normalization requirement for dense method odd res --------- Co-authored-by: Garrett Wright Co-authored-by: Garrett Wright <47759732+garrettwrong@users.noreply.github.com> --- src/aspire/basis/__init__.py | 1 + src/aspire/basis/fle_2d.py | 706 ++++++++++++++++++ src/aspire/basis/fle_2d_utils.py | 202 +++++ src/aspire/basis/steerable.py | 4 +- src/aspire/nufft/__init__.py | 12 +- tests/saved_test_data/fle_radial_fn_32x32.npy | Bin 0 -> 8320 bytes tests/test_FLEbasis2D.py | 321 ++++++++ 7 files changed, 1241 insertions(+), 5 deletions(-) create mode 100644 src/aspire/basis/fle_2d.py create mode 100644 src/aspire/basis/fle_2d_utils.py create mode 100644 tests/saved_test_data/fle_radial_fn_32x32.npy create mode 100644 tests/test_FLEbasis2D.py diff --git a/src/aspire/basis/__init__.py b/src/aspire/basis/__init__.py index cfaea78358..a482127338 100644 --- a/src/aspire/basis/__init__.py +++ b/src/aspire/basis/__init__.py @@ -11,6 +11,7 @@ from .fb_3d import FBBasis3D from .ffb_2d import FFBBasis2D from .ffb_3d import FFBBasis3D +from .fle_2d import FLEBasis2D from .fpswf_2d import FPSWFBasis2D from .fpswf_3d import FPSWFBasis3D from .fspca import FSPCABasis diff --git a/src/aspire/basis/fle_2d.py b/src/aspire/basis/fle_2d.py new file mode 100644 index 0000000000..77f8d8d286 --- /dev/null +++ b/src/aspire/basis/fle_2d.py @@ -0,0 +1,706 @@ +import logging + +import numpy as np +import scipy.sparse as sparse +from scipy.fft import dct, idct +from scipy.special import jv + +from aspire.basis import FBBasisMixin, SteerableBasis2D +from aspire.basis.basis_utils import besselj_zeros +from aspire.basis.fle_2d_utils import ( + barycentric_interp_sparse, + precomp_transform_complex_to_real, + transform_complex_to_real, +) +from aspire.nufft import anufft, nufft +from aspire.numeric import fft +from aspire.utils import complex_type, grid_2d + +logger = logging.getLogger(__name__) + + +class FLEBasis2D(SteerableBasis2D, FBBasisMixin): + """ + Define a derived class for Fast Fourier Bessel 2D expansion using interpolation + from Chebyshev nodes. + The algorithms used are described in the following publication: + N. F. Marshall, O. Mickelin, A. Singer, Fast Expansion into Harmonics on the Disk: + A Steerable Basis with Fast Radial Convolution. (submitted) + + https://arxiv.org/pdf/2207.13674.pdf + """ + + def __init__( + self, size, bandlimit=None, epsilon=1e-10, dtype=np.float32, match_fb=True + ): + """ + :param size: The size of the vectors for which to define the FLE basis. + Currently only square images are supported. + :param bandlimit: Maximum frequency band for computing basis functions. Defaults to the + resolution of the basis. + :param epsilon: Relative precision between FLE fast method and dense matrix multiplication. + :param dtype: Datatype of images and coefficients represented. + :param match_fb: + With this flag set the following will ensure that the basis functions are + identical to `FBBasis2D`: + - The initial heuristic for the number of basis functions, based on the resolution, will + be set to that of `FBBasis2D`, and the FLE frequency thresholding procedure to reduce the + number of functions will not be carried out. This means the number of basis functions for + a given image size will be identical across the two bases. + - The signs of basis functions and coefficients with `sgn == 1` will be flipped relative to + the original FLE implementation, to match FB. + - The basis functions returned will be reordered according to the FB ordering, that is, first + by `ell`s, then by `sgn`s, then by `k`s. + + """ + if isinstance(size, int): + size = (size, size) + ndim = len(size) + assert ndim == 2, "Only two-dimensional basis functions are supported." + assert len(set(size)) == 1, "Only square domains are supported" + + self.bandlimit = bandlimit + self.epsilon = epsilon + self.match_fb = match_fb + self.dtype = dtype + super().__init__(size, ell_max=None, dtype=self.dtype) + + def _build(self): + """ + Build the internal data structure for the FLEBasis2D class. + """ + + # bandlimit set to basis size by default + if not self.bandlimit: + self.bandlimit = self.nres + + # compute number of k's for each ell + self._calc_k_max() + + if self.match_fb: + # Use FB2D and FFB2D heuristic for computing max basis functions + self.max_basis_functions = self.k_max[0] + sum(2 * self.k_max[1:]) + else: + # Regular Fourier-Bessel bandlimit (equivalent to pi*R**2) + # Final self.count will be < self.max_basis_functions + # See self._threshold_basis_functions() + self.max_basis_functions = int(self.nres**2 * np.pi / 4) + + self._compute_maxitr_and_numsparse() + + self._compute_cartesian_gridpoints() + + self._precomp() + + # Steerable basis indices + self._build_indices() + + # FB compatability indices + self._generate_fb_compat_indices() + + def _build_indices(self): + self.angular_indices = np.abs(self.ells) + self.radial_indices = self.ks - 1 + self.signs_indices = np.sign(self.ells) + + def indices(self): + """ + Return the precomputed indices for each basis function. + """ + return { + "ells": self.angular_indices, + "ks": self.radial_indices, + "sgns": self.signs_indices, + } + + def _generate_fb_compat_indices(self): + """ + Generate indices to shuffle basis function ordering and flip signs in order + to match `FBBasis2D`. + """ + ind = self.indices() + # basis function ordering + self.fb_compat_indices = np.lexsort((ind["ks"], ind["sgns"], ind["ells"])) + # flip signs + self.flip_sign_indices = np.where(self.signs_indices == 1) + + def _precomp(self): + """ + Precompute the basis functions and other objects used in the evaluation of + coefficients. + """ + + # Find bessel functions zeros (the eigenvalues of the Laplacian on + # the disk) and generate the FLE Basis functions + self._lap_eig_disk() + + # Some important constants + self.smallest_lambda = np.min(self.bessel_zeros) + self.greatest_lambda = np.max(self.bessel_zeros) + self.max_ell = np.max(np.abs(self.ells)) + self.h = 1 / (self.nres / 2) + + # give each ell a positive index increasing first in |ell| + # then in sign, e.g. 0->1, -1->2, 1->3, -2->4, 2->5, etc. + self.ells_p = 2 * np.abs(self.ells) - (self.ells < 0) + self.ell_p_max = np.max(self.ells_p) + # idx_list[k] contains the indices j of ells_p where ells_p[j] = k + idx_list = [[] for i in range(self.ell_p_max + 1)] + for i in range(self.count): + ellp = self.ells_p[i] + idx_list[ellp].append(i) + self.idx_list = idx_list + + # real <-> complex + self.c2r = precomp_transform_complex_to_real(self.ells) + self.r2c = sparse.csr_matrix(self.c2r.transpose().conj()) + + # create an ordered list of the original ell values + # used in step2 (in both directions) + self.nus = np.zeros(1 + 2 * self.max_ell, dtype=int) + self.nus[0] = 0 + for i in range(1, self.max_ell + 1): + self.nus[2 * i - 1] = -i + self.nus[2 * i] = i + self.c2r_nus = precomp_transform_complex_to_real(self.nus) + self.r2c_nus = sparse.csr_matrix(self.c2r_nus.transpose().conj()) + + # radial and angular nodes for NUFFT + self._compute_nufft_points() + self.num_interp = self.num_radial_nodes + if self.numsparse > 0: + self.num_interp = 2 * self.num_radial_nodes + + self._build_interpolation_matrix() + + def _compute_maxitr_and_numsparse(self): + """ + Uses heuristics from paper to assign self.maxitr and self.numsparse. + """ + # maxitr: maximum number of iterations for numerically solving linear + # system in self.evaluate() + # numsparse: parameter used to create sparse Chebyshev interpolation matrix + # see self._build_interpolation_matrix() + + if self.epsilon >= 1e-4: + numsparse = 8 + maxitr = 1 + int(np.log2(self.nres)) // 2 + elif self.epsilon >= 1e-7: + numsparse = 16 + maxitr = 1 + int(np.log2(self.nres)) + elif self.epsilon >= 1e-10: + numsparse = 22 + maxitr = 1 + int(2 * np.log2(self.nres)) + else: + # epsilon < 1e-10 + numsparse = 32 + maxitr = 1 + int(3 * np.log2(self.nres)) + + self.maxitr = maxitr + self.numsparse = numsparse + + def _compute_cartesian_gridpoints(self): + """ + Creates meshgrids based on basis size. + """ + if self.match_fb: + # creates correct odd-resolution grid + # matching other FB classes + grid = grid_2d(self.nres, dtype=self.dtype) + self.xs = grid["x"] + self.ys = grid["y"] + self.rs = grid["r"] + else: + # original implementation + R = self.nres // 2 + x = np.arange(-R, R + self.nres % 2) + xs, ys = np.meshgrid(x, x) + # Note, the original original grids were xs/R, R=nres//2. + self.xs, self.ys = xs / (self.nres / 2), ys / (self.nres / 2) + self.rs = np.sqrt(self.xs**2 + self.ys**2) + self.radial_mask = self.rs > 1 + 1e-13 + + def _compute_nufft_points(self): + """ + Computes gridpoints for the non-uniform FFT. + """ + + # Number of radial nodes + # (Lemma 4.1) + # compute max {2.4 * self.nres , Log2 ( 1 / epsilon) } + Q = int(np.ceil(2.4 * self.nres)) + num_radial_nodes = Q + tmp = 1 / (np.sqrt(np.pi)) + for q in range(1, Q + 1): + tmp = tmp / q * (np.sqrt(np.pi) * self.nres / 4) + if tmp <= self.epsilon: + num_radial_nodes = int(max(q, np.log2(1 / self.epsilon))) + break + self.num_radial_nodes = max( + num_radial_nodes, int(np.ceil(np.log2(1 / self.epsilon))) + ) + + # Number of angular nodes + # (Lemma 4.2) + # compute max {7.08 * self.nres, Log2(1/epsilon) + Log2(self.nres**2) } + + S = int(max(7.08 * self.nres, -np.log2(self.epsilon) + 2 * np.log2(self.nres))) + num_angular_nodes = S + for s in range(int(self.greatest_lambda + self.ell_p_max) + 1, S + 1): + tmp = self.nres**2 * ((self.greatest_lambda + self.ell_p_max) / s) ** s + if tmp <= self.epsilon: + num_angular_nodes = int(max(int(s), np.log2(1 / self.epsilon))) + break + + # must be even + if num_angular_nodes % 2 == 1: + num_angular_nodes += 1 + + self.num_angular_nodes = num_angular_nodes + + # create gridpoints + nodes = 1 - (2 * np.arange(self.num_radial_nodes, dtype=self.dtype) + 1) / ( + 2 * self.num_radial_nodes + ) + nodes = (np.cos(np.pi * nodes) + 1) / 2 + nodes = ( + self.greatest_lambda - self.smallest_lambda + ) * nodes + self.smallest_lambda + nodes = nodes.reshape(self.num_radial_nodes, 1) + + radius = self.nres / 2 + h = 1 / radius + + phi = ( + 2 + * np.pi + * np.arange(self.num_angular_nodes // 2, dtype=self.dtype) + / self.num_angular_nodes + ) + x = np.cos(phi).reshape(1, self.num_angular_nodes // 2) + y = np.sin(phi).reshape(1, self.num_angular_nodes // 2) + x = x * nodes * h + y = y * nodes * h + self.grid_x = x.flatten() + self.grid_y = y.flatten() + + def _build_interpolation_matrix(self): + """ + Create the matrix used in the third step of evaluate_t() and the first step of evaluate() + for barycentric interpolation from Chebyshev nodes. + """ + A3 = [None] * (self.ell_p_max + 1) + A3_T = [None] * (self.ell_p_max + 1) + # known points from which to interpolate Beta values to desired points + known_points = np.cos( + np.pi * (1 - (2 * np.arange(self.num_interp) + 1) / (2 * self.num_interp)) + ) + for i in range(self.ell_p_max + 1): + # target points to evaluate Betas + target_points = ( + 2 + * (self.bessel_zeros[self.idx_list[i]] - self.smallest_lambda) + / (self.greatest_lambda - self.smallest_lambda) + - 1 + ) + + A3[i], A3_T[i] = barycentric_interp_sparse( + target_points, known_points, self.numsparse + ) + self.A3 = A3 + self.A3_T = A3_T + + def _lap_eig_disk(self): + """ + Compute the eigenvalues of the Laplacian operator on a disk with Dirichlet boundary conditions. + """ + # max number of Bessel function orders being considered + max_ell = int(3 * np.sqrt(self.max_basis_functions)) + # max number of zeros per Bessel function (number of frequencies per bessel) + max_k = int(2 * np.sqrt(self.max_basis_functions)) + + # preallocate containers for roots + # 0 frequency plus pos and negative frequencies for each bessel function + # num functions per frequency + num_ells = 1 + 2 * max_ell + self.ells = np.zeros((num_ells, max_k), dtype=int) + self.ks = np.zeros((num_ells, max_k), dtype=int) + self.bessel_zeros = np.ones((num_ells, max_k), dtype=np.float64) * np.Inf + + # keep track of which order Bessel function we're on + self.ells[0, :] = 0 + # bessel_roots[0, m] is the m'th zero of J_0 + self.bessel_zeros[0, :] = besselj_zeros(0, max_k) + # table of values of which zero of J_0 we are finding + self.ks[0, :] = np.arange(max_k) + 1 + + # add roots of J_ell for ell>0 twice with +k and -k (frequencies) + # iterate over Bessel function order + for ell in range(1, max_ell + 1): + self.ells[2 * ell - 1, :] = -ell + self.ks[2 * ell - 1, :] = np.arange(max_k) + 1 + + self.bessel_zeros[2 * ell - 1, :max_k] = besselj_zeros(ell, max_k) + + self.ells[2 * ell, :] = ell + self.ks[2 * ell, :] = self.ks[2 * ell - 1, :] + self.bessel_zeros[2 * ell, :] = self.bessel_zeros[2 * ell - 1, :] + + # Reshape the arrays and order by the size of the Bessel function zeros + self._flatten_and_sort_bessel_zeros() + + # Apply threshold criterion to throw out some basis functions + # Grab final number of basis functions for this Basis + self.count = self._threshold_basis_functions() + + self._create_basis_functions() + + def _flatten_and_sort_bessel_zeros(self): + """ + Reshapes arrays self.ells, self.ks, and self.bessel_zeros + """ + # flatten list of zeros, ells and ks: + self.ells = self.ells.flatten() + self.ks = self.ks.flatten() + self.bessel_zeros = self.bessel_zeros.flatten() + + idx = np.argsort(self.bessel_zeros) + self.ells = self.ells[idx] + self.ks = self.ks[idx] + self.bessel_zeros = self.bessel_zeros[idx] + + # sort complex conjugate pairs: -ell first, +ell second + idx = np.arange(self.max_basis_functions + 1) + for i in range(self.max_basis_functions + 1): + if self.ells[i] >= 0: + continue + if np.abs(self.bessel_zeros[i] - self.bessel_zeros[i + 1]) < 1e-14: + continue + idx[i - 1] = i + idx[i] = i - 1 + + self.ells = self.ells[idx] + self.ks = self.ks[idx] + self.bessel_zeros = self.bessel_zeros[idx] + + def _threshold_basis_functions(self): + """ + Implements the bandlimit threshold which caps the number of basis functions + that are actually required. + :return: The final overall number of basis functions to be used. + """ + # Maximum bandlimit + # (Section 4.1) + # Can remove frequencies above this threshold based on the fact that + # there should not be more basis functions than pixels contained in the + # unit disk inscribed on the image + _final_num_basis_functions = self.max_basis_functions + + # implement FLE thresholding unless we want to match count of other FB bases + if not self.match_fb: + for _ in range(len(self.bessel_zeros)): + if ( + self.bessel_zeros[_final_num_basis_functions] / (np.pi) + >= (self.bandlimit - 1) // 2 + ): + _final_num_basis_functions -= 1 + + # potentially subtract one to keep complex conjugate pairs + if self.ells[_final_num_basis_functions - 1] < 0: + _final_num_basis_functions -= 1 + + # discard zeros above the threshold + self.ells = self.ells[:_final_num_basis_functions] + self.ks = self.ks[:_final_num_basis_functions] + self.bessel_zeros = self.bessel_zeros[:_final_num_basis_functions] + + return _final_num_basis_functions + + def _create_basis_functions(self): + """ + Generate the actual basis functions as Python lambda operators + """ + norm_constants = np.zeros(self.count) + basis_functions = [None] * self.count + for i in range(self.count): + # parameters defining the basis function: bessel order and which bessel root + ell = self.ells[i] + bessel_zero = self.bessel_zeros[i] + + # compute normalization constant + # see Eq. 6 + c = 1 / np.sqrt(np.pi * jv(ell + 1, bessel_zero) ** 2) + # create function + # See Eq. 1 + if ell == 0: + basis_functions[i] = ( + lambda r, t, c=c, ell=ell, bessel_zero=bessel_zero: c + * jv(ell, bessel_zero * r) + * (r <= 1) + ) + else: + basis_functions[i] = ( + lambda r, t, c=c, ell=ell, bessel_zero=bessel_zero: c + * jv(ell, bessel_zero * r) + * np.exp(1j * ell * t) + * (-1) ** np.abs(ell) + * (r <= 1) + ) + + norm_constants[i] = c + + self.norm_constants = norm_constants + self.basis_functions = basis_functions + + def _evaluate(self, coeffs): + """ + Evaluates FLE coefficients and return in standard 2D Cartesian coordinates. + + :param v: A coefficient vector (or an array of coefficient vectors) to + be evaluated. The last dimension must be equal to `self.count` + :return: An Image object containing the corresponding images. + """ + if self.match_fb: + # sign of basis functions with positive indices flipped relative to FB2d + coeffs[self.flip_sign_indices] *= -1.0 + # reorder coefficients by FB2d ordering + coeffs = coeffs[self.fb_compat_indices] + + # See Remark 3.3 and Section 3.4 + betas = self._step3(coeffs) + z = self._step2(betas) + im = self._step1(z) + return im.astype(self.dtype) + + def _evaluate_t(self, imgs): + """ + Evaluate 2D Cartesian image(s) and return the corresponding FLE coefficients. + + :param imgs: An Image object containing square images of size `self.nres`. + :return: A NumPy array of size `(num_images, self.count)` containing the FLE + coefficients. + """ + # See Section 3.5 + imgs = imgs.copy() + imgs[:, self.radial_mask] = 0 + z = self._step1_t(imgs) + b = self._step2_t(z) + coeffs = self._step3_t(b) + if self.match_fb: + coeffs[:, self.flip_sign_indices] *= -1.0 + coeffs = coeffs[:, self.fb_compat_indices] + return coeffs.astype(self.coefficient_dtype) + + def _step1_t(self, im): + """ + Step 1 of the adjoint transformation (images to coefficients). + Calculates the NUFFT of the image on gridpoints `self.grid_x` and `self.grid_y`. + """ + im = im.reshape(-1, self.nres, self.nres).astype(complex_type(self.dtype)) + num_img = im.shape[0] + z = np.zeros( + (num_img, self.num_radial_nodes, self.num_angular_nodes), + dtype=complex_type(self.dtype), + ) + _z = ( + nufft(im, np.stack((self.grid_x, self.grid_y)), epsilon=self.epsilon) + * self.h**2 + ) + _z = _z.reshape(num_img, self.num_radial_nodes, self.num_angular_nodes // 2) + z[:, :, : self.num_angular_nodes // 2] = _z + z[:, :, self.num_angular_nodes // 2 :] = np.conj(_z) + return z + + def _step2_t(self, z): + """ + Step 2 of the adjoint transformation (images to coefficients). + Computes values of the analytic functions Beta_n at the Chebyshev nodes. + See Lemma 2.2. + """ + num_img = z.shape[0] + # Compute FFT along angular nodes + betas = fft.fft(z, axis=2) / self.num_angular_nodes + betas = betas[:, :, self.nus] + betas = np.conj(betas) + betas = np.swapaxes(betas, 0, 2) + betas = betas.reshape(-1, self.num_radial_nodes * num_img) + betas = self.c2r_nus @ betas + betas = betas.reshape(-1, self.num_radial_nodes, num_img) + betas = np.real(np.swapaxes(betas, 0, 2)) + return betas + + def _step3_t(self, betas): + """ + Step 3 of the adjoint transformation (images to coefficients). + Uses barycenteric interpolation to compute the values of the Betas + at the Bessel roots to arrive at the Fourier-Bessel coefficients. + """ + num_img = betas.shape[0] + if self.num_interp > self.num_radial_nodes: + betas = dct(betas, axis=1, type=2) / (2 * self.num_radial_nodes) + zeros = np.zeros(betas.shape) + betas = np.concatenate((betas, zeros), axis=1) + betas = idct(betas, axis=1, type=2) * 2 * betas.shape[1] + betas = np.moveaxis(betas, 0, -1) + + coeffs = np.zeros((self.count, num_img), dtype=np.float64) + for i in range(self.ell_p_max + 1): + coeffs[self.idx_list[i]] = self.A3[i] @ betas[:, i, :] + coeffs = coeffs.T + + return coeffs * self.norm_constants / self.h + + def _step3(self, coeffs): + """ + Adjoint of _step3_t and Step 1 of the forward transformation (coefficients + to images). + Uses barycenteric interpolation in reverse to compute values of Betas + at Chebyshev nodes, given an array of FLE coefficients. + """ + coeffs = coeffs.copy().reshape(-1, self.count) + num_img = coeffs.shape[0] + coeffs *= self.h * self.norm_constants + coeffs = coeffs.T + + out = np.zeros( + (self.num_interp, 2 * self.max_ell + 1, num_img), + dtype=np.float64, + ) + for i in range(self.ell_p_max + 1): + out[:, i, :] = self.A3_T[i] @ coeffs[self.idx_list[i]] + out = np.moveaxis(out, -1, 0) + if self.num_interp > self.num_radial_nodes: + out = dct(out, axis=1, type=2) + out = out[:, : self.num_radial_nodes, :] + out = idct(out, axis=1, type=2) + + return out + + def _step2(self, betas): + """ + Adjoint of _step2_t and Step 2 of the forward transformation (coefficients + to images). + Uses the IFFT to convert Beta values into Fourier-space images. + """ + num_img = betas.shape[0] + tmp = np.zeros( + (num_img, self.num_radial_nodes, self.num_angular_nodes), + dtype=np.complex128, + ) + + betas = np.swapaxes(betas, 0, 2) + betas = betas.reshape(-1, self.num_radial_nodes * num_img) + betas = self.r2c_nus @ betas + betas = betas.reshape(-1, self.num_radial_nodes, num_img) + betas = np.swapaxes(betas, 0, 2) + + tmp[:, :, self.nus] = np.conj(betas) + z = fft.ifft(tmp, axis=2) + + return z + + def _step1(self, z): + """ + Adjoint of _step1_t and final step of the forward transformation (coefficients + to images). + Performs the NUFFT on Fourier-space images to compute real-space images. + """ + num_img = z.shape[0] + z = z[:, :, : self.num_angular_nodes // 2].reshape(num_img, -1) + im = anufft( + z.astype(complex_type(self.dtype)), + np.stack((self.grid_x, self.grid_y)), + (self.nres, self.nres), + epsilon=self.epsilon, + ) + im = im + np.conj(im) + im = np.real(im) + im = im.reshape(num_img, self.nres, self.nres) + im[:, self.radial_mask] = 0 + + return im + + def _create_dense_matrix(self): + """ + Directly computes the transformation matrix from Cartesian coordinates to + FLE coordinates without any shortcuts. + :return: A NumPy array of size `(self.nres**2, self.count)` containing the matrix + entries. + """ + # See Eqns. 3 and 4, Section 1.2 + ts = np.arctan2(self.ys, self.xs) + + B = np.zeros((self.nres, self.nres, self.count), dtype=np.complex128) + for i in range(self.count): + B[:, :, i] = self.basis_functions[i](self.rs, ts) * self.h + B = B.reshape(self.nres**2, self.count) + B = transform_complex_to_real(np.conj(B), self.ells) + B = B.reshape(self.nres**2, self.count) + if self.match_fb: + B[:, self.flip_sign_indices] *= -1.0 + B = B[:, self.fb_compat_indices] + return B + + def lowpass(self, coeffs, bandlimit): + """ + Apply a low-pass filter to FLE coefficients `coeffs` with threshold `bandlimit`. + :param coeffs: A NumPy array of FLE coefficients, of shape (num_images, self.count) + :param bandlimit: Integer bandlimit (max frequency). + :return: Band-limited coefficient array. + """ + if len(coeffs.shape) == 1: + coeffs = coeffs.reshape((1, coeffs.shape[0])) + assert ( + len(coeffs.shape) == 2 + ), "Input a stack of coefficients of dimension (num_images, self.count)." + assert ( + coeffs.shape[1] == self.count + ), "Number of coefficients must match self.count." + + k = self.count - 1 + for _ in range(self.count): + if self.bessel_zeros[k] / (np.pi) > (bandlimit - 1) // 2: + k = k - 1 + coeffs[:, k + 1 :] = 0 + + return coeffs + + def radial_convolve(self, coeffs, radial_img): + """ + Convolve a stack of FLE coefficients with a 2D radial function. + :param coeffs: A NumPy array of FLE coefficients of size (num_images, self.count). + :param radial_img: A 2D NumPy array of size (self.nres, self.nres). + :return: Convolved FLE coefficients. + """ + num_img = coeffs.shape[0] + coeffs_conv = np.zeros(coeffs.shape) + for k in range(num_img): + _coeffs = coeffs[k, :] + z = self._step1_t(radial_img) + b = self._step2_t(z) + weights = self._radial_convolve_weights(b) + b = weights / (self.h**2) + b = b.reshape(self.count) + coeffs_conv[k, :] = np.real(self.c2r @ (b * (self.r2c @ _coeffs).flatten())) + + return coeffs_conv + + def _radial_convolve_weights(self, b): + """ + Helper function for step 3 of convolving with a radial function. + """ + b = np.squeeze(b) + b = np.array(b) + if self.num_interp > self.num_radial_nodes: + b = dct(b, axis=0, type=2) / (2 * self.num_radial_nodes) + bz = np.zeros(b.shape) + b = np.concatenate((b, bz), axis=0) + b = idct(b, axis=0, type=2) * 2 * b.shape[0] + a = np.zeros(self.count, dtype=np.float64) + y = [None] * (self.ell_p_max + 1) + for i in range(self.ell_p_max + 1): + y[i] = (self.A3[i] @ b[:, 0]).flatten() + for i in range(self.ell_p_max + 1): + a[self.idx_list[i]] = y[i] + + return a.flatten() diff --git a/src/aspire/basis/fle_2d_utils.py b/src/aspire/basis/fle_2d_utils.py new file mode 100644 index 0000000000..e97d309524 --- /dev/null +++ b/src/aspire/basis/fle_2d_utils.py @@ -0,0 +1,202 @@ +import numpy as np +import scipy.sparse as sparse + + +def transform_complex_to_real(B_conj, ells): + """ + Transforms coefficients of the matrix B (see Eq. 3) from complex + to real. B is the linear transformation that takes FLE coefficients + to images. + + :param B_conj: Complex conjugate of the matrix B. + :param ells: List of ells (Bessel function orders) in this basis. + :return: Transformed matrix. + """ + num_basis_functions = B_conj.shape[1] + X = np.zeros(B_conj.shape, dtype=np.float64) + + for i in range(num_basis_functions): + ell = ells[i] + if ell == 0: + X[:, i] = np.real(B_conj[:, i]) + # for each ell != 0, we can populate two entries of the matrix + # by taking the complex conjugate of the ell with the opposite sign + if ell < 0: + s = (-1) ** np.abs(ell) + x0 = (B_conj[:, i] + s * B_conj[:, i + 1]) / np.sqrt(2) + x1 = (-B_conj[:, i] + s * B_conj[:, i + 1]) / (1j * np.sqrt(2)) + X[:, i] = np.real(x0) + X[:, i + 1] = np.real(x1) + + return X + + +def precomp_transform_complex_to_real(ells): + """ + Returns a sparse matrix that transforms coefficients into the complex + representation of the basis to coefficients in the real + representation of the basis. See Remark 1.1 of Marshall, Mickelin, + and Singer. + + :param ells: The list of integer Bessel function orders. + :return: Sparse complex to real transformation matrix. + """ + count = len(ells) + num_nonzero = np.sum(ells == 0) + 2 * np.sum(ells != 0) + idx = np.zeros(num_nonzero, dtype=int) + jdx = np.zeros(num_nonzero, dtype=int) + vals = np.zeros(num_nonzero, dtype=np.complex128) + + k = 0 + for i in range(count): + ell = ells[i] + # ell = 0 is a special case (DC component) + if ell == 0: + vals[k] = 1 + idx[k] = i + jdx[k] = i + k = k + 1 + # Only branch the case ell < 0 and also update -ell + # via complex conjugation + if ell < 0: + s = (-1) ** np.abs(ell) + + # positive ell + vals[k] = 1 / np.sqrt(2) + idx[k] = i + jdx[k] = i + k = k + 1 + + # positive ell + vals[k] = s / np.sqrt(2) + idx[k] = i + jdx[k] = i + 1 + k = k + 1 + + # negative ell + vals[k] = -1 / (1j * np.sqrt(2)) + idx[k] = i + 1 + jdx[k] = i + k = k + 1 + + # negative ell + vals[k] = s / (1j * np.sqrt(2)) + idx[k] = i + 1 + jdx[k] = i + 1 + k = k + 1 + + A = sparse.csr_matrix((vals, (idx, jdx)), shape=(count, count), dtype=np.complex128) + + return A + + +def barycentric_interp_sparse(target_points, known_points, numsparse): + """ + Returns the sparse matrices that perform barycentric interpolation to compute values + of Betas at the points `target_points` at known points `known_points`, and the transpose + of this operation. For each target point in `target_points`, only `numsparse` centered + source points from `known_points` around the target point are used. + + Performed via the method described in + + "Barycentric Lagrange Interpolation", Jean-Paul Berrut and Lloyd Trefethen. + SIAM Review 2004 46:3, 501-517 + https://people.maths.ox.ac.uk/trefethen/barycentric.pdf + + :param target_points: The target set of points at which to evaluate the functions. + :param known_points: The points at which the values of the functions are known. + :param numsparse: Number of points used for interpolation around each target point. + :return: The interpolation matrix and its transpose as a 2-tuple. + """ + + n = len(target_points) + m = len(known_points) + + # Modify points by 2e-16 to avoid division by zero + vals, x_ind, xs_ind = np.intersect1d( + target_points, known_points, return_indices=True, assume_unique=True + ) + target_points[x_ind] = target_points[x_ind] + 2e-16 + + idx = np.zeros((n, numsparse)) + jdx = np.zeros((n, numsparse)) + vals = np.zeros((n, numsparse)) + xss = np.zeros((n, numsparse)) + denom = np.zeros((n, 1)) + temp = np.zeros((n, 1)) + ws = np.zeros((n, numsparse)) + xdiff = np.zeros(n) + + # loop over target points + for i in range(n): + # choose `numsparse` source points centered around each target point + # in order to apply a sparse barycentric interpolation to this target + # point + k = np.searchsorted(target_points[i] < known_points, True) + + idp = np.arange(k - numsparse // 2, k + (numsparse + 1) // 2) + if idp[0] < 0: + idp = np.arange(numsparse) + if idp[-1] >= m: + idp = np.arange(m - numsparse, m) + # xss stores the values from `known_points` used for interpolation on the i'th + # target point + xss[i, :] = known_points[idp] + jdx[i, :] = idp + idx[i, :] = i + + # Auxiliary vector for computing products of expressions of the form xss[:,i] - xss[:,j] + # in order not to include xss[:,i] - xss[:,i] = 0. Will be all ones except for the index i + # not to include in the running product. this index is updated in the loop + Iw = np.ones(numsparse, dtype=bool) + ew = np.zeros((n, 1)) + xtw = np.zeros((n, numsparse - 1)) + + Iw[0] = False + const = np.zeros((n, 1)) + + for _ in range(numsparse): + ew = np.sum(-np.log(np.abs(xss[:, 0].reshape(-1, 1) - xss[:, Iw])), axis=1) + # normalization constant + constw = np.exp(ew / numsparse) + constw = constw.reshape(-1, 1) + const += constw + # this normalization constant prevents numerical issues and cancels out in the end + # not included in return result + const = const / numsparse + + for j in range(numsparse): + Iw[j] = False + # compute the denominator in Eq 3.2 of Berrut and Trefethen + xtw = const * (xss[:, j].reshape(-1, 1) - xss[:, Iw]) + ws[:, j] = 1 / np.prod(xtw, axis=1) + Iw[j] = True + + xdiff = xdiff.flatten() + target_points = target_points.flatten() + temp = temp.flatten() + denom = denom.flatten() + + for j in range(numsparse): + # xdiff[i] is the i'th target point minus the j'th source point for that target pt + # see the denominator in Eq. 3.3 of Berrut and Trefethen + xdiff = target_points - xss[:, j] + temp = ws[:, j] / xdiff + # vals[:,j] = (1/const)*w_j/(x[i] - xs[j]), with the notation in Eq. 3.3 + vals[:, j] = vals[:, j] + temp + denom = denom + temp + + # Eq 4.2 + # note that const cancels in numerator and denominator + vals = vals / denom.reshape(-1, 1) + + vals = vals.flatten() + idx = idx.flatten() + jdx = jdx.flatten() + # A is the linear operator mapping the function values from the fixed source + # points to the fixed target points. + # A(i,j) = \ell(x[i] ) w_j/(x[i] - xs[j]), with the notation in Eq. 3.3 + A = sparse.csr_matrix((vals, (idx, jdx)), shape=(n, m), dtype=np.float64) + A_T = sparse.csr_matrix((vals, (jdx, idx)), shape=(m, n), dtype=np.float64) + + return A, A_T diff --git a/src/aspire/basis/steerable.py b/src/aspire/basis/steerable.py index 3363f1cdf5..955c92dc4e 100644 --- a/src/aspire/basis/steerable.py +++ b/src/aspire/basis/steerable.py @@ -164,7 +164,9 @@ def rotate(self, coef, radians, refl=None): ) # else: radians can be a constant - assert self.count == coef.shape[-1] + assert ( + self.count == coef.shape[-1] + ), "Number of coefficients must match self.count." # self.angular_indices are `ks` # For all coef in stack, diff --git a/src/aspire/nufft/__init__.py b/src/aspire/nufft/__init__.py index 65662c7213..3fbf3dea2b 100644 --- a/src/aspire/nufft/__init__.py +++ b/src/aspire/nufft/__init__.py @@ -141,7 +141,7 @@ def __new__(cls, *args, **kwargs): return super(Plan, cls).__new__(cls) -def anufft(sig_f, fourier_pts, sz, real=False): +def anufft(sig_f, fourier_pts, sz, real=False, epsilon=1e-8): """ Wrapper for 1, 2, and 3 dimensional Non Uniform FFT Adjoint. Dimension is based on the dimension of fourier_pts and checked against sig_f. @@ -173,12 +173,14 @@ def anufft(sig_f, fourier_pts, sz, real=False): if len(sig_f.shape) == 2: ntransforms = sig_f.shape[0] - plan = Plan(sz=sz, fourier_pts=fourier_pts, ntransforms=ntransforms) + plan = Plan( + sz=sz, fourier_pts=fourier_pts, ntransforms=ntransforms, epsilon=epsilon + ) adjoint = plan.adjoint(sig_f) return np.real(adjoint) if real else adjoint -def nufft(sig_f, fourier_pts, real=False): +def nufft(sig_f, fourier_pts, real=False, epsilon=1e-8): """ Wrapper for 1, 2, and 3 dimensional Non Uniform FFT Dimension is based on the dimension of fourier_pts and checked against sig_f. @@ -219,6 +221,8 @@ def nufft(sig_f, fourier_pts, real=False): if len(sig_f.shape) == dimension + 1: ntransforms = sig_f.shape[0] - plan = Plan(sz=sz, fourier_pts=fourier_pts, ntransforms=ntransforms) + plan = Plan( + sz=sz, fourier_pts=fourier_pts, ntransforms=ntransforms, epsilon=epsilon + ) transform = plan.transform(sig_f) return np.real(transform) if real else transform diff --git a/tests/saved_test_data/fle_radial_fn_32x32.npy b/tests/saved_test_data/fle_radial_fn_32x32.npy new file mode 100644 index 0000000000000000000000000000000000000000..350d6179fab839e506fc030367dbe2b0f1997cf6 GIT binary patch literal 8320 zcmd^^dsvlK7RC<>NL~_65fv2`-(wb{6448Ako zsZ-c>YwhEy&5re|G-|#3ZtQyOhSk#8);qCjl@aoW)^k*i)pFwd!rQB zrnQ{yK2tGfp7rd<9`^Bv{|9&kA9%qJANax7KEUK1b8&61E$Et&l%AQh&b7iC5&HU4 z!+zb!JHBJx^eL6rdbxr3itgSpqCTr#y0L$i_1^uJ8}k;{7?!cuPOfZ!&$`sP?3tRE zoOCCT`271fU6rk9)jOdL+;v1y+L@CxJyqoTZe`Q9`{=6P-v!0C>Y=8FhaVi9)Ju6K z)pnUYVy0%yJnPwyJ?!HT|KI^1c)<@J_`%mwxA1@G9g}MEw(o3rJmg}9aqe!L{53QG zZ7uOUyCdd@N~5x{_Ne8ho7N?7xLzOdLzS_@b;S5%fwk7WM|Y3zar>TOY}h`zuu(nr zV)Vs}tu{2!p)Y^mc3!HxiphN?;Zo<;`o8V+mp>WSMQz=*G$?R&4?QGiln(D2u8U9Q zHVJPQuNX7WdiIBjJ?!rnfB4T9Jm9-2c)<@J_}St6i+Ru8dzt5Lzisk9{I@IjXQWmd zKa6{Egv;I=)^DzS*iX&BW&Eq4>h!(x+tw@dPn<8DamUEJ)W2wK;(hDl_6fm-)%CRh zS8l7mKIx`hJLO~y9M?=2rR@Cjczhf6lhg9X0nc>R4Lv*Ud16tB>eFY-&r@easNB!@ zr{9T9(2SWsC+pddJ?y85Km3;o9`JoFc)@>2_`vT0-%jTD9eKpO=aH?=^LCzK@-F>o z^N{&2w~Ps!67`~sRn}_XnCKVNYm7F99}nF#@UAs_ThigA(H32Ga`@21iOyR9_-{il2thA?&@>>1(;*ovyx6V~l(h_DX z#>}&x{n*2PjrhZVE5QT4NrD&r@PS{1@U?d|<688)=J$PSyLrz;FPrBbT43@{3AiQf)tq9#qK8Mn*U zLSJa=+wJ|%9hB$to$Yr`3D7O}Wv6T^4pZLqOTXCU)lWqPWfdF#NxGwqnP)xwCyG7n zHy3~SPZB)fOBTG~j}Sia8z+1%E@nJGXP9vgmmEh_JeBkO(*b=FJYST@8Z=n<8*=+xeUi`})q|NQK<2CbCqbeDd!(mU#^ z_fyG z-J9q}_6+NcwvVZQdba3zc555mDJ}ye~83l=mA&g znD^Ww=e@4L~|G=*ne02;s2K40bi)#1%Hb0fggPBpP6;);Js$O z_N>$yk&?exOCH-P@wcy^8P|&Bd(4x2-qy`LZ!f_)CME8PYX1;DWKLsC&|6Qa1nZHe zPj(-qo%iq9_d?Q072?x;OWOV^YE)5md8*&*nlbb7vY!28#UA#(#2^0m2_Eo$EO@~m zA$;HmUyIb|Nx^2FTDs1x*A7YDQY!hoR`S?TiRWV^uEogreOm7MDmm}&!aFfN?#S8; zQF=hzg9{!{d|E9FsXkG0bb|iHmU*_U%Pc*){o6h{b5<$F%(I^T*u(xA@rVDhf(Lxn zf*1S`_y|AvTGGsSNo1Z`pS#aB>(pAQzg8?a>y}W--=6tq9_uLa*)DPIC;47i%e{@0 z^Lt8o=d|mxWKHTMeSDUY-u%;Ps{5hn#l^4K)t>h*P1=*SQFoIu^Y6%d_G1tGm&G6c zTL~WUg$Q2oR|+5ac?(~=yvIu9U6L;Kd1WiJPTec@T7c9o_xqXo`=aDAyTtQciEG>B z`)(-r{7pIUQ^I?J|2UV)cSq|5&!_jcypXGH33a<)3*4a?GtYYVV-NfI!#{Yy2VU^Q z2Y&DkzG}WhEfMB>EKlAgF;buVNu9b(>a!B5TlPx+E|oksSmL>5w;9(0t`OYJ<=~D;PfFDPeZ5^{rF=qa8S`H6!jJL7WEhP81)(T8uc6X9Q7Ubo}>DYdXfFqlh~); z#6R^Y_^4OGPd)o^{pYCur(ULhrk=tl5q%-}x8s|NaN9g`Tqj literal 0 HcmV?d00001 diff --git a/tests/test_FLEbasis2D.py b/tests/test_FLEbasis2D.py new file mode 100644 index 0000000000..d17f2e994c --- /dev/null +++ b/tests/test_FLEbasis2D.py @@ -0,0 +1,321 @@ +import os + +import numpy as np +import pytest + +from aspire.basis import FBBasis2D, FLEBasis2D +from aspire.image import Image +from aspire.nufft import backend_available +from aspire.numeric import fft +from aspire.source import Simulation +from aspire.utils import utest_tolerance +from aspire.volume import Volume + +from ._basis_util import UniversalBasisMixin + +DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") + + +def show_fle_params(basis): + return f"{basis.nres}-{basis.epsilon}" + + +def gpu_ci_skip(): + pytest.skip("1e-7 precision for FLEBasis2D.evaluate()") + + +fle_params = [ + (32, 1e-4), + (32, 1e-7), + (32, 1e-10), + (32, 1e-14), + (33, 1e-4), + (33, 1e-7), + (33, 1e-10), + (33, 1e-14), +] + +test_bases = [ + FLEBasis2D(L, epsilon=epsilon, dtype=np.float64, match_fb=False) + for L, epsilon in fle_params +] + +# add one case ensuring input/output dtypes for evaluate and evaluate_t +test_bases.append(FLEBasis2D(8, epsilon=1e-4, dtype=np.float32, match_fb=False)) + +test_bases_match_fb = [ + FLEBasis2D(L, epsilon=epsilon, dtype=np.float64) for L, epsilon in fle_params +] + + +def create_images(L, n): + # create sample data + v = Volume( + np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")).astype(np.float64) + ) + v = v.downsample(L) + sim = Simulation(L=L, n=n, vols=v, dtype=v.dtype, seed=1103) + img = sim.clean_images[:] + return img + + +def relerr(base, approx): + # relative error of two arrays + base = np.array(base).flatten() + approx = np.array(approx).flatten() + return np.linalg.norm(base - approx) / np.linalg.norm(base) + + +@pytest.mark.parametrize("basis", test_bases, ids=show_fle_params) +class TestFLEBasis2D(UniversalBasisMixin): + # check closeness guarantees for fast vs dense matrix method + def testFastVDense_T(self, basis): + dense_b = basis._create_dense_matrix() + + # create sample particle + x = create_images(basis.nres, 1).asnumpy() + xvec = x.reshape((basis.nres**2, 1)) + + # explicit matrix multiplication + result_dense = dense_b.T @ xvec + # fast evaluate_t + result_fast = basis.evaluate_t(Image(x)) + + assert relerr(result_dense.T, result_fast) < basis.epsilon + + def testFastVDense(self, basis): + if backend_available("cufinufft") and basis.epsilon == 1e-7: + gpu_ci_skip() + + dense_b = basis._create_dense_matrix() + + # get sample coefficients + x = create_images(basis.nres, 1) + # hold input test data constant (would depend on epsilon parameter) + coeffs = FLEBasis2D( + basis.nres, epsilon=1e-4, dtype=np.float64, match_fb=False + ).evaluate_t(x) + + result_dense = dense_b @ coeffs.T + result_fast = basis.evaluate(coeffs).asnumpy() + + assert relerr(result_dense, result_fast) < basis.epsilon + + def testEvaluateExpand(self, basis): + if backend_available("cufinufft") and basis.epsilon == 1e-7: + gpu_ci_skip() + + # compare result of evaluate() vs more accurate expand() + # get sample coefficients + x = create_images(basis.nres, 1) + # hold input test data constant (would depend on epsilon parameter) + evaluate_t = basis.evaluate(basis.evaluate_t(x)) + expand = basis.evaluate(basis.expand(evaluate_t)) + + assert relerr(expand.asnumpy(), evaluate_t.asnumpy()) < basis.epsilon + + +@pytest.mark.parametrize("basis", test_bases_match_fb, ids=show_fle_params) +def testMatchFBEvaluate(basis): + if backend_available("cufinufft") and basis.epsilon == 1e-7: + gpu_ci_skip() + + # ensure that the basis functions are identical when in match_fb mode + fb_basis = FBBasis2D(basis.nres, dtype=np.float64) + + # in match_fb, count is the same for both bases + coeffs = np.eye(basis.count) + + fb_images = fb_basis.evaluate(coeffs) + fle_images = basis.evaluate(coeffs) + + assert np.allclose(fb_images._data, fle_images._data, atol=1e-4) + + +@pytest.mark.parametrize("basis", test_bases_match_fb, ids=show_fle_params) +def testMatchFBDenseEvaluate(basis): + # ensure that images are the same when evaluating coefficients via slow + # matrix multiplication + + fb_basis = FBBasis2D(basis.nres, dtype=np.float64) + + coeffs = np.eye(basis.count) + + fb_images = fb_basis.evaluate(coeffs).asnumpy() + fle_out = basis._create_dense_matrix() @ coeffs + fle_images = Image(fle_out.T.reshape(-1, basis.nres, basis.nres)).asnumpy() + + # Matrix column reording in match_fb mode flips signs of some of the basis functions + assert np.allclose(np.abs(fb_images), np.abs(fle_images), atol=1e-3) + + +@pytest.mark.parametrize("basis", test_bases_match_fb, ids=show_fle_params) +def testMatchFBEvaluate_t(basis): + # ensure that coefficients are the same when evaluating images + fb_basis = FBBasis2D(basis.nres, dtype=np.float64) + + # test images to evaluate + images = fb_basis.evaluate(np.eye(basis.count)) + + fb_coeffs = fb_basis.evaluate_t(images) + fle_coeffs = basis.evaluate_t(images) + + assert np.allclose(fb_coeffs, fle_coeffs, atol=1e-4) + + +@pytest.mark.parametrize("basis", test_bases_match_fb, ids=show_fle_params) +def testMatchFBDenseEvaluate_t(basis): + # ensure that coefficients are the same when evaluating images via slow + # matrix multiplication + + fb_basis = FBBasis2D(basis.nres, dtype=np.float64) + + # test images to evaluate + # gets a stack of shape (basis.count, L, L) + images = fb_basis.evaluate(np.eye(basis.count)) + # reshape to a stack of basis.count vectors of length L**2 + vec = images.asnumpy().reshape((-1, basis.nres**2)) + + fb_coeffs = fb_basis.evaluate_t(images) + fle_coeffs = basis._create_dense_matrix().T @ vec.T + + # Matrix column reording in match_fb mode flips signs of some of the basis coefficients + assert np.allclose(np.abs(fb_coeffs), np.abs(fle_coeffs), atol=1e-4) + + +def testLowPass(): + # test that low passing removes more and more high frequency + # elements as bandlimit decreases + + L = 128 + basis = FLEBasis2D(L, match_fb=False) + + # sample coefficients + ims = create_images(L, 1) + coeffs = basis.evaluate_t(ims) + + nonzero_coeffs = [] + for i in range(4): + bandlimit = L // (2**i) + coeffs_lowpassed = basis.lowpass(coeffs, bandlimit) + nonzero_coeffs.append(np.sum(coeffs_lowpassed != 0)) + + # for bandlimit == L, no frequencies should be removed + assert nonzero_coeffs[0] == basis.count + + # for lower bandlimits, there should be fewer and fewer nonzero coeffs + assert nonzero_coeffs[0] > nonzero_coeffs[1] > nonzero_coeffs[2] > nonzero_coeffs[3] + + # make sure you can pass in a 1-D array if you want + _ = basis.lowpass(coeffs[0, :], L) + + # cannot pass in the wrong number of coefficients + with pytest.raises( + AssertionError, match="Number of coefficients must match self.count." + ): + _ = basis.lowpass(coeffs[:, :1000], L) + + # cannot pass in wrong shape + with pytest.raises( + AssertionError, + match="Input a stack of coefficients of dimension", + ): + _ = basis.lowpass(np.zeros((3, 3, 3)), L) + + +def testRotate(): + # test ability to accurately rotate images via + # FLE coefficients + + L = 128 + basis = FLEBasis2D(L, match_fb=False) + + # sample image + ims = create_images(L, 1) + # rotate 90 degrees in cartesian coordinates + ims_90 = Image(np.rot90(ims.asnumpy(), axes=(1, 2))) + + # get FLE coefficients + coeffs = basis.evaluate_t(ims) + coeffs_cart_rot = basis.evaluate_t(ims_90) + + # rotate original image in FLE space using Steerable rotate method + coeffs_fle_rot = basis.rotate(coeffs, np.pi / 2) + + # back to cartesian + ims_cart_rot = basis.evaluate(coeffs_cart_rot) + ims_fle_rot = basis.evaluate(coeffs_fle_rot) + + # test rot90 close + assert np.allclose(ims_cart_rot[0], ims_fle_rot[0], atol=1e-4) + + # 2Pi identity in FLE space (rotate by 2Pi) + coeffs_fle_2pi = basis.rotate(coeffs, 2 * np.pi) + ims_fle_2pi = basis.evaluate(coeffs_fle_2pi) + + # test 2Pi identity + assert np.allclose(ims[0], ims_fle_2pi[0], atol=utest_tolerance(basis.dtype)) + + # Reflect in FLE space (rotate by Pi) + coeffs_fle_pi = basis.rotate(coeffs, np.pi) + ims_fle_pi = basis.evaluate(coeffs_fle_pi) + + # test reflection + assert np.allclose(np.flipud(ims[0]), ims_fle_pi[0], atol=1e-4) + + # make sure you can pass in a 1-D array if you want + _ = basis.lowpass(np.zeros((basis.count,)), np.pi) + + # cannot pass in the wrong number of coefficients + with pytest.raises( + AssertionError, match="Number of coefficients must match self.count." + ): + _ = basis.rotate(np.zeros((1, 10)), np.pi) + + # cannot pass in wrong shape + with pytest.raises( + AssertionError, + match="Input a stack of coefficients of dimension", + ): + _ = basis.lowpass(np.zeros((3, 3, 3)), np.pi) + + +def testRadialConvolution(): + # test ability to accurately convolve with a radial + # (e.g. CTF) function via FLE coefficients + + L = 32 + basis = FLEBasis2D(L, match_fb=False) + # load test radial function + x = np.load(os.path.join(DATA_DIR, "fle_radial_fn_32x32.npy")).reshape(1, 32, 32) + x = x / np.max(np.abs(x.flatten())) + + # get sample images + ims = create_images(L, 10) + # convolve using coefficients + coeffs = basis.evaluate_t(ims) + coeffs_convolved = basis.radial_convolve(coeffs, x) + imgs_convolved_fle = basis.evaluate(coeffs_convolved).asnumpy() + + # convolve using FFT + x = basis.evaluate(basis.evaluate_t(x)).asnumpy() + ims = basis.evaluate(coeffs).asnumpy() + + imgs_convolved_slow = np.zeros((10, L, L)) + for i in range(10): + x_pad = np.zeros((2 * L, 2 * L)) + ims_pad = np.zeros((2 * L, 2 * L)) + x_pad[L // 2 : L // 2 + L, L // 2 : L // 2 + L] = x[0, :, :] + ims_pad[L // 2 : L // 2 + L, L // 2 : L // 2 + L] = ims[i, :, :] + + x_shift = fft.fftshift(x_pad.reshape(2 * L, 2 * L)) + ims_shift = fft.fftshift(ims_pad.reshape(2 * L, 2 * L)) + + convolution_fft_pad = fft.fftshift( + fft.ifft2(np.fft.fft2(x_shift) * np.fft.fft2(ims_shift)) + ) + imgs_convolved_slow[i, :, :] = np.real( + convolution_fft_pad[L // 2 : L // 2 + L, L // 2 : L // 2 + L] + ) + + assert np.allclose(imgs_convolved_fle, imgs_convolved_slow, atol=1e-5) From 5dd785857861fa2d0e7e0d5921dea7c1a29c4983 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Feb 2023 15:04:45 -0500 Subject: [PATCH 131/424] Fixup review remarks --- gallery/tutorials/pipeline_demo.py | 2 +- src/aspire/noise/noise.py | 2 ++ src/aspire/source/image.py | 14 +++++++++----- src/aspire/utils/misc.py | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index c3b5b530de..a5b12bef18 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -68,7 +68,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Noise and CTF Filters # ^^^^^^^^^^^^^^^^^^^^^ -# Let's start by CTF filters. The ``operators`` package contains a collection +# Let's start by creating CTF filters. The ``operators`` package contains a collection # of filter classes that can be supplied to a ``Simulation``. # We use ``RadialCTFFilter`` to generate a set of CTF filters with various defocus values. diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index df31dd7bb4..1a4fcb7139 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -77,7 +77,9 @@ def noise_var(self, res=512): Return noise variance. CustomNoiseAdder will estimate noise_var using the `noise_filter`. + :param res: Resolution to use when evaluating noise filter, default 512. + :returns: Noise variance estimated at `res`. """ # Take mean of user provided _noise_filter, before the PowerFilter is applied. return np.mean(self._noise_filter.evaluate_grid(res)) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 325c0ee2fa..ace23bb928 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -812,7 +812,7 @@ def estimate_signal_mean_energy( if sample_n > self.n: logger.warning( f"`estimate_signal_mean_energy` sample_n > Source.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + f" Accuracy may be impaired, setting sample_n=self.n={self.n}" ) sample_n = self.n @@ -826,7 +826,7 @@ def estimate_signal_mean_energy( for i in trange(0, sample_n, batch_size): # Gather this batch of images and mask off area outside support_radius images_masked = self._signal_images[i : i + batch_size].asnumpy()[..., mask] - # Accumulate first and second moments + # Accumulate second moments s += np.sum(images_masked**2) / _denom logger.debug(f"Source estimated signal mean energy: {s}") @@ -851,7 +851,7 @@ def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512 if sample_n > self.n: logger.warning( f"`estimate_signal_var` sample_n > Source.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + f" Accuracy may be impaired, setting sample_n=self.n={self.n}" ) sample_n = self.n @@ -957,7 +957,11 @@ def estimate_snr( ): """ Estimate the SNR of the simulated data set using - estimated signal variance / noise variance. + estimated signal power / noise power. + + Note signal power depends on choice of `signal_power_method`, + but differences should be small in practice when background + noise is zero centered. :param sample_n: Number of images used for estimate. Defaults to all images in source. @@ -977,7 +981,7 @@ def estimate_snr( if sample_n > self.n: logger.warning( f"`estimate_snr` sample_n > Source.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" + f" Accuracy may be impaired, setting sample_n=self.n={self.n}" ) sample_n = self.n diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index 78dad7c84c..6e683e7a98 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -391,7 +391,7 @@ def support_mask(L, support_radius=None, dtype=np.float64): # Disables mask, here to reduce code duplication. return np.full((L, L), fill_value=True, dtype=bool) - elif not 0 < support_radius <= L * np.sqrt(2): + elif not 0 < support_radius <= L // 2 * np.sqrt(2): raise ValueError( "support_radius should be" f" `(0, L*sqrt(2)={L*np.sqrt(2)}]` or -1 to disable." From 872719c7cc02798b1f12e7ca37064473eff6c099 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Feb 2023 12:27:32 -0500 Subject: [PATCH 132/424] CTF Gallery review remarks minor cleanup, no intentional functional changes --- gallery/tutorials/ctf.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 0be73c8774..f913a33ea6 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -4,7 +4,7 @@ ================================ This tutorial demonstrates the CTF and corresponds to -lecture notes from MATH586. +lecture notes from MATH586 at Princeton. """ # sphinx_gallery_thumbnail_path = '../../gallery/tutorials/data/ctffind4/diagnostic_output.png' @@ -21,7 +21,7 @@ import aspire -# Resolution to use throughout the demo. +# Image size to use throughout the demo. IMG_SIZE = 512 # %% @@ -59,15 +59,16 @@ # %% # Elliptical CTF # ^^^^^^^^^^^^^^ -# For the general ``CTFFilter``, we provide defocus u and v seperately, -# along with a defocus angle. +# For the general ``CTFFilter``, +# we provide defocus along two perpendicular axes u and v separately, +# along with the angle the u-axis makes with the horizontal axis. ctf_filter = CTFFilter( pixel_size=1, # Angstrom voltage=200, # kV defocus_u=15000, # Angstrom, 10000A = 1um defocus_v=10000, - defocus_ang=np.pi / 4, # Radians + defocus_ang=np.pi / 6, # Radians Cs=2.26, # Spherical aberration constant alpha=0.07, # Amplitude contrast phase in radians B=0, # Envelope decay in inverse square angstrom (default 0) @@ -103,7 +104,7 @@ # %% # Generate Example Image # ^^^^^^^^^^^^^^^^^^^^^^ -def gen_ex_img(L, noise_variance=0.1): +def generate_example_image(L, noise_variance=0.1): """ Generates data similar to the MATH586 lecture notes. @@ -114,19 +115,19 @@ def gen_ex_img(L, noise_variance=0.1): # Empty Array img = np.zeros((L, L)) + # Construct grid + g2d = aspire.utils.grid_2d(L, normalized=False) + # Make central square center = L // 2 - half_width = L // 6 - lb, ub = center - half_width, center + half_width - img[lb:ub, lb:ub] = 1 + img[(np.abs(g2d["x"]) < L // 6) & (np.abs(g2d["y"]) < L // 6)] = 1 # Remove the outer corners - g2d = aspire.utils.grid_2d(L, normalized=False) - disc = g2d["r"] > (1.2 * half_width) + disc = g2d["r"] > (1.2 * L // 6) img[disc] = 0 # Remove center circle - disc = g2d["r"] < half_width // 4 + disc = g2d["r"] < L // 24 img[disc] = 0 # Smooth hard edges with some Gaussian blur @@ -138,7 +139,7 @@ def gen_ex_img(L, noise_variance=0.1): return img -img = gen_ex_img(IMG_SIZE) +img = generate_example_image(IMG_SIZE) plt.imshow(img) plt.colorbar() plt.show() @@ -223,7 +224,7 @@ def gen_ex_img(L, noise_variance=0.1): alpha=0.07, B=0, ) -# Evaluate Filter, returning a numpy array. +# Evaluate Filter, returning a Numpy array. bad_ctf_fn = bad_est_ctf_filter.evaluate_grid(IMG_SIZE) c = IMG_SIZE // 2 + 1 @@ -362,7 +363,7 @@ def gen_ex_img(L, noise_variance=0.1): # Beyond Simulations: Experimental Data Sources # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # When loading experimental data, -# CTF params in the starfile should be loaded automatically. +# CTF parameters in the STAR file should be loaded automatically. from aspire.source import RelionSource From 3c854b0441fa8f9262f0f465a2707d9be693362d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Feb 2023 12:41:05 -0500 Subject: [PATCH 133/424] Swap CTF u axis --- gallery/tutorials/ctf.py | 2 +- src/aspire/operators/filters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index f913a33ea6..165a859744 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -61,7 +61,7 @@ # ^^^^^^^^^^^^^^ # For the general ``CTFFilter``, # we provide defocus along two perpendicular axes u and v separately, -# along with the angle the u-axis makes with the horizontal axis. +# along with the angle the u-axis makes with the horizontal (x) axis. ctf_filter = CTFFilter( pixel_size=1, # Angstrom diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index 29713894a8..8f02b6bced 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -440,7 +440,7 @@ def __init__( self.defocus_diff = 0.5 * (self.defocus_u - self.defocus_v) def _evaluate(self, omega): - om_x, om_y = np.vsplit(omega / (2 * np.pi * self.pixel_size), 2) + om_y, om_x = np.vsplit(omega / (2 * np.pi * self.pixel_size), 2) eps = np.finfo(np.pi).eps ind_nz = (np.abs(om_x) > eps) | (np.abs(om_y) > eps) From 4204ce74a569ee8ca67fdbac7a343a8ff2213911 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Feb 2023 12:55:54 -0500 Subject: [PATCH 134/424] tox, forgot unused variable --- gallery/tutorials/ctf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 165a859744..33652f82de 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -119,7 +119,6 @@ def generate_example_image(L, noise_variance=0.1): g2d = aspire.utils.grid_2d(L, normalized=False) # Make central square - center = L // 2 img[(np.abs(g2d["x"]) < L // 6) & (np.abs(g2d["y"]) < L // 6)] = 1 # Remove the outer corners From a15f5cf50ee0fb155ac907327f52e33136f20461 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Feb 2023 13:28:08 -0500 Subject: [PATCH 135/424] First round review comments --- gallery/tutorials/lecture_feature_demo.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 17edd2377b..47b18fbf6f 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -108,10 +108,11 @@ # --------- # Alternatively, for quick sanity checking purposes we can view as a contour plot. -# We'll use three orthographic projections, one per axis. -for axis in range(3): - plt.imshow(np.sum(v2[0], axis=axis), cmap="gray") - plt.show() +# We'll use three orthographic projections, one per axis +fig, axs = plt.subplots(1, 3) +for i in range(3): + axs[i].imshow(np.sum(v2[0], axis=i), cmap="gray") +plt.show() # %% # ``Rotation`` Class - Generating Random Rotations @@ -337,7 +338,7 @@ def noise_function(x, y): # Real Experimental Data - ``RelionSource`` # ----------------------------------------- # -# Now that we know have some basics, +# Now that we have some basics, # we can try to replace the simulation with a real experimental data source. # # Lets attempt the same CL experiment, but with a ``RelionSource``. From 219d8d089ec8391b43368344e0adb076f9dd5819 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 21 Feb 2023 11:39:09 -0500 Subject: [PATCH 136/424] use np.argwhere in place of loop --- tests/test_orient_symmetric.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 991ab79721..34dd50b449 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -88,13 +88,7 @@ def test_estimate_rotations(L, order, dtype): # For order>4 we cannot expect estimates from equator images to be accurate. # So we exclude those from testing. if order > 4: - equator_inds = np.array( - [ - ind - for (ind, rot_gt) in enumerate(rots_gt) - if abs(np.arccos(rot_gt[2, 2]) - np.pi / 2) < 10 * np.pi / 180 - ] - ) + equator_inds = np.argwhere(abs(np.arccos(rots_gt[:,2, 2]) - np.pi / 2) < 10 * np.pi / 180) # Exclude equator estimates and ground truths. rots_est = np.delete(rots_est, equator_inds, axis=0) @@ -256,13 +250,7 @@ def test_relative_viewing_directions(L, order, dtype): # For order>4 we cannot expect estimates from equator images to be accurate. # So we exclude those from testing. if order > 4: - equator_inds = np.array( - [ - ind - for (ind, rot_gt) in enumerate(rots_gt) - if abs(np.arccos(rot_gt[2, 2]) - np.pi / 2) < 10 * np.pi / 180 - ] - ) + equator_inds = np.argwhere(abs(np.arccos(rots_gt[:,2, 2]) - np.pi / 2) < 10 * np.pi / 180) # Exclude ii estimates and ground truths. min_theta_vii = np.delete(min_theta_vii, equator_inds, axis=0) From e371ec16a674fe74ea7378117a10a2a7c550968f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Feb 2023 13:30:39 -0500 Subject: [PATCH 137/424] Try to always return Image from Image methods --- src/aspire/image/image.py | 26 +++++++++++++------------- src/aspire/source/image.py | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index a337c37418..a344201f9d 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -138,7 +138,7 @@ def _check_key_dims(self, key): def __getitem__(self, key): self._check_key_dims(key) - return self._data[key] + return self.__class__(self._data[key]) def __setitem__(self, key, value): self._check_key_dims(key) @@ -166,31 +166,31 @@ def stack_reshape(self, *args): f"Number of images {self.n_images} cannot be reshaped to {shape}." ) - return Image(self._data.reshape(*shape, *self._data.shape[-2:])) + return self.__class__(self._data.reshape(*shape, *self._data.shape[-2:])) def __add__(self, other): if isinstance(other, Image): other = other._data - return Image(self._data + other) + return self.__class__(self._data + other) def __sub__(self, other): if isinstance(other, Image): other = other._data - return Image(self._data - other) + return self.__class__(self._data - other) def __mul__(self, other): if isinstance(other, Image): other = other._data - return Image(self._data * other) + return self.__class__(self._data * other) def __neg__(self): - return Image(-self._data) + return self.__class__(-self._data) def sqrt(self): - return Image(np.sqrt(self._data)) + return self.__class__(np.sqrt(self._data)) @property def T(self): @@ -212,7 +212,7 @@ def transpose(self): im = self.stack_reshape(-1) imt = np.transpose(im._data, (0, -1, -2)) - return Image(imt).stack_reshape(original_stack_shape) + return self.__class__(imt).stack_reshape(original_stack_shape) def flip(self, axis=-2): """ @@ -235,7 +235,7 @@ def flip(self, axis=-2): f"Cannot flip axis {ax}: stack axis. Did you mean {ax-3}?" ) - return Image(np.flip(self._data, axis)) + return self.__class__(np.flip(self._data, axis)) def __repr__(self): msg = f"{self.n_images} {self.dtype} images arranged as a {self.stack_shape} stack" @@ -246,7 +246,7 @@ def asnumpy(self): return self._data def copy(self): - return Image(self._data.copy()) + return self.__class__(self._data.copy()) def shift(self, shifts): """ @@ -292,7 +292,7 @@ def downsample(self, ds_res): ds_res**2 / self.resolution**2 ) - return Image(out).stack_reshape(original_stack_shape) + return self.__class__(out).stack_reshape(original_stack_shape) def filter(self, filter): """ @@ -317,7 +317,7 @@ def filter(self, filter): im = xp.asnumpy(fft.centered_ifft2(xp.asarray(im_f))) im = np.real(im) - return Image(im).stack_reshape(original_stack_shape) + return self.__class__(im).stack_reshape(original_stack_shape) def rotate(self): raise NotImplementedError @@ -376,7 +376,7 @@ def _im_translate(self, shifts): im_translated = np.real(im_translated) # Reshape to stack shape - return Image(im_translated).stack_reshape(stack_shape) + return self.__class__(im_translated).stack_reshape(stack_shape) def norm(self): return anorm(self._data) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 6a176c688e..3616a2e521 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -380,7 +380,7 @@ def _apply_filters( for i, filt in enumerate(filters): idx_k = np.where(indices == i)[0] if len(idx_k) > 0: - im[idx_k] = Image(im[idx_k]).filter(filt).asnumpy() + im[idx_k] = im[idx_k].filter(filt).asnumpy() return im @@ -865,7 +865,7 @@ def _images(self, indices): """ # Load cached data and apply transforms return self.generation_pipeline.forward( - Image(self._cached_im[indices, :, :]), indices + self._cached_im[indices, :, :], indices ) def _rots(self): From 197d1bf56bfa5fa5eb6de8f8fa846c02d0a986f0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Feb 2023 14:18:15 -0500 Subject: [PATCH 138/424] tox --- src/aspire/source/image.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 3616a2e521..1b1c703283 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -864,9 +864,7 @@ def _images(self, indices): :return: An `Image` object. """ # Load cached data and apply transforms - return self.generation_pipeline.forward( - self._cached_im[indices, :, :], indices - ) + return self.generation_pipeline.forward(self._cached_im[indices, :, :], indices) def _rots(self): """ From abe2ac83a5ffd3622d1ebc6b62b4afa9414638da Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 07:56:49 -0500 Subject: [PATCH 139/424] Workaround grpc#32163 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index d33110d9f8..f4c0a45f57 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ def read(fname): "confuse>=2.0.0", "finufft", "gemmi>=0.4.8", + "grpcio<=1.48.2", "joblib", "matplotlib>=3.2.0", "mrcfile", From f1bebf6cc9bff9eec4835b711a0791d9306429c3 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 23 Feb 2023 08:31:10 -0500 Subject: [PATCH 140/424] use array operations to find pairwise_equator_inds. Adapt to pairs_to_linear to accept arrays of indices. --- src/aspire/utils/misc.py | 7 +++++-- tests/test_orient_symmetric.py | 22 ++++++++++++++-------- tests/test_utils.py | 17 +++++++++++------ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index b801873049..20b4e39110 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -308,7 +308,7 @@ def all_pairs(n): :param n: The number of items to be indexed. :return: All n-choose-2 pairs (i,j), i 0 and (n-2, n-1) --> n * (n - 1)/2 - 1 """ - assert i < j < n, "i must be less than j, and both must be less than n." + i = np.array(i) + j = np.array(j) + + assert (i < j).all() < n, "i must be less than j, and both must be less than n." linear_index = n * (n - 1) // 2 - (n - i) * (n - i - 1) // 2 + j - i - 1 diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 34dd50b449..bb034ff7e4 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -12,7 +12,7 @@ get_rots_mse, register_rotations, ) -from aspire.utils.misc import J_conjugate, all_pairs, cyclic_rotations +from aspire.utils.misc import J_conjugate, all_pairs, cyclic_rotations, pairs_to_linear from aspire.utils.random import randn from aspire.volume import CnSymmetricVolume @@ -88,7 +88,9 @@ def test_estimate_rotations(L, order, dtype): # For order>4 we cannot expect estimates from equator images to be accurate. # So we exclude those from testing. if order > 4: - equator_inds = np.argwhere(abs(np.arccos(rots_gt[:,2, 2]) - np.pi / 2) < 10 * np.pi / 180) + equator_inds = np.argwhere( + abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) < 10 * np.pi / 180 + ) # Exclude equator estimates and ground truths. rots_est = np.delete(rots_est, equator_inds, axis=0) @@ -250,18 +252,22 @@ def test_relative_viewing_directions(L, order, dtype): # For order>4 we cannot expect estimates from equator images to be accurate. # So we exclude those from testing. if order > 4: - equator_inds = np.argwhere(abs(np.arccos(rots_gt[:,2, 2]) - np.pi / 2) < 10 * np.pi / 180) + equator_inds = np.argwhere( + abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) < 10 * np.pi / 180 + ) # Exclude ii estimates and ground truths. min_theta_vii = np.delete(min_theta_vii, equator_inds, axis=0) sii = np.delete(sii, equator_inds, axis=0) # Exclude ij estimates and ground truths. - pairwise_equator_inds = [] - for pair in pairs: - for ind in equator_inds: - if ind in pair: - pairwise_equator_inds.append(pairs.index(pair)) + matches = np.equal( + equator_inds[:, None], pairs + ) # Find where equator_inds match pairs indices + bad_pairs = pairs[ + np.sum(matches, axis=(0, 2)).astype(bool) + ] # Take pairs where at least 1 index matches + pairwise_equator_inds = pairs_to_linear(n_img, bad_pairs[:, 0], bad_pairs[:, 1]) min_theta_vij = np.delete(min_theta_vij, pairwise_equator_inds, axis=0) sij = np.delete(sij, pairwise_equator_inds, axis=0) diff --git a/tests/test_utils.py b/tests/test_utils.py index b5af2424a6..d318c5baab 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -145,18 +145,23 @@ def testAllPairs(self): n = 25 pairs = all_pairs(n) nchoose2 = n * (n - 1) // 2 + # Build all pairs using a loop to ensure numpy upper_triu() ordering matches. + pairs_from_loop = [[i, j] for i in range(n) for j in range(n) if i < j] self.assertTrue(len(pairs) == nchoose2) self.assertTrue(len(pairs[0]) == 2) + self.assertTrue((pairs == pairs_from_loop).all()) def testPairsToLinear(self): n = 10 pairs = all_pairs(n) - all_pairs_index = np.zeros(len(pairs)) - pairs_to_linear_index = np.zeros(len(pairs)) - for idx, (i, j) in enumerate(pairs): - all_pairs_index[idx] = pairs.index((i, j)) - pairs_to_linear_index[idx] = pairs_to_linear(n, i, j) - self.assertTrue(np.allclose(all_pairs_index, pairs_to_linear_index)) + linear_index = np.arange(len(pairs)) + + # Test single pair + self.assertTrue(pairs_to_linear(n, 0, 2) == 1) + # Test full set of pairs + self.assertTrue( + (pairs_to_linear(n, pairs[:, 0], pairs[:, 1]) == linear_index).all() + ) def testAllTriplets(self): n = 25 From a2bc3fb3dcac730df59bd98754d5db3ab6dcef62 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 23 Feb 2023 10:10:23 -0500 Subject: [PATCH 141/424] branch for cn tolerances. rewrite mean_outer_product test. --- tests/test_orient_symmetric.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index bb034ff7e4..c18f1d1a53 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -279,10 +279,15 @@ def test_relative_viewing_directions(L, order, dtype): # ie. check that the svd is close to [1, 0, 0]. error_ij = np.linalg.norm(np.array([1, 0, 0], dtype=dtype) - sij, axis=1) error_ii = np.linalg.norm(np.array([1, 0, 0], dtype=dtype) - sii, axis=1) + max_tol_ii = 1e-5 + mean_tol_ii = 1e-5 + if order > 4: + max_tol_ii = 5e-2 + mean_tol_ii = 2e-2 assert np.max(error_ij) < 2e-1 - assert np.max(error_ii) < 5e-2 + assert np.max(error_ii) < max_tol_ii assert np.mean(error_ij) < 2e-3 - assert np.mean(error_ii) < 1e-5 + assert np.mean(error_ii) < mean_tol_ii # Check that the mean angular difference is within 2 degrees. angle_tol = 2 * np.pi / 180 @@ -521,23 +526,27 @@ def build_outer_products(n_img, dtype): return vijs, viis, gt_vis -def test_mean_estimator_simple(): +def test_mean_outer_product_estimator(): """ Manully run MeanOuterProductEstimator for prebaked inputs. """ est = MeanOuterProductEstimator() - # Push two matrices with opposite signs. - est.push(np.full((3, 3), -2, dtype=np.float64)) - est.push(np.full((3, 3), 2, dtype=np.float64)) + # Test arrays with opposite conjugation. + V = np.array([[1, 1, 1], [3, 3, 3], [5, 5, 5]]) + V_J = np.array([[1, 1, -1], [3, 3, -3], [-5, -5, 5]]) + + # Push two matrices with opposite conjugation. + est.push(V) + est.push(V_J) # synchronized_mean will J-conjugate the second entry prior to averaging. - assert np.allclose( - est.synchronized_mean(), np.array([[0, 0, -2], [0, 0, -2], [-2, -2, 0]]) - ) + assert np.allclose(est.synchronized_mean(), V) - est.push(np.full((3, 3), -2, dtype=np.float64)) - est.push(np.full((3, 3), -2, dtype=np.float64)) + # Push two more. + est.push(V_J) + est.push(V) - assert np.allclose(est.synchronized_mean(), np.full((3, 3), -1, dtype=np.float64)) + # The resulting synchronized_mean should be V. + assert np.allclose(est.synchronized_mean(), V) From b1deaaaae32765280d9b65e4786234e050201637 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 08:47:21 -0500 Subject: [PATCH 142/424] remove db units arg and related code --- src/aspire/source/image.py | 10 +--------- src/aspire/source/simulation.py | 1 - 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 2eb531cd2a..a89b5a70d1 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -25,7 +25,7 @@ PowerFilter, ) from aspire.storage import MrcStats, StarFile -from aspire.utils import Rotation, grid_2d, ratio_to_decibel, support_mask, trange +from aspire.utils import Rotation, grid_2d, support_mask, trange logger = logging.getLogger(__name__) @@ -966,7 +966,6 @@ def estimate_snr( batch_size=512, noise_power=None, signal_power_method="estimate_signal_mean_energy", - units=None, ): """ Estimate the SNR of the simulated data set using @@ -984,7 +983,6 @@ def estimate_snr( :param signal_power_method: Method used for computing signal energy. Defaults to mean via `estimate_signal_mean_energy`. Can use variance method via `estimate_signal_var`. - :param units: Optionally, convert from default ratio to log scale (`dB`). :returns: Estimated signal to noise ratio. """ @@ -1012,12 +1010,6 @@ def estimate_snr( # `estimate_signal_var` we yield: signal_variance / noise_variance snr = signal_power / noise_power - # Perform any unit conversion - if units == "dB": - snr = ratio_to_decibel(snr) - elif units is not None: - raise ValueError("Units should be `None` or `dB`.") - return snr diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 60e11bf9fc..55a3757cce 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -14,7 +14,6 @@ ainner, anorm, make_symmat, - ratio_to_decibel, trange, uniform_random_angles, vecmat_to_volmat, From c389bf27c12e75c0f894a278a79a65b53e743cdd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 09:55:32 -0500 Subject: [PATCH 143/424] remove psnr and related tests --- src/aspire/source/simulation.py | 54 +-------------------------------- tests/test_noise.py | 25 --------------- 2 files changed, 1 insertion(+), 78 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 55a3757cce..25544c9d9e 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -3,7 +3,6 @@ import numpy as np from scipy.linalg import eigh, qr -from sklearn.metrics import mean_squared_error from aspire.image import Image from aspire.noise import NoiseAdder, WhiteNoiseAdder @@ -481,7 +480,7 @@ def estimate_snr(self, *args, **kwargs): """ # For clean images return infinite SNR. # Note, relationship with CTF and other sim corruptions still isn't clear to me... - if self.noise_adder is None: + if self.noise_adder or self.noise_adder.noise_var == 0: return np.inf # For SNR of Simulations, use the theoretical noise variance @@ -552,54 +551,3 @@ def from_snr( sim.rotations = _rots return sim - - def estimate_psnr(self, sample_n=None, batch_size=512, units=None): - """ - Estimate Peak SNR as max(signal)^2 / MSE, - where MSE is computed between `projections` - and the resulting simulated `images`. - PSNR is computed along the stack axis and an average - value across the stack sample is returned. - Note that PSNR is inherently a poor metric for identical images. - - :param sample_n: Number of images used for estimate. - Defaults to all images in source. - :param units: Optionally, convert from default ratio to log scale (`dB`). - :returns: Estimated peak signal to noise ratio. - """ - - if sample_n is None: - sample_n = self.n - - if sample_n > self.n: - logger.warning( - f"`estimate_psnr` sample_n > Source.n: {sample_n} > {self.n}." - f" Accuracy may be impaired, settting sample_n=self.n={self.n}" - ) - sample_n = self.n - - peaksq = np.empty(sample_n, dtype=self.dtype) - mse = np.empty(sample_n, dtype=self.dtype) - for start in trange(0, sample_n, batch_size): - end = min(start + batch_size, sample_n) - - signal = self.projections[start:end].asnumpy() - images = self.images[start:end].asnumpy() - peaksq[start:end] = np.square(signal).max(axis=(-1, -2)) # max per image - - # Reshape and Transpose for Scikit metrics, - # which expect a 2d (samples, outputs) - mse[start:end] = mean_squared_error( - signal.reshape(sample_n, -1).T, - images.reshape(sample_n, -1).T, - multioutput="raw_values", - ) - - psnr = peaksq / mse - if units == "dB": - psnr = ratio_to_decibel(psnr) - elif units is not None: - raise ValueError("Units should be `None` or `dB`.") - - # Return the mean psnr of the stack. - return np.mean(psnr) diff --git a/tests/test_noise.py b/tests/test_noise.py index 92896a0787..600fc38d3b 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -150,31 +150,6 @@ def pinkish_spectrum(x, y): assert np.isclose(sampled_noise_var, target_noise_variance, rtol=0.1) -@pytest.mark.parametrize("res", RESOLUTIONS, ids=lambda param: f"res={param}") -@pytest.mark.parametrize( - "target_noise_variance", VARS, ids=lambda param: f"var={param}" -) -@pytest.mark.parametrize("dtype", DTYPES, ids=lambda param: f"dtype={param}") -def test_psnr(res, target_noise_variance, dtype): - vol = np.ones((res,) * 3, dtype=dtype) - g = grid_3d(res, normalized=False) - mask = g["r"] > res // 2 - vol[mask] = 0 - - sim = Simulation( - vols=Volume(vol), - n=16, - amplitudes=1, - offsets=0, - dtype=dtype, - noise_adder=WhiteNoiseAdder(var=target_noise_variance), - ) - - psnr = sim.estimate_psnr(units="dB") - logger.debug(f"PSNR target={target_noise_variance} L={sim.L} {sim.dtype} {psnr}") - assert np.isclose(psnr, -10 * np.log10(target_noise_variance), rtol=0.01) - - @pytest.mark.parametrize( "target_noise_variance", VARS, ids=lambda param: f"var={param}" ) From ebafd5d04eff3b4ed006fca48102ede0e92240d5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 12:39:31 -0500 Subject: [PATCH 144/424] Add numpy interop dunder methods to Image --- src/aspire/image/image.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index a344201f9d..18f3785b95 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -122,6 +122,11 @@ def __init__(self, data, dtype=None): self.n_images = np.prod(self.stack_shape) self.resolution = self._data.shape[-1] + # Numpy interop + # https://numpy.org/devdocs/user/basics.interoperability.html#the-array-interface-protocol + self.__array_interface__ = self._data.__array_interface__ + self.__array__ = self._data + @property def res(self): warn( From 2ddee49f292df1e258d350e1ce223c1cab1016aa Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 13:10:16 -0500 Subject: [PATCH 145/424] try resolving capture issue with seperate cell --- gallery/tutorials/lecture_feature_demo.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 47b18fbf6f..5206347fc9 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -354,6 +354,8 @@ def noise_function(x, y): src.images[:10].show() +# %% + orient_est = CLSyncVoting(src, n_theta=36) orient_est.estimate_rotations() rots_est = orient_est.rotations From b9803fa63b1fb2176e30c71f753feb89943ef2e4 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 13:34:53 -0500 Subject: [PATCH 146/424] Image return, Image unit test patches --- src/aspire/covariance/covar.py | 2 +- src/aspire/ctf/ctf_estimator.py | 7 +++---- src/aspire/image/xform.py | 4 +--- src/aspire/noise/noise.py | 9 +++++---- src/aspire/source/coordinates.py | 2 +- src/aspire/source/relion.py | 2 +- src/aspire/source/simulation.py | 4 +--- tests/test_FFBbasis2D.py | 24 ++++++++++++------------ tests/test_FLEbasis2D.py | 2 +- tests/test_coordinate_source.py | 2 +- tests/test_downsample.py | 4 ++-- tests/test_simulation.py | 2 +- tests/test_starfile_stack.py | 4 ++-- tests/test_volume.py | 2 +- 14 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/aspire/covariance/covar.py b/src/aspire/covariance/covar.py index b1dd76f734..25b65290be 100644 --- a/src/aspire/covariance/covar.py +++ b/src/aspire/covariance/covar.py @@ -178,7 +178,7 @@ def src_backward(self, mean_vol, noise_variance, shrink_method=None): (batch_n, self.src.L, self.src.L, self.src.L), dtype=self.dtype ) for j in range(batch_n): - im_centered_b[j] = self.src.im_backward(Image(im_centered[j]), i + j) + im_centered_b[j] = self.src.im_backward(im_centered[j], i + j) im_centered_b = Volume(im_centered_b).to_vec() covar_b += vecmat_to_volmat(im_centered_b.T @ im_centered_b) / self.src.n diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 1f2f151fb7..41c8a0ffad 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -289,7 +289,6 @@ def background_subtract_1d( :param n_low_freq_cutoffs: Low frequency cutoffs (loop iterations). :return: 2-tuple of NumPy arrays (PSD after noise subtraction and estimated noise) """ - # compute radial average center = amplitude_spectrum.shape[-1] // 2 @@ -304,7 +303,7 @@ def background_subtract_1d( f"Invalid ndimension for amplitude_spectrum {amplitude_spectrum.shape}" ) - amplitude_spectrum = amplitude_spectrum[center, center:] + amplitude_spectrum = amplitude_spectrum.asnumpy()[0, center, center:] amplitude_spectrum = amplitude_spectrum[ 0 : 3 * amplitude_spectrum.shape[-1] // 4 ] @@ -839,7 +838,7 @@ def estimate_ctf( os.path.join(output_dir, os.path.splitext(name)[0] + "_noise.mrc"), overwrite=True, ) as mrc: - mrc.set_data(background_2d[0].astype(np.float32)) + mrc.set_data(background_2d.asnumpy()[0].astype(np.float32)) mrc.voxel_size = pixel_size if save_ctf_images: @@ -858,7 +857,7 @@ def estimate_ctf( ) ctf_signal = np.zeros(ctf_im.shape, ctf_im.dtype) ctf_signal[: ctf_im.shape[0] // 2, :] = ctf_im[: ctf_im.shape[0] // 2, :] - ctf_signal[ctf_im.shape[0] // 2 + 1 :, :] = signal[ + ctf_signal[ctf_im.shape[0] // 2 + 1 :, :] = signal.asnumpy()[ :, :, ctf_im.shape[0] // 2 + 1 ] diff --git a/src/aspire/image/xform.py b/src/aspire/image/xform.py index c1960b3392..189bc33ce4 100644 --- a/src/aspire/image/xform.py +++ b/src/aspire/image/xform.py @@ -348,9 +348,7 @@ def _indexed_operation(self, im, indices, which): # Apply the transformation to the selected indices in the Image object if len(im_data_indices) > 0: fn_handle = getattr(xform, which) - im_data[im_data_indices] = fn_handle( - Image(im[im_data_indices]) - ).asnumpy() + im_data[im_data_indices] = fn_handle(im[im_data_indices]).asnumpy() return Image(im_data) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index 4f5097987e..347fed68de 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -44,16 +44,17 @@ def __str__(self): return f"{self.__class__.__name__}" def _forward(self, im, indices): - im = im.copy() + _im = im.asnumpy().copy() for i, idx in enumerate(indices): # Note: The following random seed behavior is directly taken from MATLAB Cov3D code. random_seed = self.seed + 191 * (idx + 1) im_s = randn(2 * im.resolution, 2 * im.resolution, seed=random_seed) - im_s = Image(im_s).filter(self.noise_filter)[0] - im[i] += im_s[: im.resolution, : im.resolution] + # Use numpy because im_s and im are different image sizes + im_s = Image(im_s).filter(self.noise_filter).asnumpy()[0] + _im[i] += im_s[: im.resolution, : im.resolution] - return im + return Image(_im) @abc.abstractproperty def noise_var(self): diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 53c7143a5a..4e2689cb51 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -427,7 +427,7 @@ def _images(self, indices): if self._cached_im is not None: logger.info("Loading images from cache") return self.generation_pipeline.forward( - Image(self._cached_im[indices, :, :]), indices + self._cached_im[indices, :, :], indices ) logger.info(f"Loading {len(indices)} images from micrographs") diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 549511a811..7a6f1bce4b 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -199,7 +199,7 @@ def _images(self, indices): if self._cached_im is not None: logger.debug("Loading images from cache") return self.generation_pipeline.forward( - Image(self._cached_im[indices, :, :]), indices + self._cached_im[indices, :, :], indices ) logger.debug(f"Loading {len(indices)} images from STAR file") diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 49014e5827..ee06f3229c 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -252,9 +252,7 @@ def _images(self, indices, enable_noise=True): # check for cached images first if self._cached_im is not None: logger.debug("Loading images from cache") - return self.generation_pipeline.forward( - Image(self._cached_im[indices, :, :]), indices - ) + return self.generation_pipeline.forward(self._cached_im[indices], indices) im = self.projections[indices] # apply original CTF distortion to image diff --git a/tests/test_FFBbasis2D.py b/tests/test_FFBbasis2D.py index a81962a882..8fda4002d6 100644 --- a/tests/test_FFBbasis2D.py +++ b/tests/test_FFBbasis2D.py @@ -113,19 +113,19 @@ def testRotate(self, basis): v1 = basis.rotate(v1, np.pi / 2) # Evaluate back into cartesian - y1 = basis.evaluate(v1) - y2 = basis.evaluate(v2) - y3 = basis.evaluate(v3) - y4 = basis.evaluate(v4) + y1 = basis.evaluate(v1).asnumpy() + y2 = basis.evaluate(v2).asnumpy() + y3 = basis.evaluate(v3).asnumpy() + y4 = basis.evaluate(v4).asnumpy() # Rotate 90 assert np.allclose(y1[0], y2[0], atol=1e-5) # 2*pi Identity - assert np.allclose(x1[0], y3[0], atol=1e-5) + assert np.allclose(x1.asnumpy()[0], y3[0], atol=1e-5) # Refl (flipped using flipud) - assert np.allclose(np.flipud(x1[0]), y4[0], atol=1e-5) + assert np.allclose(np.flipud(x1.asnumpy()[0]), y4[0], atol=1e-5) def testRotateComplex(self, basis): # Now low res (8x8) had problems; @@ -158,19 +158,19 @@ def testRotateComplex(self, basis): v1 = basis.to_real(basis.complex_rotate(basis.to_complex(v1), np.pi / 2)) # Evaluate back into cartesian - y1 = basis.evaluate(v1) - y2 = basis.evaluate(v2) - y3 = basis.evaluate(v3) - y4 = basis.evaluate(v4) + y1 = basis.evaluate(v1).asnumpy() + y2 = basis.evaluate(v2).asnumpy() + y3 = basis.evaluate(v3).asnumpy() + y4 = basis.evaluate(v4).asnumpy() # Rotate 90 assert np.allclose(y1[0], y2[0], atol=1e-5) # 2*pi Identity - assert np.allclose(x1[0], y3[0], atol=1e-5) + assert np.allclose(x1[0].asnumpy(), y3[0], atol=1e-5) # Refl (flipped using flipud) - assert np.allclose(np.flipud(x1[0]), y4[0], atol=1e-5) + assert np.allclose(np.flipud(x1.asnumpy()[0]), y4[0], atol=1e-5) def testShift(self, basis): """ diff --git a/tests/test_FLEbasis2D.py b/tests/test_FLEbasis2D.py index d17f2e994c..a22d12758b 100644 --- a/tests/test_FLEbasis2D.py +++ b/tests/test_FLEbasis2D.py @@ -261,7 +261,7 @@ def testRotate(): ims_fle_pi = basis.evaluate(coeffs_fle_pi) # test reflection - assert np.allclose(np.flipud(ims[0]), ims_fle_pi[0], atol=1e-4) + assert np.allclose(np.flipud(ims.asnumpy()[0]), ims_fle_pi[0], atol=1e-4) # make sure you can pass in a 1-D array if you want _ = basis.lowpass(np.zeros((basis.count,)), np.pi) diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index b40abd4406..ec0dc05b63 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -493,7 +493,7 @@ def testSave(self): saved_mrcs_stack = mrcfile.open(os.path.join(self.data_folder, mrcs_path)).data # assert that the particles saved are correct for i in range(10): - self.assertTrue(np.array_equal(imgs[i], saved_mrcs_stack[i])) + self.assertTrue(np.array_equal(imgs.asnumpy()[i], saved_mrcs_stack[i])) # assert that the star file has the correct metadata self.assertEqual( saved_star[""].columns.tolist(), diff --git a/tests/test_downsample.py b/tests/test_downsample.py index dfe2f5b5d1..9eaf0c6bbd 100644 --- a/tests/test_downsample.py +++ b/tests/test_downsample.py @@ -56,8 +56,8 @@ def checkCenterPoint(self, data_org, data_ds): # indeterminacy for 3D tolerance = 5e-2 return np.allclose( - data_org[(..., *center_org)], - data_ds[(..., *center_ds)], + data_org.asnumpy()[(..., *center_org)], + data_ds.asnumpy()[(..., *center_ds)], atol=tolerance, ) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 004914f54c..675f985e12 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -190,7 +190,7 @@ def testSimulationImagesShape(self): def testSimulationImagesDownsampleShape(self): self.sim.downsample(6) - first_image = self.sim.images[0][0] + first_image = self.sim.images[0].asnumpy()[0] self.assertEqual(first_image.shape, (6, 6)) def testSimulationEigen(self): diff --git a/tests/test_starfile_stack.py b/tests/test_starfile_stack.py index 50b26aaff9..207139d441 100644 --- a/tests/test_starfile_stack.py +++ b/tests/test_starfile_stack.py @@ -57,7 +57,7 @@ def testMetadata(self): def testImageDownsample(self): self.src.downsample(16) - first_image = self.src.images[0][0] + first_image = self.src.images[0].asnumpy()[0] self.assertEqual(first_image.shape, (16, 16)) def testLegacyStarFile(self): @@ -92,5 +92,5 @@ def tearDown(self): def testMRCSWithOneParticle(self): # tests conversion of 2D numpy arrays into 3D stacks in the case # where there is only one image in the mrcs - single_image = self.src.images[0][0] + single_image = self.src.images[0].asnumpy()[0] self.assertEqual(single_image.shape, (200, 200)) diff --git a/tests/test_volume.py b/tests/test_volume.py index 405131f0d3..0ccc0b7448 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -188,7 +188,7 @@ def testProject(self): for r in range(len(r_stack)): # Get result of test projection at center of Image. - prj_along_axis = img_stack[r][21, 21] + prj_along_axis = img_stack.asnumpy()[r][21, 21] # For Volume, take mean along the axis of rotation. vol_along_axis = np.mean(self.vols_1[vol_id], axis=r % 3) From f86c7b58ad768bdab3e4c487893f9f64e3630844 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 23 Feb 2023 13:35:26 -0500 Subject: [PATCH 147/424] use np.argwhere for equator indices --- src/aspire/abinitio/commonline_cn.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 9313c30fa1..6ce9d60fe9 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -109,12 +109,8 @@ def _estimate_relative_viewing_directions(self): # Remove candidates that are equator images. Equator candidates induce collinear # self common-lines, which always have perfect correlation. # TODO: Should the threshold be parameter-dependent instead of set to 10 degrees? - cii_equators_inds = np.array( - [ - ind - for (ind, Ri_tilde) in enumerate(Ris_tilde) - if abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) < 10 * np.pi / 180 - ] + cii_equators_inds = np.argwhere( + abs(np.arccos(Ris_tilde[:, 2, 2]) - np.pi / 2) < 10 * np.pi / 180 ) scores_self_corrs[:, cii_equators_inds] = 0 From 8ef1b5cc77a356de08795862167ac7350239a4de Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 13:36:55 -0500 Subject: [PATCH 148/424] rm unused import --- src/aspire/covariance/covar.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aspire/covariance/covar.py b/src/aspire/covariance/covar.py index 25b65290be..a34c635a27 100644 --- a/src/aspire/covariance/covar.py +++ b/src/aspire/covariance/covar.py @@ -6,7 +6,6 @@ from scipy.linalg import norm from scipy.sparse.linalg import LinearOperator -from aspire.image import Image from aspire.nufft import anufft from aspire.numeric import fft from aspire.operators import evaluate_src_filters_on_grid From bef1f00aa2593bb684c1b904f0b5b2b9f4134da9 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 23 Feb 2023 15:37:56 -0500 Subject: [PATCH 149/424] resolve deprecation warnings --- src/aspire/ctf/ctf_estimator.py | 6 ++++-- src/aspire/source/relion.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 1f2f151fb7..14336a2f0f 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -279,7 +279,7 @@ def elliptical_average(self, ffbbasis, amplitude_spectrum, circular): return psd, noise def background_subtract_1d( - self, amplitude_spectrum, linprog_method="interior-point", n_low_freq_cutoffs=14 + self, amplitude_spectrum, linprog_method="highs", n_low_freq_cutoffs=14 ): """ Estimate and subtract the background from the power spectrum @@ -760,8 +760,10 @@ def estimate_ctf( # Optionally changing to: linprog_method='simplex', # will more deterministically repro results in exchange for speed. + # linprog_method was changed from 'interior-point' to 'highs' due to + # "interior-point' being deprecated. signal_1d, background_1d = ctf_object.background_subtract_1d( - amplitude_spectrum, linprog_method="interior-point" + amplitude_spectrum, linprog_method="highs" ) avg_defocus, low_freq_skip = ctf_object.opt1d( diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 549511a811..845ffd8a16 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -166,7 +166,7 @@ def populate_metadata(self): # so we can get the particle's index in the .mrcs stack as an int metadata[["__mrc_index", "__mrc_filename"]] = metadata[ "_rlnImageName" - ].str.split("@", 1, expand=True) + ].str.split("@", n=1, expand=True) # __mrc_index corresponds to the integer index of the particle in the __mrc_filename stack # Note that this is 1-based indexing metadata["__mrc_index"] = pd.to_numeric(metadata["__mrc_index"]) From 69f738d2bc66d3c676a8f269a028ffce537f5d0f Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 23 Feb 2023 16:07:12 -0500 Subject: [PATCH 150/424] docstring. equator_threshold parameter. --- src/aspire/abinitio/commonline_cn.py | 28 +++++++++++++++++++++++++++- tests/test_orient_symmetric.py | 6 ++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 6ce9d60fe9..3e6ce6f07b 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -11,6 +11,11 @@ class CLSymmetryCn(CLSymmetryC3C4): + """ + Define a class to estimate 3D orientations using common lines methods for molecules with + Cn cyclic symmetry, with n>4. + """ + def __init__( self, src, @@ -23,8 +28,27 @@ def __init__( max_iters=1000, degree_res=1, n_points_sphere=500, + equator_threshold=10, seed=None, ): + """ + Initialize object for estimating 3D orientations for molecules with Cn symmetry, n>4. + + :param src: The source object of 2D denoised or class-averaged images with metadata + :param symmetry: A string, 'Cn', indicating the symmetry type. + :param n_rad: The number of points in the radial direction. + :param n_theta: The number of points in the theta direction. + :param max_shift: Maximum range for shifts as a proportion of resolution. Default = 0.15. + :param shift_step: Resolution of shift estimation in pixels. Default = 1 pixel. + :param epsilon: Tolerance for the power method. + :param max_iter: Maximum iterations for the power method. + :param degree_res: Degree resolution for estimating in-plane rotations. + :param n_points_sphere: The number of candidate rotations used to estimate viewing directions. + :param equator_threshold: Threshold for removing candidate rotations within `equator_threshold` + degrees of being an equator image. Default is 10 degrees. + :param seed: Optional seed for RNG. + """ + super().__init__( src, symmetry=symmetry, @@ -39,6 +63,7 @@ def __init__( ) self.n_points_sphere = n_points_sphere + self.equator_threshold = equator_threshold def _check_symmetry(self, symmetry): if symmetry is None: @@ -110,7 +135,8 @@ def _estimate_relative_viewing_directions(self): # self common-lines, which always have perfect correlation. # TODO: Should the threshold be parameter-dependent instead of set to 10 degrees? cii_equators_inds = np.argwhere( - abs(np.arccos(Ris_tilde[:, 2, 2]) - np.pi / 2) < 10 * np.pi / 180 + abs(np.arccos(Ris_tilde[:, 2, 2]) - np.pi / 2) + < self.equator_threshold * np.pi / 180 ) scores_self_corrs[:, cii_equators_inds] = 0 diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index c18f1d1a53..cd589f3088 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -89,7 +89,8 @@ def test_estimate_rotations(L, order, dtype): # So we exclude those from testing. if order > 4: equator_inds = np.argwhere( - abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) < 10 * np.pi / 180 + abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) + < cl_symm.equator_threshold * np.pi / 180 ) # Exclude equator estimates and ground truths. @@ -253,7 +254,8 @@ def test_relative_viewing_directions(L, order, dtype): # So we exclude those from testing. if order > 4: equator_inds = np.argwhere( - abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) < 10 * np.pi / 180 + abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) + < cl_symm.equator_threshold * np.pi / 180 ) # Exclude ii estimates and ground truths. From f7ed97fe7a5ffce94b30bf34d8d20f5ef95bd139 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 06:38:57 -0500 Subject: [PATCH 151/424] Try windows server 2019 instead of latest (2022) --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 8de9cbb9ce..f2b9f0445a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -64,7 +64,7 @@ jobs: shell: bash -el {0} strategy: matrix: - os: [ubuntu-latest, ubuntu-20.04, macOS-latest, macOS-11, windows-latest] + os: [ubuntu-latest, ubuntu-20.04, macOS-latest, macOS-11, windows-2019] backend: [default, openblas] python-version: ['3.8'] include: From c832e4e0746d3aeee493be33edae7cdb36c1e5a6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 07:32:29 -0500 Subject: [PATCH 152/424] Volume return unit test patches (asnumpy) --- src/aspire/reconstruction/kernel.py | 5 +++++ src/aspire/source/simulation.py | 2 +- src/aspire/volume/volume.py | 30 +++++++++++++++++++++++++---- tests/test_mean_estimator.py | 2 +- tests/test_simulation.py | 4 ++-- tests/test_synthetic_volume.py | 4 ++-- tests/test_volume.py | 6 +++--- 7 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/aspire/reconstruction/kernel.py b/src/aspire/reconstruction/kernel.py index 0718153d1a..ddc887ef8e 100644 --- a/src/aspire/reconstruction/kernel.py +++ b/src/aspire/reconstruction/kernel.py @@ -5,6 +5,7 @@ from aspire.numeric import fft from aspire.utils import roll_dim, unroll_dim, vec_to_vol, vecmat_to_volmat, vol_to_vec from aspire.utils.matlab_compat import m_reshape +from aspire.volume import Volume logger = logging.getLogger(__name__) @@ -80,6 +81,10 @@ def convolve_volume(self, x): :param x: An N-by-N-by-N-by-... array of volumes to be convolved. :return: The original volumes convolved by the kernel with the same dimensions as before. """ + # Note, we can do better here, but some changes in `wts` already... + if isinstance(x, Volume): + x = x.asnumpy()[0] + N = x.shape[0] kernel_f = self.kernel[..., np.newaxis] N_ker = kernel_f.shape[0] diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index ee06f3229c..26905fd221 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -339,7 +339,7 @@ def eigs(self): # Arrange in descending order (flip column order in eigenvector matrix) w = w[::-1] - eigs_true = Volume(eigs_true[::-1]) + eigs_true = Volume(eigs_true.asnumpy()[::-1]) return eigs_true, np.diag(w) diff --git a/src/aspire/volume/volume.py b/src/aspire/volume/volume.py index 35b220e1d4..7539cde93e 100644 --- a/src/aspire/volume/volume.py +++ b/src/aspire/volume/volume.py @@ -27,7 +27,7 @@ def qr_vols_forward(sim, s, n, vols, k): """ ims = np.zeros((k, n, sim.L, sim.L), dtype=vols.dtype) for ell in range(k): - ims[ell] = sim.vol_forward(Volume(vols[ell]), s, n).asnumpy() + ims[ell] = sim.vol_forward(vols[ell], s, n).asnumpy() ims = np.swapaxes(ims, 1, 3) ims = np.swapaxes(ims, 0, 2) @@ -96,6 +96,11 @@ def __init__(self, data, dtype=None): self.n_vols = np.prod(self.stack_shape) self.resolution = self._data.shape[-1] + # Numpy interop + # https://numpy.org/devdocs/user/basics.interoperability.html#the-array-interface-protocol + self.__array_interface__ = self._data.__array_interface__ + self.__array__ = self._data + def asnumpy(self): """ Return volume as a (n_vols, resolution, resolution, resolution) array. @@ -123,7 +128,7 @@ def _check_key_dims(self, key): def __getitem__(self, key): self._check_key_dims(key) - return self._data[key] + return Volume(self._data[key]) def __setitem__(self, key, value): self._check_key_dims(key) @@ -196,6 +201,23 @@ def __mul__(self, other): def __rmul__(self, otherL): return self * otherL + def __truediv__(self, other): + """ + Scalar division, follows numpy semantics. + """ + if isinstance(other, Volume): + res = Volume(self._data / other.asnumpy()) + else: + res = Volume(self._data / other) + + return res + + def __rtruedive__(self, otherL): + """ + Right scalar division, follows numpy semantics. + """ + return self / otherL + def project(self, vol_idx, rot_matrices): """ Using the stack of rot_matrices, @@ -223,7 +245,7 @@ def project(self, vol_idx, rot_matrices): " In the future this will raise an error." ) - data = self[vol_idx] + data = self[vol_idx].asnumpy() n = rot_matrices.shape[0] @@ -403,7 +425,7 @@ def rotate(self, rot_matrices, zero_nyquist=True): for i in range(K): pts_rot[i] = rotated_grids_3d(self.resolution, rot_matrices[i]) - vol_f[i] = nufft(self[i], pts_rot[i]) + vol_f[i] = nufft(self[i].asnumpy(), pts_rot[i]) vol_f = vol_f.reshape(-1, self.resolution, self.resolution, self.resolution) diff --git a/tests/test_mean_estimator.py b/tests/test_mean_estimator.py index 5a71eff0ef..73a815ea06 100644 --- a/tests/test_mean_estimator.py +++ b/tests/test_mean_estimator.py @@ -52,7 +52,7 @@ def testEstimate(self): estimate = self.estimator.estimate() self.assertTrue( np.allclose( - estimate[0][:, :, 4], + estimate.asnumpy()[0][:, :, 4], [ [ +0.00000000, diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 675f985e12..a32982d30d 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -197,7 +197,7 @@ def testSimulationEigen(self): eigs_true, lambdas_true = self.sim.eigs() self.assertTrue( np.allclose( - eigs_true[0, :, :, 2], + eigs_true.asnumpy()[0, :, :, 2], np.array( [ [ @@ -371,7 +371,7 @@ def testSimulationMean(self): 0.01210055, ], ], - mean_vol[0, :, :, 4], + mean_vol.asnumpy()[0, :, :, 4], ) ) diff --git a/tests/test_synthetic_volume.py b/tests/test_synthetic_volume.py index f78d1f58a6..57fdc25116 100644 --- a/tests/test_synthetic_volume.py +++ b/tests/test_synthetic_volume.py @@ -115,8 +115,8 @@ def test_compact_support(vol_fixture): vol = vol_fixture.generate() # Check that volume is zero outside of support and positive inside. - assert vol[0][outside].all() == 0 - assert (vol[0][inside] > 0).all() + assert vol.asnumpy()[0][outside].all() == 0 + assert (vol.asnumpy()[0][inside] > 0).all() def test_volume_symmetry(vol_fixture): diff --git a/tests/test_volume.py b/tests/test_volume.py index 0ccc0b7448..dee6ed45de 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -191,7 +191,7 @@ def testProject(self): prj_along_axis = img_stack.asnumpy()[r][21, 21] # For Volume, take mean along the axis of rotation. - vol_along_axis = np.mean(self.vols_1[vol_id], axis=r % 3) + vol_along_axis = np.mean(self.vols_1.asnumpy()[vol_id], axis=r % 3) # Volume is uncentered, take the mean of a 2x2 window. vol_along_axis = np.mean(vol_along_axis[20:22, 20:22]) @@ -359,8 +359,8 @@ def testDownsample(self): # check gridpoints self.assertTrue( np.allclose( - vols[:, res // 2, res // 2, res // 2], - result[:, ds_res // 2, ds_res // 2, ds_res // 2], + vols.asnumpy()[:, res // 2, res // 2, res // 2], + result.asnumpy()[:, ds_res // 2, ds_res // 2, ds_res // 2], atol=1e-4, ) ) From 42969cca0e74902e0405532fed5bae7843169f59 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 07:43:33 -0500 Subject: [PATCH 153/424] Replace return Volume~~>self.__class__ --- src/aspire/volume/volume.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/aspire/volume/volume.py b/src/aspire/volume/volume.py index 7539cde93e..2b473b9f1f 100644 --- a/src/aspire/volume/volume.py +++ b/src/aspire/volume/volume.py @@ -118,7 +118,7 @@ def astype(self, dtype, copy=True): Defaults to True. :return: Volume instance """ - return Volume(self.asnumpy().astype(dtype, copy=copy)) + return self.__class__(self.asnumpy().astype(dtype, copy=copy)) def _check_key_dims(self, key): if isinstance(key, tuple) and (len(key) > self._data.ndim): @@ -128,7 +128,7 @@ def _check_key_dims(self, key): def __getitem__(self, key): self._check_key_dims(key) - return Volume(self._data[key]) + return self.__class__(self._data[key]) def __setitem__(self, key, value): self._check_key_dims(key) @@ -156,7 +156,7 @@ def stack_reshape(self, *args): f"Number of volumes {self.n_vols} cannot be reshaped to {shape}." ) - return Volume(self._data.reshape(*shape, *self._data.shape[-3:])) + return self.__class__(self._data.reshape(*shape, *self._data.shape[-3:])) def __repr__(self): msg = ( @@ -170,9 +170,9 @@ def __len__(self): def __add__(self, other): if isinstance(other, Volume): - res = Volume(self._data + other.asnumpy()) + res = self.__class__(self._data + other.asnumpy()) else: - res = Volume(self._data + other) + res = self.__class__(self._data + other) return res @@ -181,20 +181,20 @@ def __radd__(self, otherL): def __sub__(self, other): if isinstance(other, Volume): - res = Volume(self._data - other.asnumpy()) + res = self.__class__(self._data - other.asnumpy()) else: - res = Volume(self._data - other) + res = self.__class__(self._data - other) return res def __rsub__(self, otherL): - return Volume(otherL - self._data) + return self.__class__(otherL - self._data) def __mul__(self, other): if isinstance(other, Volume): - res = Volume(self._data * other.asnumpy()) + res = self.__class__(self._data * other.asnumpy()) else: - res = Volume(self._data * other) + res = self.__class__(self._data * other) return res @@ -206,9 +206,9 @@ def __truediv__(self, other): Scalar division, follows numpy semantics. """ if isinstance(other, Volume): - res = Volume(self._data / other.asnumpy()) + res = self.__class__(self._data / other.asnumpy()) else: - res = Volume(self._data / other) + res = self.__class__(self._data / other) return res @@ -289,7 +289,7 @@ def from_vec(vec): data = vec.reshape((n_vols, resolution, resolution, resolution)) - return Volume(data) + return self.__class__(data) def transpose(self): """ @@ -300,7 +300,7 @@ def transpose(self): original_stack_shape = self.stack_shape v = self.stack_reshape(-1) vt = np.transpose(v._data, (0, -1, -2, -3)) - return Volume(vt).stack_reshape(original_stack_shape) + return self.__class__(vt).stack_reshape(original_stack_shape) @property def T(self): @@ -341,7 +341,7 @@ def flip(self, axis=-3): f"Cannot flip axis {ax}: stack axis. Did you mean {ax-4}?" ) - return Volume(np.flip(self._data, axis)) + return self.__class__(np.flip(self._data, axis)) def downsample(self, ds_res, mask=None): """ @@ -368,7 +368,7 @@ def downsample(self, ds_res, mask=None): ds_res**3 / self.resolution**3 ) # returns a new Volume object - return Volume(np.real(out)).stack_reshape(original_stack_shape) + return self.__class__(np.real(out)).stack_reshape(original_stack_shape) def shift(self): raise NotImplementedError @@ -439,7 +439,7 @@ def rotate(self, rot_matrices, zero_nyquist=True): np.real(fft.centered_ifftn(xp.asarray(vol_f), axes=(-3, -2, -1))) ) - return Volume(vol) + return self.__class__(vol) def denoise(self): raise NotImplementedError @@ -480,7 +480,7 @@ def load(filename, permissive=True, dtype=np.float32): loaded_data = mrc.data if loaded_data.dtype != dtype: logger.info(f"{filename} with dtype {loaded_data.dtype} loaded as {dtype}") - return Volume(loaded_data.astype(dtype)) + return self.__class__(loaded_data.astype(dtype)) class CartesianVolume(Volume): From 3ff34970d437fb87f89420b534502f5570452f92 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 07:47:25 -0500 Subject: [PATCH 154/424] Class method --- src/aspire/volume/volume.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aspire/volume/volume.py b/src/aspire/volume/volume.py index 2b473b9f1f..1d32d888bd 100644 --- a/src/aspire/volume/volume.py +++ b/src/aspire/volume/volume.py @@ -270,8 +270,8 @@ def to_vec(self): """Returns an N x resolution ** 3 array.""" return self._data.reshape((self.n_vols, self.resolution**3)) - @staticmethod - def from_vec(vec): + @classmethod + def from_vec(cls, vec): """ Returns a Volume instance from a (N, resolution**3) array or (resolution**3) array. @@ -289,7 +289,7 @@ def from_vec(vec): data = vec.reshape((n_vols, resolution, resolution, resolution)) - return self.__class__(data) + return cls(data) def transpose(self): """ @@ -464,8 +464,8 @@ def save(self, filename, overwrite=False): if self.dtype != np.float32: logger.info(f"Volume with dtype {self.dtype} saved with dtype float32") - @staticmethod - def load(filename, permissive=True, dtype=np.float32): + @classmethod + def load(cls, filename, permissive=True, dtype=np.float32): """ Load an mrc file as a Volume instance. @@ -480,7 +480,7 @@ def load(filename, permissive=True, dtype=np.float32): loaded_data = mrc.data if loaded_data.dtype != dtype: logger.info(f"{filename} with dtype {loaded_data.dtype} loaded as {dtype}") - return self.__class__(loaded_data.astype(dtype)) + return cls(loaded_data.astype(dtype)) class CartesianVolume(Volume): From e8cd3c86cd974294950fb6e36e89c03166dd3041 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 13 Feb 2023 13:21:49 -0500 Subject: [PATCH 155/424] initial stubs for a base ClassAvgSource [skip ci] --- src/aspire/classification/class2d.py | 9 -- src/aspire/classification/rir_class2d.py | 60 +-------- src/aspire/denoising/class_avg.py | 161 +++++++++++++++++++++-- 3 files changed, 152 insertions(+), 78 deletions(-) diff --git a/src/aspire/classification/class2d.py b/src/aspire/classification/class2d.py index 1542c12ac3..e18f40febd 100644 --- a/src/aspire/classification/class2d.py +++ b/src/aspire/classification/class2d.py @@ -15,7 +15,6 @@ def __init__( self, src, n_nbor=100, - n_classes=50, seed=None, dtype=None, ): @@ -24,7 +23,6 @@ def __init__( :param src: ImageSource or subclass, provides images. :param n_nbor: Number of nearest neighbors to compute. - :param n_classes: Number of class averages to return. :param seed: Optional RNG seed to be passed to random methods, (example Random NN). :param dtype: Numpy dtype, defaults to `src.dtype`. """ @@ -40,7 +38,6 @@ def __init__( self.dtype = self.src.dtype self.n_nbor = n_nbor - self.n_classes = n_classes self.seed = seed @abstractmethod @@ -50,9 +47,3 @@ def classify(self): Returns classes and associated metadata (classes, reflections, distances) """ - - @abstractmethod - def averages(self, classes, refl, distances): - """ - Returns class averages. - """ diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index e1ce9dae60..7739fcbd28 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -5,12 +5,7 @@ from sklearn.neighbors import NearestNeighbors from aspire.basis import FSPCABasis -from aspire.classification import ( - BFSReddyChatterjiAverager2D, - Class2D, - ClassSelector, - RandomClassSelector, -) +from aspire.classification import Class2D from aspire.classification.legacy_implementations import bispec_2drot_large, pca_y from aspire.numeric import ComplexPCA from aspire.utils import trange @@ -29,15 +24,11 @@ def __init__( sample_n=4000, bispectrum_components=300, n_nbor=100, - n_classes=50, bispectrum_freq_cutoff=None, large_pca_implementation="legacy", nn_implementation="legacy", bispectrum_implementation="legacy", - selector=None, - num_procs=None, batch_size=512, - averager=None, dtype=None, seed=None, ): @@ -54,8 +45,7 @@ def __init__( Z. Zhao, Y. Shkolnisky, A. Singer, Rotationally Invariant Image Representation for Viewing Direction Classification in Cryo-EM. (2014) - :param src: Source instance. Note it is possible to use one `source` for classification (ie CWF), - and a different `source` for stacking in the `averager`. + :param src: Source instance, for classification. :param pca_basis: Optional FSPCA Basis instance :param fspca_components: Optinally set number of components (top eigvals) to keep from full FSCPA. Default value of None will infer from `pca_basis` when provided, otherwise defaults to 400. @@ -63,16 +53,11 @@ def __init__( :param sample_n: Threshold for random sampling of bispectrum coefs. Default 4000, high values such as 50000 reduce random sampling. :param n_nbor: Number of nearest neighbors to compute. - :param n_classes: Number of class averages to return. :param bispectrum_freq_cutoff: Truncate (zero) high k frequecies above (int) value, defaults off (None). :param large_pca_implementation: See `pca`. :param nn_implementation: See `nn_classification`. :param bispectrum_implementation: See `bispectrum`. - :param selector: A ClassSelector subclass. Defaults to RandomClassSelector. - :param num_procs: Number of processes to use. - `None` will attempt computing a suggestion based on machine resources. :param batch_size: Chunk size (typically number of images) for batched methods. - :param averager: An Averager2D subclass. Defaults to BFSReddyChatterjiAverager2D. :param dtype: Optional dtype, otherwise taken from src. :param seed: Optional RNG seed to be passed to random methods, (example Random NN). :return: RIRClass2D instance to be used to compute bispectrum-like rotationally invariant 2D classification. @@ -81,11 +66,9 @@ def __init__( super().__init__( src=src, n_nbor=n_nbor, - n_classes=n_classes, seed=seed, dtype=dtype, ) - self.num_procs = num_procs self.batch_size = int(batch_size) # Implementation Checks @@ -129,13 +112,6 @@ def __init__( ) self._bispectrum = bispectrum_implementations[bispectrum_implementation] - # Setup class selection - if selector is None: - selector = RandomClassSelector(seed=self.seed) - elif not isinstance(selector, ClassSelector): - raise RuntimeError("`selector` must be subclass of `ClassSelector`") - self.selector = selector - # For now, only run with FSPCA basis if pca_basis and not isinstance(pca_basis, FSPCABasis): raise NotImplementedError( @@ -175,7 +151,6 @@ def __init__( self.sample_n = sample_n self.alpha = alpha self.bispectrum_freq_cutoff = bispectrum_freq_cutoff - self.averager = averager if self.src.n < self.bispectrum_components: raise RuntimeError( @@ -202,21 +177,6 @@ def classify(self, diagnostics=False): # For convenience, assign the fb_basis used in the pca_basis. self.fb_basis = self.pca_basis.basis - # When not provided by a user, the averager is instantiated after - # we are certain our pca_basis has been constructed. - if self.averager is None: - self.averager = BFSReddyChatterjiAverager2D( - self.fb_basis, self.src, num_procs=self.num_procs, dtype=self.dtype - ) - else: - # When user provides `averager` and `num_procs` - # we should warn when `num_procs` mismatched. - if self.num_procs is not None and self.averager.num_procs != self.num_procs: - logger.warning( - f"{self.__class__.__name__} intialized with num_procs={self.num_procs} does not" - f" match provided {self.averager.__class__.__name__}.{self.averager.num_procs}" - ) - # Get the expanded coefs in the compressed FSPCA space. self.fspca_coef = self.pca_basis.spca_coef @@ -241,22 +201,6 @@ def classify(self, diagnostics=False): return classes, reflections, distances - def averages(self, classes, reflections, distances): - # # Stage 3: Class Selection - # This is an area open to active research. - logger.info(f"Select {self.n_classes} Classes from Nearest Neighbors") - self.selection = selection = self.selector.select( - self.n_classes, classes, reflections, distances - ) - classes, reflections = classes[selection], reflections[selection] - - # # Stage 4: Averager - logger.info( - f"Begin Averaging of {classes.shape[0]} Classes using {self.averager}." - ) - - return self.averager.average(classes, reflections) - def pca(self, M): """ Any PCA implementation here should return both diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 54672aa75d..cffaa4ec5d 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -1,26 +1,165 @@ import logging -from aspire.denoising import Denoiser +from aspire.classification import Class2D, ClassSelector +from aspire.source import ImageSource logger = logging.getLogger(__name__) -class ClassAvg(Denoiser): +class ClassAvgSource(ImageSource): """ - Define a derived class for denoising 2D images using class average methods + Source for denoised 2D images using class average methods. """ - def __init__(self, img_src, class_index): + def __init__( + self, + classification_src, + classifier, + class_selector, + averager, + registration_src=None, + ): """ - constructor of an object for denoising 2D images using class averaging method. + Constructor of an object for denoising 2D images using class averaging methods. + + :param classification_src: Source used for image classification. + :param classifier: Class2D subclass used for image classification. + Example, RIRClass2D. + :param class_selector: A ClassSelector subclass. + :param averager: An Averager2D subclass. + :param registration_src: Optional, Source used for image registration and averaging. + Defaults to `classification_src`. """ - pass + self.classification_src = classification_src + if not isinstance(self.classification_src, ImageSource): + raise ValueError( + f"`classification_src` should be subclass of `ImageSource`, found {self.classification_src}." + ) + + self.classifier = classifier + if not isinstance(self.classifier, Class2D): + raise ValueError( + f"`classifier` should be subclass of `Class2D`, found {self.classifier}." + ) + + self.class_selector = class_selector + if not isinstance(self.class_selector, ClassSelector): + raise ValueError( + f"`class_selector` should be instance of `ClassSelector`, found {class_selector}." + ) + + self.registration_src = registration_src + if self.registration_src is None: + self.registration_src = self.classification_src + if not isinstance(self.registration_src, ImageSource): + raise ValueError( + f"`registration_src` should be subclass of `ImageSource`, found {self.registration_src}." + ) - def class_averaging(self): - pass + self._nn_classes = None + self._nn_reflections = None + self._nn_distances = None - def output_images(self): + # Flag for lazy eval, we'll classify once, on first touch. + # We could use self._nn_* vars, but might lose some flexibility later. + self._classified = False + self._selected = False + + # Note n will potentially be updated after class selection. + super().__init__( + L=self.registration_src.L, + n=self.registration_src.n, + dtype=self.registration_src.dtype, + ) + + def _classify(self): """ - Output the clean images + Perform the image classification (if not already done). """ - pass + + # Short circuit + if self._classified: + logger.debug(f"{self.__class__.__name__} already classified, skipping") + return + + ( + self._nn_classes, + self._nn_reflections, + self._nn_distances, + ) = self.classifier.classify() + self._classified = True + + def _class_select(self): + """ + Uses the `class_selector` in conjunction with the classifier results + to select the classes (and order) used for image registration, + if not already done. + """ + + # Short circuit + if self._selected: + logger.debug( + f"{self.__class__.__name__} already selected classes, skipping" + ) + return + + # Evaluate the classification network. + if not self._classified: + self.classify() + + # Perform class selection + self.selection_indices = self.selector.select( + self._nn_classes, self._nn_reflections, self._nn_distances + ) + + # Override the initial self.n + self.n = len(self.selection_indices) + + self._selected = True + + def _images(self, indices): + """ + Output images + """ + + # Lazy evaluate the class selection + if not self._selected: + self._class_select() + + # check for cached images first + if self._cached_im is not None: + logger.debug("Loading images from cache") + im = Image(self._cached_im[indices, :, :]) + else: + # Perform image registration for the requested classes + im = self.registration(classes[indices], reflections[indices]) + + # Finally, apply transforms to resulting Image + return self.generation_pipeline.forward(im, indices) + + +# # Legacy +# :param num_procs: Number of processes to use. +# `None` will attempt computing a suggestion based on machine resources. +# # Setup class selection +# if selector is None: +# selector = RandomClassSelector(seed=self.seed) +# elif not isinstance(selector, ClassSelector): +# raise RuntimeError("`selector` must be subclass of `ClassSelector`") +# self.selector = selector + +# self.averager = averager +# # When not provided by a user, the averager is instantiated after +# # we are certain our pca_basis has been constructed. +# if self.averager is None: +# self.averager = BFSReddyChatterjiAverager2D( +# self.fb_basis, self.src, num_procs=self.num_procs, dtype=self.dtype +# ) +# else: +# # When user provides `averager` and `num_procs` +# # we should warn when `num_procs` mismatched. +# if self.num_procs is not None and self.averager.num_procs != self.num_procs: +# logger.warning( +# f"{self.__class__.__name__} intialized with num_procs={self.num_procs} does not" +# f" match provided {self.averager.__class__.__name__}.{self.averager.num_procs}" +# ) From 844c1f8dc484ea4b9d0da3999c2befbeedb8d24d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 13 Feb 2023 15:49:19 -0500 Subject: [PATCH 156/424] trim unit test --- tests/test_class2D.py | 159 +++++++++++++++++++----------------------- 1 file changed, 73 insertions(+), 86 deletions(-) diff --git a/tests/test_class2D.py b/tests/test_class2D.py index 4b2703464b..ae1069c30e 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -220,17 +220,13 @@ def testRIRLegacy(self): rir = RIRClass2D( self.clean_src, clean_fspca_basis, - n_classes=5, bispectrum_components=42, large_pca_implementation="legacy", nn_implementation="legacy", bispectrum_implementation="legacy", - selector=TopClassSelector(), - num_procs=1 if xfail_ray_dev() else 2, ) - classification_results = rir.classify() - _ = rir.averages(*classification_results) + _ = rir.classify() def testRIRDevelBisp(self): """ @@ -245,7 +241,6 @@ def testRIRDevelBisp(self): large_pca_implementation="legacy", nn_implementation="legacy", bispectrum_implementation="devel", - num_procs=1 if xfail_ray_dev() else 2, ) _ = rir.classify() @@ -262,20 +257,12 @@ def testRIRsk(self): self.noisy_fspca_basis, bispectrum_components=100, sample_n=42, - n_classes=self.n_classes, large_pca_implementation="sklearn", nn_implementation="sklearn", bispectrum_implementation="devel", - averager=BFRAverager2D( - self.noisy_fspca_basis.basis, # FFB basis - self.noisy_src, - n_angles=100, - num_procs=1, - ), ) - classification_results = rir.classify() - _ = rir.averages(*classification_results) + _ = rir.classify() def testEigenImages(self): """ @@ -362,77 +349,77 @@ def testImplementations(self): ): _ = RIRClass2D(self.clean_src, self.basis) - def testSelectionImplementations(self): - """ - Test optional implementations handle bad inputs with a descriptive error. - """ - - class CustomClassSelector(ClassSelector): - def __init__(self, x): - self.x = x - - def _select(self, n, classes, reflections, distances): - return self.x - - # lower bound - with pytest.raises(ValueError, match=r".*out of bounds.*"): - rir = RIRClass2D( - self.clean_src, - self.clean_fspca_basis, - n_classes=self.n_classes, - bispectrum_components=self.clean_fspca_basis.components - 1, - selector=CustomClassSelector(np.arange(self.n_classes) - 1), - ) - _ = rir.averages(*rir.classify()) - - # upper bound - with pytest.raises(ValueError, match=r".*out of bounds.*"): - rir = RIRClass2D( - self.clean_src, - self.clean_fspca_basis, - n_classes=self.n_classes, - bispectrum_components=self.clean_fspca_basis.components - 1, - selector=CustomClassSelector( - np.arange(self.n_classes) + self.clean_src.n - ), - ) - _ = rir.averages(*rir.classify()) - - # too short - with pytest.raises(ValueError, match=r".*must be len.*"): - rir = RIRClass2D( - self.clean_src, - self.clean_fspca_basis, - n_classes=self.n_classes, - bispectrum_components=self.clean_fspca_basis.components - 1, - selector=CustomClassSelector(np.arange(self.n_classes - 1)), - ) - _ = rir.averages(*rir.classify()) - - # too long - with pytest.raises(ValueError, match=r".*must be len.*"): - rir = RIRClass2D( - self.clean_src, - self.clean_fspca_basis, - n_classes=self.n_classes, - bispectrum_components=self.clean_fspca_basis.components - 1, - selector=CustomClassSelector(np.arange(self.n_classes + 1)), - ) - _ = rir.averages(*rir.classify()) - - def testIncorrectSelectorClass(self): - """ - Test passing incorect ClassSelector raises with a descriptive error. - """ - - with pytest.raises(RuntimeError, match=r".*must be subclass of.*"): - rir = RIRClass2D( - self.clean_src, - self.clean_fspca_basis, - n_classes=self.n_classes, - selector=range(self.n_classes), - ) - _ = rir.averages(*rir.classify()) + # def testSelectionImplementations(self): + # """ + # Test optional implementations handle bad inputs with a descriptive error. + # """ + + # class CustomClassSelector(ClassSelector): + # def __init__(self, x): + # self.x = x + + # def _select(self, n, classes, reflections, distances): + # return self.x + + # # lower bound + # with pytest.raises(ValueError, match=r".*out of bounds.*"): + # rir = RIRClass2D( + # self.clean_src, + # self.clean_fspca_basis, + # n_classes=self.n_classes, + # bispectrum_components=self.clean_fspca_basis.components - 1, + # selector=CustomClassSelector(np.arange(self.n_classes) - 1), + # ) + # _ = rir.averages(*rir.classify()) + + # # upper bound + # with pytest.raises(ValueError, match=r".*out of bounds.*"): + # rir = RIRClass2D( + # self.clean_src, + # self.clean_fspca_basis, + # n_classes=self.n_classes, + # bispectrum_components=self.clean_fspca_basis.components - 1, + # selector=CustomClassSelector( + # np.arange(self.n_classes) + self.clean_src.n + # ), + # ) + # _ = rir.averages(*rir.classify()) + + # # too short + # with pytest.raises(ValueError, match=r".*must be len.*"): + # rir = RIRClass2D( + # self.clean_src, + # self.clean_fspca_basis, + # n_classes=self.n_classes, + # bispectrum_components=self.clean_fspca_basis.components - 1, + # selector=CustomClassSelector(np.arange(self.n_classes - 1)), + # ) + # _ = rir.averages(*rir.classify()) + + # # too long + # with pytest.raises(ValueError, match=r".*must be len.*"): + # rir = RIRClass2D( + # self.clean_src, + # self.clean_fspca_basis, + # n_classes=self.n_classes, + # bispectrum_components=self.clean_fspca_basis.components - 1, + # selector=CustomClassSelector(np.arange(self.n_classes + 1)), + # ) + # _ = rir.averages(*rir.classify()) + + # def testIncorrectSelectorClass(self): + # """ + # Test passing incorect ClassSelector raises with a descriptive error. + # """ + + # with pytest.raises(RuntimeError, match=r".*must be subclass of.*"): + # rir = RIRClass2D( + # self.clean_src, + # self.clean_fspca_basis, + # n_classes=self.n_classes, + # selector=range(self.n_classes), + # ) + # _ = rir.averages(*rir.classify()) class LegacyImplementationTestCase(TestCase): From 15158d2f2657806b6ec9cc239e44885334228443 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Feb 2023 15:30:01 -0500 Subject: [PATCH 157/424] excercise the mvp ClassAvgSource --- gallery/tutorials/class_averaging.py | 53 ++++++++++++++++++------- gallery/tutorials/pipeline_demo.py | 38 +++++++++++++++--- src/aspire/classification/averager2d.py | 5 +-- src/aspire/denoising/__init__.py | 1 + src/aspire/denoising/class_avg.py | 47 ++++++++++++++-------- tests/test_class2D.py | 9 +---- tox.ini | 4 +- 7 files changed, 107 insertions(+), 50 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 4d968dbb3a..5a48499b9d 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -11,7 +11,13 @@ import numpy as np from PIL import Image as PILImage -from aspire.classification import RIRClass2D, TopClassSelector +from aspire.basis import FFBBasis2D +from aspire.classification import ( + BFSReddyChatterjiAverager2D, + RIRClass2D, + TopClassSelector, +) +from aspire.denoising import ClassAvgSource from aspire.image import Image from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. @@ -103,21 +109,31 @@ # # We use the ASPIRE ``RIRClass2D`` class to classify the images via the rotationally invariant representation (RIR) # algorithm. We then yield class averages by performing ``classify``. +n_classes = 10 rir = RIRClass2D( src, fspca_components=400, bispectrum_components=300, # Compressed Features after last PCA stage. n_nbor=10, - n_classes=10, large_pca_implementation="legacy", nn_implementation="legacy", bispectrum_implementation="legacy", +) + +averager = BFSReddyChatterjiAverager2D( + FFBBasis2D(src.L, dtype=src.dtype), + src, num_procs=1, # Change to "auto" if your machine has many processors + dtype=rir.dtype, ) -classes, reflections, dists = rir.classify() -avgs = rir.averages(classes, reflections, dists) +avgs = ClassAvgSource( + classification_src=src, + classifier=rir, + class_selector=TopClassSelector(), + averager=averager, +) # %% # Display Classes @@ -164,16 +180,24 @@ fspca_components=400, bispectrum_components=300, n_nbor=10, - n_classes=10, - selector=TopClassSelector(), large_pca_implementation="legacy", nn_implementation="sklearn", bispectrum_implementation="legacy", +) + +noisy_averager = BFSReddyChatterjiAverager2D( + FFBBasis2D(src.L, dtype=src.dtype), + noisy_src, num_procs=1, # Change to "auto" if your machine has many processors + dtype=rir.dtype, ) -classes, reflections, dists = noisy_rir.classify() -avgs = noisy_rir.averages(classes, reflections, dists) +avgs = ClassAvgSource( + classification_src=noisy_src, + classifier=noisy_rir, + class_selector=TopClassSelector(), + averager=noisy_averager, +) # %% # Display Classes @@ -194,6 +218,8 @@ noisy_src.images[review_class].show() # Report the identified neighbor indices +classes = avgs._nn_classes +reflections = avgs._nn_reflections logger.info(f"Class {review_class}'s neighors: {classes[review_class]}") # Report the identified neighbors @@ -208,12 +234,11 @@ # # Alignment details are exposed when avaialable from an underlying ``averager``. # In this case, we'll get the estimated alignments for the ``review_class``. -# These alignment arrays are indexed the same as ``classes``, -# having shape (n_classes, n_nbor). -est_rotations = noisy_rir.averager.rotations[review_class] -est_shifts = noisy_rir.averager.shifts[review_class] -est_correlations = noisy_rir.averager.correlations[review_class] + +est_rotations = noisy_averager.rotations +est_shifts = noisy_averager.shifts +est_correlations = noisy_averager.correlations logger.info(f"Estimated Rotations: {est_rotations}") logger.info(f"Estimated Shifts: {est_shifts}") @@ -230,7 +255,7 @@ original_img_nbr = noisy_src.images[original_img_nbr_idx].asnumpy()[0] # Rotate using estimated rotations. -angle = est_rotations[nbr] * 180 / np.pi +angle = est_rotations[0, nbr] * 180 / np.pi if reflections[review_class][nbr]: original_img_nbr = np.flipud(original_img_nbr) rotated_img_nbr = np.asarray(PILImage.fromarray(original_img_nbr).rotate(angle)) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 579a5d5bbd..5b2b007681 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -150,7 +150,11 @@ def download(url, save_path, chunk_size=1024 * 1024): # also includes a set of protocols for selecting a set of images to be used for classification. # Here we're using ``TopClassSelector``, which selects the first ``n_classes`` images from the source. -from aspire.classification import RIRClass2D, TopClassSelector +from aspire.classification import ( + BFSReddyChatterjiAverager2D, + RIRClass2D, + TopClassSelector, +) # set parameters n_classes = 200 @@ -163,15 +167,37 @@ def download(url, save_path, chunk_size=1024 * 1024): fspca_components=40, bispectrum_components=30, n_nbor=n_nbor, - n_classes=n_classes, - selector=TopClassSelector(), +) + +from aspire.basis import FFBBasis2D +from aspire.classification import BFSReddyChatterjiAverager2D + +averager = BFSReddyChatterjiAverager2D( + FFBBasis2D(src.L, dtype=src.dtype), + src, num_procs=1, # Change to "auto" if your machine has many processors + dtype=rir.dtype, ) -# classify and average -classes, reflections, distances = rir.classify() -avgs = rir.averages(classes, reflections, distances) +from aspire.denoising import ClassAvgSource +avgs = ClassAvgSource( + classification_src=src, + classifier=rir, + class_selector=TopClassSelector(), + averager=averager, +) + +# For a small example, it is more effective to just cache these now. +# avgs.cache() +from aspire.source import ArrayImageSource + +avgs = ArrayImageSource(avgs.images[:n_classes]) + +# classify and average +# classes, reflections, distances = rir.classify() +# avgs = rir.averages(classes, reflections, distances) +# avgs = cls_src.images[:n_classes] # %% # View the Class Averages diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index f761b93cc2..eb332e5133 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -8,7 +8,6 @@ from aspire import config from aspire.classification.reddy_chatterji import reddy_chatterji_register from aspire.image import Image -from aspire.source import ArrayImageSource from aspire.utils import trange from aspire.utils.coor_trans import grid_2d from aspire.utils.multiprocessing import num_procs_suggestion @@ -232,7 +231,7 @@ def _innerloop(i): b_avgs[i] = result # Now we convert the averaged images from Basis to Cartesian. - return ArrayImageSource(self.composite_basis.evaluate(b_avgs)) + return self.composite_basis.evaluate(b_avgs) def _shift_search_grid(self, L, radius, roll_zero=False): """ @@ -673,7 +672,7 @@ def _innerloop(i): b_avgs[i] = result # Now we convert the averaged images from Basis to Cartesian. - return ArrayImageSource(self.composite_basis.evaluate(b_avgs)) + return self.composite_basis.evaluate(b_avgs) class BFSReddyChatterjiAverager2D(ReddyChatterjiAverager2D): diff --git a/src/aspire/denoising/__init__.py b/src/aspire/denoising/__init__.py index 492f1f6aec..e314a3425b 100644 --- a/src/aspire/denoising/__init__.py +++ b/src/aspire/denoising/__init__.py @@ -1,4 +1,5 @@ from .adaptive_support import adaptive_support +from .class_avg import ClassAvgSource from .denoised_src import DenoisedImageSource from .denoiser import Denoiser from .denoiser_cov2d import DenoiserCov2D, src_wiener_coords diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index cffaa4ec5d..edb18eb61b 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -1,6 +1,7 @@ import logging -from aspire.classification import Class2D, ClassSelector +from aspire.classification import Averager2D, Class2D, ClassSelector +from aspire.image import Image from aspire.source import ImageSource logger = logging.getLogger(__name__) @@ -17,7 +18,7 @@ def __init__( classifier, class_selector, averager, - registration_src=None, + averager_src=None, ): """ Constructor of an object for denoising 2D images using class averaging methods. @@ -27,7 +28,7 @@ def __init__( Example, RIRClass2D. :param class_selector: A ClassSelector subclass. :param averager: An Averager2D subclass. - :param registration_src: Optional, Source used for image registration and averaging. + :param averager_src: Optional, Source used for image registration and averaging. Defaults to `classification_src`. """ self.classification_src = classification_src @@ -48,12 +49,18 @@ def __init__( f"`class_selector` should be instance of `ClassSelector`, found {class_selector}." ) - self.registration_src = registration_src - if self.registration_src is None: - self.registration_src = self.classification_src - if not isinstance(self.registration_src, ImageSource): + self.averager = averager + if not isinstance(self.averager, Averager2D): raise ValueError( - f"`registration_src` should be subclass of `ImageSource`, found {self.registration_src}." + f"`averager` should be instance of `Averger2D`, found {self.averager_src}." + ) + + self.averager_src = averager_src + if self.averager_src is None: + self.averager_src = self.classification_src + if not isinstance(self.averager_src, ImageSource): + raise ValueError( + f"`averager_src` should be subclass of `ImageSource`, found {self.averager_src}." ) self._nn_classes = None @@ -67,9 +74,9 @@ def __init__( # Note n will potentially be updated after class selection. super().__init__( - L=self.registration_src.L, - n=self.registration_src.n, - dtype=self.registration_src.dtype, + L=self.averager_src.L, + n=self.averager_src.n, + dtype=self.averager_src.dtype, ) def _classify(self): @@ -92,7 +99,7 @@ def _classify(self): def _class_select(self): """ Uses the `class_selector` in conjunction with the classifier results - to select the classes (and order) used for image registration, + to select the classes (and order) used for image averager, if not already done. """ @@ -105,14 +112,18 @@ def _class_select(self): # Evaluate the classification network. if not self._classified: - self.classify() + self._classify() # Perform class selection - self.selection_indices = self.selector.select( - self._nn_classes, self._nn_reflections, self._nn_distances + self.selection_indices = self.class_selector.select( + self.classification_src.n, + self._nn_classes, + self._nn_reflections, + self._nn_distances, ) # Override the initial self.n + logger.info(f"Selecting {len(self.selection_indices)} of {self.n} classes.") self.n = len(self.selection_indices) self._selected = True @@ -131,8 +142,10 @@ def _images(self, indices): logger.debug("Loading images from cache") im = Image(self._cached_im[indices, :, :]) else: - # Perform image registration for the requested classes - im = self.registration(classes[indices], reflections[indices]) + # Perform image averaging for the requested classes + im = self.averager.average( + self._nn_classes[indices], self._nn_reflections[indices] + ) # Finally, apply transforms to resulting Image return self.generation_pipeline.forward(im, indices) diff --git a/tests/test_class2D.py b/tests/test_class2D.py index ae1069c30e..6fad2e0a3f 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -7,20 +7,13 @@ from sklearn import datasets from aspire.basis import FFBBasis2D, FSPCABasis -from aspire.classification import ( - BFRAverager2D, - ClassSelector, - RIRClass2D, - TopClassSelector, -) +from aspire.classification import RIRClass2D from aspire.classification.legacy_implementations import bispec_2drot_large, pca_y from aspire.noise import WhiteNoiseAdder from aspire.source import Simulation from aspire.utils import utest_tolerance from aspire.volume import Volume -from .test_averager2d import xfail_ray_dev - logger = logging.getLogger(__name__) diff --git a/tox.ini b/tox.ini index 74b2f33eac..421bf4b9a3 100644 --- a/tox.ini +++ b/tox.ini @@ -69,8 +69,8 @@ per-file-ignores = gallery/tutorials/configuration.py: T201, E402 gallery/tutorials/ctf.py: T201, E402 # Ignore Sphinx gallery builds - docs/build/html/_downloads/*/*.py: T201, E402 - docs/source/auto*/*.py: T201, E402 + docs/build/html/_downloads/*/*.py: T201, E402, E265 + docs/source/auto*/*.py: T201, E402, E265 [isort] default_section = THIRDPARTY From 670324fe41ea8e1958bcc1dfe7464b09133f4164 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Feb 2023 16:02:18 -0500 Subject: [PATCH 158/424] Stub in packed ClassAvgSource subclasses to see how they feel --- src/aspire/denoising/class_avg.py | 129 ++++++++++++++++++++++++------ 1 file changed, 103 insertions(+), 26 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index edb18eb61b..d8293cbee2 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -1,6 +1,15 @@ import logging -from aspire.classification import Averager2D, Class2D, ClassSelector +from aspire.basis import FFBBasis2D +from aspire.classification import ( + Averager2D, + BFRAverager2D, + BFSReddyChatterjiAverager2D, + Class2D, + ClassSelector, + RIRClass2D, + TopClassSelector, +) from aspire.image import Image from aspire.source import ImageSource @@ -151,28 +160,96 @@ def _images(self, indices): return self.generation_pipeline.forward(im, indices) -# # Legacy -# :param num_procs: Number of processes to use. -# `None` will attempt computing a suggestion based on machine resources. -# # Setup class selection -# if selector is None: -# selector = RandomClassSelector(seed=self.seed) -# elif not isinstance(selector, ClassSelector): -# raise RuntimeError("`selector` must be subclass of `ClassSelector`") -# self.selector = selector - -# self.averager = averager -# # When not provided by a user, the averager is instantiated after -# # we are certain our pca_basis has been constructed. -# if self.averager is None: -# self.averager = BFSReddyChatterjiAverager2D( -# self.fb_basis, self.src, num_procs=self.num_procs, dtype=self.dtype -# ) -# else: -# # When user provides `averager` and `num_procs` -# # we should warn when `num_procs` mismatched. -# if self.num_procs is not None and self.averager.num_procs != self.num_procs: -# logger.warning( -# f"{self.__class__.__name__} intialized with num_procs={self.num_procs} does not" -# f" match provided {self.averager.__class__.__name__}.{self.averager.num_procs}" -# ) +class DebugClassAvgSource(ClassAvgSource): + """ + Source for denoised 2D images using class average methods. + + Packs base with common debug defaults. + """ + + def __init__( + self, + classification_src, + n_nbor=10, + num_procs=1, # Change to "auto" if your machine has many processors + classifier=None, + class_selector=None, + averager=None, + ): + dtype = classification_src.dtype + + if classifier is None: + classifier = RIRClass2D( + classification_src, + fspca_components=400, + bispectrum_components=300, # Compressed Features after last PCA stage. + n_nbor=n_nbor, + large_pca_implementation="legacy", + nn_implementation="legacy", + bispectrum_implementation="legacy", + ) + + if averager is None: + averager = BFRAverager2D( + classification_src, + num_procs=num_procs, + dtype=dtype, + ) + + if class_selector is None: + class_selector = TopClassSelector() + + super().__init__( + classification_src=classification_src, + classifier=classifier, + class_selector=class_selector, + averager=averager, + averager_src=classification_src, + ) + + +class LegacyClassAvgSource(ClassAvgSource): + """ + Source for denoised 2D images using class average methods. + + Packs base with common v9 and v10 defaults. + """ + + def __init__( + self, + classification_src, + n_nbor=10, + num_procs=1, # Change to "auto" if your machine has many processors + classifier=None, + class_selector=None, + averager=None, + ): + dtype = classification_src.dtype + + if classifier is None: + classifier = RIRClass2D( + classification_src, + fspca_components=400, + bispectrum_components=300, # Compressed Features after last PCA stage. + n_nbor=n_nbor, + large_pca_implementation="legacy", + nn_implementation="legacy", + bispectrum_implementation="legacy", + ) + + if averager is None: + basis_2d = FFBBasis2D(classification_src.L, dtype=dtype) + averager = BFSReddyChatterjiAverager2D( + basis_2d, classification_src, num_procs=num_procs, dtype=dtype + ) + + if class_selector is None: + class_selector = TopClassSelector() + + super().__init__( + classification_src=classification_src, + classifier=classifier, + class_selector=class_selector, + averager=averager, + averager_src=classification_src, + ) From 9d94d25f409f13d8bc8915928bc168560e8a2c2e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Feb 2023 16:41:56 -0500 Subject: [PATCH 159/424] Try using the packed ClassAvgSource subclasses in the gallery --- gallery/tutorials/class_averaging.py | 68 +++++----------------------- src/aspire/denoising/__init__.py | 2 +- src/aspire/denoising/class_avg.py | 2 +- 3 files changed, 14 insertions(+), 58 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 5a48499b9d..8c9ecaff0b 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -11,13 +11,7 @@ import numpy as np from PIL import Image as PILImage -from aspire.basis import FFBBasis2D -from aspire.classification import ( - BFSReddyChatterjiAverager2D, - RIRClass2D, - TopClassSelector, -) -from aspire.denoising import ClassAvgSource +from aspire.denoising import LegacyClassAvgSource from aspire.image import Image from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. @@ -111,28 +105,10 @@ # algorithm. We then yield class averages by performing ``classify``. n_classes = 10 -rir = RIRClass2D( - src, - fspca_components=400, - bispectrum_components=300, # Compressed Features after last PCA stage. +avgs = LegacyClassAvgSource( + classification_src=src, n_nbor=10, - large_pca_implementation="legacy", - nn_implementation="legacy", - bispectrum_implementation="legacy", -) - -averager = BFSReddyChatterjiAverager2D( - FFBBasis2D(src.L, dtype=src.dtype), - src, num_procs=1, # Change to "auto" if your machine has many processors - dtype=rir.dtype, -) - -avgs = ClassAvgSource( - classification_src=src, - classifier=rir, - class_selector=TopClassSelector(), - averager=averager, ) # %% @@ -169,34 +145,14 @@ # %% # RIR with Noise # ^^^^^^^^^^^^^^ -# This also demonstrates changing the Nearest Neighbor search to using -# scikit-learn, and using ``TopClassSelector``.``TopClassSelector`` -# will deterministically select the first ``n_classes``. This is useful -# for development and debugging. By default ``RIRClass2D`` uses a -# ``RandomClassSelector``. - -noisy_rir = RIRClass2D( - noisy_src, - fspca_components=400, - bispectrum_components=300, - n_nbor=10, - large_pca_implementation="legacy", - nn_implementation="sklearn", - bispectrum_implementation="legacy", -) +# Internally this uses scikit-learn for NN and ``TopClassSelector``. +# ``TopClassSelector`` will deterministically select the first ``n_classes``. +# This is useful for development and debugging. -noisy_averager = BFSReddyChatterjiAverager2D( - FFBBasis2D(src.L, dtype=src.dtype), - noisy_src, - num_procs=1, # Change to "auto" if your machine has many processors - dtype=rir.dtype, -) - -avgs = ClassAvgSource( +avgs = LegacyClassAvgSource( classification_src=noisy_src, - classifier=noisy_rir, - class_selector=TopClassSelector(), - averager=noisy_averager, + n_nbor=10, + num_procs=1, # Change to "auto" if your machine has many processors ) # %% @@ -236,9 +192,9 @@ # In this case, we'll get the estimated alignments for the ``review_class``. -est_rotations = noisy_averager.rotations -est_shifts = noisy_averager.shifts -est_correlations = noisy_averager.correlations +est_rotations = avgs.averager.rotations +est_shifts = avgs.averager.shifts +est_correlations = avgs.averager.correlations logger.info(f"Estimated Rotations: {est_rotations}") logger.info(f"Estimated Shifts: {est_shifts}") diff --git a/src/aspire/denoising/__init__.py b/src/aspire/denoising/__init__.py index e314a3425b..72c565f4a6 100644 --- a/src/aspire/denoising/__init__.py +++ b/src/aspire/denoising/__init__.py @@ -1,5 +1,5 @@ from .adaptive_support import adaptive_support -from .class_avg import ClassAvgSource +from .class_avg import ClassAvgSource, DebugClassAvgSource, LegacyClassAvgSource from .denoised_src import DenoisedImageSource from .denoiser import Denoiser from .denoiser_cov2d import DenoiserCov2D, src_wiener_coords diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index d8293cbee2..2b3dfa8bd3 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -233,7 +233,7 @@ def __init__( bispectrum_components=300, # Compressed Features after last PCA stage. n_nbor=n_nbor, large_pca_implementation="legacy", - nn_implementation="legacy", + nn_implementation="sklearn", # Note this is different than debug bispectrum_implementation="legacy", ) From f315fe2176426cf06e33422052bda607c7fd1ac1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Feb 2023 16:08:10 -0500 Subject: [PATCH 160/424] Stub in class selectors some loosely based on matlab code --- src/aspire/classification/class_selection.py | 237 +++++++++++++++++-- src/aspire/denoising/class_avg.py | 4 +- 2 files changed, 223 insertions(+), 18 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index cf921c320e..49e0a45716 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -1,8 +1,13 @@ import logging from abc import ABC, abstractmethod +from heapq import heappush, heappushpop import numpy as np +from aspire.classification import Averager2D +from aspire.image import Image +from aspire.utils import grid_2d + logger = logging.getLogger(__name__) """ @@ -28,7 +33,7 @@ class ClassSelector(ABC): """ @abstractmethod - def _select(self, n, classes, reflections, distances): + def _select(self, classes, reflections, distances): """ Using the provided arguments, returns an array representing an index into `n` selected `classes`. @@ -43,13 +48,12 @@ def _select(self, n, classes, reflections, distances): :return: array of indices into `classes` """ - def select(self, n, classes, reflections, distances): + def select(self, classes, reflections, distances): """ Using the provided arguments, calls internal `_select` method, checks selection is sane, and returns an array representing - an index into `n` selected `classes`. + an ordered index into `classes`. - :param n: number of classes to select :param classes: (n_img, n_nbor) array of image indices :param reflections: (n_img, n_nbor) boolean array of reflections between `classes[i][0]` and classes[i][j]` :param distances: (n_img, n_nbor) array of distances between `classes[i][0]` and classes[i][j]` @@ -57,28 +61,32 @@ def select(self, n, classes, reflections, distances): :return: array of indices into `classes` """ + # Compute max class id found in the network for sanity checking. when unknown. + # Some subclasses may have a more explicit `_max_n` from an initializing `Source`. + if not hasattr(self, "_max_n"): + self._max_n = np.max(classes[:, 0]) + # Call the selection logic - selection = self._select(n, classes, reflections, distances) + selection = self._select(classes, reflections, distances) # n_img should not exceed the largest index in first column of `classes` n_img = np.max(classes[:, 0]) + 1 # Check values in selection are in bounds. - self._check_selection(selection, n, n_img) + self._check_selection(selection, n_img) return selection - def _check_selection(self, selection, n_classes, n_img): + def _check_selection(self, selection, n_img): """ Check that class `selection` is sane. :param selection: selection indices - :param n_classes: number of classes expected :param n_img: number of images available """ # Check length - if len(selection) != n_classes: + if len(selection) != self._max_n: raise ValueError( - f"Class selection must be len {n_classes}, got {len(selection)}" + f"Class selection must be len {self._max_n}, got {len(selection)}" ) # Check indices [0, n_img) @@ -90,14 +98,13 @@ def _check_selection(self, selection, n_classes, n_img): class TopClassSelector(ClassSelector): - def _select(self, n, classes, reflections, distances): + def _select(self, classes, reflections, distances): """ - Selects the first `n` classes. + Returns classes in `Source` order. - This may be useful for debugging or when a `Source` is known - to be sufficiently randomized. + Mainly useful for debugging. """ - return np.arange(n) + return np.arange(self._max_n) class RandomClassSelector(ClassSelector): @@ -107,11 +114,207 @@ def __init__(self, seed=None): """ self.seed = seed - def _select(self, n, classes, reflections, distances): + def _select(self, classes, reflections, distances): """ Select random `n` classes from the population. """ # Instantiate a random Generator rng = np.random.default_rng(self.seed) # Generate and return indices for random sample - return rng.choice(np.max(classes[:, 0]), size=n, replace=False) + return rng.choice(self._max_n, size=self._max_n, replace=False) + + +class ContrastClassSelector(ClassSelector): + """ + Selects top classes based on highest contrast, + as estimated by variances of `distances`. + + Note that `distances` is the Nearest Neighbors distances, + and in the case of RIR this is a small rotationally invariant feature + vector. For methods based on class average images, see subclasses of `GlobalClassSelector`. + """ + + def _select(self, classes, reflections, distances): + # Compute per class variance + dist_var = np.var(distances[:, 1:], axis=1) + + # Compute the ordering, descending + sorted_class_inds = np.argsort(dist_var)[::-1] + + # Return indices + return sorted_class_inds + + +class DistanceClassSelector(ClassSelector): + """ + Selects top classes based on lowest mean distance + as estimated by `distances`. + + Note that `distances` is the Nearest Neighbors distances, + and in the case of RIR this is a small rotationally invariant feature + vector. For methods based on class average images, see subclasses of `GlobalClassSelector`. + """ + + def _select(self, classes, reflections, distances): + # Compute per class variance + dist_var = np.var(distances[:, 1:], axis=1) + + # Compute the ordering, descending + sorted_class_inds = np.argsort(dist_var)[::-1] + + # Return indices + return sorted_class_inds + + +class GlobalClassSelector(ClassSelector): + """ + Extends ClassSelector for methods that require + passing over all class average images. + """ + + def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): + """ + :param averager: An Averager2D subclass. + :param quality_function: Function that takes an image and returns numeric quality score. + This score will be used to sort the classes. + For example, this module provides methods for Contrast and SNR. + """ + self.averager = averager + if not isinstance(self.averager, Averager2D): + raise ValueError( + f"`averager` should be instance of `Averger2D`, found {self.averager_src}." + ) + + self._quality_function = quality_function + self._max_n = self.averager.src.n + # We should hit every entry, but along the way identify missing values as nan. + self.quality_scores = np.full(self._max_n, fill_value=float("nan")) + + # To be used for heapq (score, img_id, avg_im) + # Note the default implementation is min heap, so `pop` returns smallest value. + self.heap = [] + + self._heap_limit_bytes = heap_size_limit_bytes + self._heap_item_size = self.averager.src.L**2 + self.averager.dtype.itemsize + + def _heap_size(self): + n = len(self.heap) + if n == 0: + return 0 + + return n * self._heap_item_size + + def _select(self, classes, reflections, distances): + for i in classes[:, 0]: + im = self.averager.average(classes[i], self.reflections[i]) + + quality_score = self._quality_function(im) + + # Assign in global quality score array + self.quality_scores[i] = quality_score + + # Create cachable payload item + item = (quality_score, i, im) + + if self._heap_item_size + self._heap_size < self._heap_limit_bytes: + heappush(self.heap, item) + else: + # We're out of space, + # pop and throw away the worst entry + # after updating (pushing to) the heap. + _ = heappushpop(self.heap, item) + + +class PriorRejection: + def __init__(self, aggresive=True): + self.excluded = set() + self.aggresive = bool(aggresive) + + def _select(self, classes, reflections, distances): + # Get the indices sorted by the next resolving `_select` method. + sorted_inds = super()._select(classes, reflections, distances) + + results = [] + for i in sorted_inds: + # Skip when this class's base image has been seen in a prior class. + if i in self.excluded: + continue + + # Get the images in this class + cls = classes[i] + + # Aggresive mode skips any class containing neighbors + # we have have seen in prior class. + if self.aggressive and self.excluded.intersection(cls[1:]): + continue + + # Add images from this class to the exclusion list + self.excluded.update(cls) + + results.append(i) + + return np.array(results, dtype=int) + + +class ContrastWithPriorClassSelector(PriorRejection, ContrastClassSelector): + """ + Selects top classes based on highest contrast with prior rejection. + """ + + +class GlobalWithPriorClassSelector(PriorRejection, ClassSelector): + """ + Extends ClassSelector for methods that require + passing over all class average images and also PriorRejection. + """ + + +# Weight functions, todo, maybe make a class to hide the grid detail. +def banded_snr( + img, grid=None, center_radius=0.5, outer_band_start=0.8, outer_band_end=1 +): + if isinstance(img, Image): + img = img.asnumpy()[0] + + L = img.shape[-1] + + if grid is None: + grid = grid_2d(L) + + center_mask = grid["r"] < center_radius + outer_mask = (grid["r"] > outer_band_start) & (grid["r"] < outer_band_end) + + return np.var(img[center_mask]) / np.var(img[outer_mask]) + + +def weighted_snr(img, grid=None, weight_function=None): + """ + Take a 1d radial weight function. + The function is expected to be defined [0,sqrt(2)]. + Function range is not limited, but should probably be [0,1]. + """ + + if isinstance(img, Image): + img = img.asnumpy()[0] + + if weight_function is None: + + def weight_function(r): + # linear ramp + return np.max(1 - r, 0) + + # def weight_function(r): + # # bump function (normalized to [0,1] + # if r >= 1: + # return 0 + # else: + # return np.exp(-1/(1-r**2) + 1) + + L = img.shape[-1] + + if grid is None: + grid = grid_2d(L) + + weights = np.frompyfunc(weight_function)(grid["r"]) + + return img * weights diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 2b3dfa8bd3..511e99d7d8 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -125,7 +125,6 @@ def _class_select(self): # Perform class selection self.selection_indices = self.class_selector.select( - self.classification_src.n, self._nn_classes, self._nn_reflections, self._nn_distances, @@ -150,6 +149,9 @@ def _images(self, indices): if self._cached_im is not None: logger.debug("Loading images from cache") im = Image(self._cached_im[indices, :, :]) + # elif hasattr(self.class_selector, "heap") and : + # # attempt to get set(indices).intesection(heap_ids) from heap + # # then compute remaining batch self._images[leftover_inds] else: # Perform image averaging for the requested classes im = self.averager.average( From ccb1ae852ae666e4e19822f34828ad223af83db3 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Feb 2023 10:18:16 -0500 Subject: [PATCH 161/424] Clean up some of the class weighting function examples --- src/aspire/classification/class_selection.py | 134 ++++++++++++++----- 1 file changed, 101 insertions(+), 33 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 49e0a45716..2009d9f106 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -269,52 +269,120 @@ class GlobalWithPriorClassSelector(PriorRejection, ClassSelector): """ -# Weight functions, todo, maybe make a class to hide the grid detail. -def banded_snr( - img, grid=None, center_radius=0.5, outer_band_start=0.8, outer_band_end=1 -): - if isinstance(img, Image): - img = img.asnumpy()[0] +class ImageQualityFunction(ABC): + """ + A callable image quality scoring function. + """ + + _grid_cache = {} + + @abstractmethod + def _function(self, img): + """ + User defined 1d radial weight function. + The function is expected to be defined for [0,sqrt(2)]. + Function range is not limited, but [0,1] is favorable. + + Develops can use the self._grid_cache for access to + a grid_2d instance matching resolution of img. + + :param img: 2d numpy array + :returns: Image quality score + """ + + def __call__(self, img): + """ + Given an image instance or a 2D np array, + builds a grid once if needed then + calls the instance's weight function. + + :param img: `Image`, 2d numpy array, + or 3d array where slow axis is stack axis. + When called with an image stack>1, an array is returned. + :returns: Image quality score(s) + """ + + if isinstance(img, Image): + img = img.asnumpy() + + if img.ndim == 2: + img = img[np.newaxis, :, :] - L = img.shape[-1] + stack_len = img.shape[0] + L = img.shape[-1] - if grid is None: - grid = grid_2d(L) + # Generate grid on first call so user can expect it exists. + self._grid_cache.setdefault(L, grid_2d(L, dtype=img.dype)) - center_mask = grid["r"] < center_radius - outer_mask = (grid["r"] > outer_band_start) & (grid["r"] < outer_band_end) + # Call the function over img stack + res = np.fromiter(map(self.function, img), dtype=img.dtype) - return np.var(img[center_mask]) / np.var(img[outer_mask]) + if stack_len == 1: + res = res[0] + return res -def weighted_snr(img, grid=None, weight_function=None): + +class BandedSNRImageQualityFunction(ImageQualityFunction): + def _function(self, img, center_radius=0.5, outer_band_start=0.8, outer_band_end=1): + # Look up the precached grid. + grid = self._grid_cache[img.shape[-1]] + + center_mask = grid["r"] < center_radius + outer_mask = (grid["r"] > outer_band_start) & (grid["r"] < outer_band_end) + + return np.var(img[center_mask]) / np.var(img[outer_mask]) + + +class WeightedImageQualityFunction(ImageQualityFunction): """ - Take a 1d radial weight function. - The function is expected to be defined [0,sqrt(2)]. - Function range is not limited, but should probably be [0,1]. + A callable image quality scoring function using a radial grid weighted function. """ - if isinstance(img, Image): - img = img.asnumpy()[0] + def __init__(self): + # Each weight function will need a seperate cache, + # but the same function should be deterministic up to resolution. + self._weights_cache = {} - if weight_function is None: + @abstractmethod + def _weight_function(self, r): + """ + Take a 1d radial weight function. + The function is expected to be defined [0,sqrt(2)]. + Function range is not limited, but should probably be [0,1]. + """ + + def weights(self, L): + """ + Lookup weights for a given resolution L, + computes and updates cache on first request. - def weight_function(r): - # linear ramp - return np.max(1 - r, 0) + :param L: resolution pixels + :returns: 2d weight array for LxL grid. + """ + grid = self._grid_cache[L] + return self._weights_cache.setdefault( + L, np.frompyfunc(self.weight_function)(grid["r"]) + ) + + def _function(self, img): + L = img.shape[-1] - # def weight_function(r): - # # bump function (normalized to [0,1] - # if r >= 1: - # return 0 - # else: - # return np.exp(-1/(1-r**2) + 1) + # if we ignore the issue of dependence, + # we could also do sum(per_pixel_variance * weights) + return (img * img) * self.weights(L) - L = img.shape[-1] - if grid is None: - grid = grid_2d(L) +class RampImageQualityFunction(WeightedImageQualityFunction): + def _weight_function(r): + # linear ramp + return np.max(1 - r, 0) - weights = np.frompyfunc(weight_function)(grid["r"]) - return img * weights +class BumpImageQualityFunction(WeightedImageQualityFunction): + def _weight_function(r): + # bump function (normalized to [0,1] + if r >= 1: + return 0 + else: + return np.exp(-1 / (1 - r**2) + 1) From 98103752ad3be86892d7d3a11fe163940cfd7cd7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Feb 2023 12:05:42 -0500 Subject: [PATCH 162/424] Stub in rough caching lookup --- src/aspire/classification/class_selection.py | 17 +++++++ src/aspire/denoising/class_avg.py | 49 +++++++++++++++++++- 2 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 2009d9f106..9ad3ee27f6 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -197,6 +197,7 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): self._heap_limit_bytes = heap_size_limit_bytes self._heap_item_size = self.averager.src.L**2 + self.averager.dtype.itemsize + @property def _heap_size(self): n = len(self.heap) if n == 0: @@ -204,6 +205,22 @@ def _heap_size(self): return n * self._heap_item_size + @property + def _heap_ids(self): + """ + Return the image ids currently in the heap. + """ + return [item[1] for item in self.heap] # heap item=(score, img_id, img) + + @property + def _heap_id_dict(self): + """ + Return map of image ids to heap position currently in the heap. + """ + return { + item[1]: i for i, item in enumerate(self.heap) + } # heap item=(score, img_id, img) + def _select(self, classes, reflections, distances): for i in classes[:, 0]: im = self.averager.average(classes[i], self.reflections[i]) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 511e99d7d8..356a1fae92 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -1,5 +1,7 @@ import logging +import numpy as np + from aspire.basis import FFBBasis2D from aspire.classification import ( Averager2D, @@ -145,11 +147,54 @@ def _images(self, indices): if not self._selected: self._class_select() - # check for cached images first + # check for wholly cached image sets first if self._cached_im is not None: logger.debug("Loading images from cache") im = Image(self._cached_im[indices, :, :]) - # elif hasattr(self.class_selector, "heap") and : + + # check for cached image sets from class_selector + elif hasattr(self.class_selector, "heap") and ( + heap_inds := set(indices).intesection(self.class_selector._heap_ids) + ): + # Images in heap_inds can be fetched from class_selector. + # For others, create an indexing map that preserves original order. + indices_to_compute = { + ind: i for i, ind in enumerate(indices) if i not in heap_inds + } + indices_from_heap = { + ind: i for i, ind in enumerate(indices) if i in heap_inds + } + + # Recursion, heap_inds set should be empty in the recursive call. + computed_imgs = self._images(list(indices_to_compute.values())) + # Get the map once to avoid traversing heap in a loop + heapd = self._heap_id_dict + + imgs = np.empty( + (len(indices), computed_imgs.resolution, computed_imgs.resolution), + dtype=computed_imgs.dtype, + ) + + _inds = list(indices_to_compute.values()) + imgs[_inds] = computed_imgs + _inds = list(indices_from_heap.values()) + imgs[_inds] = map( + lambda k: self.class_selector.heap[heapd[k]][2], + list(indices_from_heap.keys()), + ) + # for (i,ind) in enumerate(indices): + # # collect the images + # if ind in heapd: + # loc = heapd[ind] + # assert self.class_selector.heap[loc][1] == ind + # _img = self.class_selector.heap[loc][2] + # else: + # _img = computed_imgs[indices_to_compute[ind]] + # # assign the image + # imgs[i] = _img + + return imgs + # # attempt to get set(indices).intesection(heap_ids) from heap # # then compute remaining batch self._images[leftover_inds] else: From 3a662faff730e6fc5b328f2c822add8faf998b1f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Feb 2023 15:29:47 -0500 Subject: [PATCH 163/424] Begin adding unit tests for ClassAvgSource code --- src/aspire/classification/class_selection.py | 15 +-- src/aspire/denoising/class_avg.py | 98 +++++++++++--------- 2 files changed, 60 insertions(+), 53 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 9ad3ee27f6..c49f25d6ff 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -83,10 +83,10 @@ def _check_selection(self, selection, n_img): :param selection: selection indices :param n_img: number of images available """ - # Check length - if len(selection) != self._max_n: + # Check length, +1 for zero indexing + if len(selection) != self._max_n + 1: raise ValueError( - f"Class selection must be len {self._max_n}, got {len(selection)}" + f"Class selection must be len {self._max_n+1}, got {len(selection)}" ) # Check indices [0, n_img) @@ -104,7 +104,7 @@ def _select(self, classes, reflections, distances): Mainly useful for debugging. """ - return np.arange(self._max_n) + return np.arange(self._max_n + 1) # +1 for zero indexing class RandomClassSelector(ClassSelector): @@ -121,7 +121,8 @@ def _select(self, classes, reflections, distances): # Instantiate a random Generator rng = np.random.default_rng(self.seed) # Generate and return indices for random sample - return rng.choice(self._max_n, size=self._max_n, replace=False) + # +1 for zero indexing + return rng.choice(self._max_n + 1, size=self._max_n + 1, replace=False) class ContrastClassSelector(ClassSelector): @@ -186,9 +187,9 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): ) self._quality_function = quality_function - self._max_n = self.averager.src.n + self._max_n = self.averager.src.n - 1 # We should hit every entry, but along the way identify missing values as nan. - self.quality_scores = np.full(self._max_n, fill_value=float("nan")) + self.quality_scores = np.full(self._max_n + 1, fill_value=float("nan")) # To be used for heapq (score, img_id, avg_im) # Note the default implementation is min heap, so `pop` returns smallest value. diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 356a1fae92..5305a8e7b8 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -133,7 +133,11 @@ def _class_select(self): ) # Override the initial self.n - logger.info(f"Selecting {len(self.selection_indices)} of {self.n} classes.") + # Some selectors will (dramitcally) reduce the space of classes. + if len(self.selection_indices) != self.n: + logger.info( + f"After selection process, updating maximum {len(self.selection_indices)} classes from {self.n}." + ) self.n = len(self.selection_indices) self._selected = True @@ -149,56 +153,57 @@ def _images(self, indices): # check for wholly cached image sets first if self._cached_im is not None: - logger.debug("Loading images from cache") + logger.debug(f"Loading {len(indices)} images from cache") im = Image(self._cached_im[indices, :, :]) - # check for cached image sets from class_selector - elif hasattr(self.class_selector, "heap") and ( - heap_inds := set(indices).intesection(self.class_selector._heap_ids) - ): - # Images in heap_inds can be fetched from class_selector. - # For others, create an indexing map that preserves original order. - indices_to_compute = { - ind: i for i, ind in enumerate(indices) if i not in heap_inds - } - indices_from_heap = { - ind: i for i, ind in enumerate(indices) if i in heap_inds - } - - # Recursion, heap_inds set should be empty in the recursive call. - computed_imgs = self._images(list(indices_to_compute.values())) - # Get the map once to avoid traversing heap in a loop - heapd = self._heap_id_dict - - imgs = np.empty( - (len(indices), computed_imgs.resolution, computed_imgs.resolution), - dtype=computed_imgs.dtype, - ) + # # check for cached image sets from class_selector + # elif hasattr(self.class_selector, "heap") and ( + # heap_inds := set(indices).intesection(self.class_selector._heap_ids) + # ): + # # logger.debug(f"Mapping {len(heap_inds)} images from heap cache.") + + # # Images in heap_inds can be fetched from class_selector. + # # For others, create an indexing map that preserves original order. + # indices_to_compute = { + # ind: i for i, ind in enumerate(indices) if i not in heap_inds + # } + # indices_from_heap = { + # ind: i for i, ind in enumerate(indices) if i in heap_inds + # } + + # # Recursion, heap_inds set should be empty in the recursive call. + # computed_imgs = self._images(list(indices_to_compute.values())) + # # Get the map once to avoid traversing heap in a loop + # heapd = self._heap_id_dict + + # imgs = np.empty( + # (len(indices), computed_imgs.resolution, computed_imgs.resolution), + # dtype=computed_imgs.dtype, + # ) + + # _inds = list(indices_to_compute.values()) + # imgs[_inds] = computed_imgs + # _inds = list(indices_from_heap.values()) + # imgs[_inds] = map( + # lambda k: self.class_selector.heap[heapd[k]][2], + # list(indices_from_heap.keys()), + # ) + # # for (i,ind) in enumerate(indices): + # # # collect the images + # # if ind in heapd: + # # loc = heapd[ind] + # # assert self.class_selector.heap[loc][1] == ind + # # _img = self.class_selector.heap[loc][2] + # # else: + # # _img = computed_imgs[indices_to_compute[ind]] + # # # assign the image + # # imgs[i] = _img + + # return imgs - _inds = list(indices_to_compute.values()) - imgs[_inds] = computed_imgs - _inds = list(indices_from_heap.values()) - imgs[_inds] = map( - lambda k: self.class_selector.heap[heapd[k]][2], - list(indices_from_heap.keys()), - ) - # for (i,ind) in enumerate(indices): - # # collect the images - # if ind in heapd: - # loc = heapd[ind] - # assert self.class_selector.heap[loc][1] == ind - # _img = self.class_selector.heap[loc][2] - # else: - # _img = computed_imgs[indices_to_compute[ind]] - # # assign the image - # imgs[i] = _img - - return imgs - - # # attempt to get set(indices).intesection(heap_ids) from heap - # # then compute remaining batch self._images[leftover_inds] else: # Perform image averaging for the requested classes + logger.debug(f"Averaging {len(indices)} images from source") im = self.averager.average( self._nn_classes[indices], self._nn_reflections[indices] ) @@ -238,6 +243,7 @@ def __init__( if averager is None: averager = BFRAverager2D( + FFBBasis2D(classification_src.L, dtype=classification_src.dtype), classification_src, num_procs=num_procs, dtype=dtype, From 83ee2bb8f6d0e5b750dee5f8d0d53437666d74a0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Feb 2023 15:46:19 -0500 Subject: [PATCH 164/424] Refactor heap cache code to not use walrus --- src/aspire/denoising/class_avg.py | 93 ++++++++++++++++--------------- 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 5305a8e7b8..a984c97503 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -151,55 +151,60 @@ def _images(self, indices): if not self._selected: self._class_select() + # Check is there is a cache available from class selection component + # Note, we can use := for this in the branch directly, but requires Python>=3.8 + heap_inds = None + if hasattr(self.class_selector, "heap"): + # Then check if heap holds anything in indices + heap_inds = set(indices).intesection(self.class_selector._heap_ids) + # check for wholly cached image sets first if self._cached_im is not None: logger.debug(f"Loading {len(indices)} images from cache") im = Image(self._cached_im[indices, :, :]) - # # check for cached image sets from class_selector - # elif hasattr(self.class_selector, "heap") and ( - # heap_inds := set(indices).intesection(self.class_selector._heap_ids) - # ): - # # logger.debug(f"Mapping {len(heap_inds)} images from heap cache.") - - # # Images in heap_inds can be fetched from class_selector. - # # For others, create an indexing map that preserves original order. - # indices_to_compute = { - # ind: i for i, ind in enumerate(indices) if i not in heap_inds - # } - # indices_from_heap = { - # ind: i for i, ind in enumerate(indices) if i in heap_inds - # } - - # # Recursion, heap_inds set should be empty in the recursive call. - # computed_imgs = self._images(list(indices_to_compute.values())) - # # Get the map once to avoid traversing heap in a loop - # heapd = self._heap_id_dict - - # imgs = np.empty( - # (len(indices), computed_imgs.resolution, computed_imgs.resolution), - # dtype=computed_imgs.dtype, - # ) - - # _inds = list(indices_to_compute.values()) - # imgs[_inds] = computed_imgs - # _inds = list(indices_from_heap.values()) - # imgs[_inds] = map( - # lambda k: self.class_selector.heap[heapd[k]][2], - # list(indices_from_heap.keys()), - # ) - # # for (i,ind) in enumerate(indices): - # # # collect the images - # # if ind in heapd: - # # loc = heapd[ind] - # # assert self.class_selector.heap[loc][1] == ind - # # _img = self.class_selector.heap[loc][2] - # # else: - # # _img = computed_imgs[indices_to_compute[ind]] - # # # assign the image - # # imgs[i] = _img - - # return imgs + # check for cached image sets from class_selector + elif heap_inds: + logger.debug(f"Mapping {len(heap_inds)} images from heap cache.") + + # Images in heap_inds can be fetched from class_selector. + # For others, create an indexing map that preserves original order. + indices_to_compute = { + ind: i for i, ind in enumerate(indices) if i not in heap_inds + } + indices_from_heap = { + ind: i for i, ind in enumerate(indices) if i in heap_inds + } + + # Recursion, heap_inds set should be empty in the recursive call. + computed_imgs = self._images(list(indices_to_compute.values())) + # Get the map once to avoid traversing heap in a loop + heapd = self._heap_id_dict + + imgs = np.empty( + (len(indices), computed_imgs.resolution, computed_imgs.resolution), + dtype=computed_imgs.dtype, + ) + + _inds = list(indices_to_compute.values()) + imgs[_inds] = computed_imgs + _inds = list(indices_from_heap.values()) + imgs[_inds] = map( + lambda k: self.class_selector.heap[heapd[k]][2], + list(indices_from_heap.keys()), + ) + # for (i,ind) in enumerate(indices): + # # collect the images + # if ind in heapd: + # loc = heapd[ind] + # assert self.class_selector.heap[loc][1] == ind + # _img = self.class_selector.heap[loc][2] + # else: + # _img = computed_imgs[indices_to_compute[ind]] + # # assign the image + # imgs[i] = _img + + return imgs else: # Perform image averaging for the requested classes From 4615824c6157236506ab58cdeb9e0c7250815be5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Feb 2023 14:51:01 -0500 Subject: [PATCH 165/424] Stub in smoke tests for new class selectors --- src/aspire/classification/__init__.py | 12 +- src/aspire/classification/averager2d.py | 3 + src/aspire/classification/class_selection.py | 111 +++++++++++++++---- 3 files changed, 104 insertions(+), 22 deletions(-) diff --git a/src/aspire/classification/__init__.py b/src/aspire/classification/__init__.py index 1cc7e42e7f..e39619b1bb 100644 --- a/src/aspire/classification/__init__.py +++ b/src/aspire/classification/__init__.py @@ -9,5 +9,15 @@ ReddyChatterjiAverager2D, ) from .class2d import Class2D -from .class_selection import ClassSelector, RandomClassSelector, TopClassSelector +from .class_selection import ( + BandedSNRImageQualityFunction, + ClassSelector, + ContrastClassSelector, + ContrastWithRepulsionClassSelector, + DistanceClassSelector, + GlobalClassSelector, + GlobalWithRepulsionClassSelector, + RandomClassSelector, + TopClassSelector, +) from .rir_class2d import RIRClass2D diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index eb332e5133..ad57ff513e 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -174,6 +174,9 @@ def average( This subclass assumes we get alignment details from `align` method. Otherwise. see Averager2D.average """ + classes = np.atleast_2d(classes) + reflections = np.atleast_2d(reflections) + self.rotations, self.shifts, self.correlations = self.align( classes, reflections, coefs ) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index c49f25d6ff..2375a6eee8 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -1,6 +1,7 @@ import logging from abc import ABC, abstractmethod from heapq import heappush, heappushpop +from operator import eq, le import numpy as np @@ -48,6 +49,18 @@ def _select(self, classes, reflections, distances): :return: array of indices into `classes` """ + @classmethod + @property + @abstractmethod + def quality_scores(cls): + """ + All `ClassSelector` should assign a quality score + array the same length as the selection output. + + For subclasses like TopClassSelector and RandomClassSelector + where no quality information is derived, this array should be zeros. + """ + def select(self, classes, reflections, distances): """ Using the provided arguments, calls internal `_select` @@ -76,7 +89,7 @@ def select(self, classes, reflections, distances): return selection - def _check_selection(self, selection, n_img): + def _check_selection(self, selection, n_img, len_operator=eq): """ Check that class `selection` is sane. @@ -84,9 +97,9 @@ def _check_selection(self, selection, n_img): :param n_img: number of images available """ # Check length, +1 for zero indexing - if len(selection) != self._max_n + 1: + if not len_operator(len(selection), self._max_n + 1): raise ValueError( - f"Class selection must be len {self._max_n+1}, got {len(selection)}" + f"Class selection must be {str(len_operator)} {self._max_n+1}, got {len(selection)}" ) # Check indices [0, n_img) @@ -97,7 +110,21 @@ def _check_selection(self, selection, n_img): ) -class TopClassSelector(ClassSelector): +class ClassSelectorUnranked(ClassSelector): + @property + def quality_scores(self): + return np.zeros(self._max_n + 1) + + +class ClassSelectorRanked(ClassSelector): + @property + def quality_scores(self): + if not hasattr(self, "_quality_scores"): + raise RuntimeError("Must run `.select(...)` to compute quality_scores") + return self._quality_scores + + +class TopClassSelector(ClassSelectorUnranked): def _select(self, classes, reflections, distances): """ Returns classes in `Source` order. @@ -107,7 +134,7 @@ def _select(self, classes, reflections, distances): return np.arange(self._max_n + 1) # +1 for zero indexing -class RandomClassSelector(ClassSelector): +class RandomClassSelector(ClassSelectorUnranked): def __init__(self, seed=None): """ :param seed: RNG seed, de @@ -125,7 +152,7 @@ def _select(self, classes, reflections, distances): return rng.choice(self._max_n + 1, size=self._max_n + 1, replace=False) -class ContrastClassSelector(ClassSelector): +class ContrastClassSelector(ClassSelectorRanked): """ Selects top classes based on highest contrast, as estimated by variances of `distances`. @@ -141,12 +168,14 @@ def _select(self, classes, reflections, distances): # Compute the ordering, descending sorted_class_inds = np.argsort(dist_var)[::-1] + # Store the sorted quality scores (maps to selection output). + self._quality_scores = dist_var[sorted_class_inds] # Return indices return sorted_class_inds -class DistanceClassSelector(ClassSelector): +class DistanceClassSelector(ClassSelectorRanked): """ Selects top classes based on lowest mean distance as estimated by `distances`. @@ -158,16 +187,17 @@ class DistanceClassSelector(ClassSelector): def _select(self, classes, reflections, distances): # Compute per class variance - dist_var = np.var(distances[:, 1:], axis=1) + dist_mean = np.mean(distances[:, 1:], axis=1) # Compute the ordering, descending - sorted_class_inds = np.argsort(dist_var)[::-1] + sorted_class_inds = np.argsort(dist_mean)[::-1] + self._quality_scores = dist_mean[sorted_class_inds] # Return indices return sorted_class_inds -class GlobalClassSelector(ClassSelector): +class GlobalClassSelector(ClassSelectorRanked): """ Extends ClassSelector for methods that require passing over all class average images. @@ -189,7 +219,7 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): self._quality_function = quality_function self._max_n = self.averager.src.n - 1 # We should hit every entry, but along the way identify missing values as nan. - self.quality_scores = np.full(self._max_n + 1, fill_value=float("nan")) + self._quality_scores = np.full(self._max_n + 1, fill_value=float("nan")) # To be used for heapq (score, img_id, avg_im) # Note the default implementation is min heap, so `pop` returns smallest value. @@ -224,12 +254,12 @@ def _heap_id_dict(self): def _select(self, classes, reflections, distances): for i in classes[:, 0]: - im = self.averager.average(classes[i], self.reflections[i]) + im = self.averager.average(classes[i], reflections[i]) quality_score = self._quality_function(im) # Assign in global quality score array - self.quality_scores[i] = quality_score + self._quality_scores[i] = quality_score # Create cachable payload item item = (quality_score, i, im) @@ -242,11 +272,39 @@ def _select(self, classes, reflections, distances): # after updating (pushing to) the heap. _ = heappushpop(self.heap, item) + # Now that we have computed the global quality_scores, + # the selection ordering can be applied + sorted_class_inds = np.argsort(self._quality_scores)[::-1] + self._quality_scores = self._quality_scores[sorted_class_inds] + return sorted_class_inds + + +class ClassRepulsion: + """ + Mixin to overload class selection based on excluding + classes we've alreay seen as neighbors of another class. + + If the classes are well sorted (by some measure of quality), + we can assume the best representation is the first seen. + + :param aggressive: Aggresive mode will additionally exclude + any new class containing a neighbor that has already + been incorporated. Defaults to True. + + """ + + def __init__(self, *args, aggressive=True, **kwargs): + """ + Instatiates and sets `aggresive`. All other args and **kwagrs are pass through to super(). + + :param aggressive: Aggresive mode will additionally exclude + any new class containing a neighbor that has already + been incorporated. Defaults to True. + """ -class PriorRejection: - def __init__(self, aggresive=True): self.excluded = set() - self.aggresive = bool(aggresive) + self.aggressive = aggressive + super().__init__(*args, **kwargs) def _select(self, classes, reflections, distances): # Get the indices sorted by the next resolving `_select` method. @@ -273,17 +331,28 @@ def _select(self, classes, reflections, distances): return np.array(results, dtype=int) + def _check_selection(self, selection, n_img): + """ + Check that class `selection` is sane. + + Repulsion can reduce the number of classes (dramatically). + + :param selection: selection indices + :param n_img: number of images available + """ + return super()._check_selection(selection, n_img, len_operator=le) + -class ContrastWithPriorClassSelector(PriorRejection, ContrastClassSelector): +class ContrastWithRepulsionClassSelector(ClassRepulsion, ContrastClassSelector): """ Selects top classes based on highest contrast with prior rejection. """ -class GlobalWithPriorClassSelector(PriorRejection, ClassSelector): +class GlobalWithRepulsionClassSelector(ClassRepulsion, GlobalClassSelector): """ Extends ClassSelector for methods that require - passing over all class average images and also PriorRejection. + passing over all class average images and also ClassRepulsion. """ @@ -330,10 +399,10 @@ def __call__(self, img): L = img.shape[-1] # Generate grid on first call so user can expect it exists. - self._grid_cache.setdefault(L, grid_2d(L, dtype=img.dype)) + self._grid_cache.setdefault(L, grid_2d(L, dtype=img.dtype)) # Call the function over img stack - res = np.fromiter(map(self.function, img), dtype=img.dtype) + res = np.fromiter(map(self._function, img), dtype=img.dtype) if stack_len == 1: res = res[0] From 0786f982e56b40897b81056221846504f9e400ad Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Feb 2023 15:58:13 -0500 Subject: [PATCH 166/424] Would make more sense if I added the file --- tests/test_class_src.py | 159 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 tests/test_class_src.py diff --git a/tests/test_class_src.py b/tests/test_class_src.py new file mode 100644 index 0000000000..07876a3e42 --- /dev/null +++ b/tests/test_class_src.py @@ -0,0 +1,159 @@ +import logging +import os +from itertools import product, repeat + +import numpy as np +import pytest + +from aspire.basis import FFBBasis2D +from aspire.classification import ( + BandedSNRImageQualityFunction, + BFRAverager2D, + ContrastClassSelector, + ContrastWithRepulsionClassSelector, + DistanceClassSelector, + GlobalClassSelector, + GlobalWithRepulsionClassSelector, + RandomClassSelector, + RIRClass2D, + TopClassSelector, +) +from aspire.denoising import DebugClassAvgSource, LegacyClassAvgSource +from aspire.source import Simulation +from aspire.utils import Rotation +from aspire.volume import Volume + +logger = logging.getLogger(__name__) + + +DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") + + +RESOLUTIONS = [32] +DTYPES = [np.float64] +CLS_SRCS = [DebugClassAvgSource, LegacyClassAvgSource] + + +def sim_fixture_id(params): + res = params[0] + dtype = params[1] + return f"res={res}, dtype={dtype.__name__}" + + +@pytest.fixture(params=product(RESOLUTIONS, DTYPES), ids=sim_fixture_id) +def class_sim_fixture(request): + """ + Construct a Simulation with explicit viewing angles forming + synthetic classes. + """ + + # Configuration + res, dtype = request.param + n_inplane_rots = 40 + + # Platonic solids can generate our views. + # Start with a cube, 8 vertices (use +-1 wlog), + # each represents an viewing axis. + cube_vertices = list(product(*repeat((-1, 1), 3))) + inplane_rots = np.linspace(0, 2 * np.pi, n_inplane_rots, endpoint=False) + # We want the first rotation to have angle 2pi instead of 0, + # so the norm isn't degenerate (0) later. + inplane_rots[0] = 2 * np.pi + logger.info(f"inplane_rots: {inplane_rots}") + + # Total rotations will be number of axis * number of angles + # ie. vertices * n_inplane_rots + n = len(cube_vertices) * n_inplane_rots + logger.info(f"Constructing {n} rotations.") + + # Generate Rotations + # Normalize the rotation axes to 1 + rotvecs = cube_vertices / np.linalg.norm(cube_vertices, axis=0) + logger.info(f"rotvecs: {rotvecs}") + # renormalize by broadcasting with angle amounts in inplane_rots + rotvecs = (rotvecs[np.newaxis].T * inplane_rots).T.reshape(n, 3) + # Construct rotation object + true_rots = Rotation.from_rotvec(rotvecs, dtype=dtype) + + # Load sample molecule volume + # TODO, probably our default volume should work for this stuff... tighter var? + v = Volume( + np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")), dtype=dtype + ).downsample(res) + + # Contruct the Simulation source + src = Simulation( + L=res, n=n, vols=v, offsets=0, amplitudes=1, C=1, angles=true_rots.angles + ) + # Prefetch all the images + src.cache() + + return src + + +@pytest.mark.parametrize( + "test_src_cls", CLS_SRCS, ids=lambda param: f"ClassSource={param}" +) +def test_run(class_sim_fixture, test_src_cls): + # Images from the original Source + orig_imgs = class_sim_fixture.images[:5] + + # Classify, Select, and average images using test_src_cls + test_src = test_src_cls(classification_src=class_sim_fixture) + test_imgs = test_src.images[:5] + + # Sanity check + assert np.allclose( + np.linalg.norm((orig_imgs - test_imgs).asnumpy(), axis=(1, 2)), 0, atol=0.001 + ) + + +@pytest.fixture() +def cls_fixture(class_sim_fixture): + """ + Classifier fixture. + """ + # Create the classifier + c2d = RIRClass2D(class_sim_fixture, nn_implementation="sklearn") + # Compute the classification + # (classes, reflections, distances) + return c2d.classify() + + +LOCAL_SELECTORS = [ + ContrastClassSelector, + ContrastWithRepulsionClassSelector, + DistanceClassSelector, + RandomClassSelector, + TopClassSelector, +] + + +@pytest.mark.parametrize( + "selector", LOCAL_SELECTORS, ids=lambda param: f"Selector={param}" +) +def test_custom_local_selector(cls_fixture, selector): + # classes, reflections, distances = cls_fixture + selection = selector().select(*cls_fixture) + logger.info(f"{selector}: {selection}") + + +GLOBAL_SELECTORS = [ + GlobalClassSelector, + GlobalWithRepulsionClassSelector, +] + + +@pytest.mark.parametrize( + "selector", GLOBAL_SELECTORS, ids=lambda param: f"Selector={param}" +) +def test_custom_global_selector(class_sim_fixture, cls_fixture, selector): + basis = FFBBasis2D(class_sim_fixture.L, dtype=class_sim_fixture.dtype) + + averager = BFRAverager2D(basis, class_sim_fixture, num_procs=1) + + fun = BandedSNRImageQualityFunction() + + # classes, reflections, distances = cls_fixture + selection = selector(averager, fun).select(*cls_fixture) + logger.info(f"{selector}: {selection}") From aaf91a18b12ea030945f06b70d0178195d2d334d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Feb 2023 09:04:54 -0500 Subject: [PATCH 167/424] Shake down bugs\n\n[skip ci] --- src/aspire/denoising/class_avg.py | 5 ++++- src/aspire/source/image.py | 11 +++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index a984c97503..ef965d8768 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -138,7 +138,7 @@ def _class_select(self): logger.info( f"After selection process, updating maximum {len(self.selection_indices)} classes from {self.n}." ) - self.n = len(self.selection_indices) + self._set_n(len(self.selection_indices)) self._selected = True @@ -151,6 +151,9 @@ def _images(self, indices): if not self._selected: self._class_select() + # Remap to the selected ordering + indices = self.selection_indices[indices] + # Check is there is a cache available from class selection component # Note, we can use := for this in the branch directly, but requires Python>=3.8 heap_inds = None diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 6a176c688e..ae4d7ed186 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -162,6 +162,17 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") + def _set_n(self, n): + """ + Sets max image index `n`. + + Used in sources that potentially reduce the set of images. + + :param n: Number of images. + """ + self._img_accessor.num_imgs = n + self.n = n + @property def n_ctf_filters(self): """ From 63adf780af8a7fe665352842d3fd18bc74292a9e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 06:52:02 -0500 Subject: [PATCH 168/424] Two minor patches (bulk averaging loop, typo) --- src/aspire/classification/class_selection.py | 4 +--- src/aspire/denoising/class_avg.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 2375a6eee8..63f773c765 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -253,9 +253,7 @@ def _heap_id_dict(self): } # heap item=(score, img_id, img) def _select(self, classes, reflections, distances): - for i in classes[:, 0]: - im = self.averager.average(classes[i], reflections[i]) - + for i, im in enumerate(self.averager.average(classes, reflections)): quality_score = self._quality_function(im) # Assign in global quality score array diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index ef965d8768..2f2a837422 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -159,7 +159,7 @@ def _images(self, indices): heap_inds = None if hasattr(self.class_selector, "heap"): # Then check if heap holds anything in indices - heap_inds = set(indices).intesection(self.class_selector._heap_ids) + heap_inds = set(indices).intersection(self.class_selector._heap_ids) # check for wholly cached image sets first if self._cached_im is not None: From 4d8d54f34e74966252b5ef0ab423cf171ed811e9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 08:03:42 -0500 Subject: [PATCH 169/424] Aggresive was too aggresive --- src/aspire/classification/class_selection.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 63f773c765..e1ad0bfc02 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -291,17 +291,18 @@ class ClassRepulsion: """ - def __init__(self, *args, aggressive=True, **kwargs): + def __init__(self, *args, **kwargs): """ Instatiates and sets `aggresive`. All other args and **kwagrs are pass through to super(). :param aggressive: Aggresive mode will additionally exclude any new class containing a neighbor that has already - been incorporated. Defaults to True. + been incorporated. Defaults to False. + Note this can dramatically reduce your class set. """ self.excluded = set() - self.aggressive = aggressive + self.aggressive = kwargs.pop("aggressive", False) super().__init__(*args, **kwargs) def _select(self, classes, reflections, distances): From 2118884bcbca3db7a3a7d90644e7ba8b62ec9af7 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 24 Feb 2023 09:05:05 -0500 Subject: [PATCH 170/424] reduce n_imgs to reduce test time and make 1 non-long-running test. --- src/aspire/abinitio/commonline_cn.py | 11 ++++------- tests/test_orient_symmetric.py | 7 +++++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 3e6ce6f07b..2d8f94a6ca 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -109,12 +109,11 @@ def _estimate_relative_viewing_directions(self): scores_self_corrs = np.zeros((n_img, n_cands), dtype=self.dtype) logger.info("Computing likelihood wrt self common-lines.") for i in trange(n_img): - pf_i = pf[i] pf_full_i = pf_full[i] # Generate shifted versions of image. pf_i_shifted = np.array( - [pf_i * shift_phase for shift_phase in all_shift_phases] + [pf[i] * shift_phase for shift_phase in all_shift_phases] ) pf_i_shifted = np.reshape(pf_i_shifted, (n_shifts * n_theta // 2, r_max)) @@ -122,7 +121,7 @@ def _estimate_relative_viewing_directions(self): pf_full_i[:, 0] = 0 pf_i_shifted[:, 0] = 0 - # Compute correlation of pf_i with itself over all shifts. + # Compute correlation of pf[i] with itself over all shifts. corrs = pf_i_shifted @ np.conj(pf_full_i).T corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) corrs_cands = np.max( @@ -133,7 +132,6 @@ def _estimate_relative_viewing_directions(self): # Remove candidates that are equator images. Equator candidates induce collinear # self common-lines, which always have perfect correlation. - # TODO: Should the threshold be parameter-dependent instead of set to 10 degrees? cii_equators_inds = np.argwhere( abs(np.arccos(Ris_tilde[:, 2, 2]) - np.pi / 2) < self.equator_threshold * np.pi / 180 @@ -157,11 +155,9 @@ def _estimate_relative_viewing_directions(self): with tqdm(total=n_vijs) as pbar: for i in range(n_img): - pf_i = pf[i] - # Generate shifted versions of the images. pf_i_shifted = np.array( - [pf_i * shift_phase for shift_phase in all_shift_phases] + [pf[i] * shift_phase for shift_phase in all_shift_phases] ) pf_i_shifted = np.reshape( pf_i_shifted, (n_shifts * n_theta // 2, r_max) @@ -372,6 +368,7 @@ def push(self, V): """ # Accumulate synchronized entries to compute synchronized mean. + # Method added Feb, 2023. (Josh Carmichael) if self.count == 0: self.V_estimates_sync += V else: diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index cd589f3088..0d1fff82b2 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -31,8 +31,11 @@ # For testing Cn methods where n>4. param_list_cn = [ + (44, 5, np.float32), pytest.param(44, 5, np.float32, marks=pytest.mark.expensive), pytest.param(45, 6, np.float64, marks=pytest.mark.expensive), + pytest.param(44, 8, np.float32, marks=pytest.mark.expensive), + pytest.param(45, 9, np.float64, marks=pytest.mark.expensive), ] @@ -75,7 +78,7 @@ def source_orientation_objs(L, n_img, order, dtype): def test_estimate_rotations(L, order, dtype): n_img = 24 if order > 4: - n_img = 32 + n_img = 9 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Estimate rotations. @@ -193,7 +196,7 @@ def test_relative_viewing_directions(L, order, dtype): # volume with C3 or C4 symmetry. n_img = 24 if order > 4: - n_img = 32 + n_img = 9 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Calculate ground truth relative viewing directions, viis and vijs. From 78d5ef0e2e0c88cc5b97cba4d1fe4eb8e35e11ae Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Feb 2023 10:01:05 -0500 Subject: [PATCH 171/424] breakout pink spectrum helper function --- tests/test_noise.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/tests/test_noise.py b/tests/test_noise.py index 600fc38d3b..9c636b54ea 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -61,6 +61,17 @@ def adder(request): return request.param +# Create the custom noise function and associated Filter +def _pinkish_spectrum(x, y): + """ + Util method for generating a pink like noise spectrum. + """ + s = x[-1] - x[-2] + f = 2 * s / (np.hypot(x, y) + s) + m = np.mean(f) + return f / m + + def test_white_noise_estimator_clean_corners(sim_fixture): """ Tests that a clean image yields a noise estimate that is virtually zero. @@ -120,14 +131,9 @@ def test_custom_noise_adder(sim_fixture, target_noise_variance): by generating a sample of the noise. """ - # Create the custom noise function and associated Filter - def pinkish_spectrum(x, y): - s = x[-1] - x[-2] - f = 2 * s / (np.hypot(x, y) + s) - m = np.mean(f) - return f * target_noise_variance / m - - custom_filter = FunctionFilter(f=pinkish_spectrum) + custom_filter = FunctionFilter(f=_pinkish_spectrum) * ScalarFilter( + value=target_noise_variance + ) # Create the CustomNoiseAdder sim_fixture.noise_adder = CustomNoiseAdder(noise_filter=custom_filter) @@ -208,14 +214,9 @@ def test_from_snr_pink_iso(sim_fixture, target_noise_variance): are close for a variety of paramaters. """ - # Create the custom noise function and associated Filter - def pinkish_spectrum(x, y): - s = x[-1] - x[-2] - f = 2 * s / (np.hypot(x, y) + s) - m = np.mean(f) - return f * target_noise_variance / m - - custom_filter = FunctionFilter(f=pinkish_spectrum) + custom_filter = FunctionFilter(f=_pinkish_spectrum) * ScalarFilter( + value=target_noise_variance + ) # Create the CustomNoiseAdder sim_fixture.noise_adder = CustomNoiseAdder(noise_filter=custom_filter) From f97431d562ed98e0ea8fdee27714fa948032dda7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 10:59:08 -0500 Subject: [PATCH 172/424] fix short circuit --- src/aspire/source/simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 25544c9d9e..3e10a6545c 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -480,7 +480,7 @@ def estimate_snr(self, *args, **kwargs): """ # For clean images return infinite SNR. # Note, relationship with CTF and other sim corruptions still isn't clear to me... - if self.noise_adder or self.noise_adder.noise_var == 0: + if self.noise_adder is None or self.noise_adder.noise_var == 0: return np.inf # For SNR of Simulations, use the theoretical noise variance From d5b6aefbe56f2a58c09e0d9e822927edcda0162b Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 24 Feb 2023 12:26:02 -0500 Subject: [PATCH 173/424] pre-compute all shifts by broadcasting --- src/aspire/abinitio/commonline_cn.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 2d8f94a6ca..4ff37506d3 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -105,17 +105,16 @@ def _estimate_relative_viewing_directions(self): pf /= norm(pf, axis=2)[..., np.newaxis] # Normalize each ray. pf_full = np.concatenate((pf, np.conj(pf)), axis=1) + # Pre-compute shifted pf's. + pf_shifted = (pf * all_shift_phases[:, None, None]).swapaxes(0, 1) + pf_shifted = pf_shifted.reshape((n_img, n_shifts * (n_theta // 2), r_max)) + # Step 1: pre-calculate the likelihood with respect to the self-common-lines. scores_self_corrs = np.zeros((n_img, n_cands), dtype=self.dtype) logger.info("Computing likelihood wrt self common-lines.") for i in trange(n_img): pf_full_i = pf_full[i] - - # Generate shifted versions of image. - pf_i_shifted = np.array( - [pf[i] * shift_phase for shift_phase in all_shift_phases] - ) - pf_i_shifted = np.reshape(pf_i_shifted, (n_shifts * n_theta // 2, r_max)) + pf_i_shifted = pf_shifted[i] # Ignore dc-component. pf_full_i[:, 0] = 0 @@ -155,13 +154,8 @@ def _estimate_relative_viewing_directions(self): with tqdm(total=n_vijs) as pbar: for i in range(n_img): - # Generate shifted versions of the images. - pf_i_shifted = np.array( - [pf[i] * shift_phase for shift_phase in all_shift_phases] - ) - pf_i_shifted = np.reshape( - pf_i_shifted, (n_shifts * n_theta // 2, r_max) - ) + # Grab shifted image. + pf_i_shifted = pf_shifted[i] # Ignore dc-component. pf_i_shifted[:, 0] = 0 @@ -272,7 +266,6 @@ def _compute_scls_inds(self, Ri_cands): scls_inds[i_cand, :, 1] = c2s_inds return scls_inds - # TODO: cache def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): """ Compute the common-lines indices induced by the candidate rotations. From e94db3c081d23fcb65a9ab7334a681076d49a3dd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 14:08:29 -0500 Subject: [PATCH 174/424] Refators towards NoiseAdder.from_snr --- src/aspire/noise/noise.py | 32 +++++++++++- src/aspire/source/simulation.py | 88 ++++++--------------------------- tests/test_noise.py | 68 ++++--------------------- 3 files changed, 54 insertions(+), 134 deletions(-) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index 1a4fcb7139..9650c68656 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -85,6 +85,17 @@ def noise_var(self, res=512): return np.mean(self._noise_filter.evaluate_grid(res)) +class DelayedWhiteNoiseAdder: + def __init__(self, snr, seed=0): + self.snr = snr + self.seed = seed + + def __call__(self, signal_power): + return WhiteNoiseAdder.from_snr( + snr=self.snr, signal_power=signal_power, seed=self.seed + ) + + class WhiteNoiseAdder(NoiseAdder): """ A Xform that adds white noise, optionally passed through a Filter object, to all incoming images. @@ -98,9 +109,29 @@ def __init__(self, var, seed=0): :param var: Target noise variance. :param seed: Optinally provide a random seed used to generate white noise. """ + self._noise_var = var super().__init__(noise_filter=ScalarFilter(dim=2, value=var), seed=seed) + @staticmethod + def from_snr(snr, signal_power=None, seed=0): + """ + Generates a WhiteNoiseAdder configured to produce a target signal to noise ratio. + + If signal_power is not provided, it returns a callable that will generate + the appropriate WhiteNoiseAdder later when provided `signal_power`. + + :param snr: Desired signal to noise ratio of + the returned source. + :param signal_variance: Optional, if the signal power is known. + :param seed: Optinally provide a random seed used to generate white noise. + """ + if signal_power is None: + return DelayedWhiteNoiseAdder(snr=snr, seed=seed) + else: + noise_var = signal_power / snr + return WhiteNoiseAdder(var=noise_var, seed=seed) + def __str__(self): return f"{self.__class__.__name__} with variance={self._noise_var}" @@ -133,7 +164,6 @@ def __init__(self, src, bgRadius=1, batchSize=512): the radius of disc inscribing a `(src.L, src.L)` image. :param batchSize: The size of the batches in which to compute the variance estimate """ - self.src = src self.dtype = self.src.dtype self.bgRadius = bgRadius diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 3e10a6545c..b4b341eedb 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -5,7 +5,7 @@ from scipy.linalg import eigh, qr from aspire.image import Image -from aspire.noise import NoiseAdder, WhiteNoiseAdder +from aspire.noise import NoiseAdder from aspire.source import ImageSource from aspire.source.image import _ImageAccessor from aspire.utils import ( @@ -13,7 +13,6 @@ ainner, anorm, make_symmat, - trange, uniform_random_angles, vecmat_to_volmat, ) @@ -134,10 +133,8 @@ def __init__( states = randi(self.C, n, seed=seed) self.states = states - self._uniform_random_angles = False if angles is None: angles = uniform_random_angles(n, seed=seed, dtype=self.dtype) - self._uniform_random_angles = True self.angles = angles if unique_filters is None: @@ -159,16 +156,24 @@ def __init__( self.offsets = offsets self.amplitudes = amplitudes + self._projections_accessor = _ImageAccessor(self._projections, self.n) + self._clean_images_accessor = _ImageAccessor(self._clean_images, self.n) + # For Simulation, signal can be computed directly from clean_images. + self._signal_images = self.clean_images + + # Note the delayed eval may attempt to use self.*_accessors if noise_adder is not None: logger.info(f"Appending {noise_adder} to generation pipeline") + # If we need to calculate signal_power from Simulation, + # do so now, and closing the DelayedWhiteNoiseAdder. + if callable(noise_adder): + noise_adder = noise_adder(signal_power=self.estimate_signal_power()) + + # At this point we should have a fully baked NoiseAdder if not isinstance(noise_adder, NoiseAdder): raise RuntimeError("`noise_adder` should be subclass of NoiseAdder") - self.noise_adder = noise_adder - self._projections_accessor = _ImageAccessor(self._projections, self.n) - self._clean_images_accessor = _ImageAccessor(self._clean_images, self.n) - # For Simulation, signal can be computed directly from clean_images. - self._signal_images = self.clean_images + self.noise_adder = noise_adder def _populate_ctf_metadata(self, filter_indices): # Since we are not reading from a starfile, we must construct @@ -486,68 +491,3 @@ def estimate_snr(self, *args, **kwargs): # For SNR of Simulations, use the theoretical noise variance # known from the noise_adder instead of deriving from PSD. return super().estimate_snr(noise_power=self.noise_adder.noise_var) - - @classmethod - def from_snr( - cls, - target_snr, - *args, - sample_n=None, - support_radius=None, - batch_size=512, - signal_power_method="estimate_signal_mean_energy", - **kwargs, - ): - """ - Generates a Simulation source with a WhiteNoiseAdder - configured to produce a target signal to noise ratio. - - :param target_snr: Desired signal to noise ratio of - the returned source. - :param sample_n: Number of images used for estimate. - Defaults to all images in source. - :param support_radius: Pixel radius used for masking signal support. - Default of None will compute inscribed circle, `self.L // 2`. - :param batch_size: Images per batch, defaults 512. - :param signal_power_method: Method used for computing signal energy. - Defaults to mean via `estimate_signal_mean_energy`. - Can use variance method via `estimate_signal_var`. - :returns: Simulation source. - """ - - # Create a Simulation - sim = cls(*args, **kwargs) - - # When a user supplies angles and requests `sample_n` subset of images - # randomize the rotations for them to avoid as much bias as possible. - # The original rotations stored in `_rots` will be restored later. - shuffle = (not sim._uniform_random_angles) and (sample_n is not None) - if shuffle: - # Store the original rotations. - _rots = sim.rotations.copy() - # Shuffle the Sim rotations - sim.rotations = sim.rotations[np.random.permutation(sim.n)] - - # Assert NoiseAdder has not been provided - if "noise_adder" in kwargs or sim.noise_adder is not None: - raise RuntimeError( - "Cannot provide 'noise_adder' when using {cls.__name__}.from_snr." - ) - - # Estimate the required noise variance - signal_var = sim.estimate_signal_power( - sample_n=sample_n, - support_radius=support_radius, - signal_power_method=signal_power_method, - ) - noise_var = signal_var / target_snr - - # Assign the noise_adder - sim.noise_adder = WhiteNoiseAdder(var=noise_var) - logger.info(f"Appended {sim.noise_adder} to generation pipeline") - - # Restore the original rotations when previously shuffled. - if shuffle: - sim.rotations = _rots - - return sim diff --git a/tests/test_noise.py b/tests/test_noise.py index 9c636b54ea..12b5b47dde 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -14,8 +14,7 @@ ) from aspire.operators import FunctionFilter, ScalarFilter from aspire.source.simulation import Simulation -from aspire.utils import grid_3d, uniform_random_angles -from aspire.volume import AsymmetricVolume, Volume +from aspire.volume import AsymmetricVolume DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -173,15 +172,12 @@ def test_from_snr_white(sim_fixture, target_noise_variance): # Attempt to create a new simulation at this `target_snr` # For unit testing, we will use `sim_fixture`'s volume, # but the new Simulation instance should yield different projections. - sim_from_snr = Simulation.from_snr( - target_snr, + sim_from_snr = Simulation( vols=sim_fixture.vols, # Force the previously generated volume. n=sim_fixture.n, offsets=0, amplitudes=1.0, - # Excercise the `sample_n` and shuffle branches - angles=uniform_random_angles(sim_fixture.n, dtype=sim_fixture.dtype), - sample_n=sim_fixture.n - 1, + noise_adder=WhiteNoiseAdder.from_snr(target_snr), ) # Check we're within 5% of explicit target @@ -221,40 +217,17 @@ def test_from_snr_pink_iso(sim_fixture, target_noise_variance): # Create the CustomNoiseAdder sim_fixture.noise_adder = CustomNoiseAdder(noise_filter=custom_filter) - # and compute the resulting snr of the sim. - target_snr = sim_fixture.estimate_snr() - - # Attempt to create a new simulation at this `target_snr` - # For unit testing, we will use `sim_fixture`'s volume, - # but the new Simulation instance should yield different projects. - sim_from_snr = Simulation.from_snr( - target_snr, - vols=sim_fixture.vols, # Force the previously generated volume. - n=sim_fixture.n, - offsets=0, - amplitudes=1.0, - ) - - # Check we're within 1% of explicit target - logger.info( - "sim_from_snr.noise_adder.noise_var, target_noise_variance =" - f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" - ) - assert np.isclose( - sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 - ) - # TODO, potentially remove or change to Isotropic after #842 # Compare with AnisotropicNoiseEstimator consuming sim_from_snr - noise_estimator = AnisotropicNoiseEstimator(sim_from_snr, batchSize=512) + noise_estimator = AnisotropicNoiseEstimator(sim_fixture, batchSize=512) est_noise_variance = noise_estimator.estimate() logger.info( "est_noise_variance, target_noise_variance =" f" {est_noise_variance}, {target_noise_variance}" ) - # Check we're within 1% - assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.01) + # Check we're within 5% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.05) @pytest.mark.parametrize( @@ -278,37 +251,14 @@ def aniso_spectrum(x, y): # Create the CustomNoiseAdder sim_fixture.noise_adder = CustomNoiseAdder(noise_filter=custom_filter) - # and compute the resulting snr of the sim. - target_snr = sim_fixture.estimate_snr() - - # Attempt to create a new simulation at this `target_snr` - # For unit testing, we will use `sim_fixture`'s volume, - # but the new Simulation instance should yield different projections. - sim_from_snr = Simulation.from_snr( - target_snr, - vols=sim_fixture.vols, # Force the previously generated volume. - n=sim_fixture.n, - offsets=0, - amplitudes=1.0, - ) - - # Check we're within 1% of explicit target - logger.info( - "sim_from_snr.noise_adder.noise_var, target_noise_variance =" - f" {sim_from_snr.noise_adder.noise_var}, {target_noise_variance}" - ) - assert np.isclose( - sim_from_snr.noise_adder.noise_var, target_noise_variance, rtol=0.01 - ) - # TODO, potentially remove after #842 # Compare with AnisotropicNoiseEstimator consuming sim_from_snr - noise_estimator = AnisotropicNoiseEstimator(sim_from_snr, batchSize=512) + noise_estimator = AnisotropicNoiseEstimator(sim_fixture, batchSize=512) est_noise_variance = noise_estimator.estimate() logger.info( "est_noise_variance, target_noise_variance =" f" {est_noise_variance}, {target_noise_variance}" ) - # Check we're within 1% - assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.01) + # Check we're within 5% + assert np.isclose(est_noise_variance, target_noise_variance, rtol=0.05) From b65cad24cb476712019a82e064a90acbdcad0c96 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 24 Feb 2023 14:20:18 -0500 Subject: [PATCH 175/424] Remove ignore DC-component as DC-component is already ignored in self.pf --- src/aspire/abinitio/commonline_cn.py | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 4ff37506d3..286c3ea953 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -113,15 +113,8 @@ def _estimate_relative_viewing_directions(self): scores_self_corrs = np.zeros((n_img, n_cands), dtype=self.dtype) logger.info("Computing likelihood wrt self common-lines.") for i in trange(n_img): - pf_full_i = pf_full[i] - pf_i_shifted = pf_shifted[i] - - # Ignore dc-component. - pf_full_i[:, 0] = 0 - pf_i_shifted[:, 0] = 0 - # Compute correlation of pf[i] with itself over all shifts. - corrs = pf_i_shifted @ np.conj(pf_full_i).T + corrs = pf_shifted[i] @ np.conj(pf_full[i]).T corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) corrs_cands = np.max( np.real(corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 @@ -154,20 +147,9 @@ def _estimate_relative_viewing_directions(self): with tqdm(total=n_vijs) as pbar: for i in range(n_img): - # Grab shifted image. - pf_i_shifted = pf_shifted[i] - - # Ignore dc-component. - pf_i_shifted[:, 0] = 0 - for j in range(i + 1, n_img): - pf_full_j = pf_full[j] - - # Ignore dc-component. - pf_full_j[:, 0] = 0 - # Compute correlation. - corrs_ij = pf_i_shifted @ np.conj(pf_full_j).T + corrs_ij = pf_shifted[i] @ np.conj(pf_full[j]).T # Max out over shifts. corrs_ij = np.max( From 2a3f5058e23f3e31a392569cdeecd9a0d62c29ae Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 24 Feb 2023 14:32:47 -0500 Subject: [PATCH 176/424] remove redundant np.real --- src/aspire/abinitio/commonline_cn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 286c3ea953..80f5142fa2 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -120,7 +120,7 @@ def _estimate_relative_viewing_directions(self): np.real(corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 ) - scores_self_corrs[i] = np.mean(np.real(corrs_cands), axis=1) + scores_self_corrs[i] = np.mean(corrs_cands, axis=1) # Remove candidates that are equator images. Equator candidates induce collinear # self common-lines, which always have perfect correlation. From b3276ca9a3e0e14a33e56f60f65230d284936f8e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Feb 2023 14:16:49 -0500 Subject: [PATCH 177/424] Pipeline demo updates --- gallery/tutorials/pipeline_demo.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index a5b12bef18..6ed2876953 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -90,13 +90,14 @@ def download(url, save_path, chunk_size=1024 * 1024): # Initialize Simulation Object # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # We feed our ``Volume`` and filters into ``Simulation`` to generate the dataset of images. -# When controlled white Gaussian noise is desired, ``Simulation.from_snr()`` +# When controlled white Gaussian noise is desired, ``*NoiseAdder.from_snr()`` # can be used to generate a simulation data set around a specific SNR. # # Alternatively, users can bring their own images using an ``ArrayImageSource``, # or define their own custom noise functions via ``Simulation(..., noise_adder=CustomNoiseAdder(...))``. # Examples can be found in ``tutorials/class_averaging.py`` and ``experiments/simulated_abinitio_pipeline.py``. +from aspire.noise import WhiteNoiseAdder from aspire.source import Simulation # set parameters @@ -109,13 +110,13 @@ def download(url, save_path, chunk_size=1024 * 1024): # For this ``Simulation`` we set all 2D offset vectors to zero, # but by default offset vectors will be randomly distributed. -src = Simulation.from_snr( - target_snr=snr, # desired SNR +src = Simulation( L=res, # resolution n=n_imgs, # number of projections vols=vol, # volume source offsets=np.zeros((n_imgs, 2)), # Default: images are randomly shifted unique_filters=ctf_filters, + noise_adder=WhiteNoiseAdder.from_snr(snr=snr), # desired SNR ) From f86b97968a1002b7f5c113eec4c71b9037a32abc Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 27 Feb 2023 09:14:49 -0500 Subject: [PATCH 178/424] generate candidate rotations on the fly excluding equator images. --- src/aspire/abinitio/commonline_cn.py | 49 +++++++++------------------- 1 file changed, 15 insertions(+), 34 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 80f5142fa2..5f30794020 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -5,7 +5,7 @@ from aspire.abinitio import CLSymmetryC3C4 from aspire.utils import J_conjugate, Rotation, anorm, cyclic_rotations, tqdm, trange -from aspire.utils.random import randn +from aspire.utils.random import Random, randn logger = logging.getLogger(__name__) @@ -122,14 +122,6 @@ def _estimate_relative_viewing_directions(self): scores_self_corrs[i] = np.mean(corrs_cands, axis=1) - # Remove candidates that are equator images. Equator candidates induce collinear - # self common-lines, which always have perfect correlation. - cii_equators_inds = np.argwhere( - abs(np.arccos(Ris_tilde[:, 2, 2]) - np.pi / 2) - < self.equator_threshold * np.pi / 180 - ) - scores_self_corrs[:, cii_equators_inds] = 0 - # Step 2: Compute the likelihood for each pair of candidate matrices with respect # to the common-lines they induce. logger.info("Computing pairwise likelihood.") @@ -288,8 +280,20 @@ def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): def _generate_cand_rots(self): logger.info("Generating candidate rotations.") # Construct candidate rotations, Ris_tilde. - vis = self._generate_cand_rots_third_rows() - Ris_tilde = np.array([self._complete_third_row_to_rot(vi) for vi in vis]) + Ris_tilde = np.zeros((self.n_points_sphere, 3, 3)) + counter = 0 + with Random(self.seed): + while counter < self.n_points_sphere: + third_row = randn(3) + third_row /= anorm(third_row, axes=(-1,)) + Ri_tilde = self._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. + angle_from_equator = abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) + if angle_from_equator >= self.equator_threshold * np.pi / 180: + Ris_tilde[counter] = Ri_tilde + counter += 1 # Construct all in-plane rotations, R_theta_ijs # The number of R_theta_ijs must be divisible by the symmetric order. @@ -299,29 +303,6 @@ def _generate_cand_rots(self): return Ris_tilde, R_theta_ijs - def _generate_cand_rots_third_rows(self, legacy=True): - n_points_sphere = self.n_points_sphere - if legacy: - # Genereate random points on the sphere - third_rows = randn(n_points_sphere, 3, seed=self.seed) - third_rows /= anorm(third_rows, axes=(-1,))[:, np.newaxis] - else: - # Use Fibonocci sphere points - third_rows = np.zeros((n_points_sphere, 3)) - phi = np.pi * (3.0 - np.sqrt(5.0)) # golden angle in radians - - for i in range(n_points_sphere): - y = 1 - (i / float(n_points_sphere - 1)) * 2 # y goes from 1 to -1 - radius = np.sqrt(1 - y * y) # radius at y - - theta = phi * i # golden angle increment - x = np.cos(theta) * radius - z = np.sin(theta) * radius - - third_rows[i] = x, y, z - - return third_rows - class MeanOuterProductEstimator: """ From 1e58cdd2b84d54599d7990de812b6df5e4dfb4c9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Feb 2023 12:05:59 -0500 Subject: [PATCH 179/424] xfail FLE test broken on windows (only on windows). --- tests/test_FLEbasis2D.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_FLEbasis2D.py b/tests/test_FLEbasis2D.py index d17f2e994c..9ea14d5658 100644 --- a/tests/test_FLEbasis2D.py +++ b/tests/test_FLEbasis2D.py @@ -1,4 +1,5 @@ import os +import sys import numpy as np import pytest @@ -101,6 +102,11 @@ def testFastVDense(self, basis): assert relerr(result_dense, result_fast) < basis.epsilon + @pytest.mark.xfail( + sys.platform == "win32", + reason="Bug on windows with latest envs, #862", + raises=RuntimeError, + ) def testEvaluateExpand(self, basis): if backend_available("cufinufft") and basis.epsilon == 1e-7: gpu_ci_skip() From 711101629e05850a19d6a0b35ad90a561e913ef5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Feb 2023 12:51:01 -0500 Subject: [PATCH 180/424] Breakout ImageStacker class --- src/aspire/classification/averager2d.py | 21 ++- src/aspire/image/__init__.py | 6 + src/aspire/image/image_stacker.py | 163 ++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 src/aspire/image/image_stacker.py diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index ad57ff513e..7a8530b1ab 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -7,7 +7,7 @@ from aspire import config from aspire.classification.reddy_chatterji import reddy_chatterji_register -from aspire.image import Image +from aspire.image import Image, ImageStacker, MeanImageStacker from aspire.utils import trange from aspire.utils.coor_trans import grid_2d from aspire.utils.multiprocessing import num_procs_suggestion @@ -107,12 +107,20 @@ class AligningAverager2D(Averager2D): """ def __init__( - self, composite_basis, src, alignment_basis=None, num_procs=None, dtype=None + self, + composite_basis, + src, + alignment_basis=None, + image_stacker=None, + num_procs=None, + dtype=None, ): """ :param composite_basis: Basis to be used during class average composition (eg hi res Cartesian/FFB2D). :param src: Source of original images. :param alignment_basis: Optional, basis to be used only during alignment (eg FSPCA). + :param image_stacker: Optional, provide a user defined `ImageStacker` instance, + used during image stacking (averaging). Defaults to MeanImageStacker. :param num_procs: Number of processes to use. `None` will attempt computing a suggestion based on machine resources. Note some underlying code may already use threading. @@ -128,6 +136,11 @@ def __init__( # If alignment_basis is None, use composite_basis self.alignment_basis = alignment_basis or self.composite_basis + # If image_stacker is None, use mean + self.image_stacker = image_stacker or MeanImageStacker() + if not isinstance(self.image_stacker, ImageStacker): + raise ValueError(f"`image_stacker` should be subclass of ImageStacker.") + if not hasattr(self.composite_basis, "rotate"): raise RuntimeError( f"{self.__class__.__name__}'s composite_basis {self.composite_basis} must provide a `rotate` method." @@ -211,7 +224,7 @@ def _innerloop(i): ) # Averaging in composite_basis - return np.mean(neighbors_coefs, axis=0) + return self.image_stacker(neighbors_coefs) if self.num_procs <= 1: for i in trange(n_classes): @@ -652,7 +665,7 @@ def _innerloop(i): ) # Averaging in composite_basis - return np.mean(neighbors_coefs, axis=0) + return self.image_stacker(neighbors_coefs) if self.num_procs <= 1: for i in trange(n_classes): diff --git a/src/aspire/image/__init__.py b/src/aspire/image/__init__.py index 33f40f9b61..319d5d55c4 100644 --- a/src/aspire/image/__init__.py +++ b/src/aspire/image/__init__.py @@ -7,3 +7,9 @@ PolarImage, normalize_bg, ) +from .image_stacker import ( + ImageStacker, + MeanImageStacker, + MedianImageStacker, + SigmaRejectionImageStacker, +) diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py new file mode 100644 index 0000000000..a8552d4789 --- /dev/null +++ b/src/aspire/image/image_stacker.py @@ -0,0 +1,163 @@ +import abc +import logging +from warnings import catch_warnings, filterwarnings, warn + +import numpy as np +import numpy.ma as ma + +from aspire.image import Image + +logger = logging.getLogger(__name__) + + +class ImageStacker(abc.ABC): + """ + Interface for image and coefficient stacking classes. + + It is assumed that provided images are already aligned. + Instances of `Stacker` act like functions. + + stacker = ImageStacker(...) + stacker(images_to_stack). + """ + + def __init__(self): + """ + Initialize ImageStacker instance. + """ + self._return_image = True + + @abc.abstractmethod + def __call__(self, stack): + """ + Stack the elements of `stack`. + + Stack admits an Image class, or an ndarray. + + In the case of ndarray, the data should be 2D + where the first (slow) dimension is stack axis, + and data is flattened to the last (fastest) axis. + In this case an Numpy array is returned. + + When passing `Image`, the stack_shape must be 1D. + In this case an `Image` is returned. + + :param stack: Image instance or ndarray. + """ + + def _check_and_convert(self, stack): + """ + Check stack and returns consistent 2D np.array. + """ + + # If we are an image, flatten image data. + if isinstance(stack, Image): + self._return_image = True + + if stack.stack_ndim != 1: + return ValueError( + f"`stack` shape of Image should be 1D for ImageStacking not {stack.stack_shape}." + " Try Image.stack_reshape if needed." + ) + stack = stack.asnumpy().reshape(stack.n_images, -1) + + # By this point we should alway be an np array with data in the last axis. + if not isinstance(stack, np.ndarray): + raise ValueError("`stack` should be `Image` instance or Numpy array.") + elif stack.ndim != 2: + return ValueError( + f"`stack` numpy array shape should be 2D for ImageStacking not {stack.shape}." + " Try Image.stack_reshape if needed." + ) + + # Store the stack shape. + self._stack_shape = stack.shape[0] + + return stack + + def _return(self, result): + """ + When ImageStacker has been passed an `Image`, + this method creates an `Image` instance to be returned. + + :param result: Result data as Numpy Array. + :return: Image(result) or Numpy array(result) based on initial call type. + """ + if self._return_image: + result = Image(result) + return result + + +class MeanImageStacker(ImageStacker): + """ + Stack using `mean`. + """ + + def __call__(self, stack): + stack = self._check_and_convert(stack) + + return stack.mean(axis=0) + + +class MedianImageStacker(ImageStacker): + """ + Stack using `median`. + """ + + def __call__(self, stack): + stack = self._check_and_convert(stack) + + self._return(stack.median(axis=0)) + + +class SigmaRejectionImageStacker(ImageStacker): + """ + Stack using Sigma Rejection. + """ + + def __init__(self, rejection_sigma=2): + """ + Instantiates SigmaRejectionImageStacker instance with + presribed `rejection_sigma`. + + If no outliers should return equivalent of `mean`. + In the presence of outliers, pixels outside of + `rejection_sigma` from the per-pixel-mean are discarded. + + :param rejection_sigma: Values outside `rejection_sigma` + """ + self.sigma = float(rejection_sigma) + + def __call__(self, stack): + stack = self._check_and_convert(stack) + + # Compute the mean and standard deviations, pixelwise + means = stack.mean(axis=0) + std_deviations = stack.std(axis=0) + + # Compute values that lie outside sigma deviations + outliers = np.abs(stack - mean) > self.sigma * std_deviations + + # Mask off the outliers + masked_stack = ma.masked_array(stack, mask=outliers) + + # Return mean withou the outliers + self._return(masked_stack.mean(axis=0)) + + +class PoissonRejectionImageStacker(ImageStacker): + """ + Stack using Poisson Rejection. + """ + + +class WinsorizedImageStacker(ImageStacker): + """ + Stack using Winsorized Sigma Rejection. + """ + + +class RobustChauvenetRejectionImageStacker(ImageStacker): + """ + Stack using Robust Chauvenet Outlier Rejection. + """ From 23a52dac3480860210ab2e4fc179dc20095f45b8 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 27 Feb 2023 15:01:26 -0500 Subject: [PATCH 181/424] refactor all_triplets using itertools.combinations. --- src/aspire/utils/misc.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index 20b4e39110..9bc50cffaf 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -335,9 +335,7 @@ def all_triplets(n): :param n: The number of items to be indexed. :returns: All 3-tuples (i,j,k), i Date: Mon, 27 Feb 2023 15:53:52 -0500 Subject: [PATCH 182/424] basic image_stacker tests --- src/aspire/image/image_stacker.py | 25 ++++---- tests/test_image_stacker.py | 97 +++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 tests/test_image_stacker.py diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index a8552d4789..45301c5950 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -25,7 +25,8 @@ def __init__(self): """ Initialize ImageStacker instance. """ - self._return_image = True + + self._return_image_size = None @abc.abstractmethod def __call__(self, stack): @@ -50,12 +51,14 @@ def _check_and_convert(self, stack): Check stack and returns consistent 2D np.array. """ + self._return_image_size = None # If we are an image, flatten image data. if isinstance(stack, Image): - self._return_image = True + # Store the image size for returning later + self._return_image_size = (1,) + stack.shape[1:] if stack.stack_ndim != 1: - return ValueError( + raise ValueError( f"`stack` shape of Image should be 1D for ImageStacking not {stack.stack_shape}." " Try Image.stack_reshape if needed." ) @@ -65,8 +68,8 @@ def _check_and_convert(self, stack): if not isinstance(stack, np.ndarray): raise ValueError("`stack` should be `Image` instance or Numpy array.") elif stack.ndim != 2: - return ValueError( - f"`stack` numpy array shape should be 2D for ImageStacking not {stack.shape}." + raise ValueError( + f"`stack` numpy array shape should be 2D for ImageStacker not {stack.shape}." " Try Image.stack_reshape if needed." ) @@ -83,8 +86,8 @@ def _return(self, result): :param result: Result data as Numpy Array. :return: Image(result) or Numpy array(result) based on initial call type. """ - if self._return_image: - result = Image(result) + if self._return_image_size is not None: + result = Image(result.reshape(self._return_image_size)) return result @@ -96,7 +99,7 @@ class MeanImageStacker(ImageStacker): def __call__(self, stack): stack = self._check_and_convert(stack) - return stack.mean(axis=0) + return self._return(stack.mean(axis=0)) class MedianImageStacker(ImageStacker): @@ -107,7 +110,7 @@ class MedianImageStacker(ImageStacker): def __call__(self, stack): stack = self._check_and_convert(stack) - self._return(stack.median(axis=0)) + return self._return(np.median(stack, axis=0)) class SigmaRejectionImageStacker(ImageStacker): @@ -136,13 +139,13 @@ def __call__(self, stack): std_deviations = stack.std(axis=0) # Compute values that lie outside sigma deviations - outliers = np.abs(stack - mean) > self.sigma * std_deviations + outliers = np.abs(stack - means) > self.sigma * std_deviations # Mask off the outliers masked_stack = ma.masked_array(stack, mask=outliers) # Return mean withou the outliers - self._return(masked_stack.mean(axis=0)) + return self._return(masked_stack.mean(axis=0)) class PoissonRejectionImageStacker(ImageStacker): diff --git a/tests/test_image_stacker.py b/tests/test_image_stacker.py new file mode 100644 index 0000000000..3c8f2c920b --- /dev/null +++ b/tests/test_image_stacker.py @@ -0,0 +1,97 @@ +import itertools +import logging + +import numpy as np +import pytest + +from aspire.image import ( + Image, + MeanImageStacker, + MedianImageStacker, + SigmaRejectionImageStacker, +) + +logger = logging.getLogger(__name__) + +SAMPLE_N = 32 +IMAGE_SIZES = [8, 9] +DTYPES = [np.float32, np.float64] + + +def xx_fixture_id(params): + image_size = params[0] + dtype = params[1] + return f"image_size={image_size}, dtype={dtype.__name__}" + + +@pytest.fixture(params=itertools.product(IMAGE_SIZES, DTYPES), ids=xx_fixture_id) +def xx_fixture(request): + # unpack fixture params + img_size, dtype = request.param + + # Generate test data. + A = np.ones((SAMPLE_N, img_size, img_size), dtype=dtype) + + # Force outliers + for i in range(2, 7): + A[0, i - 1] = i + + return Image(A) + + +def test_mean_stacker(xx_fixture): + img_np = xx_fixture.asnumpy() + ref_result = img_np.mean(axis=0) + n = img_np.shape[0] + + stacker = MeanImageStacker() + + # Check stacker(Image(...)) == numpy + stacked = stacker(xx_fixture) + assert isinstance(stacked, Image) + assert np.allclose(stacked.asnumpy(), ref_result) + + # Check stacker(ndarray) == numpy + stacked = stacker(xx_fixture.asnumpy().reshape(n, -1)) + assert isinstance(stacked, np.ndarray) + assert np.allclose(stacked, ref_result.reshape(1, -1)) + + +def test_mean_stacker(xx_fixture): + img_np = xx_fixture.asnumpy() + ref_result = img_np.mean(axis=0) + n = img_np.shape[0] + + stacker = MeanImageStacker() + + # Check stacker(Image(...)) == numpy + stacked = stacker(xx_fixture) + assert isinstance(stacked, Image) + assert np.allclose(stacked.asnumpy(), ref_result) + + # Check stacker(ndarray) == numpy + stacked = stacker(xx_fixture.asnumpy().reshape(n, -1)) + assert isinstance(stacked, np.ndarray) + assert np.allclose(stacked, ref_result.reshape(1, -1)) + + +def test_median_stacker(xx_fixture): + img_np = xx_fixture.asnumpy() + + stacker = MedianImageStacker() + + stacked = stacker(xx_fixture) + + # Median should ignore the single outliers + assert np.allclose(stacked.asnumpy(), 1) + + +def test_sigma_stacker(xx_fixture): + img_np = xx_fixture.asnumpy() + + stacker = SigmaRejectionImageStacker() + + stacked = stacker(xx_fixture) + + # Sigma should ignore the single outliers + assert np.allclose(stacked.asnumpy(), 1) From 6cbd18c9fe481fc9bc8c3de3d976ad49145fb4b1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Feb 2023 14:01:32 -0500 Subject: [PATCH 183/424] Aggresive mode not practical. --- src/aspire/classification/class_selection.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index e1ad0bfc02..c362878002 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -285,10 +285,6 @@ class ClassRepulsion: If the classes are well sorted (by some measure of quality), we can assume the best representation is the first seen. - :param aggressive: Aggresive mode will additionally exclude - any new class containing a neighbor that has already - been incorporated. Defaults to True. - """ def __init__(self, *args, **kwargs): @@ -302,7 +298,6 @@ def __init__(self, *args, **kwargs): """ self.excluded = set() - self.aggressive = kwargs.pop("aggressive", False) super().__init__(*args, **kwargs) def _select(self, classes, reflections, distances): @@ -318,11 +313,6 @@ def _select(self, classes, reflections, distances): # Get the images in this class cls = classes[i] - # Aggresive mode skips any class containing neighbors - # we have have seen in prior class. - if self.aggressive and self.excluded.intersection(cls[1:]): - continue - # Add images from this class to the exclusion list self.excluded.update(cls) From faa8d05fa1ce7ad38f5f09efddb7f6ee2fb6d8f0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Feb 2023 14:38:00 -0500 Subject: [PATCH 184/424] Change the repulsion to be a bit more like the legacy code --- src/aspire/classification/class_selection.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index c362878002..d37121a309 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -277,6 +277,8 @@ def _select(self, classes, reflections, distances): return sorted_class_inds +# TODO: When a consistent measure of distance is implemented +# by preceeding components we can implement exclusion based on neighbor distances. class ClassRepulsion: """ Mixin to overload class selection based on excluding @@ -289,14 +291,15 @@ class ClassRepulsion: def __init__(self, *args, **kwargs): """ - Instatiates and sets `aggresive`. All other args and **kwagrs are pass through to super(). + Sets optional `exclude_k`. All other args and **kwagrs are passed to super(). - :param aggressive: Aggresive mode will additionally exclude - any new class containing a neighbor that has already - been incorporated. Defaults to False. - Note this can dramatically reduce your class set. - """ + ClassRepulsion is similar to `cryo_select_subset` from MATLAB, + but MATLAB found `exclude_k` iteratively based on a desired result set size. + :param exclude_k: Number of neighbors from each class to exclude. + Defaults to + """ + self.exclude_k = kwargs.pop("exclude_k", None) self.excluded = set() super().__init__(*args, **kwargs) @@ -304,6 +307,9 @@ def _select(self, classes, reflections, distances): # Get the indices sorted by the next resolving `_select` method. sorted_inds = super()._select(classes, reflections, distances) + # If exclude_k is not provided, default to exluding all neighbors. + k = self.exclude_k or classes.shape[-1] + results = [] for i in sorted_inds: # Skip when this class's base image has been seen in a prior class. @@ -314,7 +320,7 @@ def _select(self, classes, reflections, distances): cls = classes[i] # Add images from this class to the exclusion list - self.excluded.update(cls) + self.excluded.update(cls[:k]) results.append(i) From 652a7500ebf9c8a5a644982bf3b402c46e4e4fb6 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 27 Feb 2023 15:56:36 -0500 Subject: [PATCH 185/424] comment documenting vii estimate conflicting method --- src/aspire/abinitio/commonline_cn.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 5f30794020..2e9adffcc6 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -203,6 +203,12 @@ def _estimate_relative_viewing_directions(self): c += 1 pbar.update() + # There are conflicting methods betweeen the paper and the Matlab code for + # finding the optimal estimate. The paper suggests using SVD to find the + # estimate for vii which is closest to rank 1. The Matlab code takes the + # median over the stack of all estimates for vii. Here we have implemented + # a method which J-synchronizes the estimates prior to taking the mean. + # See issue #869 for more details. viis[i] = mean_est[i].synchronized_mean() return vijs, viis From aa196b0e8b5218c703544e25b0c57e73ead0e999 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Feb 2023 16:03:49 -0500 Subject: [PATCH 186/424] tox checks --- src/aspire/classification/averager2d.py | 2 +- src/aspire/image/image_stacker.py | 1 - tests/test_image_stacker.py | 22 ---------------------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 7a8530b1ab..3921ed516e 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -139,7 +139,7 @@ def __init__( # If image_stacker is None, use mean self.image_stacker = image_stacker or MeanImageStacker() if not isinstance(self.image_stacker, ImageStacker): - raise ValueError(f"`image_stacker` should be subclass of ImageStacker.") + raise ValueError("`image_stacker` should be subclass of ImageStacker.") if not hasattr(self.composite_basis, "rotate"): raise RuntimeError( diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index 45301c5950..8a167a6701 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -1,6 +1,5 @@ import abc import logging -from warnings import catch_warnings, filterwarnings, warn import numpy as np import numpy.ma as ma diff --git a/tests/test_image_stacker.py b/tests/test_image_stacker.py index 3c8f2c920b..6ee80e761d 100644 --- a/tests/test_image_stacker.py +++ b/tests/test_image_stacker.py @@ -57,27 +57,7 @@ def test_mean_stacker(xx_fixture): assert np.allclose(stacked, ref_result.reshape(1, -1)) -def test_mean_stacker(xx_fixture): - img_np = xx_fixture.asnumpy() - ref_result = img_np.mean(axis=0) - n = img_np.shape[0] - - stacker = MeanImageStacker() - - # Check stacker(Image(...)) == numpy - stacked = stacker(xx_fixture) - assert isinstance(stacked, Image) - assert np.allclose(stacked.asnumpy(), ref_result) - - # Check stacker(ndarray) == numpy - stacked = stacker(xx_fixture.asnumpy().reshape(n, -1)) - assert isinstance(stacked, np.ndarray) - assert np.allclose(stacked, ref_result.reshape(1, -1)) - - def test_median_stacker(xx_fixture): - img_np = xx_fixture.asnumpy() - stacker = MedianImageStacker() stacked = stacker(xx_fixture) @@ -87,8 +67,6 @@ def test_median_stacker(xx_fixture): def test_sigma_stacker(xx_fixture): - img_np = xx_fixture.asnumpy() - stacker = SigmaRejectionImageStacker() stacked = stacker(xx_fixture) From e55a5f703ee32f184605f1381fdde14766526ac6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 08:01:09 -0500 Subject: [PATCH 187/424] Typo oversight in ctf test --- tests/test_CtfEstimate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_CtfEstimate.py b/tests/test_CtfEstimate.py index 2bd9e438bf..ef5e02deff 100644 --- a/tests/test_CtfEstimate.py +++ b/tests/test_CtfEstimate.py @@ -74,8 +74,8 @@ def testEstimateCTF(self): # defocusV self.assertTrue( np.allclose( - result["defocus_u"], - self.test_output["defocus_u"], + result["defocus_v"], + self.test_output["defocus_v"], rtol=0.05, ) ) From ecfcb63b3e0a771e0915b96818f9af9130e92169 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 08:11:57 -0500 Subject: [PATCH 188/424] rir should store classes etc once computed --- src/aspire/classification/rir_class2d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 7739fcbd28..06f5e0324f 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -185,7 +185,7 @@ def classify(self, diagnostics=False): # # Stage 2: Compute Nearest Neighbors logger.info(f"Calculate Nearest Neighbors using {self._nn_implementation}.") - classes, reflections, distances = self.nn_classification(coef_b, coef_b_r) + self.classes, self.reflections, self.distances = self.nn_classification(coef_b, coef_b_r) if diagnostics: # Lets peek at the distribution of distances @@ -199,7 +199,7 @@ def classify(self, diagnostics=False): f" {100 * np.mean(reflections) } %" ) - return classes, reflections, distances + return self.classes, self.reflections, self.distances def pca(self, M): """ From e53f53274f83bae0ab7316fa2eff7ada5eb128bc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 08:26:43 -0500 Subject: [PATCH 189/424] Add small note to CTF gallery about astigmatism amounts. --- gallery/tutorials/ctf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 33652f82de..576927066a 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -62,6 +62,8 @@ # For the general ``CTFFilter``, # we provide defocus along two perpendicular axes u and v separately, # along with the angle the u-axis makes with the horizontal (x) axis. +# Note that we chose an extreme astigmatism for demonstration, +# and the values more typically differ by a few percent. ctf_filter = CTFFilter( pixel_size=1, # Angstrom From d094f419ddd17794e2501215d31510e32adeefd3 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 08:19:50 -0500 Subject: [PATCH 190/424] migrate legacy NN code from corr to dist=1-corr --- src/aspire/classification/rir_class2d.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 06f5e0324f..2e8540cb58 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -185,7 +185,9 @@ def classify(self, diagnostics=False): # # Stage 2: Compute Nearest Neighbors logger.info(f"Calculate Nearest Neighbors using {self._nn_implementation}.") - self.classes, self.reflections, self.distances = self.nn_classification(coef_b, coef_b_r) + self.classes, self.reflections, self.distances = self.nn_classification( + coef_b, coef_b_r + ) if diagnostics: # Lets peek at the distribution of distances @@ -315,22 +317,28 @@ def _legacy_nn_classification(self, coeff_b, coeff_b_r): classes = np.zeros((n_im, n_nbor), dtype=int) distances = np.zeros((n_im, n_nbor), dtype=self.dtype) + norm_concat_coeff = np.linalg.norm(concat_coeff) for i in trange(num_batches): start = i * self.batch_size finish = min((i + 1) * self.batch_size, n_im) - corr = np.real( - np.dot(np.conjugate(coeff_b[:, start:finish]).T, concat_coeff) - ) + batch = np.conjugate(coeff_b[:, start:finish]) + corr = (np.real(np.dot(batch.T, concat_coeff)) + / (np.linalg.norm(batch) * norm_concat_coeff)) + assert np.all(np.abs(corr)<=1), f"Corr out of [-1,1] bounds {np.min(corr)} {np.max(corr)}." + # Note legacy did not include the original image? # classes[start:finish] = np.argsort(-corr, axis=1)[:, 1 : n_nbor + 1] # This now does include the original image # (Matches sklean implementation.) - # Check with Joakim about preference. - # I (GBW) think class[i] should have class[i][0] be the original image index. - classes[start:finish] = np.argsort(-corr, axis=1)[:, :n_nbor] + # + # Also we've converted from correlation to distance=1-correlation + # https://github.com/ComputationalCryoEM/ASPIRE-Python/discussions/867 + dist = 1 - corr + + classes[start:finish] = np.argsort(dist, axis=1)[:, :n_nbor] # Store the corr values for the n_nbors in this batch distances[start:finish] = np.take_along_axis( - corr, classes[start:finish], axis=1 + dist, classes[start:finish], axis=1 ) # There were two sets of vectors each n_img long. From 8cca95ef21bfe67e7f326e3651ca7ea1d7b7b641 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 09:24:24 -0500 Subject: [PATCH 191/424] Expose the selection --- src/aspire/classification/class_selection.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index d37121a309..7de52a236c 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -80,14 +80,14 @@ def select(self, classes, reflections, distances): self._max_n = np.max(classes[:, 0]) # Call the selection logic - selection = self._select(classes, reflections, distances) + self.selection = self._select(classes, reflections, distances) # n_img should not exceed the largest index in first column of `classes` n_img = np.max(classes[:, 0]) + 1 # Check values in selection are in bounds. - self._check_selection(selection, n_img) + self._check_selection(self.selection, n_img) - return selection + return self.selection def _check_selection(self, selection, n_img, len_operator=eq): """ From 2ed63a96a99787109258424e67b704a2ece7c432 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 09:24:38 -0500 Subject: [PATCH 192/424] Use matlab sampling default for RIR --- src/aspire/classification/rir_class2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 2e8540cb58..ccf98498e3 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -21,7 +21,7 @@ def __init__( pca_basis=None, fspca_components=None, alpha=1 / 3, - sample_n=4000, + sample_n=50000, # Paper had 4000, but MATLAB code suggested 50000 bispectrum_components=300, n_nbor=100, bispectrum_freq_cutoff=None, From 6c4717b37e7811276c64d8cc9d6a6e2b546388c2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 09:38:05 -0500 Subject: [PATCH 193/424] Add stub for bandpass class selection implemented but unused in MATLAB --- src/aspire/classification/class_selection.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 7de52a236c..5315762879 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -416,6 +416,17 @@ def _function(self, img, center_radius=0.5, outer_band_start=0.8, outer_band_end return np.var(img[center_mask]) / np.var(img[outer_mask]) +class BandpassImageQualityFunction(ImageQualityFunction): + """ + Replicate behavior of MATLAB `cryo_sort_stack_bandpass` method. + """ + + # TODO, sort by polar Fourier bandpass of Image. + # Probably requires Polar2D to work with both even and odds.. + # Note, we may derive a similar method for non global use using bispectrum + # (if we do bookkeeping during compression). + + class WeightedImageQualityFunction(ImageQualityFunction): """ A callable image quality scoring function using a radial grid weighted function. From 2ac73e20dd4333c690917d3d6cb717dde3854855 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 28 Feb 2023 13:54:47 -0500 Subject: [PATCH 194/424] Use element-wise multiplication for J_conjugate --- src/aspire/utils/misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index 9bc50cffaf..a9511e9096 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -347,9 +347,9 @@ def J_conjugate(A): :param A: A 3x3 matrix. :return: J*A*J """ - J = np.diag((-1, -1, 1)) + J = np.array([[1, 1, -1], [1, 1, -1], [-1, -1, 1]], dtype=A.dtype) - return J @ A @ J + return A * J def cyclic_rotations(order, dtype=np.float64): From 35821c1e7ceee88206a110ccbe7f55a7b3f350e1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Feb 2023 15:32:48 -0500 Subject: [PATCH 195/424] Exclude bounds from CTFEstimator linprog Co-authored-by: Josh Carmichael --- src/aspire/ctf/ctf_estimator.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 6b03cea572..033856bce3 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -291,7 +291,7 @@ def background_subtract_1d( Estimate and subtract the background from the power spectrum :param amplitude_spectrum: Estimated power spectrum - :param linprog_method: Method passed to linear progam solver (scipy.optimize.linprog). Defaults to 'interior-point'. + :param linprog_method: Method passed to linear progam solver (scipy.optimize.linprog). :param n_low_freq_cutoffs: Low frequency cutoffs (loop iterations). :return: 2-tuple of NumPy arrays (PSD after noise subtraction and estimated noise) """ @@ -358,20 +358,22 @@ def background_subtract_1d( axis=0, ) - x_bound_lst = [ - (signal[i], signal[i], -1 * np.inf, np.inf) - for i in range(signal.shape[0]) - ] - x_bound = np.asarray(x_bound_lst, A.dtype) - x_bound = np.concatenate((x_bound[:, :2], x_bound[:, 2:]), axis=0) + # The original code used `bounds`, + # but for many problems, linprog reports infeasable constraints. + # In practice for a micrograph from the paper, and our tutorial, + # the code seems to work better without it... + # ASPIRE #417 x = linprog( f, A_ub=A, b_ub=np.zeros(A.shape[0]), - bounds=x_bound, method=linprog_method, ) + + if not x.success: + raise RuntimeError("Linear program did not succeed. Halting") + background = x.x[N:] bs_psd = signal - background From 3580ef26803a647305a27e3c880bafec8cc50b97 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 1 Mar 2023 08:13:40 -0500 Subject: [PATCH 196/424] spelling typo, progam --- src/aspire/ctf/ctf_estimator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 033856bce3..f1f7ade793 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -291,7 +291,7 @@ def background_subtract_1d( Estimate and subtract the background from the power spectrum :param amplitude_spectrum: Estimated power spectrum - :param linprog_method: Method passed to linear progam solver (scipy.optimize.linprog). + :param linprog_method: Method passed to linear program solver (scipy.optimize.linprog). :param n_low_freq_cutoffs: Low frequency cutoffs (loop iterations). :return: 2-tuple of NumPy arrays (PSD after noise subtraction and estimated noise) """ From 9091eee6afa3c06d4d86fba4f928bbf2b15c9ecf Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 1 Mar 2023 11:11:11 -0500 Subject: [PATCH 197/424] Docsting and comment cleanup. --- src/aspire/noise/noise.py | 37 +++++++++++++++++++++++++++------ src/aspire/source/image.py | 4 ---- src/aspire/source/simulation.py | 6 ++++-- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index 9650c68656..9d28024d54 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -85,13 +85,38 @@ def noise_var(self, res=512): return np.mean(self._noise_filter.evaluate_grid(res)) -class DelayedWhiteNoiseAdder: - def __init__(self, snr, seed=0): +class _DelayedNoiseAdder: + """ + Helper class for delaying the calculation of noise variance. + """ + + def __init__(self, cls, snr, seed=0): + """ + Inializes and returns a callable instance. + + :param cls: Class to be constructed when called. + :param snr: Target signal to noise ratio. + :param seed: Optional RNG seed used by NoiseAdder. + """ + self.cls = cls self.snr = snr self.seed = seed + if not issubclass(cls, NoiseAdder): + raise RuntimeError( + f"_DelayedNoiseAdder.cls must be a NoiseAdder, received {cls}" + ) + def __call__(self, signal_power): - return WhiteNoiseAdder.from_snr( + """ + Callable returns a completed NoiseAdder. + + :param signal_power: Estimated signal power. + Used to compute target noise. + + :return: Instance of NoiseAdder. + """ + return self.cls.from_snr( snr=self.snr, signal_power=signal_power, seed=self.seed ) @@ -123,11 +148,11 @@ def from_snr(snr, signal_power=None, seed=0): :param snr: Desired signal to noise ratio of the returned source. - :param signal_variance: Optional, if the signal power is known. + :param signal_power: Optional, if the signal power is known. :param seed: Optinally provide a random seed used to generate white noise. """ if signal_power is None: - return DelayedWhiteNoiseAdder(snr=snr, seed=seed) + return _DelayedNoiseAdder(WhiteNoiseAdder, snr=snr, seed=seed) else: noise_var = signal_power / snr return WhiteNoiseAdder(var=noise_var, seed=seed) @@ -162,7 +187,7 @@ def __init__(self, src, bgRadius=1, batchSize=512): :param bgRadius: The radius of the disk whose complement is used to estimate the noise. Radius is relative proportion, where `1` represents the radius of disc inscribing a `(src.L, src.L)` image. - :param batchSize: The size of the batches in which to compute the variance estimate + :param batchSize: The size of the batches in which to compute the variance estimate. """ self.src = src self.dtype = self.src.dtype diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index a89b5a70d1..39a1c6ff85 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -832,8 +832,6 @@ def estimate_signal_mean_energy( mask = support_mask(self.L, support_radius, dtype=self.dtype) # mean is estimated batch-wise, compare with numpy - # Note, for simulation we are implicitly assuming taking `sample_n` is random, - # but this does not need to be the case. We can add a `random_shuffle` param. s = 0.0 _denom = sample_n * np.sum(mask) for i in trange(0, sample_n, batch_size): @@ -872,8 +870,6 @@ def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512 # Var is estimated batch-wise, compare with numpy # np_estimated_var = np.var(self._signal_images[:sample_n].asnumpy()[..., mask]) - # Note, for simulation we are implicitly assuming taking `sample_n` is random, - # but this does not need to be the case. We can add a `random_shuffle` param. first_moment = 0.0 second_moment = 0.0 _denom = sample_n * np.sum(mask) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index b4b341eedb..fcbdf075bc 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -161,11 +161,13 @@ def __init__( # For Simulation, signal can be computed directly from clean_images. self._signal_images = self.clean_images - # Note the delayed eval may attempt to use self.*_accessors + # If a user prescribed NoiseAdder.from_snr(...), + # noise_adder will be a function returning a completed class. + # Note the delayed eval may attempt to use self.*_accessors above. if noise_adder is not None: logger.info(f"Appending {noise_adder} to generation pipeline") # If we need to calculate signal_power from Simulation, - # do so now, and closing the DelayedWhiteNoiseAdder. + # do so now (via _DelayedNoiseAdder). if callable(noise_adder): noise_adder = noise_adder(signal_power=self.estimate_signal_power()) From 10b71589f5c6b3cde272819b09fe5f1084915520 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 1 Mar 2023 15:36:28 -0500 Subject: [PATCH 198/424] Update simulated pipeline --- .../simulated_abinitio_pipeline.py | 39 ++++------ gallery/tutorials/class_averaging.py | 72 ++++++++++++++----- gallery/tutorials/pipeline_demo.py | 34 ++------- src/aspire/classification/class_selection.py | 2 +- src/aspire/classification/rir_class2d.py | 15 ++-- src/aspire/denoising/__init__.py | 2 +- src/aspire/denoising/class_avg.py | 53 ++++++++++---- tests/test_class_src.py | 4 +- 8 files changed, 128 insertions(+), 93 deletions(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 59820cfffc..abc62e168b 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -22,12 +22,11 @@ from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis2D, FFBBasis3D -from aspire.classification import BFSReddyChatterjiAverager2D, RIRClass2D -from aspire.denoising import DenoiserCov2D +from aspire.denoising import ClassicClassAvgSource, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator, CustomNoiseAdder from aspire.operators import FunctionFilter, RadialCTFFilter from aspire.reconstruction import MeanEstimator -from aspire.source import Simulation +from aspire.source import ArrayImageSource, Simulation from aspire.utils.coor_trans import ( get_aligned_rotations, get_rots_mse, @@ -157,36 +156,27 @@ def noise_function(x, y): if interactive: classification_src.images[:10].show() - # Use regular `src` for the alignment and composition (averaging). - composite_basis = FFBBasis2D((src.L,) * 2, dtype=src.dtype) - custom_averager = BFSReddyChatterjiAverager2D(composite_basis, src, dtype=src.dtype) - - # %% # Class Averaging # ---------------------- # # Now perform classification and averaging for each class. +# This also demonstrates the potential to use a different source for classification and averaging. -logger.info("Begin Class Averaging") - -rir = RIRClass2D( - classification_src, # Source used for classification - fspca_components=400, - bispectrum_components=300, # Compressed Features after last PCA stage. +avgs_src = ClassicClassAvgSource( + classification_src, n_nbor=n_nbor, - n_classes=n_classes, - large_pca_implementation="legacy", - nn_implementation="sklearn", - bispectrum_implementation="legacy", - averager=custom_averager, + averager_src=src, + num_procs=None, # Automaticaly configure parallel processing ) -classes, reflections, distances = rir.classify() -avgs = rir.averages(classes, reflections, distances) +# We'll manually cache `n_classes` worth to speed things up. +avgs = ArrayImageSource(avgs_src.images[:n_classes]) + if interactive: avgs.images[:10].show() + # %% # Common Line Estimation # ---------------------- @@ -196,9 +186,10 @@ def noise_function(x, y): logger.info("Begin Orientation Estimation") -# Stash true rotations for later comparison, -# note this line only works with naive class selection... -true_rotations = src.rotations[:n_classes] +# Stash true rotations for later comparison. +# Note class selection re-ordered our images, so we remap the indices back to the original source. +indices = avgs_src.selection_indices +true_rotations = src.rotations[indices[:n_classes]] orient_est = CLSyncVoting(avgs, n_theta=36) # Get the estimated rotations diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 8c9ecaff0b..1db5138d41 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -11,7 +11,7 @@ import numpy as np from PIL import Image as PILImage -from aspire.denoising import LegacyClassAvgSource +from aspire.denoising import ClassicClassAvgSource, DebugClassAvgSource from aspire.image import Image from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. @@ -101,19 +101,35 @@ # Class Average # ------------- # -# We use the ASPIRE ``RIRClass2D`` class to classify the images via the rotationally invariant representation (RIR) -# algorithm. We then yield class averages by performing ``classify``. +# We use the ``DebugClassAvgSource`` to classify the images +# via the rotationally invariant representation (``RIRClass2D``) algorithm. +# ``DebugClassAvgSource`` internally uses ``TopClassSelector`` by default. +# ``TopClassSelector`` deterministically selects the first ``n_classes``. +# ``DebugClassAvgSource`` also uses brute force rotational alignment without shifts. +# These simplifications are useful for development and debugging. +# Later we will use the more general purpose ``ClassicClassAvgSource``, +# more suitable to simulations and experimental datasets. +# Furthermore, total customization can be achieved by instantiating your own components, +# and combining them using the generic ``ClassAvgSource``. + n_classes = 10 -avgs = LegacyClassAvgSource( +avgs = DebugClassAvgSource( classification_src=src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors ) +# .. note: +# ``ClassAvgSource``s are lazily evaluated. +# They will generally compute the classifications, selections, +# and serve averaged results on request using the `.images[...]`. + + # %% # Display Classes # ^^^^^^^^^^^^^^^ +# Now we will request the first 10 images and display them. avgs.images[:10].show() @@ -145,11 +161,10 @@ # %% # RIR with Noise # ^^^^^^^^^^^^^^ -# Internally this uses scikit-learn for NN and ``TopClassSelector``. -# ``TopClassSelector`` will deterministically select the first ``n_classes``. -# This is useful for development and debugging. +# Here we will use the more advanced ``ClassicClassAvgSource`` that might be used with +# complete ``Simulation``s or experimental data sources. -avgs = LegacyClassAvgSource( +avgs = ClassicClassAvgSource( classification_src=noisy_src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors @@ -158,6 +173,14 @@ # %% # Display Classes # ^^^^^^^^^^^^^^^ +# Here a little more work occurs, as the ``ClassicClassAvgSource`` will +# compute an internal measure quality (contrast), +# and avoid images already included in higher ranking classes. +# All this occurs inside the ``ClassAvgSource`` component. +# When using more advanced class average sources, +# the images are remapped by the `selector`. +# So in this case, the first 10 images will be those with the highest contrast, +# that we have not already seen. avgs.images[:10].show() @@ -166,21 +189,31 @@ # Review a class # -------------- # -# Select a class to review. +# Select a class to review in the output. review_class = 5 -# Display the original image. -noisy_src.images[review_class].show() +# Map this image from the sorted selection back to the input ``noisy_src``. -# Report the identified neighbor indices -classes = avgs._nn_classes -reflections = avgs._nn_reflections +# Report the identified neighbor indices with respect to the input ``noise_src``. +classes = avgs._nn_classes[review_class] +reflections = avgs._nn_reflections[review_class] logger.info(f"Class {review_class}'s neighors: {classes[review_class]}") -# Report the identified neighbors -Image(noisy_src.images[:][classes[review_class]]).show() +# The original image is the initial image in the class array. +original_image_idx = classes[0] + +# %% +# Report the identified neighbors, original is the first image. + +noisy_src.images[classes].show() + +# %% +# Display original image. +noisy_src.images[original_image_idx].show() + +# %% # Display the averaged result avgs.images[review_class].show() @@ -203,8 +236,8 @@ # Compare the original unaligned images with the estimated alignment. # Get the indices from the classification results. nbr = 3 -original_img_0_idx = classes[review_class][0] -original_img_nbr_idx = classes[review_class][nbr] +original_img_0_idx = classes[0] +original_img_nbr_idx = classes[nbr] # Lookup the images. original_img_0 = noisy_src.images[original_img_0_idx].asnumpy()[0] @@ -212,7 +245,8 @@ # Rotate using estimated rotations. angle = est_rotations[0, nbr] * 180 / np.pi -if reflections[review_class][nbr]: +if reflections[nbr]: + logger.info("Reflection reported.") original_img_nbr = np.flipud(original_img_nbr) rotated_img_nbr = np.asarray(PILImage.fromarray(original_img_nbr).rotate(angle)) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 5b2b007681..052547d9c1 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -149,18 +149,15 @@ def download(url, save_path, chunk_size=1024 * 1024): # representation (RIR) algorithm. Class selection is customizable. The classification module # also includes a set of protocols for selecting a set of images to be used for classification. # Here we're using ``TopClassSelector``, which selects the first ``n_classes`` images from the source. +# In practice, the selection is done by sorting based on some configurable notion of quality. -from aspire.classification import ( - BFSReddyChatterjiAverager2D, - RIRClass2D, - TopClassSelector, -) +from aspire.classification import RIRClass2D, TopClassSelector # set parameters n_classes = 200 n_nbor = 6 -# Create a class averaging instance. Note that the ``fspca_components`` and +# We will customize our class averaging source. Note that the ``fspca_components`` and # ``bispectrum_components`` were selected for this small tutorial. rir = RIRClass2D( src, @@ -169,36 +166,19 @@ def download(url, save_path, chunk_size=1024 * 1024): n_nbor=n_nbor, ) -from aspire.basis import FFBBasis2D -from aspire.classification import BFSReddyChatterjiAverager2D - -averager = BFSReddyChatterjiAverager2D( - FFBBasis2D(src.L, dtype=src.dtype), - src, - num_procs=1, # Change to "auto" if your machine has many processors - dtype=rir.dtype, -) +from aspire.denoising import DebugClassAvgSource -from aspire.denoising import ClassAvgSource - -avgs = ClassAvgSource( +avgs = DebugClassAvgSource( classification_src=src, classifier=rir, - class_selector=TopClassSelector(), - averager=averager, ) -# For a small example, it is more effective to just cache these now. -# avgs.cache() +# For this small example, it is most effective to just cache these manually. + from aspire.source import ArrayImageSource avgs = ArrayImageSource(avgs.images[:n_classes]) -# classify and average -# classes, reflections, distances = rir.classify() -# avgs = rir.averages(classes, reflections, distances) -# avgs = cls_src.images[:n_classes] - # %% # View the Class Averages # ----------------------- diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 5315762879..f47544cbd9 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -103,7 +103,7 @@ def _check_selection(self, selection, n_img, len_operator=eq): ) # Check indices [0, n_img) - if np.max(selection) >= n_img or np.min(selection) < 0: + if (np.max(selection) >= n_img) or (np.min(selection) < 0): raise ValueError( f"Class selection out of bounds [0, {n_img - 1}]" f"with [{np.min(selection)}, {np.max(selection)}]" diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index ccf98498e3..ae5016cd0a 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -192,13 +192,13 @@ def classify(self, diagnostics=False): if diagnostics: # Lets peek at the distribution of distances # zero index is self, distance 0, ignored - plt.hist(distances[:, 1:].flatten(), bins="auto") + plt.hist(self.distances[:, 1:].flatten(), bins="auto") plt.show() # Report some information about reflections logger.info( - f"Count reflected: {np.sum(reflections)}" - f" {100 * np.mean(reflections) } %" + f"Count reflected: {np.sum(self.reflections)}" + f" {100 * np.mean(self.reflections) } %" ) return self.classes, self.reflections, self.distances @@ -322,9 +322,12 @@ def _legacy_nn_classification(self, coeff_b, coeff_b_r): start = i * self.batch_size finish = min((i + 1) * self.batch_size, n_im) batch = np.conjugate(coeff_b[:, start:finish]) - corr = (np.real(np.dot(batch.T, concat_coeff)) - / (np.linalg.norm(batch) * norm_concat_coeff)) - assert np.all(np.abs(corr)<=1), f"Corr out of [-1,1] bounds {np.min(corr)} {np.max(corr)}." + corr = np.real(np.dot(batch.T, concat_coeff)) / ( + np.linalg.norm(batch) * norm_concat_coeff + ) + assert np.all( + np.abs(corr) <= 1 + ), f"Corr out of [-1,1] bounds {np.min(corr)} {np.max(corr)}." # Note legacy did not include the original image? # classes[start:finish] = np.argsort(-corr, axis=1)[:, 1 : n_nbor + 1] diff --git a/src/aspire/denoising/__init__.py b/src/aspire/denoising/__init__.py index 72c565f4a6..4d87ee1d84 100644 --- a/src/aspire/denoising/__init__.py +++ b/src/aspire/denoising/__init__.py @@ -1,5 +1,5 @@ from .adaptive_support import adaptive_support -from .class_avg import ClassAvgSource, DebugClassAvgSource, LegacyClassAvgSource +from .class_avg import ClassAvgSource, ClassicClassAvgSource, DebugClassAvgSource from .denoised_src import DenoisedImageSource from .denoiser import Denoiser from .denoiser_cov2d import DenoiserCov2D, src_wiener_coords diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 2f2a837422..bddbe6afc0 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -6,9 +6,10 @@ from aspire.classification import ( Averager2D, BFRAverager2D, - BFSReddyChatterjiAverager2D, + BFSRAverager2D, Class2D, ClassSelector, + ContrastWithRepulsionClassSelector, RIRClass2D, TopClassSelector, ) @@ -107,6 +108,11 @@ def _classify(self): ) = self.classifier.classify() self._classified = True + @property + def selection_indices(self): + self._class_select() + return self._selection_indices + def _class_select(self): """ Uses the `class_selector` in conjunction with the classifier results @@ -126,7 +132,7 @@ def _class_select(self): self._classify() # Perform class selection - self.selection_indices = self.class_selector.select( + self._selection_indices = self.class_selector.select( self._nn_classes, self._nn_reflections, self._nn_distances, @@ -134,11 +140,11 @@ def _class_select(self): # Override the initial self.n # Some selectors will (dramitcally) reduce the space of classes. - if len(self.selection_indices) != self.n: + if len(self._selection_indices) != self.n: logger.info( - f"After selection process, updating maximum {len(self.selection_indices)} classes from {self.n}." + f"After selection process, updating maximum {len(self._selection_indices)} classes from {self.n}." ) - self._set_n(len(self.selection_indices)) + self._set_n(len(self._selection_indices)) self._selected = True @@ -220,6 +226,11 @@ def _images(self, indices): return self.generation_pipeline.forward(im, indices) +# The following sub classes attempt to pack sensible defaults +# into ClassAvgSource so that users don't need to +# instantiate every component. + + class DebugClassAvgSource(ClassAvgSource): """ Source for denoised 2D images using class average methods. @@ -269,21 +280,26 @@ def __init__( ) -class LegacyClassAvgSource(ClassAvgSource): +class ClassicClassAvgSource(ClassAvgSource): """ Source for denoised 2D images using class average methods. - Packs base with common v9 and v10 defaults. + Defaults to using Contrast based class selection (on the fly, compressed), + avoiding neighbors of previous classes, + and a brute force image alignment. + + Currently this is the most reasonable default for experimental data. """ def __init__( self, classification_src, - n_nbor=10, - num_procs=1, # Change to "auto" if your machine has many processors + n_nbor=50, + num_procs=None, classifier=None, class_selector=None, averager=None, + averager_src=None, ): dtype = classification_src.dtype @@ -299,13 +315,24 @@ def __init__( ) if averager is None: - basis_2d = FFBBasis2D(classification_src.L, dtype=dtype) - averager = BFSReddyChatterjiAverager2D( - basis_2d, classification_src, num_procs=num_procs, dtype=dtype + if averager_src is None: + averager_src = classification_src + + basis_2d = FFBBasis2D(averager_src.L, dtype=dtype) + + averager = BFSRAverager2D( + composite_basis=basis_2d, + src=averager_src, + num_procs=num_procs, + dtype=dtype, + ) + elif averager_src is not None: + raise RuntimeError( + "When providing an instantiated `averager`, cannot assign `averager_src`." ) if class_selector is None: - class_selector = TopClassSelector() + class_selector = ContrastWithRepulsionClassSelector() super().__init__( classification_src=classification_src, diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 07876a3e42..7df61469df 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -18,7 +18,7 @@ RIRClass2D, TopClassSelector, ) -from aspire.denoising import DebugClassAvgSource, LegacyClassAvgSource +from aspire.denoising import ClassicClassAvgSource, DebugClassAvgSource from aspire.source import Simulation from aspire.utils import Rotation from aspire.volume import Volume @@ -31,7 +31,7 @@ RESOLUTIONS = [32] DTYPES = [np.float64] -CLS_SRCS = [DebugClassAvgSource, LegacyClassAvgSource] +CLS_SRCS = [DebugClassAvgSource, ClassicClassAvgSource] def sim_fixture_id(params): From fce2521eba39e697b8b502db38dfaa818095e136 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 08:36:10 -0500 Subject: [PATCH 199/424] Rename towards versioned ClassAvgSource --- gallery/experiments/simulated_abinitio_pipeline.py | 4 ++-- gallery/tutorials/class_averaging.py | 10 +++++----- src/aspire/denoising/__init__.py | 2 +- src/aspire/denoising/class_avg.py | 2 +- tests/test_class_src.py | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index abc62e168b..03340a6130 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -22,7 +22,7 @@ from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis2D, FFBBasis3D -from aspire.denoising import ClassicClassAvgSource, DenoiserCov2D +from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator, CustomNoiseAdder from aspire.operators import FunctionFilter, RadialCTFFilter from aspire.reconstruction import MeanEstimator @@ -163,7 +163,7 @@ def noise_function(x, y): # Now perform classification and averaging for each class. # This also demonstrates the potential to use a different source for classification and averaging. -avgs_src = ClassicClassAvgSource( +avgs_src = ClassAvgSourcev11( classification_src, n_nbor=n_nbor, averager_src=src, diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 1db5138d41..76783fe620 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -11,7 +11,7 @@ import numpy as np from PIL import Image as PILImage -from aspire.denoising import ClassicClassAvgSource, DebugClassAvgSource +from aspire.denoising import ClassAvgSourcev11, DebugClassAvgSource from aspire.image import Image from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. @@ -107,7 +107,7 @@ # ``TopClassSelector`` deterministically selects the first ``n_classes``. # ``DebugClassAvgSource`` also uses brute force rotational alignment without shifts. # These simplifications are useful for development and debugging. -# Later we will use the more general purpose ``ClassicClassAvgSource``, +# Later we will use the more general purpose ``ClassAvgSourcev11``, # more suitable to simulations and experimental datasets. # Furthermore, total customization can be achieved by instantiating your own components, # and combining them using the generic ``ClassAvgSource``. @@ -161,10 +161,10 @@ # %% # RIR with Noise # ^^^^^^^^^^^^^^ -# Here we will use the more advanced ``ClassicClassAvgSource`` that might be used with +# Here we will use the more advanced ``ClassAvgSourcev11`` that might be used with # complete ``Simulation``s or experimental data sources. -avgs = ClassicClassAvgSource( +avgs = ClassAvgSourcev11( classification_src=noisy_src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors @@ -173,7 +173,7 @@ # %% # Display Classes # ^^^^^^^^^^^^^^^ -# Here a little more work occurs, as the ``ClassicClassAvgSource`` will +# Here a little more work occurs, as the ``ClassAvgSourcev11`` will # compute an internal measure quality (contrast), # and avoid images already included in higher ranking classes. # All this occurs inside the ``ClassAvgSource`` component. diff --git a/src/aspire/denoising/__init__.py b/src/aspire/denoising/__init__.py index 4d87ee1d84..7228a9c633 100644 --- a/src/aspire/denoising/__init__.py +++ b/src/aspire/denoising/__init__.py @@ -1,5 +1,5 @@ from .adaptive_support import adaptive_support -from .class_avg import ClassAvgSource, ClassicClassAvgSource, DebugClassAvgSource +from .class_avg import ClassAvgSource, ClassAvgSourcev11, DebugClassAvgSource from .denoised_src import DenoisedImageSource from .denoiser import Denoiser from .denoiser_cov2d import DenoiserCov2D, src_wiener_coords diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index bddbe6afc0..095743a317 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -280,7 +280,7 @@ def __init__( ) -class ClassicClassAvgSource(ClassAvgSource): +class ClassAvgSourcev11(ClassAvgSource): """ Source for denoised 2D images using class average methods. diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 7df61469df..ce54ed269e 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -18,7 +18,7 @@ RIRClass2D, TopClassSelector, ) -from aspire.denoising import ClassicClassAvgSource, DebugClassAvgSource +from aspire.denoising import ClassAvgSourcev11, DebugClassAvgSource from aspire.source import Simulation from aspire.utils import Rotation from aspire.volume import Volume @@ -31,7 +31,7 @@ RESOLUTIONS = [32] DTYPES = [np.float64] -CLS_SRCS = [DebugClassAvgSource, ClassicClassAvgSource] +CLS_SRCS = [DebugClassAvgSource, ClassAvgSourcev11] def sim_fixture_id(params): From b8e987fd4091affafab786d4517757091ae9d3e8 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 09:04:21 -0500 Subject: [PATCH 200/424] Use smaller translation radius. --- src/aspire/classification/averager2d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 3921ed516e..ec2335a481 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -416,7 +416,7 @@ def __init__( :params n_angles: Number of brute force rotations to attempt, defaults 360. :param radius: Brute force translation search radius. - Defaults to src.L//8. + Defaults to src.L//16. """ super().__init__( composite_basis, @@ -427,7 +427,7 @@ def __init__( dtype=dtype, ) - self.radius = radius if radius is not None else src.L // 8 + self.radius = radius if radius is not None else src.L // 16 # Each shift will require calling the parent BFRAverager2D.align self._bfr_align = super().align From 6ccf0da61533eeb188acaca7b5219848e599958a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 09:11:24 -0500 Subject: [PATCH 201/424] tox checks --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- gallery/tutorials/class_averaging.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 03340a6130..3c2732eb3f 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -21,7 +21,7 @@ import numpy as np from aspire.abinitio import CLSyncVoting -from aspire.basis import FFBBasis2D, FFBBasis3D +from aspire.basis import FFBBasis3D from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator, CustomNoiseAdder from aspire.operators import FunctionFilter, RadialCTFFilter diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 76783fe620..4757d1f4fe 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -12,7 +12,6 @@ from PIL import Image as PILImage from aspire.denoising import ClassAvgSourcev11, DebugClassAvgSource -from aspire.image import Image from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. from aspire.utils import gaussian_2d From 01e5fad79249d97699de0b34c7d53adfe6fa095a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 09:12:33 -0500 Subject: [PATCH 202/424] update comment --- src/aspire/classification/class_selection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index f47544cbd9..681f6fcc92 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -422,7 +422,7 @@ class BandpassImageQualityFunction(ImageQualityFunction): """ # TODO, sort by polar Fourier bandpass of Image. - # Probably requires Polar2D to work with both even and odds.. + # This was implemented in MATLAB, but appears unused. # Note, we may derive a similar method for non global use using bispectrum # (if we do bookkeeping during compression). From 2a087cf149960d87a34cc3b1ef2671692af57020 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 2 Mar 2023 10:58:34 -0500 Subject: [PATCH 203/424] static generate_cand_rots. Rank-1 approximation for viis. Hand picked test Volume. Exclude equator images in test Simulation. Replace MSE with angular distance. --- src/aspire/abinitio/commonline_cn.py | 34 ++++++--- tests/test_orient_symmetric.py | 104 +++++++++++++-------------- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 2e9adffcc6..d67b11785c 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -87,7 +87,13 @@ def _estimate_relative_viewing_directions(self): # Generate candidate rotation matrices and the common-line and # self-common-line indices induced by those rotations. - Ris_tilde, R_theta_ijs = self._generate_cand_rots() + Ris_tilde, R_theta_ijs = self.generate_cand_rots( + self.n_points_sphere, + self.equator_threshold, + self.order, + self.degree_res, + self.seed, + ) cijs_inds = self._compute_cls_inds(Ris_tilde, R_theta_ijs) scls_inds = self._compute_scls_inds(Ris_tilde) n_cands = len(Ris_tilde) @@ -211,7 +217,14 @@ def _estimate_relative_viewing_directions(self): # See issue #869 for more details. viis[i] = mean_est[i].synchronized_mean() - return vijs, viis + # As we are using a mean to get the estimates, viis, the estimate will not be rank-1 + # So we use SVD to find a close rank-1 approximation. + U, S, V = np.linalg.svd(viis) + S_rank1 = np.zeros((n_img, 3, 3), dtype=self.dtype) + S_rank1[:, 0, 0] = S[:, 0] + viis_rank1 = U @ S_rank1 @ V + + return vijs, viis_rank1 def _compute_scls_inds(self, Ri_cands): """ @@ -283,28 +296,29 @@ def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): pbar.update() return cij_inds - def _generate_cand_rots(self): + @staticmethod + def generate_cand_rots(n, equator_threshold, order, degree_res, seed): logger.info("Generating candidate rotations.") # Construct candidate rotations, Ris_tilde. - Ris_tilde = np.zeros((self.n_points_sphere, 3, 3)) + Ris_tilde = np.zeros((n, 3, 3)) counter = 0 - with Random(self.seed): - while counter < self.n_points_sphere: + with Random(seed): + while counter < n: third_row = randn(3) third_row /= anorm(third_row, axes=(-1,)) - Ri_tilde = self._complete_third_row_to_rot(third_row) + Ri_tilde = CLSymmetryC3C4._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. angle_from_equator = abs(np.arccos(Ri_tilde[2, 2]) - np.pi / 2) - if angle_from_equator >= self.equator_threshold * np.pi / 180: + if angle_from_equator >= equator_threshold * np.pi / 180: Ris_tilde[counter] = Ri_tilde counter += 1 # Construct all in-plane rotations, R_theta_ijs # The number of R_theta_ijs must be divisible by the symmetric order. - n_theta_ij = 360 - (360 % self.order) - theta_ij = np.arange(0, n_theta_ij, self.degree_res) * np.pi / 180 + n_theta_ij = 360 - (360 % order) + theta_ij = np.arange(0, n_theta_ij, degree_res) * np.pi / 180 R_theta_ijs = Rotation.about_axis("z", theta_ij).matrices return Ris_tilde, R_theta_ijs diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 913106c096..0281f70923 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -7,12 +7,8 @@ from aspire.abinitio.commonline_cn import MeanOuterProductEstimator from aspire.source import Simulation from aspire.utils import Rotation, utest_tolerance -from aspire.utils.coor_trans import ( - get_aligned_rotations, - get_rots_mse, - register_rotations, -) -from aspire.utils.misc import J_conjugate, all_pairs, cyclic_rotations, pairs_to_linear +from aspire.utils.coor_trans import get_aligned_rotations, register_rotations +from aspire.utils.misc import J_conjugate, all_pairs, cyclic_rotations from aspire.utils.random import randn from aspire.volume import CnSymmetricVolume @@ -34,6 +30,7 @@ (44, 5, np.float32), pytest.param(44, 5, np.float32, marks=pytest.mark.expensive), pytest.param(45, 6, np.float64, marks=pytest.mark.expensive), + pytest.param(44, 7, np.float32, marks=pytest.mark.expensive), pytest.param(44, 8, np.float32, marks=pytest.mark.expensive), pytest.param(45, 9, np.float64, marks=pytest.mark.expensive), ] @@ -41,21 +38,42 @@ # Method to instantiate a Simulation source and orientation estimation object. def source_orientation_objs(L, n_img, order, dtype): - seed = 1 + # This Volume is hand picked to have a fairly even distribution of energy. + # Due to the rotations used to generate symmetric volumes, some seeds will + # generate volumes with a high concentration of energy in the center causing + # misidentification of common-lines. vol = CnSymmetricVolume( L=L, C=1, + K=25, order=order, - seed=seed, + seed=65, dtype=dtype, ).generate() + angles = None + if order > 4: + # We artificially exclude equator images from the simulation as they will be + # incorrectly identified by the CL method. We keep images slightly further away + # from being equator images than the 10 degree default threshold used in the CL method. + rotations, _ = CLSymmetryCn.generate_cand_rots( + n=n_img, + equator_threshold=15, + order=order, + degree_res=1, + seed=123, # Generate different rotations than candidates used in CL method. + ) + angles = Rotation(rotations).angles + + seed = 1 src = Simulation( L=L, n=n_img, - offsets=np.zeros((n_img, 2)), + offsets=0, + amplitudes=1, dtype=dtype, vols=vol, + angles=angles, C=1, seed=seed, ) @@ -78,7 +96,7 @@ def source_orientation_objs(L, n_img, order, dtype): def test_estimate_rotations(L, order, dtype): n_img = 24 if order > 4: - n_img = 9 + n_img = 8 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Estimate rotations. @@ -88,31 +106,30 @@ def test_estimate_rotations(L, order, dtype): # Ground truth rotations. rots_gt = src.rotations - # For order>4 we cannot expect estimates from equator images to be accurate. - # So we exclude those from testing. - if order > 4: - equator_inds = np.argwhere( - abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) - < cl_symm.equator_threshold * np.pi / 180 - ) - - # Exclude equator estimates and ground truths. - rots_est = np.delete(rots_est, equator_inds, axis=0) - rots_gt = np.delete(rots_gt, equator_inds, axis=0) - # g-synchronize ground truth rotations. rots_gt_sync = cl_symm.g_sync(rots_est, order, rots_gt) - # Register estimates to ground truth rotations and compute MSE. + # Register estimates to ground truth rotations and compute the + # angular distance between them (in degrees). Q_mat, flag = register_rotations(rots_est, rots_gt_sync) regrot = get_aligned_rotations(rots_est, Q_mat, flag) - mse_reg = get_rots_mse(regrot, rots_gt_sync) + ang_dist = np.zeros(n_img, dtype=dtype) + for i in range(n_img): + ang_dist[i] = ( + Rotation.angle_dist( + regrot[i], + rots_gt_sync[i], + dtype=dtype, + ) + * 180 + / np.pi + ) - # Assert mse is small. + # Assert mean angular distance is reasonable. if order > 4: - assert mse_reg < 0.015 + assert np.mean(ang_dist) < 5 else: - assert mse_reg < 0.005 + assert np.mean(ang_dist) < 2 @pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) @@ -196,7 +213,7 @@ def test_relative_viewing_directions(L, order, dtype): # volume with C3 or C4 symmetry. n_img = 24 if order > 4: - n_img = 9 + n_img = 8 src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Calculate ground truth relative viewing directions, viis and vijs. @@ -253,29 +270,6 @@ def test_relative_viewing_directions(L, order, dtype): (theta_vii, theta_vii_J, np.pi - theta_vii, np.pi - theta_vii_J), axis=0 ) - # For order>4 we cannot expect estimates from equator images to be accurate. - # So we exclude those from testing. - if order > 4: - equator_inds = np.argwhere( - abs(np.arccos(rots_gt[:, 2, 2]) - np.pi / 2) - < cl_symm.equator_threshold * np.pi / 180 - ) - - # Exclude ii estimates and ground truths. - min_theta_vii = np.delete(min_theta_vii, equator_inds, axis=0) - sii = np.delete(sii, equator_inds, axis=0) - - # Exclude ij estimates and ground truths. - matches = np.equal( - equator_inds[:, None], pairs - ) # Find where equator_inds match pairs indices - bad_pairs = pairs[ - np.sum(matches, axis=(0, 2)).astype(bool) - ] # Take pairs where at least 1 index matches - pairwise_equator_inds = pairs_to_linear(n_img, bad_pairs[:, 0], bad_pairs[:, 1]) - min_theta_vij = np.delete(min_theta_vij, pairwise_equator_inds, axis=0) - sij = np.delete(sij, pairwise_equator_inds, axis=0) - # Calculate the mean minimum angular distance. angular_dist_vijs = np.mean(min_theta_vij) angular_dist_viis = np.mean(min_theta_vii) @@ -287,17 +281,17 @@ def test_relative_viewing_directions(L, order, dtype): max_tol_ii = 1e-5 mean_tol_ii = 1e-5 if order > 4: - max_tol_ii = 5e-2 + max_tol_ii = 8e-2 mean_tol_ii = 2e-2 - assert np.max(error_ij) < 2e-1 + assert np.max(error_ij) < 4e-1 assert np.max(error_ii) < max_tol_ii - assert np.mean(error_ij) < 2e-3 + assert np.mean(error_ij) < 3e-3 assert np.mean(error_ii) < mean_tol_ii # Check that the mean angular difference is within 2 degrees. angle_tol = 2 * np.pi / 180 if order > 4: - angle_tol = 6 * np.pi / 180 + angle_tol = 4 * np.pi / 180 assert angular_dist_vijs < angle_tol assert angular_dist_viis < angle_tol From a3c68d003fecd582250dd0233acaebde15c34c88 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 2 Mar 2023 11:16:52 -0500 Subject: [PATCH 204/424] docstring --- src/aspire/abinitio/commonline_cn.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index d67b11785c..1adee0642c 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -298,6 +298,20 @@ def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): @staticmethod def generate_cand_rots(n, equator_threshold, order, degree_res, seed): + """ + Generate random rotations that exclude rotations inducing equator images + for use as candidates in the CLSymmetryCn algorithm. + + :param n: Number of rotations to generate. + :param equator_threshold: Angular distance from equator (in degrees). + :param order: Cyclic order of underlying molecule. Number of in-plane + rotations must be divisible by order. + :param degree_res: Degree resolution for in-plane rotations. + :param seed: Random seed. + + :returns: Candidate rotations, In-plane rotations + """ + logger.info("Generating candidate rotations.") # Construct candidate rotations, Ris_tilde. Ris_tilde = np.zeros((n, 3, 3)) From 5df11bd47a9446464ed843261fff6cecc84a33eb Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 11:20:05 -0500 Subject: [PATCH 205/424] More Gallery cleanup --- gallery/tutorials/class_averaging.py | 14 ++++++-------- src/aspire/classification/averager2d.py | 6 +++++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 4757d1f4fe..2553c8f3da 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -106,10 +106,8 @@ # ``TopClassSelector`` deterministically selects the first ``n_classes``. # ``DebugClassAvgSource`` also uses brute force rotational alignment without shifts. # These simplifications are useful for development and debugging. -# Later we will use the more general purpose ``ClassAvgSourcev11``, +# Later we will discuss the more general ``ClassAvgSource``, # more suitable to simulations and experimental datasets. -# Furthermore, total customization can be achieved by instantiating your own components, -# and combining them using the generic ``ClassAvgSource``. n_classes = 10 @@ -160,10 +158,9 @@ # %% # RIR with Noise # ^^^^^^^^^^^^^^ -# Here we will use the more advanced ``ClassAvgSourcev11`` that might be used with -# complete ``Simulation``s or experimental data sources. +# Here we will use the noise_src. -avgs = ClassAvgSourcev11( +avgs = DebugClassAvgSource( classification_src=noisy_src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors @@ -197,7 +194,8 @@ # Report the identified neighbor indices with respect to the input ``noise_src``. classes = avgs._nn_classes[review_class] reflections = avgs._nn_reflections[review_class] -logger.info(f"Class {review_class}'s neighors: {classes[review_class]}") +logger.info(f"Class {review_class}'s neighors: {classes}") +logger.info(f"Class {review_class}'s reflections: {reflections}") # The original image is the initial image in the class array. original_image_idx = classes[0] @@ -218,7 +216,7 @@ # %% # Alignment Details -# ----------------- +# ^^^^^^^^^^^^^^^^^ # # Alignment details are exposed when avaialable from an underlying ``averager``. # In this case, we'll get the estimated alignments for the ``review_class``. diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index ec2335a481..4cc55936de 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -345,6 +345,7 @@ def _innerloop(k): else: nbr_coef = basis_coefficients[classes[k]] + norm_0 = np.linalg.norm(nbr_coef[0]) for i, angle in enumerate(test_angles): # Rotate the set of neighbors by angle, rotated_nbrs = self.alignment_basis.rotate( @@ -353,7 +354,10 @@ def _innerloop(k): # then store dot between class base image (0) and each nbor for j, nbor in enumerate(rotated_nbrs): - _correlations[j, i] = np.dot(nbr_coef[0], nbor) + norm_nbor = np.linalg.norm(nbor) + _correlations[j, i] = np.dot(nbr_coef[0], nbor) / ( + norm_nbor * norm_0 + ) # Now along each class, find the index of the angle reporting highest correlation angle_idx = np.argmax(_correlations, axis=1) From 97c76d434e1305676889bdd6286d3afecb863ecf Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 11:46:36 -0500 Subject: [PATCH 206/424] Fixup random bispec failures --- src/aspire/classification/rir_class2d.py | 42 +++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index ae5016cd0a..f799d8840c 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -227,7 +227,7 @@ def nn_classification(self, coef_b, coef_b_r): each having shape (n_img, features) where features = min(self.bispectrum_components, n_img). - Result is array (n_img, n_nbor) with entry `i` reprsenting + Result is array (n_img, n_nbor) with entry `i` representing index `i` into class input img array (src). To extend with an additonal Nearest Neighbor algo, @@ -466,13 +466,16 @@ def _devel_bispectrum(self, coef): return coef_b, coef_b_r - def _legacy_bispectrum(self, coef): + def _legacy_bispectrum(self, coef, retry_attempts=3): """ This code was ported to Python by an unkown author, and is the closest viable reference material. - It is copied here to compare while - fresh code is developed for this class. + :param coef: Real valued basis coefficients. + :param retry_attempts: Optional, max attempts to retry randomized + bispec_2drot_large. Defaults to 3. + + :return: Compressed feature and reflected feature vectors. """ # The legacy code expects the complex representation @@ -481,12 +484,29 @@ def _legacy_bispectrum(self, coef): self.pca_basis.complex_count ) # flatten - coef_b, coef_b_r = bispec_2drot_large( - coeff=coef.T, # Note F style tranpose here and in return - freqs=self.pca_basis.complex_angular_indices, - eigval=complex_eigvals, - alpha=self.alpha, - sample_n=self.sample_n, - ) + # bispec_2drot_large has a random selection component. + # Sometimes this can fail to return a complete feature vector. + # In this case we can retry, but if not successful raise an error. + # This seems to occur more frequently at very low resolutions (<=32), + # and likely requires tuning other RIR parameters for small problems. + attempt = 0 + while attempt < retry_attempts: + attempt += 1 + coef_b, coef_b_r = bispec_2drot_large( + coeff=coef.T, # Note F style transpose here and in return + freqs=self.pca_basis.complex_angular_indices, + eigval=complex_eigvals, + alpha=self.alpha, + sample_n=self.sample_n, + ) + if coef_b.shape[0] == self.bispectrum_components: + break # Return feature vector. + + # while-else: we've exceeded retry attempts. + else: + raise RuntimeError( + "bispec_2drot_large failed to return valid feature vector" + f"{coef_b.shape} after {attempt} attempts." + ) return coef_b.T, coef_b_r.T From 8aa94e7f7290ee23b98e04b0381964aceca22447 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 12:21:54 -0500 Subject: [PATCH 207/424] mark global tests as expensive --- tests/test_class_src.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_class_src.py b/tests/test_class_src.py index ce54ed269e..fe7454abea 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -147,6 +147,7 @@ def test_custom_local_selector(cls_fixture, selector): @pytest.mark.parametrize( "selector", GLOBAL_SELECTORS, ids=lambda param: f"Selector={param}" ) +@pytest.mark.expensive def test_custom_global_selector(class_sim_fixture, cls_fixture, selector): basis = FFBBasis2D(class_sim_fixture.L, dtype=class_sim_fixture.dtype) From 75455d10ccbec0caf6fd8d63a6e7eb768e071df8 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 12:22:13 -0500 Subject: [PATCH 208/424] rm unused var --- gallery/experiments/simulated_abinitio_pipeline.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 3c2732eb3f..946efc08de 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -146,7 +146,6 @@ def noise_function(x, y): # you may remove this code block and associated variables. classification_src = src -custom_averager = None if do_cov2d: # Use CWF denoising cwf_denoiser = DenoiserCov2D(src) From c5b616412b4812d7665d44706fd45b8f19ac7d46 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 14:18:05 -0500 Subject: [PATCH 209/424] Update experimental pipeline example --- .../experimental_abinitio_pipeline.py | 48 +++++++++---------- src/aspire/denoising/denoised_src.py | 2 +- 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline.py b/gallery/experiments/experimental_abinitio_pipeline.py index 883485838d..5e7f6a1461 100644 --- a/gallery/experiments/experimental_abinitio_pipeline.py +++ b/gallery/experiments/experimental_abinitio_pipeline.py @@ -28,12 +28,11 @@ import numpy as np from aspire.abinitio import CLSyncVoting -from aspire.basis import FFBBasis2D, FFBBasis3D -from aspire.classification import BFSReddyChatterjiAverager2D, RIRClass2D -from aspire.denoising import DenoiserCov2D +from aspire.basis import FFBBasis3D +from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import RelionSource +from aspire.source import ArrayImageSource, RelionSource logger = logging.getLogger(__name__) @@ -45,14 +44,16 @@ interactive = False # Draw blocking interactive plots? do_cov2d = True # Use CWF coefficients -n_imgs = 20000 # Set to None for all images in starfile, can set smaller for tests. +n_imgs = None # Set to None for all images in starfile, can set smaller for tests. img_size = 32 # Downsample the images/reconstruction to a desired resolution -n_classes = 1000 # How many class averages to compute. -n_nbor = 50 # How many neighbors to stack -starfile_in = "10028/data/shiny_2sets.star" +n_classes = 2000 # How many class averages to compute. +n_nbor = 64 # How many neighbors to stack +starfile_in = "10028/data/shiny_2sets_fixed9.star" +data_folder = "." # This depends on the specific starfile entries. volume_filename_prefix_out = f"10028_recon_c{n_classes}_m{n_nbor}_{img_size}.mrc" pixel_size = 1.34 + # %% # Source data and Preprocessing # ----------------------------- @@ -62,7 +63,9 @@ # to correct for CTF and noise. # Create a source object for the experimental images -src = RelionSource(starfile_in, pixel_size=pixel_size, max_rows=n_imgs) +src = RelionSource( + starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder +) # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") @@ -119,11 +122,6 @@ if interactive: classification_src.images[:10].show() - # Use regular `src` for the alignment and composition (averaging). - composite_basis = FFBBasis2D((src.L,) * 2, dtype=src.dtype) - custom_averager = BFSReddyChatterjiAverager2D(composite_basis, src, dtype=src.dtype) - - # %% # Class Averaging # ---------------------- @@ -132,23 +130,23 @@ logger.info("Begin Class Averaging") -rir = RIRClass2D( - classification_src, # Source used for classification - fspca_components=400, - bispectrum_components=300, # Compressed Features after last PCA stage. +# Now perform classification and averaging for each class. +# This also demonstrates the potential to use a different source for classification and averaging. + +avgs_src = ClassAvgSourcev11( + classification_src, n_nbor=n_nbor, - n_classes=n_classes, - large_pca_implementation="legacy", - nn_implementation="sklearn", - bispectrum_implementation="legacy", - averager=custom_averager, + averager_src=src, + num_procs=None, # Automaticaly configure parallel processing ) -classes, reflections, distances = rir.classify() -avgs = rir.averages(classes, reflections, distances) +# We'll manually cache `n_classes` worth to speed things up. +avgs = ArrayImageSource(avgs_src.images[:n_classes]) + if interactive: avgs.images[:10].show() + # %% # Common Line Estimation # ---------------------- diff --git a/src/aspire/denoising/denoised_src.py b/src/aspire/denoising/denoised_src.py index 2a4ecfe5d0..796d1b2ad5 100644 --- a/src/aspire/denoising/denoised_src.py +++ b/src/aspire/denoising/denoised_src.py @@ -47,7 +47,7 @@ def _images(self, indices): end = indices.max() nimgs = len(indices) - im = np.empty((nimgs, self.L, self.L)) + im = np.empty((nimgs, self.L, self.L), self.dtype) # If we request less than a whole batch, don't crash batch_size = min(nimgs, self.batch_size) From 09ddd5e030d732b505fd6e64aced57d581a26858 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 15:06:42 -0500 Subject: [PATCH 210/424] Fix volume right division --- src/aspire/volume/volume.py | 4 ++-- tests/test_volume.py | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/aspire/volume/volume.py b/src/aspire/volume/volume.py index 1d32d888bd..e334f4df44 100644 --- a/src/aspire/volume/volume.py +++ b/src/aspire/volume/volume.py @@ -212,11 +212,11 @@ def __truediv__(self, other): return res - def __rtruedive__(self, otherL): + def __rtruediv__(self, otherL): """ Right scalar division, follows numpy semantics. """ - return self / otherL + return otherL * Volume(1.0 / self._data) def project(self, vol_idx, rot_matrices): """ diff --git a/tests/test_volume.py b/tests/test_volume.py index dee6ed45de..b6611fc5db 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -26,7 +26,8 @@ class VolumeTestCase(TestCase): def setUp(self): self.dtype = np.float32 self.n = n = 3 - self.data_1 = np.arange(n * self.res**3, dtype=self.dtype).reshape( + # Note, range shifted by one to avoid zero division errors. + self.data_1 = np.arange(1, 1 + n * self.res**3, dtype=self.dtype).reshape( n, self.res, self.res, self.res ) self.data_2 = 123 * self.data_1.copy() @@ -154,6 +155,22 @@ def testScalarRMul(self): self.assertTrue(np.all(result == self.data_2)) self.assertTrue(isinstance(result, Volume)) + def testScalarDiv(self): + result = self.vols_2 / 123 + self.assertTrue(np.allclose(result, self.vols_1)) + + def testRightScalarDiv(self): + result = 123 / self.vols_2 + self.assertTrue(np.allclose(result, 1 / self.data_1)) + + def testDiv(self): + result = self.vols_2 / self.vols_1 + self.assertTrue(np.allclose(result, 123)) + + def testRightDiv(self): + result = self.data_2 / self.vols_1 + self.assertTrue(np.allclose(result, 123)) + def testSaveLoad(self): # Create a tmpdir in a context. It will be cleaned up on exit. with tempfile.TemporaryDirectory() as tmpdir: From 4e06ee071908282b044e606ccd3619ab58de9325 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 2 Mar 2023 15:13:44 -0500 Subject: [PATCH 211/424] Fix im->im_orig typo --- src/aspire/source/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 1b1c703283..0416efb2b2 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -373,7 +373,7 @@ def _apply_filters( f"_apply_filters() passed {type(im_orig)} instead of Image instance" ) # for now just convert it - im = Image(im_orig) + im_orig = Image(im_orig) im = im_orig.copy() From a352cf0c83015da3367b47310d317d53366c41d0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 07:20:48 -0500 Subject: [PATCH 212/424] cleanup unneeded averager_src in base class --- src/aspire/classification/class_selection.py | 2 +- src/aspire/denoising/class_avg.py | 61 ++++++++++++++------ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 681f6fcc92..d94c8fa09f 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -213,7 +213,7 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): self.averager = averager if not isinstance(self.averager, Averager2D): raise ValueError( - f"`averager` should be instance of `Averger2D`, found {self.averager_src}." + f"`averager` should be instance of `Averger2D`, found {self.averager}." ) self._quality_function = quality_function diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 095743a317..8c9a7a96f7 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -30,7 +30,6 @@ def __init__( classifier, class_selector, averager, - averager_src=None, ): """ Constructor of an object for denoising 2D images using class averaging methods. @@ -40,8 +39,6 @@ def __init__( Example, RIRClass2D. :param class_selector: A ClassSelector subclass. :param averager: An Averager2D subclass. - :param averager_src: Optional, Source used for image registration and averaging. - Defaults to `classification_src`. """ self.classification_src = classification_src if not isinstance(self.classification_src, ImageSource): @@ -64,15 +61,7 @@ def __init__( self.averager = averager if not isinstance(self.averager, Averager2D): raise ValueError( - f"`averager` should be instance of `Averger2D`, found {self.averager_src}." - ) - - self.averager_src = averager_src - if self.averager_src is None: - self.averager_src = self.classification_src - if not isinstance(self.averager_src, ImageSource): - raise ValueError( - f"`averager_src` should be subclass of `ImageSource`, found {self.averager_src}." + f"`averager` should be instance of `Averger2D`, found {self.averager}." ) self._nn_classes = None @@ -86,9 +75,9 @@ def __init__( # Note n will potentially be updated after class selection. super().__init__( - L=self.averager_src.L, - n=self.averager_src.n, - dtype=self.averager_src.dtype, + L=self.averager.src.L, + n=self.averager.src.n, + dtype=self.averager.src.dtype, ) def _classify(self): @@ -228,7 +217,7 @@ def _images(self, indices): # The following sub classes attempt to pack sensible defaults # into ClassAvgSource so that users don't need to -# instantiate every component. +# instantiate every component to get started. class DebugClassAvgSource(ClassAvgSource): @@ -236,6 +225,22 @@ class DebugClassAvgSource(ClassAvgSource): Source for denoised 2D images using class average methods. Packs base with common debug defaults. + + :param n_nbor: Number of nearest neighbors. Default 10. + :param num_procs: Number of processors. Default of 1 runs serially. + `None` attempts to compute a reasonable value + based on available cores and memory. + :param classifier: `Class2D` classifier instance. + Default `None` creates `RIRClass2D`. + See code for parameter details. + :param class_selector: `ClassSelector` instance. + Default `None` creates `TopClassSelector`. + :param averager: `Averager2D` instance. + Default `None` ceates `BFRAverager2D` instance. + See code for parameter details. + + :return: ClassAvgSource instance. + """ def __init__( @@ -276,7 +281,6 @@ def __init__( classifier=classifier, class_selector=class_selector, averager=averager, - averager_src=classification_src, ) @@ -301,6 +305,28 @@ def __init__( averager=None, averager_src=None, ): + """ + Instantiates ClassAvgSourcev11 with the following parameters. + + :param n_nbor: Number of nearest neighbors. Default 50. + :param num_procs: Number of processors. Use 1 to run serially. + Default `None` attempts to compute a reasonable value + based on available cores and memory. + :param classifier: `Class2D` classifier instance. + Default `None` creates `RIRClass2D`. + See code for parameter details. + :param class_selector: `ClassSelector` instance. + Default `None` creates `ContrastWithRepulsionClassSelector`. + :param averager: `Averager2D` instance. + Default `None` ceates `BFSRAverager2D` instance. + See code for parameter details. + :param averager_src: Optionally explicitly assign source + to BFSRAverager2D during initialization. + Raises error when combined with an explicit `averager` + argument. + + :return: ClassAvgSource instance. + """ dtype = classification_src.dtype if classifier is None: @@ -339,5 +365,4 @@ def __init__( classifier=classifier, class_selector=class_selector, averager=averager, - averager_src=classification_src, ) From f50e97361e8a616b2a89a353bf7ad545e7d9f663 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 3 Mar 2023 08:52:11 -0500 Subject: [PATCH 213/424] pairs_to_linear map with numpy array brrrm brrrm. --- src/aspire/abinitio/commonline_c3_c4.py | 21 ++++++++++---------- src/aspire/utils/__init__.py | 1 - src/aspire/utils/misc.py | 26 ++++++++----------------- tests/test_orient_symmetric.py | 8 ++++---- tests/test_utils.py | 14 +++---------- 5 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 2d12871b4e..c95feb52ed 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -11,7 +11,6 @@ all_triplets, anorm, cyclic_rotations, - pairs_to_linear, tqdm, trange, ) @@ -172,20 +171,21 @@ def _global_J_sync(self, vijs, viis): # We use the fact that if v_ii and v_ij are of the same handedness, then v_ii @ v_ij = v_ij. # If they are opposite handed then Jv_iiJ @ v_ij = v_ij. We compare each v_ii against all # previously synchronized v_ij to get a consensus on the handedness of v_ii. + _, pairs_to_linear = all_pairs(n_img) for i in range(n_img): vii = viis[i] vii_J = J_conjugate(vii) J_consensus = 0 for j in range(n_img): if j < i: - idx = pairs_to_linear(n_img, j, i) + idx = pairs_to_linear[j, i] vji = vijs[idx] err1 = norm(vji @ vii - vji) err2 = norm(vji @ vii_J - vji) elif j > i: - idx = pairs_to_linear(n_img, i, j) + idx = pairs_to_linear[i, j] vij = vijs[idx] err1 = norm(vii @ vij - vij) @@ -226,7 +226,7 @@ def _estimate_third_rows(self, vijs, viis): V = np.zeros((n_img, n_img, 3, 3), dtype=vijs.dtype) # All pairs (i,j) where i 0 and (n-2, n-1) --> n * (n - 1)/2 - 1 - """ - i = np.array(i) - j = np.array(j) - - assert (i < j).all() < n, "i must be less than j, and both must be less than n." - - linear_index = n * (n - 1) // 2 - (n - i) * (n - i - 1) // 2 + j - i - 1 - - return linear_index + return pairs, pairs_to_linear_map def all_triplets(n): diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 0281f70923..91236ca151 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -151,7 +151,7 @@ def test_relative_rotations(L, order, dtype): rots_gt = src.rotations # Find the angular distance between each Rij and the ground truth. - pairs = all_pairs(n_img) + pairs, _ = all_pairs(n_img) angular_distance = np.zeros(len(pairs)) for idx, (i, j) in enumerate(pairs): Rij = Rijs[idx] @@ -224,7 +224,7 @@ def test_relative_viewing_directions(L, order, dtype): vi = rots_gt[i, 2] viis_gt[i] = np.outer(vi, vi) - pairs = all_pairs(n_img) + pairs, _ = all_pairs(n_img) n_pairs = len(pairs) vijs_gt = np.zeros((n_pairs, 3, 3)) for idx, (i, j) in enumerate(pairs): @@ -353,7 +353,7 @@ def test_commonlines(L, order, dtype): # Compare common-line indices with ground truth angles. rots = src.rotations # ground truth rotations rots_symm = cyclic_rotations(order, dtype).matrices - pairs = all_pairs(n_img) + pairs, _ = all_pairs(n_img) within_1_degree = 0 within_5_degrees = 0 for i, j in pairs: @@ -514,7 +514,7 @@ def build_outer_products(n_img, dtype): viis = np.zeros((n_img, 3, 3), dtype=dtype) # All pairs (i,j) where i Date: Fri, 3 Mar 2023 09:24:43 -0500 Subject: [PATCH 214/424] review changes --- src/aspire/utils/misc.py | 2 +- tests/test_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index b04402fd0c..b55f0dbfc5 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -308,7 +308,7 @@ def all_pairs(n): :param n: The number of items to be indexed. :returns: - n x 2 array of pairs (i, j), i Date: Fri, 3 Mar 2023 07:35:11 -0500 Subject: [PATCH 215/424] fixup legacy bispec patch --- src/aspire/classification/rir_class2d.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index f799d8840c..084f62d8e3 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -499,14 +499,15 @@ def _legacy_bispectrum(self, coef, retry_attempts=3): alpha=self.alpha, sample_n=self.sample_n, ) - if coef_b.shape[0] == self.bispectrum_components: + # If we have produced a feature vector + if coef_b.size != 0: break # Return feature vector. # while-else: we've exceeded retry attempts. else: raise RuntimeError( - "bispec_2drot_large failed to return valid feature vector" - f"{coef_b.shape} after {attempt} attempts." + "bispec_2drot_large failed to return valid feature vector." + f" Returned {coef_b.shape} after {attempt} attempts." ) return coef_b.T, coef_b_r.T From 2784ca110b49bd1ab5ecfc7034f687a70d54be6a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 07:38:45 -0500 Subject: [PATCH 216/424] Begin additional section in class avg tutorial --- gallery/tutorials/class_averaging.py | 31 +++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 2553c8f3da..4b3a50f438 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -97,17 +97,17 @@ src.images[:10].show() # %% -# Class Average -# ------------- +# Basic Class Average +# ------------------- # -# We use the ``DebugClassAvgSource`` to classify the images +# This first example uses the ``DebugClassAvgSource`` to classify images # via the rotationally invariant representation (``RIRClass2D``) algorithm. # ``DebugClassAvgSource`` internally uses ``TopClassSelector`` by default. # ``TopClassSelector`` deterministically selects the first ``n_classes``. # ``DebugClassAvgSource`` also uses brute force rotational alignment without shifts. # These simplifications are useful for development and debugging. -# Later we will discuss the more general ``ClassAvgSource``, -# more suitable to simulations and experimental datasets. +# Later we will discuss the more general ``ClassAvgSource`` and the modular +# components that are more suitable to simulations and experimental datasets. n_classes = 10 @@ -264,3 +264,24 @@ plt.xlabel(f"Img {nbr} rotated {angle:.4}*") plt.tight_layout() plt.show() + + +# %% +# ClassAvgSource Components +# ------------------------- +# For more realistic simulations and experimental data, +# ASPIRE provides a wholly customizable base ``ClassAvgSource`` +# class. This class expects a user to instantiate and provide +# all components required for class averaging. +# +# To make things easier a practical starting point +# ``ClassAvgSourcev11`` is provided which fills in reasonable +# defaults based on what is available in the current ASPIRE-Python. +# The defaults can be overridden simply by instantiating your own +# instances of components and passing during initialization. + +# Using the defaults requires only passing a source. +# After understanding the various components that can be +# combined in a ``ClassAvgSource``, they can be customized +# of easily extended. +avg_src = ClassAvgSourcev11(noisy_src) From 67d7f75c70fb78edd2f54b1898c38af246092da0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 11:29:14 -0500 Subject: [PATCH 217/424] Update image_stacker strings, method, and tests --- src/aspire/image/__init__.py | 1 + src/aspire/image/image_stacker.py | 156 +++++++++++++++++++++++++----- tests/test_image_stacker.py | 47 ++++++--- 3 files changed, 166 insertions(+), 38 deletions(-) diff --git a/src/aspire/image/__init__.py b/src/aspire/image/__init__.py index 319d5d55c4..3d0db12be6 100644 --- a/src/aspire/image/__init__.py +++ b/src/aspire/image/__init__.py @@ -12,4 +12,5 @@ MeanImageStacker, MedianImageStacker, SigmaRejectionImageStacker, + WinsorizedImageStacker, ) diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index 8a167a6701..41e3449346 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -3,6 +3,7 @@ import numpy as np import numpy.ma as ma +from scipy.stats.mstats import winsorize from aspire.image import Image @@ -25,6 +26,7 @@ def __init__(self): Initialize ImageStacker instance. """ + # When provided an image, keep track of shape. self._return_image_size = None @abc.abstractmethod @@ -32,30 +34,42 @@ def __call__(self, stack): """ Stack the elements of `stack`. - Stack admits an Image class, or an ndarray. + Stack admits an Image class, or an Numpy array. - In the case of ndarray, the data should be 2D + In the case of Numpy array, the data should be 2D where the first (slow) dimension is stack axis, - and data is flattened to the last (fastest) axis. + and signal data is flattened to the last (fastest) axis. In this case an Numpy array is returned. When passing `Image`, the stack_shape must be 1D. In this case an `Image` is returned. + Users with multidimensional `Image` data + may use `Image.stack_reshape` or stack slicing + before/after stacking. - :param stack: Image instance or ndarray. + :param stack: Image instance (or Numpy array). + + :return: Stacked data as Image (or Numpy array). """ def _check_and_convert(self, stack): """ Check stack and returns consistent 2D np.array. + + :param stack: Image instance (or Numpy array). + + :return: 2D Numpy array. """ + # Careful, we need to reset this in case this stack is different from last. self._return_image_size = None - # If we are an image, flatten image data. + + # Flatten image data. if isinstance(stack, Image): # Store the image size for returning later self._return_image_size = (1,) + stack.shape[1:] + # Sanity check dimensions if stack.stack_ndim != 1: raise ValueError( f"`stack` shape of Image should be 1D for ImageStacking not {stack.stack_shape}." @@ -63,7 +77,7 @@ def _check_and_convert(self, stack): ) stack = stack.asnumpy().reshape(stack.n_images, -1) - # By this point we should alway be an np array with data in the last axis. + # By this point we should alway be an array with signal data in the last axis. if not isinstance(stack, np.ndarray): raise ValueError("`stack` should be `Image` instance or Numpy array.") elif stack.ndim != 2: @@ -72,9 +86,6 @@ def _check_and_convert(self, stack): " Try Image.stack_reshape if needed." ) - # Store the stack shape. - self._stack_shape = stack.shape[0] - return stack def _return(self, result): @@ -82,8 +93,11 @@ def _return(self, result): When ImageStacker has been passed an `Image`, this method creates an `Image` instance to be returned. + Stacking Numpy array will pass through. + :param result: Result data as Numpy Array. - :return: Image(result) or Numpy array(result) based on initial call type. + + :return: Image(result) or Numpy array(result) based on initial `stack` input type. """ if self._return_image_size is not None: result = Image(result.reshape(self._return_image_size)) @@ -113,49 +127,139 @@ def __call__(self, stack): class SigmaRejectionImageStacker(ImageStacker): - """ - Stack using Sigma Rejection. - """ + """Stack using Sigma Rejection. + + When no outliers exist, sigma rejection should return equivalent + of `mean`. In the presence of outliers, pixels outside of + `rejection_sigma` from the per-pixel-mean are discarded. + + For potentially less Gaussian distributions, 'FWHM' and 'FWTM' + methods are provided. These will take the mean of values lieing + in the FWHM and FWTM. Essentially this is the same wing clipping + procedure. + + Note, in both cases, user's are responsible for ensuring metohds + are called on reasonable data (in FW* cases we should be talking + intesnities). No corrections or pedestals are incorporated at this + time.""" - def __init__(self, rejection_sigma=2): + _width_methods = {"FWHM": 0.5, "FWTM": 0.1} + + def __init__(self, rejection_sigma=3): """ Instantiates SigmaRejectionImageStacker instance with presribed `rejection_sigma`. - If no outliers should return equivalent of `mean`. - In the presence of outliers, pixels outside of - `rejection_sigma` from the per-pixel-mean are discarded. + :param rejection_sigma: Values falling outside + `rejection_sigma` standard deviations are + rejected. Defaults to 3. Also accepts 'FWHM' and 'FWTM' + corresponding to per-pixel full width at half maximum and + tenth maximum respectively. - :param rejection_sigma: Values outside `rejection_sigma` """ - self.sigma = float(rejection_sigma) + # Handle string `rejection_sigma` + self._method = self._gaussian + if isinstance(rejection_sigma, str): + self.rejection_sigma = rejection_sigma.upper() + self._method = self._width + if self.rejection_sigma not in self._width_methods: + raise ValueError( + f"`rejection_sigma` must be numeric or {self._width_methods.keys()}." + ) + else: + self.sigma = float(rejection_sigma) def __call__(self, stack): + """ + Dispatches rejection method based on `rejection_sigma`. + """ stack = self._check_and_convert(stack) + return self._return(self._method(stack)) + + def _gaussian(self, stack): + """ + Gaussian rejection. + """ # Compute the mean and standard deviations, pixelwise means = stack.mean(axis=0) - std_deviations = stack.std(axis=0) + std_devs = stack.std(axis=0) # Compute values that lie outside sigma deviations - outliers = np.abs(stack - means) > self.sigma * std_deviations + outliers = np.abs(stack - means) > self.sigma * std_devs + + # Mask off the outliers + masked_stack = ma.masked_array(stack, mask=outliers) + + # Return mean without the outliers + return masked_stack.mean(axis=0) + + def _width(self, stack): + """ + Width rejection. + """ + # Compute per-pixel max + maxima = stack.max(axis=0) + + # Find the per-pixel value for clipping + y = maxima * self._width_methods[self.rejection_sigma] + + # Compute outliers, values below clipping value. + outliers = stack < y # Mask off the outliers masked_stack = ma.masked_array(stack, mask=outliers) # Return mean withou the outliers - return self._return(masked_stack.mean(axis=0)) + return masked_stack.mean(axis=0) -class PoissonRejectionImageStacker(ImageStacker): +class WinsorizedImageStacker(ImageStacker): """ - Stack using Poisson Rejection. + Stack using Winsorization. + + Winsorizing is similar to SigmaRejectionImageStacker, + excect it admits a `percentile` and replaces rejected + values with values at +/- `percentile`. """ + def __init__(self, percentile=0.1): + """Instantiates WinsorizedImageStacker instance with + presribed `percentile`. -class WinsorizedImageStacker(ImageStacker): + See scipy docs for more details: + https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mstats.winsorize.html + + :param percentile: Float or tuple of floats. Values must be + [0,1] and represent the percentile used for trimming. A + tuple allows different lower and upper percentiles + respectively. Example (0.1, 0.2) would Winsorize the lower + 10% and upper 20% of values. In the case of a single + value, that value will be used for both upper and lower + percentiles. + """ + # Convert scalars to tuples. + if not isinstance(percentile, tuple): + percentile = (float(percentile),) * 2 + if (min(percentile) < 0) or (max(percentile) > 1): + raise ValueError(f"`percentile` must be [0,1], passed {percentile}.") + self.percentile = percentile + + def __call__(self, stack): + """ + Winsorizing + """ + stack = self._check_and_convert(stack) + + stack = winsorize(stack, limits=self.percentile, inplace=False) + + # Return mean of Winsorized data. + return self._return(stack.mean(axis=0)) + + +class PoissonRejectionImageStacker(ImageStacker): """ - Stack using Winsorized Sigma Rejection. + Stack using Poisson Rejection. """ diff --git a/tests/test_image_stacker.py b/tests/test_image_stacker.py index 6ee80e761d..16cf244c3a 100644 --- a/tests/test_image_stacker.py +++ b/tests/test_image_stacker.py @@ -9,6 +9,7 @@ MeanImageStacker, MedianImageStacker, SigmaRejectionImageStacker, + WinsorizedImageStacker, ) logger = logging.getLogger(__name__) @@ -18,14 +19,16 @@ DTYPES = [np.float32, np.float64] -def xx_fixture_id(params): +def simple_data_fixture_id(params): image_size = params[0] dtype = params[1] return f"image_size={image_size}, dtype={dtype.__name__}" -@pytest.fixture(params=itertools.product(IMAGE_SIZES, DTYPES), ids=xx_fixture_id) -def xx_fixture(request): +@pytest.fixture( + params=itertools.product(IMAGE_SIZES, DTYPES), ids=simple_data_fixture_id +) +def simple_data_fixture(request): # unpack fixture params img_size, dtype = request.param @@ -39,37 +42,57 @@ def xx_fixture(request): return Image(A) -def test_mean_stacker(xx_fixture): - img_np = xx_fixture.asnumpy() +def test_mean_stacker(simple_data_fixture): + img_np = simple_data_fixture.asnumpy() ref_result = img_np.mean(axis=0) n = img_np.shape[0] stacker = MeanImageStacker() # Check stacker(Image(...)) == numpy - stacked = stacker(xx_fixture) + stacked = stacker(simple_data_fixture) assert isinstance(stacked, Image) assert np.allclose(stacked.asnumpy(), ref_result) # Check stacker(ndarray) == numpy - stacked = stacker(xx_fixture.asnumpy().reshape(n, -1)) + stacked = stacker(simple_data_fixture.asnumpy().reshape(n, -1)) assert isinstance(stacked, np.ndarray) assert np.allclose(stacked, ref_result.reshape(1, -1)) -def test_median_stacker(xx_fixture): +def test_median_stacker(simple_data_fixture): stacker = MedianImageStacker() - stacked = stacker(xx_fixture) + stacked = stacker(simple_data_fixture) # Median should ignore the single outliers assert np.allclose(stacked.asnumpy(), 1) -def test_sigma_stacker(xx_fixture): - stacker = SigmaRejectionImageStacker() +def test_sigma_stacker(simple_data_fixture): + stacker = SigmaRejectionImageStacker(rejection_sigma=2) - stacked = stacker(xx_fixture) + stacked = stacker(simple_data_fixture) # Sigma should ignore the single outliers assert np.allclose(stacked.asnumpy(), 1) + + +def test_sigma_fw_stacker(simple_data_fixture): + stacker = SigmaRejectionImageStacker(rejection_sigma="FWHM") + + # Manipulate simple_data_fixture's outliers to be small. + intensities = np.square(1 / simple_data_fixture.asnumpy().reshape(SAMPLE_N, -1)) + stacked = stacker(intensities) + + # Sigma FWHM should ignore the small outliers + assert np.allclose(stacked, 1) + + +def test_windsor_stacker(simple_data_fixture): + stacker = WinsorizedImageStacker(percentile=0.1) + + stacked = stacker(simple_data_fixture) + + # Winsorize should ignore the outliers + assert np.allclose(stacked.asnumpy(), 1) From 8ac678c7f921a86bcca8b5f1ed98e25879ab93ee Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 11:46:53 -0500 Subject: [PATCH 218/424] Cleanup 10028 experiment --- ...> experimental_abinitio_pipeline_10005.py} | 2 +- .../experimental_abinitio_pipeline_10028.py | 188 ++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) rename gallery/experiments/{experimental_abinitio_pipeline.py => experimental_abinitio_pipeline_10005.py} (99%) create mode 100644 gallery/experiments/experimental_abinitio_pipeline_10028.py diff --git a/gallery/experiments/experimental_abinitio_pipeline.py b/gallery/experiments/experimental_abinitio_pipeline_10005.py similarity index 99% rename from gallery/experiments/experimental_abinitio_pipeline.py rename to gallery/experiments/experimental_abinitio_pipeline_10005.py index 5e7f6a1461..77f04fb7b3 100644 --- a/gallery/experiments/experimental_abinitio_pipeline.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10005.py @@ -47,7 +47,7 @@ n_imgs = None # Set to None for all images in starfile, can set smaller for tests. img_size = 32 # Downsample the images/reconstruction to a desired resolution n_classes = 2000 # How many class averages to compute. -n_nbor = 64 # How many neighbors to stack +n_nbor = 100 # How many neighbors to stack starfile_in = "10028/data/shiny_2sets_fixed9.star" data_folder = "." # This depends on the specific starfile entries. volume_filename_prefix_out = f"10028_recon_c{n_classes}_m{n_nbor}_{img_size}.mrc" diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py new file mode 100644 index 0000000000..0ac46ead38 --- /dev/null +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -0,0 +1,188 @@ +""" +Abinitio Pipeline - Experimental Data Empiar 10028 +================================================== + +This notebook introduces a selection of +components corresponding to loading real Relion picked +particle Cryo-EM data and running key ASPIRE-Python +Abinitio model components as a pipeline. + +Specifically this pipeline uses the +EMPIAR 10028 picked particles data, available here: + +https://www.ebi.ac.uk/empiar/EMPIAR-10028 + +https://www.ebi.ac.uk/emdb/EMD-2660 +""" + +# %% +# Imports +# ------- +# First import some of the usual suspects. +# In addition, import some classes from +# the ASPIRE package that will be used throughout this experiment. + +import logging + +import matplotlib.pyplot as plt +import numpy as np + +from aspire.abinitio import CLSyncVoting +from aspire.basis import FFBBasis3D +from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D +from aspire.noise import AnisotropicNoiseEstimator +from aspire.reconstruction import MeanEstimator +from aspire.source import ArrayImageSource, RelionSource + +logger = logging.getLogger(__name__) + + +# %% +# Parameters +# --------------- +# Example simulation configuration. + +interactive = False # Draw blocking interactive plots? +do_cov2d = True # Use CWF coefficients +n_imgs = None # Set to None for all images in starfile, can set smaller for tests. +img_size = 32 # Downsample the images/reconstruction to a desired resolution +n_classes = 2000 # How many class averages to compute. +n_nbor = 100 # How many neighbors to stack +starfile_in = "10028/data/shiny_2sets_fixed9.star" +data_folder = "." # This depends on the specific starfile entries. +volume_filename_prefix_out = f"10028_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +pixel_size = 1.34 + + +# %% +# Source data and Preprocessing +# ----------------------------- +# +# `RelionSource` is used to access the experimental data via a `starfile`. +# Begin by downsampling to our chosen resolution, then preprocess +# to correct for CTF and noise. + +# Create a source object for the experimental images +src = RelionSource( + starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder +) + +# Downsample the images +logger.info(f"Set the resolution to {img_size} X {img_size}") +src.downsample(img_size) + +# Peek +if interactive: + src.images[:10].show() + +# Use phase_flip to attempt correcting for CTF. +logger.info("Perform phase flip to input images.") +src.phase_flip() + +# Estimate the noise and `Whiten` based on the estimated noise +aiso_noise_estimator = AnisotropicNoiseEstimator(src) +src.whiten(aiso_noise_estimator.filter) + +# Plot the noise profile for inspection +if interactive: + plt.imshow(aiso_noise_estimator.filter.evaluate_grid(img_size)) + plt.show() + +# Peek, what do the whitened images look like... +if interactive: + src.images[:10].show() + +# # Optionally invert image contrast, depends on data convention. +# # This is not needed for 10028, but included anyway. +# logger.info("Invert the global density contrast") +# src.invert_contrast() + +# %% +# Optional: CWF Denoising +# ----------------------- +# +# Optionally generate an alternative source that is denoised with `cov2d`, +# then configure a customized averager. This allows the use of CWF denoised +# images for classification, but stacks the original images for averages +# used in the remainder of the reconstruction pipeline. +# +# In this example, this behavior is controlled by the `do_cov2d` boolean variable. +# When disabled, the original src and default averager is used. +# If you will not be using cov2d, +# you may remove this code block and associated variables. + +classification_src = src +custom_averager = None +if do_cov2d: + # Use CWF denoising + cwf_denoiser = DenoiserCov2D(src) + # Use denoised src for classification + classification_src = cwf_denoiser.denoise() + # Peek, what do the denoised images look like... + if interactive: + classification_src.images[:10].show() + +# %% +# Class Averaging +# ---------------------- +# +# Now perform classification and averaging for each class. + +logger.info("Begin Class Averaging") + +# Now perform classification and averaging for each class. +# This also demonstrates the potential to use a different source for classification and averaging. + +avgs_src = ClassAvgSourcev11( + classification_src, + n_nbor=n_nbor, + averager_src=src, + num_procs=None, # Automaticaly configure parallel processing +) + +# We'll manually cache `n_classes` worth to speed things up. +avgs = ArrayImageSource(avgs_src.images[:n_classes]) + +if interactive: + avgs.images[:10].show() + + +# %% +# Common Line Estimation +# ---------------------- +# +# Next create a CL instance for estimating orientation of projections +# using the Common Line with Synchronization Voting method. + +logger.info("Begin Orientation Estimation") + +orient_est = CLSyncVoting(avgs, n_theta=36) +# Get the estimated rotations +orient_est.estimate_rotations() +rots_est = orient_est.rotations + +# %% +# Volume Reconstruction +# ---------------------- +# +# Using the estimated rotations, attempt to reconstruct a volume. + +logger.info("Begin Volume reconstruction") + +# Assign the estimated rotations to the class averages +avgs.rotations = rots_est + +# Create a reasonable Basis for the 3d Volume +basis = FFBBasis3D((img_size,) * 3, dtype=src.dtype) + +# Setup an estimator to perform the back projection. +estimator = MeanEstimator(avgs, basis) + +# Perform the estimation and save the volume. +estimated_volume = estimator.estimate() +estimated_volume.save(volume_filename_prefix_out, overwrite=True) + +# Peek at result +if interactive: + plt.imshow(np.sum(estimated_volume[0], axis=-1)) + plt.show() From bc8ed560509452346bafad0717326e3f0eea77a3 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 12:08:55 -0500 Subject: [PATCH 219/424] initial draft 10005 experiment --- .../experimental_abinitio_pipeline_10005.py | 108 +++++------------- 1 file changed, 26 insertions(+), 82 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10005.py b/gallery/experiments/experimental_abinitio_pipeline_10005.py index 77f04fb7b3..dbaf85fffc 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10005.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10005.py @@ -1,18 +1,18 @@ """ -Abinitio Pipeline - Experimental Data -===================================== +Abinitio Pipeline - Experimental Data Empiar 10005 +================================================== This notebook introduces a selection of -components corresponding to loading real Relion picked -particle Cryo-EM data and running key ASPIRE-Python +components corresponding to loading real coordinate +base particle Cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. Specifically this pipeline uses the -EMPIAR 10028 picked particles data, available here: +EMPIAR 10005 picked particles data, available here: -https://www.ebi.ac.uk/empiar/EMPIAR-10028 +https://www.ebi.ac.uk/empiar/EMPIAR-10005 -https://www.ebi.ac.uk/emdb/EMD-10028 +https://www.ebi.ac.uk/emdb/EMD-5778 """ # %% @@ -22,17 +22,16 @@ # In addition, import some classes from # the ASPIRE package that will be used throughout this experiment. +import os +import glob import logging -import matplotlib.pyplot as plt -import numpy as np - from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis3D -from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D +from aspire.denoising import ClassAvgSourcev11 from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, RelionSource +from aspire.source import ArrayImageSource, CentersCoordinateSource logger = logging.getLogger(__name__) @@ -42,86 +41,45 @@ # --------------- # Example simulation configuration. -interactive = False # Draw blocking interactive plots? -do_cov2d = True # Use CWF coefficients +working_dir = "/scratch/ExperimentalData/staging/10017/data" n_imgs = None # Set to None for all images in starfile, can set smaller for tests. img_size = 32 # Downsample the images/reconstruction to a desired resolution n_classes = 2000 # How many class averages to compute. n_nbor = 100 # How many neighbors to stack -starfile_in = "10028/data/shiny_2sets_fixed9.star" -data_folder = "." # This depends on the specific starfile entries. -volume_filename_prefix_out = f"10028_recon_c{n_classes}_m{n_nbor}_{img_size}.mrc" -pixel_size = 1.34 +volume_filename_prefix_out = f"10005_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +particle_size = 300 # %% # Source data and Preprocessing # ----------------------------- # -# `RelionSource` is used to access the experimental data via a `starfile`. +# `CentersCoordinateSource` is used to access the experimental data from +# mrcs using coordinate files. +# # Begin by downsampling to our chosen resolution, then preprocess # to correct for CTF and noise. +mrcs = sorted(glob.glob(os.path.join(working_dir, "*.mrc"))) +coords = sorted(glob.glob(os.path.join(working_dir, "*.coord"))) +files = list(zip(mrcs, coords)) + # Create a source object for the experimental images -src = RelionSource( - starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder -) +src = CentersCoordinateSource(files, particle_size=particle_size, max_rows=n_imgs) # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") src.downsample(img_size) -# Peek -if interactive: - src.images[:10].show() - # Use phase_flip to attempt correcting for CTF. logger.info("Perform phase flip to input images.") src.phase_flip() # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) +src.normalize_background() src.whiten(aiso_noise_estimator.filter) -# Plot the noise profile for inspection -if interactive: - plt.imshow(aiso_noise_estimator.filter.evaluate_grid(img_size)) - plt.show() - -# Peek, what do the whitened images look like... -if interactive: - src.images[:10].show() - -# # Optionally invert image contrast, depends on data convention. -# # This is not needed for 10028, but included anyway. -# logger.info("Invert the global density contrast") -# src.invert_contrast() - -# %% -# Optional: CWF Denoising -# ----------------------- -# -# Optionally generate an alternative source that is denoised with `cov2d`, -# then configure a customized averager. This allows the use of CWF denoised -# images for classification, but stacks the original images for averages -# used in the remainder of the reconstruction pipeline. -# -# In this example, this behavior is controlled by the `do_cov2d` boolean variable. -# When disabled, the original src and default averager is used. -# If you will not be using cov2d, -# you may remove this code block and associated variables. - -classification_src = src -custom_averager = None -if do_cov2d: - # Use CWF denoising - cwf_denoiser = DenoiserCov2D(src) - # Use denoised src for classification - classification_src = cwf_denoiser.denoise() - # Peek, what do the denoised images look like... - if interactive: - classification_src.images[:10].show() - # %% # Class Averaging # ---------------------- @@ -131,20 +89,11 @@ logger.info("Begin Class Averaging") # Now perform classification and averaging for each class. -# This also demonstrates the potential to use a different source for classification and averaging. - -avgs_src = ClassAvgSourcev11( - classification_src, - n_nbor=n_nbor, - averager_src=src, - num_procs=None, # Automaticaly configure parallel processing -) +# Automaticaly configure parallel processing +avgs = ClassAvgSourcev11(src, n_nbor=n_nbor, num_procs=None) # We'll manually cache `n_classes` worth to speed things up. -avgs = ArrayImageSource(avgs_src.images[:n_classes]) - -if interactive: - avgs.images[:10].show() +avgs = ArrayImageSource(avgs.images[:n_classes]) # %% @@ -181,8 +130,3 @@ # Perform the estimation and save the volume. estimated_volume = estimator.estimate() estimated_volume.save(volume_filename_prefix_out, overwrite=True) - -# Peek at result -if interactive: - plt.imshow(np.sum(estimated_volume[0], axis=-1)) - plt.show() From 8d8601d738625f9e4e6d6411bdb9dfbf6b9215d1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 12:08:55 -0500 Subject: [PATCH 220/424] initial draft 10005 experiment --- .../experimental_abinitio_pipeline_10005.py | 108 +++++------------- 1 file changed, 26 insertions(+), 82 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10005.py b/gallery/experiments/experimental_abinitio_pipeline_10005.py index 77f04fb7b3..96d795ce54 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10005.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10005.py @@ -1,18 +1,18 @@ """ -Abinitio Pipeline - Experimental Data -===================================== +Abinitio Pipeline - Experimental Data Empiar 10005 +================================================== This notebook introduces a selection of -components corresponding to loading real Relion picked -particle Cryo-EM data and running key ASPIRE-Python +components corresponding to loading real coordinate +base particle Cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. Specifically this pipeline uses the -EMPIAR 10028 picked particles data, available here: +EMPIAR 10005 picked particles data, available here: -https://www.ebi.ac.uk/empiar/EMPIAR-10028 +https://www.ebi.ac.uk/empiar/EMPIAR-10005 -https://www.ebi.ac.uk/emdb/EMD-10028 +https://www.ebi.ac.uk/emdb/EMD-5778 """ # %% @@ -22,17 +22,16 @@ # In addition, import some classes from # the ASPIRE package that will be used throughout this experiment. +import glob import logging - -import matplotlib.pyplot as plt -import numpy as np +import os from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis3D -from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D +from aspire.denoising import ClassAvgSourcev11 from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, RelionSource +from aspire.source import ArrayImageSource, CentersCoordinateSource logger = logging.getLogger(__name__) @@ -42,86 +41,45 @@ # --------------- # Example simulation configuration. -interactive = False # Draw blocking interactive plots? -do_cov2d = True # Use CWF coefficients +working_dir = "/scratch/ExperimentalData/staging/10017/data" n_imgs = None # Set to None for all images in starfile, can set smaller for tests. img_size = 32 # Downsample the images/reconstruction to a desired resolution n_classes = 2000 # How many class averages to compute. n_nbor = 100 # How many neighbors to stack -starfile_in = "10028/data/shiny_2sets_fixed9.star" -data_folder = "." # This depends on the specific starfile entries. -volume_filename_prefix_out = f"10028_recon_c{n_classes}_m{n_nbor}_{img_size}.mrc" -pixel_size = 1.34 +volume_filename_prefix_out = f"10005_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +particle_size = 300 # %% # Source data and Preprocessing # ----------------------------- # -# `RelionSource` is used to access the experimental data via a `starfile`. +# `CentersCoordinateSource` is used to access the experimental data from +# mrcs using coordinate files. +# # Begin by downsampling to our chosen resolution, then preprocess # to correct for CTF and noise. +mrcs = sorted(glob.glob(os.path.join(working_dir, "*.mrc"))) +coords = sorted(glob.glob(os.path.join(working_dir, "*.coord"))) +files = list(zip(mrcs, coords)) + # Create a source object for the experimental images -src = RelionSource( - starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder -) +src = CentersCoordinateSource(files, particle_size=particle_size, max_rows=n_imgs) # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") src.downsample(img_size) -# Peek -if interactive: - src.images[:10].show() - # Use phase_flip to attempt correcting for CTF. logger.info("Perform phase flip to input images.") src.phase_flip() # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) +src.normalize_background() src.whiten(aiso_noise_estimator.filter) -# Plot the noise profile for inspection -if interactive: - plt.imshow(aiso_noise_estimator.filter.evaluate_grid(img_size)) - plt.show() - -# Peek, what do the whitened images look like... -if interactive: - src.images[:10].show() - -# # Optionally invert image contrast, depends on data convention. -# # This is not needed for 10028, but included anyway. -# logger.info("Invert the global density contrast") -# src.invert_contrast() - -# %% -# Optional: CWF Denoising -# ----------------------- -# -# Optionally generate an alternative source that is denoised with `cov2d`, -# then configure a customized averager. This allows the use of CWF denoised -# images for classification, but stacks the original images for averages -# used in the remainder of the reconstruction pipeline. -# -# In this example, this behavior is controlled by the `do_cov2d` boolean variable. -# When disabled, the original src and default averager is used. -# If you will not be using cov2d, -# you may remove this code block and associated variables. - -classification_src = src -custom_averager = None -if do_cov2d: - # Use CWF denoising - cwf_denoiser = DenoiserCov2D(src) - # Use denoised src for classification - classification_src = cwf_denoiser.denoise() - # Peek, what do the denoised images look like... - if interactive: - classification_src.images[:10].show() - # %% # Class Averaging # ---------------------- @@ -131,20 +89,11 @@ logger.info("Begin Class Averaging") # Now perform classification and averaging for each class. -# This also demonstrates the potential to use a different source for classification and averaging. - -avgs_src = ClassAvgSourcev11( - classification_src, - n_nbor=n_nbor, - averager_src=src, - num_procs=None, # Automaticaly configure parallel processing -) +# Automaticaly configure parallel processing +avgs = ClassAvgSourcev11(src, n_nbor=n_nbor, num_procs=None) # We'll manually cache `n_classes` worth to speed things up. -avgs = ArrayImageSource(avgs_src.images[:n_classes]) - -if interactive: - avgs.images[:10].show() +avgs = ArrayImageSource(avgs.images[:n_classes]) # %% @@ -181,8 +130,3 @@ # Perform the estimation and save the volume. estimated_volume = estimator.estimate() estimated_volume.save(volume_filename_prefix_out, overwrite=True) - -# Peek at result -if interactive: - plt.imshow(np.sum(estimated_volume[0], axis=-1)) - plt.show() From 499d6e195f61a4790eee9296aaf5e51c715dd074 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 13:14:52 -0500 Subject: [PATCH 221/424] BFS corr bug now that we're using normalized corr --- src/aspire/classification/averager2d.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 4cc55936de..620db124d2 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -327,7 +327,8 @@ def align(self, classes, reflections, basis_coefficients): correlations = np.empty(classes.shape, dtype=self.dtype) def _innerloop(k): - _correlations = np.empty((n_nbor, self.n_angles)) + _correlations = np.full((n_nbor, self.n_angles), fill_value=-np.inf) + _correlations[0, 0] = 1 # Set this now so we can skip it in the loop. # Get the coefs for these neighbors if basis_coefficients is None: # Retrieve relevant images @@ -354,12 +355,15 @@ def _innerloop(k): # then store dot between class base image (0) and each nbor for j, nbor in enumerate(rotated_nbrs): + # Skip the base image. + if j == 0: + continue norm_nbor = np.linalg.norm(nbor) _correlations[j, i] = np.dot(nbr_coef[0], nbor) / ( norm_nbor * norm_0 ) - # Now along each class, find the index of the angle reporting highest correlation + # Now find the index of the angle reporting highest correlation angle_idx = np.argmax(_correlations, axis=1) # Take the correlation corresponding to angle_idx From f9f10b550f4983e54b9b31881c928f9b2c723385 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 13:14:56 -0500 Subject: [PATCH 222/424] Revert matlab suggestion for rir sample_n size --- src/aspire/classification/rir_class2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 084f62d8e3..6d369c6477 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -21,7 +21,7 @@ def __init__( pca_basis=None, fspca_components=None, alpha=1 / 3, - sample_n=50000, # Paper had 4000, but MATLAB code suggested 50000 + sample_n=4000, # Paper had 4000, but MATLAB code suggested 50000 bispectrum_components=300, n_nbor=100, bispectrum_freq_cutoff=None, From a24c3d9f844fb33a46b8783c34d8d46cda9687c5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 14:03:32 -0500 Subject: [PATCH 223/424] give up on 10005, try 10081 for now --- .../experimental_abinitio_pipeline_10028.py | 2 +- ...> experimental_abinitio_pipeline_10081.py} | 43 +++++++++---------- 2 files changed, 21 insertions(+), 24 deletions(-) rename gallery/experiments/{experimental_abinitio_pipeline_10005.py => experimental_abinitio_pipeline_10081.py} (74%) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index 0ac46ead38..f8878c86ad 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -156,7 +156,7 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSyncVoting(avgs, n_theta=36) +orient_est = CLSyncVoting(avgs, n_theta=360) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/experimental_abinitio_pipeline_10005.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py similarity index 74% rename from gallery/experiments/experimental_abinitio_pipeline_10005.py rename to gallery/experiments/experimental_abinitio_pipeline_10081.py index dbaf85fffc..3beaaf011e 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10005.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -1,18 +1,18 @@ """ -Abinitio Pipeline - Experimental Data Empiar 10005 +Abinitio Pipeline - Experimental Data Empiar 10081 ================================================== This notebook introduces a selection of -components corresponding to loading real coordinate -base particle Cryo-EM data and running key ASPIRE-Python +components corresponding to loading real Relion picked +particle Cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. Specifically this pipeline uses the -EMPIAR 10005 picked particles data, available here: +EMPIAR 10081 picked particles data, available here: -https://www.ebi.ac.uk/empiar/EMPIAR-10005 +https://www.ebi.ac.uk/empiar/EMPIAR-10081 -https://www.ebi.ac.uk/emdb/EMD-5778 +https://www.ebi.ac.uk/emdb/EMD-8511 """ # %% @@ -22,16 +22,17 @@ # In addition, import some classes from # the ASPIRE package that will be used throughout this experiment. -import os -import glob import logging +import matplotlib.pyplot as plt +import numpy as np + from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis3D from aspire.denoising import ClassAvgSourcev11 from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, CentersCoordinateSource +from aspire.source import ArrayImageSource, RelionSource logger = logging.getLogger(__name__) @@ -41,31 +42,28 @@ # --------------- # Example simulation configuration. -working_dir = "/scratch/ExperimentalData/staging/10017/data" n_imgs = None # Set to None for all images in starfile, can set smaller for tests. img_size = 32 # Downsample the images/reconstruction to a desired resolution n_classes = 2000 # How many class averages to compute. n_nbor = 100 # How many neighbors to stack -volume_filename_prefix_out = f"10005_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" -particle_size = 300 +starfile_in = "/scratch/ExperimentalData/staging/10081/data/Particles/micrographs/data.star" +data_folder = "." # This depends on the specific starfile entries. +volume_filename_prefix_out = f"10081_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +pixel_size = 1.3 # %% # Source data and Preprocessing # ----------------------------- # -# `CentersCoordinateSource` is used to access the experimental data from -# mrcs using coordinate files. -# +# `RelionSource` is used to access the experimental data via a `starfile`. # Begin by downsampling to our chosen resolution, then preprocess # to correct for CTF and noise. -mrcs = sorted(glob.glob(os.path.join(working_dir, "*.mrc"))) -coords = sorted(glob.glob(os.path.join(working_dir, "*.coord"))) -files = list(zip(mrcs, coords)) - # Create a source object for the experimental images -src = CentersCoordinateSource(files, particle_size=particle_size, max_rows=n_imgs) +src = RelionSource( + starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder +) # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") @@ -77,7 +75,6 @@ # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) -src.normalize_background() src.whiten(aiso_noise_estimator.filter) # %% @@ -89,7 +86,7 @@ logger.info("Begin Class Averaging") # Now perform classification and averaging for each class. -# Automaticaly configure parallel processing + # Automaticaly configure parallel processing avgs = ClassAvgSourcev11(src, n_nbor=n_nbor, num_procs=None) # We'll manually cache `n_classes` worth to speed things up. @@ -105,7 +102,7 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSyncVoting(avgs, n_theta=36) +orient_est = CLSyncVoting(avgs, n_theta=360) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations From 54847b4cfea1b8b13b2e977a97e360ee1bbde683 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 14:18:42 -0500 Subject: [PATCH 224/424] merge conflict --- .../experimental_abinitio_pipeline_10081.py | 52 ++++--------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index bb180199ac..4160910af0 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -1,30 +1,18 @@ """ -<<<<<<< HEAD:gallery/experiments/experimental_abinitio_pipeline_10081.py Abinitio Pipeline - Experimental Data Empiar 10081 -======= -Abinitio Pipeline - Experimental Data Empiar 10005 ->>>>>>> f9f10b550f4983e54b9b31881c928f9b2c723385:gallery/experiments/experimental_abinitio_pipeline_10005.py ================================================== This notebook introduces a selection of -components corresponding to loading real coordinate -base particle Cryo-EM data and running key ASPIRE-Python +components corresponding to loading real Relion picked +particle Cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. Specifically this pipeline uses the -<<<<<<< HEAD:gallery/experiments/experimental_abinitio_pipeline_10081.py EMPIAR 10081 picked particles data, available here: https://www.ebi.ac.uk/empiar/EMPIAR-10081 https://www.ebi.ac.uk/emdb/EMD-8511 -======= -EMPIAR 10005 picked particles data, available here: - -https://www.ebi.ac.uk/empiar/EMPIAR-10005 - -https://www.ebi.ac.uk/emdb/EMD-5778 ->>>>>>> f9f10b550f4983e54b9b31881c928f9b2c723385:gallery/experiments/experimental_abinitio_pipeline_10005.py """ # %% @@ -34,16 +22,14 @@ # In addition, import some classes from # the ASPIRE package that will be used throughout this experiment. -import glob import logging -import os from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis3D from aspire.denoising import ClassAvgSourcev11 from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, CentersCoordinateSource +from aspire.source import ArrayImageSource, RelionSource logger = logging.getLogger(__name__) @@ -53,41 +39,30 @@ # --------------- # Example simulation configuration. -<<<<<<< HEAD:gallery/experiments/experimental_abinitio_pipeline_10081.py -======= -working_dir = "/scratch/ExperimentalData/staging/10017/data" ->>>>>>> f9f10b550f4983e54b9b31881c928f9b2c723385:gallery/experiments/experimental_abinitio_pipeline_10005.py n_imgs = None # Set to None for all images in starfile, can set smaller for tests. img_size = 32 # Downsample the images/reconstruction to a desired resolution n_classes = 2000 # How many class averages to compute. n_nbor = 100 # How many neighbors to stack -<<<<<<< HEAD:gallery/experiments/experimental_abinitio_pipeline_10081.py -starfile_in = "/scratch/ExperimentalData/staging/10081/data/Particles/micrographs/data.star" -data_folder = "." # This depends on the specific starfile entries. +starfile_in = ( + "/scratch/ExperimentalData/staging/10081/data/Particles/micrographs/data.star" +) +data_folder = "../.." # This depends on the specific starfile entries. volume_filename_prefix_out = f"10081_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" pixel_size = 1.3 -======= -volume_filename_prefix_out = f"10005_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" -particle_size = 300 ->>>>>>> f9f10b550f4983e54b9b31881c928f9b2c723385:gallery/experiments/experimental_abinitio_pipeline_10005.py # %% # Source data and Preprocessing # ----------------------------- # -# `CentersCoordinateSource` is used to access the experimental data from -# mrcs using coordinate files. -# +# `RelionSource` is used to access the experimental data via a `starfile`. # Begin by downsampling to our chosen resolution, then preprocess # to correct for CTF and noise. -mrcs = sorted(glob.glob(os.path.join(working_dir, "*.mrc"))) -coords = sorted(glob.glob(os.path.join(working_dir, "*.coord"))) -files = list(zip(mrcs, coords)) - # Create a source object for the experimental images -src = CentersCoordinateSource(files, particle_size=particle_size, max_rows=n_imgs) +src = RelionSource( + starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder +) # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") @@ -99,7 +74,6 @@ # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) -src.normalize_background() src.whiten(aiso_noise_estimator.filter) # %% @@ -111,11 +85,7 @@ logger.info("Begin Class Averaging") # Now perform classification and averaging for each class. -<<<<<<< HEAD:gallery/experiments/experimental_abinitio_pipeline_10081.py - # Automaticaly configure parallel processing -======= # Automaticaly configure parallel processing ->>>>>>> f9f10b550f4983e54b9b31881c928f9b2c723385:gallery/experiments/experimental_abinitio_pipeline_10005.py avgs = ClassAvgSourcev11(src, n_nbor=n_nbor, num_procs=None) # We'll manually cache `n_classes` worth to speed things up. From 89d9fb5c5a0fac6d9a00bf05ef755d194cdfe504 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 3 Mar 2023 16:33:38 -0500 Subject: [PATCH 225/424] Josh re-review remarks, strings --- gallery/tutorials/pipeline_demo.py | 19 ++----------------- tests/test_noise.py | 12 ++++++------ 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 6ed2876953..a60a37b0a4 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -66,7 +66,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% -# Noise and CTF Filters +# CTF Filters # ^^^^^^^^^^^^^^^^^^^^^ # Let's start by creating CTF filters. The ``operators`` package contains a collection # of filter classes that can be supplied to a ``Simulation``. @@ -90,7 +90,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # Initialize Simulation Object # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # We feed our ``Volume`` and filters into ``Simulation`` to generate the dataset of images. -# When controlled white Gaussian noise is desired, ``*NoiseAdder.from_snr()`` +# When controlled white Gaussian noise is desired, ``WhiteNoiseAdder.from_snr()`` # can be used to generate a simulation data set around a specific SNR. # # Alternatively, users can bring their own images using an ``ArrayImageSource``, @@ -192,21 +192,6 @@ def download(url, save_path, chunk_size=1024 * 1024): # work because we used ``TopClassSelector`` to classify our images. src.images[0:10].show() -# %% - -# Compare the estimated SNR before and after Class Averaging. -src_snr = src.estimate_snr() -avgs_snr = avgs.estimate_snr() -logging.info( - f"Simulation SNR (includes any additonal offset, amplitude, and CTF corruptions): {src_snr}" -) -logging.info(f"Class Averaged SNR: {avgs_snr}") -# Alternatively, estimate power in the areas outside the signal support, -# which should be predominantly noise. -src_noise_power = src.estimate_noise_power() -avgs_noise_power = avgs.estimate_noise_power() -logging.info(f"Simulation noise power: {src_noise_power}") -logging.info(f"Class Averaged noise power: {avgs_noise_power}") # %% # Orientation Estimation diff --git a/tests/test_noise.py b/tests/test_noise.py index 12b5b47dde..91f4346484 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -204,10 +204,10 @@ def test_from_snr_white(sim_fixture, target_noise_variance): @pytest.mark.parametrize( "target_noise_variance", VARS, ids=lambda param: f"var={param}" ) -def test_from_snr_pink_iso(sim_fixture, target_noise_variance): +def test_pink_iso_noise_estimation(sim_fixture, target_noise_variance): """ - Test that prescribing noise directly by var and by `from_snr`, - are close for a variety of paramaters. + Test that prescribing isotropic pink-ish noise + is close to target for a variety of paramaters. """ custom_filter = FunctionFilter(f=_pinkish_spectrum) * ScalarFilter( @@ -233,10 +233,10 @@ def test_from_snr_pink_iso(sim_fixture, target_noise_variance): @pytest.mark.parametrize( "target_noise_variance", VARS, ids=lambda param: f"var={param}" ) -def test_from_snr_aniso(sim_fixture, target_noise_variance): +def test_pink_aniso_noise_estimation(sim_fixture, target_noise_variance): """ - Test that prescribing noise directly by var and by `from_snr`, - are close for a variety of paramaters. + Test that prescribing anisotropic pink-ish noise + is close to target for a variety of paramaters. """ # Create the custom noise function and associated Filter From 3a3ef849e0dd10465f6132047741387a587bb967 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 09:26:19 -0500 Subject: [PATCH 226/424] tutorial tweaks and comment line lens --- gallery/tutorials/class_averaging.py | 55 ++++++++++-------- gallery/tutorials/pipeline_demo.py | 87 +++++++++++++++++----------- 2 files changed, 83 insertions(+), 59 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 4b3a50f438..1679f52458 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -2,7 +2,8 @@ Class Averaging =============== -We demonstrate class averaging using the rotationally invariant representation algorithm. +We demonstrate class averaging using the rotationally invariant +representation algorithm. """ import logging @@ -68,7 +69,8 @@ # Example Data Set Source # ^^^^^^^^^^^^^^^^^^^^^^^ # -# We concatenate and shuffle 512 rotations of the Gaussian images above to create our data set. +# We concatenate and shuffle 512 rotations of the Gaussian images +# above to create our data set. # How many entries (angles) in our stack N = 512 @@ -85,12 +87,14 @@ classYOvalL[i] = np.asarray(PILImage.fromarray(yoval_discL).rotate(theta)) classYOvalR[i] = np.asarray(PILImage.fromarray(yoval_discR).rotate(theta)) -# We'll make an example data set by concatentating then shuffling these. +# We'll make an example data set by concatentating then shuffling +# these. example_array = np.concatenate((classRound, classOval, classYOvalL, classYOvalR)) np.random.seed(1234567) np.random.shuffle(example_array) -# So now that we have cooked up an example dataset, lets create an ASPIRE source +# So now that we have cooked up an example dataset, lets create an +# ASPIRE source src = ArrayImageSource(example_array) # Let's peek at the images to make sure they're shuffled up nicely @@ -100,14 +104,16 @@ # Basic Class Average # ------------------- # -# This first example uses the ``DebugClassAvgSource`` to classify images -# via the rotationally invariant representation (``RIRClass2D``) algorithm. -# ``DebugClassAvgSource`` internally uses ``TopClassSelector`` by default. -# ``TopClassSelector`` deterministically selects the first ``n_classes``. -# ``DebugClassAvgSource`` also uses brute force rotational alignment without shifts. -# These simplifications are useful for development and debugging. -# Later we will discuss the more general ``ClassAvgSource`` and the modular -# components that are more suitable to simulations and experimental datasets. +# This first example uses the ``DebugClassAvgSource`` to classify +# images via the rotationally invariant representation +# (``RIRClass2D``) algorithm. ``DebugClassAvgSource`` internally uses +# ``TopClassSelector`` by default. ``TopClassSelector`` +# deterministically selects the first ``n_classes``. +# ``DebugClassAvgSource`` also uses brute force rotational alignment +# without shifts. These simplifications are useful for development +# and debugging. Later we will discuss the more general +# ``ClassAvgSource`` and the modular components that are more suitable +# to simulations and experimental datasets. n_classes = 10 @@ -169,14 +175,12 @@ # %% # Display Classes # ^^^^^^^^^^^^^^^ -# Here a little more work occurs, as the ``ClassAvgSourcev11`` will -# compute an internal measure quality (contrast), -# and avoid images already included in higher ranking classes. -# All this occurs inside the ``ClassAvgSource`` component. -# When using more advanced class average sources, -# the images are remapped by the `selector`. -# So in this case, the first 10 images will be those with the highest contrast, -# that we have not already seen. +# Here, on request for images, the class average source will classify, +# select, and average images. All this occurs inside the +# ``ClassAvgSource`` components. When using more advanced class +# average sources, the images are remapped by the `selector`. In this +# case, using ``DebugClassAvgSource`` the first 10 images will simply +# correspond to the first ten from ``noise_src``. avgs.images[:10].show() @@ -189,9 +193,11 @@ review_class = 5 -# Map this image from the sorted selection back to the input ``noisy_src``. +# Map this image from the sorted selection back to the input +# ``noisy_src``. -# Report the identified neighbor indices with respect to the input ``noise_src``. +# Report the identified neighbor indices with respect to the input +# ``noise_src``. classes = avgs._nn_classes[review_class] reflections = avgs._nn_reflections[review_class] logger.info(f"Class {review_class}'s neighors: {classes}") @@ -218,8 +224,9 @@ # Alignment Details # ^^^^^^^^^^^^^^^^^ # -# Alignment details are exposed when avaialable from an underlying ``averager``. -# In this case, we'll get the estimated alignments for the ``review_class``. +# Alignment details are exposed when avaialable from an underlying +# ``averager``. In this case, we'll get the estimated alignments for +# the ``review_class``. est_rotations = avgs.averager.rotations diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 052547d9c1..b7a9783c3a 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -3,15 +3,16 @@ Ab-initio Pipeline Demonstration ================================ -This tutorial demonstrates some key components of an ab-initio reconstruction pipeline using -synthetic data generated with ASPIRE's ``Simulation`` class of objects. +This tutorial demonstrates some key components of an ab-initio +reconstruction pipeline using synthetic data generated with ASPIRE's +``Simulation`` class of objects. """ # %% # Download a Volume # ----------------- -# We begin by downloading a high resolution volume map of the 80S Ribosome, sourced from -# EMDB: https://www.ebi.ac.uk/emdb/EMD-2660. +# We begin by downloading a high resolution volume map of the 80S +# Ribosome, sourced from EMDB: https://www.ebi.ac.uk/emdb/EMD-2660. import os @@ -59,25 +60,29 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Create a Simulation Source # -------------------------- -# ASPIRE's ``Simulation`` class can be used to generate a synthetic dataset of projection images. -# A ``Simulation`` object produces random projections of a supplied Volume and applies noise and -# CTF filters. The resulting stack of 2D images is stored in an ``Image`` object. +# ASPIRE's ``Simulation`` class can be used to generate a synthetic +# dataset of projection images. A ``Simulation`` object produces +# random projections of a supplied Volume and applies noise and CTF +# filters. The resulting stack of 2D images is stored in an ``Image`` +# object. # %% # Noise and CTF Filters # ^^^^^^^^^^^^^^^^^^^^^ -# Let's start by creating noise and CTF filters. The ``operators`` package contains a collection -# of filter classes that can be supplied to a ``Simulation``. We use ``ScalarFilter`` to create -# Gaussian white noise and ``RadialCTFFilter`` to generate a set of CTF filters with various defocus values. +# Let's start by creating noise and CTF filters. The ``operators`` +# package contains a collection of filter classes that can be supplied +# to a ``Simulation``. We use ``ScalarFilter`` to create Gaussian +# white noise and ``RadialCTFFilter`` to generate a set of CTF filters +# with various defocus values. # Create noise and CTF filters from aspire.noise import WhiteNoiseAdder from aspire.operators import RadialCTFFilter -# Gaussian noise filter. -# Note, the value supplied to the ``WhiteNoiseAdder``, chosen based on other parameters -# for this quick tutorial, can be changed to adjust the power of the noise. +# Gaussian noise filter. Note, the value supplied to the +# ``WhiteNoiseAdder``, chosen based on other parameters for this quick +# tutorial, can be changed to adjust the power of the noise. noise_adder = WhiteNoiseAdder(var=1e-5) # Radial CTF Filter @@ -94,7 +99,8 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Initialize Simulation Object # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -# We feed our ``Volume`` and filters into ``Simulation`` to generate the dataset of images. +# We feed our ``Volume`` and filters into ``Simulation`` to generate +# the dataset of images. from aspire.source import Simulation @@ -145,11 +151,14 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Class Averaging # --------------- -# We use ``RIRClass2D`` object to classify the images via the rotationally invariant -# representation (RIR) algorithm. Class selection is customizable. The classification module -# also includes a set of protocols for selecting a set of images to be used for classification. -# Here we're using ``TopClassSelector``, which selects the first ``n_classes`` images from the source. -# In practice, the selection is done by sorting based on some configurable notion of quality. +# We use ``RIRClass2D`` object to classify the images via the +# rotationally invariant representation (RIR) algorithm. Class +# selection is customizable. The classification module also includes a +# set of protocols for selecting a set of images to be used for +# classification. Here we're using ``TopClassSelector``, which +# selects the first ``n_classes`` images from the source. In +# practice, the selection is done by sorting class averages based on +# some configurable notion of quality. from aspire.classification import RIRClass2D, TopClassSelector @@ -157,8 +166,9 @@ def download(url, save_path, chunk_size=1024 * 1024): n_classes = 200 n_nbor = 6 -# We will customize our class averaging source. Note that the ``fspca_components`` and -# ``bispectrum_components`` were selected for this small tutorial. +# We will customize our class averaging source. Note that the +# ``fspca_components`` and ``bispectrum_components`` were selected for +# this small tutorial. rir = RIRClass2D( src, fspca_components=40, @@ -173,7 +183,8 @@ def download(url, save_path, chunk_size=1024 * 1024): classifier=rir, ) -# For this small example, it is most effective to just cache these manually. +# For this small example, it is most effective to just cache these +# manually. from aspire.source import ArrayImageSource @@ -188,16 +199,18 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% -# Show original images corresponding to those classes. This 1:1 comparison is only expected to -# work because we used ``TopClassSelector`` to classify our images. +# Show original images corresponding to those classes. This 1:1 +# comparison is only expected to work because we used +# ``TopClassSelector`` to classify our images. src.images[0:10].show() # %% # Orientation Estimation # ---------------------- -# We initialize a ``CLSyncVoting`` class instance for estimating the orientations of the images. -# The estimation employs the common lines method with synchronization and voting. +# We initialize a ``CLSyncVoting`` class instance for estimating the +# orientations of the images. The estimation employs the common lines +# method with synchronization and voting. from aspire.abinitio import CLSyncVoting @@ -214,8 +227,9 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Mean Squared Error # ------------------ -# ASIPRE has some built-in utility functions for globally aligning the estimated rotations -# to the true rotations and computing the mean squared error. +# ASIPRE has some built-in utility functions for globally aligning the +# estimated rotations to the true rotations and computing the mean +# squared error. from aspire.utils.coor_trans import ( get_aligned_rotations, @@ -233,8 +247,9 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Volume Reconstruction # --------------------- -# Now that we have our class averages and rotation estimates, we can estimate the -# mean volume by supplying the class averages and basis for back projection. +# Now that we have our class averages and rotation estimates, we can +# estimate the mean volume by supplying the class averages and basis +# for back projection. from aspire.basis import FFBBasis3D from aspire.reconstruction import MeanEstimator @@ -255,14 +270,16 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Comparison of Estimated Volume with Source Volume # ------------------------------------------------- -# To get a visual confirmation that our results are sane, we rotate the -# estimated volume by the estimated rotations and project along the z-axis. -# These estimated projections should align with the original projection images. +# To get a visual confirmation that our results are sane, we rotate +# the estimated volume by the estimated rotations and project along +# the z-axis. These estimated projections should align with the +# original projection images. from aspire.source import ArrayImageSource -# Get projections from the estimated volume using the estimated orientations. -# We instantiate the projections as an ``ArrayImageSource`` to access the ``Image.show()`` method. +# Get projections from the estimated volume using the estimated +# orientations. We instantiate the projections as an +# ``ArrayImageSource`` to access the ``Image.show()`` method. projections_est = ArrayImageSource(estimated_volume.project(0, rots_est)) # We view the first 10 projections of the estimated volume. From 678ad706168caf04e29d508c97a375c2497eeb2b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 09:47:06 -0500 Subject: [PATCH 227/424] Remove Ranked/Unranked subclasses --- src/aspire/classification/class_selection.py | 47 +++++++++----------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index d94c8fa09f..00a3f4846a 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -49,17 +49,22 @@ def _select(self, classes, reflections, distances): :return: array of indices into `classes` """ - @classmethod @property - @abstractmethod - def quality_scores(cls): + def quality_scores(self): """ - All `ClassSelector` should assign a quality score - array the same length as the selection output. + All `ClassSelector` should assign a quality score array the + same length as the selection output. For subclasses like TopClassSelector and RandomClassSelector - where no quality information is derived, this array should be zeros. + where no quality information is derived, the associated + `_quality_scores` should be set to zeros by `_select`. """ + # Remind the user to run selection first. We could run it + # automatically, but some of the class selectors may take a + # long time. + if not hasattr(self, "_quality_scores"): + raise RuntimeError("Must run `.select(...)` to compute quality_scores") + return self._quality_scores def select(self, classes, reflections, distances): """ @@ -110,31 +115,20 @@ def _check_selection(self, selection, n_img, len_operator=eq): ) -class ClassSelectorUnranked(ClassSelector): - @property - def quality_scores(self): - return np.zeros(self._max_n + 1) - - -class ClassSelectorRanked(ClassSelector): - @property - def quality_scores(self): - if not hasattr(self, "_quality_scores"): - raise RuntimeError("Must run `.select(...)` to compute quality_scores") - return self._quality_scores - - -class TopClassSelector(ClassSelectorUnranked): +class TopClassSelector(ClassSelector): def _select(self, classes, reflections, distances): """ Returns classes in `Source` order. Mainly useful for debugging. """ + # Assign uniform quality. + self._quality_scores = np.zeros(self._max_n + 1) + # Return same indices as initial source. return np.arange(self._max_n + 1) # +1 for zero indexing -class RandomClassSelector(ClassSelectorUnranked): +class RandomClassSelector(ClassSelector): def __init__(self, seed=None): """ :param seed: RNG seed, de @@ -145,6 +139,9 @@ def _select(self, classes, reflections, distances): """ Select random `n` classes from the population. """ + # Assign uniform quality. + self._quality_scores = np.zeros(self._max_n + 1) + # Instantiate a random Generator rng = np.random.default_rng(self.seed) # Generate and return indices for random sample @@ -152,7 +149,7 @@ def _select(self, classes, reflections, distances): return rng.choice(self._max_n + 1, size=self._max_n + 1, replace=False) -class ContrastClassSelector(ClassSelectorRanked): +class ContrastClassSelector(ClassSelector): """ Selects top classes based on highest contrast, as estimated by variances of `distances`. @@ -175,7 +172,7 @@ def _select(self, classes, reflections, distances): return sorted_class_inds -class DistanceClassSelector(ClassSelectorRanked): +class DistanceClassSelector(ClassSelector): """ Selects top classes based on lowest mean distance as estimated by `distances`. @@ -197,7 +194,7 @@ def _select(self, classes, reflections, distances): return sorted_class_inds -class GlobalClassSelector(ClassSelectorRanked): +class GlobalClassSelector(ClassSelector): """ Extends ClassSelector for methods that require passing over all class average images. From 1571ae286c89fc7a0ba4b9e6396ddbe35b4ca085 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 09:58:10 -0500 Subject: [PATCH 228/424] Replace self._max_n + 1 with self.n --- src/aspire/classification/class_selection.py | 32 +++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 00a3f4846a..dca7dff1d9 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -79,17 +79,19 @@ def select(self, classes, reflections, distances): :return: array of indices into `classes` """ - # Compute max class id found in the network for sanity checking. when unknown. - # Some subclasses may have a more explicit `_max_n` from an initializing `Source`. - if not hasattr(self, "_max_n"): - self._max_n = np.max(classes[:, 0]) + # Compute max class id found in the network for sanity + # checking. Base class defaults to the `n` known from the + # initializing `Source`. This `n` may be reduced through + # class selection. + if not hasattr(self, "n"): + self.n = np.max(classes[:, 0]) + 1 # +1 for zero indexing # Call the selection logic self.selection = self._select(classes, reflections, distances) - # n_img should not exceed the largest index in first column of `classes` - n_img = np.max(classes[:, 0]) + 1 - # Check values in selection are in bounds. + # n_img should not exceed the largest index in first column of `classes`. + n_img = np.max(classes[:, 0]) + 1 # +1 for zero indexing + # Check values in selection are in the bounds. self._check_selection(self.selection, n_img) return self.selection @@ -102,9 +104,9 @@ def _check_selection(self, selection, n_img, len_operator=eq): :param n_img: number of images available """ # Check length, +1 for zero indexing - if not len_operator(len(selection), self._max_n + 1): + if not len_operator(len(selection), self.n): raise ValueError( - f"Class selection must be {str(len_operator)} {self._max_n+1}, got {len(selection)}" + f"Class selection must be {str(len_operator)} {self.n}, got {len(selection)}" ) # Check indices [0, n_img) @@ -123,9 +125,9 @@ def _select(self, classes, reflections, distances): Mainly useful for debugging. """ # Assign uniform quality. - self._quality_scores = np.zeros(self._max_n + 1) + self._quality_scores = np.zeros(self.n) # Return same indices as initial source. - return np.arange(self._max_n + 1) # +1 for zero indexing + return np.arange(self.n) class RandomClassSelector(ClassSelector): @@ -140,13 +142,13 @@ def _select(self, classes, reflections, distances): Select random `n` classes from the population. """ # Assign uniform quality. - self._quality_scores = np.zeros(self._max_n + 1) + self._quality_scores = np.zeros(self.n) # Instantiate a random Generator rng = np.random.default_rng(self.seed) # Generate and return indices for random sample # +1 for zero indexing - return rng.choice(self._max_n + 1, size=self._max_n + 1, replace=False) + return rng.choice(self.n, size=self.n, replace=False) class ContrastClassSelector(ClassSelector): @@ -214,9 +216,9 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): ) self._quality_function = quality_function - self._max_n = self.averager.src.n - 1 + self.n = self.averager.src.n # We should hit every entry, but along the way identify missing values as nan. - self._quality_scores = np.full(self._max_n + 1, fill_value=float("nan")) + self._quality_scores = np.full(self.n, fill_value=float("nan")) # To be used for heapq (score, img_id, avg_im) # Note the default implementation is min heap, so `pop` returns smallest value. From 734c4c212978813750e3d7a02e3b969c26fe106e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 12:59:22 -0500 Subject: [PATCH 229/424] Cleanup heap with _HeapItem --- src/aspire/classification/class_selection.py | 96 ++++++++++++++++---- src/aspire/denoising/class_avg.py | 14 +-- 2 files changed, 80 insertions(+), 30 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index dca7dff1d9..49e4bb8303 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -196,6 +196,37 @@ def _select(self, classes, reflections, distances): return sorted_class_inds +class _HeapItem: + """ + Organize our heap entries. + """ + + def __init__(self, value, index, image): + """ + Initialize a heap item. + + :param value: Float value used for heap ranking + :param index: Image index + :param image: Image object + """ + self.value = float(value) + self.index = int(index) + self.image = image + + @staticmethod + def nbytes(img_size, dtype): + """ + Computes a rough size of _HeapItem based on assigned + attributes. Note doesn't include python object overhead. + + :param img_size: Image size in pixels, (img_size, img_size). + :param dtype: Image datatype. + + :return: Sum of attribute sizes. + """ + return img_size**2 * dtype.itemsize + 16 + + class GlobalClassSelector(ClassSelector): """ Extends ClassSelector for methods that require @@ -204,10 +235,22 @@ class GlobalClassSelector(ClassSelector): def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): """ + Initializes a GlobalClassSelector. + + Because GlobalClassSelectors must compute all class averages, + a heap is maintained cache the top class averages as scored by + `quality_function`. If you have the memory, recommend setting + the cache to be > n_classes*img_size*img_size*img.dtype. + :param averager: An Averager2D subclass. - :param quality_function: Function that takes an image and returns numeric quality score. - This score will be used to sort the classes. - For example, this module provides methods for Contrast and SNR. + :param quality_function: Function that takes an image and + returns numeric quality score. This score will be used to + sort the classes. User's may provide a callable function, + but extending `ImageQualityFunction` is recommended. For + example, this module provides methods for Contrast and SNR + based quality. + :param heap_size_limit_bytes: Max heap size in Bytes. + Defaults 2GB, 0 will disable. """ self.averager = averager if not isinstance(self.averager, Averager2D): @@ -216,40 +259,50 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): ) self._quality_function = quality_function + if not callable(self._quality_function): + raise ValueError( + "`quality_function` must be a callable function. See ImageQualityFunction." + ) + self.n = self.averager.src.n - # We should hit every entry, but along the way identify missing values as nan. + # We should eventually compute all `n` entries, + # but start with identifying missing values as nan. self._quality_scores = np.full(self.n, fill_value=float("nan")) # To be used for heapq (score, img_id, avg_im) # Note the default implementation is min heap, so `pop` returns smallest value. self.heap = [] - self._heap_limit_bytes = heap_size_limit_bytes - self._heap_item_size = self.averager.src.L**2 + self.averager.dtype.itemsize + self._heap_limit_bytes = int(heap_size_limit_bytes) + self._heap_item_size = self.averager.src.L**2 * self.averager.dtype.itemsize @property def _heap_size(self): + """ + Return estimate of heap_size. Note this is not the exact + heap_size, as it doesn't include the overhead for python + objects. + """ n = len(self.heap) - if n == 0: - return 0 + item_size = _HeapItem.nbytes( + img_size=self.averager.composite_basis.nres, dtype=self.averager.dtype + ) - return n * self._heap_item_size + return n * item_size @property - def _heap_ids(self): + def heap_ids(self): """ Return the image ids currently in the heap. """ - return [item[1] for item in self.heap] # heap item=(score, img_id, img) + return [item.index for item in self.heap] @property - def _heap_id_dict(self): + def heap_id_dict(self): """ Return map of image ids to heap position currently in the heap. """ - return { - item[1]: i for i, item in enumerate(self.heap) - } # heap item=(score, img_id, img) + return {item.index: i for i, item in enumerate(self.heap)} def _select(self, classes, reflections, distances): for i, im in enumerate(self.averager.average(classes, reflections)): @@ -258,16 +311,23 @@ def _select(self, classes, reflections, distances): # Assign in global quality score array self._quality_scores[i] = quality_score + # Skip heap if single item is larger than heap limit. + # Implies `self._heap_limit_bytes = 0` disables heap. + if self._heap_item_size > self._heap_limit_bytes: + continue + # Create cachable payload item - item = (quality_score, i, im) + item = _HeapItem(quality_score, i, im) if self._heap_item_size + self._heap_size < self._heap_limit_bytes: + # There is space to push another entry onto heap heappush(self.heap, item) - else: - # We're out of space, + elif self._heap_size < self._heap_limit_bytes: + # Heap is currently out of space, # pop and throw away the worst entry # after updating (pushing to) the heap. _ = heappushpop(self.heap, item) + # else: # Now that we have computed the global quality_scores, # the selection ordering can be applied diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 8c9a7a96f7..2941f8aa31 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -154,7 +154,7 @@ def _images(self, indices): heap_inds = None if hasattr(self.class_selector, "heap"): # Then check if heap holds anything in indices - heap_inds = set(indices).intersection(self.class_selector._heap_ids) + heap_inds = set(indices).intersection(self.class_selector.heap_ids) # check for wholly cached image sets first if self._cached_im is not None: @@ -177,7 +177,7 @@ def _images(self, indices): # Recursion, heap_inds set should be empty in the recursive call. computed_imgs = self._images(list(indices_to_compute.values())) # Get the map once to avoid traversing heap in a loop - heapd = self._heap_id_dict + heapd = self.self.class_selector.heap_id_dict imgs = np.empty( (len(indices), computed_imgs.resolution, computed_imgs.resolution), @@ -191,16 +191,6 @@ def _images(self, indices): lambda k: self.class_selector.heap[heapd[k]][2], list(indices_from_heap.keys()), ) - # for (i,ind) in enumerate(indices): - # # collect the images - # if ind in heapd: - # loc = heapd[ind] - # assert self.class_selector.heap[loc][1] == ind - # _img = self.class_selector.heap[loc][2] - # else: - # _img = computed_imgs[indices_to_compute[ind]] - # # assign the image - # imgs[i] = _img return imgs From 930152f6fe0231d0eb5ca2188b4caa3475425659 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 15:47:05 -0500 Subject: [PATCH 230/424] User C3C4 for 10081 experiment. [skip ci] --- gallery/experiments/experimental_abinitio_pipeline_10081.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index 4160910af0..a743b08cd8 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -24,7 +24,7 @@ import logging -from aspire.abinitio import CLSyncVoting +from aspire.abinitio import CLSymmetryC3C4 from aspire.basis import FFBBasis3D from aspire.denoising import ClassAvgSourcev11 from aspire.noise import AnisotropicNoiseEstimator @@ -101,7 +101,7 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSyncVoting(avgs, n_theta=360) +orient_est = CLSymmetryC3C4(avgs, symmetry=f"C4", n_theta=360, max_shift=1) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations From b2b35090f6cd8099e58677c79a5d81dea1c8d6ef Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 15:48:52 -0500 Subject: [PATCH 231/424] cls classes typo --- src/aspire/classification/class_selection.py | 150 +++++++++++++------ 1 file changed, 101 insertions(+), 49 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 49e4bb8303..12823a6b32 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -336,50 +336,61 @@ def _select(self, classes, reflections, distances): return sorted_class_inds -# TODO: When a consistent measure of distance is implemented -# by preceeding components we can implement exclusion based on neighbor distances. -class ClassRepulsion: +# TODO: When a consistent measure of distance is implemented by +# preceeding components we can implement exclusion based on neighbor +# distances or other ideas (VDM?) as different ClassRepulsion like +# classes. +class GreedyClassRepulsion: """ Mixin to overload class selection based on excluding classes we've alreay seen as neighbors of another class. If the classes are well sorted (by some measure of quality), - we can assume the best representation is the first seen. - + we assume the best representation is the first seen. """ def __init__(self, *args, **kwargs): """ - Sets optional `exclude_k`. All other args and **kwagrs are passed to super(). + Sets optional `exclude_k`. All other args and **kwargs are + passed to super(). - ClassRepulsion is similar to `cryo_select_subset` from MATLAB, - but MATLAB found `exclude_k` iteratively based on a desired result set size. + GreedyClassRepulsion is similar to `cryo_select_subset` from + MATLAB, but MATLAB found `exclude_k` iteratively based on a + desired result set size. - :param exclude_k: Number of neighbors from each class to exclude. - Defaults to + :param exclude_k: Number of neighbors from each class to + exclude. Defaults to """ + # Pop of the parameter unique to GreedyClassRepulsion. self.exclude_k = kwargs.pop("exclude_k", None) + + # Instantiate an empty set to hold our excluded indices. self.excluded = set() + + # Pass everything else through to the super __init__. super().__init__(*args, **kwargs) def _select(self, classes, reflections, distances): - # Get the indices sorted by the next resolving `_select` method. + """ + Overload the ClassSelector._select + """ + # Get the indices sorted by the super's `_select` method. sorted_inds = super()._select(classes, reflections, distances) - # If exclude_k is not provided, default to exluding all neighbors. + # If exclude_k is not provided, default to exluding all + # neighbors seen. k = self.exclude_k or classes.shape[-1] results = [] for i in sorted_inds: - # Skip when this class's base image has been seen in a prior class. + # Skip when this class's base image has been seen in a + # prior class. if i in self.excluded: continue - # Get the images in this class - cls = classes[i] - - # Add images from this class to the exclusion list - self.excluded.update(cls[:k]) + # Get the images in this class `i`, and add class images + # up to `k` the exclusion list. + self.excluded.update(classes[i, :k]) results.append(i) @@ -389,7 +400,8 @@ def _check_selection(self, selection, n_img): """ Check that class `selection` is sane. - Repulsion can reduce the number of classes (dramatically). + Repulsion can reduce the number of classes (dramatically), + so we need to adjust the checking operation. :param selection: selection indices :param n_img: number of images available @@ -397,22 +409,25 @@ def _check_selection(self, selection, n_img): return super()._check_selection(selection, n_img, len_operator=le) -class ContrastWithRepulsionClassSelector(ClassRepulsion, ContrastClassSelector): +class ContrastWithRepulsionClassSelector(GreedyClassRepulsion, ContrastClassSelector): """ Selects top classes based on highest contrast with prior rejection. """ -class GlobalWithRepulsionClassSelector(ClassRepulsion, GlobalClassSelector): +class GlobalWithRepulsionClassSelector(GreedyClassRepulsion, GlobalClassSelector): """ Extends ClassSelector for methods that require - passing over all class average images and also ClassRepulsion. + passing over all class average images and also GreedyClassRepulsion. """ class ImageQualityFunction(ABC): """ A callable image quality scoring function. + + The main advantage to using this class is to gain access to a grid + caching and Image/Numpy conversion. """ _grid_cache = {} @@ -420,24 +435,23 @@ class ImageQualityFunction(ABC): @abstractmethod def _function(self, img): """ - User defined 1d radial weight function. - The function is expected to be defined for [0,sqrt(2)]. - Function range is not limited, but [0,1] is favorable. + User defined 1d radial weight function. The function is + expected to be defined on [0,sqrt(2)]. Function range is + currently not limited, but [0,1] is favorable. - Develops can use the self._grid_cache for access to - a grid_2d instance matching resolution of img. + Developers can use the self._grid_cache for access to a + grid_2d instance matching resolution of img. - :param img: 2d numpy array + :param img: 2d Numpy array :returns: Image quality score """ def __call__(self, img): """ Given an image instance or a 2D np array, - builds a grid once if needed then calls the instance's weight function. - :param img: `Image`, 2d numpy array, + :param img: `Image`, 2d Numpy array, or 3d array where slow axis is stack axis. When called with an image stack>1, an array is returned. :returns: Image quality score(s) @@ -446,31 +460,61 @@ def __call__(self, img): if isinstance(img, Image): img = img.asnumpy() + # Allow the function to be used on a single image presented as + # a 2d Numpy array, as well as a stack. if img.ndim == 2: + stack_len = 0 # singleton img = img[np.newaxis, :, :] + else: + stack_len = img.shape[0] - stack_len = img.shape[0] + # Image size. L = img.shape[-1] - # Generate grid on first call so user can expect it exists. + # Generate grid on first call as needed. self._grid_cache.setdefault(L, grid_2d(L, dtype=img.dtype)) - # Call the function over img stack + # Call the function over img stack. res = np.fromiter(map(self._function, img), dtype=img.dtype) - if stack_len == 1: + # Return singleton when given a singleton (2d array). + if stack_len == 0: res = res[0] return res class BandedSNRImageQualityFunction(ImageQualityFunction): + """ + Computes the ratio of variance of central pixels of image to + pixels in a configurable outer band. + """ + def _function(self, img, center_radius=0.5, outer_band_start=0.8, outer_band_end=1): - # Look up the precached grid. - grid = self._grid_cache[img.shape[-1]] + """Configurable scoring function. + + :param img: Input image as 2d Numpy array. + :param center_radius: Proportion of image radius defining the center. + :param outer_band_start: Proportion of image radius defining + the inner boundary of band. + :param outer_band_end: Proportion of image radius defining the + outer boundary of band. Must be larger than outer_band_start. + + :return: Ratio central variance to outer band variance. + """ + # Image Size + L = img.shape[-1] + + # Look up the grid. + grid = self._grid_cache[L] center_mask = grid["r"] < center_radius outer_mask = (grid["r"] > outer_band_start) & (grid["r"] < outer_band_end) + if not np.any(outer_mask): + # outer_mask is empty, need to correct outer_band_{start,end} + raise RuntimeError( + f"Band of ({outer_band_start}, {outer_band_end}) empty for image size {L}, adjust band boundaries." + ) return np.var(img[center_mask]) / np.var(img[outer_mask]) @@ -488,12 +532,12 @@ class BandpassImageQualityFunction(ImageQualityFunction): class WeightedImageQualityFunction(ImageQualityFunction): """ - A callable image quality scoring function using a radial grid weighted function. + Extends ImageQualityFunction with a radial grid weighted function + for use in user defined `_function` calls. """ def __init__(self): - # Each weight function will need a seperate cache, - # but the same function should be deterministic up to resolution. + # Each weight function will need a seperate cache. self._weights_cache = {} @abstractmethod @@ -502,36 +546,44 @@ def _weight_function(self, r): Take a 1d radial weight function. The function is expected to be defined [0,sqrt(2)]. Function range is not limited, but should probably be [0,1]. + + :param r: Radius (grid). + :return: 2D grid of weights """ def weights(self, L): """ - Lookup weights for a given resolution L, - computes and updates cache on first request. + Returns 2D array of weights for a given resolution L. + Computes and caches on first request. :param L: resolution pixels - :returns: 2d weight array for LxL grid. + + :return: 2d weight array for LxL grid. """ grid = self._grid_cache[L] return self._weights_cache.setdefault( L, np.frompyfunc(self.weight_function)(grid["r"]) ) - def _function(self, img): - L = img.shape[-1] - - # if we ignore the issue of dependence, - # we could also do sum(per_pixel_variance * weights) - return (img * img) * self.weights(L) - +# These classes are provided as helpers/examples. class RampImageQualityFunction(WeightedImageQualityFunction): + """ + Users can extend this class when they would like to apply a linear + ramp. + """ + def _weight_function(r): # linear ramp return np.max(1 - r, 0) class BumpImageQualityFunction(WeightedImageQualityFunction): + """ + Users can extend this class when they would like to apply a [0,1] + bump function. + """ + def _weight_function(r): # bump function (normalized to [0,1] if r >= 1: From 6e142253639cb5f5908ada9f230adc31a5759c1e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 6 Mar 2023 15:54:00 -0500 Subject: [PATCH 232/424] log filename in experiment examples --- .../experiments/experimental_abinitio_pipeline_10028.py | 6 ++++-- .../experiments/experimental_abinitio_pipeline_10081.py | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index f8878c86ad..b0db6484af 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -23,6 +23,7 @@ # the ASPIRE package that will be used throughout this experiment. import logging +from pathlib import Path import matplotlib.pyplot as plt import numpy as np @@ -50,7 +51,7 @@ n_nbor = 100 # How many neighbors to stack starfile_in = "10028/data/shiny_2sets_fixed9.star" data_folder = "." # This depends on the specific starfile entries. -volume_filename_prefix_out = f"10028_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +volume_output_filename = f"10028_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" pixel_size = 1.34 @@ -180,7 +181,8 @@ # Perform the estimation and save the volume. estimated_volume = estimator.estimate() -estimated_volume.save(volume_filename_prefix_out, overwrite=True) +estimated_volume.save(volume_output_filename, overwrite=True) +logger.info(f"Saved Volume to {str(Path(volume_output_filename).resolve())}") # Peek at result if interactive: diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index a743b08cd8..6545b2a79b 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -23,6 +23,7 @@ # the ASPIRE package that will be used throughout this experiment. import logging +from pathlib import Path from aspire.abinitio import CLSymmetryC3C4 from aspire.basis import FFBBasis3D @@ -47,7 +48,7 @@ "/scratch/ExperimentalData/staging/10081/data/Particles/micrographs/data.star" ) data_folder = "../.." # This depends on the specific starfile entries. -volume_filename_prefix_out = f"10081_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +volume_output_filename = f"10081_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" pixel_size = 1.3 @@ -101,7 +102,7 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSymmetryC3C4(avgs, symmetry=f"C4", n_theta=360, max_shift=1) +orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=360, max_shift=1) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations @@ -125,4 +126,5 @@ # Perform the estimation and save the volume. estimated_volume = estimator.estimate() -estimated_volume.save(volume_filename_prefix_out, overwrite=True) +estimated_volume.save(volume_output_filename, overwrite=True) +logger.info(f"Saved Volume to {str(Path(volume_output_filename).resolve())}") From 155eadd8fd4dce771333ffcc9f9fb0f782bc0c5d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 06:31:05 -0500 Subject: [PATCH 233/424] rename classification_src to src --- .../experimental_abinitio_pipeline_10028.py | 8 ++--- .../simulated_abinitio_pipeline.py | 8 ++--- gallery/tutorials/class_averaging.py | 4 +-- gallery/tutorials/pipeline_demo.py | 2 +- src/aspire/denoising/class_avg.py | 32 +++++++++---------- tests/test_class_src.py | 2 +- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index b0db6484af..5c0570673b 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -112,16 +112,16 @@ # If you will not be using cov2d, # you may remove this code block and associated variables. -classification_src = src +src = src custom_averager = None if do_cov2d: # Use CWF denoising cwf_denoiser = DenoiserCov2D(src) # Use denoised src for classification - classification_src = cwf_denoiser.denoise() + src = cwf_denoiser.denoise() # Peek, what do the denoised images look like... if interactive: - classification_src.images[:10].show() + src.images[:10].show() # %% # Class Averaging @@ -135,7 +135,7 @@ # This also demonstrates the potential to use a different source for classification and averaging. avgs_src = ClassAvgSourcev11( - classification_src, + src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automaticaly configure parallel processing diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 946efc08de..edf1d08992 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -145,15 +145,15 @@ def noise_function(x, y): # If you will not be using cov2d, # you may remove this code block and associated variables. -classification_src = src +src = src if do_cov2d: # Use CWF denoising cwf_denoiser = DenoiserCov2D(src) # Use denoised src for classification - classification_src = cwf_denoiser.denoise() + src = cwf_denoiser.denoise() # Peek, what do the denoised images look like... if interactive: - classification_src.images[:10].show() + src.images[:10].show() # %% # Class Averaging @@ -163,7 +163,7 @@ def noise_function(x, y): # This also demonstrates the potential to use a different source for classification and averaging. avgs_src = ClassAvgSourcev11( - classification_src, + src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automaticaly configure parallel processing diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 1679f52458..c98a65235b 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -118,7 +118,7 @@ n_classes = 10 avgs = DebugClassAvgSource( - classification_src=src, + src=src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors ) @@ -167,7 +167,7 @@ # Here we will use the noise_src. avgs = DebugClassAvgSource( - classification_src=noisy_src, + src=noisy_src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors ) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index b7a9783c3a..630c10d824 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -179,7 +179,7 @@ def download(url, save_path, chunk_size=1024 * 1024): from aspire.denoising import DebugClassAvgSource avgs = DebugClassAvgSource( - classification_src=src, + src=src, classifier=rir, ) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 2941f8aa31..3b3effeee3 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -26,7 +26,7 @@ class ClassAvgSource(ImageSource): def __init__( self, - classification_src, + src, classifier, class_selector, averager, @@ -34,16 +34,16 @@ def __init__( """ Constructor of an object for denoising 2D images using class averaging methods. - :param classification_src: Source used for image classification. + :param src: Source used for image classification. :param classifier: Class2D subclass used for image classification. Example, RIRClass2D. :param class_selector: A ClassSelector subclass. :param averager: An Averager2D subclass. """ - self.classification_src = classification_src - if not isinstance(self.classification_src, ImageSource): + self.src = src + if not isinstance(self.src, ImageSource): raise ValueError( - f"`classification_src` should be subclass of `ImageSource`, found {self.classification_src}." + f"`src` should be subclass of `ImageSource`, found {self.src}." ) self.classifier = classifier @@ -235,18 +235,18 @@ class DebugClassAvgSource(ClassAvgSource): def __init__( self, - classification_src, + src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors classifier=None, class_selector=None, averager=None, ): - dtype = classification_src.dtype + dtype = src.dtype if classifier is None: classifier = RIRClass2D( - classification_src, + src, fspca_components=400, bispectrum_components=300, # Compressed Features after last PCA stage. n_nbor=n_nbor, @@ -257,8 +257,8 @@ def __init__( if averager is None: averager = BFRAverager2D( - FFBBasis2D(classification_src.L, dtype=classification_src.dtype), - classification_src, + FFBBasis2D(src.L, dtype=src.dtype), + src, num_procs=num_procs, dtype=dtype, ) @@ -267,7 +267,7 @@ def __init__( class_selector = TopClassSelector() super().__init__( - classification_src=classification_src, + src=src, classifier=classifier, class_selector=class_selector, averager=averager, @@ -287,7 +287,7 @@ class ClassAvgSourcev11(ClassAvgSource): def __init__( self, - classification_src, + src, n_nbor=50, num_procs=None, classifier=None, @@ -317,11 +317,11 @@ def __init__( :return: ClassAvgSource instance. """ - dtype = classification_src.dtype + dtype = src.dtype if classifier is None: classifier = RIRClass2D( - classification_src, + src, fspca_components=400, bispectrum_components=300, # Compressed Features after last PCA stage. n_nbor=n_nbor, @@ -332,7 +332,7 @@ def __init__( if averager is None: if averager_src is None: - averager_src = classification_src + averager_src = src basis_2d = FFBBasis2D(averager_src.L, dtype=dtype) @@ -351,7 +351,7 @@ def __init__( class_selector = ContrastWithRepulsionClassSelector() super().__init__( - classification_src=classification_src, + src=src, classifier=classifier, class_selector=class_selector, averager=averager, diff --git a/tests/test_class_src.py b/tests/test_class_src.py index fe7454abea..0f7b174c29 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -99,7 +99,7 @@ def test_run(class_sim_fixture, test_src_cls): orig_imgs = class_sim_fixture.images[:5] # Classify, Select, and average images using test_src_cls - test_src = test_src_cls(classification_src=class_sim_fixture) + test_src = test_src_cls(src=class_sim_fixture) test_imgs = test_src.images[:5] # Sanity check From 5d60a639f6e3742e57511d123b9e3517f3bab2ec Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 07:15:02 -0500 Subject: [PATCH 234/424] update class_avg doc strings and heap code --- src/aspire/denoising/class_avg.py | 87 +++++++++++++++++-------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 3b3effeee3..a2904217ec 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -149,24 +149,26 @@ def _images(self, indices): # Remap to the selected ordering indices = self.selection_indices[indices] - # Check is there is a cache available from class selection component - # Note, we can use := for this in the branch directly, but requires Python>=3.8 + # Check if there is a cache available from class selection component. + # Note, we can use := for this in the branch directly, when Python>=3.8 heap_inds = None if hasattr(self.class_selector, "heap"): - # Then check if heap holds anything in indices + # Then check if request matches anything in the heap. heap_inds = set(indices).intersection(self.class_selector.heap_ids) - # check for wholly cached image sets first + # Check if this src cached images. if self._cached_im is not None: - logger.debug(f"Loading {len(indices)} images from cache") + logger.debug(f"Loading {len(indices)} images from image cache") im = Image(self._cached_im[indices, :, :]) - # check for cached image sets from class_selector + # Check for heap cached image sets from class_selector. elif heap_inds: logger.debug(f"Mapping {len(heap_inds)} images from heap cache.") # Images in heap_inds can be fetched from class_selector. - # For others, create an indexing map that preserves original order. + # For others, create an indexing map that preserves + # original order. Both of these dicts map requested image + # id to location in the return array `im`. indices_to_compute = { ind: i for i, ind in enumerate(indices) if i not in heap_inds } @@ -174,34 +176,38 @@ def _images(self, indices): ind: i for i, ind in enumerate(indices) if i in heap_inds } - # Recursion, heap_inds set should be empty in the recursive call. - computed_imgs = self._images(list(indices_to_compute.values())) - # Get the map once to avoid traversing heap in a loop + # Get heap dict once to avoid traversing heap in a loop. heapd = self.self.class_selector.heap_id_dict - imgs = np.empty( - (len(indices), computed_imgs.resolution, computed_imgs.resolution), + # Create an empty array to pack results. + im = np.empty( + (len(indices), _imgs.resolution, _imgs.resolution), dtype=computed_imgs.dtype, ) + # Recursively call `_images`. + # `heap_inds` set should be empty in the recursive call, + # and compute only remaining images (those not in heap). + _imgs = self._images(list(indices_to_compute.keys())) + + # Pack images computed from `_images` recursive call. _inds = list(indices_to_compute.values()) - imgs[_inds] = computed_imgs - _inds = list(indices_from_heap.values()) - imgs[_inds] = map( - lambda k: self.class_selector.heap[heapd[k]][2], - list(indices_from_heap.keys()), - ) + im[_inds] = _imgs - return imgs + # Pack images from heap. + for k, i in indices_from_heap.items(): + # map the image index to heap item location + heap_loc = heapd[k] + im[i] = self.class_selector.heap[heap_loc].image else: - # Perform image averaging for the requested classes + # Perform image averaging for the requested images (classes) logger.debug(f"Averaging {len(indices)} images from source") im = self.averager.average( self._nn_classes[indices], self._nn_reflections[indices] ) - # Finally, apply transforms to resulting Image + # Finally, apply transforms to resulting Images return self.generation_pipeline.forward(im, indices) @@ -214,23 +220,8 @@ class DebugClassAvgSource(ClassAvgSource): """ Source for denoised 2D images using class average methods. - Packs base with common debug defaults. - - :param n_nbor: Number of nearest neighbors. Default 10. - :param num_procs: Number of processors. Default of 1 runs serially. - `None` attempts to compute a reasonable value - based on available cores and memory. - :param classifier: `Class2D` classifier instance. - Default `None` creates `RIRClass2D`. - See code for parameter details. - :param class_selector: `ClassSelector` instance. - Default `None` creates `TopClassSelector`. - :param averager: `Averager2D` instance. - Default `None` ceates `BFRAverager2D` instance. - See code for parameter details. - - :return: ClassAvgSource instance. - + Defaults to `RIRClass2D`, `TopClassSelector`, `BFRAverager2D` + using a single processor. """ def __init__( @@ -242,6 +233,25 @@ def __init__( class_selector=None, averager=None, ): + """ + Instantiates with default debug paramaters. + + :param src: Source used for image classification. + :param n_nbor: Number of nearest neighbors. Default 10. + :param num_procs: Number of processors. Default of 1 runs serially. + `None` attempts to compute a reasonable value + based on available cores and memory. + :param classifier: `Class2D` classifier instance. + Default `None` creates `RIRClass2D`. + See code for parameter details. + :param class_selector: `ClassSelector` instance. + Default `None` creates `TopClassSelector`. + :param averager: `Averager2D` instance. + Default `None` ceates `BFRAverager2D` instance. + See code for parameter details. + + :return: ClassAvgSource instance. + """ dtype = src.dtype if classifier is None: @@ -298,6 +308,7 @@ def __init__( """ Instantiates ClassAvgSourcev11 with the following parameters. + :param src: Source used for image classification. :param n_nbor: Number of nearest neighbors. Default 50. :param num_procs: Number of processors. Use 1 to run serially. Default `None` attempts to compute a reasonable value From 227d986681dd7f761d915ac0f87bad7f5fe816a7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 07:45:48 -0500 Subject: [PATCH 235/424] tox --- src/aspire/denoising/class_avg.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index a2904217ec..5c21f70183 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -179,17 +179,17 @@ def _images(self, indices): # Get heap dict once to avoid traversing heap in a loop. heapd = self.self.class_selector.heap_id_dict - # Create an empty array to pack results. - im = np.empty( - (len(indices), _imgs.resolution, _imgs.resolution), - dtype=computed_imgs.dtype, - ) - # Recursively call `_images`. # `heap_inds` set should be empty in the recursive call, # and compute only remaining images (those not in heap). _imgs = self._images(list(indices_to_compute.keys())) + # Create an empty array to pack results. + im = np.empty( + (len(indices), _imgs.resolution, _imgs.resolution), + dtype=_imgs.dtype, + ) + # Pack images computed from `_images` recursive call. _inds = list(indices_to_compute.values()) im[_inds] = _imgs From 6714a3bc6dd39aaa4e0d1750947a51019936472e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 07:42:33 -0500 Subject: [PATCH 236/424] cleanup image_stacker some more --- src/aspire/image/image_stacker.py | 106 ++++++++++++++++++------------ 1 file changed, 64 insertions(+), 42 deletions(-) diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index 41e3449346..dd010dd3e7 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -26,7 +26,7 @@ def __init__(self): Initialize ImageStacker instance. """ - # When provided an image, keep track of shape. + # This variable will be used to keep track of shape. self._return_image_size = None @abc.abstractmethod @@ -40,12 +40,18 @@ def __call__(self, stack): where the first (slow) dimension is stack axis, and signal data is flattened to the last (fastest) axis. In this case an Numpy array is returned. + This allows performing stacking arithmetic in an arbitrary + basis. When passing `Image`, the stack_shape must be 1D. In this case an `Image` is returned. Users with multidimensional `Image` data may use `Image.stack_reshape` or stack slicing - before/after stacking. + before/after stacking to ensure the correct + aggregation is occurring. + + If multidimensional stacking would be useful, + submit a feature request. :param stack: Image instance (or Numpy array). @@ -61,15 +67,16 @@ def _check_and_convert(self, stack): :return: 2D Numpy array. """ - # Careful, we need to reset this in case this stack is different from last. + # Careful, we need to reset this in case this stack is + # different from last call. self._return_image_size = None # Flatten image data. if isinstance(stack, Image): - # Store the image size for returning later + # Store the image size for returning later. self._return_image_size = (1,) + stack.shape[1:] - # Sanity check dimensions + # Sanity check dimensions. if stack.stack_ndim != 1: raise ValueError( f"`stack` shape of Image should be 1D for ImageStacking not {stack.stack_shape}." @@ -77,7 +84,8 @@ def _check_and_convert(self, stack): ) stack = stack.asnumpy().reshape(stack.n_images, -1) - # By this point we should alway be an array with signal data in the last axis. + # By this point we should always be an array with signal data + # in the last axis. if not isinstance(stack, np.ndarray): raise ValueError("`stack` should be `Image` instance or Numpy array.") elif stack.ndim != 2: @@ -93,7 +101,8 @@ def _return(self, result): When ImageStacker has been passed an `Image`, this method creates an `Image` instance to be returned. - Stacking Numpy array will pass through. + In the case we started with a Numpy array this should pass + through. :param result: Result data as Numpy Array. @@ -127,28 +136,33 @@ def __call__(self, stack): class SigmaRejectionImageStacker(ImageStacker): - """Stack using Sigma Rejection. - - When no outliers exist, sigma rejection should return equivalent - of `mean`. In the presence of outliers, pixels outside of - `rejection_sigma` from the per-pixel-mean are discarded. - - For potentially less Gaussian distributions, 'FWHM' and 'FWTM' - methods are provided. These will take the mean of values lieing - in the FWHM and FWTM. Essentially this is the same wing clipping - procedure. - - Note, in both cases, user's are responsible for ensuring metohds - are called on reasonable data (in FW* cases we should be talking - intesnities). No corrections or pedestals are incorporated at this - time.""" + """ + Stack using Sigma Rejection. + + When no outliers exist, sigma rejection is equivalent to `mean`. + In the presence of outliers, pixels outside of `rejection_sigma` + from the per-pixel-mean are discarded before the mean is + performed. + + For potentially less Gaussian distributions, 'FWHM' and 'FWTM' + methods are also provided. These will take the mean of all values + laying above half the maximum and tenth maximum + respectively. Essentially this is the same wing clipping + procedure, but the location of the clip is determined by the peak + intensity instead of standard deviation. + + Note, in both cases, user's are responsible for ensuring methods + are called on reasonable data (in FW* cases we should be probably + be using intensities). No corrections or pedestals are + incorporated at this time, but could easily be added in the + future. + """ _width_methods = {"FWHM": 0.5, "FWTM": 0.1} def __init__(self, rejection_sigma=3): """ - Instantiates SigmaRejectionImageStacker instance with - presribed `rejection_sigma`. + Instantiates with presribed `rejection_sigma`. :param rejection_sigma: Values falling outside `rejection_sigma` standard deviations are @@ -158,10 +172,10 @@ def __init__(self, rejection_sigma=3): """ # Handle string `rejection_sigma` - self._method = self._gaussian + self._method = self._gaussian_method if isinstance(rejection_sigma, str): self.rejection_sigma = rejection_sigma.upper() - self._method = self._width + self._method = self._width_method if self.rejection_sigma not in self._width_methods: raise ValueError( f"`rejection_sigma` must be numeric or {self._width_methods.keys()}." @@ -176,38 +190,39 @@ def __call__(self, stack): stack = self._check_and_convert(stack) return self._return(self._method(stack)) - def _gaussian(self, stack): + def _gaussian_method(self, stack): """ Gaussian rejection. """ - # Compute the mean and standard deviations, pixelwise + # Compute the mean and standard deviations, pixel-wise. means = stack.mean(axis=0) std_devs = stack.std(axis=0) - # Compute values that lie outside sigma deviations + # Find values that lie outside per-pixel sigma deviations. outliers = np.abs(stack - means) > self.sigma * std_devs - # Mask off the outliers + # Mask off the outliers. masked_stack = ma.masked_array(stack, mask=outliers) # Return mean without the outliers return masked_stack.mean(axis=0) - def _width(self, stack): + def _width_method(self, stack): """ Width rejection. """ - # Compute per-pixel max + + # Compute per-pixel max. maxima = stack.max(axis=0) # Find the per-pixel value for clipping y = maxima * self._width_methods[self.rejection_sigma] - # Compute outliers, values below clipping value. + # Compute outliers; values below clipping value. outliers = stack < y - # Mask off the outliers + # Mask off the outliers. masked_stack = ma.masked_array(stack, mask=outliers) # Return mean withou the outliers @@ -218,14 +233,15 @@ class WinsorizedImageStacker(ImageStacker): """ Stack using Winsorization. - Winsorizing is similar to SigmaRejectionImageStacker, - excect it admits a `percentile` and replaces rejected - values with values at +/- `percentile`. + Winsorizing is similar to SigmaRejectionImageStacker, except it + admits a `percentile` and replaces rejected values with values at + +/- `percentile`. """ def __init__(self, percentile=0.1): - """Instantiates WinsorizedImageStacker instance with - presribed `percentile`. + """ + Instantiates WinsorizedImageStacker instance with prescribed + `percentile`. See scipy docs for more details: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mstats.winsorize.html @@ -235,8 +251,8 @@ def __init__(self, percentile=0.1): tuple allows different lower and upper percentiles respectively. Example (0.1, 0.2) would Winsorize the lower 10% and upper 20% of values. In the case of a single - value, that value will be used for both upper and lower - percentiles. + value, the same value will be used for both upper and + lower percentiles. """ # Convert scalars to tuples. if not isinstance(percentile, tuple): @@ -247,16 +263,22 @@ def __init__(self, percentile=0.1): def __call__(self, stack): """ - Winsorizing + Apply Winsorizing process the return the stack mean. """ + stack = self._check_and_convert(stack) + # Note, we intentionally disable `inplace` to avoid mutating + # stack, just-in-case. stack = winsorize(stack, limits=self.percentile, inplace=False) # Return mean of Winsorized data. return self._return(stack.mean(axis=0)) +# The following will be blocked by ABC because they are not +# implemented yet, and are open to discussion/removal/implementation +# pending time. class PoissonRejectionImageStacker(ImageStacker): """ Stack using Poisson Rejection. From d652ba6070be759b34f74fca2e373cea07b17161 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 07:59:00 -0500 Subject: [PATCH 237/424] replace _set_n hack with proper property and setter --- src/aspire/denoising/class_avg.py | 3 ++- src/aspire/source/image.py | 25 +++++++++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 5c21f70183..5769c282cb 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -133,7 +133,8 @@ def _class_select(self): logger.info( f"After selection process, updating maximum {len(self._selection_indices)} classes from {self.n}." ) - self._set_n(len(self._selection_indices)) + # Note, setter should be inherited from base ImageSource. + self.n = len(self._selection_indices) self._selected = True diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index ae4d7ed186..0e88c84fa2 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -127,6 +127,10 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): :param memory: str or None The path of the base directory to use as a data store or None. If None is given, no caching is performed. """ + + # Instantiate the accessor for the `images` property + self._img_accessor = _ImageAccessor(self._images, n) + self.L = L self.n = n self.dtype = np.dtype(dtype) @@ -157,21 +161,30 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): self.generation_pipeline = Pipeline(xforms=None, memory=memory) self._metadata_out = None - # Instantiate the accessor for the `images` property - self._img_accessor = _ImageAccessor(self._images, self.n) - logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") - def _set_n(self, n): + @property + def n(self): + return self._n + + @n.setter + def n(self, n): """ - Sets max image index `n`. + Sets max image index `n` in `src` and associated + `ImageAccessor`. Used in sources that potentially reduce the set of images. :param n: Number of images. """ + + # Enforce type, just-in-case. + if n != int(n): + raise TypeError("`n` must be an integer") + n = int(n) + self._img_accessor.num_imgs = n - self.n = n + self._n = n @property def n_ctf_filters(self): From 4b4f6d27210f8e250339cae53bbc0f014da61353 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 09:45:34 -0500 Subject: [PATCH 238/424] Clean up test_class_src --- src/aspire/classification/class_selection.py | 8 ++ tests/test_class_src.py | 80 ++++++++++++++++---- tests/test_image_stacker.py | 2 +- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 12823a6b32..508a1070d7 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -213,6 +213,12 @@ def __init__(self, value, index, image): self.index = int(index) self.image = image + def __lt__(self, other): + """ + This comparator is used by `heapq` to order and maintain the heap. + """ + return self.value < other.value + @staticmethod def nbytes(img_size, dtype): """ @@ -224,6 +230,8 @@ def nbytes(img_size, dtype): :return: Sum of attribute sizes. """ + # Allow passing dtype directly, or instance of dtype. + dtype = np.dtype(dtype) return img_size**2 * dtype.itemsize + 16 diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 0f7b174c29..c08c835dc7 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -1,5 +1,6 @@ import logging import os +from heapq import heappush, heappushpop from itertools import product, repeat import numpy as np @@ -18,7 +19,9 @@ RIRClass2D, TopClassSelector, ) +from aspire.classification.class_selection import _HeapItem from aspire.denoising import ClassAvgSourcev11, DebugClassAvgSource +from aspire.image import Image from aspire.source import Simulation from aspire.utils import Rotation from aspire.volume import Volume @@ -32,6 +35,8 @@ RESOLUTIONS = [32] DTYPES = [np.float64] CLS_SRCS = [DebugClassAvgSource, ClassAvgSourcev11] +# For very small problems, it usually isn't worth running in parallel. +NUM_PROCS = 1 def sim_fixture_id(params): @@ -59,29 +64,31 @@ def class_sim_fixture(request): # We want the first rotation to have angle 2pi instead of 0, # so the norm isn't degenerate (0) later. inplane_rots[0] = 2 * np.pi - logger.info(f"inplane_rots: {inplane_rots}") + logger.debug(f"inplane_rots: {inplane_rots}") # Total rotations will be number of axis * number of angles # ie. vertices * n_inplane_rots n = len(cube_vertices) * n_inplane_rots - logger.info(f"Constructing {n} rotations.") + logger.debug(f"Constructing {n} rotations.") # Generate Rotations # Normalize the rotation axes to 1 rotvecs = cube_vertices / np.linalg.norm(cube_vertices, axis=0) - logger.info(f"rotvecs: {rotvecs}") + logger.debug(f"rotvecs: {rotvecs}") # renormalize by broadcasting with angle amounts in inplane_rots rotvecs = (rotvecs[np.newaxis].T * inplane_rots).T.reshape(n, 3) # Construct rotation object true_rots = Rotation.from_rotvec(rotvecs, dtype=dtype) # Load sample molecule volume - # TODO, probably our default volume should work for this stuff... tighter var? + # TODO, probably our default volume should "just work" for this stuff... tighter var? v = Volume( np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")), dtype=dtype ).downsample(res) - # Contruct the Simulation source + # Contruct the Simulation source. + # Note using a single volume via C=1 is critical to matching + # alignment without the complexity of remapping via states etc. src = Simulation( L=res, n=n, vols=v, offsets=0, amplitudes=1, C=1, angles=true_rots.angles ) @@ -94,13 +101,23 @@ def class_sim_fixture(request): @pytest.mark.parametrize( "test_src_cls", CLS_SRCS, ids=lambda param: f"ClassSource={param}" ) -def test_run(class_sim_fixture, test_src_cls): - # Images from the original Source - orig_imgs = class_sim_fixture.images[:5] +def test_basic_averaging(class_sim_fixture, test_src_cls): + """ + Test that the default `ClassAvgSource` implementations return + class averages. + """ + + cmp_n = 5 - # Classify, Select, and average images using test_src_cls - test_src = test_src_cls(src=class_sim_fixture) - test_imgs = test_src.images[:5] + # Classify, Select, and compute averaged images. + test_src = test_src_cls(src=class_sim_fixture, num_procs=NUM_PROCS) + test_imgs = test_src.images[:cmp_n] + + # Fetch reference images from the original source. + # We need remap the indices back to the original ids because + # selectors will potentially reorder the classes. + remapped_indices = test_src.selection_indices[list(range(cmp_n))] + orig_imgs = class_sim_fixture.images[remapped_indices] # Sanity check assert np.allclose( @@ -108,6 +125,29 @@ def test_run(class_sim_fixture, test_src_cls): ) +# Test the _HeapItem helper class +def test_heap_helper(): + dtype = np.dtype(np.float64) + + # Test the static method + assert _HeapItem.nbytes(img_size=2, dtype=dtype) == 4 * dtype.itemsize + 16 + + # Create an empty heap + test_heap = [] + + _img = Image(np.empty((2, 2), dtype=dtype)) + + # Push item onto the heap + a = _HeapItem(123, 0, _img) + heappush(test_heap, a) + + # Push a better item onto heap, pop off the worst item. + b = _HeapItem(456, 1, _img) + popped = heappushpop(test_heap, b) + + assert popped == a, "Failed to pop min item" + + @pytest.fixture() def cls_fixture(class_sim_fixture): """ @@ -120,7 +160,9 @@ def cls_fixture(class_sim_fixture): return c2d.classify() -LOCAL_SELECTORS = [ +# These are selectors that do not need to pass over all the global set +# of aligned and stacked class averages. +ONLINE_SELECTORS = [ ContrastClassSelector, ContrastWithRepulsionClassSelector, DistanceClassSelector, @@ -130,14 +172,19 @@ def cls_fixture(class_sim_fixture): @pytest.mark.parametrize( - "selector", LOCAL_SELECTORS, ids=lambda param: f"Selector={param}" + "selector", ONLINE_SELECTORS, ids=lambda param: f"Selector={param}" ) -def test_custom_local_selector(cls_fixture, selector): +def test_online_selector(cls_fixture, selector): # classes, reflections, distances = cls_fixture selection = selector().select(*cls_fixture) + # Smoke test. logger.info(f"{selector}: {selection}") +# These are selectors which compute the entire global set of class +# averages before applying some criterion to select the "best" +# classes. These are closer to the methods used historically in +# MATLAB experiments, sometimes called "out-of-core" in legacy code. GLOBAL_SELECTORS = [ GlobalClassSelector, GlobalWithRepulsionClassSelector, @@ -148,13 +195,14 @@ def test_custom_local_selector(cls_fixture, selector): "selector", GLOBAL_SELECTORS, ids=lambda param: f"Selector={param}" ) @pytest.mark.expensive -def test_custom_global_selector(class_sim_fixture, cls_fixture, selector): +def test_global_selector(class_sim_fixture, cls_fixture, selector): basis = FFBBasis2D(class_sim_fixture.L, dtype=class_sim_fixture.dtype) - averager = BFRAverager2D(basis, class_sim_fixture, num_procs=1) + averager = BFRAverager2D(basis, class_sim_fixture, num_procs=NUM_PROCS) fun = BandedSNRImageQualityFunction() # classes, reflections, distances = cls_fixture selection = selector(averager, fun).select(*cls_fixture) + # smoke test logger.info(f"{selector}: {selection}") diff --git a/tests/test_image_stacker.py b/tests/test_image_stacker.py index 16cf244c3a..05ac3ee9bf 100644 --- a/tests/test_image_stacker.py +++ b/tests/test_image_stacker.py @@ -35,7 +35,7 @@ def simple_data_fixture(request): # Generate test data. A = np.ones((SAMPLE_N, img_size, img_size), dtype=dtype) - # Force outliers + # Force outliers, single outlier per pixel across stack. for i in range(2, 7): A[0, i - 1] = i From 3afc8941007b30c10a906fb020cb14e3fd637feb Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 12:42:27 -0500 Subject: [PATCH 239/424] Remove deprecated tests --- tests/test_class2D.py | 72 ------------------------------------------- 1 file changed, 72 deletions(-) diff --git a/tests/test_class2D.py b/tests/test_class2D.py index 6fad2e0a3f..7fc9306dbe 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -342,78 +342,6 @@ def testImplementations(self): ): _ = RIRClass2D(self.clean_src, self.basis) - # def testSelectionImplementations(self): - # """ - # Test optional implementations handle bad inputs with a descriptive error. - # """ - - # class CustomClassSelector(ClassSelector): - # def __init__(self, x): - # self.x = x - - # def _select(self, n, classes, reflections, distances): - # return self.x - - # # lower bound - # with pytest.raises(ValueError, match=r".*out of bounds.*"): - # rir = RIRClass2D( - # self.clean_src, - # self.clean_fspca_basis, - # n_classes=self.n_classes, - # bispectrum_components=self.clean_fspca_basis.components - 1, - # selector=CustomClassSelector(np.arange(self.n_classes) - 1), - # ) - # _ = rir.averages(*rir.classify()) - - # # upper bound - # with pytest.raises(ValueError, match=r".*out of bounds.*"): - # rir = RIRClass2D( - # self.clean_src, - # self.clean_fspca_basis, - # n_classes=self.n_classes, - # bispectrum_components=self.clean_fspca_basis.components - 1, - # selector=CustomClassSelector( - # np.arange(self.n_classes) + self.clean_src.n - # ), - # ) - # _ = rir.averages(*rir.classify()) - - # # too short - # with pytest.raises(ValueError, match=r".*must be len.*"): - # rir = RIRClass2D( - # self.clean_src, - # self.clean_fspca_basis, - # n_classes=self.n_classes, - # bispectrum_components=self.clean_fspca_basis.components - 1, - # selector=CustomClassSelector(np.arange(self.n_classes - 1)), - # ) - # _ = rir.averages(*rir.classify()) - - # # too long - # with pytest.raises(ValueError, match=r".*must be len.*"): - # rir = RIRClass2D( - # self.clean_src, - # self.clean_fspca_basis, - # n_classes=self.n_classes, - # bispectrum_components=self.clean_fspca_basis.components - 1, - # selector=CustomClassSelector(np.arange(self.n_classes + 1)), - # ) - # _ = rir.averages(*rir.classify()) - - # def testIncorrectSelectorClass(self): - # """ - # Test passing incorect ClassSelector raises with a descriptive error. - # """ - - # with pytest.raises(RuntimeError, match=r".*must be subclass of.*"): - # rir = RIRClass2D( - # self.clean_src, - # self.clean_fspca_basis, - # n_classes=self.n_classes, - # selector=range(self.n_classes), - # ) - # _ = rir.averages(*rir.classify()) - class LegacyImplementationTestCase(TestCase): """ From 04ab512008de802cd04cfa1a36ef5a0472497bf0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 15:14:00 -0500 Subject: [PATCH 240/424] Revert over aggresive sed change --- gallery/experiments/experimental_abinitio_pipeline_10028.py | 6 +++--- gallery/experiments/simulated_abinitio_pipeline.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index 5c0570673b..13ab2db424 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -112,13 +112,13 @@ # If you will not be using cov2d, # you may remove this code block and associated variables. -src = src +classification_src = src custom_averager = None if do_cov2d: # Use CWF denoising cwf_denoiser = DenoiserCov2D(src) # Use denoised src for classification - src = cwf_denoiser.denoise() + classification_src = cwf_denoiser.denoise() # Peek, what do the denoised images look like... if interactive: src.images[:10].show() @@ -135,7 +135,7 @@ # This also demonstrates the potential to use a different source for classification and averaging. avgs_src = ClassAvgSourcev11( - src, + classification_src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automaticaly configure parallel processing diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index edf1d08992..8aec594040 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -145,12 +145,12 @@ def noise_function(x, y): # If you will not be using cov2d, # you may remove this code block and associated variables. -src = src +classification_src = src if do_cov2d: # Use CWF denoising cwf_denoiser = DenoiserCov2D(src) # Use denoised src for classification - src = cwf_denoiser.denoise() + classificiation_src = cwf_denoiser.denoise() # Peek, what do the denoised images look like... if interactive: src.images[:10].show() @@ -163,7 +163,7 @@ def noise_function(x, y): # This also demonstrates the potential to use a different source for classification and averaging. avgs_src = ClassAvgSourcev11( - src, + classificiation_src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automaticaly configure parallel processing From 23b663fe6766bf7db47b103ba7c6503d9b05087d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 7 Mar 2023 16:43:49 -0500 Subject: [PATCH 241/424] gallery changes from our demo dry run --- gallery/tutorials/lecture_feature_demo.py | 148 ++++------------------ gallery/tutorials/pipeline_demo.py | 3 +- 2 files changed, 24 insertions(+), 127 deletions(-) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 5206347fc9..80f6fa94f2 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -38,25 +38,17 @@ import matplotlib.pyplot as plt import numpy as np -from aspire.abinitio import CLSyncVoting from aspire.noise import ( AnisotropicNoiseEstimator, CustomNoiseAdder, WhiteNoiseAdder, WhiteNoiseEstimator, ) -from aspire.operators import FunctionFilter, RadialCTFFilter +from aspire.operators import FunctionFilter from aspire.source import RelionSource, Simulation -from aspire.utils import ( - Rotation, - get_aligned_rotations, - get_rots_mse, - register_rotations, -) +from aspire.utils import Rotation from aspire.volume import Volume -logger = logging.getLogger(__name__) - # %% # ``Image`` Class # --------------- @@ -130,15 +122,18 @@ num_rotations = 2 rots = Rotation.generate_random_rotations(n=num_rotations, seed=12345) +# %% # We can access the numpy array holding the actual stack of 3x3 matrices: -logger.info(rots) -logger.info(rots.matrices) +logging.info(rots) +logging.info(rots.matrices) # Using the first (and in this case, only) volume, compute projections using the stack of rotations: projections = v.project(0, rots) +logging.info(projections) + +# %% +# ``project()`` returns an Image instance, so we can call ``show``. -# project() returns an Image instance. -logger.info(projections) projections.show() # Neat, we've generated random projections of some real data. @@ -185,11 +180,11 @@ sim_seed.images[:10].show() # We can also view the rotations used to create these projections -# logger.info(sim2.rotations) # Commented due to long output +# logging.info(sim2.rotations) # Commented due to long output # %% -# Simulation with Noise - Filters -# ------------------------------- +# Simulation with Filters and Noise +# --------------------------------- # # Filters # ^^^^^^^ @@ -197,6 +192,7 @@ # `Filters `_ # are a collection of classes which once configured can be applied to ``Source`` pipelines. # Common filters we might use are ``ScalarFilter``, ``PowerFilter``, ``FunctionFilter``, and ``CTFFilter``. +# ``CTFFilter`` is detailed in the ``ctf.py`` demo. # # Adding to Simulation # ^^^^^^^^^^^^^^^^^^^^ @@ -208,68 +204,19 @@ # Then when used in the ``noise_filter``, this scalar will be multiplied by a random sample. # Similar to before, if you require a different sample, this would be controlled via a ``seed``. -# Using the sample variance, we'll compute a target noise variance +# Get the sample variance var = np.var(sim2.images[:].asnumpy()) -logger.info(f"sim2 clean sample var {var}") -noise_variance = 100.0 * var -logger.info(f"noise var {noise_variance}") +logging.info(f"Sample Variance: {var}") +target_noise_variance = 100.0 * var +logging.info(f"Target Noise Variance: {target_noise_variance}") +# Then create a NoiseAdder based on that variance. +white_noise_adder = WhiteNoiseAdder(target_noise_variance) -# Then create a CustomNoiseAdder based on that variance. -white_noise_adder = WhiteNoiseAdder(var=noise_variance) # We can create a similar simulation with this additional noise_filter argument: sim3 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder) sim3.images[:10].show() # These should be rather noisy now ... -# %% -# Common Line Estimation -# ---------------------- -# -# Now we can create a CL instance for estimating orientation of projections using the Common Line with synchronization method. -# -# We will import `CLSyncVoting <(https://computationalcryoem.github.io/ASPIRE-Python/aspire.abinitio.html?highlight=clsyncvoting#aspire.abinitio.commonline_sync.CLSyncVoting>`_, -# then several helper utilities fron the ``coor_trans`` package to help verify our estimates. -# -# For each iteration in the loop: -# - Save the true rotations -# - Compute orientation estimate using CLSyncVoting method -# - Compare the estimated vs true rotations -# -# Each iteration will logger.info some diagnostic information that contains the top eigenvalues found. -# From class we learned that a healthy eigendistribution should have a significant gap after the third eigenvalue. -# It is clear we have such eigenspacing for the clean images, but not for the noisy images. - -for desc, _sim in [ - ("High Res", sim), - ("Downsampled", sim2), - ("Downsampled with Noise", sim3), -]: - logger.info(desc) - true_rotations = _sim.rotations # for later comparison - - orient_est = CLSyncVoting(_sim, n_theta=36) - # Get the estimated rotations - orient_est.estimate_rotations() - rots_est = orient_est.rotations - - # Compare with known true rotations - Q_mat, flag = register_rotations(rots_est, true_rotations) - regrot = get_aligned_rotations(rots_est, Q_mat, flag) - mse_reg = get_rots_mse(regrot, true_rotations) - logger.info( - f"MSE deviation of the estimated rotations using register_rotations : {mse_reg}\n" - ) - -# %% -# Experiment -# ^^^^^^^^^^ -# -# We confirmed a dramatic change in the eigenspacing when we add a lot of noise. -# Compute the SNR in this case using the formula described from class. -# Repeat the experiment with varying levels of noise to find at what level the character of the eigenspacing changes. -# This will require changing the Simulation Source's noise_filter. -# How does this compare with the levels discussed in lecture? - # %% # More Advanced Noise - Whitening # ------------------------------- @@ -287,6 +234,8 @@ # # The white noise estimator should log a diagnostic variance value. How does this compare with the known noise variance above? +# %% + # Create another Simulation source to tinker with. sim_wht = Simulation( L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder @@ -294,6 +243,7 @@ # Estimate the white noise. noise_estimator = WhiteNoiseEstimator(sim_wht) +logging.info(noise_estimator.estimate()) # %% # A Custom ``FunctionFilter`` @@ -355,56 +305,4 @@ def noise_function(x, y): src.images[:10].show() # %% - -orient_est = CLSyncVoting(src, n_theta=36) -orient_est.estimate_rotations() -rots_est = orient_est.rotations - -# %% -# We can see that the code can easily run with experimental data by subsituting the ``Source`` class. -# However, we have hit the point where we need denoising algorithms to perform orientation estimation. - -# %% -# CTF Filter -# ---------- -# -# Here we can use the ``RadialCTFFilter`` subclass of -# `CTFFilter `_ -# to generate some simulated images with CTF effects. -# -# We use the ``unique_filter`` argument of the ``Simulation`` class to apply a collection of several CTFs with different defocus. -# The defocus values are generated from the ``np.linspace`` method. We end up with a list of filters. -# -# By combining CTFFilters, noise, and other filters ASPIRE can generate repeatable rich data sets with controlled parameters. -# The ``Simulation`` class will attempt to apply transforms ``on the fly`` to batches of our images, allowing us to generate arbitrarily long stacks of data. - -# Specify the CTF parameters not used for this example -# but necessary for initializing the simulation object -pixel_size = 5 # Pixel size of the images (in angstroms) -voltage = 200 # Voltage (in KV) -defocus_min = 1.5e4 # Minimum defocus value (in angstroms) -defocus_max = 2.5e4 # Maximum defocus value (in angstroms) -defocus_ct = 7 # Number of defocus groups. -Cs = 2.0 # Spherical aberration -alpha = 0.1 # Amplitude contrast - -# Initialize simulation object with CTF filters. -# Create CTF filters -filters = [ - RadialCTFFilter(pixel_size, voltage, defocus=d, Cs=2.0, alpha=0.1) - for d in np.linspace(defocus_min, defocus_max, defocus_ct) -] - - -# Here we will combine CTF and noise features to our projections. -sim5 = Simulation( - L=v2.resolution, - n=num_imgs, - vols=v2, - unique_filters=filters, - noise_adder=custom_noise, -) -# Images with CTF, no noise applied -sim5.clean_images[:10].show() -# Images with both CTF and noise -sim5.images[:10].show() +# We have hit the point where we need denoising algorithms to perform orientation estimation as demonstrated in our ``pipeline_demo.py``. diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 579a5d5bbd..f06835ff22 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -105,10 +105,9 @@ def download(url, save_path, chunk_size=1024 * 1024): # For this ``Simulation`` we set all 2D offset vectors to zero, # but by default offset vectors will be randomly distributed. src = Simulation( - L=res, # resolution n=n_imgs, # number of projections vols=vol, # volume source - offsets=np.zeros((n_imgs, 2)), # Default: images are randomly shifted + offsets=0, # Default: images are randomly shifted noise_adder=noise_adder, unique_filters=ctf_filters, ) From 28b4b6abced2397782952f2fd19c38fc2de8d3a9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 8 Mar 2023 14:17:09 -0500 Subject: [PATCH 242/424] Change weighted function to mixin and adjust doctstring --- src/aspire/classification/class_selection.py | 46 ++++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 508a1070d7..00c7d72111 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -1,3 +1,20 @@ +""" +Selecting the "best" classes is an area of active research. + +Here we provide an abstract base class with two naive approaches as +concrete implementations. + +`RandomClassSelector` will select random indices from across the +entire dataset, with RNG controlled by `seed`. + +`TopClassSelector' will select the first `n_classes` in order. This +may be useful for debugging and development. + +Additionally we provide a few methods that have been used +historically, along with a few classes which should aid in +constructing new methods. +""" + import logging from abc import ABC, abstractmethod from heapq import heappush, heappushpop @@ -11,22 +28,6 @@ logger = logging.getLogger(__name__) -""" -Selecting the "best" classes is an area of active research. - -Here we provide an abstract base class with two naive approaches as concrete implementations. - -`RandomClassSelector` will select random indices from across the entire dataset, -with RNG controlled by `seed`. - -`TopClassSelector' will select the first `n_classes` in order. -This may be useful for debugging and development. - -Interested researchers are encouraged to implement their own selector -which will be evaluated at runtime immediately before `averages` -are computed by RIRClass2D. -""" - class ClassSelector(ABC): """ @@ -538,7 +539,7 @@ class BandpassImageQualityFunction(ImageQualityFunction): # (if we do bookkeeping during compression). -class WeightedImageQualityFunction(ImageQualityFunction): +class WeightedImageQualityMixin(ABC): """ Extends ImageQualityFunction with a radial grid weighted function for use in user defined `_function` calls. @@ -547,6 +548,7 @@ class WeightedImageQualityFunction(ImageQualityFunction): def __init__(self): # Each weight function will need a seperate cache. self._weights_cache = {} + super().__init__() @abstractmethod def _weight_function(self, r): @@ -575,10 +577,9 @@ def weights(self, L): # These classes are provided as helpers/examples. -class RampImageQualityFunction(WeightedImageQualityFunction): +class RampWeightedImageQualityMixin(WeightedImageQualityMixin): """ - Users can extend this class when they would like to apply a linear - ramp. + ImageQualityMixin to apply a linear ramp. """ def _weight_function(r): @@ -586,10 +587,9 @@ def _weight_function(r): return np.max(1 - r, 0) -class BumpImageQualityFunction(WeightedImageQualityFunction): +class BumpWeightedImageQualityFunction(WeightedImageQualityMixin): """ - Users can extend this class when they would like to apply a [0,1] - bump function. + ImageQualityMixin to apply a linear ramp to apply a [0,1] bump function. """ def _weight_function(r): From e47f23e618f8c3f2e8f905ed6dad39dea849c75a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 8 Mar 2023 14:20:53 -0500 Subject: [PATCH 243/424] Change getting started to getting started CLI --- docs/source/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index 509d2c68d5..20b160059e 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -1,5 +1,5 @@ -Getting Started -=============== +Getting Started - CLI +===================== After installing ASPIRE, the module can be invoked as a script, allowing you to perform several actions on a stack of CRYO projections (MRC files). From 78b64a553856e3824fb38c5e9423fbd65c6e8139 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 8 Mar 2023 14:44:33 -0500 Subject: [PATCH 244/424] initial add class_source rst --- docs/source/class_source.rst | 18 ++++++++++++++++++ docs/source/index.rst | 1 + 2 files changed, 19 insertions(+) create mode 100644 docs/source/class_source.rst diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst new file mode 100644 index 0000000000..189bdd85d9 --- /dev/null +++ b/docs/source/class_source.rst @@ -0,0 +1,18 @@ +Class Averaging Architecture +============================ + +ASPIRE now contains a broad collection of highly configurable +components which can be combined to create class averaging solutions +tailored to different datasets and circumstances. Each of these +components was designed to be extended by users. + +Roadmap +******* + +lorem ipsum + +.. uml:: + + Alice -> Bob: Hi! + Alice <- Bob: How are you? + diff --git a/docs/source/index.rst b/docs/source/index.rst index 4fec8d6e1d..772f6d00e1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -16,6 +16,7 @@ linear and nonlinear dimensionality reduction, randomized algorithms in numerica installation quickstart + class_source modules authors From b6d5d408db8da326ec8b195e0d722ef5fc16a1c7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Mar 2023 10:55:34 -0500 Subject: [PATCH 245/424] Change Aniso to White noise estimator as default for image source --- src/aspire/source/image.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 39a1c6ff85..7f1f8739d0 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -17,7 +17,7 @@ Multiply, Pipeline, ) -from aspire.noise import AnisotropicNoiseEstimator +from aspire.noise import WhiteNoiseEstimator from aspire.operators import ( CTFFilter, IdentityFilter, @@ -927,7 +927,7 @@ def estimate_noise_power( sample_n=None, support_radius=None, batch_size=512, - noise_estimator=AnisotropicNoiseEstimator, + noise_estimator=WhiteNoiseEstimator, ): """ Estimate the noise energy of `sample_n` images using prescribed estimator. @@ -938,7 +938,7 @@ def estimate_noise_power( Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. :param noise_estimator: Method used for estimating noise. - Defaults to AnisotropicNoiseEstimator. + Defaults to WhiteNoiseEstimator. :returns: Estimated noise energy (variance). """ From b4b2349d62a70ef4884a9cc261d8816c77cc0b22 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Mar 2023 12:02:53 -0500 Subject: [PATCH 246/424] Replace callable with more explicit flags and setters --- src/aspire/noise/noise.py | 110 +++++++++++++++++--------------- src/aspire/source/simulation.py | 6 +- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index 9d28024d54..ff538f4b20 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -85,45 +85,10 @@ def noise_var(self, res=512): return np.mean(self._noise_filter.evaluate_grid(res)) -class _DelayedNoiseAdder: - """ - Helper class for delaying the calculation of noise variance. - """ - - def __init__(self, cls, snr, seed=0): - """ - Inializes and returns a callable instance. - - :param cls: Class to be constructed when called. - :param snr: Target signal to noise ratio. - :param seed: Optional RNG seed used by NoiseAdder. - """ - self.cls = cls - self.snr = snr - self.seed = seed - - if not issubclass(cls, NoiseAdder): - raise RuntimeError( - f"_DelayedNoiseAdder.cls must be a NoiseAdder, received {cls}" - ) - - def __call__(self, signal_power): - """ - Callable returns a completed NoiseAdder. - - :param signal_power: Estimated signal power. - Used to compute target noise. - - :return: Instance of NoiseAdder. - """ - return self.cls.from_snr( - snr=self.snr, signal_power=signal_power, seed=self.seed - ) - - class WhiteNoiseAdder(NoiseAdder): """ - A Xform that adds white noise, optionally passed through a Filter object, to all incoming images. + A Xform that adds white noise, optionally passed through a Filter + object, to all incoming images. """ # TODO, check if we can change seed and/or why not. @@ -135,27 +100,54 @@ def __init__(self, var, seed=0): :param seed: Optinally provide a random seed used to generate white noise. """ - self._noise_var = var - super().__init__(noise_filter=ScalarFilter(dim=2, value=var), seed=seed) + self.signal_power = None # Used with `from_snr` + self.requires_signal_power = False # Used with `from_snr` + self.noise_var = var + self.seed = seed + # When we know the var, complete building the filter. + if var is not None: + self._build() + else: + # Otherwise, we will flag that we require signal power. + # Assigning `signal_power` later will complete builing + # filter. + self.requires_signal_power = True + + def _build(self): + """ + Builds underlying Filter for this NoiseAdder. + """ + super().__init__( + noise_filter=ScalarFilter(dim=2, value=self.noise_var), seed=self.seed + ) - @staticmethod - def from_snr(snr, signal_power=None, seed=0): + @classmethod + def from_snr(cls, snr, signal_power=None, seed=0): """ - Generates a WhiteNoiseAdder configured to produce a target signal to noise ratio. + Generates a WhiteNoiseAdder configured to produce a target + signal to noise ratio. - If signal_power is not provided, it returns a callable that will generate - the appropriate WhiteNoiseAdder later when provided `signal_power`. + When `signal_power` is not provided, `requires_signal_power` + attribute will be set. Consumers can check this attribute and + set `signal_power` as required. Setting `signal_power` should + then complete building the filter. :param snr: Desired signal to noise ratio of the returned source. :param signal_power: Optional, if the signal power is known. - :param seed: Optinally provide a random seed used to generate white noise. + :param seed: Optionally provide a random seed used to generate white noise. """ - if signal_power is None: - return _DelayedNoiseAdder(WhiteNoiseAdder, snr=snr, seed=seed) - else: - noise_var = signal_power / snr - return WhiteNoiseAdder(var=noise_var, seed=seed) + + noise_adder = cls(var=None, seed=seed) + # signal_power.setter will use `_snr` to compute the noise + # variance. + noise_adder._snr = snr + + # `signal_power.setter` should complete _build when provided + # `signal_power` is not None + noise_adder.signal_power = signal_power + + return noise_adder def __str__(self): return f"{self.__class__.__name__} with variance={self._noise_var}" @@ -168,11 +160,27 @@ def noise_var(self): """ Returns noise variance. - Note in this white noise case noise variance is known, + Note in this white noise case, noise variance is known, because the `WhiteNoiseAdder` was instantied with an explicit variance. """ return self._noise_var + @noise_var.setter + def noise_var(self, v): + self._noise_var = v + + @property + def signal_power(self): + return self._signal_power + + @signal_power.setter + def signal_power(self, p): + self._signal_power = p + if p is not None: + self.requires_signal_power = False + self.noise_var = p / self._snr + self._build() + class NoiseEstimator: """ diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index fcbdf075bc..be85f028dc 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -167,9 +167,9 @@ def __init__( if noise_adder is not None: logger.info(f"Appending {noise_adder} to generation pipeline") # If we need to calculate signal_power from Simulation, - # do so now (via _DelayedNoiseAdder). - if callable(noise_adder): - noise_adder = noise_adder(signal_power=self.estimate_signal_power()) + # do so now and assign it to complete the Filter. + if getattr(noise_adder, "requires_signal_power", False): + noise_adder.signal_power = self.estimate_signal_power() # At this point we should have a fully baked NoiseAdder if not isinstance(noise_adder, NoiseAdder): From fa3ab83957ce56a5651e7f9d9a485a69d3fa8ef5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Mar 2023 12:59:05 -0500 Subject: [PATCH 247/424] Break out signal power into estimated and true --- src/aspire/source/image.py | 38 ++++++++++++++++++++++----------- src/aspire/source/simulation.py | 16 +++++++++++--- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 7f1f8739d0..464b969730 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -160,8 +160,6 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): # Instantiate the accessor for the `images` property self._img_accessor = _ImageAccessor(self._images, self.n) - # For base ImageSource, signal is estimated from `images` - self._signal_images = self.images logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") @@ -806,7 +804,11 @@ def save_images( im.save(mrcs_filepath, overwrite=overwrite) def estimate_signal_mean_energy( - self, sample_n=None, support_radius=None, batch_size=512 + self, + sample_n=None, + support_radius=None, + batch_size=512, + image_accessor=None, ): """ Estimate the signal mean of `sample_n` projections. @@ -816,6 +818,7 @@ def estimate_signal_mean_energy( :param support_radius: Pixel radius used for masking signal support. Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. + :param image_accessor: Optionally override images. Defaults `self.images`. :returns: Estimated signal mean """ @@ -829,6 +832,8 @@ def estimate_signal_mean_energy( ) sample_n = self.n + images = image_accessor or self.images + mask = support_mask(self.L, support_radius, dtype=self.dtype) # mean is estimated batch-wise, compare with numpy @@ -836,7 +841,7 @@ def estimate_signal_mean_energy( _denom = sample_n * np.sum(mask) for i in trange(0, sample_n, batch_size): # Gather this batch of images and mask off area outside support_radius - images_masked = self._signal_images[i : i + batch_size].asnumpy()[..., mask] + images_masked = images[i : i + batch_size].asnumpy()[..., mask] # Accumulate second moments s += np.sum(images_masked**2) / _denom @@ -844,7 +849,9 @@ def estimate_signal_mean_energy( return s - def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512): + def estimate_signal_var( + self, sample_n=None, support_radius=None, batch_size=512, image_accessor=None + ): """ Estimate the signal variance of `sample_n` projections. @@ -853,6 +860,7 @@ def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512 :param support_radius: Pixel radius used for masking signal support. Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. + :param image_accessor: Optionally override images. Defaults `self.images`. :returns: Estimated signal variance. """ @@ -866,16 +874,18 @@ def estimate_signal_var(self, sample_n=None, support_radius=None, batch_size=512 ) sample_n = self.n + images = image_accessor or self.images + mask = support_mask(self.L, support_radius, dtype=self.dtype) # Var is estimated batch-wise, compare with numpy - # np_estimated_var = np.var(self._signal_images[:sample_n].asnumpy()[..., mask]) + # np_estimated_var = np.var(images[:sample_n].asnumpy()[..., mask]) first_moment = 0.0 second_moment = 0.0 _denom = sample_n * np.sum(mask) for i in trange(0, sample_n, batch_size): # Gather this batch of images and mask off area outside support_radius - images_masked = self._signal_images[i : i + batch_size].asnumpy()[..., mask] + images_masked = images[i : i + batch_size].asnumpy()[..., mask] # Accumulate first and second moments first_moment += np.sum(images_masked) / _denom second_moment += np.sum(images_masked**2) / _denom @@ -892,6 +902,7 @@ def estimate_signal_power( support_radius=None, batch_size=512, signal_power_method="estimate_signal_mean_energy", + image_accessor=None, ): """ Estimate the signal energy of `sample_n` projections using prescribed method. @@ -904,7 +915,7 @@ def estimate_signal_power( :param signal_power_method: Method used for computing signal energy. Defaults to mean via `estimate_signal_mean_energy`. Can use variance method via `estimate_signal_var`. - + :param image_accessor: Optionally override images. Defaults `self.images`. :returns: Estimated signal variance. """ @@ -917,7 +928,10 @@ def estimate_signal_power( ) signal_power = signal_estimate_method( - sample_n=sample_n, support_radius=support_radius, batch_size=batch_size + sample_n=sample_n, + support_radius=support_radius, + batch_size=batch_size, + image_accessor=image_accessor, ) return signal_power @@ -1002,9 +1016,9 @@ def estimate_snr( signal_power_method=signal_power_method, ) - # For `estimate_signal_mean_energy` we yield: mean(signal**2) / noise_variance - # `estimate_signal_var` we yield: signal_variance / noise_variance - snr = signal_power / noise_power + # For `estimate_signal_mean_energy` we yield: mean(signal**2) + # `estimate_signal_var` we yield: signal_variance + snr = (signal_power - noise_power) / noise_power return snr diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index be85f028dc..231b71738a 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -158,8 +158,6 @@ def __init__( self._projections_accessor = _ImageAccessor(self._projections, self.n) self._clean_images_accessor = _ImageAccessor(self._clean_images, self.n) - # For Simulation, signal can be computed directly from clean_images. - self._signal_images = self.clean_images # If a user prescribed NoiseAdder.from_snr(...), # noise_adder will be a function returning a completed class. @@ -169,7 +167,7 @@ def __init__( # If we need to calculate signal_power from Simulation, # do so now and assign it to complete the Filter. if getattr(noise_adder, "requires_signal_power", False): - noise_adder.signal_power = self.estimate_signal_power() + noise_adder.signal_power = self.true_signal_power() # At this point we should have a fully baked NoiseAdder if not isinstance(noise_adder, NoiseAdder): @@ -318,6 +316,18 @@ def vol_coords(self, mean_vol=None, eig_vols=None): return coords.squeeze(), res_norms, res_inners + def true_signal_power(self, *args, **kwargs): + """ + Estimate the signal power of `clean_images`. + + For usage, see `ImageSource.estimate_signal_power`. + + :returns: Estimated signal power of `clean_images` + """ + + kwargs["image_accessor"] = self.clean_images + return self.estimate_signal_power(*args, **kwargs) + def mean_true(self): return Volume(np.mean(self.vols, 0)) From d2cc437381405d5447402ec2df05a91a7d252cd7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Mar 2023 15:55:09 -0500 Subject: [PATCH 248/424] Add versioned DefaulClassAvgSource --- .../experimental_abinitio_pipeline_10028.py | 4 +- .../experimental_abinitio_pipeline_10081.py | 4 +- .../simulated_abinitio_pipeline.py | 4 +- gallery/tutorials/class_averaging.py | 6 +- src/aspire/denoising/__init__.py | 2 +- src/aspire/denoising/class_avg.py | 61 ++++++++++++++++++- tests/test_class_src.py | 4 +- 7 files changed, 72 insertions(+), 13 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index 13ab2db424..31fbe15d99 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -30,7 +30,7 @@ from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis3D -from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D +from aspire.denoising import DefaultClassAvgSource, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator from aspire.source import ArrayImageSource, RelionSource @@ -134,7 +134,7 @@ # Now perform classification and averaging for each class. # This also demonstrates the potential to use a different source for classification and averaging. -avgs_src = ClassAvgSourcev11( +avgs_src = DefaultClassAvgSource( classification_src, n_nbor=n_nbor, averager_src=src, diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index 6545b2a79b..fa9b93715d 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -27,7 +27,7 @@ from aspire.abinitio import CLSymmetryC3C4 from aspire.basis import FFBBasis3D -from aspire.denoising import ClassAvgSourcev11 +from aspire.denoising import DefaultClassAvgSource from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator from aspire.source import ArrayImageSource, RelionSource @@ -87,7 +87,7 @@ # Now perform classification and averaging for each class. # Automaticaly configure parallel processing -avgs = ClassAvgSourcev11(src, n_nbor=n_nbor, num_procs=None) +avgs = DefaultClassAvgSource(src, n_nbor=n_nbor, num_procs=None) # We'll manually cache `n_classes` worth to speed things up. avgs = ArrayImageSource(avgs.images[:n_classes]) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 8aec594040..b14a293cc7 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -22,7 +22,7 @@ from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis3D -from aspire.denoising import ClassAvgSourcev11, DenoiserCov2D +from aspire.denoising import DefaultClassAvgSource, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator, CustomNoiseAdder from aspire.operators import FunctionFilter, RadialCTFFilter from aspire.reconstruction import MeanEstimator @@ -162,7 +162,7 @@ def noise_function(x, y): # Now perform classification and averaging for each class. # This also demonstrates the potential to use a different source for classification and averaging. -avgs_src = ClassAvgSourcev11( +avgs_src = DefaultClassAvgSource( classificiation_src, n_nbor=n_nbor, averager_src=src, diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index c98a65235b..9710a92131 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -12,7 +12,7 @@ import numpy as np from PIL import Image as PILImage -from aspire.denoising import ClassAvgSourcev11, DebugClassAvgSource +from aspire.denoising import DebugClassAvgSource, DefaultClassAvgSource from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. from aspire.utils import gaussian_2d @@ -282,7 +282,7 @@ # all components required for class averaging. # # To make things easier a practical starting point -# ``ClassAvgSourcev11`` is provided which fills in reasonable +# ``DefaultClassAvgSource`` is provided which fills in reasonable # defaults based on what is available in the current ASPIRE-Python. # The defaults can be overridden simply by instantiating your own # instances of components and passing during initialization. @@ -291,4 +291,4 @@ # After understanding the various components that can be # combined in a ``ClassAvgSource``, they can be customized # of easily extended. -avg_src = ClassAvgSourcev11(noisy_src) +avg_src = DefaultClassAvgSource(noisy_src) diff --git a/src/aspire/denoising/__init__.py b/src/aspire/denoising/__init__.py index 7228a9c633..96bd4a8213 100644 --- a/src/aspire/denoising/__init__.py +++ b/src/aspire/denoising/__init__.py @@ -1,5 +1,5 @@ from .adaptive_support import adaptive_support -from .class_avg import ClassAvgSource, ClassAvgSourcev11, DebugClassAvgSource +from .class_avg import ClassAvgSource, DebugClassAvgSource, DefaultClassAvgSource from .denoised_src import DenoisedImageSource from .denoiser import Denoiser from .denoiser_cov2d import DenoiserCov2D, src_wiener_coords diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 5769c282cb..0c3fcb0ffb 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -285,7 +285,66 @@ def __init__( ) -class ClassAvgSourcev11(ClassAvgSource): +def DefaultClassAvgSource( + src, + n_nbor=50, + num_procs=None, + classifier=None, + class_selector=None, + averager=None, + averager_src=None, + version=None, +): + """ + Source for denoised 2D images using class average methods. + + Accepts `version`, to dispatch ClassAvgSource with parameters + below. Default `version` is latest available. + + :param src: Source used for image classification. + :param n_nbor: Number of nearest neighbors. Default 50. + :param num_procs: Number of processors. Use 1 to run serially. + Default `None` attempts to compute a reasonable value + based on available cores and memory. + :param classifier: `Class2D` classifier instance. + Default `None` creates `RIRClass2D`. + See code for parameter details. + :param class_selector: `ClassSelector` instance. + Default `None` creates `ContrastWithRepulsionClassSelector`. + :param averager: `Averager2D` instance. + Default `None` ceates `BFSRAverager2D` instance. + See code for parameter details. + :param averager_src: Optionally explicitly assign source + to BFSRAverager2D during initialization. + Raises error when combined with an explicit `averager` + argument. + :param version: Optionally selects a versioned DefaultClassAvgSource. + Defaults to latest available. + :return: ClassAvgSource instance. + """ + + _versions = { + None: ClassAvgSourcev110, + "latest": ClassAvgSourcev110, + "11.0": ClassAvgSourcev110, + } + + if version not in _versions: + raise RuntimeError(f"DefaultClassAvgSource version {version} not found.") + cls = _versions[version] + + return cls( + src, + n_nbor=n_nbor, + num_procs=num_procs, + classifier=classifier, + class_selector=class_selector, + averager=averager, + averager_src=averager_src, + ) + + +class ClassAvgSourcev110(ClassAvgSource): """ Source for denoised 2D images using class average methods. diff --git a/tests/test_class_src.py b/tests/test_class_src.py index c08c835dc7..b7346db68c 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -20,7 +20,7 @@ TopClassSelector, ) from aspire.classification.class_selection import _HeapItem -from aspire.denoising import ClassAvgSourcev11, DebugClassAvgSource +from aspire.denoising import DebugClassAvgSource, DefaultClassAvgSource from aspire.image import Image from aspire.source import Simulation from aspire.utils import Rotation @@ -34,7 +34,7 @@ RESOLUTIONS = [32] DTYPES = [np.float64] -CLS_SRCS = [DebugClassAvgSource, ClassAvgSourcev11] +CLS_SRCS = [DebugClassAvgSource, DefaultClassAvgSource] # For very small problems, it usually isn't worth running in parallel. NUM_PROCS = 1 From 5292d4be0d38b17b8d40b3896ef61cea53c854f9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 9 Mar 2023 16:29:16 -0500 Subject: [PATCH 249/424] Add ContrastImageQualityFunction --- src/aspire/classification/class_selection.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 00c7d72111..ab08ade533 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -420,7 +420,7 @@ def _check_selection(self, selection, n_img): class ContrastWithRepulsionClassSelector(GreedyClassRepulsion, ContrastClassSelector): """ - Selects top classes based on highest contrast with prior rejection. + Selects top classes based on highest contrast with GreedyClassRepulsion. """ @@ -493,6 +493,22 @@ def __call__(self, img): return res +class ContrastImageQualityFunction(ImageQualityFunction): + """ + Computes the ratio of variance of pixels. + """ + + def _function(self, img): + """ + Scoring function based on variance. + + :param img: Input image as 2d Numpy array. + + :return: Pixel variance. + """ + return np.var(img) + + class BandedSNRImageQualityFunction(ImageQualityFunction): """ Computes the ratio of variance of central pixels of image to From dc74d0061495ce6eaffa2294f7b12cc96facd263 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 10 Mar 2023 09:26:32 -0500 Subject: [PATCH 250/424] cleanup global selector and weight mixin functions --- src/aspire/classification/__init__.py | 6 +++ src/aspire/classification/class_selection.py | 42 ++++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/aspire/classification/__init__.py b/src/aspire/classification/__init__.py index e39619b1bb..66e09553f0 100644 --- a/src/aspire/classification/__init__.py +++ b/src/aspire/classification/__init__.py @@ -11,13 +11,19 @@ from .class2d import Class2D from .class_selection import ( BandedSNRImageQualityFunction, + BumpWeightedContrastImageQualityFunction, + BumpWeightedImageQualityMixin, ClassSelector, ContrastClassSelector, + ContrastImageQualityFunction, ContrastWithRepulsionClassSelector, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, + RampWeightedContrastImageQualityFunction, + RampWeightedImageQualityMixin, RandomClassSelector, TopClassSelector, + WeightedImageQualityMixin, ) from .rir_class2d import RIRClass2D diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index ab08ade533..447bbb92d4 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -164,7 +164,7 @@ class ContrastClassSelector(ClassSelector): def _select(self, classes, reflections, distances): # Compute per class variance - dist_var = np.var(distances[:, 1:], axis=1) + dist_var = np.var(distances, axis=1) # Compute the ordering, descending sorted_class_inds = np.argsort(dist_var)[::-1] @@ -186,7 +186,8 @@ class DistanceClassSelector(ClassSelector): """ def _select(self, classes, reflections, distances): - # Compute per class variance + # Compute per class variance. + # Skips self distance (0). dist_mean = np.mean(distances[:, 1:], axis=1) # Compute the ordering, descending @@ -495,7 +496,7 @@ def __call__(self, img): class ContrastImageQualityFunction(ImageQualityFunction): """ - Computes the ratio of variance of pixels. + Computes the variance of pixels. """ def _function(self, img): @@ -587,10 +588,21 @@ def weights(self, L): :return: 2d weight array for LxL grid. """ grid = self._grid_cache[L] + # frompyfunc performs the broadcast. return self._weights_cache.setdefault( - L, np.frompyfunc(self.weight_function)(grid["r"]) + L, np.frompyfunc(self._weight_function, 1, 1)(grid["r"]) ) + def _function(self, img): + """ + Apply weights to `img` before calling underlying `_function`. + + Users may override this to use weights in more sophisticated + manner. + """ + img = img * self.weights(img.shape[-1]) + return super()._function(img) + # These classes are provided as helpers/examples. class RampWeightedImageQualityMixin(WeightedImageQualityMixin): @@ -598,19 +610,35 @@ class RampWeightedImageQualityMixin(WeightedImageQualityMixin): ImageQualityMixin to apply a linear ramp. """ - def _weight_function(r): + def _weight_function(self, r): # linear ramp return np.max(1 - r, 0) -class BumpWeightedImageQualityFunction(WeightedImageQualityMixin): +class BumpWeightedImageQualityMixin(WeightedImageQualityMixin): """ ImageQualityMixin to apply a linear ramp to apply a [0,1] bump function. """ - def _weight_function(r): + def _weight_function(self, r): # bump function (normalized to [0,1] if r >= 1: return 0 else: return np.exp(-1 / (1 - r**2) + 1) + + +class BumpWeightedContrastImageQualityFunction( + BumpWeightedImageQualityMixin, ContrastImageQualityFunction +): + """ + Computes the variance of pixels after weighting with Bump function. + """ + + +class RampWeightedContrastImageQualityFunction( + RampWeightedImageQualityMixin, ContrastImageQualityFunction +): + """ + Computes the variance of pixels after weighting with Ramp function. + """ From 356ff7bc0e2a83b80cb26b62d8afac48149f32a2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 10 Mar 2023 10:36:39 -0500 Subject: [PATCH 251/424] Additional tests and fixtures for class src --- tests/test_class_src.py | 80 +++++++++++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/tests/test_class_src.py b/tests/test_class_src.py index b7346db68c..8162256ba0 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -10,11 +10,14 @@ from aspire.classification import ( BandedSNRImageQualityFunction, BFRAverager2D, + BumpWeightedContrastImageQualityFunction, ContrastClassSelector, + ContrastImageQualityFunction, ContrastWithRepulsionClassSelector, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, + RampWeightedContrastImageQualityFunction, RandomClassSelector, RIRClass2D, TopClassSelector, @@ -32,8 +35,14 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") -RESOLUTIONS = [32] -DTYPES = [np.float64] +IMG_SIZES = [ + 32, + pytest.param(31, marks=pytest.mark.expensive), +] +DTYPES = [ + np.float64, + pytest.param(np.float32, marks=pytest.mark.expensive), +] CLS_SRCS = [DebugClassAvgSource, DefaultClassAvgSource] # For very small problems, it usually isn't worth running in parallel. NUM_PROCS = 1 @@ -42,18 +51,27 @@ def sim_fixture_id(params): res = params[0] dtype = params[1] - return f"res={res}, dtype={dtype.__name__}" + return f"res={res}, dtype={dtype}" + + +@pytest.fixture(params=DTYPES, ids=lambda x: f"dtype={x}") +def dtype(request): + return request.param + + +@pytest.fixture(params=IMG_SIZES, ids=lambda x: f"img_size={x}") +def img_size(request): + return request.param -@pytest.fixture(params=product(RESOLUTIONS, DTYPES), ids=sim_fixture_id) -def class_sim_fixture(request): +@pytest.fixture +def class_sim_fixture(dtype, img_size): """ Construct a Simulation with explicit viewing angles forming synthetic classes. """ # Configuration - res, dtype = request.param n_inplane_rots = 40 # Platonic solids can generate our views. @@ -84,13 +102,13 @@ def class_sim_fixture(request): # TODO, probably our default volume should "just work" for this stuff... tighter var? v = Volume( np.load(os.path.join(DATA_DIR, "clean70SRibosome_vol.npy")), dtype=dtype - ).downsample(res) + ).downsample(img_size) # Contruct the Simulation source. # Note using a single volume via C=1 is critical to matching # alignment without the complexity of remapping via states etc. src = Simulation( - L=res, n=n, vols=v, offsets=0, amplitudes=1, C=1, angles=true_rots.angles + L=img_size, n=n, vols=v, offsets=0, amplitudes=1, C=1, angles=true_rots.angles ) # Prefetch all the images src.cache() @@ -190,19 +208,59 @@ def test_online_selector(cls_fixture, selector): GlobalWithRepulsionClassSelector, ] +QUALITY_FUNCTIONS = [ + BandedSNRImageQualityFunction, + ContrastImageQualityFunction, + BumpWeightedContrastImageQualityFunction, + RampWeightedContrastImageQualityFunction, +] + @pytest.mark.parametrize( "selector", GLOBAL_SELECTORS, ids=lambda param: f"Selector={param}" ) +@pytest.mark.parametrize( + "quality_function", QUALITY_FUNCTIONS, ids=lambda param: f"Quality Function={param}" +) @pytest.mark.expensive -def test_global_selector(class_sim_fixture, cls_fixture, selector): +def test_global_selector(class_sim_fixture, cls_fixture, selector, quality_function): basis = FFBBasis2D(class_sim_fixture.L, dtype=class_sim_fixture.dtype) averager = BFRAverager2D(basis, class_sim_fixture, num_procs=NUM_PROCS) - fun = BandedSNRImageQualityFunction() + fun = quality_function() - # classes, reflections, distances = cls_fixture + # Note: classes, reflections, distances = cls_fixture selection = selector(averager, fun).select(*cls_fixture) # smoke test logger.info(f"{selector}: {selection}") + + +# Try to put methods in the `DefaultClassAvgSource`s under continual +# test. RIRClass2D, BFRAverager2D, and stacking are covered +# elsewhere, so that leaves manually testing contrast selection, +def test_contrast_selector(dtype): + """ + Test selector is actually ranking by contrast. + """ + + n_classes = 5 + n_nbor = 32 + + # Generate test data + classes = np.arange(n_nbor * n_classes, dtype=int).reshape(n_nbor, n_classes).T + reflections = np.random.rand(n_classes, n_nbor) > 0.5 # Random bool + distances = np.random.rand(n_classes, n_nbor).astype(dtype) # [0,1) + + # Compute reference manually. + V = distances.var(axis=1) + ref_class_ids = np.argsort(V)[::-1] + ref_scores = V[ref_class_ids] + + # Compute using class under test. + selector = ContrastClassSelector() + selection = selector.select(classes, reflections, distances) + + # Compare indices and scores. + assert np.all(selection == ref_class_ids) + assert np.allclose(selector._quality_scores, ref_scores) From 15e5a18294b284dc5a7fb7b154da46e32a56bbe4 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Tue, 7 Mar 2023 09:43:59 -0500 Subject: [PATCH 252/424] Use adjusted Rand score to find clustering accuracy. Co-authored-by: Garrett Wright <47759732+garrettwrong@users.noreply.github.com> --- src/aspire/source/simulation.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 49014e5827..8328fe0458 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -3,6 +3,7 @@ import numpy as np from scipy.linalg import eigh, qr +from sklearn.metrics import adjusted_rand_score from aspire.image import Image from aspire.noise import NoiseAdder @@ -406,18 +407,19 @@ def eval_eigs(self, eigs_est, lambdas_est): def eval_clustering(self, vol_idx): """ - Evaluate clustering estimation + Evaluate clustering estimation using an adjusted Rand score. :param vol_idx: Indexes of the volumes determined (0-indexed) - :return: Accuracy [0-1] in terms of proportion of correctly assigned labels + :return: Accuracy [-0.5, 1] in terms of proportion of correctly assigned labels. + Identical clusters (up to a permutation) have a score of 1, random labeling + will be close to 0, and discordant clusterings will be negative. """ assert ( len(vol_idx) == self.n ), f"Need {self.n} vol indexes to evaluate clustering" - # Remember that `states` is 1-indexed while vol_idx is 0-indexed - correctly_classified = np.sum(self.states - 1 == vol_idx) + # Remember that `states` is 1-indexed while vol_idx is 0-indexed. - return correctly_classified / self.n + return adjusted_rand_score(self.states - 1, vol_idx) def eval_coords(self, mean_vol, eig_vols, coords_est): """ @@ -435,7 +437,6 @@ def eval_coords(self, mean_vol, eig_vols, coords_est): # 0-indexed states vector states = self.states - 1 - coords_true = coords_true[states] res_norms = res_norms[states] res_inners = res_inners[:, states] From decf77713f1fa95189f34b2cf09ce4a5936e5ce7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 10 Mar 2023 13:02:08 -0500 Subject: [PATCH 253/424] add class avg source documentation --- docs/source/class_source.rst | 335 ++++++++++++++++++++++++++++++++++- docs/source/conf.py | 1 + setup.py | 1 + 3 files changed, 328 insertions(+), 9 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 189bdd85d9..d433914ea3 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -1,18 +1,335 @@ Class Averaging Architecture ============================ -ASPIRE now contains a broad collection of highly configurable +ASPIRE now contains a broad collection of configurable and extensible components which can be combined to create class averaging solutions -tailored to different datasets and circumstances. Each of these -components was designed to be extended by users. +tailored to different datasets. The architecture was designed to be +both modular and encourage experimentation. Lower level components +are aggregated into a high level interface by ``ClassAvgSource`` +instances. Starting there this document will descend into each +contributing component. -Roadmap -******* -lorem ipsum +ClassAvgSource +************** -.. uml:: +``ClassAvgSource`` is the fully customizable base class which links +together components into a cohesive source to be used with other +ASPIRE components. A power user can instantiate an instance of each +required component and assign them here for complete control. - Alice -> Bob: Hi! - Alice <- Bob: How are you? +.. mermaid:: + + classDiagram + class ClassAvgSource{ + src: ImageSource + classifier: Class2D + class_selector: ClassSelector + averager: Averager2D + +images() + } + + ClassAvgSource o-- ImageSource + ClassAvgSource o-- Class2D + ClassAvgSource o-- ClassSelector + ClassAvgSource o-- Averager2D + + class ImageSource{ + +images() + } + class Class2D{ + +classify() + } + class ClassSelector{ + +select() + } + class Averager2D{ + +average() + } + +"""""""""" + +While that allows for full customization, two helper classes are +provided that provide defaults as a jumping off point. Both of these +helper sources only require an input ``Source`` to be instantiated. +They can still be fully customized, but they are intended to start +with sensible defaults, so users only need to instantiate the specific +components they wish to configure. + +.. mermaid:: + + classDiagram + ClassAvgSource <|-- DebugClassAvgSource + ClassAvgSource <|-- DefaultClassAvgSource + class DebugClassAvgSource{ + src: ImageSource + classifier: RIRClass2D + class_selector: TopClassSelector + averager: BFRAverager2D + +images() + } + class DefaultClassAvgSource{ + version="11.0" + src: ImageSource + classifier: RIRClass2D + class_selector: ContrastWithRepulsionClassSelector + averager: BFSRAverager2D + +images() + } + +``DebugClassAvgSource`` is designed for use in testing, documentation, +and development because it defaults to the simplest components while +also maintaining the original input source index ordering. That is, +the first 10 class averages from ``DebugClassAvgSource`` should +correspond with the first 10 source images without requiring any index +mappings etc. + +``DefaultClassAvgSource`` applies the most sensible defaults available +in the current ASPIRE release. ``DefaultClassAvgSource`` takes a +version string, such as ``11.0`` which will return a specific +configuration. This version should allow users to perform a similar +experiment across release as ASPIRE implements improved method. When +a version is not provided, ``DefaultClassAvgSource`` defaults to the +latest version available. + + +Classifiers +*********** + +Classifiers take an image ``Source`` and attempts to classify into +``classes`` that identify images have similar viewing angles up to +reflection. All ``Class2D`` instances are expected to implement a +``classify`` method which returns ``(classes, reflections, +distances)``. The three returned variables are expected to be 2D +Numpy arrays in a neighbor network format having shape ``(n_classes, +n_nbors)``. So to retrieve the input source indices for the first +classes' neighbors, we would want ``classes[0,:]``. The first class +index should be the base image used for classification. So we would +expect ``classes[0,0]`` to be ``input_src.images[0]``. + +No further class selection or order occurs during classification. +Those methods are broken out into other components. + +Currently ASPIRE has a single classification algorithm known as +``RIRClass2D``. This algorithm uses multiple applications of PCA in +conjunction with Bispectrum analysis to identify nearest neighbors in +a rotationally invariant feature space. + +.. mermaid:: + + classDiagram + class Class2D{ + +classify() + } + Class2D <|-- RIRClass2D + +Class Selectors +*************** + +Class Selectors consume the output of ``Class2D`` and attempt to order +and/or filter classes down to a selection. Selection the "best" +classes in CryoEM problems is still an area of active research. Some +common methods are provided, along with an extensible base interface. + +Generally, Class Selection comes in two flavors depending on what +information is required to perform the selection. + +Local Class Selectors +--------------------- + +For "Local" class selection, we will attempt to use only the +information returned from ``Class2D``. In the case of ``RIRClass2D`` +this would primarily be network of ``distances`` as measured in the +compressed feature space. + +This approach has two main advantages. First, we already have this +information computed as part of classification. Second, it allows us +to register and stack a relatively small subset of the "best" classes. +Because registration and alignment are computationally expensive this +can reduce pipeline run times by an order of magnitude. + +.. mermaid:: + + classDiagram + class ClassSelector{ + +select() + } + ClassSelector <|-- TopClassSelector + ClassSelector <|-- RandomClassSelector + ClassSelector <|-- ContrastClassSelector + ClassSelector <|-- DistanceClassSelector + ClassSelector o-- GreedyClassRepulsion + +Global Class Selectors +---------------------- + +Global Class Selection techniques first compute the entire collection +registered and aligned class averages, then compute some quality +measure on all classes. + +These are most similar to the historical MATLAB approaches, sometimes +referred to as "out-of-core" methods. It is believed that many legacy +MATLAB experiments computed contrast (variance) of all class averaged +images, sorted the class averages to express highest contrast, +potentially avoiding classes with views we've already seen. This can +be accomplished now by using the ``ContrastImageQualityFunction`` in a +``GlobalWithRepulsionClassSelector``. + +An SNR based approach is also provided, and a Bandpass method should +be implemented in a future release. Again, these components are fully +customizable and the base interfaces were designed with algorithm +developers in mind. + +Implementing concrete ``GlobalClassSelector`` leverage subcomponents +described below. + +.. mermaid:: + + classDiagram + ClassSelector <|-- GlobalClassSelector + class GlobalClassSelector{ + averager: Averager2D + function: ImageQuaityFunction + heap_size: int + } + GlobalClassSelector *-- ImageQualityFunction + GlobalClassSelector ..> Heap + GlobalClassSelector <|-- GlobalWithRepulsionClassSelector + + + class ImageQualityFunction{ + -_function + +__call__() + } + ImageQualityFunction o-- WeightedImageQualityMixin + ImageQualityFunction <|-- BandedSNRImageQualityFunction + ImageQualityFunction <|-- ContrastImageQualityFunction + ImageQualityFunction <|-- BandpassImageQualityFunction_TBD + + class WeightedImageQualityMixin{ + -_weight_function + } + WeightedImageQualityMixin <|-- RampWeightedImageQualityMixin + WeightedImageQualityMixin <|-- BumpWeightedImageQualityMixin + + GlobalClassSelector <|-- RampWeightedContrastImageQualityFunction + RampWeightedImageQualityMixin <|-- RampWeightedContrastImageQualityFunction + GlobalClassSelector <|-- BumpWeightedContrastImageQualityFunction + BumpWeightedImageQualityMixin <|-- BumpWeightedContrastImageQualityFunction + +Class Repulsion +^^^^^^^^^^^^^^^ + +Class Repulsion are techniques used to avoid classes based on some +criterion. Currently we provide ``GreedyClassRepulsion``, but this +mix-in class can be mimicked to implement alternate schemes. + +``GreedyClassRepulsion`` is based on the following intuition. Assume +the selection has in fact ordered the classes s.t. *the "best" +classes occur first*. It follows that the "best" expression of a +viewing angle locus will be the first seen. Now assume *the +classifier returns classes with closest viewing angles* (up to +reflections). Then the classes formed by *neighbors of the current +expression are inferior*. The aggressiveness of the neighbor +repulsion count is tunable. + +In practice, ``GreedyClassRepulsion`` is a mix-in designed to be mixed +into any other ``ClassSelector``. Note, that repulsion can (and will) +dramatically reduce the population of class averages returned. + + +Image Quality Functions +^^^^^^^^^^^^^^^^^^^^^^^ + +The ``ImageQualityFunction`` interface provides a consistent way to +bring your own function to measure the quality of an aligned and +registered class average. This function should operate on a single +Image, with conversions and broadcasting being handled behind the +scenes. + +An example would be ``ContrastImageQualityFunction`` which computes +and returns contrast as variance. + +Another advantage of using the class is that it exposes and manages a +grid cache, which is handy to avoid recomputing the same grid for +every image when using spatial methods. + +WeightedImageQualityMixin +^^^^^^^^^^^^^^^^^^^^^^^^^ + +``WeightedImageQualityMixin`` is designed to mix with subclasses of +``ImageQualityFunction``, extending them with a weighted image mask +applied prior to the image quality function call. + +Two concrete examples are provided +``BumpWeightedContrastImageQualityFunction`` and +``RampWeightedContrastImageQualityFunction`` which apply the +respective weight functions prior to the Contrast calculation. + +Again, ``WeightedImageQualityMixin`` exposes and manages a grid cache, +this time for grid weights. + + +Averagers +********* + +Averagers consume from a ``Source`` and returning averaged images +defined by class network arguments ``classes`` and ``reflections``. +You may find the terms averaging and stacking used interchangeably in +this context, so know that averaging does not always imply *arithmetic +mean*, + +Some averaging techniques, those subclassing ``AligningAverager2D`` +have distinct ``alignment`` and ``averaging`` stages. Others such as +Expectation-Maximization may perform these internally and provide only +an opaque ``averages`` stage. + +.. mermaid:: + + classDiagram + class Averager2D{ + basis: Basis + src: ImageSource + +average() + } + Averager2D ..> ImageStacker + Averager2D <|-- AligningAverager2D + class AligningAverager2D{ + align() + } + ImageSource *-- Averager2D + Averager2D <|-- AligningAverager2D + Averager2D <|-- EMAverager2D_TBD + Averager2D <|-- FTKAverager2D_TBD + AligningAverager2D <|-- BFRAverager2D + BFRAverager2D <|-- BFSRAverager2D + AligningAverager2D <|-- ReddyChetterjiAverager2D + ReddyChetterjiAverager2D <|-- BFSReddyChetterjiAverager2D + + class ImageStacker{ + stack() + } + class SigmaRejectionImageStacker{ + sigma + } + class WinsorizedImageStacker{ + percentile + } + ImageStacker <|-- MeanImageStacker + ImageStacker <|-- MedianImageStacker + ImageStacker <|-- SigmaRejectionImageStacker + SigmaRejectionImageStacker .. Gaussian + SigmaRejectionImageStacker .. FWHM + ImageStacker <|-- WinsorizedImageStacker + +ImageStacker +------------ + +``ImageStacker`` provides an interface for the common task of stacking +images. Implementations for common stacking methods are provided and +should work for both ``Image`` and (1D) coefficient stacks. Users +experimenting with advanced stacking are responsible for selecting an +ImageStacker method appropriate for their data. + +Note that the ASPIRE default is naturally ``MeanImageStacker``. diff --git a/docs/source/conf.py b/docs/source/conf.py index a99fe4ef9d..b79cc994cd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -39,6 +39,7 @@ "sphinx.ext.autodoc", "sphinx.ext.mathjax", "sphinxcontrib.bibtex", + "sphinxcontrib.mermaid", "sphinx.ext.napoleon", "sphinx_gallery.gen_gallery", ] diff --git a/setup.py b/setup.py index f4c0a45f57..13a500605a 100644 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ def read(fname): "pytest-xdist", "requests", "sphinxcontrib-bibtex", + "sphinxcontrib-mermaid", "sphinx-gallery", "sphinx-rtd-theme>=0.4.2", "snakeviz", From 41c195cf8fa920a0d381a57ad16d355066fd16ab Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 10 Mar 2023 13:12:44 -0500 Subject: [PATCH 254/424] tox touch ups --- docs/source/conf.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index b79cc994cd..9aad79aa5b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -39,7 +39,7 @@ "sphinx.ext.autodoc", "sphinx.ext.mathjax", "sphinxcontrib.bibtex", - "sphinxcontrib.mermaid", + "sphinxcontrib.mermaid", "sphinx.ext.napoleon", "sphinx_gallery.gen_gallery", ] diff --git a/setup.py b/setup.py index 13a500605a..fdf17c1e61 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,7 @@ def read(fname): "pytest-xdist", "requests", "sphinxcontrib-bibtex", - "sphinxcontrib-mermaid", + "sphinxcontrib-mermaid", "sphinx-gallery", "sphinx-rtd-theme>=0.4.2", "snakeviz", From 06617f27d005128c9f1c21369cc85bf9af2468c6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 13 Mar 2023 15:02:03 -0400 Subject: [PATCH 255/424] Initial addition of RemappedSource --- src/aspire/source/__init__.py | 2 +- src/aspire/source/image.py | 105 +++++++++++++++++++++++++++--- tests/test_remapped_source.py | 40 ++++++++++++ tests/test_simulation_metadata.py | 30 +++++++++ 4 files changed, 166 insertions(+), 11 deletions(-) create mode 100644 tests/test_remapped_source.py diff --git a/src/aspire/source/__init__.py b/src/aspire/source/__init__.py index db3e955e78..494bba6ccf 100644 --- a/src/aspire/source/__init__.py +++ b/src/aspire/source/__init__.py @@ -1,7 +1,7 @@ import logging from aspire.source.coordinates import BoxesCoordinateSource, CentersCoordinateSource -from aspire.source.image import ArrayImageSource, ImageSource +from aspire.source.image import ArrayImageSource, ImageSource, RemappedSource from aspire.source.relion import RelionSource from aspire.source.simulation import Simulation diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 6a176c688e..01881503ef 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -162,6 +162,22 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") + def __getitem__(self, indices): + """ + Check `indices` and return slice of current Source as a new + Source. + + Internally uses `RemappedSource`. + + :param indices: Requested indices as a Python slice object, + 1-D NumPy array, list, or a single integer. Slices default + to a start of 0, an end of self.num_imgs, and a step of 1. + See _ImageAccessor. + :return: Source composed of the images and metadata at `indices`. + """ + + return RemappedSource(self, indices) + @property def n_ctf_filters(self): """ @@ -311,17 +327,29 @@ def has_metadata(self, metadata_fields): metadata_fields = [metadata_fields] return all(f in self._metadata.columns for f in metadata_fields) - def get_metadata(self, metadata_fields, indices=None, default_value=None): - """ - Get metadata field information of this ImageSource for selected indices + def get_metadata(self, metadata_fields=None, indices=None, default_value=None): + """ + Get metadata field information of this ImageSource for a + selection of fields of indices. The default should return the + entire metadata table. + + :param metadata_fields: A string, or list of strings, + representing the metadata field(s) to be queried. + Defaults to None, which yields all populated columns. + :param indices: A list of 0-based indices indicating the + indices for which to get metadata. If indices is None, + then values corresponding to all indices in this Source + object are returned. + :param default_value: Default scalar value to use for any + fields not found in the metadata. If None, no default + value is used, and missing field(s) cause a RuntimeError. + :return: An ndarray of values (any valid np types) + representing metadata info. + """ + # When metadata_fields=None, default to returning all. + if metadata_fields is None: + metadata_fields = self._metadata.columns - :param metadata_fields: A string, of list of strings, representing the metadata field(s) to be queried. - :param indices: A list of 0-based indices indicating the indices for which to get metadata. - If indices is None, then values corresponding to all indices in this Source object are returned. - :param default_value: Default scalar value to use for any fields not found in the metadata. If None, - no default value is used, and missing field(s) cause a RuntimeError. - :return: An ndarray of values (any valid np types) representing metadata info. - """ if isinstance(metadata_fields, str): metadata_fields = [metadata_fields] if indices is None: @@ -803,6 +831,63 @@ def save_images( im.save(mrcs_filepath, overwrite=overwrite) +class RemappedSource(ImageSource): + """ + Map into another into ImageSource. + """ + + def __init__(self, src, indices, memory=None): + """ + Instantiates a new source along given `indices`. + + :param src: ImageSource to be used as the source. + :param index_map: index_map + :param memory: str or None + The path of the base directory to use as a data store or + None. If None is given, no caching is performed. + """ + + self.src = src + if not isinstance(src, ImageSource): + raise TypeError(f"Input src {src} must be an ImageSource.") + + # `_ImageAccessor` performs checking and slicing logic. + # `index_map` sequence forms a natural map from the "new" source -> "self". + # Example, if request=slice(500,1000), + # then new_src[0] ~> old_src[500]; index_map[0] = 500. + self.index_map = _ImageAccessor(lambda x: x, src.n)[indices] + + # Get all the metadata associated with these indices. + # Note, I would have prefered to use our API (get_metadata) + # here, but it returns a Numpy array, which would need to be + # converted back into Pandas for use below. So here we'll just + # use `loc` to return a dataframe. + metadata = self.src._metadata.loc[self.index_map] + + # Construct a fully formed ImageSource with this metadata + super().__init__( + L=src.L, + n=len(self.index_map), + dtype=src.dtype, + metadata=metadata, + memory=memory, + ) + + def _images(self, indices): + """ + Returns images from `self.src` corresponding to `indices` + remapped by `self.index_map`. + + :param indices: A 1-D NumPy array of indices. + :return: An `Image` object. + """ + mapped_indices = self.index_map[indices] + return self.src.images[mapped_indices] + + def __repr__(self): + return f"{self.__class__.__name__} mapping {self.n} of {self.src.n} indices from {self.src.__class__.__name__}." + + class ArrayImageSource(ImageSource): """ An `ImageSource` object that holds a reference to an underlying `Image` object (a thin wrapper on an ndarray) diff --git a/tests/test_remapped_source.py b/tests/test_remapped_source.py new file mode 100644 index 0000000000..ac7ee7910f --- /dev/null +++ b/tests/test_remapped_source.py @@ -0,0 +1,40 @@ +import logging + +import numpy as np +import pytest + +from aspire.source import Simulation + +logger = logging.getLogger(__name__) + + +@pytest.fixture +def sim_fixture(): + """ + Generate a very small simulation and slice it. + """ + sim = Simulation(L=8, n=10, C=1) + sim2 = sim[0::2] # Slice the evens + return sim, sim2 + + +def test_remapping(sim_fixture): + sim, sim2 = sim_fixture + + # Check images are served correctly. + assert np.allclose(sim.images[sim2.index_map].asnumpy(), sim2.images[:].asnumpy()) + + # Check meta is served correctly. + assert np.all(sim.get_metadata(indices=sim2.index_map) == sim2.get_metadata()) + + +def test_repr(sim_fixture): + sim, sim2 = sim_fixture + + logger.debug(f"repr(RemappedSource): {repr(sim2)}") + + # Check `sim` is mentioned in the repr + assert type(sim).__name__ in repr(sim2) + + # Check index counts are mentioned in the repr + assert f"{sim2.n} of {sim.n}" in repr(sim2) diff --git a/tests/test_simulation_metadata.py b/tests/test_simulation_metadata.py index eab76225d1..0d208297e2 100644 --- a/tests/test_simulation_metadata.py +++ b/tests/test_simulation_metadata.py @@ -82,3 +82,33 @@ def testMetadata5(self): equal_nan=True, ) ) + + def test_get_metadata_all(self): + """ + Test we can get the entire metadata table. + """ + + # Get the metadata via our API. + metadata_api = self.sim.get_metadata() + + # Access the metadata directly in the frame. + metadata_df = self.sim._metadata.to_numpy() + + # Assert we've returned the entire table. + self.assertTrue(np.all(metadata_api == metadata_df)) + + def test_get_metadata_index_slice(self): + """ + Test we can get all columns for a selection of rows. + """ + # Test rows + rows = [0, 1, 42] + + # Get the metadata from our API. + metadata_api = self.sim.get_metadata(indices=rows) + + # Access the metadata directly in the frame. + metadata_df = self.sim._metadata.loc[rows].to_numpy() + + # Assert we've returned the rows + self.assertTrue(np.all(metadata_api == metadata_df)) From 1cd60073a66168282406b5acf76013f65b7b4ef5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 12:01:05 -0400 Subject: [PATCH 256/424] Remove noise_estimator arg --- src/aspire/source/image.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 464b969730..58cc313c38 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -941,18 +941,16 @@ def estimate_noise_power( sample_n=None, support_radius=None, batch_size=512, - noise_estimator=WhiteNoiseEstimator, ): """ - Estimate the noise energy of `sample_n` images using prescribed estimator. + Estimate the noise energy of `sample_n` images using + `WhiteNoiseEstimator`. :param sample_n: Number of images used for estimate. Defaults to all images in source. :param support_radius: Pixel radius used for masking signal support. Default of None will compute inscribed circle, `self.L // 2`. :param batch_size: Images per batch, defaults 512. - :param noise_estimator: Method used for estimating noise. - Defaults to WhiteNoiseEstimator. :returns: Estimated noise energy (variance). """ @@ -963,7 +961,7 @@ def estimate_noise_power( # Note, noise_estimator expects radius as proportion. support_radius_proportion = support_radius / (self.L // 2) - est = noise_estimator( + est = WhiteNoiseEstimator( src=self, bgRadius=support_radius_proportion, batchSize=batch_size ) From 92fd4438204489c49cf1056f62a65eddc06d8af7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 12:38:12 -0400 Subject: [PATCH 257/424] replace estimate_snr overload with seperate true_snr for Simulation --- src/aspire/source/simulation.py | 16 +++++++++++++--- tests/test_noise.py | 5 +++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 231b71738a..2bcfebc8f3 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -325,6 +325,12 @@ def true_signal_power(self, *args, **kwargs): :returns: Estimated signal power of `clean_images` """ + # Note, in the future we can do something more clever here, + # perhaps starting with the simulation Volume. For now we + # share code with ImageSource, so the method is at least + # identical, up to using `clean_images`. The method is also + # the same as NoiseEstimator, up to ignoring the first moment + # and a few optional parameters. kwargs["image_accessor"] = self.clean_images return self.estimate_signal_power(*args, **kwargs) @@ -491,9 +497,11 @@ def eval_coords(self, mean_vol, eig_vols, coords_est): return {"err": err, "rel_err": rel_err, "corr": corr} - def estimate_snr(self, *args, **kwargs): + def true_snr(self, *args, **kwargs): """ - See ImageSource.estimate_snr() for documentation. + Compute SNR using `true_signal_power` and the noise power known to simulation. + + See Simulation.true_signal_power() for parameters. """ # For clean images return infinite SNR. # Note, relationship with CTF and other sim corruptions still isn't clear to me... @@ -502,4 +510,6 @@ def estimate_snr(self, *args, **kwargs): # For SNR of Simulations, use the theoretical noise variance # known from the noise_adder instead of deriving from PSD. - return super().estimate_snr(noise_power=self.noise_adder.noise_var) + noise_power = self.noise_adder.noise_var + signal_power = self.true_signal_power(*args, **kwargs) + return signal_power / noise_power diff --git a/tests/test_noise.py b/tests/test_noise.py index 91f4346484..5a3fa4b3d7 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -169,6 +169,11 @@ def test_from_snr_white(sim_fixture, target_noise_variance): # and compute the resulting snr of the sim. target_snr = sim_fixture.estimate_snr() + # Compute the `true_snr` of the sim. + computed_true_snr = sim_fixture.true_snr() + # Compare the `estimate_snr()` with `true_snr()` + assert np.isclose(target_snr, computed_true_snr, rtol=0.05) + # Attempt to create a new simulation at this `target_snr` # For unit testing, we will use `sim_fixture`'s volume, # but the new Simulation instance should yield different projections. From 50facbfdfb602dda7571e2612581a56dcdd6a889 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 14:00:54 -0400 Subject: [PATCH 258/424] Review updates --- docs/source/class_source.rst | 24 +++++++++---------- .../experimental_abinitio_pipeline_10028.py | 4 ++-- .../experimental_abinitio_pipeline_10081.py | 4 ++-- .../simulated_abinitio_pipeline.py | 8 +++---- gallery/tutorials/class_averaging.py | 7 +++--- gallery/tutorials/pipeline_demo.py | 2 +- src/aspire/classification/class_selection.py | 19 ++++++++++----- src/aspire/denoising/class_avg.py | 6 ++--- src/aspire/image/image_stacker.py | 2 +- 9 files changed, 41 insertions(+), 35 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index d433914ea3..3ec305ac87 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -3,9 +3,9 @@ Class Averaging Architecture ASPIRE now contains a broad collection of configurable and extensible components which can be combined to create class averaging solutions -tailored to different datasets. The architecture was designed to be -both modular and encourage experimentation. Lower level components -are aggregated into a high level interface by ``ClassAvgSource`` +tailored to different datasets. The architecture was designed to both +be modular and encourage experimentation. Lower level components are +aggregated into a high level interface by ``ClassAvgSource`` instances. Starting there this document will descend into each contributing component. @@ -50,7 +50,7 @@ required component and assign them here for complete control. """""""""" While that allows for full customization, two helper classes are -provided that provide defaults as a jumping off point. Both of these +provided that supply defaults as a jumping off point. Both of these helper sources only require an input ``Source`` to be instantiated. They can still be fully customized, but they are intended to start with sensible defaults, so users only need to instantiate the specific @@ -88,22 +88,22 @@ mappings etc. in the current ASPIRE release. ``DefaultClassAvgSource`` takes a version string, such as ``11.0`` which will return a specific configuration. This version should allow users to perform a similar -experiment across release as ASPIRE implements improved method. When -a version is not provided, ``DefaultClassAvgSource`` defaults to the -latest version available. +experiment across releases as ASPIRE implements improved methods. +When a version is not provided, ``DefaultClassAvgSource`` defaults to +the latest version available. Classifiers *********** Classifiers take an image ``Source`` and attempts to classify into -``classes`` that identify images have similar viewing angles up to +``classes`` that identify images with similar viewing angles up to reflection. All ``Class2D`` instances are expected to implement a ``classify`` method which returns ``(classes, reflections, distances)``. The three returned variables are expected to be 2D Numpy arrays in a neighbor network format having shape ``(n_classes, n_nbors)``. So to retrieve the input source indices for the first -classes' neighbors, we would want ``classes[0,:]``. The first class +class's neighbors, we would want ``classes[0,:]``. The first class index should be the base image used for classification. So we would expect ``classes[0,0]`` to be ``input_src.images[0]``. @@ -127,7 +127,7 @@ Class Selectors *************** Class Selectors consume the output of ``Class2D`` and attempt to order -and/or filter classes down to a selection. Selection the "best" +and/or filter classes down to a selection. Selecting the "best" classes in CryoEM problems is still an area of active research. Some common methods are provided, along with an extensible base interface. @@ -139,7 +139,7 @@ Local Class Selectors For "Local" class selection, we will attempt to use only the information returned from ``Class2D``. In the case of ``RIRClass2D`` -this would primarily be network of ``distances`` as measured in the +this would primarily be a network of ``distances`` as measured in the compressed feature space. This approach has two main advantages. First, we already have this @@ -273,7 +273,7 @@ this time for grid weights. Averagers ********* -Averagers consume from a ``Source`` and returning averaged images +Averagers consume from a ``Source`` and return averaged images defined by class network arguments ``classes`` and ``reflections``. You may find the terms averaging and stacking used interchangeably in this context, so know that averaging does not always imply *arithmetic diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index 31fbe15d99..c92e9a53a3 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -121,7 +121,7 @@ classification_src = cwf_denoiser.denoise() # Peek, what do the denoised images look like... if interactive: - src.images[:10].show() + classification_src.images[:10].show() # %% # Class Averaging @@ -138,7 +138,7 @@ classification_src, n_nbor=n_nbor, averager_src=src, - num_procs=None, # Automaticaly configure parallel processing + num_procs=None, # Automatically configure parallel processing ) # We'll manually cache `n_classes` worth to speed things up. diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index fa9b93715d..b7106221a2 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -86,7 +86,7 @@ logger.info("Begin Class Averaging") # Now perform classification and averaging for each class. -# Automaticaly configure parallel processing +# Automatically configure parallel processing avgs = DefaultClassAvgSource(src, n_nbor=n_nbor, num_procs=None) # We'll manually cache `n_classes` worth to speed things up. @@ -102,7 +102,7 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=360, max_shift=1) +orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=360, max_shift=0) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index b14a293cc7..9ec50771cd 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -150,10 +150,10 @@ def noise_function(x, y): # Use CWF denoising cwf_denoiser = DenoiserCov2D(src) # Use denoised src for classification - classificiation_src = cwf_denoiser.denoise() + classification_src = cwf_denoiser.denoise() # Peek, what do the denoised images look like... if interactive: - src.images[:10].show() + classification_src.images[:10].show() # %% # Class Averaging @@ -166,7 +166,7 @@ def noise_function(x, y): classificiation_src, n_nbor=n_nbor, averager_src=src, - num_procs=None, # Automaticaly configure parallel processing + num_procs=None, # Automatically configure parallel processing ) # We'll manually cache `n_classes` worth to speed things up. @@ -190,7 +190,7 @@ def noise_function(x, y): indices = avgs_src.selection_indices true_rotations = src.rotations[indices[:n_classes]] -orient_est = CLSyncVoting(avgs, n_theta=36) +orient_est = CLSyncVoting(avgs, n_theta=180) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 9710a92131..ae58c0cf94 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -115,14 +115,13 @@ # ``ClassAvgSource`` and the modular components that are more suitable # to simulations and experimental datasets. -n_classes = 10 - avgs = DebugClassAvgSource( src=src, n_nbor=10, num_procs=1, # Change to "auto" if your machine has many processors ) +# %% # .. note: # ``ClassAvgSource``s are lazily evaluated. # They will generally compute the classifications, selections, @@ -224,7 +223,7 @@ # Alignment Details # ^^^^^^^^^^^^^^^^^ # -# Alignment details are exposed when avaialable from an underlying +# Alignment details are exposed when available from an underlying # ``averager``. In this case, we'll get the estimated alignments for # the ``review_class``. @@ -290,5 +289,5 @@ # Using the defaults requires only passing a source. # After understanding the various components that can be # combined in a ``ClassAvgSource``, they can be customized -# of easily extended. +# or easily extended. avg_src = DefaultClassAvgSource(noisy_src) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index a99722f560..dd4b5b6a9b 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -226,7 +226,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # %% # Mean Squared Error # ------------------ -# ASIPRE has some built-in utility functions for globally aligning the +# ASPIRE has some built-in utility functions for globally aligning the # estimated rotations to the true rotations and computing the mean # squared error. diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 447bbb92d4..6521daf790 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -56,6 +56,12 @@ def quality_scores(self): All `ClassSelector` should assign a quality score array the same length as the selection output. + Function range is currently not limited, but [0,1] is + favorable. Currently there is not an expectation that one + quality scoring system relates to another, or that the score + is a proper metric. Quality scores are only required to be a + self consistent ordering. + For subclasses like TopClassSelector and RandomClassSelector where no quality information is derived, the associated `_quality_scores` should be set to zeros by `_select`. @@ -103,6 +109,8 @@ def _check_selection(self, selection, n_img, len_operator=eq): :param selection: selection indices :param n_img: number of images available + :param len_operator: Operation used for comparison. + Defaults to equality (`eq`). """ # Check length, +1 for zero indexing if not len_operator(len(selection), self.n): @@ -186,7 +194,7 @@ class DistanceClassSelector(ClassSelector): """ def _select(self, classes, reflections, distances): - # Compute per class variance. + # Compute per class distance. # Skips self distance (0). dist_mean = np.mean(distances[:, 1:], axis=1) @@ -337,7 +345,6 @@ def _select(self, classes, reflections, distances): # pop and throw away the worst entry # after updating (pushing to) the heap. _ = heappushpop(self.heap, item) - # else: # Now that we have computed the global quality_scores, # the selection ordering can be applied @@ -353,7 +360,7 @@ def _select(self, classes, reflections, distances): class GreedyClassRepulsion: """ Mixin to overload class selection based on excluding - classes we've alreay seen as neighbors of another class. + classes we've already seen as neighbors of another class. If the classes are well sorted (by some measure of quality), we assume the best representation is the first seen. @@ -369,7 +376,7 @@ def __init__(self, *args, **kwargs): desired result set size. :param exclude_k: Number of neighbors from each class to - exclude. Defaults to + exclude. Defaults to all neighbors. """ # Pop of the parameter unique to GreedyClassRepulsion. self.exclude_k = kwargs.pop("exclude_k", None) @@ -617,11 +624,11 @@ def _weight_function(self, r): class BumpWeightedImageQualityMixin(WeightedImageQualityMixin): """ - ImageQualityMixin to apply a linear ramp to apply a [0,1] bump function. + ImageQualityMixin to apply a [0,1] bump function. """ def _weight_function(self, r): - # bump function (normalized to [0,1] + # bump function (normalized to [0,1]). if r >= 1: return 0 else: diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 0c3fcb0ffb..cc7c473b4b 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -128,7 +128,7 @@ def _class_select(self): ) # Override the initial self.n - # Some selectors will (dramitcally) reduce the space of classes. + # Some selectors will (dramatically) reduce the space of classes. if len(self._selection_indices) != self.n: logger.info( f"After selection process, updating maximum {len(self._selection_indices)} classes from {self.n}." @@ -178,7 +178,7 @@ def _images(self, indices): } # Get heap dict once to avoid traversing heap in a loop. - heapd = self.self.class_selector.heap_id_dict + heap_dict = self.self.class_selector.heap_id_dict # Recursively call `_images`. # `heap_inds` set should be empty in the recursive call, @@ -198,7 +198,7 @@ def _images(self, indices): # Pack images from heap. for k, i in indices_from_heap.items(): # map the image index to heap item location - heap_loc = heapd[k] + heap_loc = heap_dict[k] im[i] = self.class_selector.heap[heap_loc].image else: diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index dd010dd3e7..71775f19ab 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -151,7 +151,7 @@ class SigmaRejectionImageStacker(ImageStacker): procedure, but the location of the clip is determined by the peak intensity instead of standard deviation. - Note, in both cases, user's are responsible for ensuring methods + Note, in both cases, users are responsible for ensuring methods are called on reasonable data (in FW* cases we should be probably be using intensities). No corrections or pedestals are incorporated at this time, but could easily be added in the From 62ca4e9d9c8078a1d50c03046b3e77b873e8fa76 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 14:59:50 -0400 Subject: [PATCH 259/424] Remove redundant norms --- src/aspire/classification/rir_class2d.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 6d369c6477..88a38fddd6 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -317,17 +317,19 @@ def _legacy_nn_classification(self, coeff_b, coeff_b_r): classes = np.zeros((n_im, n_nbor), dtype=int) distances = np.zeros((n_im, n_nbor), dtype=self.dtype) - norm_concat_coeff = np.linalg.norm(concat_coeff) + for i in trange(num_batches): start = i * self.batch_size finish = min((i + 1) * self.batch_size, n_im) batch = np.conjugate(coeff_b[:, start:finish]) - corr = np.real(np.dot(batch.T, concat_coeff)) / ( - np.linalg.norm(batch) * norm_concat_coeff - ) + corr = np.real(np.dot(batch.T, concat_coeff)) + assert np.all( - np.abs(corr) <= 1 + np.abs(corr) <= 1.01 # Allow some numerical wiggle ), f"Corr out of [-1,1] bounds {np.min(corr)} {np.max(corr)}." + # Clamp + corr = np.maximum(corr, -1) + corr = np.minimum(corr, 1) # Note legacy did not include the original image? # classes[start:finish] = np.argsort(-corr, axis=1)[:, 1 : n_nbor + 1] From f36256e2758337bf0ac09cfb75abaf6ff1e5b201 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 15:17:11 -0400 Subject: [PATCH 260/424] still can't spell --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 9ec50771cd..becc0fbd01 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -163,7 +163,7 @@ def noise_function(x, y): # This also demonstrates the potential to use a different source for classification and averaging. avgs_src = DefaultClassAvgSource( - classificiation_src, + classification_src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automatically configure parallel processing From 053f861a80541e369c6e292ea18794997a004dda Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 16:37:13 -0400 Subject: [PATCH 261/424] Rename ContrastClassSelector NeighborVarianceClassSelector --- docs/source/class_source.rst | 2 +- src/aspire/classification/__init__.py | 2 +- src/aspire/classification/class_selection.py | 9 +++++---- tests/test_class_src.py | 6 +++--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 3ec305ac87..9471fcb51b 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -156,7 +156,7 @@ can reduce pipeline run times by an order of magnitude. } ClassSelector <|-- TopClassSelector ClassSelector <|-- RandomClassSelector - ClassSelector <|-- ContrastClassSelector + ClassSelector <|-- NeighborVarianceClassSelector ClassSelector <|-- DistanceClassSelector ClassSelector o-- GreedyClassRepulsion diff --git a/src/aspire/classification/__init__.py b/src/aspire/classification/__init__.py index 66e09553f0..dfb56f3f5e 100644 --- a/src/aspire/classification/__init__.py +++ b/src/aspire/classification/__init__.py @@ -14,12 +14,12 @@ BumpWeightedContrastImageQualityFunction, BumpWeightedImageQualityMixin, ClassSelector, - ContrastClassSelector, ContrastImageQualityFunction, ContrastWithRepulsionClassSelector, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, + NeighborVarianceClassSelector, RampWeightedContrastImageQualityFunction, RampWeightedImageQualityMixin, RandomClassSelector, diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 6521daf790..fc51b04ec5 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -160,10 +160,9 @@ def _select(self, classes, reflections, distances): return rng.choice(self.n, size=self.n, replace=False) -class ContrastClassSelector(ClassSelector): +class NeighborVarianceClassSelector(ClassSelector): """ - Selects top classes based on highest contrast, - as estimated by variances of `distances`. + Selects classes based on variances of `distances`. Note that `distances` is the Nearest Neighbors distances, and in the case of RIR this is a small rotationally invariant feature @@ -426,7 +425,9 @@ def _check_selection(self, selection, n_img): return super()._check_selection(selection, n_img, len_operator=le) -class ContrastWithRepulsionClassSelector(GreedyClassRepulsion, ContrastClassSelector): +class ContrastWithRepulsionClassSelector( + GreedyClassRepulsion, NeighborVarianceClassSelector +): """ Selects top classes based on highest contrast with GreedyClassRepulsion. """ diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 8162256ba0..bac1c3bae8 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -11,12 +11,12 @@ BandedSNRImageQualityFunction, BFRAverager2D, BumpWeightedContrastImageQualityFunction, - ContrastClassSelector, ContrastImageQualityFunction, ContrastWithRepulsionClassSelector, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, + NeighborVarianceClassSelector, RampWeightedContrastImageQualityFunction, RandomClassSelector, RIRClass2D, @@ -181,7 +181,7 @@ def cls_fixture(class_sim_fixture): # These are selectors that do not need to pass over all the global set # of aligned and stacked class averages. ONLINE_SELECTORS = [ - ContrastClassSelector, + NeighborVarianceClassSelector, ContrastWithRepulsionClassSelector, DistanceClassSelector, RandomClassSelector, @@ -258,7 +258,7 @@ def test_contrast_selector(dtype): ref_scores = V[ref_class_ids] # Compute using class under test. - selector = ContrastClassSelector() + selector = NeighborVarianceClassSelector() selection = selector.select(classes, reflections, distances) # Compare indices and scores. From 6f439c3f929856dad5fb4d665039354d72ed6d0e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 16:43:14 -0400 Subject: [PATCH 262/424] Rename NeighborVarianceWithRepulsionClassSelector --- docs/source/class_source.rst | 2 +- src/aspire/classification/__init__.py | 2 +- src/aspire/classification/class_selection.py | 2 +- src/aspire/denoising/class_avg.py | 8 ++++---- tests/test_class_src.py | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 9471fcb51b..dc607a6458 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -72,7 +72,7 @@ components they wish to configure. version="11.0" src: ImageSource classifier: RIRClass2D - class_selector: ContrastWithRepulsionClassSelector + class_selector: NeighborVarianceWithRepulsionClassSelector averager: BFSRAverager2D +images() } diff --git a/src/aspire/classification/__init__.py b/src/aspire/classification/__init__.py index dfb56f3f5e..eeef460e8b 100644 --- a/src/aspire/classification/__init__.py +++ b/src/aspire/classification/__init__.py @@ -15,11 +15,11 @@ BumpWeightedImageQualityMixin, ClassSelector, ContrastImageQualityFunction, - ContrastWithRepulsionClassSelector, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, NeighborVarianceClassSelector, + NeighborVarianceWithRepulsionClassSelector, RampWeightedContrastImageQualityFunction, RampWeightedImageQualityMixin, RandomClassSelector, diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index fc51b04ec5..a416371473 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -425,7 +425,7 @@ def _check_selection(self, selection, n_img): return super()._check_selection(selection, n_img, len_operator=le) -class ContrastWithRepulsionClassSelector( +class NeighborVarianceWithRepulsionClassSelector( GreedyClassRepulsion, NeighborVarianceClassSelector ): """ diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index cc7c473b4b..f97b82831b 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -9,7 +9,7 @@ BFSRAverager2D, Class2D, ClassSelector, - ContrastWithRepulsionClassSelector, + NeighborVarianceWithRepulsionClassSelector, RIRClass2D, TopClassSelector, ) @@ -310,7 +310,7 @@ def DefaultClassAvgSource( Default `None` creates `RIRClass2D`. See code for parameter details. :param class_selector: `ClassSelector` instance. - Default `None` creates `ContrastWithRepulsionClassSelector`. + Default `None` creates `NeighborVarianceWithRepulsionClassSelector`. :param averager: `Averager2D` instance. Default `None` ceates `BFSRAverager2D` instance. See code for parameter details. @@ -377,7 +377,7 @@ def __init__( Default `None` creates `RIRClass2D`. See code for parameter details. :param class_selector: `ClassSelector` instance. - Default `None` creates `ContrastWithRepulsionClassSelector`. + Default `None` creates `NeighborVarianceWithRepulsionClassSelector`. :param averager: `Averager2D` instance. Default `None` ceates `BFSRAverager2D` instance. See code for parameter details. @@ -419,7 +419,7 @@ def __init__( ) if class_selector is None: - class_selector = ContrastWithRepulsionClassSelector() + class_selector = NeighborVarianceWithRepulsionClassSelector() super().__init__( src=src, diff --git a/tests/test_class_src.py b/tests/test_class_src.py index bac1c3bae8..0c12f5d0b9 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -12,11 +12,11 @@ BFRAverager2D, BumpWeightedContrastImageQualityFunction, ContrastImageQualityFunction, - ContrastWithRepulsionClassSelector, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, NeighborVarianceClassSelector, + NeighborVarianceWithRepulsionClassSelector, RampWeightedContrastImageQualityFunction, RandomClassSelector, RIRClass2D, @@ -182,7 +182,7 @@ def cls_fixture(class_sim_fixture): # of aligned and stacked class averages. ONLINE_SELECTORS = [ NeighborVarianceClassSelector, - ContrastWithRepulsionClassSelector, + NeighborVarianceWithRepulsionClassSelector, DistanceClassSelector, RandomClassSelector, TopClassSelector, From eac67ed8e8c64b1ff9badde61bba83210110c143 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 22:40:47 -0400 Subject: [PATCH 263/424] Flip ordering after changing measure from dot towards distance --- src/aspire/classification/class_selection.py | 10 +++++----- tests/test_class_src.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index a416371473..d2836f751b 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -173,8 +173,8 @@ def _select(self, classes, reflections, distances): # Compute per class variance dist_var = np.var(distances, axis=1) - # Compute the ordering, descending - sorted_class_inds = np.argsort(dist_var)[::-1] + # Compute the ordering, ascending + sorted_class_inds = np.argsort(dist_var) # Store the sorted quality scores (maps to selection output). self._quality_scores = dist_var[sorted_class_inds] @@ -197,8 +197,8 @@ def _select(self, classes, reflections, distances): # Skips self distance (0). dist_mean = np.mean(distances[:, 1:], axis=1) - # Compute the ordering, descending - sorted_class_inds = np.argsort(dist_mean)[::-1] + # Compute the ordering, ascending + sorted_class_inds = np.argsort(dist_mean) self._quality_scores = dist_mean[sorted_class_inds] # Return indices @@ -346,7 +346,7 @@ def _select(self, classes, reflections, distances): _ = heappushpop(self.heap, item) # Now that we have computed the global quality_scores, - # the selection ordering can be applied + # the selection ordering can be applied, descending sorted_class_inds = np.argsort(self._quality_scores)[::-1] self._quality_scores = self._quality_scores[sorted_class_inds] return sorted_class_inds diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 0c12f5d0b9..8f6a2ac4a9 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -254,7 +254,7 @@ def test_contrast_selector(dtype): # Compute reference manually. V = distances.var(axis=1) - ref_class_ids = np.argsort(V)[::-1] + ref_class_ids = np.argsort(V) ref_scores = V[ref_class_ids] # Compute using class under test. From cb648d1eae2fcad28a3caaa423bd781881078981 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Mar 2023 09:34:58 -0400 Subject: [PATCH 264/424] overzealous asnumpy --- tests/test_FFBbasis2D.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_FFBbasis2D.py b/tests/test_FFBbasis2D.py index 8fda4002d6..05830176f2 100644 --- a/tests/test_FFBbasis2D.py +++ b/tests/test_FFBbasis2D.py @@ -122,7 +122,7 @@ def testRotate(self, basis): assert np.allclose(y1[0], y2[0], atol=1e-5) # 2*pi Identity - assert np.allclose(x1.asnumpy()[0], y3[0], atol=1e-5) + assert np.allclose(x1[0], y3[0], atol=1e-5) # Refl (flipped using flipud) assert np.allclose(np.flipud(x1.asnumpy()[0]), y4[0], atol=1e-5) From 603f8d04329c936b0ecc18fba7d5cd8254a4a4d1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Mar 2023 11:59:33 -0400 Subject: [PATCH 265/424] Rename RemappedSource to IndexedSource --- src/aspire/source/__init__.py | 2 +- src/aspire/source/image.py | 6 +++--- tests/test_remapped_source.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/aspire/source/__init__.py b/src/aspire/source/__init__.py index 494bba6ccf..55afda4833 100644 --- a/src/aspire/source/__init__.py +++ b/src/aspire/source/__init__.py @@ -1,7 +1,7 @@ import logging from aspire.source.coordinates import BoxesCoordinateSource, CentersCoordinateSource -from aspire.source.image import ArrayImageSource, ImageSource, RemappedSource +from aspire.source.image import ArrayImageSource, ImageSource, IndexedSource from aspire.source.relion import RelionSource from aspire.source.simulation import Simulation diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 01881503ef..cf5c5561a8 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -167,7 +167,7 @@ def __getitem__(self, indices): Check `indices` and return slice of current Source as a new Source. - Internally uses `RemappedSource`. + Internally uses `IndexedSource`. :param indices: Requested indices as a Python slice object, 1-D NumPy array, list, or a single integer. Slices default @@ -176,7 +176,7 @@ def __getitem__(self, indices): :return: Source composed of the images and metadata at `indices`. """ - return RemappedSource(self, indices) + return IndexedSource(self, indices) @property def n_ctf_filters(self): @@ -831,7 +831,7 @@ def save_images( im.save(mrcs_filepath, overwrite=overwrite) -class RemappedSource(ImageSource): +class IndexedSource(ImageSource): """ Map into another into ImageSource. """ diff --git a/tests/test_remapped_source.py b/tests/test_remapped_source.py index ac7ee7910f..75b029ee3b 100644 --- a/tests/test_remapped_source.py +++ b/tests/test_remapped_source.py @@ -31,7 +31,7 @@ def test_remapping(sim_fixture): def test_repr(sim_fixture): sim, sim2 = sim_fixture - logger.debug(f"repr(RemappedSource): {repr(sim2)}") + logger.debug(f"repr(IndexedSource): {repr(sim2)}") # Check `sim` is mentioned in the repr assert type(sim).__name__ in repr(sim2) From a8c54881cddf6c1019c1d297569698d426fa160a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Mar 2023 12:03:24 -0400 Subject: [PATCH 266/424] Update and renamed IndexedSource unit test file --- tests/{test_remapped_source.py => test_indexed_source.py} | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) rename tests/{test_remapped_source.py => test_indexed_source.py} (78%) diff --git a/tests/test_remapped_source.py b/tests/test_indexed_source.py similarity index 78% rename from tests/test_remapped_source.py rename to tests/test_indexed_source.py index 75b029ee3b..30a23ee16b 100644 --- a/tests/test_remapped_source.py +++ b/tests/test_indexed_source.py @@ -21,9 +21,13 @@ def sim_fixture(): def test_remapping(sim_fixture): sim, sim2 = sim_fixture - # Check images are served correctly. + # Check images are served correctly, using internal index. assert np.allclose(sim.images[sim2.index_map].asnumpy(), sim2.images[:].asnumpy()) + # Check images are served correctly, using known index (evens). + index = list(range(0, sim.n, 2)) + assert np.allclose(sim.images[index].asnumpy(), sim2.images[:].asnumpy()) + # Check meta is served correctly. assert np.all(sim.get_metadata(indices=sim2.index_map) == sim2.get_metadata()) From 64bb8ee3cec2de2c6e77eb69bb34ab4f879c53fb Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 16 Mar 2023 14:57:56 -0400 Subject: [PATCH 267/424] all_pairs return_map kwarg. --- src/aspire/abinitio/commonline_c3_c4.py | 12 ++++++------ src/aspire/utils/misc.py | 15 ++++++++++----- tests/test_orient_symmetric.py | 8 ++++---- tests/test_utils.py | 2 +- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index c95feb52ed..ffca30fc7c 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -171,7 +171,7 @@ def _global_J_sync(self, vijs, viis): # We use the fact that if v_ii and v_ij are of the same handedness, then v_ii @ v_ij = v_ij. # If they are opposite handed then Jv_iiJ @ v_ij = v_ij. We compare each v_ii against all # previously synchronized v_ij to get a consensus on the handedness of v_ii. - _, pairs_to_linear = all_pairs(n_img) + _, pairs_to_linear = all_pairs(n_img, return_map=True) for i in range(n_img): vii = viis[i] vii_J = J_conjugate(vii) @@ -226,7 +226,7 @@ def _estimate_third_rows(self, vijs, viis): V = np.zeros((n_img, n_img, 3, 3), dtype=vijs.dtype) # All pairs (i,j) where i Date: Thu, 16 Mar 2023 16:27:09 -0400 Subject: [PATCH 268/424] Add show() smoke test for ImageSource and Simulation accessors. --- tests/test_image.py | 13 +++++++++++++ tests/test_simulation.py | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/test_image.py b/tests/test_image.py index 1cebafc800..f04316a07c 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,6 +1,8 @@ import logging import os.path +import warnings +import matplotlib import numpy as np import pytest from pytest import raises @@ -281,3 +283,14 @@ def testMultiDimBroadcast(): mdim_ims_np, mdim_ims = get_mdim_images() X = mdim_ims + ims assert np.allclose(X[0], 2 * ims.asnumpy()) + + +def testShow(): + """ + Test show doesn't crash. + """ + # Use non GUI backend. + matplotlib.use("Agg") + warnings.filterwarnings("ignore", "Matplotlib is currently using agg") + im = Image(np.random.random((3, 8, 8))) + im.show() diff --git a/tests/test_simulation.py b/tests/test_simulation.py index a32982d30d..84f565198c 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -1,7 +1,9 @@ import os.path import tempfile +import warnings from unittest import TestCase +import matplotlib import numpy as np from pytest import raises @@ -28,6 +30,24 @@ def testImage(self): """Test we can get an Image from a length 1 Sim.""" _ = self.sim.images[0] + def testImageShow(self): + # Use non GUI backend. + matplotlib.use("Agg") + warnings.filterwarnings("ignore", "Matplotlib is currently using agg") + self.sim.images[:].show() + + def testCleanImagesShow(self): + # Use non GUI backend. + matplotlib.use("Agg") + warnings.filterwarnings("ignore", "Matplotlib is currently using agg") + self.sim.clean_images[:].show() + + def testProjectionsShow(self): + # Use non GUI backend. + matplotlib.use("Agg") + warnings.filterwarnings("ignore", "Matplotlib is currently using agg") + self.sim.projections[:].show() + class SimVolTestCase(TestCase): """Test Simulation with Volume provided.""" From 051c8f3bfdce3a900e19411710a3e0bae584871e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Mar 2023 16:27:38 -0400 Subject: [PATCH 269/424] Add patch for Image.show() --- src/aspire/image/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index 18f3785b95..42ea2a8fed 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -461,7 +461,7 @@ def show(self, columns=5, figsize=(20, 10), colorbar=True): ) plt.figure(figsize=figsize) - for i, im in enumerate(self): + for i, im in enumerate(self.asnumpy()): plt.subplot(self.n_images // columns + 1, columns, i + 1) plt.imshow(im, cmap="gray") if colorbar: From c9f1bdc4d33bff756fd4e75f13dee1accb256fef Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 17 Mar 2023 08:59:07 -0400 Subject: [PATCH 270/424] BIG dtype bug in all_pairs --- src/aspire/utils/misc.py | 4 ++-- tests/test_orient_symmetric.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index ebde4edd77..7abc244167 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -312,10 +312,10 @@ def all_pairs(n, return_map=False): - n x 2 array of pairs (i, j), i4. param_list_cn = [ (44, 5, np.float32), - pytest.param(44, 5, np.float32, marks=pytest.mark.expensive), pytest.param(45, 6, np.float64, marks=pytest.mark.expensive), pytest.param(44, 7, np.float32, marks=pytest.mark.expensive), pytest.param(44, 8, np.float32, marks=pytest.mark.expensive), From 1ae3546637df19bc15485ad0f62e551a818297cf Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Mar 2023 09:12:32 -0400 Subject: [PATCH 271/424] add decorator and context manager for matplotlib no gui calls --- tests/test_image.py | 8 +++----- tests/test_simulation.py | 16 +++++----------- tests/test_utils.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/tests/test_image.py b/tests/test_image.py index f04316a07c..9d80f1ff3b 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,8 +1,6 @@ import logging import os.path -import warnings -import matplotlib import numpy as np import pytest from pytest import raises @@ -11,6 +9,8 @@ from aspire.image import Image from aspire.utils import powerset, utest_tolerance +from .test_utils import matplotlib_dry_run + DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") logger = logging.getLogger(__name__) @@ -285,12 +285,10 @@ def testMultiDimBroadcast(): assert np.allclose(X[0], 2 * ims.asnumpy()) +@matplotlib_dry_run def testShow(): """ Test show doesn't crash. """ - # Use non GUI backend. - matplotlib.use("Agg") - warnings.filterwarnings("ignore", "Matplotlib is currently using agg") im = Image(np.random.random((3, 8, 8))) im.show() diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 84f565198c..3e1df49549 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -1,9 +1,7 @@ import os.path import tempfile -import warnings from unittest import TestCase -import matplotlib import numpy as np from pytest import raises @@ -14,6 +12,8 @@ from aspire.utils.types import utest_tolerance from aspire.volume import LegacyVolume, Volume +from .test_utils import matplotlib_dry_run + DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") @@ -30,22 +30,16 @@ def testImage(self): """Test we can get an Image from a length 1 Sim.""" _ = self.sim.images[0] + @matplotlib_dry_run def testImageShow(self): - # Use non GUI backend. - matplotlib.use("Agg") - warnings.filterwarnings("ignore", "Matplotlib is currently using agg") self.sim.images[:].show() + @matplotlib_dry_run def testCleanImagesShow(self): - # Use non GUI backend. - matplotlib.use("Agg") - warnings.filterwarnings("ignore", "Matplotlib is currently using agg") self.sim.clean_images[:].show() + @matplotlib_dry_run def testProjectionsShow(self): - # Use non GUI backend. - matplotlib.use("Agg") - warnings.filterwarnings("ignore", "Matplotlib is currently using agg") self.sim.projections[:].show() diff --git a/tests/test_utils.py b/tests/test_utils.py index b5af2424a6..210875123a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,9 @@ +import warnings +from contextlib import contextmanager from unittest import TestCase from unittest.mock import patch +import matplotlib import numpy as np from parameterized import parameterized from pytest import raises @@ -312,3 +315,38 @@ def testVrtSuggestion(self): def testGetNumMultiProcs(self): self.assertTrue(isinstance(num_procs_suggestion(), int)) + + +@contextmanager +def matplotlib_no_gui(): + """ + Context manager for disabling and restoring matplotlib plots, and + ignoring associated warnings. + """ + + # Save current backend + backend = matplotlib.get_backend() + + # Use non GUI backend. + matplotlib.use("Agg") + + # Save and restore current warnings list. + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", "Matplotlib is currently using agg") + + yield + + # Restore backend + matplotlib.use(backend) + + +def matplotlib_dry_run(func): + """ + Decorator that wraps function in `matplotlib_no_gui` context. + """ + + def wrapper(*args, **kwargs): + with matplotlib_no_gui(): + return func(*args, **kwargs) + + return wrapper From c6afcee2b404ccdbe0424080fb6a326518a9a65f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Mar 2023 09:58:52 -0400 Subject: [PATCH 272/424] add warning and clamp for extremal snr --- src/aspire/source/image.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index d630969b07..b561ded088 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -1018,6 +1018,14 @@ def estimate_snr( # `estimate_signal_var` we yield: signal_variance snr = (signal_power - noise_power) / noise_power + # Check for extremal values. + if snr < 0: + logger.warning( + "For extremely low SNR, estimation accuracy may be impaired." + f" Clamping estimated snr {snr} to 0." + ) + snr = 0 + return snr From d9a761c229d94ec2b9ff8c9f369975ac85ecc22b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Mar 2023 10:42:32 -0400 Subject: [PATCH 273/424] caps acronym --- src/aspire/source/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index b561ded088..ed0434915a 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -1022,7 +1022,7 @@ def estimate_snr( if snr < 0: logger.warning( "For extremely low SNR, estimation accuracy may be impaired." - f" Clamping estimated snr {snr} to 0." + f" Clamping estimated SNR {snr} to 0." ) snr = 0 From b44fe20a07e5f435043fd08515411fc42f0e4cee Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 17 Mar 2023 14:01:51 -0400 Subject: [PATCH 274/424] normalize singular values for comparison to [1, 0, 0]. Code comments. --- tests/test_orient_symmetric.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 7f18dc6cef..675376d2aa 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -95,7 +95,7 @@ def source_orientation_objs(L, n_img, order, dtype): def test_estimate_rotations(L, order, dtype): n_img = 24 if order > 4: - n_img = 8 + n_img = 8 # This is to save on test time. src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Estimate rotations. @@ -212,7 +212,7 @@ def test_relative_viewing_directions(L, order, dtype): # volume with C3 or C4 symmetry. n_img = 24 if order > 4: - n_img = 8 + n_img = 8 # This is to save on test time. src, cl_symm = source_orientation_objs(L, n_img, order, dtype) # Calculate ground truth relative viewing directions, viis and vijs. @@ -273,19 +273,27 @@ def test_relative_viewing_directions(L, order, dtype): angular_dist_vijs = np.mean(min_theta_vij) angular_dist_viis = np.mean(min_theta_vii) - # Check that estimates are indeed approximately rank 1. + # Check that estimates are indeed approximately rank-1. # ie. check that the svd is close to [1, 0, 0]. + sii = ( + sii / np.linalg.norm(sii, axis=1)[..., np.newaxis] + ) # Normalize for comparison to [1, 0, 0] + sij = ( + sij / np.linalg.norm(sij, axis=1)[..., np.newaxis] + ) # Normalize for comparison to [1, 0, 0] error_ij = np.linalg.norm(np.array([1, 0, 0], dtype=dtype) - sij, axis=1) error_ii = np.linalg.norm(np.array([1, 0, 0], dtype=dtype) - sii, axis=1) - max_tol_ii = 1e-5 - mean_tol_ii = 1e-5 - if order > 4: - max_tol_ii = 8e-2 - mean_tol_ii = 2e-2 - assert np.max(error_ij) < 4e-1 - assert np.max(error_ii) < max_tol_ii - assert np.mean(error_ij) < 3e-3 - assert np.mean(error_ii) < mean_tol_ii + max_tol_ij = 1e-7 + mean_tol_ij = 1e-7 + # For order < 5, the method for estimating vijs leads to estimates + # which do not as tightly approximate rank-1. + if order < 5: + max_tol_ij = 4e-1 + mean_tol_ij = 3e-3 + assert np.max(error_ij) < max_tol_ij + assert np.max(error_ii) < 1e-6 + assert np.mean(error_ij) < mean_tol_ij + assert np.mean(error_ii) < 1e-7 # Check that the mean angular difference is within 2 degrees. angle_tol = 2 * np.pi / 180 From 1b3022b199d4a7cf09bcf59aaabbba978d441b5c Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 17 Mar 2023 14:25:44 -0400 Subject: [PATCH 275/424] symmetry check now checks that n > 4. --- src/aspire/abinitio/commonline_c3_c4.py | 2 +- src/aspire/abinitio/commonline_cn.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index ffca30fc7c..85b65ad298 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -95,7 +95,7 @@ def _check_symmetry(self, symmetry): def estimate_rotations(self): """ - Estimate rotation matrices for molecules with Cn symmetry. + Estimate rotation matrices for molecules with Cn symmetry, n > 2. :return: Array of rotation matrices, size n_imgx3x3. """ diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 1adee0642c..493ce59779 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -72,11 +72,13 @@ def _check_symmetry(self, symmetry): ) else: symmetry = symmetry.upper() - if not symmetry[0] == "C": + _sym_type = symmetry[0] + _order = int(symmetry[1:]) + if not (_sym_type == "C" and _order > 4): raise NotImplementedError( - f"Only Cn symmetry supported. {symmetry} was supplied." + f"Only Cn symmetry, n > 4, supported. {symmetry} was supplied." ) - self.order = int(symmetry[1:]) + self.order = _order def _estimate_relative_viewing_directions(self): n_img = self.n_img From f461740ea39720178feb901b25f523446ef9f834 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 17 Mar 2023 14:33:07 -0400 Subject: [PATCH 276/424] generate_cand_rots --> generate_candidate_rots --- src/aspire/abinitio/commonline_cn.py | 4 ++-- tests/test_orient_symmetric.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 493ce59779..0b1fd5c310 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -89,7 +89,7 @@ def _estimate_relative_viewing_directions(self): # Generate candidate rotation matrices and the common-line and # self-common-line indices induced by those rotations. - Ris_tilde, R_theta_ijs = self.generate_cand_rots( + Ris_tilde, R_theta_ijs = self.generate_candidate_rots( self.n_points_sphere, self.equator_threshold, self.order, @@ -299,7 +299,7 @@ def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): return cij_inds @staticmethod - def generate_cand_rots(n, equator_threshold, order, degree_res, seed): + def generate_candidate_rots(n, equator_threshold, order, degree_res, seed): """ Generate random rotations that exclude rotations inducing equator images for use as candidates in the CLSymmetryCn algorithm. diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 675376d2aa..a474e6736c 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -55,7 +55,7 @@ def source_orientation_objs(L, n_img, order, dtype): # We artificially exclude equator images from the simulation as they will be # incorrectly identified by the CL method. We keep images slightly further away # from being equator images than the 10 degree default threshold used in the CL method. - rotations, _ = CLSymmetryCn.generate_cand_rots( + rotations, _ = CLSymmetryCn.generate_candidate_rots( n=n_img, equator_threshold=15, order=order, From 74cb4c2cefc67cad6e590662bf1a202cbf3af6fb Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 15 Mar 2023 07:05:22 -0400 Subject: [PATCH 277/424] Initial Intro tutorial restructuring --- docs/source/conf.py | 1 + gallery/tutorials/aspire_introduction.py | 597 ++++++++++++++++++++++ gallery/tutorials/lecture_feature_demo.py | 308 ----------- setup.py | 1 + 4 files changed, 599 insertions(+), 308 deletions(-) create mode 100644 gallery/tutorials/aspire_introduction.py delete mode 100644 gallery/tutorials/lecture_feature_demo.py diff --git a/docs/source/conf.py b/docs/source/conf.py index a99fe4ef9d..9aad79aa5b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -39,6 +39,7 @@ "sphinx.ext.autodoc", "sphinx.ext.mathjax", "sphinxcontrib.bibtex", + "sphinxcontrib.mermaid", "sphinx.ext.napoleon", "sphinx_gallery.gen_gallery", ] diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py new file mode 100644 index 0000000000..9ae999f1f8 --- /dev/null +++ b/gallery/tutorials/aspire_introduction.py @@ -0,0 +1,597 @@ +""" +========================== +ASPIRE-Python Introduction +========================== + +In this notebook we will introduce the core API components, then +demonstrate basic usage corresponding to topics from Princeton's +MATH586. +""" + +# %% +# Installation +# ------------ +# +# ASPIRE can generally install on Linux, Mac, and Windows under +# Anaconda Python, by following the instructions in the README. `The +# instructions for developers is the most comprehensive +# `_. +# Windows is provided, but generally Linux and OSX are recommended, +# with Linux being the most diversely tested platform. +# + +# %% +# Princeton Research Computing +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# ASPIRE requires some resources to run, so if you wouldn't run +# typical data science codes on your machine (a netbook for example), +# you may use Tiger/Adroit/Della at Princeton or another cluster. +# After logging into Tiger, ``module load anaconda3/2020.7`` and +# continue to follow the anaconda instructions for developers in the +# link above. Those instructions should create a working environment +# for tinkering with ASPIRE code found in this notebook. + +# %% +# Imports +# ^^^^^^^ +# First we import some typical scientific computing packages. +# Along the way we will import relevant components from ``aspire``. +# Users may also import ``aspire`` once as a top level package. + +import logging +import os + +import matplotlib.pyplot as plt +import numpy as np + +import aspire + + +# %% +# Primitive API Components +# ------------------------ +# The ASPIRE framework is a collection of modules containing interoperable extensible components. +# Underlying the more sophisticated components and algorithms are some core data structures. +# Sophisticated components are designed to interoperate by exchanging, consuming, or producing these basic structures. +# The most common structures encountered when starting out are: + +# %% +# .. list-table:: Core API Components +# :header-rows: 1 +# +# * - Component +# - Description +# * - ``Image`` +# - Utility class for stacks of 2D arrays. +# * - ``Volume`` +# - Utility class for stacks of 3D arrays. +# * - ``Rotations`` +# - Utility class for stacks of 3D rotations. +# * - ``Filter`` +# - Constructs and applied Image filters. +# * - ``Basis`` +# - Basis conversions and operations. +# * - ``Source`` +# - Produces primitive components. ``ImageSource`` produces ``Images``. + + +# %% +# ``Image`` Class +# --------------- +# +# The `Image `_ class +# is a thin wrapper over Numpy arrays for a stack containing 1 or more images (2D data). +# In this notebook we won't be working directly with the ``Image`` class a lot, but it will be one of the fundemental structures behind the scenes. +# A lot of ASPIRE code passes around ``Image`` and ``Volume`` instances. + +from aspire.image import Image + +# Create an ``Image`` instance from random data. +img_data = np.random.random((100,100)) +img = Image(img_data) +logging.info(f"img shape: {img.shape}") # Note this produces a stack of 1. +logging.info(f"str(img): {img}") # Note this produces a stack of 1. + +# Create an Image for a stack of 3 100x100 images. +img_data = np.random.random((3,100,100)) +img = Image(img_data) + +# Most often, ``Image``s will behave like Numpy arrays, but you explicitly access the underlying Numpy array via ``asnumpy()``. +img.asnumpy() + +# Image have a built in ``show()`` method, which works well for peeking at data. +img.show() + +# %% +# .. note:: +# The user is responsible for using ``show`` responsibly. Avoid +# asking for large numbers of images that you would not normally +# plot. Ten or less is reasonable. + +# %% +# More examples using the Image class can be found in: +# +# - :ref:`sphx_glr_auto_tutorials_image_class.py` +# - :ref:`sphx_glr_auto_tutorials_basic_image_array.py` + + +# %% +# ``Volume`` Class +# ---------------- +# +# Like ``Image``, the `Volume `_ class +# is a thin wrapper over Numpy arrays that provides specialized methods for a stack containing 1 or more volumes (3D data). + +from aspire.volume import Volume + +# %% +# Initialize Volume - ``load`` +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# A ``Volume`` may be instantiated with Numpy data similarly to +# ``Image``. Both ``Image`` and ``Volume`` provide ``save`` and +# ``load`` methods which can be used to work with files. For +# ``Volumes`` ``.map`` and ``.mrc`` are currently supported. For +# ``.npy`` Numpy can be used. + +# A low res example file is included in the repo as a sanity check. +# We can instantiate this as an ASPIRE Volume instance using +# ``Volume.load()``. +DATA_DIR = "data" +v = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) + +# More interesting data requires downloading locally. +# v = Volume.load("path/to/EMD-2660/map/emd_2660.map") + +# %% +# Downsample Volume +# ^^^^^^^^^^^^^^^^^ +# Here we downsample the above volume to a desired resolution (32 should be good). +# For the data source I chose to download a real volume density map from `EMDB `_. +# The download was uncompressed in my local directory. The notebook defaults to a small low resolution sample file you may use to sanity check. +# Unfortunately real data can be quite large so we do not ship it with the repo. + +# Downsample the volume to a desired resolution +img_size = 32 +# Volume.downsample() returns a new Volume instance. +# We will use this lower resolution volume later, calling it `v2`. +v2 = v.downsample(img_size) +# L is often used as short hand for image and volume sizes (in pixels/voxels). +L = v2.resolution + +# %% +# Plot Data +# """"""""" +# For quick sanity checking purposes we can view as a contour plot. +# We'll use three orthographic projections, one per axis +fig, axs = plt.subplots(1, 3) +for i in range(3): + axs[i].imshow(np.sum(v2[0], axis=i), cmap="gray") +plt.show() + +# %% +# ``Rotation`` Class +# ------------------ +# While you may bring your own 3x3 matrices or generate manually (say +# from your own Euler angles), ASPIRE has a `Rotation class +# `_ +# which can do this random rotation generation for us. It also has +# some other utility methods, including support for Rodrigues +# rotations (ie, axis-angle). Other ASPIRE components dealing with 3D +# rotations will generally expect instances of ``Rotation``. +# +# A common task in Computational CryoEM is generating random +# projections, which can be achieved by applying random 3D +# rotations. The following code will generate some random rotations, +# and use the ``Volume.project()`` method to return an ``Image`` +# instance representing the stack of projections. We can display +# projection images using the ``Image.show()`` method. + +from aspire.utils import Rotation + +num_rotations = 2 +rots = Rotation.generate_random_rotations(n=num_rotations, seed=12345) + +# %% +# We can access the Numpy array holding the actual stack of 3x3 matrices: +logging.info(rots) +logging.info(rots.matrices) + +# Using the zero-th (and in this case, only) volume, compute projections using the stack of rotations: +projections = v.project(0, rots) +logging.info(projections) + +# %% +# ``project()`` returns an Image instance, so we can call ``show``. +projections.show() + +# %% +# Neat, we've generated random projections of some real data. This +# tutorial go on to show how this can be performed systematically with +# other Computational CryoEM data simulation tasks. + +# The ``filter`` Package +# ---------------------- +# `Filters +# `_ +# are a collection of classes which once configured can be applied to +# ``Images``, typically in an ``ImageSource`` pipeline which will be +# discussed in a later section. + +# %% +# +# .. mermaid:: +# +# classDiagram +# class Filter{ +# +evaluate() +# +fb_mat() +# +scale() +# +evaluate_grid() +# +dual() +# +sign() +# } +# +# Filter o-- DualFilter +# Filter o-- FunctionFilter +# Filter o-- PowerFilter +# Filter o-- LambdaFilter +# Filter o-- MultiplicativeFilter +# Filter o-- ScaledFilter +# Filter o-- ArrayFilter +# Filter o-- ScalarFilter +# Filter o-- ZeroFilter +# Filter o-- IdentityFilter +# Filter o-- CTFFilter +# CTFFilter o-- RadialCTFFilter + +# %% +# ``CTFFilter`` and ``RadialCTFFilter`` the most common filters +# encountered when starting out and are detailed in +# :ref:`sphx_glr_auto_tutorials_ctf.py` The other filters are used +# behind the scenes in components like ``NoiseAdders`` or more +# advanced customized pipelines. + +# %% +# ``Basis`` +# --------- +# What can I say. + +# %% +# The ``source`` Package +# ---------------------- +# +# `aspire.source +# `_ +# package contains a collection of data source interfaces. +# Ostensibly, a ``Source`` is a producer of some primitive type, most +# notably ``Image``. ASPIRE components that consume (process) images +# are designed to accept an ``ImageSource``. +# +# The first reason for this is to normalize the way a wide variety of +# higher level components interface. ``ImageSource`` instances have a +# consistent method ``images`` which must be implemented to serve up +# images dynamically. This supports batch computation among other +# things. ``Source``s also store and serve up metadata like +# `rotations`, `dtype`, and supporting pipelining certain +# transformations. +# +# The second reason is so we can design an experiment using a synthetic ``Simulation`` source or our own provided Numpy arrays via ``ArrayImageSource`` and then later swap out the source for a large experimental data set using something like ``RelionSource``. Experimental datasets can be too large to practically fit or process entirely in memory, and force the use of iteratively-batched approaches. +# +# Generally, the ``source`` package attempts to make most of this opaque to an end user. Ideally we can simply swap one source for another. +# For now we will build up to the creation and application of synthetic data set based on the various manual interactions above. + +# %% +# +# .. mermaid:: +# +# classDiagram +# class ImageSource{ +# +L +# +n +# +dtype +# ... +# +images[] +# +cache() +# +downsample() +# +whiten() +# +phase_flip() +# +invert_conrast() +# +normalize_background() +# +save() +# +save_images() +# ... +# } +# ImageSource o-- ArrayImageSource +# ImageSource o-- Simulation +# ImageSource o-- RelionSource +# ImageSource o-- CoordinateSource +# CoordinateSource o-- BoxesCoordinateSource +# CoordinateSource o-- CentersCoordinateSource + + +# %% +# ``Simulation`` Class +# ^^^^^^^^^^^^^^^^^^^^ +# Generating realistic synthetic data sources is a common task. +# The process of generating then projecting random rotations is integrated into the +# `Simulation `_ class. +# Using ``Simulation``, we can generate arbitrary numbers of projections for use in experiments. +# Then additional features are introduced which allow us to create more realistic data sources. + +from aspire.source import Simulation + +# Total images in our source. +num_imgs = 100 + +# Generate a Simulation instance based on the original volume data. +sim = Simulation(n=num_imgs, vols=v) +# Display the first 10 images +sim.images[:10].show() # Hi Res + +# Repeat for the lower resolution (downsampled) volume v2. +sim = Simulation(n=num_imgs, vols=v2) +sim.images[:10].show() # Lo Res + +# Note both of those simulations have the same rotations +# because they had the same seed by default, +# We recreate ``sim`` with a distinct seed to get different random samples (of rotations). +sim = Simulation(n=num_imgs, vols=v2, seed=42) +sim.images[:10].show() + +# We can also view the rotations used to create these projections +logging.info(sim.rotations) + + +# %% +# The ``noise`` Package +# --------------------- +# The `aspire.noise +# `_ +# package contains several useful classes for generating and +# estimating different types of noise. + +# %% +# ``NoiseAdder``s +# ^^^^^^^^^^^^^^^ +# ``NoiseAdder`` subclasses are used to add common or customized noise +# to ``Simulation`` image generation pipelines. + +# %% +# ``WhiteNoiseAdder`` +# """"""""""""""""""" +# ``WhiteNoiseAdder`` is the most common type of synthetic noise. + +from aspire.noise import WhiteNoiseAdder + +# Get the sample variance +var = np.var(sim.images[:].asnumpy()) +logging.info(f"Sample Variance: {var}") +target_noise_variance = 100.0 * var +logging.info(f"Target Noise Variance: {target_noise_variance}") +# Then create a NoiseAdder based on that variance. +white_noise_adder = WhiteNoiseAdder(target_noise_variance) + +# %% +# We can customize Sources by adding stages to their generation pipeline. +# In this case of a Simulation source, we want to corrupt the projection images with noise. +# +# Internally the ``WhiteNoiseAdder`` creates a ``ScalarFilter`` which is multiplied (convolution) by a Gaussian random sample. +# Similar to before, if you require a different sample, this can be controlled via a ``seed``. + +# Creating the new simulation with this additional noise is easy: +sim = Simulation(n=num_imgs, vols=v2, noise_adder=white_noise_adder) +# These should be rather noisy now ... +sim.images[:10].show() + +# %% +# ``WhiteNoiseEstimator`` +# """"""""""""""""""""""" +# We can estimate the noise across an ``ImageSource``. +# we've generated a simulation with known noise variance. +# Lets see how the estimate compares. +# +# In this case, we know the noise to be white, so we can proceed directly to +# `WhiteNoiseEstimator `_. The noise estimators consume from a ``Source``. +# +# The white noise estimator should log a diagnostic variance value. +# Internally, it also uses the estimation results to build a +# ``Filter`` which can be used in more advanced denoising methods. + +from aspire.noise import WhiteNoiseEstimator + +noise_estimator = WhiteNoiseEstimator(sim) +noise_estimator.estimate() + + +# %% +# A Custom ``FunctionFilter`` +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# +# We will now apply some more interesting noise, using a custom function, and then apply a ``whitening`` process to our data. +# +# Using ``FunctionFilter`` we can create our own custom functions to apply in a pipeline. +# Here we want to apply a custom noise function. We will use a function of two variables for this example. + +from aspire.operators import FunctionFilter +from aspire.noise import CustomNoiseAdder + +def noise_function(x, y): + return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) + +# In python, functions are first class objects. We take advantage of +# that to pass this function around as a variable. The function is evaluated later, internally, during pipeline execution. +custom_noise = CustomNoiseAdder(noise_filter=FunctionFilter(noise_function)) + +# Create yet another Simulation source to tinker with. +sim = Simulation(n=num_imgs, vols=v2, noise_adder=custom_noise) +sim.images[:10].show() + + +# %% +# Noise Whitening +# ^^^^^^^^^^^^^^^ +# We will now combine a more advanced noise estimation technique with an ``ImageSource`` preprocessing method ``whiten``. +# +# First an anisotropic noise estimate is performed. We will use the resulting ``Filter``. + +from aspire.noise import AnisotropicNoiseEstimator + +# Estimate noise. +aiso_noise_estimator = AnisotropicNoiseEstimator(sim) + +#%% +# Applying the ``Simulation.whiten()`` method requires passing the filter corresponding to the estimated noise instance. +# Then we can inspect some of the whitened images. While noise is still present, we can see a dramatic change. + +# Whiten based on the estimated noise +sim.whiten(aiso_noise_estimator.filter) + +# What do the whitened images look like... +sim.images[:10].show() + +# %% +# Common Image Corruptions +# --------------------------- +# ``Simulation`` provides several configurable types of common CryoEM image corruptions. Users should be aware that Amplitude and Offset corruption is enabled by default. + +# %% +# Amplitudes +# """""""""" +# Simulation automatically generates random amplitude variability. +# To disable, set to ``amplitudes=1``. + +# %% +# Offsets +# """"""" +# Simulation automatically generates random offsets +# To disable, set to ``offsets=0``. + +# %% +# Noise +# """"" +# By default, no noise corruption is configured +# To enable, see ``NoiseAdder`` components. + +# %% +# CTF +# """ +# By default, no CTF corruption is configured. +# To enable, we must configure one or more CTFFilter. +# Usually we will create a range of CTFFilters for a variety of +# defocus levels. + +from aspire.operators import RadialCTFFilter + +# Radial CTF Filter params. +defocus_min = 15000 # unit is angstroms +defocus_max = 25000 +defocus_ct = 7 + +# Generate several CTFs. +ctf_filters = [ + RadialCTFFilter(pixel_size=5, defocus=d) + for d in np.linspace(defocus_min, defocus_max, defocus_ct) +] + +# %% +# Combining into a Simulation +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Here we'll combine the parameters above into a new simulation. + +sim = Simulation(n=num_imgs, + vols=v2, + amplitudes=1, + offsets=0, + noise_adder=white_noise_adder, + unique_filters=ctf_filters, + seed=42) + +# Simulation has two unique accessors ``clean_images`` which disables noise, and ``projections`` which are clean uncorrupted projections. Both act like calls to `image` and return show=able ``Image`` instances. + +# %% +# Clean projections. +sim.projections[:5].show() + +# %% +# Images with only CTF applied. +sim.clean_images[:5].show() + +# %% +# The first five corrupted images. +sim.images[:5].show() + + + +# %% +# Real Experimental Data - ``RelionSource`` +# ----------------------------------------- +# +# Now that we have some basics, +# we can try to replace the simulation with a real experimental data source. + +from aspire.source import RelionSource + +src = RelionSource( + "data/sample_relion_data.star", + data_folder="", + pixel_size=5.0, + max_rows=1024, +) + +# Downsample +src.downsample(img_size) + +# Relionsource will auto populate ``CTFFilter`` instances from the STAR file metadata when available. Having these filters allows us to perform a phase flipping correction. +src.phase_flip() + +# Display the experimental data images. +src.images[:10].show() + +# %% +# Pipeline Roadmap +# ---------------- +# lorem ipsum diagramum + +# %% +# .. list-table:: Pipeline Components +# :header-rows: 1 +# +# * - Data Source +# - Preprocessing +# - 2D Denoising +# - Orientation Estimation +# - VolumeEstimator +# * - Simulation +# - Noise Estimation +# - Class Averagring +# - CLSyncVoting +# - MeanVolumeEstimator +# * - RelionSource +# - downsample +# - cov2d (CWF) +# - CLSymmetryC3C4 +# - +# * - CoordinateSource +# - whiten +# - +# - CLSymmetryCn +# - +# * - +# - phase_flip +# - +# - +# - +# * - +# - normalize_backgrond +# - +# - +# - +# * - +# - CTFEstimator +# - +# - +# - + + +# %% +# :ref:`sphx_glr_auto_tutorials_pipeline_demo.py` diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py deleted file mode 100644 index 80f6fa94f2..0000000000 --- a/gallery/tutorials/lecture_feature_demo.py +++ /dev/null @@ -1,308 +0,0 @@ -""" -ASPIRE-Python Introduction -========================== - -In this notebook we will introduce some code from ASPIRE-Python -that corresponds to topics from MATH586. -""" - -# %% -# Imports -# ------- -# First we import some of the usual suspects. In addition, we import some classes from -# the ASPIRE package that we will use throughout this tutorial. - -# %% -# Installation -# ^^^^^^^^^^^^ -# -# Attempt to install ASPIRE on your machine. ASPIRE can generally install on -# Linux, Mac, and Windows -# under Anaconda Python, by following the instructions in the README. -# `The instructions for developers is the most comprehensive -# `_. -# Linux is the most tested platform. -# -# ASPIRE requires some resources to run, so if you wouldn't run typical data -# science codes on your machine (maybe a netbook for example), -# you may use Tiger/Adroit/Della etc if you have access. -# After logging into Tiger, ``module load anaconda3/2020.7`` -# and continue to follow the anaconda instructions for -# developers in the link above. -# Those instructions should create a working environment for tinkering with -# ASPIRE code found in this notebook. - -import logging -import os - -import matplotlib.pyplot as plt -import numpy as np - -from aspire.noise import ( - AnisotropicNoiseEstimator, - CustomNoiseAdder, - WhiteNoiseAdder, - WhiteNoiseEstimator, -) -from aspire.operators import FunctionFilter -from aspire.source import RelionSource, Simulation -from aspire.utils import Rotation -from aspire.volume import Volume - -# %% -# ``Image`` Class -# --------------- -# -# The `Image `_ class -# is a thin wrapper over numpy arrays for a stack containing 1 or more images. -# In this notebook we won't be working directly with the ``Image`` class a lot, but it will be one of the fundemental structures behind the scenes. -# A lot of ASPIRE code passes around ``Image`` and ``Volume`` classes. -# -# Examples of using the Image class can be found in: -# -# - ``gallery/tutorials/basic_image_array.py`` -# -# - ``gallery/tutorials/image_class.py`` - -# %% -# ``Volume`` Class -# ---------------- -# -# Like ``Image``, the `Volume `_ class -# is a thin wrapper over numpy arrays that provides specialized methods for a stack containing 1 or more volumes. -# -# Here we will instantiate a Volume using a numpy array and use it to downsample to a desired resolution (64 should be good). -# For the data source I chose to download a real volume density map from `EMDB `_. -# The download was uncompressed in my local directory. The notebook defaults to a small low resolution sample file you may use to sanity check. -# Unfortunately real data can be quite large so we do not ship it with the repo. - -# %% -# Initialize Volume -# ----------------- - -# A low res example file is included in the repo as a sanity check. -# We can instantiate this as an ASPIRE Volume instance using ``Volume.load()``. -DATA_DIR = "data" -v = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) - -# More interesting data requires downloading locally. -# v = Volume.load("path/to/EMD-2660/map/emd_2660.map") - -# Downsample the volume to a desired resolution -img_size = 64 -# Volume.downsample() returns a new Volume instance. -# We will use this lower resolution volume later, calling it `v2`. -v2 = v.downsample(img_size) -L = v2.resolution - -# %% -# Plot Data -# --------- - -# Alternatively, for quick sanity checking purposes we can view as a contour plot. -# We'll use three orthographic projections, one per axis -fig, axs = plt.subplots(1, 3) -for i in range(3): - axs[i].imshow(np.sum(v2[0], axis=i), cmap="gray") -plt.show() - -# %% -# ``Rotation`` Class - Generating Random Rotations -# ------------------------------------------------ -# -# To get general projections this brings us to generating random rotations which we will apply to our volume. -# -# While you may bring your own 3x3 matrices or generate manually (say from your own Euler angles), -# ASPIRE has a `Rotation class `_ -# which can do this random rotation generation for us. It also has some other utility methods if you would want to compare with something manual. -# -# The following code will generate some random rotations, and use the ``Volume.project()`` method to return an ``Image`` instance representing the stack of projections. -# We can display projection images using the ``Image.show()`` method. - -num_rotations = 2 -rots = Rotation.generate_random_rotations(n=num_rotations, seed=12345) - -# %% -# We can access the numpy array holding the actual stack of 3x3 matrices: -logging.info(rots) -logging.info(rots.matrices) - -# Using the first (and in this case, only) volume, compute projections using the stack of rotations: -projections = v.project(0, rots) -logging.info(projections) - -# %% -# ``project()`` returns an Image instance, so we can call ``show``. - -projections.show() -# Neat, we've generated random projections of some real data. - -# %% -# The ``source`` Package -# ---------------------- -# -# `aspire.source `_ -# package contains a collection of data source interfaces. -# The idea is that we can design an experiment using a synthetic ``Simulation`` source or our own provided array via ``ArrayImageSource``; -# then later swap out the source for a large experimental data set using something like ``RelionSource``. -# -# We do this because the experimental datasets are too large to fit in memory. -# They cannot be provided as a massive large array, and instead require methods to orchestrate batching. -# Depending on the application, they may also require corresponding batched algorithms. -# The ``Source`` classes try to make most of this opaque to an end user. Ideally we can swap one source for another. -# -# For now we will build up to the creation and application of synthetic data set based on the real volume data used previously. - -# %% -# ``Simulation`` Class -# -------------------- -# -# Generating realistic synthetic data sources is a common task. -# The process of generating then projecting random rotations is integrated into the -# `Simulation `_ class. -# Using ``Simulation``, we can generate arbitrary numbers of projections for use in experiments. -# Later we will demonstrate additional features which allow us to create more realistic data sources. - -num_imgs = 100 # Total images in our source. -# Generate a Simulation instance based on the original volume data. -sim = Simulation(L=v.resolution, n=num_imgs, vols=v) -# Display the first 10 images -sim.images[:10].show() # Hi Res - -# Repeat for the lower resolution (downsampled) volume v2. -sim2 = Simulation(L=v2.resolution, n=num_imgs, vols=v2) -sim2.images[:10].show() # Lo Res - -# Note both of those simulations have the same rotations -# because they had the same seed by default, -# We can set our own seed to get a different random samples (of rotations). -sim_seed = Simulation(L=v.resolution, n=num_imgs, vols=v, seed=42) -sim_seed.images[:10].show() - -# We can also view the rotations used to create these projections -# logging.info(sim2.rotations) # Commented due to long output - -# %% -# Simulation with Filters and Noise -# --------------------------------- -# -# Filters -# ^^^^^^^ -# -# `Filters `_ -# are a collection of classes which once configured can be applied to ``Source`` pipelines. -# Common filters we might use are ``ScalarFilter``, ``PowerFilter``, ``FunctionFilter``, and ``CTFFilter``. -# ``CTFFilter`` is detailed in the ``ctf.py`` demo. -# -# Adding to Simulation -# ^^^^^^^^^^^^^^^^^^^^ -# -# We can customize Sources by adding stages to their generation pipeline. -# In this case of a Simulation source, we want to corrupt the projection images with significant noise. -# -# First we create a constant two dimension filter (constant value set to our desired noise variance). -# Then when used in the ``noise_filter``, this scalar will be multiplied by a random sample. -# Similar to before, if you require a different sample, this would be controlled via a ``seed``. - -# Get the sample variance -var = np.var(sim2.images[:].asnumpy()) -logging.info(f"Sample Variance: {var}") -target_noise_variance = 100.0 * var -logging.info(f"Target Noise Variance: {target_noise_variance}") -# Then create a NoiseAdder based on that variance. -white_noise_adder = WhiteNoiseAdder(target_noise_variance) - -# We can create a similar simulation with this additional noise_filter argument: -sim3 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder) -sim3.images[:10].show() -# These should be rather noisy now ... - -# %% -# More Advanced Noise - Whitening -# ------------------------------- -# -# We can estimate the noise across the stack of images -# -# The ``noise`` Package -# ^^^^^^^^^^^^^^^^^^^^^ -# -# The `aspire.noise `_ -# package contains several useful classes for generating and estimating different types of noise. -# -# In this case, we know the noise to be white, so we can proceed directly to -# `WhiteNoiseEstimator `_. The noise estimators consume from a ``Source``. -# -# The white noise estimator should log a diagnostic variance value. How does this compare with the known noise variance above? - -# %% - -# Create another Simulation source to tinker with. -sim_wht = Simulation( - L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder -) - -# Estimate the white noise. -noise_estimator = WhiteNoiseEstimator(sim_wht) -logging.info(noise_estimator.estimate()) - -# %% -# A Custom ``FunctionFilter`` -# --------------------------- -# -# We will now apply some more interesting noise, using a custom function, and then apply a ``whitening`` process to our data. -# -# Using ``FunctionFilter`` we can create our own custom functions to apply in a pipeline. -# Here we want to apply a custom filter as a noise adder. We can use a function of two variables for example. - - -def noise_function(x, y): - return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) - - -# In python, functions are first class objects. -# We take advantage of that to pass this function around as a variable. -# It will be evaluated later... -custom_noise = CustomNoiseAdder(noise_filter=FunctionFilter(noise_function)) - -# Create yet another Simulation source to tinker with. -sim4 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=custom_noise) -sim4.images[:10].show() - -# %% -# Noise Whitening -# --------------- -# -# Applying the ``Simulation.whiten()`` method just requires passing the filter corresponding to the estimated noise instance. -# Then we can inspect some of the whitened images. While noise is still present, we can see a dramatic change. - -# Estimate noise. -aiso_noise_estimator = AnisotropicNoiseEstimator(sim4) - -# Whiten based on the estimated noise -sim4.whiten(aiso_noise_estimator.filter) - -# What do the whitened images look like... -sim4.images[:10].show() - -# %% -# Real Experimental Data - ``RelionSource`` -# ----------------------------------------- -# -# Now that we have some basics, -# we can try to replace the simulation with a real experimental data source. -# -# Lets attempt the same CL experiment, but with a ``RelionSource``. - -src = RelionSource( - "data/sample_relion_data.star", - data_folder="", - pixel_size=5.0, - max_rows=1024, -) - -src.downsample(img_size) - -src.images[:10].show() - -# %% -# We have hit the point where we need denoising algorithms to perform orientation estimation as demonstrated in our ``pipeline_demo.py``. diff --git a/setup.py b/setup.py index f4c0a45f57..fdf17c1e61 100644 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ def read(fname): "pytest-xdist", "requests", "sphinxcontrib-bibtex", + "sphinxcontrib-mermaid", "sphinx-gallery", "sphinx-rtd-theme>=0.4.2", "snakeviz", From ae7e8b9e9c9e757bcf0f4bc74087b8d2bb9bf7bd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Mar 2023 13:54:33 -0400 Subject: [PATCH 278/424] Breakup tutorials into intro and tutorials. --- gallery/tutorials/README.rst | 9 ++++++++- gallery/tutorials/aspire_introduction.py | 1 - gallery/tutorials/pipeline_demo.py | 1 - gallery/tutorials/tutorials/README.rst | 6 ++++++ gallery/tutorials/{ => tutorials}/apple_picker.py | 2 +- .../tutorials/{ => tutorials}/basic_image_array.py | 0 .../tutorials/{ => tutorials}/class_averaging.py | 0 .../tutorials/{ => tutorials}/cov2d_simulation.py | 2 +- .../tutorials/{ => tutorials}/cov3d_simulation.py | 0 gallery/tutorials/{ => tutorials}/ctf.py | 13 +++++++------ .../generating_volume_projections.py | 2 +- gallery/tutorials/{ => tutorials}/image_class.py | 2 +- .../tutorials/{ => tutorials}/image_expansion.py | 2 +- .../{ => tutorials}/orient3d_simulation.py | 2 +- .../{ => tutorials}/preprocess_imgs_sim.py | 2 +- gallery/tutorials/{ => tutorials}/starfile.py | 3 ++- 16 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 gallery/tutorials/tutorials/README.rst rename gallery/tutorials/{ => tutorials}/apple_picker.py (96%) rename gallery/tutorials/{ => tutorials}/basic_image_array.py (100%) rename gallery/tutorials/{ => tutorials}/class_averaging.py (100%) rename gallery/tutorials/{ => tutorials}/cov2d_simulation.py (99%) rename gallery/tutorials/{ => tutorials}/cov3d_simulation.py (100%) rename gallery/tutorials/{ => tutorials}/ctf.py (95%) rename gallery/tutorials/{ => tutorials}/generating_volume_projections.py (98%) rename gallery/tutorials/{ => tutorials}/image_class.py (98%) rename gallery/tutorials/{ => tutorials}/image_expansion.py (99%) rename gallery/tutorials/{ => tutorials}/orient3d_simulation.py (99%) rename gallery/tutorials/{ => tutorials}/preprocess_imgs_sim.py (99%) rename gallery/tutorials/{ => tutorials}/starfile.py (96%) diff --git a/gallery/tutorials/README.rst b/gallery/tutorials/README.rst index 0cf55f6a84..a8613650e2 100644 --- a/gallery/tutorials/README.rst +++ b/gallery/tutorials/README.rst @@ -1,5 +1,12 @@ Tutorials =================== -This gallery contains demonstrations of various image processing tools from the ASPIRE package, implemented using simulated data. +This gallery contains demonstrations of various image processing tools +from the ASPIRE package, implemented using simulated data. +API Introduction +---------------- + +Newcomers should start here to get an overview of ASPIRE's primitive +structures and interfaces, building up to a short Simulated Abinitio +Pipeline. diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 9ae999f1f8..224273f02d 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -1,5 +1,4 @@ """ -========================== ASPIRE-Python Introduction ========================== diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 207a2292bb..bfa0c8747b 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -1,5 +1,4 @@ """ -================================ Ab-initio Pipeline Demonstration ================================ diff --git a/gallery/tutorials/tutorials/README.rst b/gallery/tutorials/tutorials/README.rst new file mode 100644 index 0000000000..abcdb3203c --- /dev/null +++ b/gallery/tutorials/tutorials/README.rst @@ -0,0 +1,6 @@ +Component Examples +------------------ + +Selected components demonstrate some common tasks while discussing +usage and configuration. + diff --git a/gallery/tutorials/apple_picker.py b/gallery/tutorials/tutorials/apple_picker.py similarity index 96% rename from gallery/tutorials/apple_picker.py rename to gallery/tutorials/tutorials/apple_picker.py index a2ba336f22..c3ba4803c3 100644 --- a/gallery/tutorials/apple_picker.py +++ b/gallery/tutorials/tutorials/apple_picker.py @@ -20,7 +20,7 @@ # # Here we demonstrate reading in and plotting a raw micrograph. -filename = "data/falcon_2012_06_12-14_33_35_0.mrc" +filename = "../data/falcon_2012_06_12-14_33_35_0.mrc" with mrcfile.open(filename, mode="r") as mrc: micro_img = mrc.data diff --git a/gallery/tutorials/basic_image_array.py b/gallery/tutorials/tutorials/basic_image_array.py similarity index 100% rename from gallery/tutorials/basic_image_array.py rename to gallery/tutorials/tutorials/basic_image_array.py diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/tutorials/class_averaging.py similarity index 100% rename from gallery/tutorials/class_averaging.py rename to gallery/tutorials/tutorials/class_averaging.py diff --git a/gallery/tutorials/cov2d_simulation.py b/gallery/tutorials/tutorials/cov2d_simulation.py similarity index 99% rename from gallery/tutorials/cov2d_simulation.py rename to gallery/tutorials/tutorials/cov2d_simulation.py index aac77f0585..3d9a395c1a 100644 --- a/gallery/tutorials/cov2d_simulation.py +++ b/gallery/tutorials/tutorials/cov2d_simulation.py @@ -24,7 +24,7 @@ logger = logging.getLogger(__name__) -DATA_DIR = "data" +DATA_DIR = "../data" logger.info( diff --git a/gallery/tutorials/cov3d_simulation.py b/gallery/tutorials/tutorials/cov3d_simulation.py similarity index 100% rename from gallery/tutorials/cov3d_simulation.py rename to gallery/tutorials/tutorials/cov3d_simulation.py diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/tutorials/ctf.py similarity index 95% rename from gallery/tutorials/ctf.py rename to gallery/tutorials/tutorials/ctf.py index 576927066a..073ce65691 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/tutorials/ctf.py @@ -7,7 +7,7 @@ lecture notes from MATH586 at Princeton. """ -# sphinx_gallery_thumbnail_path = '../../gallery/tutorials/data/ctffind4/diagnostic_output.png' +# sphinx_gallery_thumbnail_path = '../../../gallery/tutorials/data/ctffind4/diagnostic_output.png' # Get some common imports out of the way. import os @@ -370,9 +370,10 @@ def generate_example_image(L, noise_variance=0.1): # Load an example Relion starfile src = RelionSource( - "data/sample_relion_data.star", + "../data/sample_relion_data.star", pixel_size=1.338, max_rows=10000, + data_folder='../data', ) src.downsample(64) # easier to visualize src.images[:3].show() @@ -397,27 +398,27 @@ def generate_example_image(L, noise_variance=0.1): # %% # Interactive session # -# .. literalinclude:: ../../../gallery/tutorials/data/ctffind4/input.txt +# .. literalinclude:: ../../../../gallery/tutorials/data/ctffind4/input.txt # # # %% # Diagnostic Output Text # -# .. literalinclude:: ../../../gallery/tutorials/data/ctffind4/diagnostic_output.txt +# .. literalinclude:: ../../../../gallery/tutorials/data/ctffind4/diagnostic_output.txt # # # %% # CTFFIND4 Diagnostic Output PDF (rendering of diagnostic_output_avrot.pdf). # -# .. image:: ../../../gallery/tutorials/data/ctffind4/diagnostic_output_avrot.png +# .. image:: ../../../../gallery/tutorials/data/ctffind4/diagnostic_output_avrot.png # :alt: diagnostic_output_avrot.png # # %% # CTFFIND4 Diagnostic Output MRC (rendering of diagnostic_output.mrc). # -# .. image:: ../../../gallery/tutorials/data/ctffind4/diagnostic_output.png +# .. image:: ../../../../gallery/tutorials/data/ctffind4/diagnostic_output.png # :alt: diagnostic_output.png # diff --git a/gallery/tutorials/generating_volume_projections.py b/gallery/tutorials/tutorials/generating_volume_projections.py similarity index 98% rename from gallery/tutorials/generating_volume_projections.py rename to gallery/tutorials/tutorials/generating_volume_projections.py index 7d33eea712..58d7abeb16 100644 --- a/gallery/tutorials/generating_volume_projections.py +++ b/gallery/tutorials/tutorials/generating_volume_projections.py @@ -29,7 +29,7 @@ # -------------------- # This example starts with an mrc, which can be loaded as an ASPIRE Volume. -DATA_DIR = "data" # Tutorial example data folder +DATA_DIR = "../data" # Tutorial example data folder v = Volume.load( os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc"), dtype=np.float64 ) diff --git a/gallery/tutorials/image_class.py b/gallery/tutorials/tutorials/image_class.py similarity index 98% rename from gallery/tutorials/image_class.py rename to gallery/tutorials/tutorials/image_class.py index f23bac02c3..b902bd14a2 100644 --- a/gallery/tutorials/image_class.py +++ b/gallery/tutorials/tutorials/image_class.py @@ -12,7 +12,7 @@ from aspire.image import Image from aspire.operators import CTFFilter -DATA_DIR = "data" +DATA_DIR = "../data" img_data = np.load(os.path.join(DATA_DIR, "monuments.npy")) img_data.shape, img_data.dtype diff --git a/gallery/tutorials/image_expansion.py b/gallery/tutorials/tutorials/image_expansion.py similarity index 99% rename from gallery/tutorials/image_expansion.py rename to gallery/tutorials/tutorials/image_expansion.py index 35f540e52d..c90da4920e 100644 --- a/gallery/tutorials/image_expansion.py +++ b/gallery/tutorials/tutorials/image_expansion.py @@ -28,7 +28,7 @@ # ------------------- # Load the images from NumPy array, 10 images of 70S Ribosome with size 129 x 129 -DATA_DIR = "data" +DATA_DIR = "../data" org_images = np.load(os.path.join(DATA_DIR, "example_data_np_array.npy")).T # Set the sizes of images (129, 129) diff --git a/gallery/tutorials/orient3d_simulation.py b/gallery/tutorials/tutorials/orient3d_simulation.py similarity index 99% rename from gallery/tutorials/orient3d_simulation.py rename to gallery/tutorials/tutorials/orient3d_simulation.py index e992dbe180..e7c401df13 100644 --- a/gallery/tutorials/orient3d_simulation.py +++ b/gallery/tutorials/tutorials/orient3d_simulation.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) -DATA_DIR = "data" +DATA_DIR = "../data" logger.info( "This script illustrates orientation estimation using " diff --git a/gallery/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/tutorials/preprocess_imgs_sim.py similarity index 99% rename from gallery/tutorials/preprocess_imgs_sim.py rename to gallery/tutorials/tutorials/preprocess_imgs_sim.py index a2e80cddb8..8b6d6a0efd 100644 --- a/gallery/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/tutorials/preprocess_imgs_sim.py @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) -DATA_DIR = "data" +DATA_DIR = "../data" # %% # Specify Parameters diff --git a/gallery/tutorials/starfile.py b/gallery/tutorials/tutorials/starfile.py similarity index 96% rename from gallery/tutorials/starfile.py rename to gallery/tutorials/tutorials/starfile.py index 819accb1ac..654330acc5 100644 --- a/gallery/tutorials/starfile.py +++ b/gallery/tutorials/tutorials/starfile.py @@ -21,9 +21,10 @@ # Here we load a ".star" file using the RelionSource class source = RelionSource( - "data/sample_relion_data.star", + "../data/sample_relion_data.star", pixel_size=1.338, max_rows=10000, + data_folder="../data/", ) # Reduce the resolution From 67931c411bf84eca27ac6c5180e2d702b3573057 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 16 Mar 2023 14:19:52 -0400 Subject: [PATCH 279/424] Reformatting --- gallery/tutorials/aspire_introduction.py | 138 +++++++++++++++-------- 1 file changed, 89 insertions(+), 49 deletions(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 224273f02d..74236e4b52 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -47,12 +47,14 @@ # %% -# Primitive API Components -# ------------------------ -# The ASPIRE framework is a collection of modules containing interoperable extensible components. -# Underlying the more sophisticated components and algorithms are some core data structures. -# Sophisticated components are designed to interoperate by exchanging, consuming, or producing these basic structures. -# The most common structures encountered when starting out are: +# API Primitives +# -------------- +# The ASPIRE framework is a collection of modules containing +# interoperable extensible components. Underlying the more +# sophisticated components and algorithms are some core data +# structures. Sophisticated components are designed to interoperate +# by exchanging, consuming, or producing these basic structures. The +# most common structures encountered when starting out are: # %% # .. list-table:: Core API Components @@ -78,10 +80,13 @@ # ``Image`` Class # --------------- # -# The `Image `_ class -# is a thin wrapper over Numpy arrays for a stack containing 1 or more images (2D data). -# In this notebook we won't be working directly with the ``Image`` class a lot, but it will be one of the fundemental structures behind the scenes. -# A lot of ASPIRE code passes around ``Image`` and ``Volume`` instances. +# The `Image +# `_ +# class is a thin wrapper over Numpy arrays for a stack containing 1 +# or more images (2D data). In this notebook we won't be working +# directly with the ``Image`` class a lot, but it will be one of the +# fundemental structures behind the scenes. A lot of ASPIRE code +# passes around ``Image`` and ``Volume`` instances. from aspire.image import Image @@ -95,10 +100,12 @@ img_data = np.random.random((3,100,100)) img = Image(img_data) -# Most often, ``Image``s will behave like Numpy arrays, but you explicitly access the underlying Numpy array via ``asnumpy()``. +# Most often, ``Image``s will behave like Numpy arrays, but you +# explicitly access the underlying Numpy array via ``asnumpy()``. img.asnumpy() -# Image have a built in ``show()`` method, which works well for peeking at data. +# Image have a built in ``show()`` method, which works well for +# peeking at data. img.show() # %% @@ -118,8 +125,10 @@ # ``Volume`` Class # ---------------- # -# Like ``Image``, the `Volume `_ class -# is a thin wrapper over Numpy arrays that provides specialized methods for a stack containing 1 or more volumes (3D data). +# Like ``Image``, the `Volume +# `_ +# class is a thin wrapper over Numpy arrays that provides specialized +# methods for a stack containing 1 or more volumes (3D data). from aspire.volume import Volume @@ -144,10 +153,14 @@ # %% # Downsample Volume # ^^^^^^^^^^^^^^^^^ -# Here we downsample the above volume to a desired resolution (32 should be good). -# For the data source I chose to download a real volume density map from `EMDB `_. -# The download was uncompressed in my local directory. The notebook defaults to a small low resolution sample file you may use to sanity check. -# Unfortunately real data can be quite large so we do not ship it with the repo. +# Here we downsample the above volume to a desired resolution (32 +# should be good). For the data source I chose to download a real +# volume density map from `EMDB +# `_. The download +# was uncompressed in my local directory. The notebook defaults to a +# small low resolution sample file you may use to sanity check. +# Unfortunately real data can be quite large so we do not ship it with +# the repo. # Downsample the volume to a desired resolution img_size = 32 @@ -195,7 +208,8 @@ logging.info(rots) logging.info(rots.matrices) -# Using the zero-th (and in this case, only) volume, compute projections using the stack of rotations: +# Using the zero-th (and in this case, only) volume, compute +# projections using the stack of rotations: projections = v.project(0, rots) logging.info(projections) @@ -274,10 +288,19 @@ # `rotations`, `dtype`, and supporting pipelining certain # transformations. # -# The second reason is so we can design an experiment using a synthetic ``Simulation`` source or our own provided Numpy arrays via ``ArrayImageSource`` and then later swap out the source for a large experimental data set using something like ``RelionSource``. Experimental datasets can be too large to practically fit or process entirely in memory, and force the use of iteratively-batched approaches. +# The second reason is so we can design an experiment using a +# synthetic ``Simulation`` source or our own provided Numpy arrays via +# ``ArrayImageSource`` and then later swap out the source for a large +# experimental data set using something like ``RelionSource``. +# Experimental datasets can be too large to practically fit or process +# entirely in memory, and force the use of iteratively-batched +# approaches. # -# Generally, the ``source`` package attempts to make most of this opaque to an end user. Ideally we can simply swap one source for another. -# For now we will build up to the creation and application of synthetic data set based on the various manual interactions above. +# Generally, the ``source`` package attempts to make most of this +# opaque to an end user. Ideally we can simply swap one source for +# another. For now we will build up to the creation and application +# of synthetic data set based on the various manual interactions +# above. # %% # @@ -311,11 +334,13 @@ # %% # ``Simulation`` Class # ^^^^^^^^^^^^^^^^^^^^ -# Generating realistic synthetic data sources is a common task. -# The process of generating then projecting random rotations is integrated into the -# `Simulation `_ class. -# Using ``Simulation``, we can generate arbitrary numbers of projections for use in experiments. -# Then additional features are introduced which allow us to create more realistic data sources. +# Generating realistic synthetic data sources is a common task. The +# process of generating then projecting random rotations is integrated +# into the `Simulation +# `_ +# class. Using ``Simulation``, we can generate arbitrary numbers of +# projections for use in experiments. Then additional features are +# introduced which allow us to create more realistic data sources. from aspire.source import Simulation @@ -331,9 +356,9 @@ sim = Simulation(n=num_imgs, vols=v2) sim.images[:10].show() # Lo Res -# Note both of those simulations have the same rotations -# because they had the same seed by default, -# We recreate ``sim`` with a distinct seed to get different random samples (of rotations). +# Note both of those simulations have the same rotations because they +# had the same seed by default, We recreate ``sim`` with a distinct +# seed to get different random samples (of rotations). sim = Simulation(n=num_imgs, vols=v2, seed=42) sim.images[:10].show() @@ -370,12 +395,13 @@ # Then create a NoiseAdder based on that variance. white_noise_adder = WhiteNoiseAdder(target_noise_variance) -# %% -# We can customize Sources by adding stages to their generation pipeline. -# In this case of a Simulation source, we want to corrupt the projection images with noise. -# -# Internally the ``WhiteNoiseAdder`` creates a ``ScalarFilter`` which is multiplied (convolution) by a Gaussian random sample. -# Similar to before, if you require a different sample, this can be controlled via a ``seed``. +# %% We can customize Sources by adding stages to their generation +# pipeline. In this case of a Simulation source, we want to corrupt +# the projection images with noise. Internally the +# ``WhiteNoiseAdder`` creates a ``ScalarFilter`` which is multiplied +# (convolution) by a Gaussian random sample. Similar to before, if +# you require a different sample, this can be controlled via a +# ``seed``. # Creating the new simulation with this additional noise is easy: sim = Simulation(n=num_imgs, vols=v2, noise_adder=white_noise_adder) @@ -406,10 +432,12 @@ # A Custom ``FunctionFilter`` # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ # -# We will now apply some more interesting noise, using a custom function, and then apply a ``whitening`` process to our data. +# We will now apply some more interesting noise, using a custom +# function, and then apply a ``whitening`` process to our data. # -# Using ``FunctionFilter`` we can create our own custom functions to apply in a pipeline. -# Here we want to apply a custom noise function. We will use a function of two variables for this example. +# Using ``FunctionFilter`` we can create our own custom functions to +# apply in a pipeline. Here we want to apply a custom noise function. +# We will use a function of two variables for this example. from aspire.operators import FunctionFilter from aspire.noise import CustomNoiseAdder @@ -418,7 +446,8 @@ def noise_function(x, y): return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) # In python, functions are first class objects. We take advantage of -# that to pass this function around as a variable. The function is evaluated later, internally, during pipeline execution. +# that to pass this function around as a variable. The function is +# evaluated later, internally, during pipeline execution. custom_noise = CustomNoiseAdder(noise_filter=FunctionFilter(noise_function)) # Create yet another Simulation source to tinker with. @@ -429,9 +458,11 @@ def noise_function(x, y): # %% # Noise Whitening # ^^^^^^^^^^^^^^^ -# We will now combine a more advanced noise estimation technique with an ``ImageSource`` preprocessing method ``whiten``. +# We will now combine a more advanced noise estimation technique with +# an ``ImageSource`` preprocessing method ``whiten``. # -# First an anisotropic noise estimate is performed. We will use the resulting ``Filter``. +# First an anisotropic noise estimate is performed. We will use the +# resulting ``Filter``. from aspire.noise import AnisotropicNoiseEstimator @@ -439,8 +470,10 @@ def noise_function(x, y): aiso_noise_estimator = AnisotropicNoiseEstimator(sim) #%% -# Applying the ``Simulation.whiten()`` method requires passing the filter corresponding to the estimated noise instance. -# Then we can inspect some of the whitened images. While noise is still present, we can see a dramatic change. +# Applying the ``Simulation.whiten()`` method requires passing the +# filter corresponding to the estimated noise instance. Then we can +# inspect some of the whitened images. While noise is still present, +# we can see a dramatic change. # Whiten based on the estimated noise sim.whiten(aiso_noise_estimator.filter) @@ -451,7 +484,9 @@ def noise_function(x, y): # %% # Common Image Corruptions # --------------------------- -# ``Simulation`` provides several configurable types of common CryoEM image corruptions. Users should be aware that Amplitude and Offset corruption is enabled by default. +# ``Simulation`` provides several configurable types of common CryoEM +# image corruptions. Users should be aware that Amplitude and Offset +# corruption is enabled by default. # %% # Amplitudes @@ -505,7 +540,10 @@ def noise_function(x, y): unique_filters=ctf_filters, seed=42) -# Simulation has two unique accessors ``clean_images`` which disables noise, and ``projections`` which are clean uncorrupted projections. Both act like calls to `image` and return show=able ``Image`` instances. +# Simulation has two unique accessors ``clean_images`` which disables +# noise, and ``projections`` which are clean uncorrupted projections. +# Both act like calls to `image` and return show=able ``Image`` +# instances. # %% # Clean projections. @@ -525,8 +563,8 @@ def noise_function(x, y): # Real Experimental Data - ``RelionSource`` # ----------------------------------------- # -# Now that we have some basics, -# we can try to replace the simulation with a real experimental data source. +# Now that we have some basics, we can try to replace the simulation +# with a real experimental data source. from aspire.source import RelionSource @@ -540,7 +578,9 @@ def noise_function(x, y): # Downsample src.downsample(img_size) -# Relionsource will auto populate ``CTFFilter`` instances from the STAR file metadata when available. Having these filters allows us to perform a phase flipping correction. +# Relionsource will auto populate ``CTFFilter`` instances from the +# STAR file metadata when available. Having these filters allows us to +# perform a phase flipping correction. src.phase_flip() # Display the experimental data images. From 22309abe9af2adaad6eaf255b2b93ffec958fdf1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Mar 2023 15:46:44 -0400 Subject: [PATCH 280/424] Add Pipeline roadmap section/table/links --- gallery/experiments/README.rst | 2 + gallery/tutorials/aspire_introduction.py | 77 +++++++++++------------- 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/gallery/experiments/README.rst b/gallery/experiments/README.rst index 7008ddfee4..1a389fd4f2 100644 --- a/gallery/experiments/README.rst +++ b/gallery/experiments/README.rst @@ -1,3 +1,5 @@ +.. _exp: + Experiments ============================= diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 74236e4b52..d1f6ee8a9c 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -589,48 +589,39 @@ def noise_function(x, y): # %% # Pipeline Roadmap # ---------------- -# lorem ipsum diagramum - -# %% -# .. list-table:: Pipeline Components -# :header-rows: 1 -# -# * - Data Source -# - Preprocessing -# - 2D Denoising -# - Orientation Estimation -# - VolumeEstimator -# * - Simulation -# - Noise Estimation -# - Class Averagring -# - CLSyncVoting -# - MeanVolumeEstimator -# * - RelionSource -# - downsample -# - cov2d (CWF) -# - CLSymmetryC3C4 -# - -# * - CoordinateSource -# - whiten -# - -# - CLSymmetryCn -# - -# * - -# - phase_flip -# - -# - -# - -# * - -# - normalize_backgrond -# - -# - -# - -# * - -# - CTFEstimator -# - -# - -# - - +# Now that the primitives have been introduced we can explore higher +# level components. The higher level components are designed to be +# modular and cacheable (to memory or disk) to support experimentation +# with entire pipelines or focuses algorithmic development on specific +# components. Most pipelines will follow a flow of data and +# components moving mostly left to right in the table below. This +# table is not exhaustive, but represents some of the most common +# components. + +# %% +# +----------------+--------------------+-----------------+----------------+---------------------+ +# | Image Processing | Abinitio | +# +----------------+--------------------+-----------------+----------------+---------------------+ +# | Data | Preprocessing | Denoising | Orientation | 3D Reconstruction | +# +================+====================+=================+================+=====================+ +# |Simulation | NoiseEstimator | Class Averaging | CLSyncVoting | MeanVolumeEstimator | +# +----------------+--------------------+-----------------+----------------+---------------------+ +# |RelionSource | downsample | cov2d (CWF) | CLSymmetryC3C4 | | +# +----------------+--------------------+-----------------+----------------+---------------------+ +# |CoordinateSource| whiten | | CLSymmetryCn | | +# +----------------+--------------------+-----------------+----------------+---------------------+ +# | | phase_flip | | | | +# +----------------+--------------------+-----------------+----------------+---------------------+ +# | |normalize_background| | | | +# +----------------+--------------------+-----------------+----------------+---------------------+ +# | | CTFEstimator | | | | +# +----------------+--------------------+-----------------+----------------+---------------------+ + +# %% +# We're now ready to explore a small example end to end abinitio +# pipeline using simulated data. +# :ref:`sphx_glr_auto_tutorials_pipeline_demo.py` # %% -# :ref:`sphx_glr_auto_tutorials_pipeline_demo.py` +# Larger simulations and experiments based on EMPIAR data can be found +# in :ref:`Experiments `. From da9e0a81e4d74b87d4b7d000eab6a36fbaef8049 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 17 Mar 2023 15:51:10 -0400 Subject: [PATCH 281/424] tox touch ups --- gallery/tutorials/aspire_introduction.py | 34 +++++++++++++----------- gallery/tutorials/tutorials/ctf.py | 2 +- tox.ini | 7 ++--- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index d1f6ee8a9c..be5528d520 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -44,7 +44,7 @@ import numpy as np import aspire - +from aspire.image import Image # %% # API Primitives @@ -88,16 +88,15 @@ # fundemental structures behind the scenes. A lot of ASPIRE code # passes around ``Image`` and ``Volume`` instances. -from aspire.image import Image # Create an ``Image`` instance from random data. -img_data = np.random.random((100,100)) +img_data = np.random.random((100, 100)) img = Image(img_data) logging.info(f"img shape: {img.shape}") # Note this produces a stack of 1. logging.info(f"str(img): {img}") # Note this produces a stack of 1. # Create an Image for a stack of 3 100x100 images. -img_data = np.random.random((3,100,100)) +img_data = np.random.random((3, 100, 100)) img = Image(img_data) # Most often, ``Image``s will behave like Numpy arrays, but you @@ -243,7 +242,7 @@ # +dual() # +sign() # } -# +# # Filter o-- DualFilter # Filter o-- FunctionFilter # Filter o-- PowerFilter @@ -256,7 +255,7 @@ # Filter o-- IdentityFilter # Filter o-- CTFFilter # CTFFilter o-- RadialCTFFilter - + # %% # ``CTFFilter`` and ``RadialCTFFilter`` the most common filters # encountered when starting out and are detailed in @@ -439,12 +438,14 @@ # apply in a pipeline. Here we want to apply a custom noise function. # We will use a function of two variables for this example. -from aspire.operators import FunctionFilter from aspire.noise import CustomNoiseAdder +from aspire.operators import FunctionFilter + def noise_function(x, y): return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) + # In python, functions are first class objects. We take advantage of # that to pass this function around as a variable. The function is # evaluated later, internally, during pipeline execution. @@ -469,7 +470,7 @@ def noise_function(x, y): # Estimate noise. aiso_noise_estimator = AnisotropicNoiseEstimator(sim) -#%% +# %% # Applying the ``Simulation.whiten()`` method requires passing the # filter corresponding to the estimated noise instance. Then we can # inspect some of the whitened images. While noise is still present, @@ -532,13 +533,15 @@ def noise_function(x, y): # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ # Here we'll combine the parameters above into a new simulation. -sim = Simulation(n=num_imgs, - vols=v2, - amplitudes=1, - offsets=0, - noise_adder=white_noise_adder, - unique_filters=ctf_filters, - seed=42) +sim = Simulation( + n=num_imgs, + vols=v2, + amplitudes=1, + offsets=0, + noise_adder=white_noise_adder, + unique_filters=ctf_filters, + seed=42, +) # Simulation has two unique accessors ``clean_images`` which disables # noise, and ``projections`` which are clean uncorrupted projections. @@ -558,7 +561,6 @@ def noise_function(x, y): sim.images[:5].show() - # %% # Real Experimental Data - ``RelionSource`` # ----------------------------------------- diff --git a/gallery/tutorials/tutorials/ctf.py b/gallery/tutorials/tutorials/ctf.py index 073ce65691..9b1c7dba4c 100644 --- a/gallery/tutorials/tutorials/ctf.py +++ b/gallery/tutorials/tutorials/ctf.py @@ -373,7 +373,7 @@ def generate_example_image(L, noise_variance=0.1): "../data/sample_relion_data.star", pixel_size=1.338, max_rows=10000, - data_folder='../data', + data_folder="../data", ) src.downsample(64) # easier to visualize src.images[:3].show() diff --git a/tox.ini b/tox.ini index 74b2f33eac..63ee6a3099 100644 --- a/tox.ini +++ b/tox.ini @@ -66,11 +66,12 @@ max-line-length = 88 extend-ignore = E203, E501 per-file-ignores = __init__.py: F401 + gallery/tutorials/aspire_introduction.py: F401, E402 gallery/tutorials/configuration.py: T201, E402 - gallery/tutorials/ctf.py: T201, E402 + gallery/tutorials/tutorials/ctf.py: T201, E402 # Ignore Sphinx gallery builds - docs/build/html/_downloads/*/*.py: T201, E402 - docs/source/auto*/*.py: T201, E402 + docs/build/html/_downloads/*/*.py: T201, E402, F401 + docs/source/auto*/*.py: T201, E402, F401 [isort] default_section = THIRDPARTY From 2af762a9b5b365e55211ac169cc047acbddf2f59 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 17 Mar 2023 16:03:07 -0400 Subject: [PATCH 282/424] relative_rots_to_cl_indices --- src/aspire/abinitio/commonline_cn.py | 78 ++++++++++++++-------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 0b1fd5c310..6f2f89b69d 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -228,37 +228,26 @@ def _estimate_relative_viewing_directions(self): return vijs, viis_rank1 - def _compute_scls_inds(self, Ri_cands): + def _compute_scls_inds(self, Ris_tilde): """ Compute self-common-lines indices induced by candidate rotations. - :param Ri_cands: An array of size n_candsx3x3 of candidate rotations. + :param Ris_tilde: An array of size n_candsx3x3 of candidate rotations. :return: An n_cands x (order-1)//2 x 2 array holding the indices of the (order-1)//2 non-collinear pairs of self-common-lines for each candidate rotation. """ - order = self.order - n_theta = self.n_theta - n_scl_pairs = (order - 1) // 2 - n_cands = Ri_cands.shape[0] + n_scl_pairs = (self.order - 1) // 2 + n_cands = Ris_tilde.shape[0] scls_inds = np.zeros((n_cands, n_scl_pairs, 2), dtype=np.uint16) - rots_symm = cyclic_rotations(order, dtype=self.dtype).matrices + rots_symm = cyclic_rotations(self.order, dtype=self.dtype).matrices - for i_cand, Ri_cand in enumerate(Ri_cands): + for i_cand, Ri_cand in enumerate(Ris_tilde): Riigs = Ri_cand.T @ rots_symm[1 : n_scl_pairs + 1] @ Ri_cand - c1s = np.array((-Riigs[:, 1, 2], Riigs[:, 0, 2])).T - c2s = np.array((Riigs[:, 2, 1], -Riigs[:, 2, 0])).T - - c1s_inds = self.cl_angles_to_ind(c1s, n_theta) - c2s_inds = self.cl_angles_to_ind(c2s, n_theta) - - inds = np.where(c1s_inds >= (n_theta // 2)) - c1s_inds[inds] -= n_theta // 2 - c2s_inds[inds] += n_theta // 2 - c2s_inds[inds] = np.mod(c2s_inds[inds], n_theta) + c1s, c2s = self.relative_rots_to_cl_indices(Riigs, self.n_theta) - scls_inds[i_cand, :, 0] = c1s_inds - scls_inds[i_cand, :, 1] = c2s_inds + scls_inds[i_cand, :, 0] = c1s + scls_inds[i_cand, :, 1] = c2s return scls_inds def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): @@ -270,33 +259,45 @@ def _compute_cls_inds(self, Ris_tilde, R_theta_ijs): :return: An array of size n_cands x n_cands x n_theta_ijs x 2 holding common-lines indices induced by the supplied rotations. """ - n_theta = self.n_theta n_points_sphere = self.n_points_sphere n_theta_ijs = R_theta_ijs.shape[0] cij_inds = np.zeros( (n_points_sphere, n_points_sphere, n_theta_ijs, 2), dtype=np.uint16 ) logger.info("Computing common-line indices induced by candidate rotations.") - with tqdm(total=n_points_sphere) as pbar: - for i in range(n_points_sphere): - for j in range(n_points_sphere): - R_cands = Ris_tilde[i].T @ R_theta_ijs @ Ris_tilde[j] + for i in tqdm(range(n_points_sphere)): + for j in range(n_points_sphere): + R_cands = Ris_tilde[i].T @ R_theta_ijs @ Ris_tilde[j] + + c1s, c2s = self.relative_rots_to_cl_indices(R_cands, self.n_theta) + + cij_inds[i, j, :, 0] = c1s + cij_inds[i, j, :, 1] = c2s + return cij_inds - c1s = np.array((-R_cands[:, 1, 2], R_cands[:, 0, 2])).T - c2s = np.array((R_cands[:, 2, 1], -R_cands[:, 2, 0])).T + @staticmethod + def relative_rots_to_cl_indices(relative_rots, n_theta): + """ + Given a set of relative rotations between pairs of images + produce the common-line indices for each pair. - c1s = self.cl_angles_to_ind(c1s, n_theta) - c2s = self.cl_angles_to_ind(c2s, n_theta) + :param relative_rots: The n x 3 x 3 relative rotations between pairs of images. + :param n_theta: The theta resolution for common-line indices. - inds = np.where(c1s >= n_theta // 2) - c1s[inds] -= n_theta // 2 - c2s[inds] += n_theta // 2 - c2s[inds] = np.mod(c2s[inds], n_theta) + :return: Common-line indices c1s, c2s each length n. + """ + c1s = np.array((-relative_rots[:, 1, 2], relative_rots[:, 0, 2])).T + c2s = np.array((relative_rots[:, 2, 1], -relative_rots[:, 2, 0])).T - cij_inds[i, j, :, 0] = c1s - cij_inds[i, j, :, 1] = c2s - pbar.update() - return cij_inds + c1s = CLSymmetryC3C4.cl_angles_to_ind(c1s, n_theta) + c2s = CLSymmetryC3C4.cl_angles_to_ind(c2s, n_theta) + + inds = np.where(c1s >= n_theta // 2) + c1s[inds] -= n_theta // 2 + c2s[inds] += n_theta // 2 + c2s[inds] = np.mod(c2s[inds], n_theta) + + return c1s, c2s @staticmethod def generate_candidate_rots(n, equator_threshold, order, degree_res, seed): @@ -306,8 +307,7 @@ def generate_candidate_rots(n, equator_threshold, order, degree_res, seed): :param n: Number of rotations to generate. :param equator_threshold: Angular distance from equator (in degrees). - :param order: Cyclic order of underlying molecule. Number of in-plane - rotations must be divisible by order. + :param order: Cyclic order of underlying molecule. :param degree_res: Degree resolution for in-plane rotations. :param seed: Random seed. From f85538a98c7ebc12c4ec459210f3f35f35089ab0 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 20 Mar 2023 08:55:36 -0400 Subject: [PATCH 283/424] lift np.real --- src/aspire/abinitio/commonline_cn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 6f2f89b69d..b3ae9247a6 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -122,10 +122,10 @@ def _estimate_relative_viewing_directions(self): logger.info("Computing likelihood wrt self common-lines.") for i in trange(n_img): # Compute correlation of pf[i] with itself over all shifts. - corrs = pf_shifted[i] @ np.conj(pf_full[i]).T + corrs = np.real(pf_shifted[i] @ np.conj(pf_full[i]).T) corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) corrs_cands = np.max( - np.real(corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 + (corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 ) scores_self_corrs[i] = np.mean(corrs_cands, axis=1) From d5741de62ffd6cdd0d4ea3de1c847be291360336 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 09:11:14 -0400 Subject: [PATCH 284/424] Reconcile IndexedSource merge --- .../experimental_abinitio_pipeline_10028.py | 10 ++++------ .../experimental_abinitio_pipeline_10081.py | 9 +++------ gallery/experiments/simulated_abinitio_pipeline.py | 12 +++++------- gallery/tutorials/pipeline_demo.py | 9 ++------- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index c92e9a53a3..ac020459ae 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -33,7 +33,7 @@ from aspire.denoising import DefaultClassAvgSource, DenoiserCov2D from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, RelionSource +from aspire.source import RelionSource logger = logging.getLogger(__name__) @@ -134,16 +134,13 @@ # Now perform classification and averaging for each class. # This also demonstrates the potential to use a different source for classification and averaging. -avgs_src = DefaultClassAvgSource( +avgs = DefaultClassAvgSource( classification_src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automatically configure parallel processing ) -# We'll manually cache `n_classes` worth to speed things up. -avgs = ArrayImageSource(avgs_src.images[:n_classes]) - if interactive: avgs.images[:10].show() @@ -157,7 +154,8 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSyncVoting(avgs, n_theta=360) +# Run orientation estimation on first `n_classes` from `avgs`. +orient_est = CLSyncVoting(avgs[:n_classes], n_theta=360) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index b7106221a2..e6de3752b9 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -30,7 +30,7 @@ from aspire.denoising import DefaultClassAvgSource from aspire.noise import AnisotropicNoiseEstimator from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, RelionSource +from aspire.source import RelionSource logger = logging.getLogger(__name__) @@ -89,10 +89,6 @@ # Automatically configure parallel processing avgs = DefaultClassAvgSource(src, n_nbor=n_nbor, num_procs=None) -# We'll manually cache `n_classes` worth to speed things up. -avgs = ArrayImageSource(avgs.images[:n_classes]) - - # %% # Common Line Estimation # ---------------------- @@ -102,7 +98,8 @@ logger.info("Begin Orientation Estimation") -orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=360, max_shift=0) +# Run orientation estimation on first `n_classes` from `avgs`. +orient_est = CLSymmetryC3C4(avgs[:n_classes], symmetry="C4", n_theta=360, max_shift=0) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index becc0fbd01..3994f85915 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -26,7 +26,7 @@ from aspire.noise import AnisotropicNoiseEstimator, CustomNoiseAdder from aspire.operators import FunctionFilter, RadialCTFFilter from aspire.reconstruction import MeanEstimator -from aspire.source import ArrayImageSource, Simulation +from aspire.source import Simulation from aspire.utils.coor_trans import ( get_aligned_rotations, get_rots_mse, @@ -162,16 +162,13 @@ def noise_function(x, y): # Now perform classification and averaging for each class. # This also demonstrates the potential to use a different source for classification and averaging. -avgs_src = DefaultClassAvgSource( +avgs = DefaultClassAvgSource( classification_src, n_nbor=n_nbor, averager_src=src, num_procs=None, # Automatically configure parallel processing ) -# We'll manually cache `n_classes` worth to speed things up. -avgs = ArrayImageSource(avgs_src.images[:n_classes]) - if interactive: avgs.images[:10].show() @@ -187,10 +184,11 @@ def noise_function(x, y): # Stash true rotations for later comparison. # Note class selection re-ordered our images, so we remap the indices back to the original source. -indices = avgs_src.selection_indices +indices = avgs.selection_indices true_rotations = src.rotations[indices[:n_classes]] -orient_est = CLSyncVoting(avgs, n_theta=180) +# Run orientation estimation on first `n_classes` from `avgs`. +orient_est = CLSyncVoting(avgs[:n_classes], n_theta=180) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 184d039e48..12c54abab9 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -193,12 +193,6 @@ def download(url, save_path, chunk_size=1024 * 1024): classifier=rir, ) -# For this small example, it is most effective to just cache these -# manually. - -from aspire.source import ArrayImageSource - -avgs = ArrayImageSource(avgs.images[:n_classes]) # %% # View the Class Averages @@ -227,7 +221,8 @@ def download(url, save_path, chunk_size=1024 * 1024): # Stash true rotations for later comparison true_rotations = src.rotations[:n_classes] -orient_est = CLSyncVoting(avgs, n_theta=72) +# Run orientation estimation on first `n_classes` from `avgs`. +orient_est = CLSyncVoting(avgs[:n_classes], n_theta=72) # Get the estimated rotations orient_est.estimate_rotations() From 9a6042602fb6540dda802fa22cc943c90aeeaf4b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 09:32:42 -0400 Subject: [PATCH 285/424] Allow whiten to take an estimator directly --- src/aspire/source/image.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 569a38eade..3ec94ad620 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -17,9 +17,10 @@ Multiply, Pipeline, ) -from aspire.noise import WhiteNoiseEstimator +from aspire.noise import NoiseEstimator, WhiteNoiseEstimator from aspire.operators import ( CTFFilter, + Filter, IdentityFilter, MultiplicativeFilter, PowerFilter, @@ -454,14 +455,29 @@ def downsample(self, L): self.L = L - def whiten(self, noise_filter): + def whiten(self, noise_estimate): """ Modify the `ImageSource` in-place by appending a whitening filter to the generation pipeline. - :param noise_filter: The noise psd of the images as a `Filter` object. Typically determined by a - NoiseEstimator class, and available as its `filter` attribute. + :param noise_estimate: `NoiseEstimator` or `Filter`. When + passed a `NoiseEstimator` the `filter` attribute will be + queried. Alternatively, the noise PSD may be passed + directly as a `Filter` object. :return: On return, the `ImageSource` object has been modified in place. """ + + if isinstance(noise_estimate, NoiseEstimator): + # Any NoiseEstimator instance should have a `filter`. + noise_filter = noise_estimate.filter + elif isinstance(noise_estimate, Filter): + # We were given a `Filter` object. + noise_filter = noise_estimate + else: + raise TypeError( + f"Whiten passed {noise_estimate}" + " instead of `NoiseEstimator` or `Filter`." + ) + logger.info("Whitening source object") whiten_filter = PowerFilter(noise_filter, power=-0.5) From 8b84e4f8986f2c41349d3135e79411a277017b38 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 10:28:51 -0400 Subject: [PATCH 286/424] Update `whiten()` usage --- gallery/experiments/cov2d_experiment.py.dontrun | 2 +- gallery/experiments/cov3d_experiment.dontrun | 2 +- gallery/experiments/experimental_abinitio_pipeline.py | 2 +- gallery/experiments/preprocess_imgs_exp.py.dontrun | 2 +- gallery/experiments/simulated_abinitio_pipeline.py | 4 ++-- gallery/tutorials/basic_image_array.py | 2 +- gallery/tutorials/lecture_feature_demo.py | 4 ++-- gallery/tutorials/preprocess_imgs_sim.py | 2 +- gallery/tutorials/starfile.py | 2 +- src/aspire/commands/denoise.py | 2 +- src/aspire/commands/extract_particles.py | 2 +- src/aspire/commands/preprocess.py | 2 +- tests/test_coordinate_source.py | 2 +- tests/test_preprocess_pipeline.py | 6 ++++-- 14 files changed, 19 insertions(+), 17 deletions(-) diff --git a/gallery/experiments/cov2d_experiment.py.dontrun b/gallery/experiments/cov2d_experiment.py.dontrun index 80d784d41d..7d58217d95 100755 --- a/gallery/experiments/cov2d_experiment.py.dontrun +++ b/gallery/experiments/cov2d_experiment.py.dontrun @@ -47,7 +47,7 @@ logger.info(f"var_noise before whitening {var_noise}") # Whiten the noise of images logger.info(f"Whiten the noise of images from the noise estimator") -source.whiten(noise_estimator.filter) +source.whiten(noise_estimator) # Note this changes the noise variance, # flattening spectrum and converging towards 1. # Noise variance will be recomputed in DenoiserCov2D by default. diff --git a/gallery/experiments/cov3d_experiment.dontrun b/gallery/experiments/cov3d_experiment.dontrun index 5cad831943..e0c11d088b 100644 --- a/gallery/experiments/cov3d_experiment.dontrun +++ b/gallery/experiments/cov3d_experiment.dontrun @@ -40,7 +40,7 @@ noise_estimator = AnisotropicNoiseEstimator(source, batchSize=512) # Whiten the noise of images print("Whiten the noise of images from the noise estimator") -source.whiten(noise_estimator.filter) +source.whiten(noise_estimator) # Estimate the noise variance. This is needed for the covariance estimation step below. noise_variance = noise_estimator.estimate() print(f"Noise Variance = {noise_variance}") diff --git a/gallery/experiments/experimental_abinitio_pipeline.py b/gallery/experiments/experimental_abinitio_pipeline.py index 883485838d..92d0f53fc4 100644 --- a/gallery/experiments/experimental_abinitio_pipeline.py +++ b/gallery/experiments/experimental_abinitio_pipeline.py @@ -78,7 +78,7 @@ # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) -src.whiten(aiso_noise_estimator.filter) +src.whiten(aiso_noise_estimator) # Plot the noise profile for inspection if interactive: diff --git a/gallery/experiments/preprocess_imgs_exp.py.dontrun b/gallery/experiments/preprocess_imgs_exp.py.dontrun index b4ae7e7d70..d2b28e1b87 100644 --- a/gallery/experiments/preprocess_imgs_exp.py.dontrun +++ b/gallery/experiments/preprocess_imgs_exp.py.dontrun @@ -45,7 +45,7 @@ imgs_nb = source.images[:nimgs_ext] print('Whiten noise of images') noise_estimator = WhiteNoiseEstimator(source) -source.whiten(noise_estimator.filter) +source.whiten(noise_estimator) imgs_wt = source.images[:nimgs_ext] print('Invert global density contrast') diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 59820cfffc..b071399b89 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -118,11 +118,11 @@ def noise_function(x, y): # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) -src.whiten(aiso_noise_estimator.filter) +src.whiten(aiso_noise_estimator) # Plot the noise profile for inspection if interactive: - plt.imshow(aiso_noise_estimator.filter.evaluate_grid(L)) + plt.imshow(aiso_noise_estimator.evaluate_grid(L)) plt.show() # Peek, what do the whitened images look like... diff --git a/gallery/tutorials/basic_image_array.py b/gallery/tutorials/basic_image_array.py index 08364d2870..a7dc56e7ef 100644 --- a/gallery/tutorials/basic_image_array.py +++ b/gallery/tutorials/basic_image_array.py @@ -139,7 +139,7 @@ def noise_function(x, y): # Once we have the estimator instance, # we can use it in a transform applied to our Source. -imgs_src.whiten(noise_estimator.filter) +imgs_src.whiten(noise_estimator) # Peek at two whitened images and their corresponding spectrum. diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 80f6fa94f2..5acf3573a0 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -272,14 +272,14 @@ def noise_function(x, y): # Noise Whitening # --------------- # -# Applying the ``Simulation.whiten()`` method just requires passing the filter corresponding to the estimated noise instance. +# Applying the ``Simulation.whiten()`` method just requires passing a `NoiseEstimator` instance. # Then we can inspect some of the whitened images. While noise is still present, we can see a dramatic change. # Estimate noise. aiso_noise_estimator = AnisotropicNoiseEstimator(sim4) # Whiten based on the estimated noise -sim4.whiten(aiso_noise_estimator.filter) +sim4.whiten(aiso_noise_estimator) # What do the whitened images look like... sim4.images[:10].show() diff --git a/gallery/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/preprocess_imgs_sim.py index a2e80cddb8..7f874d691d 100644 --- a/gallery/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/preprocess_imgs_sim.py @@ -97,7 +97,7 @@ logger.info("Whiten noise of images") noise_estimator = WhiteNoiseEstimator(source) -source.whiten(noise_estimator.filter) +source.whiten(noise_estimator) imgs_wt = source.images[0].asnumpy() logger.info("Invert the global density contrast if need") diff --git a/gallery/tutorials/starfile.py b/gallery/tutorials/starfile.py index 819accb1ac..8e65a3607f 100644 --- a/gallery/tutorials/starfile.py +++ b/gallery/tutorials/starfile.py @@ -37,7 +37,7 @@ # Estimate noise in the ImageSource instance noise_estimator = AnisotropicNoiseEstimator(source) # Apply whitening to ImageSource -source.whiten(noise_estimator.filter) +source.whiten(noise_estimator) # Display subset of the images images = source.images[:10] diff --git a/src/aspire/commands/denoise.py b/src/aspire/commands/denoise.py index 91dfb09e26..9f0f32c06d 100644 --- a/src/aspire/commands/denoise.py +++ b/src/aspire/commands/denoise.py @@ -96,7 +96,7 @@ def denoise( # Whiten the noise of images logger.info("Whiten the noise of images from the noise estimator") - source.whiten(noise_estimator.filter) + source.whiten(noise_estimator) if denoise_method == "CWF": logger.info("Denoise the images using CWF cov2D method.") diff --git a/src/aspire/commands/extract_particles.py b/src/aspire/commands/extract_particles.py index 8bceb4d685..6fd3946275 100644 --- a/src/aspire/commands/extract_particles.py +++ b/src/aspire/commands/extract_particles.py @@ -148,7 +148,7 @@ def extract_particles( src.normalize_background() if whiten: estimator = WhiteNoiseEstimator(src) - src.whiten(estimator.filter) + src.whiten(estimator) if invert_contrast: src.invert_contrast() diff --git a/src/aspire/commands/preprocess.py b/src/aspire/commands/preprocess.py index d9404cba17..474ae29359 100644 --- a/src/aspire/commands/preprocess.py +++ b/src/aspire/commands/preprocess.py @@ -110,7 +110,7 @@ def preprocess( if whiten: logger.info("Whiten noise of images") noise_estimator = WhiteNoiseEstimator(source) - source.whiten(noise_estimator.filter) + source.whiten(noise_estimator) if invert_contrast: logger.info("Invert global density contrast") diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index ec0dc05b63..a2c1edb83a 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -515,7 +515,7 @@ def testPreprocessing(self): src.downsample(60) src.normalize_background() noise_estimator = WhiteNoiseEstimator(src) - src.whiten(noise_estimator.filter) + src.whiten(noise_estimator) src.invert_contrast() # call .images() to ensure the filters are applied # and not just added to pipeline diff --git a/tests/test_preprocess_pipeline.py b/tests/test_preprocess_pipeline.py index c63102d07b..109f532b40 100644 --- a/tests/test_preprocess_pipeline.py +++ b/tests/test_preprocess_pipeline.py @@ -94,7 +94,7 @@ def testWhiten(dtype): L = 64 sim = get_sim_object(L, dtype) noise_estimator = AnisotropicNoiseEstimator(sim) - sim.whiten(noise_estimator.filter) + sim.whiten(noise_estimator) imgs_wt = sim.images[:num_images].asnumpy() # calculate correlation between two neighboring pixels from background @@ -110,8 +110,10 @@ def testWhiten(dtype): def testWhiten2(dtype): # Excercises missing cases using odd image resolutions with filter. # Relates to GitHub issue #401. - # Otherwise this is the same as testWhiten, though the accuracy + # Otherwise this is the similar to testWhiten, though the accuracy # (atol) for odd resolutions seems slightly worse. + # Note, we also use this test to excercise calling `whiten` + # directly with a `Filter`. L = 63 sim = get_sim_object(L, dtype) noise_estimator = AnisotropicNoiseEstimator(sim) From 90080ff799af0bc25643513a7f7a280a2cf29a78 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 12:11:48 -0400 Subject: [PATCH 287/424] Resolve minor Image merge conflict --- src/aspire/classification/averager2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 620db124d2..1c49d84dc1 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -338,7 +338,7 @@ def _innerloop(k): # Shift in real space to avoid extra conversions if self._base_image_shift is not None: neighbors_imgs[0] = ( - Image(neighbors_imgs[0]).shift(self._base_image_shift).asnumpy() + neighbors_imgs[0].shift(self._base_image_shift).asnumpy() ) # Evaluate_t into basis From 56fe9bd01e5257754a25c4437d3553932ad9e1ba Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:06:09 -0400 Subject: [PATCH 288/424] Add intro doc about Basis --- gallery/tutorials/aspire_introduction.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index be5528d520..17551089b9 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -266,7 +266,20 @@ # %% # ``Basis`` # --------- -# What can I say. +# ASPIRE provides a selection of ``Basis`` classes designed for +# working with CryoEM data in two and three dimensions. Most of these +# basis implementations are optimized for efficient rotations, often +# called the *"Steerable"* property. As of writing most algorithms in +# ASPIRE are written to work well with the Fast Fourier Bessel Basis +# classes ``FFBBasis2D`` and ``FFBBasis3D``. These correspond to +# direct slower reference ``FBBasis2D`` and ``FBBasis3D`` methods. +# +# Recently, a related Fourier Bessel method using the Fast Laplacian +# Eigenfunction transforms was integrated as``FLEBasis2D``. +# Additional, Prolate Spheroidal Wave Function methods are available +# via ``FPSWFBasis2D`` and ``FPSWFBasis3D``, but their integration +# into other components like 2D covariance analysis is incomplete, and +# slated for a future release. # %% # The ``source`` Package From a9770e94e74a1cec84d4bfc59fcc433b58f44e98 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:20:26 -0400 Subject: [PATCH 289/424] update basic_image_array with Image returns --- gallery/tutorials/basic_image_array.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gallery/tutorials/basic_image_array.py b/gallery/tutorials/basic_image_array.py index 08364d2870..1034746ec1 100644 --- a/gallery/tutorials/basic_image_array.py +++ b/gallery/tutorials/basic_image_array.py @@ -62,9 +62,9 @@ # We will plot the original and first noisy image, # because we only have one image in our Image stack right now. fig, axs = plt.subplots(1, 2) -axs[0].imshow(img[0], cmap=plt.cm.gray) +axs[0].imshow(img.asnumpy()[0], cmap=plt.cm.gray) axs[0].set_title("Starting Image") -axs[1].imshow(img_with_noise[0], cmap=plt.cm.gray) +axs[1].imshow(img_with_noise.asnumpy()[0], cmap=plt.cm.gray) axs[1].set_title("Noisy Image") plt.tight_layout() plt.show() @@ -102,7 +102,7 @@ def noise_function(x, y): # Let's see the first two noisy images. # They should each display slightly different noise. fig, axs = plt.subplots(2, 2) -for i, img in enumerate(imgs_with_noise[0:2]): +for i, img in enumerate(imgs_with_noise[0:2].asnumpy()): axs[0, i].imshow(img, cmap=plt.cm.gray) axs[0, i].set_title(f"Custom Noisy Image {i}") img_with_noise_f = np.abs(np.fft.fftshift(np.fft.fft2(img))) @@ -131,7 +131,7 @@ def noise_function(x, y): imgs_src = ArrayImageSource(imgs_with_noise) # We'll copy the orginals for comparison later, before we process them further. -noisy_imgs_copy = imgs_src.images[:n_imgs] +noisy_imgs_copy = imgs_src.images[:n_imgs].asnumpy().copy() # One of the tools we can use is a NoiseEstimator, # which consumes from a Source. @@ -144,7 +144,7 @@ def noise_function(x, y): # Peek at two whitened images and their corresponding spectrum. fig, axs = plt.subplots(2, 2) -for i, img in enumerate(imgs_src.images[:2]): +for i, img in enumerate(imgs_src.images[:2].asnumpy()): axs[0, i].imshow(img, cmap=plt.cm.gray) axs[0, i].set_title(f"Whitened Noisy Image {i}") img_with_noise_f = np.abs(np.fft.fftshift(np.fft.fft2(img))) @@ -175,7 +175,7 @@ def radial_profile(data): # Lets pickout several images and plot their the radial profile of their noise. colors = ["r", "g", "b", "k", "c"] -for i, img in enumerate(imgs_src.images[: len(colors)]): +for i, img in enumerate(imgs_src.images[: len(colors)].asnumpy()): img_with_noise_f = np.abs(np.fft.fftshift(np.fft.fft2(noisy_imgs_copy[i]))) plt.plot( radial_profile(img_with_noise_f), From 5ca7ee9584d3528a788442e0a922a96a104a269d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:28:10 -0400 Subject: [PATCH 290/424] update image_expansion with Image returns --- gallery/tutorials/image_expansion.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gallery/tutorials/image_expansion.py b/gallery/tutorials/image_expansion.py index 35f540e52d..f719d6f205 100644 --- a/gallery/tutorials/image_expansion.py +++ b/gallery/tutorials/image_expansion.py @@ -95,11 +95,11 @@ logger.info(f"Finish fast FB expansion of original images in {dtime:.4f} seconds.") # Reconstruct images from the expansion coefficients based on fast FB basis -ffb_images = ffb_basis.evaluate(ffb_coeffs) +ffb_images = ffb_basis.evaluate(ffb_coeffs).asnumpy() logger.info("Finish reconstruction of images from fast FB expansion coefficients.") # Calculate the mean value of maximum differences between the fast FB estimated images to the original images -diff = (ffb_images - org_images).asnumpy() +diff = ffb_images - org_images ffb_meanmax = np.mean(np.max(abs(diff), axis=(1, 2))) logger.info( f"Mean value of maximum differences between FFB estimated images and original images: {ffb_meanmax}" @@ -139,11 +139,11 @@ logger.info(f"Finish direct PSWF expansion of original images in {dtime:.4f} seconds.") # Reconstruct images from the expansion coefficients based on direct PSWF basis -pswf_images = pswf_basis.evaluate(pswf_coeffs) +pswf_images = pswf_basis.evaluate(pswf_coeffs).asnumpy() logger.info("Finish reconstruction of images from direct PSWF expansion coefficients.") # Calculate the mean value of maximum differences between direct PSWF estimated images and original images -diff = (pswf_images - org_images).asnumpy() +diff = pswf_images - org_images pswf_meanmax = np.mean(np.max(abs(diff), axis=(1, 2))) logger.info( f"Mean value of maximum differences between PSWF estimated images and original images: {pswf_meanmax}" @@ -183,11 +183,11 @@ logger.info(f"Finish fast PSWF expansion of original images in {dtime:.4f} seconds.") # Reconstruct images from the expansion coefficients based on direct PSWF basis -fpswf_images = fpswf_basis.evaluate(fpswf_coeffs) +fpswf_images = fpswf_basis.evaluate(fpswf_coeffs).asnumpy() logger.info("Finish reconstruction of images from fast PSWF expansion coefficients.") # Calculate mean value of maximum differences between the fast PSWF estimated images and the original images -diff = (fpswf_images - org_images).asnumpy() +diff = fpswf_images - org_images fpswf_meanmax = np.mean(np.max(abs(diff), axis=(1, 2))) logger.info( f"Mean value of maximum differences between FPSWF estimated images and original images: {fpswf_meanmax}" From 1a034680e848d6f8c8dd24c29fd8fe3a46925b83 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:40:10 -0400 Subject: [PATCH 291/424] update cov3d Volume return --- gallery/tutorials/cov3d_simulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/cov3d_simulation.py b/gallery/tutorials/cov3d_simulation.py index 234dc799e1..c30653d5d9 100644 --- a/gallery/tutorials/cov3d_simulation.py +++ b/gallery/tutorials/cov3d_simulation.py @@ -92,7 +92,7 @@ # Truncate the eigendecomposition. Since we know the true rank of the # covariance matrix, we enforce it here. -eigs_est_trunc = Volume(eigs_est[: num_vols - 1]) +eigs_est_trunc = eigs_est[: num_vols - 1] lambdas_est_trunc = lambdas_est[: num_vols - 1, : num_vols - 1] # Estimate the coordinates in the eigenbasis. Given the images, we find the From 86d651011ffab64d28db14993c50b5acb95cd730 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:40:21 -0400 Subject: [PATCH 292/424] update starfile --- gallery/tutorials/starfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/starfile.py b/gallery/tutorials/starfile.py index 819accb1ac..ab9dcc40ed 100644 --- a/gallery/tutorials/starfile.py +++ b/gallery/tutorials/starfile.py @@ -61,7 +61,7 @@ # which is wrapper on an ndarray representing a stack of one or more 3d volumes. # We will visualize the data via orthogonal projections along the three axes. -vol = mean_est[0] +vol = mean_est.asnumpy()[0] # Visualize volume L = vol.shape[0] # Plots From aacf7508def03acf477cbd9a9b6149706f7c9e18 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:40:43 -0400 Subject: [PATCH 293/424] update cov2d tutorial Image return --- gallery/tutorials/cov2d_simulation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gallery/tutorials/cov2d_simulation.py b/gallery/tutorials/cov2d_simulation.py index aac77f0585..6e1d11a86c 100644 --- a/gallery/tutorials/cov2d_simulation.py +++ b/gallery/tutorials/cov2d_simulation.py @@ -235,19 +235,19 @@ # plot the first images at different stages idm = 0 plt.subplot(2, 2, 1) -plt.imshow(-imgs_noise[idm], cmap="gray") +plt.imshow(-imgs_noise.asnumpy()[idm], cmap="gray") plt.colorbar() plt.title("Noise") plt.subplot(2, 2, 2) -plt.imshow(imgs_clean[idm], cmap="gray") +plt.imshow(imgs_clean.asnumpy()[idm], cmap="gray") plt.colorbar() plt.title("Clean") plt.subplot(2, 2, 3) -plt.imshow(imgs_est[idm], cmap="gray") +plt.imshow(imgs_est.asnumpy()[idm], cmap="gray") plt.colorbar() plt.title("Estimated") plt.subplot(2, 2, 4) -plt.imshow(imgs_est[idm] - imgs_clean[idm], cmap="gray") +plt.imshow(imgs_est.asnumpy()[idm] - imgs_clean.asnumpy()[idm], cmap="gray") plt.colorbar() plt.title("Clean-Estimated") plt.tight_layout() From d89ca00d771a0eb272426cae7328328aaea84b88 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 14:51:57 -0400 Subject: [PATCH 294/424] update intro with Image return --- gallery/tutorials/lecture_feature_demo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 80f6fa94f2..e5080e9c4a 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -103,7 +103,7 @@ # We'll use three orthographic projections, one per axis fig, axs = plt.subplots(1, 3) for i in range(3): - axs[i].imshow(np.sum(v2[0], axis=i), cmap="gray") + axs[i].imshow(np.sum(v2.asnumpy()[0], axis=i), cmap="gray") plt.show() # %% From 6effd68964f50cb541988a63f5b232a8fdbdf157 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 15:20:14 -0400 Subject: [PATCH 295/424] update class avg tutorial with Image return --- gallery/tutorials/class_averaging.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index 4d968dbb3a..b5849d3ebb 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -12,7 +12,6 @@ from PIL import Image as PILImage from aspire.classification import RIRClass2D, TopClassSelector -from aspire.image import Image from aspire.noise import WhiteNoiseAdder from aspire.source import ArrayImageSource # Helpful hint if you want to BYO array. from aspire.utils import gaussian_2d @@ -197,7 +196,7 @@ logger.info(f"Class {review_class}'s neighors: {classes[review_class]}") # Report the identified neighbors -Image(noisy_src.images[:][classes[review_class]]).show() +noisy_src.images[classes[review_class]].show() # Display the averaged result avgs.images[review_class].show() From ea72a6ba97ff2b08ef6421ec9a3ae3e03ad71940 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 20 Mar 2023 15:25:41 -0400 Subject: [PATCH 296/424] break out self-correlation step into function. Use all_pairs for loop. --- src/aspire/abinitio/commonline_cn.py | 199 ++++++++++++++------------- 1 file changed, 102 insertions(+), 97 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index b3ae9247a6..3802c963e5 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -4,7 +4,15 @@ from numpy.linalg import norm from aspire.abinitio import CLSymmetryC3C4 -from aspire.utils import J_conjugate, Rotation, anorm, cyclic_rotations, tqdm, trange +from aspire.utils import ( + J_conjugate, + Rotation, + all_pairs, + anorm, + cyclic_rotations, + tqdm, + trange, +) from aspire.utils.random import Random, randn logger = logging.getLogger(__name__) @@ -81,11 +89,7 @@ def _check_symmetry(self, symmetry): self.order = _order def _estimate_relative_viewing_directions(self): - n_img = self.n_img - n_theta = self.n_theta pf = self.pf - max_shift_1d = self.max_shift - shift_step = self.shift_step # Generate candidate rotation matrices and the common-line and # self-common-line indices induced by those rotations. @@ -104,7 +108,7 @@ def _estimate_relative_viewing_directions(self): # Generate shift phases. r_max = pf.shape[-1] shifts, shift_phases, _ = self._generate_shift_phase_and_filter( - r_max, max_shift_1d, shift_step + r_max, self.max_shift, self.shift_step ) n_shifts = len(shifts) all_shift_phases = shift_phases.T @@ -115,119 +119,120 @@ def _estimate_relative_viewing_directions(self): # Pre-compute shifted pf's. pf_shifted = (pf * all_shift_phases[:, None, None]).swapaxes(0, 1) - pf_shifted = pf_shifted.reshape((n_img, n_shifts * (n_theta // 2), r_max)) + pf_shifted = pf_shifted.reshape( + (self.n_img, n_shifts * (self.n_theta // 2), r_max) + ) # Step 1: pre-calculate the likelihood with respect to the self-common-lines. - scores_self_corrs = np.zeros((n_img, n_cands), dtype=self.dtype) - logger.info("Computing likelihood wrt self common-lines.") - for i in trange(n_img): - # Compute correlation of pf[i] with itself over all shifts. - corrs = np.real(pf_shifted[i] @ np.conj(pf_full[i]).T) - corrs = np.reshape(corrs, (n_shifts, n_theta // 2, n_theta)) - corrs_cands = np.max( - (corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 - ) - - scores_self_corrs[i] = np.mean(corrs_cands, axis=1) + scores_self_corrs = self._scl_likelihood(pf_shifted, pf_full, scls_inds) # Step 2: Compute the likelihood for each pair of candidate matrices with respect - # to the common-lines they induce. + # to the common-lines they induce. Then compute estimates for viis and vijs. logger.info("Computing pairwise likelihood.") - n_vijs = n_img * (n_img - 1) // 2 + n_vijs = self.n_img * (self.n_img - 1) // 2 vijs = np.zeros((n_vijs, 3, 3), dtype=self.dtype) - viis = np.zeros((n_img, 3, 3), dtype=self.dtype) + viis = np.zeros((self.n_img, 3, 3), dtype=self.dtype) rots_symm = cyclic_rotations(self.order, self.dtype).matrices - c = 0 # List of MeanOuterProductEstimator instances. # Used to keep a running mean of J-synchronized estimates for vii. mean_est = [] - for _ in range(n_img): + for _ in range(self.n_img): mean_est.append(MeanOuterProductEstimator()) - with tqdm(total=n_vijs) as pbar: - for i in range(n_img): - for j in range(i + 1, n_img): - # Compute correlation. - corrs_ij = pf_shifted[i] @ np.conj(pf_full[j]).T - - # Max out over shifts. - corrs_ij = np.max( - np.reshape( - np.real(corrs_ij), (n_shifts, n_theta // 2, n_theta) - ), - axis=0, - ) - - # Arrange correlation based on common lines induced by candidate rotations. - corrs = corrs_ij[cijs_inds[..., 0], cijs_inds[..., 1]] - corrs = np.reshape( - corrs, (-1, self.order, n_theta_ijs // self.order) - ) - # Take the mean over all symmetric common lines. - corrs = np.mean(corrs, axis=1) - corrs = np.reshape( - corrs, - ( - self.n_points_sphere, - self.n_points_sphere, - n_theta_ijs // self.order, - ), - ) - - # Self common-lines are invariant to n_theta_ijs (i.e., in-plane rotation angles) so max them out. - opt_theta_ij_ind_per_sphere_points = np.argmax(corrs, axis=-1) - corrs = np.max(corrs, axis=-1) - - # Maximum likelihood while taking into consideration both cls and scls. - corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j]) - - # Extract the optimal candidates. - opt_sphere_i, opt_sphere_j = np.unravel_index( - np.argmax(corrs), corrs.shape - ) - opt_theta_ij = opt_theta_ij_ind_per_sphere_points[ - opt_sphere_i, opt_sphere_j - ] - - opt_Ri_tilde = Ris_tilde[opt_sphere_i] - opt_Rj_tilde = Ris_tilde[opt_sphere_j] - opt_R_theta_ij = R_theta_ijs[opt_theta_ij] - - # Compute the estimate of vi*vi.T as given by j. - vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) - mean_est[i].push(vii_j) - - # Compute the estimate of vj*vj.T as given by i. - vjj_i = np.mean(opt_Rj_tilde.T @ rots_symm @ opt_Rj_tilde, axis=0) - mean_est[j].push(vjj_i) - - # Compute the estimate of vi*vj.T. - vijs[c] = np.mean( - opt_Ri_tilde.T @ rots_symm @ opt_R_theta_ij @ opt_Rj_tilde, - axis=0, - ) - - c += 1 - pbar.update() - - # There are conflicting methods betweeen the paper and the Matlab code for - # finding the optimal estimate. The paper suggests using SVD to find the - # estimate for vii which is closest to rank 1. The Matlab code takes the - # median over the stack of all estimates for vii. Here we have implemented - # a method which J-synchronizes the estimates prior to taking the mean. - # See issue #869 for more details. - viis[i] = mean_est[i].synchronized_mean() + pairs = all_pairs(self.n_img) + for ind, (i, j) in enumerate(tqdm(pairs)): + # Compute correlation. + corrs_ij = np.real(pf_shifted[i] @ np.conj(pf_full[j]).T) + + # Max out over shifts. + corrs_ij = np.max( + np.reshape(corrs_ij, (n_shifts, self.n_theta // 2, self.n_theta)), + axis=0, + ) + + # Arrange correlation based on common lines induced by candidate rotations. + corrs = corrs_ij[cijs_inds[..., 0], cijs_inds[..., 1]] + corrs = np.reshape( + corrs, + ( + self.n_points_sphere, + self.n_points_sphere, + self.order, + n_theta_ijs // self.order, + ), + ) + + # Take the mean over all symmetry induced common lines. + corrs = np.mean(corrs, axis=-2) + + # Self common-lines are invariant to n_theta_ijs (i.e., in-plane rotation angles) + # so we max them out to estimate viis, but hold onto the index for the best + # in-plane rotation to compute the estimate for vijs. + opt_theta_ij_ind_per_sphere_points = np.argmax(corrs, axis=-1) + corrs = np.max(corrs, axis=-1) + + # Maximum likelihood while taking into consideration both cls and scls. + corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j]) + + # Extract the optimal candidates. + opt_sphere_i, opt_sphere_j = np.unravel_index(np.argmax(corrs), corrs.shape) + opt_theta_ij = opt_theta_ij_ind_per_sphere_points[ + opt_sphere_i, opt_sphere_j + ] + + opt_Ri_tilde = Ris_tilde[opt_sphere_i] + opt_Rj_tilde = Ris_tilde[opt_sphere_j] + opt_R_theta_ij = R_theta_ijs[opt_theta_ij] + + # Compute the estimate of vi*vi.T as given by j. + vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) + mean_est[i].push(vii_j) + + # Compute the estimate of vj*vj.T as given by i. + vjj_i = np.mean(opt_Rj_tilde.T @ rots_symm @ opt_Rj_tilde, axis=0) + mean_est[j].push(vjj_i) + + # Compute the estimate of vi*vj.T. + vijs[ind] = np.mean( + opt_Ri_tilde.T @ rots_symm @ opt_R_theta_ij @ opt_Rj_tilde, + axis=0, + ) + + # There are conflicting methods betweeen the paper and the Matlab code for + # finding the optimal estimates for viis. The paper suggests using SVD to find + # the estimate for vii which is closest to rank 1. The Matlab code takes the + # median over the stack of all estimates for vii. Here we have implemented + # a method which J-synchronizes the estimates prior to taking the mean. + # See issue #869 for more details. + for i in range(self.n_img): + viis[i] = mean_est[i].synchronized_mean() # As we are using a mean to get the estimates, viis, the estimate will not be rank-1 # So we use SVD to find a close rank-1 approximation. U, S, V = np.linalg.svd(viis) - S_rank1 = np.zeros((n_img, 3, 3), dtype=self.dtype) + S_rank1 = np.zeros((self.n_img, 3, 3), dtype=self.dtype) S_rank1[:, 0, 0] = S[:, 0] viis_rank1 = U @ S_rank1 @ V return vijs, viis_rank1 + def _scl_likelihood(self, pf_shifted, pf_full, scls_inds): + scores_self_corrs = np.zeros((self.n_img, len(scls_inds)), dtype=self.dtype) + logger.info("Computing likelihood wrt self common-lines.") + for i in trange(self.n_img): + # Compute correlation of pf[i] with itself over all shifts. + corrs = np.real(pf_shifted[i] @ np.conj(pf_full[i]).T) + # Reshape to (n_shifts, n_theta//2, n_theta). + corrs = np.reshape(corrs, (-1, self.n_theta // 2, self.n_theta)) + corrs_cands = np.max( + (corrs[:, scls_inds[:, :, 0], scls_inds[:, :, 1]]), axis=0 + ) + + scores_self_corrs[i] = np.mean(corrs_cands, axis=1) + + return scores_self_corrs + def _compute_scls_inds(self, Ris_tilde): """ Compute self-common-lines indices induced by candidate rotations. From 214baa549ca826a0ba71365932085404bb9de2b5 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Mon, 20 Mar 2023 15:45:58 -0400 Subject: [PATCH 297/424] unused variable --- src/aspire/abinitio/commonline_cn.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 3802c963e5..2bdb91d408 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -102,7 +102,6 @@ def _estimate_relative_viewing_directions(self): ) cijs_inds = self._compute_cls_inds(Ris_tilde, R_theta_ijs) scls_inds = self._compute_scls_inds(Ris_tilde) - n_cands = len(Ris_tilde) n_theta_ijs = len(R_theta_ijs) # Generate shift phases. From ff274fa8e61291b5943c555d7df384addc470e2c Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 20 Mar 2023 16:08:39 -0400 Subject: [PATCH 298/424] Missing experimet imshow Image return patches --- gallery/experiments/experimental_abinitio_pipeline.py | 2 +- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline.py b/gallery/experiments/experimental_abinitio_pipeline.py index 883485838d..180f17b4b7 100644 --- a/gallery/experiments/experimental_abinitio_pipeline.py +++ b/gallery/experiments/experimental_abinitio_pipeline.py @@ -186,5 +186,5 @@ # Peek at result if interactive: - plt.imshow(np.sum(estimated_volume[0], axis=-1)) + plt.imshow(np.sum(estimated_volume.asnumpy()[0], axis=-1)) plt.show() diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 59820cfffc..82ce37e2ee 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -238,5 +238,5 @@ def noise_function(x, y): # Peek at result if interactive: - plt.imshow(np.sum(estimated_volume[0], axis=-1)) + plt.imshow(np.sum(estimated_volume.asnumpy()[0], axis=-1)) plt.show() From 3cb323431f32844666ec7d76d79347ee1a7c9073 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 08:01:51 -0400 Subject: [PATCH 299/424] Return non writable view from 'asnumpy()' --- src/aspire/image/image.py | 11 ++++++++++- src/aspire/volume/volume.py | 9 ++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index 42ea2a8fed..2d56bb2847 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -248,7 +248,16 @@ def __repr__(self): return msg def asnumpy(self): - return self._data + """ + Return image data as a (, resolution, resolution) + read-only array view. + + :return: read-only ndarray view + """ + + view = self._data.view() + view.flags.writeable = False + return view def copy(self): return self.__class__(self._data.copy()) diff --git a/src/aspire/volume/volume.py b/src/aspire/volume/volume.py index e334f4df44..9c9e9f95df 100644 --- a/src/aspire/volume/volume.py +++ b/src/aspire/volume/volume.py @@ -103,11 +103,14 @@ def __init__(self, data, dtype=None): def asnumpy(self): """ - Return volume as a (n_vols, resolution, resolution, resolution) array. + Return volume data as a (, resolution, resolution, + resolution) read-only array view. - :return: ndarray + :return: read-only ndarray view """ - return self._data + view = self._data.view() + view.flags.writeable = False + return view def astype(self, dtype, copy=True): """ From 1b552ce5eccba13c3ee1304baa7729a484ab78bd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 08:16:03 -0400 Subject: [PATCH 300/424] Test that Image and Volume 'asnumpy()' views raise on assignment --- tests/test_image.py | 13 +++++++++++++ tests/test_volume.py | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/tests/test_image.py b/tests/test_image.py index 9d80f1ff3b..3060a34360 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -292,3 +292,16 @@ def testShow(): """ im = Image(np.random.random((3, 8, 8))) im.show() + + +def test_asnumpy_readonly(): + """ + Attempting assignment should raise an error. + """ + ary = np.random.random((3, 8, 8)) + im = Image(ary) + vw = im.asnumpy() + + # Attempt assignment + with raises(ValueError, match=r".*destination is read-only.*"): + vw[0, 0, 0] = 123 diff --git a/tests/test_volume.py b/tests/test_volume.py index b6611fc5db..2ebac31e38 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -463,3 +463,16 @@ def testMultiDimBroadcast(self): X = self.data_12 + self.data_1 self.assertTrue(np.allclose(X[0], 2 * self.data_1)) self.assertTrue(np.allclose(X[1], self.data_1 + self.data_2)) + + +def test_asnumpy_readonly(): + """ + Attempting assignment should raise an error. + """ + ary = np.random.random((3, 8, 8, 8)) + im = Volume(ary) + vw = im.asnumpy() + + # Attempt assignment + with raises(ValueError, match=r".*destination is read-only.*"): + vw[0, 0, 0, 0] = 123 From 4f0e6e535dbce8233b860df730ca2c98c090d521 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 08:34:09 -0400 Subject: [PATCH 301/424] Fixup case of image data modifcation. --- src/aspire/ctf/ctf_estimator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index f3b3ff53bb..7c966820fd 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -502,7 +502,7 @@ def pca(self, signal, pixel_size, g_min, g_max): X = grid["x"] Y = grid["y"] - signal -= np.min(signal) + signal = signal - np.min(signal) rad_sq_min = N * pixel_size / g_min rad_sq_max = N * pixel_size / g_max From d3ef0a97db3ced3710ce6a710f3b25a208b7b88e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 09:44:41 -0400 Subject: [PATCH 302/424] Resolve --- gallery/tutorials/lecture_feature_demo.py | 308 ---------------------- 1 file changed, 308 deletions(-) delete mode 100644 gallery/tutorials/lecture_feature_demo.py diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py deleted file mode 100644 index e5080e9c4a..0000000000 --- a/gallery/tutorials/lecture_feature_demo.py +++ /dev/null @@ -1,308 +0,0 @@ -""" -ASPIRE-Python Introduction -========================== - -In this notebook we will introduce some code from ASPIRE-Python -that corresponds to topics from MATH586. -""" - -# %% -# Imports -# ------- -# First we import some of the usual suspects. In addition, we import some classes from -# the ASPIRE package that we will use throughout this tutorial. - -# %% -# Installation -# ^^^^^^^^^^^^ -# -# Attempt to install ASPIRE on your machine. ASPIRE can generally install on -# Linux, Mac, and Windows -# under Anaconda Python, by following the instructions in the README. -# `The instructions for developers is the most comprehensive -# `_. -# Linux is the most tested platform. -# -# ASPIRE requires some resources to run, so if you wouldn't run typical data -# science codes on your machine (maybe a netbook for example), -# you may use Tiger/Adroit/Della etc if you have access. -# After logging into Tiger, ``module load anaconda3/2020.7`` -# and continue to follow the anaconda instructions for -# developers in the link above. -# Those instructions should create a working environment for tinkering with -# ASPIRE code found in this notebook. - -import logging -import os - -import matplotlib.pyplot as plt -import numpy as np - -from aspire.noise import ( - AnisotropicNoiseEstimator, - CustomNoiseAdder, - WhiteNoiseAdder, - WhiteNoiseEstimator, -) -from aspire.operators import FunctionFilter -from aspire.source import RelionSource, Simulation -from aspire.utils import Rotation -from aspire.volume import Volume - -# %% -# ``Image`` Class -# --------------- -# -# The `Image `_ class -# is a thin wrapper over numpy arrays for a stack containing 1 or more images. -# In this notebook we won't be working directly with the ``Image`` class a lot, but it will be one of the fundemental structures behind the scenes. -# A lot of ASPIRE code passes around ``Image`` and ``Volume`` classes. -# -# Examples of using the Image class can be found in: -# -# - ``gallery/tutorials/basic_image_array.py`` -# -# - ``gallery/tutorials/image_class.py`` - -# %% -# ``Volume`` Class -# ---------------- -# -# Like ``Image``, the `Volume `_ class -# is a thin wrapper over numpy arrays that provides specialized methods for a stack containing 1 or more volumes. -# -# Here we will instantiate a Volume using a numpy array and use it to downsample to a desired resolution (64 should be good). -# For the data source I chose to download a real volume density map from `EMDB `_. -# The download was uncompressed in my local directory. The notebook defaults to a small low resolution sample file you may use to sanity check. -# Unfortunately real data can be quite large so we do not ship it with the repo. - -# %% -# Initialize Volume -# ----------------- - -# A low res example file is included in the repo as a sanity check. -# We can instantiate this as an ASPIRE Volume instance using ``Volume.load()``. -DATA_DIR = "data" -v = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) - -# More interesting data requires downloading locally. -# v = Volume.load("path/to/EMD-2660/map/emd_2660.map") - -# Downsample the volume to a desired resolution -img_size = 64 -# Volume.downsample() returns a new Volume instance. -# We will use this lower resolution volume later, calling it `v2`. -v2 = v.downsample(img_size) -L = v2.resolution - -# %% -# Plot Data -# --------- - -# Alternatively, for quick sanity checking purposes we can view as a contour plot. -# We'll use three orthographic projections, one per axis -fig, axs = plt.subplots(1, 3) -for i in range(3): - axs[i].imshow(np.sum(v2.asnumpy()[0], axis=i), cmap="gray") -plt.show() - -# %% -# ``Rotation`` Class - Generating Random Rotations -# ------------------------------------------------ -# -# To get general projections this brings us to generating random rotations which we will apply to our volume. -# -# While you may bring your own 3x3 matrices or generate manually (say from your own Euler angles), -# ASPIRE has a `Rotation class `_ -# which can do this random rotation generation for us. It also has some other utility methods if you would want to compare with something manual. -# -# The following code will generate some random rotations, and use the ``Volume.project()`` method to return an ``Image`` instance representing the stack of projections. -# We can display projection images using the ``Image.show()`` method. - -num_rotations = 2 -rots = Rotation.generate_random_rotations(n=num_rotations, seed=12345) - -# %% -# We can access the numpy array holding the actual stack of 3x3 matrices: -logging.info(rots) -logging.info(rots.matrices) - -# Using the first (and in this case, only) volume, compute projections using the stack of rotations: -projections = v.project(0, rots) -logging.info(projections) - -# %% -# ``project()`` returns an Image instance, so we can call ``show``. - -projections.show() -# Neat, we've generated random projections of some real data. - -# %% -# The ``source`` Package -# ---------------------- -# -# `aspire.source `_ -# package contains a collection of data source interfaces. -# The idea is that we can design an experiment using a synthetic ``Simulation`` source or our own provided array via ``ArrayImageSource``; -# then later swap out the source for a large experimental data set using something like ``RelionSource``. -# -# We do this because the experimental datasets are too large to fit in memory. -# They cannot be provided as a massive large array, and instead require methods to orchestrate batching. -# Depending on the application, they may also require corresponding batched algorithms. -# The ``Source`` classes try to make most of this opaque to an end user. Ideally we can swap one source for another. -# -# For now we will build up to the creation and application of synthetic data set based on the real volume data used previously. - -# %% -# ``Simulation`` Class -# -------------------- -# -# Generating realistic synthetic data sources is a common task. -# The process of generating then projecting random rotations is integrated into the -# `Simulation `_ class. -# Using ``Simulation``, we can generate arbitrary numbers of projections for use in experiments. -# Later we will demonstrate additional features which allow us to create more realistic data sources. - -num_imgs = 100 # Total images in our source. -# Generate a Simulation instance based on the original volume data. -sim = Simulation(L=v.resolution, n=num_imgs, vols=v) -# Display the first 10 images -sim.images[:10].show() # Hi Res - -# Repeat for the lower resolution (downsampled) volume v2. -sim2 = Simulation(L=v2.resolution, n=num_imgs, vols=v2) -sim2.images[:10].show() # Lo Res - -# Note both of those simulations have the same rotations -# because they had the same seed by default, -# We can set our own seed to get a different random samples (of rotations). -sim_seed = Simulation(L=v.resolution, n=num_imgs, vols=v, seed=42) -sim_seed.images[:10].show() - -# We can also view the rotations used to create these projections -# logging.info(sim2.rotations) # Commented due to long output - -# %% -# Simulation with Filters and Noise -# --------------------------------- -# -# Filters -# ^^^^^^^ -# -# `Filters `_ -# are a collection of classes which once configured can be applied to ``Source`` pipelines. -# Common filters we might use are ``ScalarFilter``, ``PowerFilter``, ``FunctionFilter``, and ``CTFFilter``. -# ``CTFFilter`` is detailed in the ``ctf.py`` demo. -# -# Adding to Simulation -# ^^^^^^^^^^^^^^^^^^^^ -# -# We can customize Sources by adding stages to their generation pipeline. -# In this case of a Simulation source, we want to corrupt the projection images with significant noise. -# -# First we create a constant two dimension filter (constant value set to our desired noise variance). -# Then when used in the ``noise_filter``, this scalar will be multiplied by a random sample. -# Similar to before, if you require a different sample, this would be controlled via a ``seed``. - -# Get the sample variance -var = np.var(sim2.images[:].asnumpy()) -logging.info(f"Sample Variance: {var}") -target_noise_variance = 100.0 * var -logging.info(f"Target Noise Variance: {target_noise_variance}") -# Then create a NoiseAdder based on that variance. -white_noise_adder = WhiteNoiseAdder(target_noise_variance) - -# We can create a similar simulation with this additional noise_filter argument: -sim3 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder) -sim3.images[:10].show() -# These should be rather noisy now ... - -# %% -# More Advanced Noise - Whitening -# ------------------------------- -# -# We can estimate the noise across the stack of images -# -# The ``noise`` Package -# ^^^^^^^^^^^^^^^^^^^^^ -# -# The `aspire.noise `_ -# package contains several useful classes for generating and estimating different types of noise. -# -# In this case, we know the noise to be white, so we can proceed directly to -# `WhiteNoiseEstimator `_. The noise estimators consume from a ``Source``. -# -# The white noise estimator should log a diagnostic variance value. How does this compare with the known noise variance above? - -# %% - -# Create another Simulation source to tinker with. -sim_wht = Simulation( - L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder -) - -# Estimate the white noise. -noise_estimator = WhiteNoiseEstimator(sim_wht) -logging.info(noise_estimator.estimate()) - -# %% -# A Custom ``FunctionFilter`` -# --------------------------- -# -# We will now apply some more interesting noise, using a custom function, and then apply a ``whitening`` process to our data. -# -# Using ``FunctionFilter`` we can create our own custom functions to apply in a pipeline. -# Here we want to apply a custom filter as a noise adder. We can use a function of two variables for example. - - -def noise_function(x, y): - return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) - - -# In python, functions are first class objects. -# We take advantage of that to pass this function around as a variable. -# It will be evaluated later... -custom_noise = CustomNoiseAdder(noise_filter=FunctionFilter(noise_function)) - -# Create yet another Simulation source to tinker with. -sim4 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=custom_noise) -sim4.images[:10].show() - -# %% -# Noise Whitening -# --------------- -# -# Applying the ``Simulation.whiten()`` method just requires passing the filter corresponding to the estimated noise instance. -# Then we can inspect some of the whitened images. While noise is still present, we can see a dramatic change. - -# Estimate noise. -aiso_noise_estimator = AnisotropicNoiseEstimator(sim4) - -# Whiten based on the estimated noise -sim4.whiten(aiso_noise_estimator.filter) - -# What do the whitened images look like... -sim4.images[:10].show() - -# %% -# Real Experimental Data - ``RelionSource`` -# ----------------------------------------- -# -# Now that we have some basics, -# we can try to replace the simulation with a real experimental data source. -# -# Lets attempt the same CL experiment, but with a ``RelionSource``. - -src = RelionSource( - "data/sample_relion_data.star", - data_folder="", - pixel_size=5.0, - max_rows=1024, -) - -src.downsample(img_size) - -src.images[:10].show() - -# %% -# We have hit the point where we need denoising algorithms to perform orientation estimation as demonstrated in our ``pipeline_demo.py``. From 9a841b6af338c6c95c922d5112582bc3c7d7201e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 10:53:23 -0400 Subject: [PATCH 303/424] remove unneeded copy() --- gallery/tutorials/basic_image_array.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/basic_image_array.py b/gallery/tutorials/basic_image_array.py index 1034746ec1..fdcf00bfbb 100644 --- a/gallery/tutorials/basic_image_array.py +++ b/gallery/tutorials/basic_image_array.py @@ -131,7 +131,7 @@ def noise_function(x, y): imgs_src = ArrayImageSource(imgs_with_noise) # We'll copy the orginals for comparison later, before we process them further. -noisy_imgs_copy = imgs_src.images[:n_imgs].asnumpy().copy() +noisy_imgs_copy = imgs_src.images[:n_imgs].asnumpy() # One of the tools we can use is a NoiseEstimator, # which consumes from a Source. From 72726e9f6de6c33b9ede706847c94f91e307e4a8 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 11:13:35 -0400 Subject: [PATCH 304/424] Add example of IndexedSource --- gallery/tutorials/aspire_introduction.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index aea8406fd3..07bb604ed0 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -374,9 +374,27 @@ sim = Simulation(n=num_imgs, vols=v2, seed=42) sim.images[:10].show() -# We can also view the rotations used to create these projections +# We can also view the rotations used to create these projections. logging.info(sim.rotations) +# % +# Given any ``Source``, we can also take slices using typical slicing +# syntax, or provide our own iterable of indices. + +sim_evens = sim[0::2] +sim_odds = sim[1::2] + +# We can also generate random selections. +# Shuffle indices then take the first 5. +subset_shuffled_inds = np.random.permutation(sim.n)[:5] +sim_shuffled_subset = sim[subset_shuffled_inds] + +# % +# Underneath those slices, ASPIRE relies on ``IndexedSource``, which +# we can also call direcly to remap indices. + +sim_shuffled_subset = IndexedSource(sim, sim_shuffled_subset) + # %% # The ``noise`` Package From 73722977a8a81421fe9418a2ac27ac23f6c94b68 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 21 Mar 2023 12:55:25 -0400 Subject: [PATCH 305/424] Tutorial file path cleanup/consistency --- gallery/tutorials/aspire_introduction.py | 16 +++++++++------- gallery/tutorials/pipeline_demo.py | 7 ++++--- gallery/tutorials/tutorials/apple_picker.py | 12 +++++++----- gallery/tutorials/tutorials/cov2d_simulation.py | 6 ++++-- .../tutorials/generating_volume_projections.py | 6 +++--- gallery/tutorials/tutorials/image_class.py | 4 ++-- gallery/tutorials/tutorials/image_expansion.py | 6 ++++-- .../tutorials/tutorials/orient3d_simulation.py | 6 ++++-- .../tutorials/tutorials/preprocess_imgs_sim.py | 6 ++++-- gallery/tutorials/tutorials/starfile.py | 9 +++++++-- 10 files changed, 48 insertions(+), 30 deletions(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 07bb604ed0..000a301a03 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -143,8 +143,8 @@ # A low res example file is included in the repo as a sanity check. # We can instantiate this as an ASPIRE Volume instance using # ``Volume.load()``. -DATA_DIR = "data" -v = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) +file_path = os.path.join(os.getcwd(), "data", "clean70SRibosome_vol_65p.mrc") +v = Volume.load(file_path) # More interesting data requires downloading locally. # v = Volume.load("path/to/EMD-2660/map/emd_2660.map") @@ -174,9 +174,9 @@ # """"""""" # For quick sanity checking purposes we can view some plots. # We'll use three orthographic projections, one per axis. -orthographic_projections = np.empty((3, L,L,L), dtype=v2.dtype) +orthographic_projections = np.empty((3, L, L, L), dtype=v2.dtype) for i in range(3): - orthographic_projections = np.sum(v2, axis=(0,i+1)) + orthographic_projections = np.sum(v2, axis=(0, i + 1)) Image(orthographic_projections).show() # %% @@ -386,14 +386,16 @@ # We can also generate random selections. # Shuffle indices then take the first 5. -subset_shuffled_inds = np.random.permutation(sim.n)[:5] -sim_shuffled_subset = sim[subset_shuffled_inds] +shuffled_inds = np.random.permutation(sim.n)[:5] +sim_shuffled_subset = sim[shuffled_inds] # % # Underneath those slices, ASPIRE relies on ``IndexedSource``, which # we can also call direcly to remap indices. -sim_shuffled_subset = IndexedSource(sim, sim_shuffled_subset) +from aspire.source import IndexedSource + +sim_shuffled_subset = IndexedSource(sim, shuffled_inds) # %% diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index bfa0c8747b..f52b2e186b 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -30,9 +30,10 @@ def download(url, save_path, chunk_size=1024 * 1024): fd.write(chunk) -if not os.path.exists("data/emd_2660.map"): +file_path = os.path.join(os.getcwd(), "data", "emd_2660.map") +if not os.path.exists(file_path): url = "https://ftp.ebi.ac.uk/pub/databases/emdb/structures/EMD-2660/map/emd_2660.map.gz" - download(url, "data/emd_2660.map") + download(url, file_path) # %% # Load a Volume @@ -42,7 +43,7 @@ def download(url, save_path, chunk_size=1024 * 1024): from aspire.volume import Volume # Load 80s Ribosome -original_vol = Volume.load("data/emd_2660.map", dtype=np.float32) +original_vol = Volume.load(file_path, dtype=np.float32) # Downsample the volume res = 41 diff --git a/gallery/tutorials/tutorials/apple_picker.py b/gallery/tutorials/tutorials/apple_picker.py index c3ba4803c3..701e77dfe0 100644 --- a/gallery/tutorials/tutorials/apple_picker.py +++ b/gallery/tutorials/tutorials/apple_picker.py @@ -4,8 +4,8 @@ We demonstrate ASPIRE's particle picking methods using the ``Apple`` class. """ - import logging +import os import matplotlib.pyplot as plt import mrcfile @@ -20,9 +20,11 @@ # # Here we demonstrate reading in and plotting a raw micrograph. -filename = "../data/falcon_2012_06_12-14_33_35_0.mrc" +file_path = os.path.join( + os.path.dirname(os.getcwd()), "data", "falcon_2012_06_12-14_33_35_0.mrc" +) -with mrcfile.open(filename, mode="r") as mrc: +with mrcfile.open(file_path, mode="r") as mrc: micro_img = mrc.data plt.title("Sample Micrograph") @@ -48,9 +50,9 @@ # Here we use the ``process_micrograph`` method from the ``Apple`` class to find particles in the micrograph. # It will also return an image suitable for display, and optionally save a jpg. -centers, particles_img = apple_picker.process_micrograph(filename, create_jpg=True) +centers, particles_img = apple_picker.process_micrograph(file_path, create_jpg=True) -# Note that if you only desire ``centers`` you may call ``process_micrograph_centers(filename,...)``. +# Note that if you only desire ``centers`` you may call ``process_micrograph_centers(file_path,...)``. # %% # Plot the Picked Particles diff --git a/gallery/tutorials/tutorials/cov2d_simulation.py b/gallery/tutorials/tutorials/cov2d_simulation.py index 18024e32d9..162eda4e80 100644 --- a/gallery/tutorials/tutorials/cov2d_simulation.py +++ b/gallery/tutorials/tutorials/cov2d_simulation.py @@ -24,7 +24,9 @@ logger = logging.getLogger(__name__) -DATA_DIR = "../data" +file_path = os.path.join( + os.path.dirname(os.getcwd()), "data", "clean70SRibosome_vol_65p.mrc" +) logger.info( @@ -83,7 +85,7 @@ f"Load 3D map and downsample 3D map to desired grids " f"of {img_size} x {img_size} x {img_size}." ) -vols = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc"), dtype=dtype) +vols = Volume.load(file_path, dtype=dtype) # Scale and downsample vols[0] /= np.max(vols[0]) diff --git a/gallery/tutorials/tutorials/generating_volume_projections.py b/gallery/tutorials/tutorials/generating_volume_projections.py index 58d7abeb16..48d82ce231 100644 --- a/gallery/tutorials/tutorials/generating_volume_projections.py +++ b/gallery/tutorials/tutorials/generating_volume_projections.py @@ -29,10 +29,10 @@ # -------------------- # This example starts with an mrc, which can be loaded as an ASPIRE Volume. -DATA_DIR = "../data" # Tutorial example data folder -v = Volume.load( - os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc"), dtype=np.float64 +file_path = os.path.join( + os.path.dirname(os.getcwd()), "data", "clean70SRibosome_vol_65p.mrc" ) +v = Volume.load(file_path, dtype=np.float64) # Then we downsample to 60x60x60 v.downsample(60) diff --git a/gallery/tutorials/tutorials/image_class.py b/gallery/tutorials/tutorials/image_class.py index b902bd14a2..439a2b7b0d 100644 --- a/gallery/tutorials/tutorials/image_class.py +++ b/gallery/tutorials/tutorials/image_class.py @@ -12,8 +12,8 @@ from aspire.image import Image from aspire.operators import CTFFilter -DATA_DIR = "../data" -img_data = np.load(os.path.join(DATA_DIR, "monuments.npy")) +file_path = os.path.join(os.path.dirname(os.getcwd()), "data", "monuments.npy") +img_data = np.load(file_path) img_data.shape, img_data.dtype # %% diff --git a/gallery/tutorials/tutorials/image_expansion.py b/gallery/tutorials/tutorials/image_expansion.py index a151299dee..d27093d4be 100644 --- a/gallery/tutorials/tutorials/image_expansion.py +++ b/gallery/tutorials/tutorials/image_expansion.py @@ -28,8 +28,10 @@ # ------------------- # Load the images from NumPy array, 10 images of 70S Ribosome with size 129 x 129 -DATA_DIR = "../data" -org_images = np.load(os.path.join(DATA_DIR, "example_data_np_array.npy")).T +file_path = os.path.join( + os.path.dirname(os.getcwd()), "data", "example_data_np_array.npy" +) +org_images = np.load(file_path).T # Set the sizes of images (129, 129) img_size = 129 diff --git a/gallery/tutorials/tutorials/orient3d_simulation.py b/gallery/tutorials/tutorials/orient3d_simulation.py index e7c401df13..86e9e94c03 100644 --- a/gallery/tutorials/tutorials/orient3d_simulation.py +++ b/gallery/tutorials/tutorials/orient3d_simulation.py @@ -19,7 +19,9 @@ logger = logging.getLogger(__name__) -DATA_DIR = "../data" +file_path = os.path.join( + os.path.dirname(os.getcwd()), "data", "clean70SRibosome_vol_65p.mrc" +) logger.info( "This script illustrates orientation estimation using " @@ -66,7 +68,7 @@ f"Load 3D map and downsample 3D map to desired grids " f"of {img_size} x {img_size} x {img_size}." ) -vols = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc"), dtype=dtype) +vols = Volume.load(file_path, dtype=dtype) vols = vols.downsample(img_size) # %% diff --git a/gallery/tutorials/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/tutorials/preprocess_imgs_sim.py index 8b6d6a0efd..86c44c7598 100644 --- a/gallery/tutorials/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/tutorials/preprocess_imgs_sim.py @@ -18,7 +18,9 @@ logger = logging.getLogger(__name__) -DATA_DIR = "../data" +file_path = os.path.join( + os.path.dirname(os.getcwd()), "data", "clean70SRibosome_vol_65p.mrc" +) # %% # Specify Parameters @@ -57,7 +59,7 @@ # Load the map file of a 70S ribosome and downsample the 3D map to desired resolution. logger.info("Load 3D map from mrc file") -vols = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) +vols = Volume.load(file_path) # Downsample the volume to a desired resolution and increase density # by 1.0e5 time for a better graph view diff --git a/gallery/tutorials/tutorials/starfile.py b/gallery/tutorials/tutorials/starfile.py index 58925ead53..1d263ed347 100644 --- a/gallery/tutorials/tutorials/starfile.py +++ b/gallery/tutorials/tutorials/starfile.py @@ -4,6 +4,8 @@ """ +import os + import matplotlib.pyplot as plt import numpy as np @@ -20,11 +22,14 @@ # They are intended to handle batching data conversion/prep behind the scenes. # Here we load a ".star" file using the RelionSource class +data_folder = os.path.join(os.path.dirname(os.getcwd()), "data") +file_path = os.path.join(data_folder, "sample_relion_data.star") + source = RelionSource( - "../data/sample_relion_data.star", + file_path, pixel_size=1.338, max_rows=10000, - data_folder="../data/", + data_folder=data_folder, ) # Reduce the resolution From 97345e782ae05909931df23fbe5b360e97c974b6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Mar 2023 08:11:47 -0400 Subject: [PATCH 306/424] Pipeline source slicing updates --- .../experiments/experimental_abinitio_pipeline_10028.py | 6 ++++-- .../experiments/experimental_abinitio_pipeline_10081.py | 7 +++++-- gallery/experiments/simulated_abinitio_pipeline.py | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index 5cbab4a01b..8e3e70d0c8 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -140,6 +140,8 @@ averager_src=src, num_procs=None, # Automatically configure parallel processing ) +# We'll continue our pipeline with the first `n_classes` from `avgs`. +avgs = avgs[:n_classes] if interactive: avgs.images[:10].show() @@ -154,8 +156,8 @@ logger.info("Begin Orientation Estimation") -# Run orientation estimation on first `n_classes` from `avgs`. -orient_est = CLSyncVoting(avgs[:n_classes], n_theta=360) +# Run orientation estimation on ``avgs``. +orient_est = CLSyncVoting(avgs, n_theta=360) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index e6de3752b9..bb5bba22cf 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -89,6 +89,9 @@ # Automatically configure parallel processing avgs = DefaultClassAvgSource(src, n_nbor=n_nbor, num_procs=None) +# We'll continue our pipeline with the first ``n_classes`` from ``avgs``. +avgs = avgs[:n_classes] + # %% # Common Line Estimation # ---------------------- @@ -98,8 +101,8 @@ logger.info("Begin Orientation Estimation") -# Run orientation estimation on first `n_classes` from `avgs`. -orient_est = CLSymmetryC3C4(avgs[:n_classes], symmetry="C4", n_theta=360, max_shift=0) +# Run orientation estimation on ``avgs``. +orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=360, max_shift=0) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 94317e101a..d3bb49cae4 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -168,6 +168,8 @@ def noise_function(x, y): averager_src=src, num_procs=None, # Automatically configure parallel processing ) +# We'll continue our pipeline with the first ``n_classes`` from ``avgs``. +avgs = avgs[:n_classes] if interactive: avgs.images[:10].show() @@ -187,8 +189,8 @@ def noise_function(x, y): indices = avgs.selection_indices true_rotations = src.rotations[indices[:n_classes]] -# Run orientation estimation on first `n_classes` from `avgs`. -orient_est = CLSyncVoting(avgs[:n_classes], n_theta=180) +# Run orientation estimation on ``avgs``. +orient_est = CLSyncVoting(avgs, n_theta=180) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations From 3ffb405a4b6a245dafabe309f3b5f71b37f8bdb1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Mar 2023 08:12:10 -0400 Subject: [PATCH 307/424] Pipeline source slicing updates --- gallery/tutorials/pipeline_demo.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 12c54abab9..80c898ac0d 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -193,6 +193,9 @@ def download(url, save_path, chunk_size=1024 * 1024): classifier=rir, ) +# We'll continue our pipeline with the first ``n_classes`` from ``avgs``. +avgs = avgs[:n_classes] + # %% # View the Class Averages @@ -221,8 +224,8 @@ def download(url, save_path, chunk_size=1024 * 1024): # Stash true rotations for later comparison true_rotations = src.rotations[:n_classes] -# Run orientation estimation on first `n_classes` from `avgs`. -orient_est = CLSyncVoting(avgs[:n_classes], n_theta=72) +# Run orientation estimation on ``avgs``. +orient_est = CLSyncVoting(avgs, n_theta=72) # Get the estimated rotations orient_est.estimate_rotations() From f3e011ac39c239628bedd940c81eab99ad90d3ab Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Mar 2023 15:31:20 -0400 Subject: [PATCH 308/424] IndexedSource and ImageSource bug fixes --- src/aspire/source/image.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index c5008d7af9..c3d12be677 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -293,6 +293,8 @@ def angles(self, values): :param values: Rotation angles in radians, as a n x 3 array :return: None """ + + values = values.astype(self.dtype) self._rotations = Rotation.from_euler(values) self.set_metadata( ["_rlnAngleRot", "_rlnAngleTilt", "_rlnAnglePsi"], np.rad2deg(values) @@ -306,6 +308,8 @@ def rotations(self, values): :param values: Rotation matrices as a n x 3 x 3 array :return: None """ + + values = values.astype(self.dtype) self._rotations = Rotation.from_matrix(values) self.set_metadata( ["_rlnAngleRot", "_rlnAngleTilt", "_rlnAnglePsi"], @@ -1104,7 +1108,7 @@ def __init__(self, src, indices, memory=None): # here, but it returns a Numpy array, which would need to be # converted back into Pandas for use below. So here we'll just # use `loc` to return a dataframe. - metadata = self.src._metadata.loc[self.index_map] + metadata = self.src._metadata.loc[self.index_map].copy() # Construct a fully formed ImageSource with this metadata super().__init__( @@ -1115,6 +1119,11 @@ def __init__(self, src, indices, memory=None): memory=memory, ) + # Create filter indices, these are required to pass unharmed through filter eval code + # that is potentially called by other methods later. + self.filter_indices = np.zeros(self.n, dtype=int) + self.unique_filters = [IdentityFilter()] + def _images(self, indices): """ Returns images from `self.src` corresponding to `indices` @@ -1124,7 +1133,13 @@ def _images(self, indices): :return: An `Image` object. """ mapped_indices = self.index_map[indices] - return self.src.images[mapped_indices] + # Load previous source image data and apply any transforms + # belonging to this IndexedSource. Note the previous source + # requires remapped indices, while the current source uses the + # `indices` arg directly. + return self.generation_pipeline.forward( + self.src.images[mapped_indices], indices + ) def __repr__(self): return f"{self.__class__.__name__} mapping {self.n} of {self.src.n} indices from {self.src.__class__.__name__}." From c0ece233e0f5c65caf219b83fd458de39f853003 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Wed, 22 Mar 2023 15:31:20 -0400 Subject: [PATCH 309/424] IndexedSource and ImageSource bug fixes --- src/aspire/source/image.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 569a38eade..ccd2a8f969 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -269,6 +269,8 @@ def angles(self, values): :param values: Rotation angles in radians, as a n x 3 array :return: None """ + + values = values.astype(self.dtype) self._rotations = Rotation.from_euler(values) self.set_metadata( ["_rlnAngleRot", "_rlnAngleTilt", "_rlnAnglePsi"], np.rad2deg(values) @@ -282,6 +284,8 @@ def rotations(self, values): :param values: Rotation matrices as a n x 3 x 3 array :return: None """ + + values = values.astype(self.dtype) self._rotations = Rotation.from_matrix(values) self.set_metadata( ["_rlnAngleRot", "_rlnAngleTilt", "_rlnAnglePsi"], @@ -1080,7 +1084,7 @@ def __init__(self, src, indices, memory=None): # here, but it returns a Numpy array, which would need to be # converted back into Pandas for use below. So here we'll just # use `loc` to return a dataframe. - metadata = self.src._metadata.loc[self.index_map] + metadata = self.src._metadata.loc[self.index_map].copy() # Construct a fully formed ImageSource with this metadata super().__init__( @@ -1091,6 +1095,11 @@ def __init__(self, src, indices, memory=None): memory=memory, ) + # Create filter indices, these are required to pass unharmed through filter eval code + # that is potentially called by other methods later. + self.filter_indices = np.zeros(self.n, dtype=int) + self.unique_filters = [IdentityFilter()] + def _images(self, indices): """ Returns images from `self.src` corresponding to `indices` @@ -1100,7 +1109,13 @@ def _images(self, indices): :return: An `Image` object. """ mapped_indices = self.index_map[indices] - return self.src.images[mapped_indices] + # Load previous source image data and apply any transforms + # belonging to this IndexedSource. Note the previous source + # requires remapped indices, while the current source uses the + # `indices` arg directly. + return self.generation_pipeline.forward( + self.src.images[mapped_indices], indices + ) def __repr__(self): return f"{self.__class__.__name__} mapping {self.n} of {self.src.n} indices from {self.src.__class__.__name__}." From f51f6853c7e0fdd8d5c0f1a97627d202e1fb2648 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 08:08:05 -0400 Subject: [PATCH 310/424] Save simulated pipeline experiment class averages. --- gallery/experiments/simulated_abinitio_pipeline.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index d3bb49cae4..c73e75388a 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -174,6 +174,8 @@ def noise_function(x, y): if interactive: avgs.images[:10].show() +# Save off the set of class average images. +avgs.save("simulated_abinitio_pipeline_class_averages.star", overwrite=True) # %% # Common Line Estimation From 250bdceb681b431051dcc452696ee9429b0105f6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 09:43:17 -0400 Subject: [PATCH 311/424] Workaround mutation of base image raising new immutable code --- src/aspire/classification/averager2d.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 1c49d84dc1..8d6d4a230d 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -89,8 +89,9 @@ def average( def _cls_images(self, cls, src=None): """ - Util to return images as an array for class k (provided as array `cls` ), - preserving the class/nbor order. + Util to return images as an array for class k (provided as + array `cls` ), preserving the class/nbor order. Note, Images + will be a read-only view, copy if mutations required. :param cls: An iterable (0/1-D array or list) that holds the indices of images to align. In class averaging, this would be a class. @@ -202,11 +203,11 @@ def _innerloop(i): # Get coefs in Composite_Basis if not provided as an argument. if coefs is None: # Retrieve relevant images directly from source. - neighbors_imgs = Image(self._cls_images(classes[i])) + neighbors_imgs = self._cls_images(classes[i]) # Do shifts if self.shifts is not None: - neighbors_imgs.shift(self.shifts[i]) + Image(neighbors_imgs).shift(self.shifts[i]) neighbors_coefs = self.composite_basis.evaluate_t(neighbors_imgs) else: @@ -332,17 +333,17 @@ def _innerloop(k): # Get the coefs for these neighbors if basis_coefficients is None: # Retrieve relevant images - neighbors_imgs = Image(self._cls_images(classes[k])) + neighbors_imgs = self._cls_images(classes[k]).copy() # We optionally can shift the base image by `_base_image_shift` # Shift in real space to avoid extra conversions if self._base_image_shift is not None: neighbors_imgs[0] = ( - neighbors_imgs[0].shift(self._base_image_shift).asnumpy() + Image(neighbors_imgs[0]).shift(self._base_image_shift).asnumpy()[0] ) # Evaluate_t into basis - nbr_coef = self.composite_basis.evaluate_t(neighbors_imgs) + nbr_coef = self.composite_basis.evaluate_t(Image(neighbors_imgs)) else: nbr_coef = basis_coefficients[classes[k]] @@ -471,7 +472,7 @@ def align(self, classes, reflections, basis_coefficients): # because we will mutate them with shifts in the loop. if basis_coefficients is None: original_coef = self.composite_basis.evaluate_t( - self._cls_images(classes[:, 0], src=self.src) + Image(self._cls_images(classes[:, 0], src=self.src)) ) else: original_coef = basis_coefficients[classes[:, 0], :].copy() From 581ce91dfb751e2e01989030979633a6d0f5ac62 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 09:43:53 -0400 Subject: [PATCH 312/424] Use index_map from IndexedSource --- gallery/experiments/experimental_abinitio_pipeline_10081.py | 6 ++---- gallery/experiments/simulated_abinitio_pipeline.py | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index bb5bba22cf..18ff2ac890 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -44,10 +44,8 @@ img_size = 32 # Downsample the images/reconstruction to a desired resolution n_classes = 2000 # How many class averages to compute. n_nbor = 100 # How many neighbors to stack -starfile_in = ( - "/scratch/ExperimentalData/staging/10081/data/Particles/micrographs/data.star" -) -data_folder = "../.." # This depends on the specific starfile entries. +starfile_in = "10081/data/particle_stacks/data.star" +data_folder = "." # This depends on the specific starfile entries. volume_output_filename = f"10081_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" pixel_size = 1.3 diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index c73e75388a..9bf2796035 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -188,8 +188,8 @@ def noise_function(x, y): # Stash true rotations for later comparison. # Note class selection re-ordered our images, so we remap the indices back to the original source. -indices = avgs.selection_indices -true_rotations = src.rotations[indices[:n_classes]] +indices = avgs.index_map # Also available from avgs.src.selection_indices[:n_classes] +true_rotations = src.rotations[indices] # Run orientation estimation on ``avgs``. orient_est = CLSyncVoting(avgs, n_theta=180) From 41b138f6de7f777c7a134e30520945e7bc932c7b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 09:45:31 -0400 Subject: [PATCH 313/424] tox checks --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- src/aspire/classification/averager2d.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 9bf2796035..ae1140501e 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -188,7 +188,7 @@ def noise_function(x, y): # Stash true rotations for later comparison. # Note class selection re-ordered our images, so we remap the indices back to the original source. -indices = avgs.index_map # Also available from avgs.src.selection_indices[:n_classes] +indices = avgs.index_map # Also available from avgs.src.selection_indices[:n_classes] true_rotations = src.rotations[indices] # Run orientation estimation on ``avgs``. diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 8d6d4a230d..9936857799 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -339,7 +339,9 @@ def _innerloop(k): # Shift in real space to avoid extra conversions if self._base_image_shift is not None: neighbors_imgs[0] = ( - Image(neighbors_imgs[0]).shift(self._base_image_shift).asnumpy()[0] + Image(neighbors_imgs[0]) + .shift(self._base_image_shift) + .asnumpy()[0] ) # Evaluate_t into basis From bfe8808e28aff31f4c3998a40aba5ca63429b07a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 10:32:09 -0400 Subject: [PATCH 314/424] tox checks --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index ae1140501e..dfe8326f24 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -188,7 +188,7 @@ def noise_function(x, y): # Stash true rotations for later comparison. # Note class selection re-ordered our images, so we remap the indices back to the original source. -indices = avgs.index_map # Also available from avgs.src.selection_indices[:n_classes] +indices = avgs.index_map # Also available from avgs.src.selection_indices[:n_classes] true_rotations = src.rotations[indices] # Run orientation estimation on ``avgs``. From b4f95c8ba9db5b3f15c5e184892206fe9f57c869 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 10:38:10 -0400 Subject: [PATCH 315/424] Build docs gallery as part of the CI now that is it faster --- .github/workflows/daily_workflow.yml | 33 ---------------------------- .github/workflows/workflow.yml | 29 +++++++++++++++++++++++- 2 files changed, 28 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/daily_workflow.yml diff --git a/.github/workflows/daily_workflow.yml b/.github/workflows/daily_workflow.yml deleted file mode 100644 index 2e860bff07..0000000000 --- a/.github/workflows/daily_workflow.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: ASPIRE Python Daily Build CI - -on: - schedule: - - cron: '0 0 * * *' - -jobs: - dev_docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - ref: develop - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.7' - - name: Install Dependencies - run: | - python -m pip install --upgrade pip - pip install -e ".[dev]" - - name: Build Sphinx docs - run: | - make clean - sphinx-apidoc -f -o ./source ../src -H Modules - make html - working-directory: ./docs - - name: Archive Sphinx docs - uses: actions/upload-artifact@v3 - with: - name: sphinx-docs - path: docs - retention-days: 7 diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index f2b9f0445a..7b34726738 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -96,6 +96,7 @@ jobs: # -n runs test in parallel using pytest-xdist pytest -n2 --durations=50 -s + # Build and Deploy production (master) docs. docs: if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest @@ -104,7 +105,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.8' - name: Install Dependencies run: | pip install -e ".[dev]" @@ -147,3 +148,29 @@ jobs: ASPIREDIR=${{ env.WORK_DIR }} pytest --durations=50 - name: Cleanup run: rm -rf ${{ env.WORK_DIR }} + + # Build branch's docs and gallery. + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.8' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + - name: Build Sphinx docs + run: | + make distclean + sphinx-apidoc -f -o ./source ../src -H Modules + make html + working-directory: ./docs + - name: Archive Sphinx docs + uses: actions/upload-artifact@v3 + with: + name: sphinx-docs + path: docs + retention-days: 7 From 41bd85f8af259f36d1f8b00a9ff13601f45569db Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 10:41:50 -0400 Subject: [PATCH 316/424] Unique workflow job names --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 7b34726738..3901414415 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -97,7 +97,7 @@ jobs: pytest -n2 --durations=50 -s # Build and Deploy production (master) docs. - docs: + docs_deploy: if: github.ref == 'refs/heads/master' runs-on: ubuntu-latest steps: From 63a2dfd222d2e143ae81c435c64f145fff164351 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 10:43:04 -0400 Subject: [PATCH 317/424] Require 'checks' passed before building docs --- .github/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 3901414415..dbf6dd7b10 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -151,6 +151,7 @@ jobs: # Build branch's docs and gallery. docs: + needs: check runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From dd50b8bcfe449a5d8721da87d6b31a75c0df3270 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 10:55:58 -0400 Subject: [PATCH 318/424] Save off class averages in experimental pipelines --- gallery/experiments/experimental_abinitio_pipeline_10028.py | 3 +++ gallery/experiments/experimental_abinitio_pipeline_10081.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index 8e3e70d0c8..ed1b22dd07 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -143,6 +143,9 @@ # We'll continue our pipeline with the first `n_classes` from `avgs`. avgs = avgs[:n_classes] +# Save off the set of class average images. +avgs.save("experimental_10028_class_averages.star", overwrite=True) + if interactive: avgs.images[:10].show() diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index 18ff2ac890..50a8e2ce3f 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -90,6 +90,9 @@ # We'll continue our pipeline with the first ``n_classes`` from ``avgs``. avgs = avgs[:n_classes] +# Save off the set of class average images. +avgs.save("experimental_10081_class_averages.star", overwrite=True) + # %% # Common Line Estimation # ---------------------- From 17094d66c59e757fba9c26f697fb2879e7184689 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 11:22:24 -0400 Subject: [PATCH 319/424] Revert incorrect string replacement. --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index b071399b89..296541c022 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -122,7 +122,7 @@ def noise_function(x, y): # Plot the noise profile for inspection if interactive: - plt.imshow(aiso_noise_estimator.evaluate_grid(L)) + plt.imshow(aiso_noise_estimator.filter.evaluate_grid(L)) plt.show() # Peek, what do the whitened images look like... From af9d801b5389a139140a7880b97ab22e33c5e7b6 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 23 Mar 2023 12:47:11 -0400 Subject: [PATCH 320/424] energy --> density --- tests/test_orient_symmetric.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index a474e6736c..58835e553b 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -37,9 +37,9 @@ # Method to instantiate a Simulation source and orientation estimation object. def source_orientation_objs(L, n_img, order, dtype): - # This Volume is hand picked to have a fairly even distribution of energy. + # This Volume is hand picked to have a fairly even distribution of density. # Due to the rotations used to generate symmetric volumes, some seeds will - # generate volumes with a high concentration of energy in the center causing + # generate volumes with a high concentration of denisty in the center causing # misidentification of common-lines. vol = CnSymmetricVolume( L=L, From 1a9edd0616738bc225970056a0bcb3454b7b19f1 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 23 Mar 2023 17:27:04 -0400 Subject: [PATCH 321/424] Fix 'id' pytest xdist bug, use class name instead of repr --- tests/test_class_src.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 8f6a2ac4a9..5a919f666c 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -117,7 +117,7 @@ def class_sim_fixture(dtype, img_size): @pytest.mark.parametrize( - "test_src_cls", CLS_SRCS, ids=lambda param: f"ClassSource={param}" + "test_src_cls", CLS_SRCS, ids=lambda param: f"ClassSource={param.__class__}" ) def test_basic_averaging(class_sim_fixture, test_src_cls): """ From d39a4ed0932577fb7b6a9c26ae8e114f0a361a03 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Mar 2023 06:56:20 -0400 Subject: [PATCH 322/424] =?UTF-8?q?Bump=20version:=200.10.1=20=E2=86=92=20?= =?UTF-8?q?0.11.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- README.md | 4 ++-- docs/source/conf.py | 2 +- docs/source/index.rst | 2 +- setup.py | 2 +- src/aspire/__init__.py | 2 +- src/aspire/config_default.yaml | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 110b7e038e..abcd3f4309 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.1 +current_version = 0.11.0 commit = True tag = True diff --git a/README.md b/README.md index e2280b26dc..6cd3797fb9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![codecov](https://codecov.io/gh/ComputationalCryoEM/ASPIRE-Python/branch/master/graph/badge.svg?token=3XFC4VONX0)](https://codecov.io/gh/ComputationalCryoEM/ASPIRE-Python) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.5657281.svg)](https://doi.org/10.5281/zenodo.5657281) -# ASPIRE - Algorithms for Single Particle Reconstruction - v0.10.1 +# ASPIRE - Algorithms for Single Particle Reconstruction - v0.11.0 This is the Python version to supersede the [Matlab ASPIRE](https://github.com/PrincetonUniversity/aspire). @@ -20,7 +20,7 @@ For more information about the project, algorithms, and related publications ple Please cite using the following DOI. This DOI represents all versions, and will always resolve to the latest one. ``` -ComputationalCryoEM/ASPIRE-Python: v0.10.1 https://doi.org/10.5281/zenodo.5657281 +ComputationalCryoEM/ASPIRE-Python: v0.11.0 https://doi.org/10.5281/zenodo.5657281 ``` diff --git a/docs/source/conf.py b/docs/source/conf.py index a99fe4ef9d..8cc7672678 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -78,7 +78,7 @@ # built documents. # # The full version, including alpha/beta/rc tags. -release = version = "0.10.1" +release = version = "0.11.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/index.rst b/docs/source/index.rst index 4fec8d6e1d..5838779230 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,4 +1,4 @@ -Aspire v0.10.1 +Aspire v0.11.0 ============== Algorithms for Single Particle Reconstruction diff --git a/setup.py b/setup.py index f4c0a45f57..d0df2899bb 100644 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ def read(fname): setup( name="aspire", - version="0.10.1", + version="0.11.0", data_files=[ ("", ["src/aspire/config_default.yaml"]), ("", ["src/aspire/logging.conf"]), diff --git a/src/aspire/__init__.py b/src/aspire/__init__.py index bca0d39dfd..5fd1b36ca0 100644 --- a/src/aspire/__init__.py +++ b/src/aspire/__init__.py @@ -11,7 +11,7 @@ from aspire.exceptions import handle_exception # version in maj.min.bld format -__version__ = "0.10.1" +__version__ = "0.11.0" # Setup `confuse` config diff --git a/src/aspire/config_default.yaml b/src/aspire/config_default.yaml index 364e33b781..0faadb95e2 100644 --- a/src/aspire/config_default.yaml +++ b/src/aspire/config_default.yaml @@ -1,4 +1,4 @@ -version: 0.10.1 +version: 0.11.0 common: # numeric module to use - one of numpy/cupy numeric: numpy From c1ef2238567bef3b80d62eae2486ed5c2931ac59 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 24 Mar 2023 09:23:57 -0400 Subject: [PATCH 323/424] get rid of double argmax --- src/aspire/abinitio/commonline_cn.py | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 2bdb91d408..642c04d988 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -165,24 +165,15 @@ def _estimate_relative_viewing_directions(self): # Take the mean over all symmetry induced common lines. corrs = np.mean(corrs, axis=-2) - # Self common-lines are invariant to n_theta_ijs (i.e., in-plane rotation angles) - # so we max them out to estimate viis, but hold onto the index for the best - # in-plane rotation to compute the estimate for vijs. - opt_theta_ij_ind_per_sphere_points = np.argmax(corrs, axis=-1) - corrs = np.max(corrs, axis=-1) - - # Maximum likelihood while taking into consideration both cls and scls. - corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j]) - - # Extract the optimal candidates. - opt_sphere_i, opt_sphere_j = np.unravel_index(np.argmax(corrs), corrs.shape) - opt_theta_ij = opt_theta_ij_ind_per_sphere_points[ - opt_sphere_i, opt_sphere_j - ] - - opt_Ri_tilde = Ris_tilde[opt_sphere_i] - opt_Rj_tilde = Ris_tilde[opt_sphere_j] - opt_R_theta_ij = R_theta_ijs[opt_theta_ij] + # Compute maximum likelihood while taking into consideration both cls and scls. + # Get indices of optimal candidates for Ri_tilde, Rj_tilde, and R_theta_ij. + corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j])[..., np.newaxis] + opt_i, opt_j, opt_ij = np.unravel_index(np.argmax(corrs), corrs.shape) + + # Optimal candidate rotations. + opt_Ri_tilde = Ris_tilde[opt_i] + opt_Rj_tilde = Ris_tilde[opt_j] + opt_R_theta_ij = R_theta_ijs[opt_ij] # Compute the estimate of vi*vi.T as given by j. vii_j = np.mean(opt_Ri_tilde.T @ rots_symm @ opt_Ri_tilde, axis=0) From e1e4ac2436acbb6a325ec22bc2cf75640cb5760e Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Fri, 24 Mar 2023 09:25:01 -0400 Subject: [PATCH 324/424] black --- src/aspire/abinitio/commonline_cn.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 642c04d988..202012af25 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -167,7 +167,10 @@ def _estimate_relative_viewing_directions(self): # Compute maximum likelihood while taking into consideration both cls and scls. # Get indices of optimal candidates for Ri_tilde, Rj_tilde, and R_theta_ij. - corrs = corrs * np.outer(scores_self_corrs[i], scores_self_corrs[j])[..., np.newaxis] + corrs = ( + corrs + * np.outer(scores_self_corrs[i], scores_self_corrs[j])[..., np.newaxis] + ) opt_i, opt_j, opt_ij = np.unravel_index(np.argmax(corrs), corrs.shape) # Optimal candidate rotations. From a550549bf0db71d9b87848a90948a26aee662926 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Mar 2023 12:21:48 -0400 Subject: [PATCH 325/424] Initial log filter utility add --- src/aspire/utils/__init__.py | 2 +- src/aspire/utils/logging.py | 50 ++++++++++++++++++++++++++++++++++++ tests/test_utils.py | 45 ++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 23e621e29e..9965d92c25 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -31,7 +31,7 @@ fuzzy_mask, ) -from .logging import get_full_version, tqdm, trange +from .logging import LogFilterByCount, get_full_version, tqdm, trange from .matrix import ( acorr, ainner, diff --git a/src/aspire/utils/logging.py b/src/aspire/utils/logging.py index a9a470fb52..fc60cc6818 100644 --- a/src/aspire/utils/logging.py +++ b/src/aspire/utils/logging.py @@ -4,6 +4,7 @@ import logging import os.path import subprocess +from collections import defaultdict import tqdm as _tqdm @@ -149,3 +150,52 @@ def getFileLoggingLevel(): # handler list is ordered according to logging.conf file_handler = logging.getLogger().handlers[1] return logging.getLevelName(file_handler.level) + + +class LogFilterByCount: + """ + Provide a context manager for filtering repetitive log messages. + """ + + # msg_cache is intentionally shared by all instances of class. + # msg_cache is map hash(str(msg)) ~~> count. + msg_cache = defaultdict(int) + + def __init__(self, logger: logging.Logger, max_count: int): + """ + Initialize context manager based on `logger` and `max_count`. + + :param logger: `Logger` instance. + :param max_count: Global limit for count of each message + encountered inside context. + """ + + self._logger = logger + self._max_count = max_count + + def filter(self, record): + """ + Increment msg_cache for `record`. + + `filter` returns True when `seen` <= `count` for this context. + True implies the logger will pass the message. + + :param record: Log record. Will be reduced by hash(str()), + :return: Boolean + """ + + # Log messages can be arbitrarily long, + # convert to a hash(str()) + msg = hash(str(record.msg)) + + # Increment seen count in cache. + self.msg_cache[msg] += 1 + seen = self.msg_cache[msg] + + return seen <= self._max_count + + def __enter__(self): + self._logger.addFilter(self) + + def __exit__(self, *args): + self._logger.removeFilter(self) diff --git a/tests/test_utils.py b/tests/test_utils.py index 210875123a..ba30d92e3d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,3 +1,4 @@ +import logging import warnings from contextlib import contextmanager from unittest import TestCase @@ -10,6 +11,7 @@ from aspire import __version__ from aspire.utils import ( + LogFilterByCount, all_pairs, all_triplets, get_full_version, @@ -30,6 +32,49 @@ grid_3d, ) +logger = logging.getLogger(__name__) + + +def test_log_filter_by_count(caplog): + msg = "A is for ASCII" + + # count passed, count in context, count printed + # Should log. 1, 0, 1 + logger.info(msg) + assert msg in caplog.text + caplog.clear() + + with LogFilterByCount(logger, 1): + # Should log. 2, 1, 2 + logger.info(msg) + assert msg in caplog.text + caplog.clear() + + # Should not log. 3, 2, 2 + # with caplog.at_level(logging.INFO): + logger.info(msg) + assert msg not in caplog.text + caplog.clear() + + # Should log. 4, 2, 3 + logger.info(msg) + + with LogFilterByCount(logger, 1): + logger.error(Exception("Should work with exceptions.")) # 1, 1, 1 + assert "Should work" in caplog.text + caplog.clear() + + # Should not log (we've seen above twice). # 5, 3, 3 + logger.info(msg) + assert msg not in caplog.text + caplog.clear() + + with LogFilterByCount(logger, 4): + # Should log (we've seen above thrice). # 6, 4, 4 + logger.info(msg) + assert msg in caplog.text + caplog.clear() + class UtilsTestCase(TestCase): def testGetFullVersion(self): From baf0b27a8897b502a9f990b46368c44fd4597a05 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Mar 2023 12:27:07 -0400 Subject: [PATCH 326/424] Try to quiet the nufft log noise --- src/aspire/nufft/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aspire/nufft/__init__.py b/src/aspire/nufft/__init__.py index 3fbf3dea2b..dba0de4314 100644 --- a/src/aspire/nufft/__init__.py +++ b/src/aspire/nufft/__init__.py @@ -3,7 +3,7 @@ import numpy as np from aspire import config -from aspire.utils import complex_type, real_type +from aspire.utils import LogFilterByCount, complex_type, real_type logger = logging.getLogger(__name__) @@ -134,7 +134,9 @@ def __new__(cls, *args, **kwargs): else: # If a Plan was constructed as a generic Plan(), use the default (best) Plan class if default_plan_class is None: - check_backends(raise_errors=True) + # Limit log noise to once + with LogFilterByCount(logger, 1): + check_backends(raise_errors=True) return super(Plan, cls).__new__(default_plan_class) else: # If a Plan-subclass was constructed directly, invoke default behavior From fe1eb4a36444a7f102060e6153435e82e664881b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Mar 2023 14:32:27 -0400 Subject: [PATCH 327/424] change noisy nufft logs to debug level --- src/aspire/nufft/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/aspire/nufft/__init__.py b/src/aspire/nufft/__init__.py index dba0de4314..6452956ba5 100644 --- a/src/aspire/nufft/__init__.py +++ b/src/aspire/nufft/__init__.py @@ -45,7 +45,7 @@ def _try_backend(backend): If imports are working but the actual backend is not then we have bigger problems anyway. """ - logger.info(f"Trying NFFT backend {backend}") + logger.debug(f"Trying NFFT backend {backend}") plan_class = None msg = None if backend == "cufinufft": @@ -80,16 +80,16 @@ def _try_backend(backend): msg = str(e) if plan_class is None: - logger.info(f"NFFT backend {backend} not usable:\n\t{msg}") + logger.debug(f"NFFT backend {backend} not usable:\n\t{msg}") else: - logger.info(f"NFFT backend {backend} usable.") + logger.debug(f"NFFT backend {backend} usable.") return plan_class # Note this dictionary is intentionally ordered backends = {k: _try_backend(k) for k in config["nufft"]["backends"].as_str_seq()} try: default_backend = next(k for k, v in backends.items() if v is not None) - logger.info(f"Selected NFFT backend = {default_backend}.") + logger.debug(f"Selected NFFT backend = {default_backend}.") default_plan_class = backends[default_backend] except StopIteration: msg = "No usable NFFT backend detected." From 60e387ea7303510695a1ccdb52dedcb201bd688d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Mar 2023 15:11:04 -0400 Subject: [PATCH 328/424] Remove debug comments --- tests/test_utils.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index ba30d92e3d..7d4bafe5fe 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -38,39 +38,38 @@ def test_log_filter_by_count(caplog): msg = "A is for ASCII" - # count passed, count in context, count printed - # Should log. 1, 0, 1 + # Should log. logger.info(msg) assert msg in caplog.text caplog.clear() with LogFilterByCount(logger, 1): - # Should log. 2, 1, 2 + # Should log. logger.info(msg) assert msg in caplog.text caplog.clear() - # Should not log. 3, 2, 2 + # Should not log. # with caplog.at_level(logging.INFO): logger.info(msg) assert msg not in caplog.text caplog.clear() - # Should log. 4, 2, 3 + # Should log. logger.info(msg) with LogFilterByCount(logger, 1): - logger.error(Exception("Should work with exceptions.")) # 1, 1, 1 + logger.error(Exception("Should work with exceptions.")) assert "Should work" in caplog.text caplog.clear() - # Should not log (we've seen above twice). # 5, 3, 3 + # Should not log (we've seen above twice). logger.info(msg) assert msg not in caplog.text caplog.clear() with LogFilterByCount(logger, 4): - # Should log (we've seen above thrice). # 6, 4, 4 + # Should log (we've seen above thrice). logger.info(msg) assert msg in caplog.text caplog.clear() From ad01d04dc1bfbf8f3eec1bd9887ec96a8bfc4914 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 24 Mar 2023 18:19:31 -0400 Subject: [PATCH 329/424] Refactor Typo --- src/aspire/denoising/class_avg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index f97b82831b..246c6f9959 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -178,7 +178,7 @@ def _images(self, indices): } # Get heap dict once to avoid traversing heap in a loop. - heap_dict = self.self.class_selector.heap_id_dict + heap_dict = self.class_selector.heap_id_dict # Recursively call `_images`. # `heap_inds` set should be empty in the recursive call, From 97f303458bdc56c444832512c9b2c1ecdf558484 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Sat, 25 Mar 2023 07:10:02 -0400 Subject: [PATCH 330/424] Quiet noisy evaluate_t warning --- src/aspire/classification/averager2d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index 9936857799..e965b0fbe7 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -203,11 +203,11 @@ def _innerloop(i): # Get coefs in Composite_Basis if not provided as an argument. if coefs is None: # Retrieve relevant images directly from source. - neighbors_imgs = self._cls_images(classes[i]) + neighbors_imgs = Image(self._cls_images(classes[i])) # Do shifts if self.shifts is not None: - Image(neighbors_imgs).shift(self.shifts[i]) + neighbors_imgs = neighbors_imgs.shift(self.shifts[i]) neighbors_coefs = self.composite_basis.evaluate_t(neighbors_imgs) else: From d3d65a067cac2214e12cf7ab50ace977dc111ef7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Sat, 25 Mar 2023 08:18:05 -0400 Subject: [PATCH 331/424] Fixup refactor and shakedown bugs in heap indices --- src/aspire/denoising/class_avg.py | 42 ++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 246c6f9959..8afbaeaf0f 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -148,6 +148,7 @@ def _images(self, indices): self._class_select() # Remap to the selected ordering + _indices = indices.copy() # Store original request indices = self.selection_indices[indices] # Check if there is a cache available from class selection component. @@ -170,30 +171,40 @@ def _images(self, indices): # For others, create an indexing map that preserves # original order. Both of these dicts map requested image # id to location in the return array `im`. - indices_to_compute = { - ind: i for i, ind in enumerate(indices) if i not in heap_inds - } + + # Note, this stores inds from the remapped `indices`. indices_from_heap = { - ind: i for i, ind in enumerate(indices) if i in heap_inds + ind: i for i, ind in enumerate(indices) if ind in heap_inds + } + # Note, this stores _inds from the original `_indices`. + # This allows the recusive call, which remaps the indices. + indices_to_compute = { + _ind: i + for i, _ind in enumerate(_indices) + if self.selection_indices[_ind] not in heap_inds } # Get heap dict once to avoid traversing heap in a loop. heap_dict = self.class_selector.heap_id_dict - # Recursively call `_images`. - # `heap_inds` set should be empty in the recursive call, - # and compute only remaining images (those not in heap). - _imgs = self._images(list(indices_to_compute.keys())) - # Create an empty array to pack results. + L = self.averager.src.L im = np.empty( - (len(indices), _imgs.resolution, _imgs.resolution), - dtype=_imgs.dtype, + (len(indices), L, L), + dtype=self.averager.dtype, ) - # Pack images computed from `_images` recursive call. - _inds = list(indices_to_compute.values()) - im[_inds] = _imgs + # Recursively call `_images`. + # `heap_inds` set should be empty in the recursive call, + # and compute only remaining images (those not in heap). + _indices = list(indices_to_compute.keys()) + # Skip when empty (everything requested in heap). + if len(_indices): + _imgs = self._images(_indices) + + # Pack images computed from `_images` recursive call. + _inds = list(indices_to_compute.values()) + im[_inds] = _imgs # Pack images from heap. for k, i in indices_from_heap.items(): @@ -201,6 +212,9 @@ def _images(self, indices): heap_loc = heap_dict[k] im[i] = self.class_selector.heap[heap_loc].image + # Finally construct an Image. + im = Image(im) + else: # Perform image averaging for the requested images (classes) logger.debug(f"Averaging {len(indices)} images from source") From 5b3394fd341f569abe2f886411296eaad64c28a3 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Sat, 25 Mar 2023 08:20:09 -0400 Subject: [PATCH 332/424] Add 10073 pipeline experiment with Global ClassAvgSource --- .../experimental_abinitio_pipeline_10073.py | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 gallery/experiments/experimental_abinitio_pipeline_10073.py diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py new file mode 100644 index 0000000000..b197a12599 --- /dev/null +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -0,0 +1,148 @@ +""" +Abinitio Pipeline - Experimental Data Empiar 10073 +================================================== + +This notebook introduces a selection of +components corresponding to loading real Relion picked +particle Cryo-EM data and running key ASPIRE-Python +Abinitio model components as a pipeline. + +Specifically this pipeline uses the +EMPIAR 10073 picked particles data, available here: + +https://www.ebi.ac.uk/empiar/EMPIAR-10073 + +https://www.ebi.ac.uk/emdb/EMD-8012 +""" + +# %% +# Imports +# ------- +# First import some of the usual suspects. +# In addition, import some classes from +# the ASPIRE package that will be used throughout this experiment. + +import logging +from pathlib import Path + +import numpy as np + +from aspire.abinitio import CLSyncVoting +from aspire.basis import FFBBasis2D, FFBBasis3D +from aspire.classification import ( + BFRAverager2D, + ContrastImageQualityFunction, + GlobalWithRepulsionClassSelector, +) +from aspire.denoising import DefaultClassAvgSource +from aspire.reconstruction import MeanEstimator +from aspire.source import RelionSource + +logger = logging.getLogger(__name__) + + +# %% +# Parameters +# --------------- +# Example simulation configuration. + +n_imgs = None # Set to None for all images in starfile, can set smaller for tests. +img_size = 32 # Downsample the images/reconstruction to a desired resolution +n_classes = 2000 # How many class averages to compute. +n_nbor = 50 # How many neighbors to stack +starfile_in = "10073/data/shiny_correctpaths_cleanedcorruptstacks.star" +data_folder = "." # This depends on the specific starfile entries. +volume_output_filename = f"10073_abinitio_c{n_classes}_m{n_nbor}_{img_size}.mrc" +pixel_size = 1.43 + + +# %% +# Source data and Preprocessing +# ----------------------------- +# +# `RelionSource` is used to access the experimental data via a `starfile`. +# Begin by downsampling to our chosen resolution, then preprocess +# to correct for CTF and noise. + +# Create a source object for the experimental images +src = RelionSource( + starfile_in, pixel_size=pixel_size, max_rows=n_imgs, data_folder=data_folder +) + +# Downsample the images +logger.info(f"Set the resolution to {img_size} X {img_size}") +src.downsample(img_size) + +src.cache() + +# %% +# Class Averaging +# ---------------------- +# +# Now perform classification and averaging for each class. + +logger.info("Begin Class Averaging") + +# Now perform classification and averaging for each class. +# This also demonstrates customizing a ClassAvgSource, by using global +# contrast selection. This computes the entire set of class averages, +# and sorts them by (highest) contrast. + +# Build up the customized components. +basis = FFBBasis2D(src.L, dtype=src.dtype) +averager = BFRAverager2D(basis, src, num_procs=32) +quality_function = ContrastImageQualityFunction() +class_selector = GlobalWithRepulsionClassSelector(averager, quality_function) + +# Assemble the components into the Source. +avgs = DefaultClassAvgSource( + src, n_nbor=n_nbor, averager=averager, class_selector=class_selector +) +np.save( + "experimental_10073_class_averages__indices.npy", + avgs.selection_indices, +) + +# We'll continue our pipeline with the first `n_classes` from `avgs`. +avgs = avgs[:n_classes] + +# Save off the set of class average images. +avgs.save("experimental_10073_class_averages_global.star", overwrite=True) + + +# %% +# Common Line Estimation +# ---------------------- +# +# Next create a CL instance for estimating orientation of projections +# using the Common Line with Synchronization Voting method. + +logger.info("Begin Orientation Estimation") + +# Run orientation estimation on ``avgs``. +orient_est = CLSyncVoting(avgs, n_theta=180) +# Get the estimated rotations +orient_est.estimate_rotations() +rots_est = orient_est.rotations + +# %% +# Volume Reconstruction +# ---------------------- +# +# Using the estimated rotations, attempt to reconstruct a volume. + +logger.info("Begin Volume reconstruction") + +# Assign the estimated rotations to the class averages +avgs.rotations = rots_est + +# Create a reasonable Basis for the 3d Volume +basis = FFBBasis3D((img_size,) * 3, dtype=src.dtype) + +# Setup an estimator to perform the back projection. +estimator = MeanEstimator(avgs, basis) + +# Perform the estimation and save the volume. +estimated_volume = estimator.estimate() +estimated_volume.save(volume_output_filename, overwrite=True) +logger.info(f"Saved Volume to {str(Path(volume_output_filename).resolve())}") From 232cee74ce674f6a672216c92072292c48b38fe5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Mar 2023 12:40:50 -0400 Subject: [PATCH 333/424] review comments round 1 --- gallery/tutorials/aspire_introduction.py | 82 ++++++++++++------------ 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 2aca26f6ee..8a06019ce2 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -69,7 +69,7 @@ # * - ``Rotations`` # - Utility class for stacks of 3D rotations. # * - ``Filter`` -# - Constructs and applied Image filters. +# - Constructs and applies Image filters. # * - ``Basis`` # - Basis conversions and operations. # * - ``Source`` @@ -99,11 +99,11 @@ img_data = np.random.random((3, 100, 100)) img = Image(img_data) -# Most often, ``Image``s will behave like Numpy arrays, but you +# Most often, Images will behave like Numpy arrays, but you # explicitly access the underlying Numpy array via ``asnumpy()``. img.asnumpy() -# Image have a built in ``show()`` method, which works well for +# Images have a built in ``show()`` method, which works well for # peeking at data. img.show() @@ -116,8 +116,8 @@ # %% # More examples using the Image class can be found in: # -# - :ref:`sphx_glr_auto_tutorials_image_class.py` -# - :ref:`sphx_glr_auto_tutorials_basic_image_array.py` +# - :ref:`sphx_glr_auto_tutorials_tutorials_image_class.py` +# - :ref:`sphx_glr_auto_tutorials_tutorials_basic_image_array.py` # %% @@ -146,23 +146,24 @@ file_path = os.path.join(os.getcwd(), "data", "clean70SRibosome_vol_65p.mrc") v = Volume.load(file_path) -# More interesting data requires downloading locally. +# %% +# More interesting data requires downloading locally. A common +# starting dataset can be downloaded from EMDB at +# `_. After +# downloading the associated `map` file, unzip in local directory. To +# simplify things, this notebook defaults to a small low resolution +# sample file. Unfortunately real data can be quite large so we do +# not ship it with the repo. # v = Volume.load("path/to/EMD-2660/map/emd_2660.map") # %% # Downsample Volume # ^^^^^^^^^^^^^^^^^ -# Here we downsample the above volume to a desired resolution (32 -# should be good). For the data source I chose to download a real -# volume density map from `EMDB -# `_. The download -# was uncompressed in my local directory. The notebook defaults to a -# small low resolution sample file you may use to sanity check. -# Unfortunately real data can be quite large so we do not ship it with -# the repo. - -# Downsample the volume to a desired resolution +# Here we downsample the above volume to a desired image size (32 +# should be good). + img_size = 32 + # Volume.downsample() returns a new Volume instance. # We will use this lower resolution volume later, calling it `v2`. v2 = v.downsample(img_size) @@ -174,9 +175,9 @@ # """"""""" # For quick sanity checking purposes we can view some plots. # We'll use three orthographic projections, one per axis. -orthographic_projections = np.empty((3, L, L, L), dtype=v2.dtype) +orthographic_projections = np.empty((3, L, L), dtype=v2.dtype) for i in range(3): - orthographic_projections = np.sum(v2, axis=(0, i + 1)) + orthographic_projections[i] = np.sum(v2, axis=(0, i + 1)) Image(orthographic_projections).show() # %% @@ -218,9 +219,10 @@ # %% # Neat, we've generated random projections of some real data. This -# tutorial go on to show how this can be performed systematically with +# tutorial will go on to show how this can be performed systematically with # other Computational CryoEM data simulation tasks. +# %% # The ``filter`` Package # ---------------------- # `Filters @@ -257,11 +259,11 @@ # CTFFilter o-- RadialCTFFilter # %% -# ``CTFFilter`` and ``RadialCTFFilter`` the most common filters +# ``CTFFilter`` and ``RadialCTFFilter`` are the most common filters # encountered when starting out and are detailed in -# :ref:`sphx_glr_auto_tutorials_ctf.py` The other filters are used -# behind the scenes in components like ``NoiseAdders`` or more -# advanced customized pipelines. +# :ref:`sphx_glr_auto_tutorials_tutorials_ctf.py` The other filters +# are used behind the scenes in components like ``NoiseAdders`` or +# more advanced customized pipelines. # %% # ``Basis`` @@ -275,7 +277,7 @@ # direct slower reference ``FBBasis2D`` and ``FBBasis3D`` methods. # # Recently, a related Fourier Bessel method using the Fast Laplacian -# Eigenfunction transforms was integrated as``FLEBasis2D``. +# Eigenfunction transforms was integrated as ``FLEBasis2D``. # Additional, Prolate Spheroidal Wave Function methods are available # via ``FPSWFBasis2D`` and ``FPSWFBasis3D``, but their integration # into other components like 2D covariance analysis is incomplete, and @@ -296,9 +298,8 @@ # higher level components interface. ``ImageSource`` instances have a # consistent method ``images`` which must be implemented to serve up # images dynamically. This supports batch computation among other -# things. ``Source``s also store and serve up metadata like -# `rotations`, `dtype`, and supporting pipelining certain -# transformations. +# things. ``Source`` instances also store and serve up metadata like +# `rotations`, `dtype`, and support pipelining transformations. # # The second reason is so we can design an experiment using a # synthetic ``Simulation`` source or our own provided Numpy arrays via @@ -377,7 +378,7 @@ # We can also view the rotations used to create these projections. logging.info(sim.rotations) -# % +# %% # Given any ``Source``, we can also take slices using typical slicing # syntax, or provide our own iterable of indices. @@ -389,7 +390,7 @@ shuffled_inds = np.random.permutation(sim.n)[:5] sim_shuffled_subset = sim[shuffled_inds] -# % +# %% # Underneath those slices, ASPIRE relies on ``IndexedSource``, which # we can also call direcly to remap indices. @@ -407,8 +408,8 @@ # estimating different types of noise. # %% -# ``NoiseAdder``s -# ^^^^^^^^^^^^^^^ +# ``NoiseAdder`` +# ^^^^^^^^^^^^^^ # ``NoiseAdder`` subclasses are used to add common or customized noise # to ``Simulation`` image generation pipelines. @@ -427,7 +428,8 @@ # Then create a NoiseAdder based on that variance. white_noise_adder = WhiteNoiseAdder(target_noise_variance) -# %% We can customize Sources by adding stages to their generation +# %% +# We can customize Sources by adding stages to their generation # pipeline. In this case of a Simulation source, we want to corrupt # the projection images with noise. Internally the # ``WhiteNoiseAdder`` creates a ``ScalarFilter`` which is multiplied @@ -504,10 +506,10 @@ def noise_function(x, y): aiso_noise_estimator = AnisotropicNoiseEstimator(sim) # %% -# Applying the ``Simulation.whiten()`` method requires passing the -# corresponding to the noise estimator instance. Then we can inspect -# some of the whitened images. While noise is still present, we can -# see a dramatic change. +# Applying the ``Simulation.whiten()`` method requires passing a +# corresponding ``NoiseEstimator`` instance. Then we can inspect some +# of the whitened images. While noise is still present, we can see a +# dramatic change. # Whiten based on the estimated noise sim.whiten(aiso_noise_estimator) @@ -517,7 +519,7 @@ def noise_function(x, y): # %% # Common Image Corruptions -# --------------------------- +# ------------------------ # ``Simulation`` provides several configurable types of common CryoEM # image corruptions. Users should be aware that Amplitude and Offset # corruption is enabled by default. @@ -531,13 +533,13 @@ def noise_function(x, y): # %% # Offsets # """"""" -# Simulation automatically generates random offsets +# Simulation automatically generates random offsets. # To disable, set to ``offsets=0``. # %% # Noise # """"" -# By default, no noise corruption is configured +# By default, no noise corruption is configured. # To enable, see ``NoiseAdder`` components. # %% @@ -578,7 +580,7 @@ def noise_function(x, y): # Simulation has two unique accessors ``clean_images`` which disables # noise, and ``projections`` which are clean uncorrupted projections. -# Both act like calls to `image` and return show=able ``Image`` +# Both act like calls to `image` and return show-able ``Image`` # instances. # %% From 99695713638bd79169a42ccc6c31c9bd6ec19c56 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Mar 2023 14:04:06 -0400 Subject: [PATCH 334/424] review comments round 2 --- gallery/tutorials/aspire_introduction.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 8a06019ce2..c99c8a012f 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -154,6 +154,7 @@ # simplify things, this notebook defaults to a small low resolution # sample file. Unfortunately real data can be quite large so we do # not ship it with the repo. + # v = Volume.load("path/to/EMD-2660/map/emd_2660.map") # %% @@ -490,7 +491,6 @@ def noise_function(x, y): sim = Simulation(n=num_imgs, vols=v2, noise_adder=custom_noise) sim.images[:10].show() - # %% # Noise Whitening # ^^^^^^^^^^^^^^^ @@ -526,25 +526,26 @@ def noise_function(x, y): # %% # Amplitudes -# """""""""" +# ^^^^^^^^^^ +# # Simulation automatically generates random amplitude variability. # To disable, set to ``amplitudes=1``. # %% # Offsets -# """"""" +# ^^^^^^^ # Simulation automatically generates random offsets. # To disable, set to ``offsets=0``. # %% # Noise -# """"" +# ^^^^^ # By default, no noise corruption is configured. # To enable, see ``NoiseAdder`` components. # %% # CTF -# """ +# ^^^ # By default, no CTF corruption is configured. # To enable, we must configure one or more CTFFilter. # Usually we will create a range of CTFFilters for a variety of From 9345c6ca8a49da7ff8b9d150d360ce080b3abecc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 27 Mar 2023 14:10:07 -0400 Subject: [PATCH 335/424] Reduce noise variance for new (smaller) img_size --- gallery/tutorials/aspire_introduction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index c99c8a012f..ba499885e0 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -424,7 +424,7 @@ # Get the sample variance var = np.var(sim.images[:].asnumpy()) logging.info(f"Sample Variance: {var}") -target_noise_variance = 100.0 * var +target_noise_variance = 10.0 * var logging.info(f"Target Noise Variance: {target_noise_variance}") # Then create a NoiseAdder based on that variance. white_noise_adder = WhiteNoiseAdder(target_noise_variance) From ae1fbfa15b34deb34b157483d0a743e8f5df9525 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Mar 2023 09:37:16 -0400 Subject: [PATCH 336/424] Force logging/images by making excessive number of cells for gallery --- gallery/tutorials/aspire_introduction.py | 46 ++++++++++++++---------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index ba499885e0..6f90cd39b1 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -88,21 +88,24 @@ # fundemental structures behind the scenes. A lot of ASPIRE code # passes around ``Image`` and ``Volume`` instances. - +# %% # Create an ``Image`` instance from random data. img_data = np.random.random((100, 100)) img = Image(img_data) logging.info(f"img shape: {img.shape}") # Note this produces a stack of 1. logging.info(f"str(img): {img}") # Note this produces a stack of 1. +# %% # Create an Image for a stack of 3 100x100 images. img_data = np.random.random((3, 100, 100)) img = Image(img_data) +# %% # Most often, Images will behave like Numpy arrays, but you # explicitly access the underlying Numpy array via ``asnumpy()``. img.asnumpy() +# %% # Images have a built in ``show()`` method, which works well for # peeking at data. img.show() @@ -149,11 +152,11 @@ # %% # More interesting data requires downloading locally. A common # starting dataset can be downloaded from EMDB at -# `_. After +# ``_. After # downloading the associated `map` file, unzip in local directory. To # simplify things, this notebook defaults to a small low resolution -# sample file. Unfortunately real data can be quite large so we do -# not ship it with the repo. +# sample file instead. Unfortunately real data can be quite large so +# we do not ship it with the repo. # v = Volume.load("path/to/EMD-2660/map/emd_2660.map") @@ -209,6 +212,7 @@ logging.info(rots) logging.info(rots.matrices) +# %% # Using the zero-th (and in this case, only) volume, compute # projections using the stack of rotations: projections = v.project(0, rots) @@ -277,9 +281,9 @@ # classes ``FFBBasis2D`` and ``FFBBasis3D``. These correspond to # direct slower reference ``FBBasis2D`` and ``FBBasis3D`` methods. # -# Recently, a related Fourier Bessel method using the Fast Laplacian +# Recently, a related Fourier Bessel method using Fast Laplacian # Eigenfunction transforms was integrated as ``FLEBasis2D``. -# Additional, Prolate Spheroidal Wave Function methods are available +# Additional Prolate Spheroidal Wave Function methods are available # via ``FPSWFBasis2D`` and ``FPSWFBasis3D``, but their integration # into other components like 2D covariance analysis is incomplete, and # slated for a future release. @@ -361,21 +365,25 @@ # Total images in our source. num_imgs = 100 +# %% # Generate a Simulation instance based on the original volume data. sim = Simulation(n=num_imgs, vols=v) # Display the first 10 images sim.images[:10].show() # Hi Res +# %% # Repeat for the lower resolution (downsampled) volume v2. sim = Simulation(n=num_imgs, vols=v2) sim.images[:10].show() # Lo Res +# %% # Note both of those simulations have the same rotations because they -# had the same seed by default, We recreate ``sim`` with a distinct -# seed to get different random samples (of rotations). +# had the same seed by default, We recreate ``sim`` with a distinct +# seed to get different random samples (of rotations). sim = Simulation(n=num_imgs, vols=v2, seed=42) sim.images[:10].show() +# %% # We can also view the rotations used to create these projections. logging.info(sim.rotations) @@ -421,12 +429,12 @@ from aspire.noise import WhiteNoiseAdder -# Get the sample variance +# %% +# Get the sample variance, then create a NoiseAdder based on that variance. var = np.var(sim.images[:].asnumpy()) logging.info(f"Sample Variance: {var}") target_noise_variance = 10.0 * var logging.info(f"Target Noise Variance: {target_noise_variance}") -# Then create a NoiseAdder based on that variance. white_noise_adder = WhiteNoiseAdder(target_noise_variance) # %% @@ -446,7 +454,7 @@ # %% # ``WhiteNoiseEstimator`` # """"""""""""""""""""""" -# We can estimate the noise across an ``ImageSource``. +# We can estimate the noise across an ``ImageSource``, and # we've generated a simulation with known noise variance. # Lets see how the estimate compares. # @@ -497,8 +505,7 @@ def noise_function(x, y): # We will now combine a more advanced noise estimation technique with # an ``ImageSource`` preprocessing method ``whiten``. # -# First an anisotropic noise estimate is performed. We will use the -# resulting ``Filter``. +# First an anisotropic noise estimate is performed. from aspire.noise import AnisotropicNoiseEstimator @@ -511,10 +518,11 @@ def noise_function(x, y): # of the whitened images. While noise is still present, we can see a # dramatic change. -# Whiten based on the estimated noise +# Whiten based on the estimated noise. sim.whiten(aiso_noise_estimator) -# What do the whitened images look like... +# %% +# What do the whitened images look like? sim.images[:10].show() # %% @@ -527,7 +535,6 @@ def noise_function(x, y): # %% # Amplitudes # ^^^^^^^^^^ -# # Simulation automatically generates random amplitude variability. # To disable, set to ``amplitudes=1``. @@ -613,14 +620,17 @@ def noise_function(x, y): max_rows=1024, ) -# Downsample +# %% +# Add downsampling to the ``src`` pipeline. src.downsample(img_size) +# %% # Relionsource will auto populate ``CTFFilter`` instances from the # STAR file metadata when available. Having these filters allows us to # perform a phase flipping correction. src.phase_flip() +# %% # Display the experimental data images. src.images[:10].show() @@ -630,7 +640,7 @@ def noise_function(x, y): # Now that the primitives have been introduced we can explore higher # level components. The higher level components are designed to be # modular and cacheable (to memory or disk) to support experimentation -# with entire pipelines or focuses algorithmic development on specific +# with entire pipelines or focused algorithmic development on specific # components. Most pipelines will follow a flow of data and # components moving mostly left to right in the table below. This # table is not exhaustive, but represents some of the most common From 26466190135ef7d1947d25093556c25eaeb87dfc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 28 Mar 2023 12:11:36 -0400 Subject: [PATCH 337/424] Use garretts class selector. --- .../experimental_abinitio_pipeline_10073.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index b197a12599..8cc37612e4 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -7,6 +7,9 @@ particle Cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. +This demonstrates using the Global BandedSNRImageQualityFunction +approach starting with Relion polished picked particles. + Specifically this pipeline uses the EMPIAR 10073 picked particles data, available here: @@ -30,8 +33,8 @@ from aspire.abinitio import CLSyncVoting from aspire.basis import FFBBasis2D, FFBBasis3D from aspire.classification import ( + BandedSNRImageQualityFunction, BFRAverager2D, - ContrastImageQualityFunction, GlobalWithRepulsionClassSelector, ) from aspire.denoising import DefaultClassAvgSource @@ -90,8 +93,8 @@ # Build up the customized components. basis = FFBBasis2D(src.L, dtype=src.dtype) -averager = BFRAverager2D(basis, src, num_procs=32) -quality_function = ContrastImageQualityFunction() +averager = BFRAverager2D(basis, src, num_procs=16) +quality_function = BandedSNRImageQualityFunction() class_selector = GlobalWithRepulsionClassSelector(averager, quality_function) # Assemble the components into the Source. @@ -99,7 +102,7 @@ src, n_nbor=n_nbor, averager=averager, class_selector=class_selector ) np.save( - "experimental_10073_class_averages__indices.npy", + "experimental_10073_class_averages_indices.npy", avgs.selection_indices, ) @@ -120,7 +123,7 @@ logger.info("Begin Orientation Estimation") # Run orientation estimation on ``avgs``. -orient_est = CLSyncVoting(avgs, n_theta=180) +orient_est = CLSyncVoting(avgs, n_theta=72) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations From 31ef4cb4d1ec0ca1f6d73f9b75964d2d0dc2e766 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 29 Mar 2023 12:21:31 -0400 Subject: [PATCH 338/424] add n_img to testing parametrization --- tests/test_orient_symmetric.py | 84 +++++++++++++++------------------- 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/tests/test_orient_symmetric.py b/tests/test_orient_symmetric.py index 58835e553b..8d6bfb9800 100644 --- a/tests/test_orient_symmetric.py +++ b/tests/test_orient_symmetric.py @@ -13,30 +13,30 @@ from aspire.volume import CnSymmetricVolume # A set of these parameters are marked expensive to reduce testing time. -# Each tuple holds the parameters (resolution "L", cyclic order "order", dtype). +# Each tuple holds the parameters (n_img, resolution "L", cyclic order "order", dtype). param_list_c3_c4 = [ - (44, 3, np.float32), - (45, 4, np.float64), - pytest.param(44, 4, np.float32, marks=pytest.mark.expensive), - pytest.param(44, 3, np.float64, marks=pytest.mark.expensive), - pytest.param(44, 4, np.float64, marks=pytest.mark.expensive), - pytest.param(45, 3, np.float32, marks=pytest.mark.expensive), - pytest.param(45, 4, np.float32, marks=pytest.mark.expensive), - pytest.param(45, 3, np.float64, marks=pytest.mark.expensive), + (24, 44, 3, np.float32), + (24, 45, 4, np.float64), + pytest.param(24, 44, 4, np.float32, marks=pytest.mark.expensive), + pytest.param(24, 44, 3, np.float64, marks=pytest.mark.expensive), + pytest.param(24, 44, 4, np.float64, marks=pytest.mark.expensive), + pytest.param(24, 45, 3, np.float32, marks=pytest.mark.expensive), + pytest.param(24, 45, 4, np.float32, marks=pytest.mark.expensive), + pytest.param(24, 45, 3, np.float64, marks=pytest.mark.expensive), ] # For testing Cn methods where n>4. param_list_cn = [ - (44, 5, np.float32), - pytest.param(45, 6, np.float64, marks=pytest.mark.expensive), - pytest.param(44, 7, np.float32, marks=pytest.mark.expensive), - pytest.param(44, 8, np.float32, marks=pytest.mark.expensive), - pytest.param(45, 9, np.float64, marks=pytest.mark.expensive), + (8, 44, 5, np.float32), + pytest.param(24, 45, 6, np.float64, marks=pytest.mark.expensive), + pytest.param(24, 44, 7, np.float32, marks=pytest.mark.expensive), + pytest.param(24, 44, 8, np.float32, marks=pytest.mark.expensive), + pytest.param(24, 45, 9, np.float64, marks=pytest.mark.expensive), ] # Method to instantiate a Simulation source and orientation estimation object. -def source_orientation_objs(L, n_img, order, dtype): +def source_orientation_objs(n_img, L, order, dtype): # This Volume is hand picked to have a fairly even distribution of density. # Due to the rotations used to generate symmetric volumes, some seeds will # generate volumes with a high concentration of denisty in the center causing @@ -91,12 +91,9 @@ def source_orientation_objs(L, n_img, order, dtype): return src, orient_est -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4 + param_list_cn) -def test_estimate_rotations(L, order, dtype): - n_img = 24 - if order > 4: - n_img = 8 # This is to save on test time. - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) +@pytest.mark.parametrize("n_img, L, order, dtype", param_list_c3_c4 + param_list_cn) +def test_estimate_rotations(n_img, L, order, dtype): + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) # Estimate rotations. cl_symm.estimate_rotations() @@ -131,12 +128,11 @@ def test_estimate_rotations(L, order, dtype): assert np.mean(ang_dist) < 2 -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def test_relative_rotations(L, order, dtype): +@pytest.mark.parametrize("n_img, L, order, dtype", param_list_c3_c4) +def test_relative_rotations(n_img, L, order, dtype): # Simulation source and common lines estimation instance # corresponding to volume with C3 or C4 symmetry. - n_img = 24 - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) # Estimate relative viewing directions. cl_symm.build_clmatrix() @@ -171,12 +167,11 @@ def test_relative_rotations(L, order, dtype): assert mean_angular_distance < 5 -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def test_self_relative_rotations(L, order, dtype): +@pytest.mark.parametrize("n_img, L, order, dtype", param_list_c3_c4) +def test_self_relative_rotations(n_img, L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. - n_img = 24 - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) # Estimate self-relative viewing directions, Riis. scl = cl_symm._self_clmatrix_c3_c4() @@ -206,14 +201,11 @@ def test_self_relative_rotations(L, order, dtype): assert mean_angular_distance < 5 -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4 + param_list_cn) -def test_relative_viewing_directions(L, order, dtype): +@pytest.mark.parametrize("n_img, L, order, dtype", param_list_c3_c4 + param_list_cn) +def test_relative_viewing_directions(n_img, L, order, dtype): # Simulation source and common lines Class corresponding to # volume with C3 or C4 symmetry. - n_img = 24 - if order > 4: - n_img = 8 # This is to save on test time. - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) # Calculate ground truth relative viewing directions, viis and vijs. rots_gt = src.rotations @@ -289,7 +281,7 @@ def test_relative_viewing_directions(L, order, dtype): # which do not as tightly approximate rank-1. if order < 5: max_tol_ij = 4e-1 - mean_tol_ij = 3e-3 + mean_tol_ij = 4e-3 assert np.max(error_ij) < max_tol_ij assert np.max(error_ii) < 1e-6 assert np.mean(error_ij) < mean_tol_ij @@ -304,10 +296,9 @@ def test_relative_viewing_directions(L, order, dtype): assert angular_dist_viis < angle_tol -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def test_self_commonlines(L, order, dtype): - n_img = 24 - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) +@pytest.mark.parametrize("n_img, L, order, dtype", param_list_c3_c4) +def test_self_commonlines(n_img, L, order, dtype): + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) n_theta = cl_symm.n_theta # Initialize common-lines orientation estimation object and compute self-common-lines matrix. @@ -347,10 +338,9 @@ def test_self_commonlines(L, order, dtype): assert np.allclose(detection_rate, 1.0) -@pytest.mark.parametrize("L, order, dtype", param_list_c3_c4) -def test_commonlines(L, order, dtype): - n_img = 24 - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) +@pytest.mark.parametrize("n_img, L, order, dtype", param_list_c3_c4) +def test_commonlines(n_img, L, order, dtype): + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) n_theta = cl_symm.n_theta # Build common-lines matrix. @@ -408,7 +398,7 @@ def test_global_J_sync(dtype): L = 16 n_img = 20 order = 3 # test not dependent on order - _, orient_est = source_orientation_objs(L, n_img, order, dtype) + _, orient_est = source_orientation_objs(n_img, L, order, dtype) # Build a set of outer products of random third rows. vijs, viis, _ = build_outer_products(n_img, dtype) @@ -438,7 +428,7 @@ def test_estimate_third_rows(dtype): L = 16 n_img = 20 order = 3 # test not dependent on order - _, orient_est = source_orientation_objs(L, n_img, order, dtype) + _, orient_est = source_orientation_objs(n_img, L, order, dtype) # Build outer products vijs, viis, and get ground truth third rows. vijs, viis, gt_vis = build_outer_products(n_img, dtype) @@ -477,7 +467,7 @@ def test_dtype_pass_through(dtype): L = 16 n_img = 20 order = 3 # test does not depend on order - src, cl_symm = source_orientation_objs(L, n_img, order, dtype) + src, cl_symm = source_orientation_objs(n_img, L, order, dtype) assert src.dtype == cl_symm.dtype From 18e275ac12e79c62beea8e3e32391e929ff8b342 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 29 Mar 2023 13:25:10 -0400 Subject: [PATCH 339/424] best_rank1_approximation method --- src/aspire/abinitio/commonline_cn.py | 6 ++---- src/aspire/utils/__init__.py | 1 + src/aspire/utils/matrix.py | 20 ++++++++++++++++++++ tests/test_matrix.py | 12 ++++++++++++ 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/aspire/abinitio/commonline_cn.py b/src/aspire/abinitio/commonline_cn.py index 202012af25..869acc93c4 100644 --- a/src/aspire/abinitio/commonline_cn.py +++ b/src/aspire/abinitio/commonline_cn.py @@ -9,6 +9,7 @@ Rotation, all_pairs, anorm, + best_rank1_approximation, cyclic_rotations, tqdm, trange, @@ -203,10 +204,7 @@ def _estimate_relative_viewing_directions(self): # As we are using a mean to get the estimates, viis, the estimate will not be rank-1 # So we use SVD to find a close rank-1 approximation. - U, S, V = np.linalg.svd(viis) - S_rank1 = np.zeros((self.n_img, 3, 3), dtype=self.dtype) - S_rank1[:, 0, 0] = S[:, 0] - viis_rank1 = U @ S_rank1 @ V + viis_rank1 = best_rank1_approximation(viis) return vijs, viis_rank1 diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 89f1245249..5e88856a66 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -34,6 +34,7 @@ acorr, ainner, anorm, + best_rank1_approximation, eigs, fix_signs, im_to_vec, diff --git a/src/aspire/utils/matrix.py b/src/aspire/utils/matrix.py index d4129ad750..7a914930f4 100644 --- a/src/aspire/utils/matrix.py +++ b/src/aspire/utils/matrix.py @@ -412,6 +412,26 @@ def eigs(A, k): return v, np.diag(w) +def best_rank1_approximation(A): + """ + Computes the best rank-1 approximation of A. + + :param A: A 2D array or a 3D array where the first axis is the stack axis. + :return: rank-1 ndarray of same size. + """ + og_shape = A.shape + + if A.ndim == 2: + A = A[np.newaxis] + assert A.ndim == 3, "Array must be 2D or 3D representing a stack." + + U, S, V = np.linalg.svd(A) + S_rank1 = np.zeros_like(A) + S_rank1[:, 0, 0] = S[:, 0] + + return (U @ S_rank1 @ V).reshape(og_shape) + + def fix_signs(u): """ Negates columns so the sign of the largest element in the column is positive. diff --git a/tests/test_matrix.py b/tests/test_matrix.py index 52e3bc3100..fbb4df2e57 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -4,6 +4,7 @@ import numpy as np from aspire.utils import ( + best_rank1_approximation, fix_signs, im_to_vec, mat_to_vec, @@ -302,6 +303,17 @@ def testVecToMatSymmIso(self): ) ) + def testRank1Approximation(self): + A = np.arange(3 * 4).reshape(3, 4) + A_rank1 = best_rank1_approximation(A) + s = np.linalg.svd(A_rank1, compute_uv=False) + + # Check return shape. + self.assertTrue(A.shape == A_rank1.shape) + + # Check return is rank-1. + self.assertTrue(np.allclose([s[1], s[2]], 0)) + def testFixSigns(self): """ Tests `fix_signs` util function. From b5a1d4cc3088791a9c0c98a374b84e7e2632f3e7 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Wed, 29 Mar 2023 13:46:12 -0400 Subject: [PATCH 340/424] estimate_rotations for Cn with docstring --- src/aspire/abinitio/commonline_c3_c4.py | 5 ++--- src/aspire/abinitio/commonline_cn.py | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/aspire/abinitio/commonline_c3_c4.py b/src/aspire/abinitio/commonline_c3_c4.py index 85b65ad298..b880452e5a 100644 --- a/src/aspire/abinitio/commonline_c3_c4.py +++ b/src/aspire/abinitio/commonline_c3_c4.py @@ -95,11 +95,10 @@ def _check_symmetry(self, symmetry): def estimate_rotations(self): """ - Estimate rotation matrices for molecules with Cn symmetry, n > 2. + Estimate rotation matrices for molecules with C3 or C4 symmetry. :return: Array of rotation matrices, size n_imgx3x3. """ - logger.info(f"Estimating relative viewing directions for {self.n_img} images.") vijs, viis = self._estimate_relative_viewing_directions() logger.info("Performing global handedness synchronization.") @@ -122,7 +121,7 @@ def _estimate_relative_viewing_directions(self): Estimate the relative viewing directions vij = vi*vj^T, i 4. + + :return: Array of rotation matrices, size n_imgx3x3. + """ + super().estimate_rotations() + def _estimate_relative_viewing_directions(self): + logger.info(f"Estimating relative viewing directions for {self.n_img} images.") pf = self.pf # Generate candidate rotation matrices and the common-line and From 559ea263fd205b1b67aa18055fef01b0cc5b11e4 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 06:46:45 -0400 Subject: [PATCH 341/424] n_theta 72 --- gallery/experiments/experimental_abinitio_pipeline_10028.py | 2 +- gallery/experiments/experimental_abinitio_pipeline_10081.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index dedabc19b4..fed1c7be84 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -160,7 +160,7 @@ logger.info("Begin Orientation Estimation") # Run orientation estimation on ``avgs``. -orient_est = CLSyncVoting(avgs, n_theta=360) +orient_est = CLSyncVoting(avgs, n_theta=72) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index 50a8e2ce3f..2a8f4e5333 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -103,7 +103,7 @@ logger.info("Begin Orientation Estimation") # Run orientation estimation on ``avgs``. -orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=360, max_shift=0) +orient_est = CLSymmetryC3C4(avgs, symmetry="C4", n_theta=72, max_shift=0) # Get the estimated rotations orient_est.estimate_rotations() rots_est = orient_est.rotations From 6dea9fb00edb631f6be20c11f8d6db0574035cd4 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 06:53:28 -0400 Subject: [PATCH 342/424] version semantic --- docs/source/class_source.rst | 4 ++-- src/aspire/denoising/class_avg.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index dc607a6458..2aaf81fe4d 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -69,7 +69,7 @@ components they wish to configure. +images() } class DefaultClassAvgSource{ - version="11.0" + version="0.11.0" src: ImageSource classifier: RIRClass2D class_selector: NeighborVarianceWithRepulsionClassSelector @@ -101,7 +101,7 @@ Classifiers take an image ``Source`` and attempts to classify into reflection. All ``Class2D`` instances are expected to implement a ``classify`` method which returns ``(classes, reflections, distances)``. The three returned variables are expected to be 2D -Numpy arrays in a neighbor network format having shape ``(n_classes, +Numpy arrays in a neighbor network format having shape ``(src.n, n_nbors)``. So to retrieve the input source indices for the first class's neighbors, we would want ``classes[0,:]``. The first class index should be the base image used for classification. So we would diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 8afbaeaf0f..a4e266cabb 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -340,7 +340,7 @@ def DefaultClassAvgSource( _versions = { None: ClassAvgSourcev110, "latest": ClassAvgSourcev110, - "11.0": ClassAvgSourcev110, + "0.11.0": ClassAvgSourcev110, } if version not in _versions: From 2384dd37e09c1c9750920e609336fe37bc8e1d43 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 06:54:39 -0400 Subject: [PATCH 343/424] n_classes ~> src.n in doc/strings --- src/aspire/classification/averager2d.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/aspire/classification/averager2d.py b/src/aspire/classification/averager2d.py index e965b0fbe7..6c8f406339 100644 --- a/src/aspire/classification/averager2d.py +++ b/src/aspire/classification/averager2d.py @@ -79,11 +79,11 @@ def average( Should return an Image source of synthetic class averages. - :param classes: class indices, refering to src. (n_classes, n_nbor). + :param classes: class indices, refering to src. (src.n, n_nbor). :param reflections: Bool representing whether to reflect image in `classes`. (n_clases, n_nbor) :param coefs: Optional basis coefs (could avoid recomputing). - (n_classes, coef_count) + (src.n, coef_count) :return: Stack of synthetic class average images as Image instance. """ @@ -157,22 +157,22 @@ def align(self, classes, reflections, basis_coefficients): During this process `rotations`, `reflections`, `shifts` and `correlations` properties will be computed for aligners. - `rotations` is an (n_classes, n_nbor) array of angles, + `rotations` is an (src.n, n_nbor) array of angles, which should represent the rotations needed to align images within that class. `rotations` is measured in CCW radians. - `shifts` is None or an (n_classes, n_nbor) array of 2D shifts + `shifts` is None or an (src.n, n_nbor) array of 2D shifts which should represent the translation needed to best align the images within that class. - `correlations` is an (n_classes, n_nbor) array representing + `correlations` is an (src.n, n_nbor) array representing a correlation like measure between classified images and their base image (image index 0). Subclasses of should implement and extend this method. - :param classes: (n_classes, n_nbor) integer array of img indices. - :param reflections: (n_classes, n_nbor) bool array of corresponding reflections, + :param classes: (src.n, n_nbor) integer array of img indices. + :param reflections: (src.n, n_nbor) bool array of corresponding reflections, :param basis_coefficients: (n_img, self.alignment_basis.count) basis coefficients, :returns: (rotations, shifts, correlations) From 852fda5e5cf3a2f1b116c088567588058d8f05dd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 07:06:55 -0400 Subject: [PATCH 344/424] reword class set index documentation --- docs/source/class_source.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 2aaf81fe4d..675d4cadd6 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -102,10 +102,12 @@ reflection. All ``Class2D`` instances are expected to implement a ``classify`` method which returns ``(classes, reflections, distances)``. The three returned variables are expected to be 2D Numpy arrays in a neighbor network format having shape ``(src.n, -n_nbors)``. So to retrieve the input source indices for the first -class's neighbors, we would want ``classes[0,:]``. The first class -index should be the base image used for classification. So we would -expect ``classes[0,0]`` to be ``input_src.images[0]``. +n_nbors)``. So to retrieve the set of input source indices for the +first class's neighbors, we would want ``classes[0,:]``. The first +index ``classes[0,0]`` in the set is the index of the reference image +used for classification. In this case ``classes[0,0]=0``. The actual +underlying image would be ``input_src.images[0]``, or more generally +``input_src.images[classes[c,0]]`` for some class ``c``. No further class selection or order occurs during classification. Those methods are broken out into other components. From 15734b9df27a3473d4cf14893168cc4f66f1db32 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 07:17:28 -0400 Subject: [PATCH 345/424] rename ContrastImageQualityFunction ~> Vairance --- docs/source/class_source.rst | 29 ++++++++++---------- src/aspire/classification/__init__.py | 6 ++-- src/aspire/classification/class_selection.py | 10 +++---- tests/test_class_src.py | 12 ++++---- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 675d4cadd6..d5c6e6f140 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -114,7 +114,7 @@ Those methods are broken out into other components. Currently ASPIRE has a single classification algorithm known as ``RIRClass2D``. This algorithm uses multiple applications of PCA in -conjunction with Bispectrum analysis to identify nearest neighbors in +conjunction with bispectrum analysis to identify nearest neighbors in a rotationally invariant feature space. .. mermaid:: @@ -169,12 +169,11 @@ Global Class Selection techniques first compute the entire collection registered and aligned class averages, then compute some quality measure on all classes. -These are most similar to the historical MATLAB approaches, sometimes -referred to as "out-of-core" methods. It is believed that many legacy -MATLAB experiments computed contrast (variance) of all class averaged -images, sorted the class averages to express highest contrast, -potentially avoiding classes with views we've already seen. This can -be accomplished now by using the ``ContrastImageQualityFunction`` in a +Many classic experiments computed variance of each class averaged +image, sorting to express highest variance. Sometimes this is +referred to as Contrast. Often times the classes are selected to +avoid classes with views we've already seen. This can be accomplished +now by using the ``VarianceImageQualityFunction`` in a ``GlobalWithRepulsionClassSelector``. An SNR based approach is also provided, and a Bandpass method should @@ -205,7 +204,7 @@ described below. } ImageQualityFunction o-- WeightedImageQualityMixin ImageQualityFunction <|-- BandedSNRImageQualityFunction - ImageQualityFunction <|-- ContrastImageQualityFunction + ImageQualityFunction <|-- VarianceImageQualityFunction ImageQualityFunction <|-- BandpassImageQualityFunction_TBD class WeightedImageQualityMixin{ @@ -214,10 +213,10 @@ described below. WeightedImageQualityMixin <|-- RampWeightedImageQualityMixin WeightedImageQualityMixin <|-- BumpWeightedImageQualityMixin - GlobalClassSelector <|-- RampWeightedContrastImageQualityFunction - RampWeightedImageQualityMixin <|-- RampWeightedContrastImageQualityFunction - GlobalClassSelector <|-- BumpWeightedContrastImageQualityFunction - BumpWeightedImageQualityMixin <|-- BumpWeightedContrastImageQualityFunction + GlobalClassSelector <|-- RampWeightedVarianceImageQualityFunction + RampWeightedImageQualityMixin <|-- RampWeightedVarianceImageQualityFunction + GlobalClassSelector <|-- BumpWeightedVarianceImageQualityFunction + BumpWeightedImageQualityMixin <|-- BumpWeightedVarianceImageQualityFunction Class Repulsion ^^^^^^^^^^^^^^^ @@ -249,7 +248,7 @@ registered class average. This function should operate on a single Image, with conversions and broadcasting being handled behind the scenes. -An example would be ``ContrastImageQualityFunction`` which computes +An example would be ``VarianceImageQualityFunction`` which computes and returns contrast as variance. Another advantage of using the class is that it exposes and manages a @@ -264,8 +263,8 @@ WeightedImageQualityMixin applied prior to the image quality function call. Two concrete examples are provided -``BumpWeightedContrastImageQualityFunction`` and -``RampWeightedContrastImageQualityFunction`` which apply the +``BumpWeightedVarianceImageQualityFunction`` and +``RampWeightedVarianceImageQualityFunction`` which apply the respective weight functions prior to the Contrast calculation. Again, ``WeightedImageQualityMixin`` exposes and manages a grid cache, diff --git a/src/aspire/classification/__init__.py b/src/aspire/classification/__init__.py index eeef460e8b..16ed250f23 100644 --- a/src/aspire/classification/__init__.py +++ b/src/aspire/classification/__init__.py @@ -11,19 +11,19 @@ from .class2d import Class2D from .class_selection import ( BandedSNRImageQualityFunction, - BumpWeightedContrastImageQualityFunction, BumpWeightedImageQualityMixin, + BumpWeightedVarianceImageQualityFunction, ClassSelector, - ContrastImageQualityFunction, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, NeighborVarianceClassSelector, NeighborVarianceWithRepulsionClassSelector, - RampWeightedContrastImageQualityFunction, RampWeightedImageQualityMixin, + RampWeightedVarianceImageQualityFunction, RandomClassSelector, TopClassSelector, + VarianceImageQualityFunction, WeightedImageQualityMixin, ) from .rir_class2d import RIRClass2D diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index d2836f751b..4d2f5e3fff 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -502,7 +502,7 @@ def __call__(self, img): return res -class ContrastImageQualityFunction(ImageQualityFunction): +class VarianceImageQualityFunction(ImageQualityFunction): """ Computes the variance of pixels. """ @@ -636,16 +636,16 @@ def _weight_function(self, r): return np.exp(-1 / (1 - r**2) + 1) -class BumpWeightedContrastImageQualityFunction( - BumpWeightedImageQualityMixin, ContrastImageQualityFunction +class BumpWeightedVarianceImageQualityFunction( + BumpWeightedImageQualityMixin, VarianceImageQualityFunction ): """ Computes the variance of pixels after weighting with Bump function. """ -class RampWeightedContrastImageQualityFunction( - RampWeightedImageQualityMixin, ContrastImageQualityFunction +class RampWeightedVarianceImageQualityFunction( + RampWeightedImageQualityMixin, VarianceImageQualityFunction ): """ Computes the variance of pixels after weighting with Ramp function. diff --git a/tests/test_class_src.py b/tests/test_class_src.py index 5a919f666c..b52b00f21d 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -10,17 +10,17 @@ from aspire.classification import ( BandedSNRImageQualityFunction, BFRAverager2D, - BumpWeightedContrastImageQualityFunction, - ContrastImageQualityFunction, + BumpWeightedVarianceImageQualityFunction, DistanceClassSelector, GlobalClassSelector, GlobalWithRepulsionClassSelector, NeighborVarianceClassSelector, NeighborVarianceWithRepulsionClassSelector, - RampWeightedContrastImageQualityFunction, + RampWeightedVarianceImageQualityFunction, RandomClassSelector, RIRClass2D, TopClassSelector, + VarianceImageQualityFunction, ) from aspire.classification.class_selection import _HeapItem from aspire.denoising import DebugClassAvgSource, DefaultClassAvgSource @@ -210,9 +210,9 @@ def test_online_selector(cls_fixture, selector): QUALITY_FUNCTIONS = [ BandedSNRImageQualityFunction, - ContrastImageQualityFunction, - BumpWeightedContrastImageQualityFunction, - RampWeightedContrastImageQualityFunction, + VarianceImageQualityFunction, + BumpWeightedVarianceImageQualityFunction, + RampWeightedVarianceImageQualityFunction, ] From 82411db815eb3eef0bd64c4de689b604dc96ecc7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 07:20:41 -0400 Subject: [PATCH 346/424] small string typos --- docs/source/class_source.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index d5c6e6f140..b9f52a18c9 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -176,7 +176,7 @@ avoid classes with views we've already seen. This can be accomplished now by using the ``VarianceImageQualityFunction`` in a ``GlobalWithRepulsionClassSelector``. -An SNR based approach is also provided, and a Bandpass method should +An SNR based approach is also provided, and a bandpass method should be implemented in a future release. Again, these components are fully customizable and the base interfaces were designed with algorithm developers in mind. @@ -226,7 +226,7 @@ criterion. Currently we provide ``GreedyClassRepulsion``, but this mix-in class can be mimicked to implement alternate schemes. ``GreedyClassRepulsion`` is based on the following intuition. Assume -the selection has in fact ordered the classes s.t. *the "best" +the selection has in fact ordered the classes so that *the "best" classes occur first*. It follows that the "best" expression of a viewing angle locus will be the first seen. Now assume *the classifier returns classes with closest viewing angles* (up to @@ -265,7 +265,7 @@ applied prior to the image quality function call. Two concrete examples are provided ``BumpWeightedVarianceImageQualityFunction`` and ``RampWeightedVarianceImageQualityFunction`` which apply the -respective weight functions prior to the Contrast calculation. +respective weight functions prior to the variance calculation. Again, ``WeightedImageQualityMixin`` exposes and manages a grid cache, this time for grid weights. From 09252d48b188c63df36603c4a1a3e9ef254a49e2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 07:22:55 -0400 Subject: [PATCH 347/424] GreedyClassreplusion->GreedyClassRepulsionMixin --- docs/source/class_source.rst | 28 ++++++++++---------- src/aspire/classification/class_selection.py | 14 +++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index b9f52a18c9..879a78dafa 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -160,7 +160,7 @@ can reduce pipeline run times by an order of magnitude. ClassSelector <|-- RandomClassSelector ClassSelector <|-- NeighborVarianceClassSelector ClassSelector <|-- DistanceClassSelector - ClassSelector o-- GreedyClassRepulsion + ClassSelector o-- GreedyClassRepulsionMixin Global Class Selectors ---------------------- @@ -222,21 +222,21 @@ Class Repulsion ^^^^^^^^^^^^^^^ Class Repulsion are techniques used to avoid classes based on some -criterion. Currently we provide ``GreedyClassRepulsion``, but this -mix-in class can be mimicked to implement alternate schemes. - -``GreedyClassRepulsion`` is based on the following intuition. Assume -the selection has in fact ordered the classes so that *the "best" -classes occur first*. It follows that the "best" expression of a -viewing angle locus will be the first seen. Now assume *the -classifier returns classes with closest viewing angles* (up to -reflections). Then the classes formed by *neighbors of the current -expression are inferior*. The aggressiveness of the neighbor +criterion. Currently we provide ``GreedyClassRepulsionMixin``, but +this mix-in class can be mimicked to implement alternate schemes. + +``GreedyClassRepulsionMixin`` is based on the following +intuition. Assume the selection has in fact ordered the classes so +that *the "best" classes occur first*. It follows that the "best" +expression of a viewing angle locus will be the first seen. Now +assume *the classifier returns classes with closest viewing angles* +(up to reflections). Then the classes formed by *neighbors of the +current expression are inferior*. The aggressiveness of the neighbor repulsion count is tunable. -In practice, ``GreedyClassRepulsion`` is a mix-in designed to be mixed -into any other ``ClassSelector``. Note, that repulsion can (and will) -dramatically reduce the population of class averages returned. +In practice, ``GreedyClassRepulsionMixin`` is a mix-in designed to be +mixed into any other ``ClassSelector``. Note, that repulsion can (and +will) dramatically reduce the population of class averages returned. Image Quality Functions diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 4d2f5e3fff..5a2744dd0d 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -356,7 +356,7 @@ def _select(self, classes, reflections, distances): # preceeding components we can implement exclusion based on neighbor # distances or other ideas (VDM?) as different ClassRepulsion like # classes. -class GreedyClassRepulsion: +class GreedyClassRepulsionMixin: """ Mixin to overload class selection based on excluding classes we've already seen as neighbors of another class. @@ -370,14 +370,14 @@ def __init__(self, *args, **kwargs): Sets optional `exclude_k`. All other args and **kwargs are passed to super(). - GreedyClassRepulsion is similar to `cryo_select_subset` from + GreedyClassRepulsionMixin is similar to `cryo_select_subset` from MATLAB, but MATLAB found `exclude_k` iteratively based on a desired result set size. :param exclude_k: Number of neighbors from each class to exclude. Defaults to all neighbors. """ - # Pop of the parameter unique to GreedyClassRepulsion. + # Pop of the parameter unique to GreedyClassRepulsionMixin. self.exclude_k = kwargs.pop("exclude_k", None) # Instantiate an empty set to hold our excluded indices. @@ -426,17 +426,17 @@ def _check_selection(self, selection, n_img): class NeighborVarianceWithRepulsionClassSelector( - GreedyClassRepulsion, NeighborVarianceClassSelector + GreedyClassRepulsionMixin, NeighborVarianceClassSelector ): """ - Selects top classes based on highest contrast with GreedyClassRepulsion. + Selects top classes based on highest contrast with GreedyClassRepulsionMixin. """ -class GlobalWithRepulsionClassSelector(GreedyClassRepulsion, GlobalClassSelector): +class GlobalWithRepulsionClassSelector(GreedyClassRepulsionMixin, GlobalClassSelector): """ Extends ClassSelector for methods that require - passing over all class average images and also GreedyClassRepulsion. + passing over all class average images and also GreedyClassRepulsionMixin. """ From c8a04f91f098fd7e62598cdfc412566996e06f87 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 30 Mar 2023 08:09:02 -0400 Subject: [PATCH 348/424] make assert more general --- tests/test_matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_matrix.py b/tests/test_matrix.py index fbb4df2e57..e0af0e3954 100644 --- a/tests/test_matrix.py +++ b/tests/test_matrix.py @@ -312,7 +312,7 @@ def testRank1Approximation(self): self.assertTrue(A.shape == A_rank1.shape) # Check return is rank-1. - self.assertTrue(np.allclose([s[1], s[2]], 0)) + self.assertTrue(np.allclose(s[1:], 0)) def testFixSigns(self): """ From 3f1d4fcf6b6fb89d1cbbbb59e44871d10d5c1117 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 08:26:55 -0400 Subject: [PATCH 349/424] more misc typos --- docs/source/class_source.rst | 4 ++-- src/aspire/classification/class_selection.py | 10 ++++------ src/aspire/denoising/class_avg.py | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 879a78dafa..f76093e6a2 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -278,11 +278,11 @@ Averagers consume from a ``Source`` and return averaged images defined by class network arguments ``classes`` and ``reflections``. You may find the terms averaging and stacking used interchangeably in this context, so know that averaging does not always imply *arithmetic -mean*, +mean*. Some averaging techniques, those subclassing ``AligningAverager2D`` have distinct ``alignment`` and ``averaging`` stages. Others such as -Expectation-Maximization may perform these internally and provide only +expectation-maximization (EM) may perform these internally and provide only an opaque ``averages`` stage. .. mermaid:: diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 5a2744dd0d..4db507649d 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -272,7 +272,7 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): self.averager = averager if not isinstance(self.averager, Averager2D): raise ValueError( - f"`averager` should be instance of `Averger2D`, found {self.averager}." + f"`averager` should be instance of `Averager2D`, found {self.averager}." ) self._quality_function = quality_function @@ -453,15 +453,13 @@ class ImageQualityFunction(ABC): @abstractmethod def _function(self, img): """ - User defined 1d radial weight function. The function is - expected to be defined on [0,sqrt(2)]. Function range is - currently not limited, but [0,1] is favorable. + User defined scoring function. Function domain and range is + currently not limited, but [0,1] is favorable range. Developers can use the self._grid_cache for access to a grid_2d instance matching resolution of img. - :param img: 2d Numpy array - :returns: Image quality score + :param img: 2d Numpy array :returns: Image quality score """ def __call__(self, img): diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index a4e266cabb..92e9d9ce9a 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -61,7 +61,7 @@ def __init__( self.averager = averager if not isinstance(self.averager, Averager2D): raise ValueError( - f"`averager` should be instance of `Averger2D`, found {self.averager}." + f"`averager` should be instance of `Averager2D`, found {self.averager}." ) self._nn_classes = None From 15d928c4e10032b4faff5e41c42badef3afc6775 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 08:32:34 -0400 Subject: [PATCH 350/424] Add Debug details to class_avg docstrings --- src/aspire/denoising/class_avg.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 92e9d9ce9a..945e9324b6 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -235,8 +235,10 @@ class DebugClassAvgSource(ClassAvgSource): """ Source for denoised 2D images using class average methods. - Defaults to `RIRClass2D`, `TopClassSelector`, `BFRAverager2D` - using a single processor. + In this context Debug means defaulting to: + * Using the defaults for `RIRClass2D`. + * Using `TopClassSelector` to select all classes maintaining the same order as the input source. + * Using `BFRAverager2D` with defaults on a single core. """ def __init__( From 16769966425951412e681b07ab73f5547f4ea9f2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 09:07:11 -0400 Subject: [PATCH 351/424] change per instance __call__ to _call --- src/aspire/image/image_stacker.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index 71775f19ab..273aed2f05 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -29,7 +29,6 @@ def __init__(self): # This variable will be used to keep track of shape. self._return_image_size = None - @abc.abstractmethod def __call__(self, stack): """ Stack the elements of `stack`. @@ -57,6 +56,9 @@ def __call__(self, stack): :return: Stacked data as Image (or Numpy array). """ + stack = self._check_and_convert(stack) + + return self._return(self._call(stack)) def _check_and_convert(self, stack): """ @@ -118,10 +120,8 @@ class MeanImageStacker(ImageStacker): Stack using `mean`. """ - def __call__(self, stack): - stack = self._check_and_convert(stack) - - return self._return(stack.mean(axis=0)) + def _call(self, stack): + return stack.mean(axis=0) class MedianImageStacker(ImageStacker): @@ -129,10 +129,8 @@ class MedianImageStacker(ImageStacker): Stack using `median`. """ - def __call__(self, stack): - stack = self._check_and_convert(stack) - - return self._return(np.median(stack, axis=0)) + def _call(self, stack): + return np.median(stack, axis=0) class SigmaRejectionImageStacker(ImageStacker): @@ -183,12 +181,11 @@ def __init__(self, rejection_sigma=3): else: self.sigma = float(rejection_sigma) - def __call__(self, stack): + def _call(self, stack): """ Dispatches rejection method based on `rejection_sigma`. """ - stack = self._check_and_convert(stack) - return self._return(self._method(stack)) + return self._method(stack) def _gaussian_method(self, stack): """ @@ -206,7 +203,7 @@ def _gaussian_method(self, stack): masked_stack = ma.masked_array(stack, mask=outliers) # Return mean without the outliers - return masked_stack.mean(axis=0) + return masked_stack.mean(axis=0).data def _width_method(self, stack): """ @@ -226,7 +223,7 @@ def _width_method(self, stack): masked_stack = ma.masked_array(stack, mask=outliers) # Return mean withou the outliers - return masked_stack.mean(axis=0) + return masked_stack.mean(axis=0).data class WinsorizedImageStacker(ImageStacker): @@ -261,19 +258,17 @@ def __init__(self, percentile=0.1): raise ValueError(f"`percentile` must be [0,1], passed {percentile}.") self.percentile = percentile - def __call__(self, stack): + def _call(self, stack): """ Apply Winsorizing process the return the stack mean. """ - stack = self._check_and_convert(stack) - # Note, we intentionally disable `inplace` to avoid mutating # stack, just-in-case. stack = winsorize(stack, limits=self.percentile, inplace=False) # Return mean of Winsorized data. - return self._return(stack.mean(axis=0)) + return stack.mean(axis=0).data # The following will be blocked by ABC because they are not From ba1aae15ffa2d014f8bc3f7914c2af2625127a8e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 10:45:37 -0400 Subject: [PATCH 352/424] change corr to dist formula --- src/aspire/classification/rir_class2d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 88a38fddd6..5cd1c9a1b6 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -338,7 +338,7 @@ def _legacy_nn_classification(self, coeff_b, coeff_b_r): # # Also we've converted from correlation to distance=1-correlation # https://github.com/ComputationalCryoEM/ASPIRE-Python/discussions/867 - dist = 1 - corr + dist = np.sqrt(2.0 - 2.0 * corr) classes[start:finish] = np.argsort(dist, axis=1)[:, :n_nbor] # Store the corr values for the n_nbors in this batch From 63a714948e6288da56ce6131170a36ad0cb339bd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 11:05:17 -0400 Subject: [PATCH 353/424] Change _nn_* to class_indices, class_refl, and class_distances props adds docstrings --- gallery/tutorials/class_averaging.py | 4 +-- src/aspire/denoising/class_avg.py | 53 ++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/gallery/tutorials/class_averaging.py b/gallery/tutorials/class_averaging.py index ae58c0cf94..85cbb20b1b 100644 --- a/gallery/tutorials/class_averaging.py +++ b/gallery/tutorials/class_averaging.py @@ -197,8 +197,8 @@ # Report the identified neighbor indices with respect to the input # ``noise_src``. -classes = avgs._nn_classes[review_class] -reflections = avgs._nn_reflections[review_class] +classes = avgs.class_indices[review_class] +reflections = avgs.class_refl[review_class] logger.info(f"Class {review_class}'s neighors: {classes}") logger.info(f"Class {review_class}'s reflections: {reflections}") diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 945e9324b6..7d16a7743b 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -102,6 +102,59 @@ def selection_indices(self): self._class_select() return self._selection_indices + @property + def class_indices(self): + """ + Returns table of class image indices as `(src.n, n_nbors)` + Numpy array. + + Each row reprsents a class, with the columns ordered by + smallest `class_distances` from the reference image (zeroth + columm). + + Note `n_nbors` is managed by `self.classifier` and used here + for documentation. + + :return: Numpy array, integers. + """ + self._classify() + return self._nn_classes + + @property + def class_refl(self): + """ + Returns table of class image reflections as `(src.n, n_nbors)` + Numpy array. + + Follows same layout as `class_indices` but holds booleans that + are True when the image should be reflected before averaging. + + Note `n_nbors` is managed by `self.classifier` and used here + for documentation. + + :return: Numpy array, boolean. + """ + self._classify() + return self._nn_reflections + + @property + def class_distances(self): + """ + Returns table of class image distances as `(src.n, n_nbors)` + Numpy array. + + Follows same layout as `class_indices` but holds floats + representing the distance (returned by classifier) to the + zeroth image in each class. + + Note `n_nbors` is managed by `self.classifier` and used here + for documentation. + + :return: Numpy array, self.dtype. + """ + self._classify() + return self._nn_distances + def _class_select(self): """ Uses the `class_selector` in conjunction with the classifier results From ed9f7275aa7cddf8ca1d3d21e7cde14e6b76bba5 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 12:31:35 -0400 Subject: [PATCH 354/424] Guard `n`. --- src/aspire/denoising/class_avg.py | 19 +++++++++++++++++++ src/aspire/source/image.py | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 7d16a7743b..1cabe55ed6 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -74,12 +74,31 @@ def __init__( self._selected = False # Note n will potentially be updated after class selection. + # Manage delayed setting `n` once. + self._n_set = False + super().__init__( L=self.averager.src.L, n=self.averager.src.n, dtype=self.averager.src.dtype, ) + @ImageSource.n.setter + def n(self, n): + """ + Sets max image index `n` in `src` and associated + `ImageAccessor`. + + :param n: Number of images. + """ + + # Resets the protection of self._n exactly once after __init__. + if (self._n is not None) and (not self._n_set): + self._n = None + self._n_set = True + + super()._set_n(n) + def _classify(self): """ Perform the image classification (if not already done). diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 51f0160ee0..6e57cf4599 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -134,6 +134,7 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): self._img_accessor = _ImageAccessor(self._images, n) self.L = L + self._n = None self.n = n self.dtype = np.dtype(dtype) @@ -191,10 +192,20 @@ def n(self, n): Sets max image index `n` in `src` and associated `ImageAccessor`. - Used in sources that potentially reduce the set of images. + :param n: Number of images. + """ + self._set_n(n) + + def _set_n(self, n): + """ + Sets max image index `n` in `src` and associated + `ImageAccessor`. :param n: Number of images. """ + # Protect _n by default. + if self._n is not None: + raise RuntimeError("Source `n` is already set.") # Enforce type, just-in-case. if n != int(n): From ddaacb941453a0e031566d4d5eaff1249a5d71aa Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 12:38:19 -0400 Subject: [PATCH 355/424] Remove vestigial `n` in docstrings --- src/aspire/classification/class_selection.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 4db507649d..433e5397f1 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -38,11 +38,10 @@ class ClassSelector(ABC): def _select(self, classes, reflections, distances): """ Using the provided arguments, returns an array representing - an index into `n` selected `classes`. + an index into selected `classes`. This is the method developers should implement for a custom algorithm. - :param n: number of classes to select :param classes: (n_img, n_nbor) array of image indices :param reflections: (n_img, n_nbor) boolean array of reflections between `classes[i][0]` and classes[i][j]` :param distances: (n_img, n_nbor) array of distances between `classes[i][0]` and classes[i][j]` @@ -148,7 +147,7 @@ def __init__(self, seed=None): def _select(self, classes, reflections, distances): """ - Select random `n` classes from the population. + Select random classes from the population. """ # Assign uniform quality. self._quality_scores = np.zeros(self.n) From d3a0f9ccf3570909c4103120c9fcaea0c35ffa53 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 12:45:40 -0400 Subject: [PATCH 356/424] rename heap_id_dict to hea_idx_map --- src/aspire/classification/class_selection.py | 2 +- src/aspire/denoising/class_avg.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 433e5397f1..54c5a63ccd 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -314,7 +314,7 @@ def heap_ids(self): return [item.index for item in self.heap] @property - def heap_id_dict(self): + def heap_idx_map(self): """ Return map of image ids to heap position currently in the heap. """ diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index 1cabe55ed6..d5d477397a 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -257,7 +257,7 @@ def _images(self, indices): } # Get heap dict once to avoid traversing heap in a loop. - heap_dict = self.class_selector.heap_id_dict + heap_dict = self.class_selector.heap_idx_map # Create an empty array to pack results. L = self.averager.src.L From 2037970d30c71176bdc738567ef9de7a49deff39 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 13:10:17 -0400 Subject: [PATCH 357/424] white space cleanup --- src/aspire/source/image.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 6e57cf4599..7200c3be65 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -193,10 +193,10 @@ def n(self, n): `ImageAccessor`. :param n: Number of images. - """ + """ self._set_n(n) - def _set_n(self, n): + def _set_n(self, n): """ Sets max image index `n` in `src` and associated `ImageAccessor`. From dc98a29aaff7ed34465132f18c9e6f040640a46f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 13:10:41 -0400 Subject: [PATCH 358/424] check averager matches before using heap cache --- src/aspire/denoising/class_avg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index d5d477397a..b1395ff550 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -226,7 +226,10 @@ def _images(self, indices): # Check if there is a cache available from class selection component. # Note, we can use := for this in the branch directly, when Python>=3.8 heap_inds = None - if hasattr(self.class_selector, "heap"): + # Check we are using the same averager before attempting to use heap. + if hasattr(self.class_selector, "heap") and ( + self.averager == self.class_selector.averager + ): # Then check if request matches anything in the heap. heap_inds = set(indices).intersection(self.class_selector.heap_ids) From d60e1baf60b60adc35297b6992c622c322bca8e3 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 13:25:42 -0400 Subject: [PATCH 359/424] doc updates --- docs/source/class_source.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index f76093e6a2..0d085e7063 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -97,17 +97,17 @@ Classifiers *********** Classifiers take an image ``Source`` and attempts to classify into -``classes`` that identify images with similar viewing angles up to +``class_indices`` that identify images with similar viewing angles up to reflection. All ``Class2D`` instances are expected to implement a -``classify`` method which returns ``(classes, reflections, -distances)``. The three returned variables are expected to be 2D +``classify`` method which returns ``(class_indices, class_refl, +class_distances)``. The three returned variables are expected to be 2D Numpy arrays in a neighbor network format having shape ``(src.n, n_nbors)``. So to retrieve the set of input source indices for the -first class's neighbors, we would want ``classes[0,:]``. The first -index ``classes[0,0]`` in the set is the index of the reference image -used for classification. In this case ``classes[0,0]=0``. The actual +first class's neighbors, we would want ``class_indices[0,:]``. The first +index ``class_indices[0,0]`` in the set is the index of the reference image +used for classification. In this case ``class_indices[0,0]=0``. The actual underlying image would be ``input_src.images[0]``, or more generally -``input_src.images[classes[c,0]]`` for some class ``c``. +``input_src.images[class_indices[c,0]]`` for some class ``c``. No further class selection or order occurs during classification. Those methods are broken out into other components. @@ -275,7 +275,7 @@ Averagers ********* Averagers consume from a ``Source`` and return averaged images -defined by class network arguments ``classes`` and ``reflections``. +defined by class network arguments ``class_indices`` and ``class_refl``. You may find the terms averaging and stacking used interchangeably in this context, so know that averaging does not always imply *arithmetic mean*. From 1200fd1515d687f07679c0a65c18a1f627315195 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 13:26:50 -0400 Subject: [PATCH 360/424] apply abstractmenthodness to image stacker refactor --- src/aspire/image/image_stacker.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/aspire/image/image_stacker.py b/src/aspire/image/image_stacker.py index 273aed2f05..500c671dfb 100644 --- a/src/aspire/image/image_stacker.py +++ b/src/aspire/image/image_stacker.py @@ -29,11 +29,27 @@ def __init__(self): # This variable will be used to keep track of shape. self._return_image_size = None + @abc.abstractmethod + def _call(self, stack): + """ + + Subclasses must implement this method. + + Given a 2D Numpy array performs the stacking computation + returning a 1D array. Packing and unpacking the array is done + by `_check_and_convert` and `_return` respectively in + `__call__`. + + :param stack: 2D Numpy array. + + :return: 1D Numpy array. + """ + def __call__(self, stack): """ Stack the elements of `stack`. - Stack admits an Image class, or an Numpy array. + Admits an Image class, or an Numpy array. In the case of Numpy array, the data should be 2D where the first (slow) dimension is stack axis, From d6dc5584027841761d2122b5a0431e2276dcf988 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 13:41:35 -0400 Subject: [PATCH 361/424] breakup the ImageStacker and Averager diagrams --- docs/source/class_source.rst | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 0d085e7063..cdd12d3ca1 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -307,6 +307,23 @@ an opaque ``averages`` stage. AligningAverager2D <|-- ReddyChetterjiAverager2D ReddyChetterjiAverager2D <|-- BFSReddyChetterjiAverager2D +Each ``AligningAverager2D`` can be configured to use a custom +``ImageStacker`` if desired. + +ImageStacker +------------ + +``ImageStacker`` provides an interface for the common task of stacking +images. Implementations for common stacking methods are provided and +should work for both ``Image`` and (1D) coefficient stacks. Users +experimenting with advanced stacking are responsible for selecting an +ImageStacker method appropriate for their data. + +Note that the ASPIRE default is naturally ``MeanImageStacker``. + +.. mermaid:: + + classDiagram class ImageStacker{ stack() } @@ -322,15 +339,3 @@ an opaque ``averages`` stage. SigmaRejectionImageStacker .. Gaussian SigmaRejectionImageStacker .. FWHM ImageStacker <|-- WinsorizedImageStacker - -ImageStacker ------------- - -``ImageStacker`` provides an interface for the common task of stacking -images. Implementations for common stacking methods are provided and -should work for both ``Image`` and (1D) coefficient stacks. Users -experimenting with advanced stacking are responsible for selecting an -ImageStacker method appropriate for their data. - -Note that the ASPIRE default is naturally ``MeanImageStacker``. - From a7d5097772755d545642d4b706017212368b6c08 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 30 Mar 2023 13:50:45 -0400 Subject: [PATCH 362/424] only upload build doc directory CI artifact --- .github/workflows/workflow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index dbf6dd7b10..eb39b95ead 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -173,5 +173,5 @@ jobs: uses: actions/upload-artifact@v3 with: name: sphinx-docs - path: docs + path: docs/build retention-days: 7 From 7f0a72491d7ce0c48ab6689dd78a42b723fe7047 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 08:16:28 -0400 Subject: [PATCH 363/424] forgot version string update --- docs/source/class_source.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index cdd12d3ca1..4520d50764 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -86,7 +86,7 @@ mappings etc. ``DefaultClassAvgSource`` applies the most sensible defaults available in the current ASPIRE release. ``DefaultClassAvgSource`` takes a -version string, such as ``11.0`` which will return a specific +version string, such as ``0.11.0`` which will return a specific configuration. This version should allow users to perform a similar experiment across releases as ASPIRE implements improved methods. When a version is not provided, ``DefaultClassAvgSource`` defaults to From f4870fe5e8f5d27454b31f028507fcd869b61c28 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 08:18:13 -0400 Subject: [PATCH 364/424] line len formatting --- docs/source/class_source.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 4520d50764..4c3e52e8d7 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -97,16 +97,17 @@ Classifiers *********** Classifiers take an image ``Source`` and attempts to classify into -``class_indices`` that identify images with similar viewing angles up to -reflection. All ``Class2D`` instances are expected to implement a +``class_indices`` that identify images with similar viewing angles up +to reflection. All ``Class2D`` instances are expected to implement a ``classify`` method which returns ``(class_indices, class_refl, -class_distances)``. The three returned variables are expected to be 2D -Numpy arrays in a neighbor network format having shape ``(src.n, -n_nbors)``. So to retrieve the set of input source indices for the -first class's neighbors, we would want ``class_indices[0,:]``. The first -index ``class_indices[0,0]`` in the set is the index of the reference image -used for classification. In this case ``class_indices[0,0]=0``. The actual -underlying image would be ``input_src.images[0]``, or more generally +class_distances)``. The three returned variables are expected to be +2D Numpy arrays in a neighbor network format having shape +``(src.n, n_nbors)``. So to retrieve the set of input source indices +for the first class's neighbors, we would want ``class_indices[0,:]``. +The first index ``class_indices[0,0]`` in the set is the index of the +reference image used for classification. In this case +``class_indices[0,0]=0``. The actual underlying image would be +``input_src.images[0]``, or more generally ``input_src.images[class_indices[c,0]]`` for some class ``c``. No further class selection or order occurs during classification. From 9623d005fd03cf5a56c8bd9095d024281c8eadc6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 08:30:47 -0400 Subject: [PATCH 365/424] class_source edits --- docs/source/class_source.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index 4c3e52e8d7..d1318481f9 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -110,7 +110,7 @@ reference image used for classification. In this case ``input_src.images[0]``, or more generally ``input_src.images[class_indices[c,0]]`` for some class ``c``. -No further class selection or order occurs during classification. +No further class selection or ordering occurs during classification. Those methods are broken out into other components. Currently ASPIRE has a single classification algorithm known as @@ -167,14 +167,14 @@ Global Class Selectors ---------------------- Global Class Selection techniques first compute the entire collection -registered and aligned class averages, then compute some quality +of registered and aligned class averages, then compute some quality measure on all classes. Many classic experiments computed variance of each class averaged image, sorting to express highest variance. Sometimes this is -referred to as Contrast. Often times the classes are selected to -avoid classes with views we've already seen. This can be accomplished -now by using the ``VarianceImageQualityFunction`` in a +referred to as contrast. Often times the classes were selected to +avoid classes with views already seen. This can be accomplished now +by using the ``VarianceImageQualityFunction`` in a ``GlobalWithRepulsionClassSelector``. An SNR based approach is also provided, and a bandpass method should @@ -182,8 +182,8 @@ be implemented in a future release. Again, these components are fully customizable and the base interfaces were designed with algorithm developers in mind. -Implementing concrete ``GlobalClassSelector`` leverage subcomponents -described below. +To implementing concrete ``GlobalClassSelector`` instances, leverage +the subcomponents described below. .. mermaid:: @@ -244,13 +244,13 @@ Image Quality Functions ^^^^^^^^^^^^^^^^^^^^^^^ The ``ImageQualityFunction`` interface provides a consistent way to -bring your own function to measure the quality of an aligned and +bring your own function to measure the quality of a single aligned and registered class average. This function should operate on a single Image, with conversions and broadcasting being handled behind the scenes. An example would be ``VarianceImageQualityFunction`` which computes -and returns contrast as variance. +and returns variance. Another advantage of using the class is that it exposes and manages a grid cache, which is handy to avoid recomputing the same grid for From d8185f6dab5d375e344fa451b6becc6579372a0e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 08:42:33 -0400 Subject: [PATCH 366/424] Use the new propertys from review in example code --- .../experimental_abinitio_pipeline_10073.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index 8cc37612e4..48d722c5f6 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -37,7 +37,7 @@ BFRAverager2D, GlobalWithRepulsionClassSelector, ) -from aspire.denoising import DefaultClassAvgSource +from aspire.denoising import ClassAvgSource from aspire.reconstruction import MeanEstimator from aspire.source import RelionSource @@ -93,16 +93,26 @@ # Build up the customized components. basis = FFBBasis2D(src.L, dtype=src.dtype) +classifier = RIRClass2D(src, n_nbor=n_nbor, nn_implementation="sklearn") averager = BFRAverager2D(basis, src, num_procs=16) quality_function = BandedSNRImageQualityFunction() class_selector = GlobalWithRepulsionClassSelector(averager, quality_function) # Assemble the components into the Source. -avgs = DefaultClassAvgSource( - src, n_nbor=n_nbor, averager=averager, class_selector=class_selector +avgs = ClassAvgSource( + src, classifier=classifier, averager=averager, class_selector=class_selector ) + +# Save out the resulting Nearest Neighbor networks arrays. +np.savez("experimental_10073_class_averages_class_indices.npz", + 'class_indices'=avgs.class_indices, + 'class_refl'=avgs.class_refl, + 'class_distances'=avgs.class_distances, + ) + +# Save the class selection rankings. np.save( - "experimental_10073_class_averages_indices.npy", + "experimental_10073_class_averages_selection_indices.npy", avgs.selection_indices, ) From 0e275ce06d4bad390e9beacc5de712ba0d2622c6 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 09:08:36 -0400 Subject: [PATCH 367/424] more minor string updates --- src/aspire/classification/class_selection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/classification/class_selection.py b/src/aspire/classification/class_selection.py index 54c5a63ccd..d035dc609e 100644 --- a/src/aspire/classification/class_selection.py +++ b/src/aspire/classification/class_selection.py @@ -254,7 +254,7 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): Initializes a GlobalClassSelector. Because GlobalClassSelectors must compute all class averages, - a heap is maintained cache the top class averages as scored by + a heap cache maintains the top class averages as scored by `quality_function`. If you have the memory, recommend setting the cache to be > n_classes*img_size*img_size*img.dtype. @@ -263,7 +263,7 @@ def __init__(self, averager, quality_function, heap_size_limit_bytes=2e9): returns numeric quality score. This score will be used to sort the classes. User's may provide a callable function, but extending `ImageQualityFunction` is recommended. For - example, this module provides methods for Contrast and SNR + example, this module provides methods for variance and SNR based quality. :param heap_size_limit_bytes: Max heap size in Bytes. Defaults 2GB, 0 will disable. From 6b8458cdc4c20ab8be7c72e3c5e2c067927167f4 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 09:43:13 -0400 Subject: [PATCH 368/424] Syntax error --- gallery/experiments/experimental_abinitio_pipeline_10073.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index 48d722c5f6..8dd734884d 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -105,9 +105,9 @@ # Save out the resulting Nearest Neighbor networks arrays. np.savez("experimental_10073_class_averages_class_indices.npz", - 'class_indices'=avgs.class_indices, - 'class_refl'=avgs.class_refl, - 'class_distances'=avgs.class_distances, + class_indices=avgs.class_indices, + class_refl=avgs.class_refl, + class_distances=avgs.class_distances, ) # Save the class selection rankings. From e09d61eaed456bbac0e5538928a1564932a29f4c Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 31 Mar 2023 09:44:30 -0400 Subject: [PATCH 369/424] Tox checks --- .../experimental_abinitio_pipeline_10073.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index 8dd734884d..b573fa8504 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -36,6 +36,7 @@ BandedSNRImageQualityFunction, BFRAverager2D, GlobalWithRepulsionClassSelector, + RIRClass2D, ) from aspire.denoising import ClassAvgSource from aspire.reconstruction import MeanEstimator @@ -104,11 +105,12 @@ ) # Save out the resulting Nearest Neighbor networks arrays. -np.savez("experimental_10073_class_averages_class_indices.npz", - class_indices=avgs.class_indices, - class_refl=avgs.class_refl, - class_distances=avgs.class_distances, - ) +np.savez( + "experimental_10073_class_averages_class_indices.npz", + class_indices=avgs.class_indices, + class_refl=avgs.class_refl, + class_distances=avgs.class_distances, +) # Save the class selection rankings. np.save( From 16dab5fc45636eb27cb70f0229a7fc2324812720 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 3 Apr 2023 09:43:36 -0400 Subject: [PATCH 370/424] Remove redundant Image conversion bug in denoised_src (cov2d) --- src/aspire/denoising/denoised_src.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/denoising/denoised_src.py b/src/aspire/denoising/denoised_src.py index 2a4ecfe5d0..34a671f251 100644 --- a/src/aspire/denoising/denoised_src.py +++ b/src/aspire/denoising/denoised_src.py @@ -38,7 +38,7 @@ def _images(self, indices): if self._cached_im is not None: logger.info("Loading images from cache") return self.generation_pipeline.forward( - Image(self._cached_im[indices, :, :]), indices + self._cached_im[indices, :, :], indices ) # start and end (and indices) refer to the indices in the DenoisedImageSource From 1a4149d67e8c1c253672aad88f3a2205478f9114 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Tue, 4 Apr 2023 11:21:22 -0400 Subject: [PATCH 371/424] ImageSource copy decorator --- src/aspire/source/image.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index c36dd01605..611f02f60c 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from collections import OrderedDict from collections.abc import Iterable - +import copy import mrcfile import numpy as np import pandas as pd @@ -98,6 +98,15 @@ def __getitem__(self, indices): return self.fun(indices) +def as_copy(func): + def wrapper(self, *args, **kwargs): + obj_copy = copy.deepcopy(self) + func_copy = copy.deepcopy(func) + func_copy(obj_copy, *args, **kwargs) + return obj_copy + return wrapper + + class ImageSource(ABC): """ When creating an `ImageSource` object, a 'metadata' table holds metadata information about all images in the @@ -425,6 +434,7 @@ def _apply_source_filters(self, im_orig, indices): self.filter_indices[indices], ) + @as_copy def cache(self): logger.info("Caching source images") self._cached_im = self.images[:] @@ -445,6 +455,7 @@ def _images(self, indices): Subclasses handle cached image check as well as applying transforms in the generation pipeline. """ + @as_copy def downsample(self, L): assert ( L <= self.L @@ -459,6 +470,7 @@ def downsample(self, L): self.L = L + @as_copy def whiten(self, noise_estimate): """ Modify the `ImageSource` in-place by appending a whitening filter to the generation pipeline. @@ -492,6 +504,7 @@ def whiten(self, noise_estimate): logger.info("Adding Whitening Filter Xform to end of generation pipeline") self.generation_pipeline.add_xform(FilterXform(whiten_filter)) + @as_copy def phase_flip(self): """ Perform phase flip on images in the source object using CTF information. @@ -516,6 +529,7 @@ def phase_flip(self): " Confirm you have correctly populated CTFFilters." ) + @as_copy def invert_contrast(self, batch_size=512): """ invert the global contrast of images @@ -565,6 +579,7 @@ def invert_contrast(self, batch_size=512): logger.info("Adding Scaling Xform to end of generation pipeline") self.generation_pipeline.add_xform(Multiply(scale_factor)) + @as_copy def normalize_background(self, bg_radius=1.0, do_ramp=True): """ Normalize the images by the noise background From bff0c41234b969f4b8bc483945c154769b391ec2 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Tue, 4 Apr 2023 13:36:36 -0400 Subject: [PATCH 372/424] Tests modified to pass again --- tests/test_class2D.py | 2 +- tests/test_coordinate_source.py | 12 ++++++------ tests/test_downsample.py | 2 +- tests/test_load_images.py | 2 +- tests/test_preprocess_pipeline.py | 14 +++++++------- tests/test_simulation.py | 6 +++--- tests/test_starfile_stack.py | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_class2D.py b/tests/test_class2D.py index 4b2703464b..d0885dc170 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -47,7 +47,7 @@ def setUp(self): vols=v, dtype=self.dtype, ) - self.src.cache() # Precompute image stack + self.src = self.src.cache() # Precompute image stack # Calculate some projection images self.imgs = self.src.images[:] diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index a2c1edb83a..d9bb254e16 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -387,9 +387,9 @@ def testImages(self): self.assertTrue(np.array_equal(imgs_coord[i], imgs_star[i])) def testCached(self): - src_cached = BoxesCoordinateSource(self.files_box) + src = BoxesCoordinateSource(self.files_box) src_uncached = BoxesCoordinateSource(self.files_box) - src_cached.cache() + src_cached = src.cache() self.assertTrue( np.array_equal( src_cached.images[:].asnumpy(), src_uncached.images[:].asnumpy() @@ -512,11 +512,11 @@ def testSave(self): def testPreprocessing(self): # ensure that the preprocessing methods that do not require CTF do not error src = BoxesCoordinateSource(self.files_box, max_rows=5) - src.downsample(60) - src.normalize_background() + src = src.downsample(60) + src = src.normalize_background() noise_estimator = WhiteNoiseEstimator(src) - src.whiten(noise_estimator) - src.invert_contrast() + src = src.whiten(noise_estimator) + src = src.invert_contrast() # call .images() to ensure the filters are applied # and not just added to pipeline src.images[:5] diff --git a/tests/test_downsample.py b/tests/test_downsample.py index 9eaf0c6bbd..1a44a538d2 100644 --- a/tests/test_downsample.py +++ b/tests/test_downsample.py @@ -91,7 +91,7 @@ def createImages(self, L, L_ds): imgs_org = sim.images[: self.n] # get images after downsample - sim.downsample(L_ds) + sim = sim.downsample(L_ds) imgs_ds = sim.images[: self.n] return imgs_org, imgs_ds diff --git a/tests/test_load_images.py b/tests/test_load_images.py index 2376cc2bd0..1aabed14e5 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -199,7 +199,7 @@ def testBadNDArray(self): def testRelionSourceCached(self): src_cached = RelionSource(self.starfile_path, data_folder=self.data_folder) - src_cached.cache() + src_cached = src_cached.cache() self.assertTrue( np.array_equal(src_cached.images[:].asnumpy(), self.src.images[:].asnumpy()) ) diff --git a/tests/test_preprocess_pipeline.py b/tests/test_preprocess_pipeline.py index 109f532b40..fb7d2427ec 100644 --- a/tests/test_preprocess_pipeline.py +++ b/tests/test_preprocess_pipeline.py @@ -38,7 +38,7 @@ def get_sim_object(L, dtype): def testPhaseFlip(L, dtype): sim = get_sim_object(L, dtype) imgs_org = sim.images[:num_images] - sim.phase_flip() + sim = sim.phase_flip() imgs_pf = sim.images[:num_images] # check energy conservation @@ -64,7 +64,7 @@ def testEmptyPhaseFlip(caplog): ) # assert we log a warning to the user with caplog.at_level(logging.WARNING): - sim.phase_flip() + _ = sim.phase_flip() assert "No Filters found" in caplog.text @@ -74,7 +74,7 @@ def testNormBackground(L, dtype): bg_radius = 1.0 grid = grid_2d(sim.L, indexing="yx") mask = grid["r"] > bg_radius - sim.normalize_background() + sim = sim.normalize_background() imgs_nb = sim.images[:num_images].asnumpy() new_mean = np.mean(imgs_nb[:, mask]) new_variance = np.var(imgs_nb[:, mask]) @@ -94,7 +94,7 @@ def testWhiten(dtype): L = 64 sim = get_sim_object(L, dtype) noise_estimator = AnisotropicNoiseEstimator(sim) - sim.whiten(noise_estimator) + sim = sim.whiten(noise_estimator) imgs_wt = sim.images[:num_images].asnumpy() # calculate correlation between two neighboring pixels from background @@ -117,7 +117,7 @@ def testWhiten2(dtype): L = 63 sim = get_sim_object(L, dtype) noise_estimator = AnisotropicNoiseEstimator(sim) - sim.whiten(noise_estimator.filter) + sim = sim.whiten(noise_estimator.filter) imgs_wt = sim.images[:num_images].asnumpy() corr_coef = np.corrcoef(imgs_wt[:, L - 1, L - 1], imgs_wt[:, L - 2, L - 1]) @@ -130,11 +130,11 @@ def testWhiten2(dtype): def testInvertContrast(L, dtype): sim1 = get_sim_object(L, dtype) imgs_org = sim1.images[:num_images] - sim1.invert_contrast() + sim1 = sim1.invert_contrast() imgs1_rc = sim1.images[:num_images] # need to set the negative images to the second simulation object sim2 = ArrayImageSource(-imgs_org) - sim2.invert_contrast() + sim2 = sim2.invert_contrast() imgs2_rc = sim2.images[:num_images] # all images should be the same after inverting contrast diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 3e1df49549..ca01c82edd 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -167,7 +167,7 @@ def testSimulationCached(self): noise_adder=WhiteNoiseAdder(var=1), dtype=self.dtype, ) - sim_cached.cache() + sim_cached = sim_cached.cache() self.assertTrue( np.array_equal(sim_cached.images[:].asnumpy(), self.sim.images[:].asnumpy()) ) @@ -185,7 +185,7 @@ def testSimulationImagesNoisy(self): def testSimulationImagesDownsample(self): # The simulation already generates images of size 8 x 8; Downsampling to resolution 8 should thus have no effect - self.sim.downsample(8) + self.sim = self.sim.downsample(8) images = self.sim.clean_images[:512].asnumpy() self.assertTrue( np.allclose( @@ -203,7 +203,7 @@ def testSimulationImagesShape(self): self.assertTrue(images.shape, (8, 8, 25)) def testSimulationImagesDownsampleShape(self): - self.sim.downsample(6) + self.sim = self.sim.downsample(6) first_image = self.sim.images[0].asnumpy()[0] self.assertEqual(first_image.shape, (6, 6)) diff --git a/tests/test_starfile_stack.py b/tests/test_starfile_stack.py index 207139d441..886bceb8f1 100644 --- a/tests/test_starfile_stack.py +++ b/tests/test_starfile_stack.py @@ -56,7 +56,7 @@ def testMetadata(self): ) def testImageDownsample(self): - self.src.downsample(16) + self.src = self.src.downsample(16) first_image = self.src.images[0].asnumpy()[0] self.assertEqual(first_image.shape, (16, 16)) From 398fed21ec2f652b9b8995ed72469d5ec9171ba0 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Tue, 4 Apr 2023 13:46:31 -0400 Subject: [PATCH 373/424] Modified other calling sites for as_copy decorated methods --- gallery/experiments/cov2d_experiment.py.dontrun | 4 ++-- gallery/experiments/cov3d_experiment.dontrun | 4 ++-- gallery/experiments/experimental_abinitio_pipeline.py | 8 ++++---- gallery/experiments/preprocess_imgs_exp.py.dontrun | 10 +++++----- gallery/experiments/simulated_abinitio_pipeline.py | 6 +++--- gallery/tutorials/basic_image_array.py | 2 +- gallery/tutorials/ctf.py | 6 +++--- gallery/tutorials/lecture_feature_demo.py | 4 ++-- gallery/tutorials/pipeline_demo.py | 2 +- gallery/tutorials/preprocess_imgs_sim.py | 10 +++++----- gallery/tutorials/starfile.py | 4 ++-- src/aspire/commands/cov3d.py | 6 +++--- src/aspire/commands/denoise.py | 6 +++--- src/aspire/commands/extract_particles.py | 8 ++++---- src/aspire/commands/preprocess.py | 10 +++++----- 15 files changed, 45 insertions(+), 45 deletions(-) diff --git a/gallery/experiments/cov2d_experiment.py.dontrun b/gallery/experiments/cov2d_experiment.py.dontrun index 7d58217d95..24d2e22799 100755 --- a/gallery/experiments/cov2d_experiment.py.dontrun +++ b/gallery/experiments/cov2d_experiment.py.dontrun @@ -31,7 +31,7 @@ source = RelionSource( # Downsample the images logger.info(f"Set the resolution to {MAX_RESOLUTION} X {MAX_RESOLUTION}") if MAX_RESOLUTION < source.L: - source.downsample(MAX_RESOLUTION) + source = source.downsample(MAX_RESOLUTION) else: logger.warn(f"Unable to downsample to {max_resolution}, using {source.L}") @@ -47,7 +47,7 @@ logger.info(f"var_noise before whitening {var_noise}") # Whiten the noise of images logger.info(f"Whiten the noise of images from the noise estimator") -source.whiten(noise_estimator) +source = source.whiten(noise_estimator) # Note this changes the noise variance, # flattening spectrum and converging towards 1. # Noise variance will be recomputed in DenoiserCov2D by default. diff --git a/gallery/experiments/cov3d_experiment.dontrun b/gallery/experiments/cov3d_experiment.dontrun index e0c11d088b..a907649480 100644 --- a/gallery/experiments/cov3d_experiment.dontrun +++ b/gallery/experiments/cov3d_experiment.dontrun @@ -32,7 +32,7 @@ source = RelionSource( # Downsample the images print(f"Set the resolution to {MAX_RESOLUTION} X {MAX_RESOLUTION}") if MAX_RESOLUTION < source.L: - source.downsample(MAX_RESOLUTION) + source = source.downsample(MAX_RESOLUTION) # Estimate the noise of images print("Estimate the noise of images using anisotropic method") @@ -40,7 +40,7 @@ noise_estimator = AnisotropicNoiseEstimator(source, batchSize=512) # Whiten the noise of images print("Whiten the noise of images from the noise estimator") -source.whiten(noise_estimator) +source = source.whiten(noise_estimator) # Estimate the noise variance. This is needed for the covariance estimation step below. noise_variance = noise_estimator.estimate() print(f"Noise Variance = {noise_variance}") diff --git a/gallery/experiments/experimental_abinitio_pipeline.py b/gallery/experiments/experimental_abinitio_pipeline.py index 6f5ff59c1f..c48d654ebd 100644 --- a/gallery/experiments/experimental_abinitio_pipeline.py +++ b/gallery/experiments/experimental_abinitio_pipeline.py @@ -66,7 +66,7 @@ # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") -src.downsample(img_size) +src = src.downsample(img_size) # Peek if interactive: @@ -74,11 +74,11 @@ # Use phase_flip to attempt correcting for CTF. logger.info("Perform phase flip to input images.") -src.phase_flip() +src = src.phase_flip() # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) -src.whiten(aiso_noise_estimator) +src = src.whiten(aiso_noise_estimator) # Plot the noise profile for inspection if interactive: @@ -92,7 +92,7 @@ # # Optionally invert image contrast, depends on data convention. # # This is not needed for 10028, but included anyway. # logger.info("Invert the global density contrast") -# src.invert_contrast() +# src = src.invert_contrast() # %% # Optional: CWF Denoising diff --git a/gallery/experiments/preprocess_imgs_exp.py.dontrun b/gallery/experiments/preprocess_imgs_exp.py.dontrun index d2b28e1b87..eb6db87b99 100644 --- a/gallery/experiments/preprocess_imgs_exp.py.dontrun +++ b/gallery/experiments/preprocess_imgs_exp.py.dontrun @@ -31,25 +31,25 @@ print('Obtain original images') imgs_od = source.images[:nimgs_ext] print('Perform phase flip to input images') -source.phase_flip() +source = source.phase_flip() imgs_pf = source.images[:nimgs_ext] max_resolution = 60 print(f'Downsample resolution to {max_resolution} X {max_resolution}') -source.downsample(max_resolution) +source = source.downsample(max_resolution) imgs_ds = source.images[:nimgs_ext] print('Normalize images to noise background') -source.normalize_background() +source = source.normalize_background() imgs_nb = source.images[:nimgs_ext] print('Whiten noise of images') noise_estimator = WhiteNoiseEstimator(source) -source.whiten(noise_estimator) +source = source.whiten(noise_estimator) imgs_wt = source.images[:nimgs_ext] print('Invert global density contrast') -source.invert_contrast() +source = source.invert_contrast() imgs_rc = source.images[:nimgs_ext] # plot the first images diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index e0015ad42a..ad3fe176b6 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -114,11 +114,11 @@ def noise_function(x, y): # Use phase_flip to attempt correcting for CTF. logger.info("Perform phase flip to input images.") -src.phase_flip() +src = src.phase_flip() # Estimate the noise and `Whiten` based on the estimated noise aiso_noise_estimator = AnisotropicNoiseEstimator(src) -src.whiten(aiso_noise_estimator) +src = src.whiten(aiso_noise_estimator) # Plot the noise profile for inspection if interactive: @@ -130,7 +130,7 @@ def noise_function(x, y): src.images[:10].show() # Cache to memory for some speedup -src.cache() +src = src.cache() # %% # Optional: CWF Denoising diff --git a/gallery/tutorials/basic_image_array.py b/gallery/tutorials/basic_image_array.py index 9e5a00229a..e99f72b328 100644 --- a/gallery/tutorials/basic_image_array.py +++ b/gallery/tutorials/basic_image_array.py @@ -139,7 +139,7 @@ def noise_function(x, y): # Once we have the estimator instance, # we can use it in a transform applied to our Source. -imgs_src.whiten(noise_estimator) +imgs_src = imgs_src.whiten(noise_estimator) # Peek at two whitened images and their corresponding spectrum. diff --git a/gallery/tutorials/ctf.py b/gallery/tutorials/ctf.py index 576927066a..0656563f40 100644 --- a/gallery/tutorials/ctf.py +++ b/gallery/tutorials/ctf.py @@ -352,7 +352,7 @@ def generate_example_image(L, noise_variance=0.1): # %% # Phase flip the images. -src.phase_flip() +src = src.phase_flip() src.images[:4].show() # %% @@ -374,11 +374,11 @@ def generate_example_image(L, noise_variance=0.1): pixel_size=1.338, max_rows=10000, ) -src.downsample(64) # easier to visualize +src = src.downsample(64) # easier to visualize src.images[:3].show() # Phase flip -src.phase_flip() +src = src.phase_flip() src.images[:3].show() diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index 737701017f..df66f86c0b 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -279,7 +279,7 @@ def noise_function(x, y): aiso_noise_estimator = AnisotropicNoiseEstimator(sim4) # Whiten based on the estimated noise -sim4.whiten(aiso_noise_estimator) +sim4 = sim4.whiten(aiso_noise_estimator) # What do the whitened images look like... sim4.images[:10].show() @@ -300,7 +300,7 @@ def noise_function(x, y): max_rows=1024, ) -src.downsample(img_size) +src = src.downsample(img_size) src.images[:10].show() diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 207a2292bb..0e7f5815a3 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -143,7 +143,7 @@ def download(url, save_path, chunk_size=1024 * 1024): # -------------- # We apply ``phase_flip()`` to correct for CTF effects. -src.phase_flip() +src = src.phase_flip() src.images[0:10].show() diff --git a/gallery/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/preprocess_imgs_sim.py index 7f874d691d..0adf7f626b 100644 --- a/gallery/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/preprocess_imgs_sim.py @@ -82,26 +82,26 @@ imgs_od = source.images[0].asnumpy() logger.info("Perform phase flip to input images.") -source.phase_flip() +source = source.phase_flip() imgs_pf = source.images[0].asnumpy() max_resolution = 15 logger.info(f"Downsample resolution to {max_resolution} X {max_resolution}") if max_resolution < source.L: - source.downsample(max_resolution) + source = source.downsample(max_resolution) imgs_ds = source.images[0].asnumpy() logger.info("Normalize images to background noise.") -source.normalize_background() +source = source.normalize_background() imgs_nb = source.images[0].asnumpy() logger.info("Whiten noise of images") noise_estimator = WhiteNoiseEstimator(source) -source.whiten(noise_estimator) +source = source.whiten(noise_estimator) imgs_wt = source.images[0].asnumpy() logger.info("Invert the global density contrast if need") -source.invert_contrast() +source = source.invert_contrast() imgs_rc = source.images[0].asnumpy() diff --git a/gallery/tutorials/starfile.py b/gallery/tutorials/starfile.py index 6a2fc9a069..e78ed7f744 100644 --- a/gallery/tutorials/starfile.py +++ b/gallery/tutorials/starfile.py @@ -28,7 +28,7 @@ # Reduce the resolution L = 12 # You may try 16 but it takes a significant amount of time. -source.downsample(L) +source = source.downsample(L) # %% # Noise Estimation and Whitening @@ -37,7 +37,7 @@ # Estimate noise in the ImageSource instance noise_estimator = AnisotropicNoiseEstimator(source) # Apply whitening to ImageSource -source.whiten(noise_estimator) +source = source.whiten(noise_estimator) # Display subset of the images images = source.images[:10] diff --git a/src/aspire/commands/cov3d.py b/src/aspire/commands/cov3d.py index 1131dd7261..9f2ff0c45f 100644 --- a/src/aspire/commands/cov3d.py +++ b/src/aspire/commands/cov3d.py @@ -47,10 +47,10 @@ def cov3d( starfile, data_folder=data_folder, pixel_size=pixel_size, max_rows=max_rows ) - source.downsample(max_resolution) - source.cache() + source = source.downsample(max_resolution) + source = source.cache() - source.whiten() + source = source.whiten() basis = FBBasis3D((max_resolution, max_resolution, max_resolution)) mean_estimator = MeanEstimator(source, basis, batch_size=8192) mean_est = mean_estimator.estimate() diff --git a/src/aspire/commands/denoise.py b/src/aspire/commands/denoise.py index 9f0f32c06d..e0900e69c9 100644 --- a/src/aspire/commands/denoise.py +++ b/src/aspire/commands/denoise.py @@ -75,10 +75,10 @@ def denoise( logger.info(f"Set the resolution to {max_resolution} X {max_resolution}") if max_resolution < source.L: # Downsample the images - source.downsample(max_resolution) + source = source.downsample(max_resolution) else: logger.warn(f"Unable to downsample to {max_resolution}, using {source.L}") - source.cache() + source = source.cache() # Specify the fast FB basis method for expending the 2D images basis = FFBBasis2D((source.L, source.L)) @@ -96,7 +96,7 @@ def denoise( # Whiten the noise of images logger.info("Whiten the noise of images from the noise estimator") - source.whiten(noise_estimator) + source = source.whiten(noise_estimator) if denoise_method == "CWF": logger.info("Denoise the images using CWF cov2D method.") diff --git a/src/aspire/commands/extract_particles.py b/src/aspire/commands/extract_particles.py index 6fd3946275..7a53eb90e1 100644 --- a/src/aspire/commands/extract_particles.py +++ b/src/aspire/commands/extract_particles.py @@ -143,14 +143,14 @@ def extract_particles( # optional preprocessing steps if 0 < downsample < src.L: - src.downsample(downsample) + src = src.downsample(downsample) if normalize_bg: - src.normalize_background() + src = src.normalize_background() if whiten: estimator = WhiteNoiseEstimator(src) - src.whiten(estimator) + src = src.whiten(estimator) if invert_contrast: - src.invert_contrast() + src = src.invert_contrast() # saves to .mrcs and STAR file with column "_rlnImageName" src.save( diff --git a/src/aspire/commands/preprocess.py b/src/aspire/commands/preprocess.py index 474ae29359..b85bb8fb50 100644 --- a/src/aspire/commands/preprocess.py +++ b/src/aspire/commands/preprocess.py @@ -97,24 +97,24 @@ def preprocess( if flip_phase: logger.info("Perform phase flip to input images") - source.phase_flip() + source = source.phase_flip() if downsample and downsample < source.L: logger.info(f"Downsample resolution to {downsample} X {downsample}") - source.downsample(downsample) + source = source.downsample(downsample) if normalize_bg: logger.info("Normalize images to noise background") - source.normalize_background() + source = source.normalize_background() if whiten: logger.info("Whiten noise of images") noise_estimator = WhiteNoiseEstimator(source) - source.whiten(noise_estimator) + source = source.whiten(noise_estimator) if invert_contrast: logger.info("Invert global density contrast") - source.invert_contrast() + source = source.invert_contrast() source.save( starfile_out, batch_size=batch_size, save_mode=save_mode, overwrite=overwrite From 4027730fad183eef253a26290ca1160c666bc846 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Tue, 4 Apr 2023 15:17:06 -0400 Subject: [PATCH 374/424] tox checked --- src/aspire/source/image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 611f02f60c..d376489318 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -1,9 +1,10 @@ +import copy import logging import os.path from abc import ABC, abstractmethod from collections import OrderedDict from collections.abc import Iterable -import copy + import mrcfile import numpy as np import pandas as pd @@ -104,6 +105,7 @@ def wrapper(self, *args, **kwargs): func_copy = copy.deepcopy(func) func_copy(obj_copy, *args, **kwargs) return obj_copy + return wrapper From 1d9cdb1b65c18b14f611c90a557aa32bb0fcd86a Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Wed, 5 Apr 2023 13:18:51 -0400 Subject: [PATCH 375/424] Custom hack to get around finufft bug --- src/aspire/source/image.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index d376489318..14c1dd3ccd 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -1,6 +1,7 @@ import copy import logging import os.path +import types from abc import ABC, abstractmethod from collections import OrderedDict from collections.abc import Iterable @@ -175,6 +176,40 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") + def __deepcopy__(self, memo): + """ + A custom __deepcopy__ implementation to individually handle special cases. + Mostly copied over from https://stackoverflow.com/a/71125311 + """ + # Get a reference to the bound deepcopy method + deepcopy_method = self.__deepcopy__ + # Temporarily disable __deepcopy__ to avoid infinite recursion + self.__deepcopy__ = None + # Create a deepcopy cp using the normal procedure + cp = copy.deepcopy(self, memo) + + # -------------------------------------- + # Handle any special cases for cp here. + # -------------------------------------- + # This is the whole reason this method exists. If this section is empty, + # then this entire __deepcopy__ implementation can be removed. + + # The 'dtype' attribute is a numpy module level singleton obtained by np.dtype(..) call + # The 'finufft' library currently compares this to the result of a new np.dtype(..) call + # by reference, not by value (as it should). A deepcopy will make a copy of the singleton, + # and thus comparison by reference will fail. Till this bug in 'finufft' is removed, we assign + # self.dtype to dtype + cp.dtype = self.dtype + + # -------------------------------------- + + # Reattach the bound deepcopy method + self.__deepcopy__ = deepcopy_method + # Get the unbounded function corresponding to the bound deepcopy method and rebind to cp + cp.__deepcopy__ = types.MethodType(deepcopy_method.__func__, cp) + + return cp + def __getitem__(self, indices): """ Check `indices` and return slice of current Source as a new From 965bcfbac3d3d287e286af3ce923ef0ac8c8cc5b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 6 Apr 2023 13:22:46 -0400 Subject: [PATCH 376/424] Review remarks --- docs/source/class_source.rst | 2 +- docs/source/quickstart.rst | 2 +- .../experimental_abinitio_pipeline_10028.py | 2 +- .../experimental_abinitio_pipeline_10073.py | 2 +- .../experimental_abinitio_pipeline_10081.py | 2 +- .../preprocess_imgs_exp.py.dontrun | 4 +- .../simulated_abinitio_pipeline.py | 2 +- gallery/tutorials/README.rst | 4 +- gallery/tutorials/aspire_introduction.py | 157 +++++++++--------- gallery/tutorials/tutorials/ctf.py | 2 +- src/aspire/abinitio/commonline_base.py | 2 +- src/aspire/source/image.py | 2 +- tox.ini | 2 +- 13 files changed, 93 insertions(+), 92 deletions(-) diff --git a/docs/source/class_source.rst b/docs/source/class_source.rst index d1318481f9..c4dd021e14 100644 --- a/docs/source/class_source.rst +++ b/docs/source/class_source.rst @@ -131,7 +131,7 @@ Class Selectors Class Selectors consume the output of ``Class2D`` and attempt to order and/or filter classes down to a selection. Selecting the "best" -classes in CryoEM problems is still an area of active research. Some +classes in cryo-EM problems is still an area of active research. Some common methods are provided, along with an extensible base interface. Generally, Class Selection comes in two flavors depending on what diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index 20b160059e..a60275115b 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -8,7 +8,7 @@ CRYO projections (MRC files). aspire -Running the ``aspire`` module as a script allows one to run different stages of the Cryo-EM data pipeline. +Running the ``aspire`` module as a script allows one to run different stages of the cryo-EM data pipeline. Substitute ```` with one of the available ``aspire`` commands. Use ``aspire --help`` to display all available commands and ``aspire --help`` to display configurable options for a particular ````. Currently, the following operations can be run with ASPIRE: diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index fed1c7be84..559a2b2155 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -4,7 +4,7 @@ This notebook introduces a selection of components corresponding to loading real Relion picked -particle Cryo-EM data and running key ASPIRE-Python +particle cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. Specifically this pipeline uses the diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index b573fa8504..3f827700a2 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -4,7 +4,7 @@ This notebook introduces a selection of components corresponding to loading real Relion picked -particle Cryo-EM data and running key ASPIRE-Python +particle cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. This demonstrates using the Global BandedSNRImageQualityFunction diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index 2a8f4e5333..43b47d110e 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -4,7 +4,7 @@ This notebook introduces a selection of components corresponding to loading real Relion picked -particle Cryo-EM data and running key ASPIRE-Python +particle cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. Specifically this pipeline uses the diff --git a/gallery/experiments/preprocess_imgs_exp.py.dontrun b/gallery/experiments/preprocess_imgs_exp.py.dontrun index d2b28e1b87..06f84babf4 100644 --- a/gallery/experiments/preprocess_imgs_exp.py.dontrun +++ b/gallery/experiments/preprocess_imgs_exp.py.dontrun @@ -1,6 +1,6 @@ #!/usr/bin/env python """ -This script illustrates how to preprocess experimental CryoEM images +This script illustrates how to preprocess experimental cryo-EM images before starting the pipeline of reconstructing 3D map. """ @@ -15,7 +15,7 @@ STARFILE_IN = '/path/to/untarred/empiar/dataset/input.star' PIXEL_SIZE = 1.34 NUM_IMGS = 100 -print('This script illustrates how to preprocess experimental CryoEM images') +print('This script illustrates how to preprocess experimental cryo-EM images') print(f'Read in images from {STARFILE_IN} and preprocess the images') source = RelionSource( STARFILE_IN, diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 691cc79ed4..d1189fc9c9 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -4,7 +4,7 @@ This notebook introduces a selection of components corresponding to generating realistic -simulated Cryo-EM data and running key ASPIRE-Python +simulated cryo-EM data and running key ASPIRE-Python Abinitio model components as a pipeline. """ diff --git a/gallery/tutorials/README.rst b/gallery/tutorials/README.rst index a8613650e2..b49aebbe95 100644 --- a/gallery/tutorials/README.rst +++ b/gallery/tutorials/README.rst @@ -8,5 +8,5 @@ API Introduction ---------------- Newcomers should start here to get an overview of ASPIRE's primitive -structures and interfaces, building up to a short Simulated Abinitio -Pipeline. +structures and interfaces, building up to a short simulated ab-initio +pipeline. diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index 6f90cd39b1..c4fba03e53 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -4,7 +4,7 @@ In this notebook we will introduce the core API components, then demonstrate basic usage corresponding to topics from Princeton's -MATH586. +MAT586. """ # %% @@ -15,7 +15,7 @@ # Anaconda Python, by following the instructions in the README. `The # instructions for developers is the most comprehensive # `_. -# Windows is provided, but generally Linux and OSX are recommended, +# Windows is provided, but generally Linux and MacOS are recommended, # with Linux being the most diversely tested platform. # @@ -26,7 +26,7 @@ # typical data science codes on your machine (a netbook for example), # you may use Tiger/Adroit/Della at Princeton or another cluster. # After logging into Tiger, ``module load anaconda3/2020.7`` and -# continue to follow the anaconda instructions for developers in the +# continue to follow the Anaconda instructions for developers in the # link above. Those instructions should create a working environment # for tinkering with ASPIRE code found in this notebook. @@ -37,7 +37,6 @@ # Along the way we will import relevant components from ``aspire``. # Users may also import ``aspire`` once as a top level package. -import logging import os import matplotlib.pyplot as plt @@ -73,7 +72,7 @@ # * - ``Basis`` # - Basis conversions and operations. # * - ``Source`` -# - Produces primitive components. ``ImageSource`` produces ``Images``. +# - Produces primitive components. ``ImageSource`` produces ``Image`` instances. # %% @@ -85,15 +84,15 @@ # class is a thin wrapper over Numpy arrays for a stack containing 1 # or more images (2D data). In this notebook we won't be working # directly with the ``Image`` class a lot, but it will be one of the -# fundemental structures behind the scenes. A lot of ASPIRE code +# fundamental structures behind the scenes. A lot of ASPIRE code # passes around ``Image`` and ``Volume`` instances. # %% # Create an ``Image`` instance from random data. img_data = np.random.random((100, 100)) img = Image(img_data) -logging.info(f"img shape: {img.shape}") # Note this produces a stack of 1. -logging.info(f"str(img): {img}") # Note this produces a stack of 1. +print(f"img shape: {img.shape}") # Note this produces a stack of one. +print(f"str(img): {img}") # %% # Create an Image for a stack of 3 100x100 images. @@ -130,7 +129,7 @@ # Like ``Image``, the `Volume # `_ # class is a thin wrapper over Numpy arrays that provides specialized -# methods for a stack containing 1 or more volumes (3D data). +# methods for a stack containing one or more volumes (3D data). from aspire.volume import Volume @@ -143,22 +142,22 @@ # ``Volumes`` ``.map`` and ``.mrc`` are currently supported. For # ``.npy`` Numpy can be used. -# A low res example file is included in the repo as a sanity check. +# A low-resolution example file is included in the repo as a sanity check. # We can instantiate this as an ASPIRE Volume instance using # ``Volume.load()``. file_path = os.path.join(os.getcwd(), "data", "clean70SRibosome_vol_65p.mrc") -v = Volume.load(file_path) +vol = Volume.load(file_path) # %% # More interesting data requires downloading locally. A common # starting dataset can be downloaded from EMDB at # ``_. After # downloading the associated `map` file, unzip in local directory. To -# simplify things, this notebook defaults to a small low resolution +# simplify things, this notebook defaults to a small low-resolution # sample file instead. Unfortunately real data can be quite large so # we do not ship it with the repo. -# v = Volume.load("path/to/EMD-2660/map/emd_2660.map") +# vol = Volume.load("path/to/EMD-2660/map/emd_2660.map") # %% # Downsample Volume @@ -170,18 +169,18 @@ # Volume.downsample() returns a new Volume instance. # We will use this lower resolution volume later, calling it `v2`. -v2 = v.downsample(img_size) +vol_ds = vol.downsample(img_size) # L is often used as short hand for image and volume sizes (in pixels/voxels). -L = v2.resolution +L = vol_ds.resolution # %% # Plot Data # """"""""" # For quick sanity checking purposes we can view some plots. # We'll use three orthographic projections, one per axis. -orthographic_projections = np.empty((3, L, L), dtype=v2.dtype) +orthographic_projections = np.empty((3, L, L), dtype=vol_ds.dtype) for i in range(3): - orthographic_projections[i] = np.sum(v2, axis=(0, i + 1)) + orthographic_projections[i] = np.sum(vol_ds, axis=(0, i + 1)) Image(orthographic_projections).show() # %% @@ -195,9 +194,9 @@ # rotations (ie, axis-angle). Other ASPIRE components dealing with 3D # rotations will generally expect instances of ``Rotation``. # -# A common task in Computational CryoEM is generating random -# projections, which can be achieved by applying random 3D -# rotations. The following code will generate some random rotations, +# A common task in computational cryo-EM is generating random +# projections, by applying random 3D rotations to a volume and projecting along the z-axis. +# The following code will generate some random rotations, # and use the ``Volume.project()`` method to return an ``Image`` # instance representing the stack of projections. We can display # projection images using the ``Image.show()`` method. @@ -209,14 +208,14 @@ # %% # We can access the Numpy array holding the actual stack of 3x3 matrices: -logging.info(rots) -logging.info(rots.matrices) +print(rots) +print(rots.matrices) # %% # Using the zero-th (and in this case, only) volume, compute # projections using the stack of rotations: -projections = v.project(0, rots) -logging.info(projections) +projections = vol.project(0, rots) +print(projections) # %% # ``project()`` returns an Image instance, so we can call ``show``. @@ -225,7 +224,7 @@ # %% # Neat, we've generated random projections of some real data. This # tutorial will go on to show how this can be performed systematically with -# other Computational CryoEM data simulation tasks. +# other cryo-EM data simulation tasks. # %% # The ``filter`` Package @@ -234,7 +233,9 @@ # `_ # are a collection of classes which once configured can be applied to # ``Images``, typically in an ``ImageSource`` pipeline which will be -# discussed in a later section. +# discussed in a later section. Specifically, applying a ``Filter`` +# convolves the filter with the images contained in the ``Image`` +# instance. # %% # @@ -250,12 +251,7 @@ # +sign() # } # -# Filter o-- DualFilter # Filter o-- FunctionFilter -# Filter o-- PowerFilter -# Filter o-- LambdaFilter -# Filter o-- MultiplicativeFilter -# Filter o-- ScaledFilter # Filter o-- ArrayFilter # Filter o-- ScalarFilter # Filter o-- ZeroFilter @@ -266,33 +262,36 @@ # %% # ``CTFFilter`` and ``RadialCTFFilter`` are the most common filters # encountered when starting out and are detailed in -# :ref:`sphx_glr_auto_tutorials_tutorials_ctf.py` The other filters +# :ref:`sphx_glr_auto_tutorials_tutorials_ctf.py`. The other filters # are used behind the scenes in components like ``NoiseAdders`` or -# more advanced customized pipelines. +# more advanced customized pipelines. Several filters for internal or +# advanced use cases are omitted from the diagram, but can be found in +# the `aspire.operators.filter` module. # %% # ``Basis`` # --------- # ASPIRE provides a selection of ``Basis`` classes designed for -# working with CryoEM data in two and three dimensions. Most of these -# basis implementations are optimized for efficient rotations, often -# called the *"Steerable"* property. As of writing most algorithms in -# ASPIRE are written to work well with the Fast Fourier Bessel Basis -# classes ``FFBBasis2D`` and ``FFBBasis3D``. These correspond to -# direct slower reference ``FBBasis2D`` and ``FBBasis3D`` methods. +# working with cryo-EM data in two and three dimensions. Most of +# these basis implementations are optimized for efficient rotations, +# often called the *"steerable"* property. As of this writing most +# algorithms in ASPIRE are written to work well with the fast +# Fourier-Bessel (FFB) basis classes ``FFBBasis2D`` and +# ``FFBBasis3D``. These correspond to direct slower reference +# ``FBBasis2D`` and ``FBBasis3D`` classes. # -# Recently, a related Fourier Bessel method using Fast Laplacian -# Eigenfunction transforms was integrated as ``FLEBasis2D``. -# Additional Prolate Spheroidal Wave Function methods are available -# via ``FPSWFBasis2D`` and ``FPSWFBasis3D``, but their integration -# into other components like 2D covariance analysis is incomplete, and -# slated for a future release. +# Recently, a related Fourier-Bessel method using fast Laplacian +# eigenfunction (FLE) transforms was integrated as ``FLEBasis2D``. +# Additional prolate spheroidal wave function (PSWF) methods are +# available via ``FPSWFBasis2D`` and ``FPSWFBasis3D``, but their +# integration into other components like 2D covariance analysis is +# incomplete, and slated for a future release. # %% # The ``source`` Package # ---------------------- # -# `aspire.source +# The `aspire.source # `_ # package contains a collection of data source interfaces. # Ostensibly, a ``Source`` is a producer of some primitive type, most @@ -300,10 +299,11 @@ # are designed to accept an ``ImageSource``. # # The first reason for this is to normalize the way a wide variety of -# higher level components interface. ``ImageSource`` instances have a -# consistent method ``images`` which must be implemented to serve up -# images dynamically. This supports batch computation among other -# things. ``Source`` instances also store and serve up metadata like +# higher-level components interface. ``ImageSource`` instances have a +# consistent property ``images`` which must be implemented to serve up +# images dynamically using a square-bracket ``[]`` syntax familiar to +# Numpy users. This supports batch computation among other things. +# ``Source`` instances also store and serve up metadata like # `rotations`, `dtype`, and support pipelining transformations. # # The second reason is so we can design an experiment using a @@ -367,25 +367,25 @@ # %% # Generate a Simulation instance based on the original volume data. -sim = Simulation(n=num_imgs, vols=v) +sim = Simulation(n=num_imgs, vols=vol) # Display the first 10 images sim.images[:10].show() # Hi Res # %% -# Repeat for the lower resolution (downsampled) volume v2. -sim = Simulation(n=num_imgs, vols=v2) +# Repeat for the lower resolution (downsampled) volume vol_ds. +sim = Simulation(n=num_imgs, vols=vol_ds) sim.images[:10].show() # Lo Res # %% # Note both of those simulations have the same rotations because they # had the same seed by default, We recreate ``sim`` with a distinct # seed to get different random samples (of rotations). -sim = Simulation(n=num_imgs, vols=v2, seed=42) +sim = Simulation(n=num_imgs, vols=vol_ds, seed=42) sim.images[:10].show() # %% # We can also view the rotations used to create these projections. -logging.info(sim.rotations) +print(sim.rotations) # %% # Given any ``Source``, we can also take slices using typical slicing @@ -396,7 +396,7 @@ # We can also generate random selections. # Shuffle indices then take the first 5. -shuffled_inds = np.random.permutation(sim.n)[:5] +shuffled_inds = np.random.choice(sim.n, 5, replace=False) sim_shuffled_subset = sim[shuffled_inds] # %% @@ -432,9 +432,9 @@ # %% # Get the sample variance, then create a NoiseAdder based on that variance. var = np.var(sim.images[:].asnumpy()) -logging.info(f"Sample Variance: {var}") +print(f"Sample Variance: {var}") target_noise_variance = 10.0 * var -logging.info(f"Target Noise Variance: {target_noise_variance}") +print(f"Target Noise Variance: {target_noise_variance}") white_noise_adder = WhiteNoiseAdder(target_noise_variance) # %% @@ -447,7 +447,7 @@ # ``seed``. # Creating the new simulation with this additional noise is easy: -sim = Simulation(n=num_imgs, vols=v2, noise_adder=white_noise_adder) +sim = Simulation(n=num_imgs, vols=vol_ds, noise_adder=white_noise_adder) # These should be rather noisy now ... sim.images[:10].show() @@ -459,7 +459,8 @@ # Lets see how the estimate compares. # # In this case, we know the noise to be white, so we can proceed directly to -# `WhiteNoiseEstimator `_. The noise estimators consume from a ``Source``. +# `WhiteNoiseEstimator `_. +# The noise estimators consume from an ``ImageSource``. # # The white noise estimator should log a diagnostic variance value. # Internally, it also uses the estimation results to build a @@ -490,13 +491,13 @@ def noise_function(x, y): return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) -# In python, functions are first class objects. We take advantage of +# In Python, functions are first class objects. We take advantage of # that to pass this function around as a variable. The function is # evaluated later, internally, during pipeline execution. custom_noise = CustomNoiseAdder(noise_filter=FunctionFilter(noise_function)) # Create yet another Simulation source to tinker with. -sim = Simulation(n=num_imgs, vols=v2, noise_adder=custom_noise) +sim = Simulation(n=num_imgs, vols=vol_ds, noise_adder=custom_noise) sim.images[:10].show() # %% @@ -528,8 +529,8 @@ def noise_function(x, y): # %% # Common Image Corruptions # ------------------------ -# ``Simulation`` provides several configurable types of common CryoEM -# image corruptions. Users should be aware that Amplitude and Offset +# ``Simulation`` provides several configurable types of common cryo-EM +# image corruptions. Users should be aware that amplitude and offset # corruption is enabled by default. # %% @@ -554,8 +555,8 @@ def noise_function(x, y): # CTF # ^^^ # By default, no CTF corruption is configured. -# To enable, we must configure one or more CTFFilter. -# Usually we will create a range of CTFFilters for a variety of +# To enable, we must configure one or more ``CTFFilter`` instances. +# Usually we will create a range of filters for a variety of # defocus levels. from aspire.operators import RadialCTFFilter @@ -578,7 +579,7 @@ def noise_function(x, y): sim = Simulation( n=num_imgs, - vols=v2, + vols=vol_ds, amplitudes=1, offsets=0, noise_adder=white_noise_adder, @@ -625,7 +626,7 @@ def noise_function(x, y): src.downsample(img_size) # %% -# Relionsource will auto populate ``CTFFilter`` instances from the +# ``RelionSource`` will auto-populate ``CTFFilter`` instances from the # STAR file metadata when available. Having these filters allows us to # perform a phase flipping correction. src.phase_flip() @@ -637,18 +638,18 @@ def noise_function(x, y): # %% # Pipeline Roadmap # ---------------- -# Now that the primitives have been introduced we can explore higher -# level components. The higher level components are designed to be -# modular and cacheable (to memory or disk) to support experimentation -# with entire pipelines or focused algorithmic development on specific -# components. Most pipelines will follow a flow of data and -# components moving mostly left to right in the table below. This -# table is not exhaustive, but represents some of the most common -# components. +# Now that the primitives have been introduced we can explore +# higher-level components. The higher-level components are designed +# to be modular and cacheable (to memory or disk) to support +# experimentation with entire pipelines or focused algorithmic +# development on specific components. Most pipelines will follow a +# flow of data and components moving mostly left to right in the table +# below. This table is not exhaustive, but represents some of the +# most common components. # %% # +----------------+--------------------+-----------------+----------------+---------------------+ -# | Image Processing | Abinitio | +# | Image Processing | Ab initio | # +----------------+--------------------+-----------------+----------------+---------------------+ # | Data | Preprocessing | Denoising | Orientation | 3D Reconstruction | # +================+====================+=================+================+=====================+ @@ -666,7 +667,7 @@ def noise_function(x, y): # +----------------+--------------------+-----------------+----------------+---------------------+ # %% -# We're now ready to explore a small example end to end abinitio +# We're now ready to explore a small example end-to-end ab initio # pipeline using simulated data. # :ref:`sphx_glr_auto_tutorials_pipeline_demo.py` diff --git a/gallery/tutorials/tutorials/ctf.py b/gallery/tutorials/tutorials/ctf.py index 9b1c7dba4c..3e1ac01722 100644 --- a/gallery/tutorials/tutorials/ctf.py +++ b/gallery/tutorials/tutorials/ctf.py @@ -386,7 +386,7 @@ def generate_example_image(L, noise_variance=0.1): # %% # CTFFIND4: External Validation # ----------------------------- -# CTFFIND4 is often used by other CryoEM distributions, +# CTFFIND4 is often used by other cryo-EM distributions, # and was used to confirm the forward CTF filter model of ASPIRE. # For transparency, an example run using the ``test_img.mrc`` # generated earlier is documented. diff --git a/src/aspire/abinitio/commonline_base.py b/src/aspire/abinitio/commonline_base.py index a9ff8c62a4..83e733eafc 100644 --- a/src/aspire/abinitio/commonline_base.py +++ b/src/aspire/abinitio/commonline_base.py @@ -217,7 +217,7 @@ def estimate_shifts(self, equations_factor=1, max_memory=4000): unknowns, shifts in x, y for a pair of images. The detailed implementation can be found in the book chapter as below: Y. Shkolnisky and A. Singer, - Center of Mass Operators for CryoEM - Theory and Implementation, + Center of Mass Operators for Cryo-EM - Theory and Implementation, Modeling Nanoscale Imaging in Electron Microscopy, T. Vogt, W. Dahmen, and P. Binev (Eds.) Nanostructure Science and Technology Series, diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 7200c3be65..8b56117ac6 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -120,7 +120,7 @@ class ImageSource(ABC): def __init__(self, L, n, dtype="double", metadata=None, memory=None): """ - A Cryo-EM ImageSource object that supplies images along with other parameters for image manipulation. + A cryo-EM ImageSource object that supplies images along with other parameters for image manipulation. :param L: resolution of (square) images (int) :param n: The total number of images available diff --git a/tox.ini b/tox.ini index 3159111123..aaf37b64eb 100644 --- a/tox.ini +++ b/tox.ini @@ -66,7 +66,7 @@ max-line-length = 88 extend-ignore = E203, E501 per-file-ignores = __init__.py: F401 - gallery/tutorials/aspire_introduction.py: F401, E402 + gallery/tutorials/aspire_introduction.py: T201, F401, E402 gallery/tutorials/configuration.py: T201, E402 gallery/tutorials/tutorials/ctf.py: T201, E402 # Ignore Sphinx gallery builds From e3145c1a3c5a9b4a18d3c917e15e20d4569d8088 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Thu, 6 Apr 2023 19:52:08 -0400 Subject: [PATCH 377/424] correct place to initialize mrc_index_to_particles array (after filtering/truncation) (#906) --- src/aspire/source/coordinates.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 4e2689cb51..49532f5a78 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -70,18 +70,6 @@ def __init__(self, files, particle_size, max_rows, B): # Read shapes of all micrographs self.mrc_shapes = self._get_mrc_shapes() - # map mrc indices to particle indices - # i'th element contains a list of particle indices corresponding to i'th mrc - self.mrc_index_to_particles = [] - for mrc_idx in range(self.num_micrographs): - self.mrc_index_to_particles.append( - [ - particle_idx - for particle_idx, particle in enumerate(self.particles) - if particle[0] == mrc_idx - ] - ) - # get first mrc and coordinate file to report some data first_mrc_index, first_coord = self.particles[0] first_mrc = self.mrc_paths[first_mrc_index] @@ -140,6 +128,18 @@ def __init__(self, files, particle_size, max_rows, B): ImageSource.__init__(self, L=L, n=n, dtype=dtype) + # map mrc indices to particle indices + # i'th element contains a list of particle indices corresponding to i'th mrc + self.mrc_index_to_particles = [] + for mrc_idx in range(self.num_micrographs): + self.mrc_index_to_particles.append( + [ + particle_idx + for particle_idx, particle in enumerate(self.particles) + if particle[0] == mrc_idx + ] + ) + # CTF envelope decay factor self.B = B # set CTF metadata to defaults From 7c35ffb7e4a2e687bbffc82985deac074d1ab5d4 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Thu, 6 Apr 2023 21:31:09 -0400 Subject: [PATCH 378/424] Add note in image expansion demo about the legacy binary data transpose --- gallery/tutorials/tutorials/image_expansion.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gallery/tutorials/tutorials/image_expansion.py b/gallery/tutorials/tutorials/image_expansion.py index d27093d4be..93adaec129 100644 --- a/gallery/tutorials/tutorials/image_expansion.py +++ b/gallery/tutorials/tutorials/image_expansion.py @@ -31,10 +31,13 @@ file_path = os.path.join( os.path.dirname(os.getcwd()), "data", "example_data_np_array.npy" ) +# Here the images were saved in Fortran order. Transpose from (129, +# 129, 10) to (10, 129, 129) so that the stack axis is the slowest +# moving axis. org_images = np.load(file_path).T # Set the sizes of images (129, 129) -img_size = 129 +img_size = org_images.shape[-1] # %% # Expand Images with Normal Fourier-Bessel Basis Method From a66debf457eef0a8c2e39554468ced0438310a2e Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Fri, 7 Apr 2023 14:05:40 -0400 Subject: [PATCH 379/424] Reimplementation of ImageSource metadata as a dict of ndarays --- src/aspire/source/image.py | 272 ++++++++++++++++++++---------- src/aspire/storage/starfile.py | 9 + tests/test_simulation_metadata.py | 36 +++- 3 files changed, 220 insertions(+), 97 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index cfdbe6b8bb..99edcf37ac 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -8,7 +8,6 @@ import mrcfile import numpy as np -import pandas as pd from aspire.image import Image, normalize_bg from aspire.image.xform import ( @@ -116,7 +115,7 @@ class ImageSource(ABC): `ImageSource`. The number of rows in this metadata table will equal the total number of images supported by this `ImageSource` (available as the 'n' attribute), though reading/writing of images is usually done in chunks. - This metadata table is implemented as a pandas `DataFrame`. + This metadata table is implemented as a dictionary of numpy arrays. The 'values' in this metadata table are usually primitive types (floats/ints/strings) that are suitable for being read from STAR files, and being written to STAR files. The columns corresponding to these fields @@ -155,14 +154,14 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): # _rotations is assigned non None value # by `rotations` or `angles` setters. - # It is potentially used by sublasses to test if we've used setters. - # This must come before the Relion/starfile meta data parsing below. + # It is potentially used by subclasses to test if we've used setters. + # This must come before the Relion/starfile metadata parsing below. self._rotations = None if metadata is None: - self._metadata = pd.DataFrame([], index=pd.RangeIndex(self.n)) + self._metadata = {} else: - self._metadata = metadata + self._metadata = copy.copy(metadata) if self.has_metadata(["_rlnAngleRot", "_rlnAngleTilt", "_rlnAnglePsi"]): self._rotations = Rotation.from_euler( np.deg2rad( @@ -174,7 +173,6 @@ def __init__(self, L, n, dtype="double", metadata=None, memory=None): self.unique_filters = [] self.generation_pipeline = Pipeline(xforms=None, memory=memory) - self._metadata_out = None logger.info(f"Creating {self.__class__.__name__} with {len(self)} images.") @@ -228,6 +226,88 @@ def __getitem__(self, indices): return IndexedSource(self, indices) + def __len__(self): + """ + Returns total number of images in source. + """ + return self.n + + def _metadata_as_dict(self, metadata_fields, indices, default_value=None): + """ + Return a dictionary of selected metadata fields at selected indices. + :param metadata_fields: An iterable of strings specifying metadata fields. + :param indices: An ndarray of 0-indexed locations we're interested in. + :param default_value: A scalar default value to use if a metadata_field is not found. + :return: A dictionary of numpy arrays of specified metadata fields at specified indices. + """ + result = {} + for metadata_field in metadata_fields: + if metadata_field in self._metadata: + result[metadata_field] = self._metadata[metadata_field][indices].copy() + else: + assert ( + default_value is not None + ), f"Missing metadata field {metadata_field} and no default_value supplied" + result[metadata_field] = np.full(len(indices), fill_value=default_value) + return result + + def _metadata_as_ndarray(self, metadata_fields, indices, default_value=None): + """ + Return a numpy array of selected metadata fields at selected indices. + :param metadata_fields: An iterable of strings specifying metadata fields. + :param indices: An ndarray of 0-indexed locations we're interested in. + :param default_value: A scalar default value to use if a metadata_field is not found. + :return: A numpy array of specified metadata fields at specified indices. + """ + # Start with the most generic type - we'll narrow it later + result = np.empty((len(indices), len(metadata_fields))).astype("object") + # Keep track of dtypes of individual metadata fields, so we can narrow the result into a single dtype + dtypes = [None] * len(metadata_fields) + + for i, metadata_field in enumerate(metadata_fields): + if metadata_field not in self._metadata: + assert ( + default_value is not None + ), f"Missing metadata field {metadata_field} and no default_value supplied" + result[:, i] = default_value + dtypes[i] = np.array([default_value]).dtype + else: + values = self._metadata[metadata_field][indices] + result[:, i] = values + dtypes[i] = values.dtype + + dtype = np.result_type(*dtypes) + result = result.astype(dtype) + + if result.shape[1] == 1: + result = result.squeeze(axis=1) + + return result + + def update(self, **kwargs): + """ + Update certain properties that modify the underlying metadata, and return a new ImageSource + object with the new properties. The original object is unchanged. + """ + updateable_props = ( + "states", + "filter_indices", + "offsets", + "amplitudes", + "angles", + "rotations", + ) + + cp = copy.deepcopy(self) + for prop in updateable_props: + if prop in kwargs: + setattr(cp, prop, kwargs.pop(prop)) + + if kwargs: + logger.warning(f"Unhandled arguments = {kwargs.keys()}") + + return cp + @property def n(self): return self._n @@ -268,12 +348,6 @@ def n_ctf_filters(self): """ return len([f for f in self.unique_filters if isinstance(f, CTFFilter)]) - def __len__(self): - """ - Returns total number of images in source. - """ - return self.n - @property def states(self): return np.atleast_1d(self.get_metadata("_rlnClassNumber")) @@ -316,7 +390,7 @@ def angles(self): :return: Rotation angles in radians, as a n x 3 array """ - # Call a private method. This allows sub classes to effeciently override. + # Call a private method. This allows subclasses to efficiently override. return self._angles() def _angles(self): @@ -385,36 +459,63 @@ def set_metadata(self, metadata_fields, values, indices=None): values should either be a scalar or a vector of length equal to the total number of images, |self.n|. :return: On return, the metadata associated with the specified indices has been modified. """ - # Convert a single metadata field into a list of single metadata field, since that's what the 'columns' - # argument of a DataFrame constructor expects. if isinstance(metadata_fields, str): metadata_fields = [metadata_fields] if indices is None: - indices = self._metadata.index.values + indices = np.arange(self.n) - df = pd.DataFrame(values, columns=metadata_fields, index=indices) - for metadata_field in metadata_fields: - series = df[metadata_field] - if metadata_field not in self._metadata.columns: - self._metadata = self._metadata.merge( - series, how="left", left_index=True, right_index=True + try: + iter(values) + except TypeError: + values = [values] * len(indices) + else: + if isinstance(values, str): + values = [values] * len(indices) + assert len(values) == len( + indices + ), "Mismatch between len(values) and len(indices)" + + values = np.array(values) # make a copy for our use + + # When creating metadata fields that are string, coerce them into python objects to allow string expansion + # later (replacing 'hello' with 'goodbye', for example). + # This is in part to conform to legacy implementation of metadata that used pandas, + # but is probably a good thing to do anyway. + # We also come up with a sensible fill value while we're at it. + if np.issubdtype(values.dtype, np.str_): + values = values.astype("object") + fill_value = "" + else: + fill_value = np.nan + + if values.ndim == 1: + values = values.reshape(-1, 1) # convert to column + values = np.tile( + values, (1, len(metadata_fields)) + ) # stack columns for each metadata field + + for i, metadata_field in enumerate(metadata_fields): + if metadata_field not in self._metadata: + self._metadata[metadata_field] = np.full(self.n, fill_value).astype( + values.dtype ) - else: - self._metadata.update(series.astype(object)) + self._metadata[metadata_field][indices] = values[:, i] def has_metadata(self, metadata_fields): """ - Find out if one more more metadata fields are available for this `ImageSource`. + Find out if one or more metadata fields are available for this `ImageSource`. :param metadata_fields: A string, of list of strings, representing the metadata field(s) to be queried. :return: Boolean value indicating whether the field(s) are available. """ if isinstance(metadata_fields, str): metadata_fields = [metadata_fields] - return all(f in self._metadata.columns for f in metadata_fields) + return all(f in self._metadata for f in metadata_fields) - def get_metadata(self, metadata_fields=None, indices=None, default_value=None): + def get_metadata( + self, metadata_fields=None, indices=None, default_value=None, as_dict=False + ): """ Get metadata field information of this ImageSource for a selection of fields of indices. The default should return the @@ -430,44 +531,39 @@ def get_metadata(self, metadata_fields=None, indices=None, default_value=None): :param default_value: Default scalar value to use for any fields not found in the metadata. If None, no default value is used, and missing field(s) cause a RuntimeError. + :param as_dict: Boolean indicating whether we want to return + metadata as a dictionary (True), or as a numpy ndarray (False). + In the latter case, all returned values are typecast to a common + numpy dtype, so use with caution. :return: An ndarray of values (any valid np types) - representing metadata info. + representing metadata info. If is_dict is True, then returns + a dictionary mapping metadata names to numpy arrays of values. """ - # When metadata_fields=None, default to returning all. if metadata_fields is None: - metadata_fields = self._metadata.columns - + metadata_fields = list(self._metadata.keys()) if isinstance(metadata_fields, str): metadata_fields = [metadata_fields] + if indices is None: - indices = self._metadata.index.values - - # The pandas .loc indexer does work with missing columns (as long as not ALL of them are missing) - # which messes with our logic. This behavior will change in pandas 0.21.0. - # See https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#indexing-with-list-with-missing-labels-is-deprecated - # We deal with the situation in a slightly verbose manner as follows. - missing_columns = [ - col for col in metadata_fields if col not in self._metadata.columns - ] - if len(missing_columns) == 0: - result = self._metadata.loc[indices, metadata_fields] + indices = np.arange(self.n) else: - if default_value is not None: - right = pd.DataFrame( - default_value, columns=missing_columns, index=indices - ) - found_columns = [ - col for col in metadata_fields if col not in missing_columns - ] - if len(found_columns) > 0: - left = self._metadata.loc[indices, found_columns] - result = left.join(right) - else: - result = right - else: - raise RuntimeError("Missing columns and no default value provided") - - return result.to_numpy().squeeze() + try: + iter(indices) + except TypeError: + indices = [indices] + + if as_dict: + return self._metadata_as_dict( + metadata_fields=metadata_fields, + indices=indices, + default_value=default_value, + ) + else: + return self._metadata_as_ndarray( + metadata_fields=metadata_fields, + indices=indices, + default_value=default_value, + ) def _apply_filters( self, @@ -756,7 +852,7 @@ def save( def _populate_common_metadata( self, - df, + meta_dict, local_cols, starfile_filepath, batch_size, @@ -765,8 +861,8 @@ def _populate_common_metadata( """ Populate metadata columns common to all `ImageSource` subclasses. """ - # Create a new column that we will be populating in the loop below - df["_rlnImageName"] = "" + # Create a new key that we will be populating in the loop below + meta_dict["_rlnImageName"] = np.full(self.n, fill_value="").astype("object") if save_mode == "single": # Save all images into one single mrc file @@ -774,11 +870,8 @@ def _populate_common_metadata( fstem = os.path.splitext(fname)[0] mrcs_filename = f"{fstem}_{0}_{self.n-1}.mrcs" - # Then set name in dataframe for the StarFile - # Note, here the row_indexer is :, representing all rows in this data frame. - # df.loc will be reponsible for dereferencing and assigning values to df. - # Pandas will assert df.shape[0] == self.n - df.loc[:, "_rlnImageName"] = [ + # Then set name in dict for the StarFile + meta_dict["_rlnImageName"][:] = [ f"{j + 1:06}@{mrcs_filename}" for j in range(self.n) ] else: @@ -790,19 +883,16 @@ def _populate_common_metadata( os.path.splitext(os.path.basename(starfile_filepath))[0] + f"_{i_start}_{i_end-1}.mrcs" ) - # Note, here the row_indexer is a slice. - # df.loc will be reponsible for dereferencing and assigning values to df. - # Pandas will assert the lnegth of row_indexer equals num. - row_indexer = df[i_start:i_end].index - df.loc[row_indexer, "_rlnImageName"] = [ + meta_dict["_rlnImageName"][i_start:i_end] = [ "{0:06}@{1}".format(j + 1, mrcs_filename) for j in range(num) ] - # Subclass-specific columns are popped to the end of the dataframe in order: - # pop() both removes the given column and returns its data as a Series, - # which is then tacked back on to the rightmost side of the df + # Subclass-specific columns are popped to the end of the dictionary in order: + # pop() both removes the given column and returns its data as a ndarray, + # which is then tacked back on to the rightmost side of metadata + # Note that all dictionaries in py>=3.7 are ordered for col in local_cols: - df[col] = df.pop(col) + meta_dict[col] = meta_dict.pop(col) def _populate_local_metadata(self): """ @@ -829,30 +919,30 @@ def save_metadata(self, starfile_filepath, batch_size=512, save_mode=None): # Get local metadata columns that were added by subclass local_cols = self._populate_local_metadata() - df = self._metadata.copy() + metadata = self.get_metadata(as_dict=True, default_value=42).copy() # Drop any column that doesn't start with a *single* underscore - df = df.drop( - [ - str(col) - for col in df.columns - if not col.startswith("_") or col.startswith("__") - ], - axis=1, - ) + metadata = { + k: v + for k, v in metadata.items() + if k.startswith("_") and not k.startswith("__") + } # Populates _rlnImageName column, setting up filepaths to .mrcs stacks self._populate_common_metadata( - df, local_cols, starfile_filepath, batch_size, save_mode + metadata, local_cols, starfile_filepath, batch_size, save_mode ) - filename_indices = df._rlnImageName.str.split(pat="@", expand=True)[1].tolist() + filename_indices = [ + x[1] + for x in np.char.split(metadata["_rlnImageName"].astype(np.str_), sep="@") + ] # initialize the star file object and save it odict = OrderedDict() # since our StarFile only has one block, the convention is to save it with the header "data_", i.e. its name is blank # if we had a block called "XYZ" it would be saved as "XYZ" # thus we index the metadata block with "" - odict[""] = df + odict[""] = metadata out_star = StarFile(blocks=odict) out_star.write(starfile_filepath) return filename_indices @@ -1191,11 +1281,7 @@ def __init__(self, src, indices, memory=None): self.index_map = _ImageAccessor(lambda x: x, src.n)[indices] # Get all the metadata associated with these indices. - # Note, I would have prefered to use our API (get_metadata) - # here, but it returns a Numpy array, which would need to be - # converted back into Pandas for use below. So here we'll just - # use `loc` to return a dataframe. - metadata = self.src._metadata.loc[self.index_map].copy() + metadata = self.src.get_metadata(indices=self.index_map, as_dict=True).copy() # Construct a fully formed ImageSource with this metadata super().__init__( diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 1ee97749b3..9cf38ad9cd 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -2,6 +2,7 @@ import os from collections import OrderedDict +import numpy as np import pandas as pd from gemmi import cif @@ -138,6 +139,14 @@ def write(self, filepath): # if this block (loop or pair) is empty, continue if len(block) == 0: continue + + # look at values to detect if we're dealing with ndarrays + # in this case, the block can be typecast as a DataFrame + if isinstance(block, dict) and all( + isinstance(v, np.ndarray) for v in block.values() + ): + block = pd.DataFrame(block) + # are we constructing a loop (DataFrame) or a pair (Dictionary)? if isinstance(block, dict): for key, value in block.items(): diff --git a/tests/test_simulation_metadata.py b/tests/test_simulation_metadata.py index 0d208297e2..f1e083156b 100644 --- a/tests/test_simulation_metadata.py +++ b/tests/test_simulation_metadata.py @@ -75,9 +75,16 @@ def testMetadata5(self): ) # Get value of metadata fields for indices 0, 1, 2, 3 values = self.sim.get_metadata(["rand_value1", "rand_value2"], [0, 1, 2, 3]) + # values that we didn't specify in get_metadata get initialized as 'missing' + # according to the detected dtype of input, in this case, np.iinfo(np.int64).min self.assertTrue( np.allclose( - np.column_stack([[11, 12, np.nan, 13], [21, 22, np.nan, 23]]), + np.column_stack( + [ + [11, 12, np.iinfo(np.int64).min, 13], + [21, 22, np.iinfo(np.int64).min, 23], + ] + ), values, equal_nan=True, ) @@ -92,10 +99,12 @@ def test_get_metadata_all(self): metadata_api = self.sim.get_metadata() # Access the metadata directly in the frame. - metadata_df = self.sim._metadata.to_numpy() + metadata_array = np.vstack( + [self.sim._metadata[k] for k in self.sim._metadata.keys()] + ).T # Assert we've returned the entire table. - self.assertTrue(np.all(metadata_api == metadata_df)) + self.assertTrue(np.all(metadata_api == metadata_array)) def test_get_metadata_index_slice(self): """ @@ -108,7 +117,26 @@ def test_get_metadata_index_slice(self): metadata_api = self.sim.get_metadata(indices=rows) # Access the metadata directly in the frame. - metadata_df = self.sim._metadata.loc[rows].to_numpy() + metadata_df = np.vstack( + [self.sim._metadata[k] for k in self.sim._metadata.keys()] + ).T[rows] # Assert we've returned the rows self.assertTrue(np.all(metadata_api == metadata_df)) + + def test_update_properties(self): + """ + Test to see if updating certain key properties that modify metadata give us a new sim, + leaving the original untouched + """ + metadata_before = self.sim.get_metadata().copy() + + sim = self.sim.update(rotations=np.zeros_like(self.sim.rotations)) + metadata_after = self.sim.get_metadata().copy() + assert np.all(metadata_before == metadata_after) + assert np.allclose(sim.rotations, np.zeros_like(self.sim.rotations)) + + sim = self.sim.update(amplitudes=np.ones_like(self.sim.amplitudes)) + metadata_after = self.sim.get_metadata().copy() + assert np.all(metadata_before == metadata_after) + assert np.allclose(sim.amplitudes, np.ones_like(self.sim.amplitudes)) From da17f97a0284c142db1fe398fbe57ab281d5a38c Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Fri, 7 Apr 2023 15:06:57 -0400 Subject: [PATCH 380/424] Added docstring to as_copy --- src/aspire/source/image.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 99edcf37ac..820860cdab 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -100,6 +100,17 @@ def __getitem__(self, indices): def as_copy(func): + """ + Method decorator that invokes the decorated method on a deepcopy of the object, + and returns it on exit. The original object is unmodified. + This allows one to take a mutating method on an object: + obj.increment(by=2) + and use it in a functional way: + another = obj.increment(by=2) # obj unmodified + Note that the original return value of the method is lost, so this decorator + is best used on methods that mutate the object but don't return anything. + """ + def wrapper(self, *args, **kwargs): obj_copy = copy.deepcopy(self) func_copy = copy.deepcopy(func) From a12c7ca7226053edba4d79c9214205832d8db916 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sat, 8 Apr 2023 10:02:02 -0400 Subject: [PATCH 381/424] saving --- src/aspire/source/relion.py | 41 ++++++++++++------------- src/aspire/storage/starfile.py | 49 +++++++++++------------------- src/aspire/utils/relion_interop.py | 18 +++++------ tests/test_coordinate_source.py | 15 +++------ tests/test_load_images.py | 3 +- 5 files changed, 51 insertions(+), 75 deletions(-) diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 200966b7b2..168b250464 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -61,12 +61,12 @@ def __init__( metadata = self.populate_metadata() - n = len(metadata) + n = len(metadata["__mrc_filepath"]) if n == 0: raise RuntimeError("No mrcs files found for starfile!") # Peek into the first image and populate some attributes - first_mrc_filepath = metadata.loc[0]["__mrc_filepath"] + first_mrc_filepath = metadata["__mrc_filepath"][0] mrc = mrcfile.open(first_mrc_filepath) # Get the 'mode' (data type) - TODO: There's probably a more direct way to do this. @@ -106,10 +106,10 @@ def __init__( "_rlnAmplitudeContrast", ] # If these all exist in the STAR file, we may create CTF filters for the source - if set(CTF_params).issubset(metadata.columns): + if set(CTF_params).issubset(metadata.keys()): # partition particles according to unique CTF parameters filter_params, filter_indices = np.unique( - metadata[CTF_params].values, return_inverse=True, axis=0 + np.array([metadata[k] for k in CTF_params]).T, return_inverse=True, axis=0 ) filters = [] # for each unique CTF configuration, create a CTFFilter object @@ -132,7 +132,7 @@ def __init__( self.filter_indices = filter_indices # We have provided some, but not all the required params - elif any(param in metadata.columns for param in CTF_params): + elif any(param in metadata for param in CTF_params): logger.warning( f"Found partially populated CTF Params." f" To automatically populate CTFFilters provide {CTF_params}" @@ -164,25 +164,22 @@ def populate_metadata(self): # particle locations are stored as e.g. '000001@first_micrograph.mrcs' # in the _rlnImageName column. here, we're splitting this information # so we can get the particle's index in the .mrcs stack as an int - metadata[["__mrc_index", "__mrc_filename"]] = metadata[ - "_rlnImageName" - ].str.split("@", n=1, expand=True) + indices_filenames = [s.split('@') for s in metadata['_rlnImageName']] # __mrc_index corresponds to the integer index of the particle in the __mrc_filename stack # Note that this is 1-based indexing - metadata["__mrc_index"] = pd.to_numeric(metadata["__mrc_index"]) + metadata["__mrc_index"] = np.array([int(s[0]) for s in indices_filenames]) + metadata["__mrc_filename"] = np.array([s[1] for s in indices_filenames]) # Adding a full-filepath field to the Dataframe helps us save time later # Note that os.path.join works as expected when the second argument is an absolute path itself - metadata["__mrc_filepath"] = metadata["__mrc_filename"].apply( - lambda filename: os.path.join(self.data_folder, filename) - ) + metadata["__mrc_filepath"] = np.array([os.path.join(self.data_folder, p) for p in metadata["__mrc_filename"]]) # finally, chop off the metadata df at max_rows if self.max_rows is None: return metadata else: - max_rows = min(self.max_rows, len(metadata)) - return metadata.iloc[:max_rows] + max_rows = min(self.max_rows, len(metadata["__mrc_filepath"])) + return {k: v[:max_rows] for k, v in metadata.items()} def __str__(self): return f"RelionSource ({self.n} images of size {self.L}x{self.L})" @@ -206,34 +203,34 @@ def _images(self, indices): # Log the indices in case needed to debug a crash logger.debug(f"Indices: {indices}") - def load_single_mrcs(filepath, df): + def load_single_mrcs(filepath, indices): arr = mrcfile.open(filepath).data # if the stack only contains one image, arr will have shape (resolution, resolution) # the code below reshapes it to (1, resolution, resolution) if len(arr.shape) == 2: arr = arr.reshape((1,) + arr.shape) # __mrc_index is the 1-based index of the particle in the stack - data = arr[df["__mrc_index"] - 1, :, :] + data = arr[self._metadata["__mrc_index"][indices] - 1, :, :] - return df.index, data + return indices, data n_workers = self.n_workers if n_workers < 0: n_workers = cpu_count() - 1 - df = self._metadata.loc[indices] im = np.empty( (len(indices), self._original_resolution, self._original_resolution), dtype=self.dtype, ) - groups = df.groupby("__mrc_filepath") - n_workers = min(n_workers, len(groups)) + filepaths, filepath_indices = np.unique(self._metadata["__mrc_filepath"], return_inverse=True) + n_workers = min(n_workers, len(filepaths)) with futures.ThreadPoolExecutor(n_workers) as executor: to_do = [] - for filepath, _df in groups: - future = executor.submit(load_single_mrcs, filepath, _df) + for i, filepath in enumerate(filepaths): + this_filepath_indices = np.where(filepath_indices == i) + future = executor.submit(load_single_mrcs, filepath, this_filepath_indices) to_do.append(future) for future in futures.as_completed(to_do): diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 9cf38ad9cd..ed82617175 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -3,7 +3,6 @@ from collections import OrderedDict import numpy as np -import pandas as pd from gemmi import cif logger = logging.getLogger(__name__) @@ -16,19 +15,10 @@ class StarFileError(Exception): class StarFile: def __init__(self, filepath="", blocks=None): """ - Initialize either from a path to a STAR file or from an OrderedDict of dataframes + Initialize either from a path to a STAR file or from an OrderedDict of ndarrays """ # if blocks are given, set self.blocks, otherwise initialize an empty OrderedDict() self.blocks = blocks or OrderedDict() - # if constructing from blocks, must switch pandas dtype to str - # otherwise comparison with StarFiles read from files will fail - # due to different data types - self.blocks = OrderedDict( - { - key: (block.astype(str) if isinstance(block, pd.DataFrame) else block) - for (key, block) in self.blocks.items() - } - ) self.filepath = str(filepath) # raise an error if blocks and a filepath are both passed if bool(self.filepath) and bool(len(self.blocks)): @@ -115,11 +105,11 @@ def _initialize_blocks(self): ) elif block_has_loop: if gemmi_block.name not in self.blocks: - # initialize DF from list of lists - # read in with dtype=str because we do not want type conversion - self.blocks[gemmi_block.name] = pd.DataFrame( - loop_data, columns=loop_tags, dtype=str - ) + # initialize dict from list of lists + d = {} + for i, loop_tag in enumerate(loop_tags): + d[loop_tag] = [str(loop_data[j][i]) for j in range(len(loop_data))] + self.blocks[gemmi_block.name] = d else: # enforce unique block names (keys of StarFile.block OrderedDict) raise StarFileError( @@ -140,28 +130,25 @@ def write(self, filepath): if len(block) == 0: continue - # look at values to detect if we're dealing with ndarrays - # in this case, the block can be typecast as a DataFrame - if isinstance(block, dict) and all( - isinstance(v, np.ndarray) for v in block.values() - ): - block = pd.DataFrame(block) + # look at values to detect if we're dealing with iterables + multiple_rows = isinstance(block, dict) and all( + not isinstance(v, str) and hasattr(v, '__iter__') for v in block.values() + ) - # are we constructing a loop (DataFrame) or a pair (Dictionary)? - if isinstance(block, dict): + if not multiple_rows: for key, value in block.items(): # simply assign one pair item for each dict entry # write out as str because we do not want type conversion _block.set_pair(key, str(value)) - elif isinstance(block, pd.DataFrame): + else: + assert len(set(len(v) for v in block.values())) == 1, "Not all iterables of the block have the same length" + _n_rows = len(list(block.values())[0]) # initialize loop with column names - _loop = _block.init_loop("", list(block.columns)) - for row in block.values.tolist(): - # write out as str because we do not want type conversion - row = [str(row[x]) for x in range(len(row))] + _keys = list(block.keys()) + _loop = _block.init_loop("", _keys) + for i in range(_n_rows): + row = [str(block[k][i]) for k in _keys] _loop.add_row(row) - else: - raise StarFileError(f"Unsupported type for block {name}: {type(block)}") _doc.write_file(filepath) diff --git a/src/aspire/utils/relion_interop.py b/src/aspire/utils/relion_interop.py index 5230a7e822..72514b144a 100644 --- a/src/aspire/utils/relion_interop.py +++ b/src/aspire/utils/relion_interop.py @@ -49,17 +49,17 @@ } -def df_to_relion_types(df): +def df_to_relion_types(d): """ Convert STAR file strings to data type for each field in a DataFrame loaded via `aspire.storage.StarFile`. Columns without a specified data type are read as `dtype=object`. - :param df: A `StarFile` block represented as a Pandas DataFrame. + :param df: A `StarFile` block represented as a dictionary. :return: The data frame with types converted where possible. """ - column_types = {name: relion_metadata_fields.get(name, str) for name in df.columns} - return df.astype(column_types) + column_types = {name: relion_metadata_fields.get(name, str) for name in d.keys()} + return dict(zip(d.keys(), [np.array(list(map(column_types[k], d[k]))) for k in d.keys()])) class RelionStarFile(StarFile): @@ -89,7 +89,7 @@ def _validate_and_detect_version(self): # validate 3.0 STAR file if len(self.blocks) == 1: data_block = self.get_block_by_index(0) - columns = data_block.columns.to_list() + columns = list(data_block.keys()) if not any( col in columns for col in ["_rlnImageName", "_rlnMicrographName", "_rlnMovieName"] @@ -120,7 +120,7 @@ def _validate_and_detect_version(self): data_block = self[self.data_block_name] # lastly, data block must contain a column identifying the type of data as well - columns = data_block.columns.to_list() + columns = list(data_block.keys()) if not any( col in columns for col in ["_rlnImageName", "_rlnMicrographName", "_rlnMovieName"] @@ -145,9 +145,7 @@ def _convert_dtypes(self): """ _blocks = OrderedDict() for block_name, block in self.blocks.items(): - # generic StarFile blocks can be DataFrame or dict - if not isinstance(block, dict): - _blocks[block_name] = df_to_relion_types(block) + _blocks[block_name] = df_to_relion_types(block) self.blocks = _blocks def get_merged_data_block(self): @@ -166,7 +164,7 @@ def get_merged_data_block(self): # based on the corresponding optics group number (returns a new dataframe) data_block = self.data_block.copy() # get a NumPy array of optics indices for each row of data - optics_indices = self.data_block["_rlnOpticsGroup"].astype(int).to_numpy() + optics_indices = self.data_block["_rlnOpticsGroup"].astype(int) for optics_index, row in self.optics_block.iterrows(): # find row indices with this optics index # Note optics group number is 1-indexed in Relion diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index d9bb254e16..28439e4943 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -9,7 +9,6 @@ import mrcfile import numpy as np from click.testing import CliRunner -from pandas import DataFrame import tests.saved_test_data from aspire.commands.extract_particles import extract_particles @@ -190,9 +189,8 @@ def createTestStarFiles(self, centers, index): y_coords = [center[1] for center in centers] blocks = OrderedDict( { - "coordinates": DataFrame( + "coordinates": {"_rlnCoordinateX": x_coords, "_rlnCoordinateY": y_coords} - ) } ) starfile = StarFile(blocks=blocks) @@ -245,9 +243,8 @@ def createFloatStarFile(self, centers): y_coords = [str(center[1]) + ".000" for center in centers] blocks = OrderedDict( { - "coordinates": DataFrame( + "coordinates": {"_rlnCoordinateX": x_coords, "_rlnCoordinateY": y_coords} - ) } ) starfile = StarFile(blocks=blocks) @@ -270,7 +267,7 @@ def createTestCtfFiles(self, index): "_rlnMicrographPixelSize": 400 + index, } blocks = OrderedDict( - {"root": DataFrame([params_dict], columns=params_dict.keys())} + {"root": params_dict} ) starfile = StarFile(blocks=blocks) starfile.write(star_fp) @@ -295,7 +292,7 @@ def createTestRelionCtfFile(self): ["opticsGroup1", 1, 500.0, 700.0, 600.0, 400.0], ["opticsGroup2", 2, 501.0, 701.0, 601.0, 401.0], ] - blocks["optics"] = DataFrame(optics_block, columns=optics_columns) + blocks["optics"] = dict(zip(optics_columns, zip(*optics_block))) micrographs_columns = [ "_rlnMicrographName", @@ -309,9 +306,7 @@ def createTestRelionCtfFile(self): [self.all_mrc_paths[0], 1, 1000.0, 900.0, 800.0], [self.all_mrc_paths[0], 2, 1001.0, 901.0, 801.0], ] - blocks["micrographs"] = DataFrame( - micrographs_block, columns=micrographs_columns - ) + blocks["micrographs"] = dict(zip(micrographs_columns, zip(*micrographs_block))) star = StarFile(blocks=blocks) star.write(star_fp) diff --git a/tests/test_load_images.py b/tests/test_load_images.py index 1aabed14e5..c87c1a9588 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -5,7 +5,6 @@ import mrcfile import numpy as np -from pandas import DataFrame from aspire.image import Image from aspire.source import RelionSource @@ -48,7 +47,7 @@ def setUp(self): starfile_loop.append(f"{j+1:06d}@{mrcs_fn}") # save starfile - starfile_data[""] = DataFrame(starfile_loop, columns=starfile_keys) + starfile_data[""] = {k:np.array(starfile_loop) for k in starfile_keys} StarFile(blocks=starfile_data).write(self.starfile_path) # create source From 27a4de9947d961e87ca8dacd9f6556a10ad367e9 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sat, 8 Apr 2023 11:17:36 -0400 Subject: [PATCH 382/424] test expected to fail --- tests/test_coordinate_source.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index a2c1edb83a..2b45de9218 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -8,6 +8,7 @@ import mrcfile import numpy as np +import pytest from click.testing import CliRunner from pandas import DataFrame @@ -275,7 +276,7 @@ def createTestCtfFiles(self, index): starfile = StarFile(blocks=blocks) starfile.write(star_fp) - def createTestRelionCtfFile(self): + def createTestRelionCtfFile(self, reverse_optics_block_rows=False): """ Creates example RELION-generated CTF file for a set of micrographs. """ @@ -295,6 +296,11 @@ def createTestRelionCtfFile(self): ["opticsGroup1", 1, 500.0, 700.0, 600.0, 400.0], ["opticsGroup2", 2, 501.0, 701.0, 601.0, 401.0], ] + # Since optics block rows are self-contained, + # reversing their order should have no affect anywhere. + if reverse_optics_block_rows: + optics_block = optics_block[::-1] + blocks["optics"] = DataFrame(optics_block, columns=optics_columns) micrographs_columns = [ @@ -539,6 +545,16 @@ def testImportCtfFromRelion(self): self._testCtfFilters(src) self._testCtfMetadata(src) + @pytest.mark.xfail(strict=True) + def testImportCtfFromRelionReverseOpticsGroup(self): + self.relion_ctf_file = self.createTestRelionCtfFile( + reverse_optics_block_rows=True + ) + src = BoxesCoordinateSource(self.files_box) + src.import_relion_ctf(self.relion_ctf_file) + self._testCtfFilters(src) + self._testCtfMetadata(src) + def testImportCtfFromRelionLegacy(self): src = BoxesCoordinateSource(self.files_box) src.import_relion_ctf(self.relion_legacy_ctf_file) From 3b264c16672fc0bf86dddf3630a51eb91f8ca9de Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sat, 8 Apr 2023 12:04:09 -0400 Subject: [PATCH 383/424] xfail fix --- src/aspire/utils/relion_interop.py | 6 +++--- tests/test_coordinate_source.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aspire/utils/relion_interop.py b/src/aspire/utils/relion_interop.py index 5230a7e822..b9121d938a 100644 --- a/src/aspire/utils/relion_interop.py +++ b/src/aspire/utils/relion_interop.py @@ -167,10 +167,10 @@ def get_merged_data_block(self): data_block = self.data_block.copy() # get a NumPy array of optics indices for each row of data optics_indices = self.data_block["_rlnOpticsGroup"].astype(int).to_numpy() - for optics_index, row in self.optics_block.iterrows(): + for _, row in self.optics_block.iterrows(): + optics_index = row["_rlnOpticsGroup"] # find row indices with this optics index - # Note optics group number is 1-indexed in Relion - match = np.nonzero(optics_indices == optics_index + 1)[ + match = np.nonzero(optics_indices == optics_index)[ 0 ] # returns 1-tuple for param in self.optics_block.columns: diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 2b45de9218..1095c3b22c 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -545,7 +545,6 @@ def testImportCtfFromRelion(self): self._testCtfFilters(src) self._testCtfMetadata(src) - @pytest.mark.xfail(strict=True) def testImportCtfFromRelionReverseOpticsGroup(self): self.relion_ctf_file = self.createTestRelionCtfFile( reverse_optics_block_rows=True From 103a5da8acd5b18a593036c449477969c0b4bd04 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sat, 8 Apr 2023 12:19:14 -0400 Subject: [PATCH 384/424] unused import removed --- tests/test_coordinate_source.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 1095c3b22c..5e08c362f6 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -8,7 +8,6 @@ import mrcfile import numpy as np -import pytest from click.testing import CliRunner from pandas import DataFrame From ab50511a8fb79ee7ab63f3148807c76a3f242f2c Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sat, 8 Apr 2023 13:19:11 -0400 Subject: [PATCH 385/424] blackened --- src/aspire/utils/relion_interop.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/aspire/utils/relion_interop.py b/src/aspire/utils/relion_interop.py index b9121d938a..558ed7ac8c 100644 --- a/src/aspire/utils/relion_interop.py +++ b/src/aspire/utils/relion_interop.py @@ -170,9 +170,7 @@ def get_merged_data_block(self): for _, row in self.optics_block.iterrows(): optics_index = row["_rlnOpticsGroup"] # find row indices with this optics index - match = np.nonzero(optics_indices == optics_index)[ - 0 - ] # returns 1-tuple + match = np.nonzero(optics_indices == optics_index)[0] # returns 1-tuple for param in self.optics_block.columns: data_block.loc[match, param] = getattr(row, param) From c8855fa1f44eaf1452aff4501ad39fb5d1ce7b5b Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sun, 9 Apr 2023 14:34:52 -0400 Subject: [PATCH 386/424] pd gone --- environment-accelerate.yml | 1 - environment-default.yml | 1 - environment-intel.yml | 1 - environment-openblas.yml | 1 - setup.py | 1 - src/aspire/ctf/ctf_estimator.py | 4 +-- src/aspire/source/coordinates.py | 36 +++++++++++-------- src/aspire/source/relion.py | 25 ++++++++----- src/aspire/storage/starfile.py | 37 +++++++++++-------- src/aspire/utils/relion_interop.py | 50 ++++++++++++++++---------- tests/test_coordinate_source.py | 18 +++------- tests/test_load_images.py | 2 +- tests/test_starfileio.py | 58 +++++++++++++++++------------- 13 files changed, 132 insertions(+), 103 deletions(-) diff --git a/environment-accelerate.yml b/environment-accelerate.yml index f3f3e9c22b..1d01c51d75 100644 --- a/environment-accelerate.yml +++ b/environment-accelerate.yml @@ -12,7 +12,6 @@ dependencies: - pip - python=3.8 - numpy=1.23.5 - - pandas=1.3.5 - scipy=1.9.3 - scikit-learn - scikit-image diff --git a/environment-default.yml b/environment-default.yml index ec4ba68b93..f092f4c3ca 100644 --- a/environment-default.yml +++ b/environment-default.yml @@ -12,7 +12,6 @@ dependencies: - pip - python=3.8 - numpy=1.23.5 - - pandas=1.3.5 - scipy=1.9.3 - scikit-learn - scikit-image diff --git a/environment-intel.yml b/environment-intel.yml index b06af37b36..fa3beba787 100644 --- a/environment-intel.yml +++ b/environment-intel.yml @@ -12,7 +12,6 @@ dependencies: - pip - python=3.8 - numpy=1.23.5 - - pandas=1.3.5 - scipy=1.9.3 - scikit-learn - scikit-image diff --git a/environment-openblas.yml b/environment-openblas.yml index 3eb19c22bf..bc4d61b46d 100644 --- a/environment-openblas.yml +++ b/environment-openblas.yml @@ -12,7 +12,6 @@ dependencies: - pip - python=3.8 - numpy=1.23.5 - - pandas=1.3.5 - scipy=1.9.3 - scikit-learn - scikit-image diff --git a/setup.py b/setup.py index d5e5817644..11898cdcb9 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,6 @@ def read(fname): "mrcfile", "numpy>=1.21.5", "packaging", - "pandas>=1.3.5", "psutil", "pyfftw", "PyWavelets", diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 7c966820fd..d219472d88 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -12,7 +12,6 @@ import mrcfile import numpy as np from numpy import linalg as npla -from pandas import DataFrame from scipy.optimize import linprog from scipy.signal.windows import dpss @@ -693,9 +692,8 @@ def write_star(self, name, params_dict, output_dir): data_block["_rlnAmplitudeContrast"] = params_dict["amplitude_contrast"] data_block["_rlnVoltage"] = params_dict["voltage"] data_block["_rlnMicrographPixelSize"] = params_dict["pixel_size"] - df = DataFrame([data_block]) blocks = OrderedDict() - blocks["root"] = df + blocks["root"] = data_block star = StarFile(blocks=blocks) star.write(os.path.join(output_dir, os.path.splitext(name)[0]) + ".star") diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 49532f5a78..10cd66b250 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -7,7 +7,6 @@ import mrcfile import numpy as np -import pandas as pd from aspire.image import Image from aspire.operators import CTFFilter, IdentityFilter @@ -223,8 +222,9 @@ def _coords_list_from_star(self, star_file): return a list of coordinates in box format. :param star_file: A path to a STAR file containing particle centers """ - df = StarFile(star_file).get_block_by_index(0).astype(float) + df = StarFile(star_file).get_block_by_index(0) coords = list(zip(df["_rlnCoordinateX"], df["_rlnCoordinateY"])) + coords = [(float(x), float(y)) for x, y in coords] return [ self._box_coord_from_center(coord, self.particle_size) for coord in coords ] @@ -315,16 +315,16 @@ def import_aspire_ctf(self, ctf): "Number of CTF STAR files must match number of micrographs." ) - # merge DataFrames from CTF files - dfs = [] + # merge dicts from CTF files + dfs = defaultdict(list) for f in ctf: # ASPIRE's CTF Estimator produces legacy (=< 3.0) STAR files containing one row star = RelionStarFile(f) - dfs.append(star.data_block) + data_block = star.data_block + for k, v in data_block.items(): + dfs[k].append(v) - df = pd.concat(dfs, ignore_index=True) - - self._extract_ctf(df) + self._extract_ctf(dfs) def import_relion_ctf(self, ctf): """ @@ -336,8 +336,8 @@ def import_relion_ctf(self, ctf): """ data_block = RelionStarFile(ctf).get_merged_data_block() - # data_block is a pandas Dataframe containing the micrographs - if not len(data_block) == self.num_micrographs: + # data_block is a dict containing the micrographs + if not len(data_block[list(data_block.keys())[0]]) == self.num_micrographs: raise ValueError( f"{ctf} has CTF information for {len(data_block)}", f" micrographs but this source has {self.num_micrographs} micrographs.", @@ -347,7 +347,7 @@ def import_relion_ctf(self, ctf): def _extract_ctf(self, df): """ - Receives a flattened DataFrame containing micrograph CTF information, and populates + Receives a dict containing micrograph CTF information, and populates the Source's CTF Filters, filter indices, and metadata. """ # required CTF params excluding pixel size @@ -361,10 +361,18 @@ def _extract_ctf(self, df): "_rlnMicrographPixelSize", ] + n = len(df["_rlnVoltage"]) # get unique ctfs from the data block # i'th entry of `indices` contains the index of `filter_params` with corresponding CTF params filter_params, indices = np.unique( - df[CTF_params].astype(self.dtype).values, return_inverse=True, axis=0 + np.vstack( + [ + np.array([df[c][i] for c in CTF_params]).astype(self.dtype) + for i in range(n) + ] + ), + return_inverse=True, + axis=0, ) # convert defocus_ang from degrees to radians @@ -642,14 +650,14 @@ def _validate_starfile(self, coord_file): """ df = StarFile(coord_file).get_block_by_index(0) # We're looking for specific columns for the X and Y coordinates - if not all(col in df.columns for col in ["_rlnCoordinateX", "_rlnCoordinateY"]): + if not all(col in df for col in ["_rlnCoordinateX", "_rlnCoordinateY"]): logger.error(f"Problem with coordinate file: {coord_file}") raise ValueError( "STAR file does not contain _rlnCoordinateX, _rlnCoordinateY columns." ) # check that all values in each column are numeric if not all( - all(df[col].apply(self._is_number)) + all(map(self._is_number, df[col])) for col in ["_rlnCoordinateX", "_rlnCoordinateY"] ): logger.error(f"Problem with coordinate file: {coord_file}") diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 168b250464..ecbe352e2d 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -5,7 +5,6 @@ import mrcfile import numpy as np -import pandas as pd from aspire.image import Image from aspire.operators import CTFFilter, IdentityFilter @@ -20,7 +19,7 @@ class RelionSource(ImageSource): A RelionSource represents a source of picked and cropped particles stored as slices in a `.mrcs` stack. It must be instantiated via a STAR file, which--at a minumum--lists the particles in each `.mrcs` stack in the `_rlnImageName` column. The STAR file may also contain Relion-specific metadata columns. This information - is read into a Pandas DataFrame table containing a row for each particle specifying its location and + is read into dictionaries containing rows for each particle specifying its location and its metadata. The metadata table may be augmented or modified via helper methods found in ImageSource. It may store, for example, Filter objects added during preprocessing. """ @@ -109,7 +108,9 @@ def __init__( if set(CTF_params).issubset(metadata.keys()): # partition particles according to unique CTF parameters filter_params, filter_indices = np.unique( - np.array([metadata[k] for k in CTF_params]).T, return_inverse=True, axis=0 + np.array([metadata[k] for k in CTF_params]).T, + return_inverse=True, + axis=0, ) filters = [] # for each unique CTF configuration, create a CTFFilter object @@ -148,7 +149,7 @@ def __init__( def populate_metadata(self): """ Relion STAR files may contain a large number of metadata columns in addition - to the locations of particles. We read this into a Pandas DataFrame and add some of + to the locations of particles. We read this into a dict and add some of our own columns for convenience. """ if self.data_folder is not None: @@ -164,7 +165,7 @@ def populate_metadata(self): # particle locations are stored as e.g. '000001@first_micrograph.mrcs' # in the _rlnImageName column. here, we're splitting this information # so we can get the particle's index in the .mrcs stack as an int - indices_filenames = [s.split('@') for s in metadata['_rlnImageName']] + indices_filenames = [s.split("@") for s in metadata["_rlnImageName"]] # __mrc_index corresponds to the integer index of the particle in the __mrc_filename stack # Note that this is 1-based indexing metadata["__mrc_index"] = np.array([int(s[0]) for s in indices_filenames]) @@ -172,7 +173,9 @@ def populate_metadata(self): # Adding a full-filepath field to the Dataframe helps us save time later # Note that os.path.join works as expected when the second argument is an absolute path itself - metadata["__mrc_filepath"] = np.array([os.path.join(self.data_folder, p) for p in metadata["__mrc_filename"]]) + metadata["__mrc_filepath"] = np.array( + [os.path.join(self.data_folder, p) for p in metadata["__mrc_filename"]] + ) # finally, chop off the metadata df at max_rows if self.max_rows is None: @@ -223,14 +226,18 @@ def load_single_mrcs(filepath, indices): dtype=self.dtype, ) - filepaths, filepath_indices = np.unique(self._metadata["__mrc_filepath"], return_inverse=True) + filepaths, filepath_indices = np.unique( + self._metadata["__mrc_filepath"], return_inverse=True + ) n_workers = min(n_workers, len(filepaths)) with futures.ThreadPoolExecutor(n_workers) as executor: to_do = [] for i, filepath in enumerate(filepaths): - this_filepath_indices = np.where(filepath_indices == i) - future = executor.submit(load_single_mrcs, filepath, this_filepath_indices) + this_filepath_indices = np.where(filepath_indices == i)[0] + future = executor.submit( + load_single_mrcs, filepath, this_filepath_indices + ) to_do.append(future) for future in futures.as_completed(to_do): diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index ed82617175..e7fa088927 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -2,7 +2,6 @@ import os from collections import OrderedDict -import numpy as np from gemmi import cif logger = logging.getLogger(__name__) @@ -18,12 +17,21 @@ def __init__(self, filepath="", blocks=None): Initialize either from a path to a STAR file or from an OrderedDict of ndarrays """ # if blocks are given, set self.blocks, otherwise initialize an empty OrderedDict() + if blocks is not None: + for k in blocks: + for _k, _v in blocks[k].items(): + if not isinstance(_v, str): + if hasattr(_v, "__iter__"): + blocks[k][_k] = [str(__v) for __v in _v] + else: + blocks[k][_k] = str(_v) + self.blocks = blocks or OrderedDict() self.filepath = str(filepath) # raise an error if blocks and a filepath are both passed if bool(self.filepath) and bool(len(self.blocks)): raise StarFileError( - "Pass either a path to a STAR file or an OrderedDict of Pandas DataFrames, not both" + "Pass either a path to a STAR file or an OrderedDict of dicts, not both" ) if self.filepath: if not os.path.exists(self.filepath): @@ -35,7 +43,7 @@ def __init__(self, filepath="", blocks=None): def _initialize_blocks(self): """ Converts a gemmi Document object representing the .star file - at self.filepath into an OrderedDict of pandas dataframes, each of which represents one block in the .star file + at self.filepath into an OrderedDict of dicts, each of which represents one block in the .star file """ logger.info(f"Parsing star file at: {self.filepath}") gemmi_doc = cif.read_file(self.filepath) @@ -108,7 +116,9 @@ def _initialize_blocks(self): # initialize dict from list of lists d = {} for i, loop_tag in enumerate(loop_tags): - d[loop_tag] = [str(loop_data[j][i]) for j in range(len(loop_data))] + d[loop_tag] = [ + str(loop_data[j][i]) for j in range(len(loop_data)) + ] self.blocks[gemmi_block.name] = d else: # enforce unique block names (keys of StarFile.block OrderedDict) @@ -132,7 +142,8 @@ def write(self, filepath): # look at values to detect if we're dealing with iterables multiple_rows = isinstance(block, dict) and all( - not isinstance(v, str) and hasattr(v, '__iter__') for v in block.values() + not isinstance(v, str) and hasattr(v, "__iter__") + for v in block.values() ) if not multiple_rows: @@ -141,7 +152,9 @@ def write(self, filepath): # write out as str because we do not want type conversion _block.set_pair(key, str(value)) else: - assert len(set(len(v) for v in block.values())) == 1, "Not all iterables of the block have the same length" + assert ( + len(set(len(v) for v in block.values())) == 1 + ), "Not all iterables of the block have the same length" _n_rows = len(list(block.values())[0]) # initialize loop with column names _keys = list(block.keys()) @@ -154,7 +167,7 @@ def write(self, filepath): def get_block_by_index(self, index): """ - Retrieve a DataFrame representing a star file block by its position in the starfile + Retrieve a dict representing a star file block by its position in the starfile """ return self.blocks[list(self.blocks.keys())[index]] @@ -192,12 +205,6 @@ def __eq__(self, other): if not isinstance(self_list[i][1], type(other_list[i][1])): return False # finally, compare the objects themselves - if isinstance(self_list[i][1], pd.DataFrame): - # test using pandas DataFrame.equals() - if not self_list[i][1].equals(other_list[i][1]): - return False - else: - # test dict equality - if not self_list[i][1] == other_list[i][1]: - return False + if not self_list[i][1] == other_list[i][1]: + return False return True diff --git a/src/aspire/utils/relion_interop.py b/src/aspire/utils/relion_interop.py index 1af00917f7..9240e47298 100644 --- a/src/aspire/utils/relion_interop.py +++ b/src/aspire/utils/relion_interop.py @@ -49,17 +49,25 @@ } -def df_to_relion_types(d): +def dict_to_relion_types(d): """ - Convert STAR file strings to data type for each field in a DataFrame loaded via + Convert STAR file strings to data type for each field in a dict loaded via `aspire.storage.StarFile`. Columns without a specified data type are read as - `dtype=object`. + `dtype=str`. - :param df: A `StarFile` block represented as a dictionary. - :return: The data frame with types converted where possible. + :param d: A `StarFile` block represented as a dictionary. + :return: A dict with types converted where possible. """ - column_types = {name: relion_metadata_fields.get(name, str) for name in d.keys()} - return dict(zip(d.keys(), [np.array(list(map(column_types[k], d[k]))) for k in d.keys()])) + column_types = {name: relion_metadata_fields.get(name, str) for name in d} + # look at values to detect if we're dealing with iterables + multiple_rows = isinstance(d, dict) and all( + not isinstance(v, str) and hasattr(v, "__iter__") for v in d.values() + ) + if multiple_rows: + retval = dict(zip(d, [np.array(list(map(column_types[k], d[k]))) for k in d])) + else: + retval = {k: column_types[k](d[k]) for k in d} + return retval class RelionStarFile(StarFile): @@ -140,19 +148,18 @@ def _validate_and_detect_version(self): def _convert_dtypes(self): """ - For data blocks that are Pandas DataFrames, convert known Relion columns - to sensible types. + For data blocks, convert known Relion columns to sensible types. """ _blocks = OrderedDict() for block_name, block in self.blocks.items(): - _blocks[block_name] = df_to_relion_types(block) + _blocks[block_name] = dict_to_relion_types(block) self.blocks = _blocks def get_merged_data_block(self): """ - Return the DataFrame containing particle/micrograph/movie information for this STAR file. + Return the dictionary containing particle/micrograph/movie information for this STAR file. - :return: A Pandas DataFrame containing the data from this RELION STAR file. + :return: A dictionary containing the data from this RELION STAR file. """ if self.relion_version == "3.0": @@ -163,13 +170,20 @@ def get_merged_data_block(self): # merge the parameters in the optics block as new columns in the data block # based on the corresponding optics group number (returns a new dataframe) data_block = self.data_block.copy() + # Create a dict from optics_group to individual optics properties + optics = self.optics_block + optics_props_by_group_id = dict( + (k, {_k: optics[_k][i] for _k in optics}) + for i, k in enumerate(optics["_rlnOpticsGroup"]) + ) # get a NumPy array of optics indices for each row of data optics_indices = self.data_block["_rlnOpticsGroup"].astype(int) - for _, row in self.optics_block.iterrows(): - optics_index = row["_rlnOpticsGroup"] - # find row indices with this optics index - match = np.nonzero(optics_indices == optics_index)[0] # returns 1-tuple - for param in self.optics_block.columns: - data_block.loc[match, param] = getattr(row, param) + for k in optics: + data_block[k] = np.array( + [ + optics_props_by_group_id[optic_index][k] + for optic_index in optics_indices + ] + ) return data_block diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 0833ef8f84..c59fc974af 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -188,10 +188,7 @@ def createTestStarFiles(self, centers, index): x_coords = [center[0] for center in centers] y_coords = [center[1] for center in centers] blocks = OrderedDict( - { - "coordinates": - {"_rlnCoordinateX": x_coords, "_rlnCoordinateY": y_coords} - } + {"coordinates": {"_rlnCoordinateX": x_coords, "_rlnCoordinateY": y_coords}} ) starfile = StarFile(blocks=blocks) starfile.write(star_fp) @@ -242,10 +239,7 @@ def createFloatStarFile(self, centers): x_coords = [str(center[0]) + ".000" for center in centers] y_coords = [str(center[1]) + ".000" for center in centers] blocks = OrderedDict( - { - "coordinates": - {"_rlnCoordinateX": x_coords, "_rlnCoordinateY": y_coords} - } + {"coordinates": {"_rlnCoordinateX": x_coords, "_rlnCoordinateY": y_coords}} ) starfile = StarFile(blocks=blocks) starfile.write(star_fp) @@ -266,9 +260,7 @@ def createTestCtfFiles(self, index): "_rlnVoltage": 500 + index, "_rlnMicrographPixelSize": 400 + index, } - blocks = OrderedDict( - {"root": params_dict} - ) + blocks = OrderedDict({"root": params_dict}) starfile = StarFile(blocks=blocks) starfile.write(star_fp) @@ -489,14 +481,14 @@ def testSave(self): # we want to read the saved mrcs file from the STAR file image_name_column = saved_star.get_block_by_index(0)["_rlnImageName"] # we're reading a string of the form 0000X@mrcs_path.mrcs - _particle, mrcs_path = image_name_column.iloc[0].split("@") + _particle, mrcs_path = image_name_column[0].split("@") saved_mrcs_stack = mrcfile.open(os.path.join(self.data_folder, mrcs_path)).data # assert that the particles saved are correct for i in range(10): self.assertTrue(np.array_equal(imgs.asnumpy()[i], saved_mrcs_stack[i])) # assert that the star file has the correct metadata self.assertEqual( - saved_star[""].columns.tolist(), + list(saved_star[""].keys()), ["_rlnImageName", "_rlnCoordinateX", "_rlnCoordinateY"], ) # assert that all the correct coordinates were saved diff --git a/tests/test_load_images.py b/tests/test_load_images.py index c87c1a9588..d09c52539d 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -47,7 +47,7 @@ def setUp(self): starfile_loop.append(f"{j+1:06d}@{mrcs_fn}") # save starfile - starfile_data[""] = {k:np.array(starfile_loop) for k in starfile_keys} + starfile_data[""] = {k: np.array(starfile_loop) for k in starfile_keys} StarFile(blocks=starfile_data).write(self.starfile_path) # create source diff --git a/tests/test_starfileio.py b/tests/test_starfileio.py index 1b23b195b8..372a790298 100644 --- a/tests/test_starfileio.py +++ b/tests/test_starfileio.py @@ -5,7 +5,6 @@ from unittest import TestCase import numpy as np -from pandas import DataFrame from scipy import misc import tests.saved_test_data @@ -70,15 +69,14 @@ def tearDown(self): def testLength(self): # StarFile is an iterable that gives us blocks - # blocks are pandas DataFrames + # blocks are dicts of iterables # We have 6 blocks in our sample starfile. self.assertEqual(6, len(self.starfile)) def testIteration(self): - # A StarFile can be iterated over, yielding DataFrames for loops - # or dictionaries for pairs + # A StarFile can be iterated over, yielding dictionaries for pairs or loops for _, block in self.starfile: - self.assertTrue(isinstance(block, DataFrame) or isinstance(block, dict)) + self.assertTrue(isinstance(block, dict)) def testBlockByIndex(self): # We can use get_block_by_index to retrieve the blocks in @@ -87,10 +85,10 @@ def testBlockByIndex(self): block0 = self.starfile.get_block_by_index(0) self.assertTrue(isinstance(block0, dict)) self.assertEqual(block0["_rlnReferenceDimensionality"], "3") - # our second block is a loop, represented by a DataFrame + # our second block is a loop, represented by a dict block1 = self.starfile.get_block_by_index(1) - self.assertTrue(isinstance(block1, DataFrame)) - self.assertEqual(block1.at[0, "_rlnClassDistribution"], "1.000000") + self.assertTrue(isinstance(block1, dict)) + self.assertEqual(block1["_rlnClassDistribution"][0], "1.000000") def testBlockByName(self): # Indexing a StarFile with a string gives us a block with that name @@ -101,17 +99,15 @@ def testBlockByName(self): self.assertEqual(len(block0), 22) # the block at index 1 has name 'model_classes' block1 = self.starfile["model_classes"] - # This block is a loop/DF with one row - self.assertEqual(len(block1), 1) + # This block is a loop with one row + self.assertEqual(len(list(block1.values())[0]), 1) def testData(self): df = self.starfile["model_class_1"] - self.assertEqual(76, len(df)) - self.assertEqual(8, len(df.columns)) + self.assertEqual(76, len(list(df.values())[0])) + self.assertEqual(8, len(df)) # Note that no typecasting of values is performed at io.StarFile level - self.assertEqual( - "0.000000", df[df["_rlnSpectralIndex"] == "0"].iloc[0]["_rlnResolution"] - ) + self.assertEqual("0.000000", df["_rlnResolution"][0]) def testFileNotFound(self): with self.assertRaises(FileNotFoundError): @@ -121,8 +117,7 @@ def testReadWriteReadBack(self): # Save the StarFile object to a .star file # Read it back for object equality # Note that __eq__ is supported for the class - # it checks the equality of the underlying OrderedDicts of DataFrames - # using pd.DataFrame.equals() + # it checks the equality of the underlying dict of iterables test_outfile = os.path.join(self.tmpdir, "sample_saved.star") self.starfile.write(test_outfile) starfile2 = StarFile(test_outfile) @@ -135,7 +130,7 @@ def testWriteReadWriteBack(self): test_outfile = os.path.join(self.tmpdir, "sample_saved.star") test_outfile2 = os.path.join(self.tmpdir, "sampled_saved2.star") - # create a new StarFile object directly via an OrderedDict of DataFrames + # create a new StarFile object directly via an OrderedDict # not by reading a file data = OrderedDict() # note that GEMMI requires the names of the fields to start with _ @@ -144,13 +139,20 @@ def testWriteReadWriteBack(self): # initialize a single-row loop. we want this to be distinct from a # set of key-value pairs block1_dict = {"_field1": 31, "_field2": 32, "_field3": 33} - block1 = DataFrame([block1_dict], columns=block1_dict.keys()) block2_keys = ["_field4", "_field5", "_field6"] block2_arr = [[f"{x}{y}" for x in range(3)] for y in range(3)] - # initialize a loop data block with a list of lists - block2 = DataFrame(block2_arr, columns=block2_keys) + # initialize a loop data block with a dict of lists + block2 = dict( + zip( + block2_keys, + [ + [block2_arr[i][j] for i in range(len(block2_arr))] + for j in range(len(block2_arr[0])) + ], + ) + ) data["pair"] = block0 - data["single_row"] = block1 + data["single_row"] = block1_dict data["loops"] = block2 # initialize with blocks kwarg original = StarFile(blocks=data) @@ -172,7 +174,7 @@ def testWriteReadWriteBack(self): def testArgsError(self): with self.assertRaises(StarFileError): _blocks = OrderedDict() - _blocks[""] = DataFrame(["test", "data"]) + _blocks[""] = {"test": [], "data": []} with importlib_path( tests.saved_test_data, "sample_data_model.star" ) as path: @@ -202,6 +204,12 @@ def testRelionStarFile(self): "_rlnDefocusAngle", "_rlnSphericalAberration", ] - self.assertTrue( - data_block_current[ctf_params].equals(data_block_legacy[ctf_params]) + + n = len(data_block_current["_rlnVoltage"]) + _ctf_current = np.vstack( + [np.array([data_block_current[c][i] for c in ctf_params]) for i in range(n)] + ) + _ctf_legacy = np.vstack( + [np.array([data_block_legacy[c][i] for c in ctf_params]) for i in range(n)] ) + self.assertTrue(np.all(_ctf_current == _ctf_legacy)) From d1f516affbe52521116021f3405e159f9fe25cff Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sun, 9 Apr 2023 20:37:49 -0400 Subject: [PATCH 387/424] platform specific np.nan behavior in tests --- tests/test_simulation_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_simulation_metadata.py b/tests/test_simulation_metadata.py index f1e083156b..e7694539b1 100644 --- a/tests/test_simulation_metadata.py +++ b/tests/test_simulation_metadata.py @@ -76,13 +76,13 @@ def testMetadata5(self): # Get value of metadata fields for indices 0, 1, 2, 3 values = self.sim.get_metadata(["rand_value1", "rand_value2"], [0, 1, 2, 3]) # values that we didn't specify in get_metadata get initialized as 'missing' - # according to the detected dtype of input, in this case, np.iinfo(np.int64).min + # according to the detected dtype of input, in this case, np.iinfo(int).min self.assertTrue( np.allclose( np.column_stack( [ - [11, 12, np.iinfo(np.int64).min, 13], - [21, 22, np.iinfo(np.int64).min, 23], + [11, 12, np.iinfo(int).min, 13], + [21, 22, np.iinfo(int).min, 23], ] ), values, From 35067e55ba94f9d255ea6ddf5c11400df313662f Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Sun, 9 Apr 2023 20:39:37 -0400 Subject: [PATCH 388/424] platform specific np.nan behavior in tests --- tests/test_simulation_metadata.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_simulation_metadata.py b/tests/test_simulation_metadata.py index f1e083156b..e7694539b1 100644 --- a/tests/test_simulation_metadata.py +++ b/tests/test_simulation_metadata.py @@ -76,13 +76,13 @@ def testMetadata5(self): # Get value of metadata fields for indices 0, 1, 2, 3 values = self.sim.get_metadata(["rand_value1", "rand_value2"], [0, 1, 2, 3]) # values that we didn't specify in get_metadata get initialized as 'missing' - # according to the detected dtype of input, in this case, np.iinfo(np.int64).min + # according to the detected dtype of input, in this case, np.iinfo(int).min self.assertTrue( np.allclose( np.column_stack( [ - [11, 12, np.iinfo(np.int64).min, 13], - [21, 22, np.iinfo(np.int64).min, 23], + [11, 12, np.iinfo(int).min, 13], + [21, 22, np.iinfo(int).min, 23], ] ), values, From 492c1d51844195fd1b807742e4f606afd718e47f Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 10 Apr 2023 08:43:08 -0400 Subject: [PATCH 389/424] Extend legacy bispec and pca_y implementations with `seed` --- .../classification/legacy_implementations.py | 16 +++++++++------- src/aspire/utils/random.py | 4 +--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/aspire/classification/legacy_implementations.py b/src/aspire/classification/legacy_implementations.py index ae974293d6..e627c19742 100644 --- a/src/aspire/classification/legacy_implementations.py +++ b/src/aspire/classification/legacy_implementations.py @@ -4,10 +4,12 @@ import scipy.sparse as sps from scipy.linalg import qr +from aspire.utils import random + logger = logging.getLogger(__name__) -def pca_y(x, k, num_iters=2): +def pca_y(x, k, num_iters=2, seed=None): """ PCA using QR factorization. @@ -39,11 +41,11 @@ def operator_transpose(mat): ones = np.ones((n, k + 2)) if x.dtype == np.dtype("complex"): h = operator( - (2 * np.random.random((k + 2, n)).T - ones) - + 1j * (2 * np.random.random((k + 2, n)).T - ones) + (2 * random((k + 2, n), seed=seed).T - ones) + + 1j * (2 * random((k + 2, n), seed=seed).T - ones) ) else: - h = operator(2 * np.random.random((k + 2, n)).T - ones) + h = operator(2 * random((k + 2, n), seed=seed).T - ones) f = [h] @@ -123,7 +125,7 @@ def bispec_operator_1(freqs): return o1, o2 -def bispec_2drot_large(coeff, freqs, eigval, alpha, sample_n): +def bispec_2drot_large(coeff, freqs, eigval, alpha, sample_n, seed=None): """ alpha 1/3 sample_n 4000 @@ -145,14 +147,14 @@ def bispec_2drot_large(coeff, freqs, eigval, alpha, sample_n): mask = np.where(p, p, -1) # taking the log in the next step will yield a 0 m = np.exp(o1 * np.log(p, where=(mask > 0))) p_m = m / m.sum() - x = np.random.rand(len(m)) + x = random(size=len(m), seed=seed) m_id = np.where(x < sample_n * p_m)[0] o1 = o1[m_id] o2 = o2[m_id] m = np.exp(o1 * coeff_norm + 1j * o2 * phase) # svd of the reduced bispectrum - u, s, v = pca_y(m, 300) + u, s, v = pca_y(m, 300, seed=seed) coeff_b = np.einsum("i, ij -> ij", s, np.conjugate(v)) coeff_b_r = np.conjugate(u.T).dot(np.conjugate(m)) diff --git a/src/aspire/utils/random.py b/src/aspire/utils/random.py index 4a68f62606..f19d3abd93 100644 --- a/src/aspire/utils/random.py +++ b/src/aspire/utils/random.py @@ -68,9 +68,7 @@ def random(*args, **kwargs): """ Wraps numpy.random.random with ASPIRE Random context manager. """ - seed = None - if "seed" in kwargs: - seed = kwargs.pop("seed") + seed = kwargs.pop("seed", None) with Random(seed): return np.random.random(*args, **kwargs) From dc824f08387fca30314cc9996fa4d9fee105fd53 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Mon, 10 Apr 2023 08:54:32 -0400 Subject: [PATCH 390/424] Add seed to class average smoke tests --- src/aspire/classification/rir_class2d.py | 3 ++- tests/test_class2D.py | 17 +++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/aspire/classification/rir_class2d.py b/src/aspire/classification/rir_class2d.py index 5cd1c9a1b6..46c348d3c5 100644 --- a/src/aspire/classification/rir_class2d.py +++ b/src/aspire/classification/rir_class2d.py @@ -364,7 +364,7 @@ def _legacy_pca(self, M): # ### The following was from legacy code. Be careful wrt order. M = M.T - u, s, v = pca_y(M, self.bispectrum_components) + u, s, v = pca_y(M, self.bispectrum_components, seed=self.seed) # Contruct coefficients coef_b = np.einsum("i, ij -> ij", s, np.conjugate(v)) @@ -500,6 +500,7 @@ def _legacy_bispectrum(self, coef, retry_attempts=3): eigval=complex_eigvals, alpha=self.alpha, sample_n=self.sample_n, + seed=self.seed, ) # If we have produced a feature vector if coef_b.size != 0: diff --git a/tests/test_class2D.py b/tests/test_class2D.py index 7fc9306dbe..8c9bd35243 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -19,6 +19,9 @@ DATA_DIR = os.path.join(os.path.dirname(__file__), "saved_test_data") +# This seed is to stabilize the extremely small unit test (img size 16 etc). +SEED = 42 + class FSPCATestCase(TestCase): def setUp(self): @@ -35,10 +38,7 @@ def setUp(self): # Create a src from the volume self.src = Simulation( - L=self.resolution, - n=321, - vols=v, - dtype=self.dtype, + L=self.resolution, n=321, vols=v, dtype=self.dtype, seed=SEED ) self.src.cache() # Precompute image stack @@ -125,10 +125,7 @@ def setUp(self): # Clean self.clean_src = Simulation( - L=self.resolution, - n=self.n_img, - vols=v, - dtype=self.dtype, + L=self.resolution, n=self.n_img, vols=v, dtype=self.dtype, seed=SEED ) # With Noise @@ -140,6 +137,7 @@ def setUp(self): vols=v, dtype=self.dtype, noise_adder=noise_adder, + seed=SEED, ) # Set up FFB @@ -199,6 +197,7 @@ def testIncorrectComponents(self): large_pca_implementation="legacy", nn_implementation="legacy", bispectrum_implementation="legacy", + seed=SEED, ) def testRIRLegacy(self): @@ -217,6 +216,7 @@ def testRIRLegacy(self): large_pca_implementation="legacy", nn_implementation="legacy", bispectrum_implementation="legacy", + seed=SEED, ) _ = rir.classify() @@ -253,6 +253,7 @@ def testRIRsk(self): large_pca_implementation="sklearn", nn_implementation="sklearn", bispectrum_implementation="devel", + seed=SEED, ) _ = rir.classify() From e6361d17c5f4e62296e5078f2614aecb5f2fc30b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 09:39:18 -0400 Subject: [PATCH 391/424] Reconcile file deletion conflict --- gallery/tutorials/lecture_feature_demo.py | 308 ---------------------- 1 file changed, 308 deletions(-) delete mode 100644 gallery/tutorials/lecture_feature_demo.py diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py deleted file mode 100644 index df66f86c0b..0000000000 --- a/gallery/tutorials/lecture_feature_demo.py +++ /dev/null @@ -1,308 +0,0 @@ -""" -ASPIRE-Python Introduction -========================== - -In this notebook we will introduce some code from ASPIRE-Python -that corresponds to topics from MATH586. -""" - -# %% -# Imports -# ------- -# First we import some of the usual suspects. In addition, we import some classes from -# the ASPIRE package that we will use throughout this tutorial. - -# %% -# Installation -# ^^^^^^^^^^^^ -# -# Attempt to install ASPIRE on your machine. ASPIRE can generally install on -# Linux, Mac, and Windows -# under Anaconda Python, by following the instructions in the README. -# `The instructions for developers is the most comprehensive -# `_. -# Linux is the most tested platform. -# -# ASPIRE requires some resources to run, so if you wouldn't run typical data -# science codes on your machine (maybe a netbook for example), -# you may use Tiger/Adroit/Della etc if you have access. -# After logging into Tiger, ``module load anaconda3/2020.7`` -# and continue to follow the anaconda instructions for -# developers in the link above. -# Those instructions should create a working environment for tinkering with -# ASPIRE code found in this notebook. - -import logging -import os - -import matplotlib.pyplot as plt -import numpy as np - -from aspire.noise import ( - AnisotropicNoiseEstimator, - CustomNoiseAdder, - WhiteNoiseAdder, - WhiteNoiseEstimator, -) -from aspire.operators import FunctionFilter -from aspire.source import RelionSource, Simulation -from aspire.utils import Rotation -from aspire.volume import Volume - -# %% -# ``Image`` Class -# --------------- -# -# The `Image `_ class -# is a thin wrapper over numpy arrays for a stack containing 1 or more images. -# In this notebook we won't be working directly with the ``Image`` class a lot, but it will be one of the fundemental structures behind the scenes. -# A lot of ASPIRE code passes around ``Image`` and ``Volume`` classes. -# -# Examples of using the Image class can be found in: -# -# - ``gallery/tutorials/basic_image_array.py`` -# -# - ``gallery/tutorials/image_class.py`` - -# %% -# ``Volume`` Class -# ---------------- -# -# Like ``Image``, the `Volume `_ class -# is a thin wrapper over numpy arrays that provides specialized methods for a stack containing 1 or more volumes. -# -# Here we will instantiate a Volume using a numpy array and use it to downsample to a desired resolution (64 should be good). -# For the data source I chose to download a real volume density map from `EMDB `_. -# The download was uncompressed in my local directory. The notebook defaults to a small low resolution sample file you may use to sanity check. -# Unfortunately real data can be quite large so we do not ship it with the repo. - -# %% -# Initialize Volume -# ----------------- - -# A low res example file is included in the repo as a sanity check. -# We can instantiate this as an ASPIRE Volume instance using ``Volume.load()``. -DATA_DIR = "data" -v = Volume.load(os.path.join(DATA_DIR, "clean70SRibosome_vol_65p.mrc")) - -# More interesting data requires downloading locally. -# v = Volume.load("path/to/EMD-2660/map/emd_2660.map") - -# Downsample the volume to a desired resolution -img_size = 64 -# Volume.downsample() returns a new Volume instance. -# We will use this lower resolution volume later, calling it `v2`. -v2 = v.downsample(img_size) -L = v2.resolution - -# %% -# Plot Data -# --------- - -# Alternatively, for quick sanity checking purposes we can view as a contour plot. -# We'll use three orthographic projections, one per axis -fig, axs = plt.subplots(1, 3) -for i in range(3): - axs[i].imshow(np.sum(v2.asnumpy()[0], axis=i), cmap="gray") -plt.show() - -# %% -# ``Rotation`` Class - Generating Random Rotations -# ------------------------------------------------ -# -# To get general projections this brings us to generating random rotations which we will apply to our volume. -# -# While you may bring your own 3x3 matrices or generate manually (say from your own Euler angles), -# ASPIRE has a `Rotation class `_ -# which can do this random rotation generation for us. It also has some other utility methods if you would want to compare with something manual. -# -# The following code will generate some random rotations, and use the ``Volume.project()`` method to return an ``Image`` instance representing the stack of projections. -# We can display projection images using the ``Image.show()`` method. - -num_rotations = 2 -rots = Rotation.generate_random_rotations(n=num_rotations, seed=12345) - -# %% -# We can access the numpy array holding the actual stack of 3x3 matrices: -logging.info(rots) -logging.info(rots.matrices) - -# Using the first (and in this case, only) volume, compute projections using the stack of rotations: -projections = v.project(0, rots) -logging.info(projections) - -# %% -# ``project()`` returns an Image instance, so we can call ``show``. - -projections.show() -# Neat, we've generated random projections of some real data. - -# %% -# The ``source`` Package -# ---------------------- -# -# `aspire.source `_ -# package contains a collection of data source interfaces. -# The idea is that we can design an experiment using a synthetic ``Simulation`` source or our own provided array via ``ArrayImageSource``; -# then later swap out the source for a large experimental data set using something like ``RelionSource``. -# -# We do this because the experimental datasets are too large to fit in memory. -# They cannot be provided as a massive large array, and instead require methods to orchestrate batching. -# Depending on the application, they may also require corresponding batched algorithms. -# The ``Source`` classes try to make most of this opaque to an end user. Ideally we can swap one source for another. -# -# For now we will build up to the creation and application of synthetic data set based on the real volume data used previously. - -# %% -# ``Simulation`` Class -# -------------------- -# -# Generating realistic synthetic data sources is a common task. -# The process of generating then projecting random rotations is integrated into the -# `Simulation `_ class. -# Using ``Simulation``, we can generate arbitrary numbers of projections for use in experiments. -# Later we will demonstrate additional features which allow us to create more realistic data sources. - -num_imgs = 100 # Total images in our source. -# Generate a Simulation instance based on the original volume data. -sim = Simulation(L=v.resolution, n=num_imgs, vols=v) -# Display the first 10 images -sim.images[:10].show() # Hi Res - -# Repeat for the lower resolution (downsampled) volume v2. -sim2 = Simulation(L=v2.resolution, n=num_imgs, vols=v2) -sim2.images[:10].show() # Lo Res - -# Note both of those simulations have the same rotations -# because they had the same seed by default, -# We can set our own seed to get a different random samples (of rotations). -sim_seed = Simulation(L=v.resolution, n=num_imgs, vols=v, seed=42) -sim_seed.images[:10].show() - -# We can also view the rotations used to create these projections -# logging.info(sim2.rotations) # Commented due to long output - -# %% -# Simulation with Filters and Noise -# --------------------------------- -# -# Filters -# ^^^^^^^ -# -# `Filters `_ -# are a collection of classes which once configured can be applied to ``Source`` pipelines. -# Common filters we might use are ``ScalarFilter``, ``PowerFilter``, ``FunctionFilter``, and ``CTFFilter``. -# ``CTFFilter`` is detailed in the ``ctf.py`` demo. -# -# Adding to Simulation -# ^^^^^^^^^^^^^^^^^^^^ -# -# We can customize Sources by adding stages to their generation pipeline. -# In this case of a Simulation source, we want to corrupt the projection images with significant noise. -# -# First we create a constant two dimension filter (constant value set to our desired noise variance). -# Then when used in the ``noise_filter``, this scalar will be multiplied by a random sample. -# Similar to before, if you require a different sample, this would be controlled via a ``seed``. - -# Get the sample variance -var = np.var(sim2.images[:].asnumpy()) -logging.info(f"Sample Variance: {var}") -target_noise_variance = 100.0 * var -logging.info(f"Target Noise Variance: {target_noise_variance}") -# Then create a NoiseAdder based on that variance. -white_noise_adder = WhiteNoiseAdder(target_noise_variance) - -# We can create a similar simulation with this additional noise_filter argument: -sim3 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder) -sim3.images[:10].show() -# These should be rather noisy now ... - -# %% -# More Advanced Noise - Whitening -# ------------------------------- -# -# We can estimate the noise across the stack of images -# -# The ``noise`` Package -# ^^^^^^^^^^^^^^^^^^^^^ -# -# The `aspire.noise `_ -# package contains several useful classes for generating and estimating different types of noise. -# -# In this case, we know the noise to be white, so we can proceed directly to -# `WhiteNoiseEstimator `_. The noise estimators consume from a ``Source``. -# -# The white noise estimator should log a diagnostic variance value. How does this compare with the known noise variance above? - -# %% - -# Create another Simulation source to tinker with. -sim_wht = Simulation( - L=v2.resolution, n=num_imgs, vols=v2, noise_adder=white_noise_adder -) - -# Estimate the white noise. -noise_estimator = WhiteNoiseEstimator(sim_wht) -logging.info(noise_estimator.estimate()) - -# %% -# A Custom ``FunctionFilter`` -# --------------------------- -# -# We will now apply some more interesting noise, using a custom function, and then apply a ``whitening`` process to our data. -# -# Using ``FunctionFilter`` we can create our own custom functions to apply in a pipeline. -# Here we want to apply a custom filter as a noise adder. We can use a function of two variables for example. - - -def noise_function(x, y): - return 1e-7 * np.exp(-(x * x + y * y) / (2 * 0.3**2)) - - -# In python, functions are first class objects. -# We take advantage of that to pass this function around as a variable. -# It will be evaluated later... -custom_noise = CustomNoiseAdder(noise_filter=FunctionFilter(noise_function)) - -# Create yet another Simulation source to tinker with. -sim4 = Simulation(L=v2.resolution, n=num_imgs, vols=v2, noise_adder=custom_noise) -sim4.images[:10].show() - -# %% -# Noise Whitening -# --------------- -# -# Applying the ``Simulation.whiten()`` method just requires passing a `NoiseEstimator` instance. -# Then we can inspect some of the whitened images. While noise is still present, we can see a dramatic change. - -# Estimate noise. -aiso_noise_estimator = AnisotropicNoiseEstimator(sim4) - -# Whiten based on the estimated noise -sim4 = sim4.whiten(aiso_noise_estimator) - -# What do the whitened images look like... -sim4.images[:10].show() - -# %% -# Real Experimental Data - ``RelionSource`` -# ----------------------------------------- -# -# Now that we have some basics, -# we can try to replace the simulation with a real experimental data source. -# -# Lets attempt the same CL experiment, but with a ``RelionSource``. - -src = RelionSource( - "data/sample_relion_data.star", - data_folder="", - pixel_size=5.0, - max_rows=1024, -) - -src = src.downsample(img_size) - -src.images[:10].show() - -# %% -# We have hit the point where we need denoising algorithms to perform orientation estimation as demonstrated in our ``pipeline_demo.py``. From 622d51bf06504d656e5c37c1566b1f77cd925c2e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 09:39:32 -0400 Subject: [PATCH 392/424] Smarter whiten preprocess default --- src/aspire/source/image.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 5965e15ce5..41569fdc5d 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -650,18 +650,24 @@ def downsample(self, L): self.L = L @as_copy - def whiten(self, noise_estimate): + def whiten(self, noise_estimate=None): """ Modify the `ImageSource` in-place by appending a whitening filter to the generation pipeline. - :param noise_estimate: `NoiseEstimator` or `Filter`. When + When no `noise_estimate` is provided, a `WhiteNoiseEstimator` + will be instantiated at this preprocessing stage behind the + scenes. + + :param noise_estimate: Optional, `NoiseEstimator` or `Filter`. When passed a `NoiseEstimator` the `filter` attribute will be queried. Alternatively, the noise PSD may be passed directly as a `Filter` object. :return: On return, the `ImageSource` object has been modified in place. """ - if isinstance(noise_estimate, NoiseEstimator): + if noise_estimate is None: + noise_filter = WhiteNoiseEstimator(self).filter + elif isinstance(noise_estimate, NoiseEstimator): # Any NoiseEstimator instance should have a `filter`. noise_filter = noise_estimate.filter elif isinstance(noise_estimate, Filter): From 19aded2a881c0b1a79a353a142f4dd2e7109efd7 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 09:40:24 -0400 Subject: [PATCH 393/424] Use new syntax in pipeline demo --- gallery/tutorials/pipeline_demo.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index a2d5d396d7..0c2d34a8c7 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -263,7 +263,8 @@ def download(url, save_path, chunk_size=1024 * 1024): from aspire.reconstruction import MeanEstimator # Assign the estimated rotations to the class averages -avgs.rotations = rots_est +# Reminder: mutating meta data will return a new source object. +avgs = avgs.update(rotations=rots_est) # Create a reasonable Basis for the 3d Volume basis = FFBBasis3D(res, dtype=vol.dtype) From 701958c00800f66f3e8eb98bca62ca4853fa06f0 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 09:40:37 -0400 Subject: [PATCH 394/424] Preprocessing tutorial updates --- .../tutorials/preprocess_imgs_sim.py | 152 +++++++++++++++--- 1 file changed, 128 insertions(+), 24 deletions(-) diff --git a/gallery/tutorials/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/tutorials/preprocess_imgs_sim.py index 59a3176a2a..5bdb325a73 100644 --- a/gallery/tutorials/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/tutorials/preprocess_imgs_sim.py @@ -26,9 +26,12 @@ # Specify Parameters # ------------------ -# Set the downsample size of images +# Set the initial simulated full size of images. img_size = 33 +# Set the demonstration downsampled image size. +ds_img_size = 15 + # Set the total number of images generated from the 3D map num_imgs = 512 @@ -57,13 +60,13 @@ for d in np.linspace(defocus_min, defocus_max, defocus_ct) ] -# Load the map file of a 70S ribosome and downsample the 3D map to desired resolution. +# Load the map file of a 70S ribosome and downsample the 3D map to desired image size. logger.info("Load 3D map from mrc file") vols = Volume.load(file_path) -# Downsample the volume to a desired resolution and increase density +# Downsample the volume to a desired image size and increase density # by 1.0e5 time for a better graph view -logger.info(f"Downsample map to a resolution of {img_size} x {img_size} x {img_size}") +logger.info(f"Downsample map to a image size of {img_size} x {img_size} x {img_size}") vols = vols.downsample(img_size) * 1.0e5 # Create a simulation object with specified filters and the downsampled 3D map @@ -76,42 +79,115 @@ noise_adder=noise_adder, ) +# We'll copy ``source`` so we can use it in a later section. +# Since ``source`` objects are designed to follow an immutable usage by default (like Numpy arrays), +# we can copy a source just by copying the object. +source_copy = source + # %% -# Apply Preprocessing Techniques -# ------------------------------ +# Apply Independent Preprocessing Techniques +# ------------------------------------------ +# Now we'll apply each technique sequentially. This is easily +# accomplished because each preprocessing technique returns a new +# ``ImageSource`` object. In this case we assign each to a new +# variable ``source_*``. That leaves the original ``source`` object +# untouched. logger.info("Obtain original images.") imgs_od = source.images[0].asnumpy() +logger.info("Perform phase flip to input images.") +source_pf = source.phase_flip() + +logger.info(f"Downsample image size to {ds_img_size} X {ds_img_size}") +source_ds = source.downsample(ds_img_size) + +logger.info("Normalize images to background noise.") +source_nb = source.normalize_background() + +logger.info("Whiten noise of images") +source_wt = source.whiten() + +logger.info("Invert the global density contrast if need") +source_rc = source.invert_contrast() + + +# %% +# Plot First Image from Each Preprocess Step +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Plot the first images. + +logger.info("Plot first image from each preprocess steps") +idm = 0 +plt.subplot(2, 3, 1) +plt.imshow(imgs_od[idm], cmap="gray") +plt.colorbar(orientation="horizontal") +plt.title("original image") + +plt.subplot(2, 3, 2) +plt.imshow(source_pf.images[0].asnumpy()[idm], cmap="gray") +plt.colorbar(orientation="horizontal") +plt.title("phase flip") + +plt.subplot(2, 3, 3) +plt.imshow(source_ds.images[0].asnumpy()[idm], cmap="gray") +plt.colorbar(orientation="horizontal") +plt.title("downsample") + +plt.subplot(2, 3, 4) +plt.imshow(source_nb.images[0].asnumpy()[idm], cmap="gray") +plt.colorbar(orientation="horizontal") +plt.title("normalize background") + +plt.subplot(2, 3, 5) +plt.imshow(source_wt.images[0].asnumpy()[idm], cmap="gray") +plt.colorbar(orientation="horizontal") +plt.title("noise whitening") + +plt.subplot(2, 3, 6) +plt.imshow(source_rc.images[0].asnumpy()[idm], cmap="gray") +plt.colorbar(orientation="horizontal") +plt.title("invert contrast") +plt.tight_layout() + + +# %% +# Apply Sequential Preprocessing Techniques +# ----------------------------------------- +# Now we'll apply each technique sequentially. +# This is accomplished by reassigning each new ``ImageSource`` to the same variable. +# In this case we reassign to ``source``. +# Note, after each ``source`` assignment we'll manually save off the images for the plot below. + +logger.info("Obtain original images.") +imgs_seq_od = source.images[0].asnumpy() + logger.info("Perform phase flip to input images.") source = source.phase_flip() -imgs_pf = source.images[0].asnumpy() +imgs_seq_pf = source.images[0].asnumpy() -max_resolution = 15 -logger.info(f"Downsample resolution to {max_resolution} X {max_resolution}") -if max_resolution < source.L: - source = source.downsample(max_resolution) -imgs_ds = source.images[0].asnumpy() +logger.info(f"Downsample image size to {ds_img_size} X {ds_img_size}") +source = source.downsample(ds_img_size) +imgs_seq_ds = source.images[0].asnumpy() logger.info("Normalize images to background noise.") source = source.normalize_background() -imgs_nb = source.images[0].asnumpy() +imgs_seq_nb = source.images[0].asnumpy() logger.info("Whiten noise of images") -noise_estimator = WhiteNoiseEstimator(source) -source = source.whiten(noise_estimator) -imgs_wt = source.images[0].asnumpy() +source = source.whiten() +imgs_seq_wt = source.images[0].asnumpy() logger.info("Invert the global density contrast if need") source = source.invert_contrast() -imgs_rc = source.images[0].asnumpy() +imgs_seq_rc = source.images[0].asnumpy() # %% # Plot First Image from Each Preprocess Step -# ------------------------------------------ +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +# Plot the first images. -# plot the first images logger.info("Plot first image from each preprocess steps") idm = 0 plt.subplot(2, 3, 1) @@ -120,27 +196,55 @@ plt.title("original image") plt.subplot(2, 3, 2) -plt.imshow(imgs_pf[idm], cmap="gray") +plt.imshow(imgs_seq_pf[idm], cmap="gray") plt.colorbar(orientation="horizontal") plt.title("phase flip") plt.subplot(2, 3, 3) -plt.imshow(imgs_ds[idm], cmap="gray") +plt.imshow(imgs_seq_ds[idm], cmap="gray") plt.colorbar(orientation="horizontal") plt.title("downsample") plt.subplot(2, 3, 4) -plt.imshow(imgs_nb[idm], cmap="gray") +plt.imshow(imgs_seq_nb[idm], cmap="gray") plt.colorbar(orientation="horizontal") plt.title("normalize background") plt.subplot(2, 3, 5) -plt.imshow(imgs_wt[idm], cmap="gray") +plt.imshow(imgs_seq_wt[idm], cmap="gray") plt.colorbar(orientation="horizontal") plt.title("noise whitening") plt.subplot(2, 3, 6) -plt.imshow(imgs_rc[idm], cmap="gray") +plt.imshow(imgs_seq_rc[idm], cmap="gray") plt.colorbar(orientation="horizontal") plt.title("invert contrast") plt.tight_layout() + + +# %% +# Apply Chained Preprocessing Techniques +# -------------------------------------- +# Now we'll apply the preprocessing in a chain syntax + +# We'll reset our ``source`` to the reference copy we started with. +source = source_copy + +logger.info("Perform phase flip to input images.") +logger.info(f"Downsample image size to {ds_img_size} X {ds_img_size}") +logger.info("Normalize images to background noise.") +logger.info("Whiten noise of images") +logger.info("Invert the global density contrast if need") +source = ( + source.phase_flip() + .downsample(ds_img_size) + .normalize_background() + .whiten() + .invert_contrast() +) +# Assign the first image from the preprocessed chain. +imgs_chained = source.images[0].asnumpy() + +# This preprocessing chain should correspond to applying each +# operation sequentially. +assert np.allclose(imgs_chained, imgs_seq_rc) From fc2d5f37bc306ab4d2c4c570e95ade3dd0ca39bc Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 09:47:33 -0400 Subject: [PATCH 395/424] Update experiments to use new syntax note requires manually testing --- .../experiments/experimental_abinitio_pipeline_10028.py | 2 +- .../experiments/experimental_abinitio_pipeline_10073.py | 2 +- .../experiments/experimental_abinitio_pipeline_10081.py | 2 +- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- gallery/tutorials/pipeline_demo.py | 7 ++++++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index dea6ffb91d..b842cdbc7c 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -174,7 +174,7 @@ logger.info("Begin Volume reconstruction") # Assign the estimated rotations to the class averages -avgs.rotations = rots_est +avgs = avgs.update(rotations=rots_est) # Create a reasonable Basis for the 3d Volume basis = FFBBasis3D((img_size,) * 3, dtype=src.dtype) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index 3f827700a2..dbe5bba325 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -149,7 +149,7 @@ logger.info("Begin Volume reconstruction") # Assign the estimated rotations to the class averages -avgs.rotations = rots_est +avgs = avgs.update(rotations=rots_est) # Create a reasonable Basis for the 3d Volume basis = FFBBasis3D((img_size,) * 3, dtype=src.dtype) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index 43b47d110e..c9b79b59cb 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -117,7 +117,7 @@ logger.info("Begin Volume reconstruction") # Assign the estimated rotations to the class averages -avgs.rotations = rots_est +avgs = avgs.update(rotations=rots_est) # Create a reasonable Basis for the 3d Volume basis = FFBBasis3D((img_size,) * 3, dtype=src.dtype) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 1932567c77..c960eddbd4 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -215,7 +215,7 @@ def noise_function(x, y): logger.info("Begin Volume reconstruction") # Assign the estimated rotations to the class averages -avgs.rotations = rots_est +avgs = avgs.update(rotations=rots_est) # Create a reasonable Basis for the 3d Volume basis = FFBBasis3D((v.resolution,) * 3, dtype=v.dtype) diff --git a/gallery/tutorials/pipeline_demo.py b/gallery/tutorials/pipeline_demo.py index 0c2d34a8c7..03c4d2d64e 100644 --- a/gallery/tutorials/pipeline_demo.py +++ b/gallery/tutorials/pipeline_demo.py @@ -263,9 +263,14 @@ def download(url, save_path, chunk_size=1024 * 1024): from aspire.reconstruction import MeanEstimator # Assign the estimated rotations to the class averages -# Reminder: mutating meta data will return a new source object. avgs = avgs.update(rotations=rots_est) +# %% +# .. note:: +# Outside of internal operations during ``ImageSource`` +# construction, mutating meta data will return a new source +# object. + # Create a reasonable Basis for the 3d Volume basis = FFBBasis3D(res, dtype=vol.dtype) From a2666a239dbffffc8bc3b9fc3c146ec12f630b80 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 10:37:23 -0400 Subject: [PATCH 396/424] Initial attempt adding `_mutable` attribute --- src/aspire/denoising/class_avg.py | 4 ++++ src/aspire/denoising/denoised_src.py | 3 +++ src/aspire/source/coordinates.py | 3 +++ src/aspire/source/image.py | 15 +++++++++++++++ src/aspire/source/relion.py | 3 +++ src/aspire/source/simulation.py | 3 +++ 6 files changed, 31 insertions(+) diff --git a/src/aspire/denoising/class_avg.py b/src/aspire/denoising/class_avg.py index b1395ff550..6f19685b55 100644 --- a/src/aspire/denoising/class_avg.py +++ b/src/aspire/denoising/class_avg.py @@ -83,6 +83,10 @@ def __init__( dtype=self.averager.src.dtype, ) + # Any further operations should not mutate this instance. + # Note, updating `n` is a special case for this source at this time. + self._mutable = False + @ImageSource.n.setter def n(self, n): """ diff --git a/src/aspire/denoising/denoised_src.py b/src/aspire/denoising/denoised_src.py index 4a107469fa..94d4c1e4e9 100644 --- a/src/aspire/denoising/denoised_src.py +++ b/src/aspire/denoising/denoised_src.py @@ -27,6 +27,9 @@ def __init__(self, src, denoiser, batch_size=512): self.denoiser = denoiser self.batch_size = batch_size + # Any further operations should not mutate this instance. + self._mutable = False + def _images(self, indices): """ Internal function to return a set of images after denoising, when accessed via the diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 49532f5a78..dabb036611 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -159,6 +159,9 @@ def __init__(self, files, particle_size, max_rows, B): "__mrc_filepath", self.mrc_paths[mrc_index], particle_indices ) + # Any further operations should not mutate this instance. + self._mutable = False + def _populate_particles(self, coord_paths): """ All subclasses create mrc_paths and coord_paths lists and pass them to diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 41569fdc5d..7383cd8580 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -140,6 +140,10 @@ class ImageSource(ABC): the unique "_rlnDefocusU"/"_rlnDefocusV" Relion parameters. """ + # The abstract class starts off _mutable. Conrete classes should + # disable _mutable as the last step in __init__. + _mutable = True + def __init__(self, L, n, dtype="double", metadata=None, memory=None): """ A cryo-EM ImageSource object that supplies images along with other parameters for image manipulation. @@ -470,6 +474,11 @@ def set_metadata(self, metadata_fields, values, indices=None): values should either be a scalar or a vector of length equal to the total number of images, |self.n|. :return: On return, the metadata associated with the specified indices has been modified. """ + + # # Check if we're in an immutable state. + # if not self._mutable: + # raise RuntimeError("This source is no longer mutable, try using `update` instead of `set_metadata`") + if isinstance(metadata_fields, str): metadata_fields = [metadata_fields] @@ -1314,6 +1323,9 @@ def __init__(self, src, indices, memory=None): self.filter_indices = np.zeros(self.n, dtype=int) self.unique_filters = [IdentityFilter()] + # Any further operations should not mutate this instance. + self._mutable = False + def _images(self, indices): """ Returns images from `self.src` corresponding to `indices` @@ -1388,6 +1400,9 @@ def __init__(self, im, metadata=None, angles=None): # which is exposed by properties `angles` and `rotations`. self.angles = angles + # Any further operations should not mutate this instance. + self._mutable = False + def _images(self, indices): """ Returns images corresponding to `indices` after being accessed via the diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 200966b7b2..8a44f18162 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -145,6 +145,9 @@ def __init__( logger.info(f"Populated {self.n_ctf_filters} CTFFilters from '{filepath}'") + # Any further operations should not mutate this instance. + self._mutable = False + def populate_metadata(self): """ Relion STAR files may contain a large number of metadata columns in addition diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 9256a91b40..9e3dea758b 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -176,6 +176,9 @@ def __init__( self.noise_adder = noise_adder + # Any further operations should not mutate this instance. + self._mutable = False + def _populate_ctf_metadata(self, filter_indices): # Since we are not reading from a starfile, we must construct # metadata based on the CTF filters by hand and set the values From 8dd0fa895732dadceb9dcb488f00f357bf4ff2c3 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 10:45:36 -0400 Subject: [PATCH 397/424] Danger --- src/aspire/source/image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 7383cd8580..a007c69a64 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -475,6 +475,7 @@ def set_metadata(self, metadata_fields, values, indices=None): :return: On return, the metadata associated with the specified indices has been modified. """ + # This breaks lots of things, maybe not something we want to rush out. # # Check if we're in an immutable state. # if not self._mutable: # raise RuntimeError("This source is no longer mutable, try using `update` instead of `set_metadata`") From 1cbcd5dd80106590eafa8a0733fa4bb9bf5d8607 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 10:57:24 -0400 Subject: [PATCH 398/424] Remove `as_copy` on `cache` and adds docstring. --- src/aspire/source/image.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index a007c69a64..31579666ec 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -623,8 +623,13 @@ def _apply_source_filters(self, im_orig, indices): self.filter_indices[indices], ) - @as_copy def cache(self): + """ + Computes all queued pipeline transformations and stores the + generated images in an array. This trades memory for fast + image access, and is useful when images will be repeatedly + queried since it avoids recomputing on-the-fly. + """ logger.info("Caching source images") self._cached_im = self.images[:] self.generation_pipeline.reset() From 955e5b0107e6b3afb4828724c64483f8bf28bd36 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 11:49:03 -0400 Subject: [PATCH 399/424] revert ImageSource.cache usage --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- src/aspire/commands/cov3d.py | 2 +- src/aspire/commands/denoise.py | 2 +- tests/test_class2D.py | 2 +- tests/test_coordinate_source.py | 6 +++--- tests/test_load_images.py | 2 +- tests/test_simulation.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index c960eddbd4..00d838e854 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -129,7 +129,7 @@ def noise_function(x, y): src.images[:10].show() # Cache to memory for some speedup -src = src.cache() +src.cache() # %% # Optional: CWF Denoising diff --git a/src/aspire/commands/cov3d.py b/src/aspire/commands/cov3d.py index 9f2ff0c45f..da883040df 100644 --- a/src/aspire/commands/cov3d.py +++ b/src/aspire/commands/cov3d.py @@ -48,7 +48,7 @@ def cov3d( ) source = source.downsample(max_resolution) - source = source.cache() + source.cache() source = source.whiten() basis = FBBasis3D((max_resolution, max_resolution, max_resolution)) diff --git a/src/aspire/commands/denoise.py b/src/aspire/commands/denoise.py index e0900e69c9..3d52340e3b 100644 --- a/src/aspire/commands/denoise.py +++ b/src/aspire/commands/denoise.py @@ -78,7 +78,7 @@ def denoise( source = source.downsample(max_resolution) else: logger.warn(f"Unable to downsample to {max_resolution}, using {source.L}") - source = source.cache() + source.cache() # Specify the fast FB basis method for expending the 2D images basis = FFBBasis2D((source.L, source.L)) diff --git a/tests/test_class2D.py b/tests/test_class2D.py index bec7348442..8c9bd35243 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -40,7 +40,7 @@ def setUp(self): self.src = Simulation( L=self.resolution, n=321, vols=v, dtype=self.dtype, seed=SEED ) - self.src = self.src.cache() # Precompute image stack + self.src.cache() # Precompute image stack # Calculate some projection images self.imgs = self.src.images[:] diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 2a704b069c..3184436c0f 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -393,11 +393,11 @@ def testImages(self): def testCached(self): src = BoxesCoordinateSource(self.files_box) - src_uncached = BoxesCoordinateSource(self.files_box) - src_cached = src.cache() + src_uncached = src[:] + src.cache() self.assertTrue( np.array_equal( - src_cached.images[:].asnumpy(), src_uncached.images[:].asnumpy() + src.images[:].asnumpy(), src_uncached.images[:].asnumpy() ) ) diff --git a/tests/test_load_images.py b/tests/test_load_images.py index 1aabed14e5..2376cc2bd0 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -199,7 +199,7 @@ def testBadNDArray(self): def testRelionSourceCached(self): src_cached = RelionSource(self.starfile_path, data_folder=self.data_folder) - src_cached = src_cached.cache() + src_cached.cache() self.assertTrue( np.array_equal(src_cached.images[:].asnumpy(), self.src.images[:].asnumpy()) ) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index ca01c82edd..0e0e263208 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -167,7 +167,7 @@ def testSimulationCached(self): noise_adder=WhiteNoiseAdder(var=1), dtype=self.dtype, ) - sim_cached = sim_cached.cache() + sim_cached.cache() self.assertTrue( np.array_equal(sim_cached.images[:].asnumpy(), self.sim.images[:].asnumpy()) ) From 2d046c66b2c889259f44c24757d05b163c306762 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 11 Apr 2023 11:59:37 -0400 Subject: [PATCH 400/424] tox --- gallery/tutorials/tutorials/preprocess_imgs_sim.py | 2 +- tests/test_coordinate_source.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/gallery/tutorials/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/tutorials/preprocess_imgs_sim.py index 5bdb325a73..03b4379694 100644 --- a/gallery/tutorials/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/tutorials/preprocess_imgs_sim.py @@ -11,7 +11,7 @@ import matplotlib.pyplot as plt import numpy as np -from aspire.noise import WhiteNoiseAdder, WhiteNoiseEstimator +from aspire.noise import WhiteNoiseAdder from aspire.operators import RadialCTFFilter from aspire.source.simulation import Simulation from aspire.volume import Volume diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 3184436c0f..40dd51fc4e 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -396,9 +396,7 @@ def testCached(self): src_uncached = src[:] src.cache() self.assertTrue( - np.array_equal( - src.images[:].asnumpy(), src_uncached.images[:].asnumpy() - ) + np.array_equal(src.images[:].asnumpy(), src_uncached.images[:].asnumpy()) ) def testImagesRandomIndices(self): From 35f76c6778d9b40f09b156d7cffe93a485e8ccb2 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Tue, 11 Apr 2023 16:55:09 -0400 Subject: [PATCH 401/424] suggestion from PR 911 --- src/aspire/storage/starfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index e7fa088927..369c51abf8 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -22,7 +22,7 @@ def __init__(self, filepath="", blocks=None): for _k, _v in blocks[k].items(): if not isinstance(_v, str): if hasattr(_v, "__iter__"): - blocks[k][_k] = [str(__v) for __v in _v] + blocks[k][_k] = list(map(str, _v)) else: blocks[k][_k] = str(_v) From c2b1878538dfe0c9f3cebf66700f3be2f09124f4 Mon Sep 17 00:00:00 2001 From: Vineet Bansal Date: Tue, 11 Apr 2023 20:23:11 -0400 Subject: [PATCH 402/424] toxed --- src/aspire/storage/starfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 369c51abf8..6c8c5dbfc9 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -22,7 +22,7 @@ def __init__(self, filepath="", blocks=None): for _k, _v in blocks[k].items(): if not isinstance(_v, str): if hasattr(_v, "__iter__"): - blocks[k][_k] = list(map(str, _v)) + blocks[k][_k] = list(map(str, _v)) else: blocks[k][_k] = str(_v) From 9c22421c9a4f0994bd2481918ffce830f7bb8a14 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 13 Apr 2023 08:54:55 -0400 Subject: [PATCH 403/424] Pass K parameter to super of AsymmetricVolume. --- src/aspire/volume/volume_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/volume/volume_synthesis.py b/src/aspire/volume/volume_synthesis.py index cdc9ada2ce..74a3fd05ce 100644 --- a/src/aspire/volume/volume_synthesis.py +++ b/src/aspire/volume/volume_synthesis.py @@ -338,7 +338,7 @@ class AsymmetricVolume(CnSymmetricVolume): """ def __init__(self, L, C, K=16, seed=None, dtype=np.float64): - super().__init__(L=L, C=C, order=1, seed=seed, dtype=dtype) + super().__init__(L=L, C=C, K=K, order=1, seed=seed, dtype=dtype) def _check_order(self): if self.order != 1: From 8787e27b4af2ccadaf97057d47f9fc6443a6c623 Mon Sep 17 00:00:00 2001 From: Josh Carmichael Date: Thu, 13 Apr 2023 10:05:35 -0400 Subject: [PATCH 404/424] Set default K to 64. --- src/aspire/volume/volume_synthesis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/volume/volume_synthesis.py b/src/aspire/volume/volume_synthesis.py index 74a3fd05ce..54ae6b578f 100644 --- a/src/aspire/volume/volume_synthesis.py +++ b/src/aspire/volume/volume_synthesis.py @@ -337,7 +337,7 @@ class AsymmetricVolume(CnSymmetricVolume): An asymmetric Volume constructed of random 3D Gaussian blobs with compact support in the unit sphere. """ - def __init__(self, L, C, K=16, seed=None, dtype=np.float64): + def __init__(self, L, C, K=64, seed=None, dtype=np.float64): super().__init__(L=L, C=C, K=K, order=1, seed=seed, dtype=dtype) def _check_order(self): From 51390e33c5a1875bc78153e4722ade8d89cf94d8 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 07:32:47 -0400 Subject: [PATCH 405/424] fix redundant assignment --- gallery/tutorials/aspire_introduction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/tutorials/aspire_introduction.py b/gallery/tutorials/aspire_introduction.py index d4d873f78c..3ca8d8dc6e 100644 --- a/gallery/tutorials/aspire_introduction.py +++ b/gallery/tutorials/aspire_introduction.py @@ -633,7 +633,7 @@ def noise_function(x, y): # %% # Display the experimental data images. -src = src.images[:10].show() +src.images[:10].show() # %% # Pipeline Roadmap From b8fbf92bb066a069b62fa432a914cf8c13434362 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 07:38:08 -0400 Subject: [PATCH 406/424] move tutorial src copy to later section --- gallery/tutorials/tutorials/preprocess_imgs_sim.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gallery/tutorials/tutorials/preprocess_imgs_sim.py b/gallery/tutorials/tutorials/preprocess_imgs_sim.py index 03b4379694..0d398c6743 100644 --- a/gallery/tutorials/tutorials/preprocess_imgs_sim.py +++ b/gallery/tutorials/tutorials/preprocess_imgs_sim.py @@ -79,11 +79,6 @@ noise_adder=noise_adder, ) -# We'll copy ``source`` so we can use it in a later section. -# Since ``source`` objects are designed to follow an immutable usage by default (like Numpy arrays), -# we can copy a source just by copying the object. -source_copy = source - # %% # Apply Independent Preprocessing Techniques # ------------------------------------------ @@ -159,6 +154,11 @@ # In this case we reassign to ``source``. # Note, after each ``source`` assignment we'll manually save off the images for the plot below. +# We'll copy ``source`` so we can use it in a later section. +# Since ``source`` objects are designed to follow an immutable usage by default (like Numpy arrays), +# we can copy a source just by copying the object. +source_copy = source + logger.info("Obtain original images.") imgs_seq_od = source.images[0].asnumpy() From aea8c5f27914d7bd8ed815a1c15675723dc99ae9 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 08:06:29 -0400 Subject: [PATCH 407/424] Migrate asserts to Errors --- src/aspire/source/image.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 31579666ec..8d954a4e7b 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -260,9 +260,10 @@ def _metadata_as_dict(self, metadata_fields, indices, default_value=None): if metadata_field in self._metadata: result[metadata_field] = self._metadata[metadata_field][indices].copy() else: - assert ( - default_value is not None - ), f"Missing metadata field {metadata_field} and no default_value supplied" + if default_value is None: + raise ValueError( + f"Missing metadata field {metadata_field} and no default_value supplied" + ) result[metadata_field] = np.full(len(indices), fill_value=default_value) return result @@ -281,9 +282,10 @@ def _metadata_as_ndarray(self, metadata_fields, indices, default_value=None): for i, metadata_field in enumerate(metadata_fields): if metadata_field not in self._metadata: - assert ( - default_value is not None - ), f"Missing metadata field {metadata_field} and no default_value supplied" + if default_value is None: + raise ValueError( + f"Missing metadata field {metadata_field} and no default_value supplied" + ) result[:, i] = default_value dtypes[i] = np.array([default_value]).dtype else: @@ -493,9 +495,10 @@ def set_metadata(self, metadata_fields, values, indices=None): else: if isinstance(values, str): values = [values] * len(indices) - assert len(values) == len( - indices - ), "Mismatch between len(values) and len(indices)" + if len(values) != len(indices): + raise RuntimeError( + f"Mismatch between len(values) {len(values)} and len(indices) {len(indices)}." + ) values = np.array(values) # make a copy for our use @@ -651,9 +654,10 @@ def _images(self, indices): @as_copy def downsample(self, L): - assert ( - L <= self.L - ), "Max desired resolution should be less than the current resolution" + if L > self.L: + raise ValueError( + "Max desired resolution {L} should be less than the current resolution {self.L}." + ) logger.info(f"Setting max. resolution of source = {L}") self.generation_pipeline.add_xform(Downsample(resolution=L)) @@ -834,7 +838,8 @@ def vol_forward(self, vol, start, num): amplitude. """ all_idx = np.arange(start, min(start + num, self.n)) - assert vol.n_vols == 1, "vol_forward expects a single volume, not a stack" + if vol.n_vols != 1: + raise ValueError("vol_forward expects a single volume, not a stack.") if vol.dtype != self.dtype: logger.warning(f"Volume.dtype {vol.dtype} inconsistent with {self.dtype}") From 632e122b582ccfb90865a786220e8d62a4c2c06d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 08:26:56 -0400 Subject: [PATCH 408/424] convert pop meta data to staticmethod --- src/aspire/source/image.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 8d954a4e7b..5cb42cb1a0 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -887,8 +887,9 @@ def save( info = {"starfile": starfile_filepath, "mrcs": unique_filenames} return info + @staticmethod def _populate_common_metadata( - self, + n, meta_dict, local_cols, starfile_filepath, @@ -899,22 +900,22 @@ def _populate_common_metadata( Populate metadata columns common to all `ImageSource` subclasses. """ # Create a new key that we will be populating in the loop below - meta_dict["_rlnImageName"] = np.full(self.n, fill_value="").astype("object") + meta_dict["_rlnImageName"] = np.full(n, fill_value="").astype("object") if save_mode == "single": # Save all images into one single mrc file fname = os.path.basename(starfile_filepath) fstem = os.path.splitext(fname)[0] - mrcs_filename = f"{fstem}_{0}_{self.n-1}.mrcs" + mrcs_filename = f"{fstem}_{0}_{n-1}.mrcs" # Then set name in dict for the StarFile meta_dict["_rlnImageName"][:] = [ - f"{j + 1:06}@{mrcs_filename}" for j in range(self.n) + f"{j + 1:06}@{mrcs_filename}" for j in range(n) ] else: # save all images into multiple mrc files in batch size - for i_start in np.arange(0, self.n, batch_size): - i_end = min(self.n, i_start + batch_size) + for i_start in np.arange(0, n, batch_size): + i_end = min(n, i_start + batch_size) num = i_end - i_start mrcs_filename = ( os.path.splitext(os.path.basename(starfile_filepath))[0] @@ -966,7 +967,7 @@ def save_metadata(self, starfile_filepath, batch_size=512, save_mode=None): # Populates _rlnImageName column, setting up filepaths to .mrcs stacks self._populate_common_metadata( - metadata, local_cols, starfile_filepath, batch_size, save_mode + self.n, metadata, local_cols, starfile_filepath, batch_size, save_mode ) filename_indices = [ From 6b42229561e588c141abf4f451713ac2454faab2 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 08:27:21 -0400 Subject: [PATCH 409/424] remove 42 magic value, use default fill --- src/aspire/source/image.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 5cb42cb1a0..c8b412bcbc 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -957,7 +957,7 @@ def save_metadata(self, starfile_filepath, batch_size=512, save_mode=None): # Get local metadata columns that were added by subclass local_cols = self._populate_local_metadata() - metadata = self.get_metadata(as_dict=True, default_value=42).copy() + metadata = self.get_metadata(as_dict=True).copy() # Drop any column that doesn't start with a *single* underscore metadata = { k: v From b47e01e691aa7b7e0c1cb55d97605661cdd6604a Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 09:49:53 -0400 Subject: [PATCH 410/424] change coord source len check from values[key] to values() --- src/aspire/source/coordinates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index a1e85f761d..4b6cbcfd5d 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -340,7 +340,7 @@ def import_relion_ctf(self, ctf): data_block = RelionStarFile(ctf).get_merged_data_block() # data_block is a dict containing the micrographs - if not len(data_block[list(data_block.keys())[0]]) == self.num_micrographs: + if not len(list(data_block.values())[0]) == self.num_micrographs: raise ValueError( f"{ctf} has CTF information for {len(data_block)}", f" micrographs but this source has {self.num_micrographs} micrographs.", From d8b1d77dd0d1f936e9c083c843d518aaaf18fb91 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 10:21:07 -0400 Subject: [PATCH 411/424] refactor ctf param data block stack --- src/aspire/source/coordinates.py | 8 ++------ src/aspire/source/relion.py | 3 ++- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 4b6cbcfd5d..f78046f74f 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -367,13 +367,9 @@ def _extract_ctf(self, df): n = len(df["_rlnVoltage"]) # get unique ctfs from the data block # i'th entry of `indices` contains the index of `filter_params` with corresponding CTF params + ctf_data = np.stack( df[c] for c in CTF_params ).astype(self.dtype).T filter_params, indices = np.unique( - np.vstack( - [ - np.array([df[c][i] for c in CTF_params]).astype(self.dtype) - for i in range(n) - ] - ), + ctf_data, return_inverse=True, axis=0, ) diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index 32223acf7a..d22576148c 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -107,8 +107,9 @@ def __init__( # If these all exist in the STAR file, we may create CTF filters for the source if set(CTF_params).issubset(metadata.keys()): # partition particles according to unique CTF parameters + ctf_data = np.stack(metadata[k] for k in CTF_params).T filter_params, filter_indices = np.unique( - np.array([metadata[k] for k in CTF_params]).T, + ctf_data, return_inverse=True, axis=0, ) From c22275a5cb8d5aea3d6cf6515aaeaa2ac17d7d90 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 10:27:01 -0400 Subject: [PATCH 412/424] replace df with data_block in coord source --- src/aspire/source/coordinates.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index f78046f74f..8d339c09d3 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -225,8 +225,8 @@ def _coords_list_from_star(self, star_file): return a list of coordinates in box format. :param star_file: A path to a STAR file containing particle centers """ - df = StarFile(star_file).get_block_by_index(0) - coords = list(zip(df["_rlnCoordinateX"], df["_rlnCoordinateY"])) + data_block = StarFile(star_file).get_block_by_index(0) + coords = list(zip(data_block["_rlnCoordinateX"], data_block["_rlnCoordinateY"])) coords = [(float(x), float(y)) for x, y in coords] return [ self._box_coord_from_center(coord, self.particle_size) for coord in coords @@ -319,15 +319,15 @@ def import_aspire_ctf(self, ctf): ) # merge dicts from CTF files - dfs = defaultdict(list) + data_blocks = defaultdict(list) for f in ctf: # ASPIRE's CTF Estimator produces legacy (=< 3.0) STAR files containing one row star = RelionStarFile(f) data_block = star.data_block for k, v in data_block.items(): - dfs[k].append(v) + data_blocks[k].append(v) - self._extract_ctf(dfs) + self._extract_ctf(data_blocks) def import_relion_ctf(self, ctf): """ @@ -348,7 +348,7 @@ def import_relion_ctf(self, ctf): self._extract_ctf(data_block) - def _extract_ctf(self, df): + def _extract_ctf(self, data_block): """ Receives a dict containing micrograph CTF information, and populates the Source's CTF Filters, filter indices, and metadata. @@ -364,10 +364,10 @@ def _extract_ctf(self, df): "_rlnMicrographPixelSize", ] - n = len(df["_rlnVoltage"]) + n = len(data_block["_rlnVoltage"]) # get unique ctfs from the data block # i'th entry of `indices` contains the index of `filter_params` with corresponding CTF params - ctf_data = np.stack( df[c] for c in CTF_params ).astype(self.dtype).T + ctf_data = np.stack( data_block[c] for c in CTF_params ).astype(self.dtype).T filter_params, indices = np.unique( ctf_data, return_inverse=True, @@ -647,16 +647,16 @@ def _validate_starfile(self, coord_file): """ Ensures that a STAR file contains numeric particle centers. """ - df = StarFile(coord_file).get_block_by_index(0) + data_block = StarFile(coord_file).get_block_by_index(0) # We're looking for specific columns for the X and Y coordinates - if not all(col in df for col in ["_rlnCoordinateX", "_rlnCoordinateY"]): + if not all(col in data_block for col in ["_rlnCoordinateX", "_rlnCoordinateY"]): logger.error(f"Problem with coordinate file: {coord_file}") raise ValueError( "STAR file does not contain _rlnCoordinateX, _rlnCoordinateY columns." ) # check that all values in each column are numeric if not all( - all(map(self._is_number, df[col])) + all(map(self._is_number, data_block[col])) for col in ["_rlnCoordinateX", "_rlnCoordinateY"] ): logger.error(f"Problem with coordinate file: {coord_file}") From 15aade3033028b8a8865deacdaa2352443ecc16e Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 10:28:22 -0400 Subject: [PATCH 413/424] refactor str() list comp --- src/aspire/storage/starfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 6c8c5dbfc9..8533016ca2 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -117,7 +117,7 @@ def _initialize_blocks(self): d = {} for i, loop_tag in enumerate(loop_tags): d[loop_tag] = [ - str(loop_data[j][i]) for j in range(len(loop_data)) + str(x[i]) for x in loop_data ] self.blocks[gemmi_block.name] = d else: From 9e01130925ab3d0727a17e7ad9e3702b203a5160 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 10:41:03 -0400 Subject: [PATCH 414/424] add comment regarding block string casts --- src/aspire/storage/starfile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 8533016ca2..090be4fa20 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -16,7 +16,8 @@ def __init__(self, filepath="", blocks=None): """ Initialize either from a path to a STAR file or from an OrderedDict of ndarrays """ - # if blocks are given, set self.blocks, otherwise initialize an empty OrderedDict() + # If blocks are given, set self.blocks, otherwise initialize an empty OrderedDict() + # The following code repacks provided blocks as strings while handling both singleton and iterable inputs. if blocks is not None: for k in blocks: for _k, _v in blocks[k].items(): From c6389c41d2ee5f9ef8ff3d24549b2cba4dbd9f3d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 10:41:12 -0400 Subject: [PATCH 415/424] toxing --- src/aspire/source/coordinates.py | 3 +-- src/aspire/storage/starfile.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 8d339c09d3..0ceae26cfa 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -364,10 +364,9 @@ def _extract_ctf(self, data_block): "_rlnMicrographPixelSize", ] - n = len(data_block["_rlnVoltage"]) # get unique ctfs from the data block # i'th entry of `indices` contains the index of `filter_params` with corresponding CTF params - ctf_data = np.stack( data_block[c] for c in CTF_params ).astype(self.dtype).T + ctf_data = np.stack(data_block[c] for c in CTF_params).astype(self.dtype).T filter_params, indices = np.unique( ctf_data, return_inverse=True, diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 090be4fa20..30a78ca2d3 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -117,9 +117,7 @@ def _initialize_blocks(self): # initialize dict from list of lists d = {} for i, loop_tag in enumerate(loop_tags): - d[loop_tag] = [ - str(x[i]) for x in loop_data - ] + d[loop_tag] = [str(x[i]) for x in loop_data] self.blocks[gemmi_block.name] = d else: # enforce unique block names (keys of StarFile.block OrderedDict) From 5c089b7e9c544e0078042334e47c2cc106f6aa04 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 13:03:05 -0400 Subject: [PATCH 416/424] refactor starfile iterable loop --- src/aspire/storage/starfile.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/aspire/storage/starfile.py b/src/aspire/storage/starfile.py index 30a78ca2d3..7f2c1fae28 100644 --- a/src/aspire/storage/starfile.py +++ b/src/aspire/storage/starfile.py @@ -22,9 +22,11 @@ def __init__(self, filepath="", blocks=None): for k in blocks: for _k, _v in blocks[k].items(): if not isinstance(_v, str): - if hasattr(_v, "__iter__"): - blocks[k][_k] = list(map(str, _v)) - else: + try: + # Cast iterable elements to string. + blocks[k][_k] = list(map(str, iter(_v))) + except TypeError: + # Singleton, cast to string. blocks[k][_k] = str(_v) self.blocks = blocks or OrderedDict() From 9c465c0422fa2264f19b8dfc22ba32b6520c141d Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 15:27:33 -0400 Subject: [PATCH 417/424] revert one of two cache commits --- gallery/experiments/simulated_abinitio_pipeline.py | 2 +- src/aspire/commands/cov3d.py | 2 +- src/aspire/commands/denoise.py | 2 +- tests/test_class2D.py | 2 +- tests/test_coordinate_source.py | 8 +++++--- tests/test_load_images.py | 2 +- tests/test_simulation.py | 2 +- 7 files changed, 11 insertions(+), 9 deletions(-) diff --git a/gallery/experiments/simulated_abinitio_pipeline.py b/gallery/experiments/simulated_abinitio_pipeline.py index 00d838e854..c960eddbd4 100644 --- a/gallery/experiments/simulated_abinitio_pipeline.py +++ b/gallery/experiments/simulated_abinitio_pipeline.py @@ -129,7 +129,7 @@ def noise_function(x, y): src.images[:10].show() # Cache to memory for some speedup -src.cache() +src = src.cache() # %% # Optional: CWF Denoising diff --git a/src/aspire/commands/cov3d.py b/src/aspire/commands/cov3d.py index da883040df..9f2ff0c45f 100644 --- a/src/aspire/commands/cov3d.py +++ b/src/aspire/commands/cov3d.py @@ -48,7 +48,7 @@ def cov3d( ) source = source.downsample(max_resolution) - source.cache() + source = source.cache() source = source.whiten() basis = FBBasis3D((max_resolution, max_resolution, max_resolution)) diff --git a/src/aspire/commands/denoise.py b/src/aspire/commands/denoise.py index 3d52340e3b..e0900e69c9 100644 --- a/src/aspire/commands/denoise.py +++ b/src/aspire/commands/denoise.py @@ -78,7 +78,7 @@ def denoise( source = source.downsample(max_resolution) else: logger.warn(f"Unable to downsample to {max_resolution}, using {source.L}") - source.cache() + source = source.cache() # Specify the fast FB basis method for expending the 2D images basis = FFBBasis2D((source.L, source.L)) diff --git a/tests/test_class2D.py b/tests/test_class2D.py index 8c9bd35243..bec7348442 100644 --- a/tests/test_class2D.py +++ b/tests/test_class2D.py @@ -40,7 +40,7 @@ def setUp(self): self.src = Simulation( L=self.resolution, n=321, vols=v, dtype=self.dtype, seed=SEED ) - self.src.cache() # Precompute image stack + self.src = self.src.cache() # Precompute image stack # Calculate some projection images self.imgs = self.src.images[:] diff --git a/tests/test_coordinate_source.py b/tests/test_coordinate_source.py index 40dd51fc4e..2a704b069c 100644 --- a/tests/test_coordinate_source.py +++ b/tests/test_coordinate_source.py @@ -393,10 +393,12 @@ def testImages(self): def testCached(self): src = BoxesCoordinateSource(self.files_box) - src_uncached = src[:] - src.cache() + src_uncached = BoxesCoordinateSource(self.files_box) + src_cached = src.cache() self.assertTrue( - np.array_equal(src.images[:].asnumpy(), src_uncached.images[:].asnumpy()) + np.array_equal( + src_cached.images[:].asnumpy(), src_uncached.images[:].asnumpy() + ) ) def testImagesRandomIndices(self): diff --git a/tests/test_load_images.py b/tests/test_load_images.py index 2376cc2bd0..1aabed14e5 100644 --- a/tests/test_load_images.py +++ b/tests/test_load_images.py @@ -199,7 +199,7 @@ def testBadNDArray(self): def testRelionSourceCached(self): src_cached = RelionSource(self.starfile_path, data_folder=self.data_folder) - src_cached.cache() + src_cached = src_cached.cache() self.assertTrue( np.array_equal(src_cached.images[:].asnumpy(), self.src.images[:].asnumpy()) ) diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 0e0e263208..ca01c82edd 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -167,7 +167,7 @@ def testSimulationCached(self): noise_adder=WhiteNoiseAdder(var=1), dtype=self.dtype, ) - sim_cached.cache() + sim_cached = sim_cached.cache() self.assertTrue( np.array_equal(sim_cached.images[:].asnumpy(), self.sim.images[:].asnumpy()) ) From b341de7cbbdcafac3e3acbc65bd322889a8ad641 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 15:27:38 -0400 Subject: [PATCH 418/424] Revert "Remove `as_copy` on `cache` and adds docstring." This reverts commit 1cbcd5dd80106590eafa8a0733fa4bb9bf5d8607. --- src/aspire/source/image.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index c8b412bcbc..53486c1759 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -626,13 +626,8 @@ def _apply_source_filters(self, im_orig, indices): self.filter_indices[indices], ) + @as_copy def cache(self): - """ - Computes all queued pipeline transformations and stores the - generated images in an array. This trades memory for fast - image access, and is useful when images will be repeatedly - queried since it avoids recomputing on-the-fly. - """ logger.info("Caching source images") self._cached_im = self.images[:] self.generation_pipeline.reset() From 010ad332055675bf59a215c5b88529565585aa37 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 15:30:59 -0400 Subject: [PATCH 419/424] Missing cache conversions --- gallery/experiments/experimental_abinitio_pipeline_10073.py | 2 +- src/aspire/source/image.py | 6 ++++++ tests/test_class_src.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index dbe5bba325..fc62da812b 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -77,7 +77,7 @@ logger.info(f"Set the resolution to {img_size} X {img_size}") src.downsample(img_size) -src.cache() +src = src.cache() # %% # Class Averaging diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 53486c1759..02d223859b 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -628,6 +628,12 @@ def _apply_source_filters(self, im_orig, indices): @as_copy def cache(self): + """ + Computes all queued pipeline transformations and stores the + generated images in an array. This trades memory for fast + image access, and is useful when images will be repeatedly + queried since it avoids recomputing on-the-fly. + """ logger.info("Caching source images") self._cached_im = self.images[:] self.generation_pipeline.reset() diff --git a/tests/test_class_src.py b/tests/test_class_src.py index b52b00f21d..faa3460416 100644 --- a/tests/test_class_src.py +++ b/tests/test_class_src.py @@ -111,7 +111,7 @@ def class_sim_fixture(dtype, img_size): L=img_size, n=n, vols=v, offsets=0, amplitudes=1, C=1, angles=true_rots.angles ) # Prefetch all the images - src.cache() + src = src.cache() return src From 2fd092acc1de89396427d1aada21c11eb2c90429 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 14 Apr 2023 15:46:23 -0400 Subject: [PATCH 420/424] Add comments to another iterable check look --- src/aspire/source/image.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 02d223859b..bdc1a33c38 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -488,11 +488,15 @@ def set_metadata(self, metadata_fields, values, indices=None): if indices is None: indices = np.arange(self.n) + # Check if we're an iterable, and in case we're not, broadcast + # the single values into a list `indices` long. try: iter(values) except TypeError: values = [values] * len(indices) else: + # Special case for single `str`, which are iterable, but + # need to be broadcast like a singleton. if isinstance(values, str): values = [values] * len(indices) if len(values) != len(indices): From 8a13bc50e96975ab439d68d64167c5e367107a0b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Fri, 21 Apr 2023 13:28:25 -0400 Subject: [PATCH 421/424] missing assignment in 10073 --- gallery/experiments/experimental_abinitio_pipeline_10073.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10073.py b/gallery/experiments/experimental_abinitio_pipeline_10073.py index fc62da812b..ba49c540d6 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10073.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10073.py @@ -75,7 +75,7 @@ # Downsample the images logger.info(f"Set the resolution to {img_size} X {img_size}") -src.downsample(img_size) +src = src.downsample(img_size) src = src.cache() From a490062f0edbaee13fb339485cd415cc292010dd Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 25 Apr 2023 15:41:35 -0400 Subject: [PATCH 422/424] Avoid future numpy deprecation, generator stack inputs --- src/aspire/source/coordinates.py | 2 +- src/aspire/source/relion.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/source/coordinates.py b/src/aspire/source/coordinates.py index 0ceae26cfa..851923136b 100644 --- a/src/aspire/source/coordinates.py +++ b/src/aspire/source/coordinates.py @@ -366,7 +366,7 @@ def _extract_ctf(self, data_block): # get unique ctfs from the data block # i'th entry of `indices` contains the index of `filter_params` with corresponding CTF params - ctf_data = np.stack(data_block[c] for c in CTF_params).astype(self.dtype).T + ctf_data = np.stack([data_block[c] for c in CTF_params]).astype(self.dtype).T filter_params, indices = np.unique( ctf_data, return_inverse=True, diff --git a/src/aspire/source/relion.py b/src/aspire/source/relion.py index d22576148c..60e41919ab 100644 --- a/src/aspire/source/relion.py +++ b/src/aspire/source/relion.py @@ -107,7 +107,7 @@ def __init__( # If these all exist in the STAR file, we may create CTF filters for the source if set(CTF_params).issubset(metadata.keys()): # partition particles according to unique CTF parameters - ctf_data = np.stack(metadata[k] for k in CTF_params).T + ctf_data = np.stack([metadata[k] for k in CTF_params]).T filter_params, filter_indices = np.unique( ctf_data, return_inverse=True, From 41be4d87cb0f8c265421b0a2be9fcb30ac3c7c10 Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 25 Apr 2023 15:45:50 -0400 Subject: [PATCH 423/424] Use caching to workaround immutable_src+ray memory leak --- gallery/experiments/experimental_abinitio_pipeline_10028.py | 5 +++++ gallery/experiments/experimental_abinitio_pipeline_10081.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/gallery/experiments/experimental_abinitio_pipeline_10028.py b/gallery/experiments/experimental_abinitio_pipeline_10028.py index b842cdbc7c..b0cc52afa7 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10028.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10028.py @@ -98,6 +98,9 @@ # logger.info("Invert the global density contrast") # src = src.invert_contrast() +# Caching is used for speeding up large datasets on high memory machines. +src = src.cache() + # %% # Optional: CWF Denoising # ----------------------- @@ -119,6 +122,8 @@ cwf_denoiser = DenoiserCov2D(src) # Use denoised src for classification classification_src = cwf_denoiser.denoise() + # Cache for speedup. Avoids recomputing. + classification_src = classification_src.cache() # Peek, what do the denoised images look like... if interactive: classification_src.images[:10].show() diff --git a/gallery/experiments/experimental_abinitio_pipeline_10081.py b/gallery/experiments/experimental_abinitio_pipeline_10081.py index c9b79b59cb..a1bc9aca13 100644 --- a/gallery/experiments/experimental_abinitio_pipeline_10081.py +++ b/gallery/experiments/experimental_abinitio_pipeline_10081.py @@ -75,6 +75,9 @@ aiso_noise_estimator = AnisotropicNoiseEstimator(src) src.whiten(aiso_noise_estimator.filter) +# Caching is used for speeding up large datasets on high memory machines. +src = src.cache() + # %% # Class Averaging # ---------------------- From e64b81c5f9cf71b191df53018bd0132785b9653b Mon Sep 17 00:00:00 2001 From: Garrett Wright Date: Tue, 25 Apr 2023 15:57:18 -0400 Subject: [PATCH 424/424] Remove a flaky superfluous windows env for now --- .github/workflows/workflow.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index eb39b95ead..644fe39198 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -64,7 +64,7 @@ jobs: shell: bash -el {0} strategy: matrix: - os: [ubuntu-latest, ubuntu-20.04, macOS-latest, macOS-11, windows-2019] + os: [ubuntu-latest, ubuntu-20.04, macOS-latest, macOS-11] backend: [default, openblas] python-version: ['3.8'] include: @@ -72,6 +72,8 @@ jobs: backend: intel - os: macOS-latest backend: accelerate + - os: windows-2019 + backend: default steps: - uses: actions/checkout@v3

    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 zΝG4-Q{okiKB8QpX4cl#}M+zwA<<-JRbKo*!n^~zSDQlr_VbLDz zDY1eC9~w@GUUM(stlw%wucCRu{mU_R>n`hx*liNkgumey=Gh1n(kHaAQVeMv{2ILpRQ**+@wJLh7buOuZJs6#*UecO+ zP9eMvtEBoq(rC*YZJ6u&!byN@!259e*^B0bm@aa7mBtX9tXy9*cn0O?RT*l9KtLKy ziaT?_s=O4&-+uY7L``lDeP1yxBU@ zI?+NakeTUjd^^e4B#o7~y z?wD>&nJ{dD)#5tIcV$a^OyWRG|q#0fIvOl6dLh4Ke zef@U?f{q-og6T%Du)IpULqeyaXg0-FuMkXPo=;Kf2+$xZd5IF)K3Cyt61}7~7nnd# zW&4tOVb{)}i=%2`mE}X0j(*q~N)3;mL^xI2E9ynsRDiWhQKr8%_nkchGi7aw?{K+t zsH0%BEKQU4!u};dtmchF_J_qS_75Xj_WV<&uH1T9s*cBh{4BL*-afUf){@u@{_ym6 z8+sZRDE+mj^_}%MrfkK2|G;V6gY6c>JW+L*M`7r6bf6v7%l?H8A*Ws19yx_R*tpn3 z?9r3wxks@+nKO~q*l@W4Y#r=3fFrSO#A=nsn(jdXgcPdX6;qa+qq*uy0hmrhlw_X( zq**0k6arWoRmibm+^C|E0uhS@7gkwk1XwHq+K5#o-IslLciV?@d#xTI^=n6Wl19Y3 z!5nsTiG3<%_oDsBJ*N!PqkZhyE*k>?(L>jnLG`@s#A(}!sD2i@Q~%V5Zp>4poeSh= z%@GTv5^c4EnLRd1`rXgZY=q&=kGa(d_K_ICD^>Q3gBci2kf5tQ+3Da3;4RoQWxrP1 zXl?WHcDgsuCJ>mN>*l8;+%@w%_wHHy+_v+urb6v&4O?umj#OpZH@I&xPt!;{%>^_j zz$V*B_RKx+=(Im(-j1&%XCPWft+Ek9Zb*?%ufZpKD*d@qNAeKhXXNj>}Ykorf-F{c+1LUAbER zynchMALNz?a0BkvpK#*!pXwTJ`K8DJhkNmQTBAE#7Kba+{qWk7+KV?IX7$)z6Nlyjy zZAW>JE8E(B1_u{rhZOjua@gQ-f|iKZh2 zi@#`G2q;pup7-nlC?c}oPgMKwdW-GsOXThng)yh<6C$vZ2{OOPaOob;(QpiiY&UVME9n(5$D>Qjp3tmex#xJ zJ9rPLC$iG4WYd_d9MXrQyUaPFi@GBJVIN8*Fs-1#yx;TVgxwnozGJ;XYG#=M6Mtr>_bTKi=BlyjTw9b zScYzai>NY|yo}c{9L4#T;C38YVpd)=Vj~k&UlARfVd>zUwMVJCa`bdD8V`&ChMV%YHKvs|A%=|{5~rmD7^4WD9Gsl0DaTk*3DMN(IlB{< zK^}smEdh_yBg%rcqcL)fw6msu3{(0=6*Z=DApAYbCujEIWa)gGFJVOUqw|+VP@x34 z9BZ6e95`fZ(5#_9hq)#>Mi{73xv(F~i6|$J!9+>1rn(H*-{A?2kyuiOM!83&49O|i zLjV%Tk@J?mkxDS}XqWV1@WNP&0F)`O9<|LWgVsEiVjcaW0+?0h4cQ@#>p|EY7g%#d z*DgyO=Oao_4^Y1O5_U= zmm*t5+E6Z)hcTy=6sL8;xKI=~6bidQB|Uf5EO1&D0T@@{&0)Q%B7zlc;3K_G5a*U}gmL=zTg;Y3zA(jZ0@=VFs!ncOA!1_DN zHF8=vhUnWBoNxzrvv%kjUBsGubg6T$i})PJagB9Q)mIdSS5*_uk-#W>2q3XoAZ5G9 z?LbzSb`@O0<+Z-uD*bp0pq4%Zsa_c6&vINH5 zCrdk7f+_P1IP`UZnwAb&4qdPYNO{tA3#gJlkLLedNmqLWwoF@K#Xf&#hb>Hc?VSfM zq6f2Rz37PiRr5w0BV|mw6B-v|^`y~}lIE(l(fz61SPT&y7Y!ESqkl+%^D!F z;LE2n5$Ob+MW-Sn4fe_ab5YJWzH3AQ>AIwkD7`B;5ip7LzwM;oT%c_7*BUnih6aNJ zN@LU-AUzAEwRizH{?o(9EuOLW*8t_i7h_@OEZBRtT(rxi@*R1;0%3RBj(&>Bg~0&R zPE~ByXm@F#DYzjm3eCxI(xgWF2yhGnUIQ>WrW0#JWFZW@>MBc(#~9E-wR`TSjCA~1sgfGg$JhXO|XC(XHuztgw7Y2tn{6!foL?rc%h*UYe#FG zV@WZ#^LCNSmECWzHW%6WX~0^s518lDxzRc$;4zfFOX(k4^EJ+v#sCHc-f0~xWWP;a zc8pt9?2}ma!Tp^!L#4sm=ksi(2M|!HRseruZ%Wv8-$On2sloxai`7hG?)_c%d-=VTEMLR<-)gNDCc#=w?R8_vL-bE3L*kN~PEY|6LZebIi4^iCi4(nrs2ww`B*l&}sc z{U)kVbDMV5a>f3-(8qWh=ls~JQS80R`SUPZ(b8hgYiFJHU6dU|dhWgz`}OU0fa>A) zR8yJLic)H8(HE zgY}>4XSe*)m0$Dcw*UP)O?0&pZ`pwE(_4S?uWSDx1LbvlG5Epy$LoL9pYCzf8?XQV zjbFX;YW>aizgK>K?N9K7^^e#8Uj6*0H*TNbwZJXf<;J(Jyjp*A{qNO=U4Ih%VEyCu zzw4jA?&!Pei@~+v+CaVXh4t^P|Gj$P^(Vm()<0hVyZ-5~^7(sVpycP}yH#V)hd*g? z#TY({6v8?PhuY{R3M1O@{3RJEYz#Es8XKQdTD19Om%zj1&7L}PRx!4;DTd=f#o$2Y z7fjd&sJ0!`iFWR#9LGG6f$1e`P_v_tlEdVQQ$6i%s z5T(#4vMXm(6@ntmU_B5y)wN7gQAN5R0x`m1z7!tt*}VnrR!Kytll<+YBYDWh?HT(j@>71^0z=Gw|&P~fw4-xva-E7o`lN3N6O(iWl(OHj;} zIOU|_`3TmQ2P_8TaS205j^qLnU2UlGq=3A2XT9A|6$C}R#sN^CoJh4Nk5oHVcy0Ju z_srYF2+0l-;cw+LIX;8^l&3w*IRGm{iAulcT^H&7<3G z_A-twjEhj5RME{<%_fBO9XWOJQ0MdW&{626Ldb<+DwPL=IR*B~0^)IVIFMpmWLNp9 z!ZBwZJ*hTQv#n?a3=wJlNk=86DXG9f#4kj^6VC1$(SGH2 z7gbK!uNO68OoZ6qQ_^_6Kh`oy&)AePXcq<%nSB+9l4zuyW-o@kqU0M(2JHR0T^QnF zlrW9ApVe-#`3{_UfIm60qP&Zd6a%YhnfsLCSyS0-4`z_EhGW@96!06DFf7kd>JX>a z!!@f?KorjB*bo(JFbW^8X|Q*t%{vfCfXm|z8*Q@3OCCJuU_8oE41<0wwogRHnr#Nm zx&uZ17Z`Krp3HH~kK_ZSnm|2nKAK9tJ4T$^2ZK+kW{P47Y&i5#r`-W)@pn{H8MsIa z9l%p!w$cc2*Z}@i5hDN-LV7#`y&_7(T*`#16|54`%Oc16T@?+^;QxM0@EEG>A$Cj- zdJOyXx|~q!i|5O1<|N}3XU{O`Bgh{YGawAdQk8_1{~eEpOF`nKZA~1rB1ALeX@@je z95WcE-x$oWS56e;po(%Hz_DC5Uw2GUun#assQ{`@LsMI975!Q|irBQ*`jo;1XcCIS z8lA-$!zl@%H?Y`GzhDF>Z9~rkz_FTC!LJf2t$#Vw*?D59i1jdViQjkeJ1*N;ckl}w zFced`Y^fRZXwDEZ#;6YCUJ7?k+2+ha9GYSqJ;se`PVOKwUomKdu+ql*;w-y(%05%w zKu*0EaL;dl^3pzN@HnO#`&9^b0yNAqr&TU95+gn{d)lI4ZYW(~dWs4S=u;@-EjqqS zGomdu7bSozJs8JCVBK)(Jy72Tnko{>Cr;$q3DM$QOMgNsJ&M*UP03qGpCurS3@D{B z9J^uFN@&9&#`o}9JP6+N7U5W=;tV3Fy8~fO;0QjMoHl(1#u!W^;JzhvsVZNqtXWzUy`;vd%tk!&q_rRpd!(g|^&fzo zHV{aubSWQWL3$pMIPRf1eY3qVQhEU&xE8fPqbGO|>?6@crW0Y$siM#VsS48P$<3X# z-z2q8OptN3IKI}OXANDM==Dt6dkR{OY!7=3X26Mh(gtzd4S;QxeNkEs9)QnfrK>Pc zU0bmR#cZIeQkDQIJ(#_xi)>wB1o(p)a-ZWqb0R}s13+au-Fn`{~HD+>-+SzE{kGBi}kx5b( zU#csmv@#4X^$Gi)POQGucuJvqlbn2wnTm{ID~*}5BWQrNA1Al~$Qus(!7&=O)pP%G z#YxxAvU1JNjUjCxR zq3t!vT0PeVfFg&SwIr1NPz=P#boO&tBFma5VtjD=B_G$($sDA==n4)*L` zJJB4r z*x$JZ+Q3s7Vml7^!(!}bPYZSZ&;Z~TfEB3mH-$#fLM!KHjzyKhd_ccU;=jwZZyqVY z_h7#~)mCABzl?V4eRy7&ZM<{XG3!+6XFFAuGFXqE8p*X&$BF=`?B@cp)g`$;5Dk`?eN;BMQ%_`UksRH_BY0Rzz6sT*uBblUe&qj zR&RMY`xuKR{lF?p&mf@NLmE|!&P6|xezWJ!E*qg*THEnd*B1#@q zJ~(L$1RDk@n=FQ~W3*v=iJ2@rA}WW)k@lKxWL*b9_Ed%1r{DIfRWkpcpN_CcpQhpz z0G(s6MNitRpZ+X22m|F7DLfjs^z@a-l~?O;uK!*C`0C%_2kRfN|9#EJS6^Oza?=-G zv)?}dYa3|2o!0+e{e1mN@PqY_*Z;16>c%n%G4EgN+N&S(F=WKvqkOPC^A(62#8F6e*Dp|S>CO`nX^Ql+K z)n?;?>(A!5W_!1Fu_=y21%?t2ZSrIv`U4jz9f=_oQ5<4--q}u-lLu*Z1g$A}iPOWPW zi6%M>Dr=|340{Jm1sK=xXx@_fQ6xwk&C_DKhEnK>icnW4l7124MVtRBFVbB{-rJqcBsZ# zI~Ht9L9g~pOC%MdB5H<`rRnw@6@voZFbzsWtZ2iSjgG}3j2B@Eg#mkL<7Fa#IadEO z)bcTurIoVxfo>}$f;I!Sy&8uh7f|O>B9iBh_YnxL# zOSSnM*~l^AqpQpr82LEGGDJrZq3owR$$G?Df^Y%?w04IQC61>O#J-9yD~X=6<6}8? z8AoQm6XOGLOHsQ}jOkPy+X}Q4bZu>I=*Wo5kN$=p5^pnnJ%Mm4Z;X3LuWhr3SWs4v-P&{dhNi*On|hqkvt)Arh_69tSvY+VfF z#EO9Br28xnmOv^3$3w6JF7)P_uO9;x$6hRpC9#J{QBmYhx;D8*RF*)SWR$YiVq&CK z%-F%AZtI=HD115J<+iJwar$1r6`&t5h?wVC58}K;t?qOB7|^=0Mn83Kn++VtQzCM! z5`_fwTx2*bCCq-~ofqsqX#vNa`g6w3sV8zBSdv2elz@C|j{U;}Xjbla@ zLA57Bt%rmWmF^S9nvuI*5!*g%n@Y8|x-=(-7!RZD;cbn!8%ONv@l1QQzQjcy#okfs zUSJ9biDT2^v@ieC4$g!ix)+&8n{Y2nI939u63Uh>nKXo^|c*Kh({#2DaD{iG7x*yoO|;*c(`RiKOB(`)ss zm>$wt;3@aS{MCBKJW{n7M`y=iUjZ0{mG8!oQgoD}DBT!2z|z@>0D!@V#aZAu`W}5N zgWmx)^o{1Qge^4>oJIiG$t&l~s6RzZ7gI(pz0B8xASh_{#R zL#!A-fPyPMN}EUJb3HH@HTI*?2TDh?XaTT!hB2POdho{F4(ns?{@`f@CtFxMiR^EG z(|~PG8nI_PN^KaXKsfzhP&Q%JX(N{9og)PiMjutNj`tNJ>3jmovZ|-4%GB@n z!t*_>Rfslwu2E{zwrakE!!O#UG^Yjj*nO)m(Ujr zwUaYR_QIud^Ibys0S{dB5)mQ?zN8&ovPTb`aTU56{FH5fd7n!c%Pd0d79eZzJXJ~r zc;WPV0gB6NMs0sqj}rrazP`++C|4XtTOZg?HphGkGcbfPDBBqL^=bT z#~dkL8Yj|kajCWhI;2G*CZE<5?Gfqw7wo_7J&RVBM6W~aMQo6NXxw091a<;L`W~PW zdF)76 ziA%3j#VU!=GsyuK7A-d$!P@Cqn+D`e$(XY}xjk0Ln7I5r_5kd=i1JYT$eT|gDhn%- z1D*n?{pN+Oj>+Nfg>mXZC(N4(3yN_ifHVNmvoOqh#JZ?`#@z(=i{^y449$}Ly|#lC zr#|+V^F!G-&`w$(c9a1&MU;lx2Od6SZ$rRW*FO%r`t5-%z;o8+X4VkS7na62WSn2ly-Rg?zDzG=9G zF4F}Xo38T|huD1&v@lKkG{&|Ygw4vby#sS5|q?G;2rr;PP0`ao; z&RYSZjg+E0LtG|gluE8r&~TFsMXf^Z>>t5EJ+`;Zu<0rmr>ifR3H6`Zgg z1iWp#qhC#H7mnt-z@yk{I^R;L&yIiTGkgMoA_ht}3=|E(|HVVc(O7=HFW&sgYu~x{ z`r5NAC%oy6n=d5zmDm5`&7ZvXoolbJJ^R%c=PM8*8kqz@OtO!r|!wsSJ!=c z{XPBFL!*i3nn72dt`D&FZ@RBtJ@Sq320y#@@6}J;ldG?;ec^`V=8oFh{Zh{F&M$a#XIsEAZzX;4gU0gVJj z3H)ua12PcFdQ-POoZfB!G*Du#l#EP8@Juw-3OUrfi+h~!ag@lKkIzIacab#= zb{B~P%&2@|91*zzd-B8vn>~+_kso63d-FL&<*4F;_xmzV#*=^&-A$=B-_AAQ5yVuR z9oW%s_YzfU$H4z-W3>&RBdUqf6^iq#aE2x33u_bc*2ZicLBJ(SkxEcfB~%4EcmPx?wvV~$6~m{Xc>`2`wF+{;{jlT8PXz^?#aO_w4`~{pJ6Ps_PoFt7l0cShoI7=+-^*z8GFO11gEB zXpn~}PQ~F-+n3#IPoq09c!DSwMoe5ak!7lvjL0ccga)7}W7mQ`P!1QC`ByuZZ4;Dm zO@;-NAMZnej&`O$MlO+h&_x?zym>1Cmo@>maleBnXkP#uX`YndG43L^^=Gndv570^ zexo*`st=vlKS$b5D^?Hfnzd`zK0q|j4-4d5}amnt%oX!>-X0$ASvxS~GZU%C0kGTMmJG{_g=~h4!su4N$k>+frT_`8a{?%onx^ru?L-uQIsVc` z2!Z{#0_Y@0nnX?`nZsgNEq3xAede?kV8%&^GDPD6CpD<$!>}FVH*>tld8NzZ+KPU0 z>=BJ09fE66DyE!#QxKMQM$uw{3u1e@Dlc4=tA540r<+$BhXa^M+e)SB1!PJ1I}Ok{ zBW1?Ady;IrffO1!2|27ug^N~_J8Ct6cnMTJyGR9{5ytmU^mg9P8a&I|{`rm~_J|C` z5-087qXRPx;Qd(hCYu_KLd2ZoLah(w^u=)#c9=>@-GDIPuB)+$CgvS!cUeU~S8+#a z8nbAIs9yr@+`B*m-K*}Om(F1Zz>6+n4a1pc`L}pVmljjS#RuTfaw*N}6UC7lvjKKn zBw7lyRIO1{`AH!f7@xiCUYH|mtTh5I#OhIiK^iMg8w3$gPZmGohjQ4(#5}mA z!wMN|FApHN-j?FVkyr;2tceRS%$65$+;Ln5N(RfL1%a}~eh$MPp)#=1Wr(^{@oVXd zz^OM9(Nox20V3ZE{N4l8NP@o}A_*!(EFGL>fY^Bg01NE^%j#45R&zz*tAyF}E2iua zKvqBF`}k!TBRGh1JYDHv#+}o?qTiJ^BmEEEy8s7ON76V|D%Pgm1NIhyVWh{s*k9<% zz$#T{q8W{(ZW@7<1qC$}a8Zfw+)Y0F{HA)?UtBjg{3lJNq-&+R>S5BA5>UD-b|34% zz#a+67VIJgB)8idD2v?MmJX|lb%*h@#QdJ-nw^#n-&MsprDWx@-tWljrG#~YU98J? z`cxj)HV^5n0n&4qVIpY%ao`hkDk_}{EqgE7Ap#cTtn=S)*<>TIAXof2+;W7btS4Q#CGlIvi*SW<13We?ku+6h9nCp@I!mKv@Pr|8BOZ@d-qW8J$=kh z!>0S@i@R;+BJIIiAmBR|T^pqbK9to(8nM|KfPk+k$W+UBu&XiwpsRC+tig?n_Wsb_cBYGkIHRp{fUBYCU99STM6zuEfB2_xm3y9Hf0rQ!){GzMW1E1lesX! z^>SNcTE%ar+GC#2h6WHM*ILS)Rsce4o6@<`_6O`w(RRzm&iF5_6;^*F%>`t%j_D!L zF^2h^TQKEt!_f)Sk%mb-?PabJ%n++~!~Q`lPnxiwzg%VhOSBK`bkgqSAiIfmK8aNE zPj0?Iv#r@*)b6ywUxNBDCsb&`D>fyUrNvu7o2x9w?-O?|+pfwk1g)t;1;cd>mZ8#R zHHS2BG^R5)&)F>C>{KoDi81H^bFO=FY1&Rc`WfC>XP~fC^;7rc>Z|L%vi_d_>T%_yx6gmIDb^c(y`_TfwEk-SZ}8pM{=5F^ z6%pC>m+OCT{JI+?+SKT3ro#GpUC*!gM{pF}`uEo#?bhE|e|P=w^}paWG6HU&|91P| z9)DNoUteE8BC@%Ri4c3kfBiT{6UGdAx9i4JD4gYtJ$~DeJ7`^q_4Og}7wBd#;VCM$ zC3yqYMlzM*{wU7&ve*hZAUdxU z<>N_v$)_^TI7azk9Rj%n7$6uDDqkqaLlrb6T$V}hcPGm4qZf%@VE7qPSka=DDkSGS z48=1YD}fT93($9ceF(CR1T88#MRG>MIIgbCMezY$Bt9mGSPqgLPA!s(mP9&1M2xB~ zqI!KYurLDCF)~OQilO3*qKK8L!?q=U(&|Z>df|M9O`i{*OElBOjW~$tAE*SgN;QhD zv$jMey&GeKd|*X@a}UheyDKiioLQ#b!z~9!2*DovR_jJZgn*;(?BG&26}Lr_U7GOe&Casgok@L1nnb+fhw%1{GuKLIS%Y?|DWu=2b^SSb>(@}Mx^)NdskWBo33^=-qUK( z$bp0gBtRnv11&&}>jv7psxI%VGPBZq?`>pcq?!Nuq9QVjZk4pN zv%lZ&P6c$u%ZL}xeD5>&o_p@OL~{X4`g2?q^pUzI^a;%A27Iok+M3SiP}-QCb$pHy zOZQfE-BqvP(&F>hp!_f3{6yP$Cs9T~Bso9QtWewQcLFM;qjF$hHa+|yn?|$RK2#=!Ok}!%tSbxR#!BqNJ#8$ujt(`7kzUaXPdmSRS zPoZ}(_#9MUjL0a)RwNFWK!)tPMLSg5>jY#wNb5?bip&ux;1ZX7akSVvda2Sx`qc6$ zPP0G-{x4QT49pQZ&+11W(j>eIMBJj+tOO8Z2Y}4g>1?}ruGoQW`;YY4FBJ9L4`!0= z8&7Y?8RwY+o@wosfhS#~9N0NosS8#CW9bpfET=`s+TV^O*^{Sh5vyfP9yq5VV4pg9 z&C{zSbwrJu6L9S zpgEFZo#)b>nD2e}b=Ypo)P4cLY7+5AF~jl>EK(87XT4qN7{H7%JtIXT8M_h+bftyq zJ33Je%sC0a%7K)#B=O-!ImIv-nkZd9hjR|mhBQS|abTBaR3gfV#zmFaYa?h!{7GBA zt&n#1meN-4KVXj!m0QnHI^~B0mQML-r6n!n$g7>_rNh)om5L=C3&eweZO3I&?jr1O zuk5k0S2)iC7kUdk5vY_}?sxOS_-K+Xw2_{r2)%4_c5_j5CLuXH63lfGQRYPTMa>1$ zWOD34Zc}rWQiB{gf}|~SG&uC!H5cf4sq&G= zpPYgX(NOq}KpzMGz;w_{sX0oSStZ4Db&h8X&{ME&7Jz)n3GlXxO@Z_28%B^4Cnvqm zXTP}XBET*hP2(wcy06rB77W_2ps&E0ZHzRTPNLCOibk^_{BSzPPSD?zyv$dKe_rXL z;#t3!XS8S^;W%kr`(MJ+>SVo%22hKmzb~Nu;n*+g%K`-*Ly4mclxO_w*4G%{0*3@( zdC}uZ1dOQ9A0x$O(V8c-tg|PJIhd3djwjaBGSqrCm>4OE@qU|{Buxx02!Yjc0Ce$y zMG!Ly002M$Nkldl#`6}EaO1i zYC~yAsJwD9_dW{P)jgSRQCGe!308LBmO7le8Gwqc& zPWfXUPg-z}0im1HSftHV_BNGNwhBOv^f;gOv%a_Aq&i?5Cs$v`R!G^qW*YQVVr+|Ji!b$BDcrL? z+Hpm;Q-0x^zfqm6$OB81j5baD)_%%krPOU+emv_pm;qLIsU&Xom?$83<@Gv0XZ?oj zI3b5dPn->qhXJ$e^-1_>1hW~f7?rDbo?^XJ@f{womhIE-@#MqrCAWoiO-H}qo7{M`}#SpwjQ{TQh6} zBlG$dw$Dd{H(UP+@HAyBZX;scB)_grOhjjYERf#A)0%D7vX0;})!wRNT5E5ABKi?{ zf5kg4=dz!=qTAVj=4^9pZQpJw8NEI0#XfEL1Ph{cJAS~Nw)uPpG=4ZjeD8-v;vfYd zuiVc0wDE}X1q-zeO8(lt86oyS^~c^|bK9Bo`5Tr`JV3f-Leb^;>kyR>t``xE&+B+5 z@IE+)x{r2MhrYwr@@Zlnb_3_R27gVs(>6HMUn}YpIz#A<_Ql*J z_BV*nF8CS6am(a%Pb*(Q>=dOtEC6rgzxRG(&Vjd3uB$rNo65PI{QJC_NTDb%BN@O)zX8U^=ev*MTmH^l)=TW@k~Tyfa=dHfH z1W=$JS@!(kdjWn74Ehk%?hsStn~PUd>F}QPwXiJ^j z0L3FzGxB!DVjL*W5VHKBQMMW-T)@sxF?=foa1 z<8*ZyyfgCSh|_~K+F@JuDrIjRN#Yqvj13xmaebUrQ)06tClx0DtquX_>Yb7qvzWas z@qIFBN^Zna+E7`A$Oc(F^=QK8*kGzvld-Z6CH~9cssbKd6b+3uak{greSOeBMa>J1 z*EY1j^lFcei0p_^e!Ne6pdMS9s^X2vE>7cEW05_czjMi+p7FI!l%1t{%<9@2NEKRK9%RccpkdN}eXa)re3O_eA z_A;hAeR`duxF+AGGyDji62H}>5d@11_rjllxqTJ02_`Q3#9`PR;cAj@dUg;zY?12T z;dUuW^18&pxUxT1K2i2(7@>S_=IVRpfRLPtKo~yl1zR5{;e=mL3PrvGNOE$?o-Pfi z;2cO{5^X#ysYv@kf7L7=d#WN7H}aJ=p15P8*?)H&7@zK<5%SQCcQNtT1&zm(DJc!+ zW@hSactd%`q_kARmz=4l!%b=k6c?p*x^{Ed^)$Rc=ZW*xX*FyLAQ&{m+a@8LUUAr( z)c9s*pbzCixWo~gh$nf6lo5=KeHt2BE_1E4THZLbPSK||{`7MLLX1)|6F8f`9!3gq z-ss#&xO7>DqVrEWK2Wn?ulg+pZR$ttJc#ojURqvAGuNR4e46sKMs9Q)=c(M@*rS%0 z&(eJ2?PG#Lp`x2MCseP=^dghbkR%x2{w9^3{vlYdnRC=WgFV7O-GC7I_kCHNry;*r zH?eAjsceAOXdzXxIW&gLjt#G3)uA9p=G!lW{D1GS$!1die#0hf2z8|rR{k(Dj(pAh z2Mc@9ZB+^ly}5pPm1GGHARG#bau6LC0}948i%XuyYZ5^LDsp6P%G@RS6x`6@{AgtS z8vQwiklsR|7^%e}vz-5ZxtRl_78S_ztJLz1xnN4G%=li0Zcv=%$lrFCrxb#${@UZD zl8(Ky#5k8y6*EKF#gdV5C&@x53|XcIC&MRSd0$B4B>ugI^o&YZ$ebyUV#n6bVh#8_kuAqjb>pF`&R!D z!K#L1m-2_Fuv-R*vc+{cAuSQ9Dr9Du_^s~byblN(z5o7u90IAB(JJ^4H$7+fpJr6L zm-4#=H?pSKiud7@y4n+oD<%rt{yz;;WeLTC2hEjd-N;Wx^;b9V%cVN>?u?Ff1>^8= zqj_izm#@?Wf@>6}Eme)2NLg{1xU`Z=RIB0kd1hr(HCI}|rWH;BpLLc%bk1}kN@xPD zPKSuI{)mt!en8$Vn^hXzX!06sM!>_`FT?s|=0fR@dln&(kqBJqi;Qg#6ulQ_oQBU= z@Q2K{0+*FvQ}M`#k_F)D&#$RW)9PD&Xn=H;Rv*YE>7{%ak^r$SnK?ecAA<+pW?1gz>dbzq;hVz@p&;VY5QduDfds1mR$ES z3RvHOz-Ba}ldQE__xFF6phpKTG;DVmnwm=L(bZTEK3$*Jhc&=m9{7d|+%lK}03i}d zvI_d7eenyJj~HhZeV!rrNI;A+q>Vp-uVDQgF8c=d(KYiQseSFxVH{sFaHU0d1Luhk z@MU=W>6lH@fzxHpRua)Ns`;M1?rVSMG+$RwTQjeDTNDIe`CGYAefyAz}- zFpS+%Q$Bgmq=Hr-4Enk4b*t1f=rR~*ec>yngj#T!(D^;LB-Yp4uW2x{=$eL*ghP3M zd2ljmRgpz zLiJdBcy*BT3+NZHdt+pUHp7SyP4IxCJcqJ+>1Rr0v#D>U@ln6KgzAI%YHTjL; zI!<%lg_G~G8*@+m^9MZu5V*-O#@brJPB>ygZ<@zRzsTb&aGFAuvjcD_dQ#0M^9r|J zP+#Ugg@v*ACjiAS2`2etEIA#4B|}(YdnAoNhkl1Q*rd_29z-8b?yD}%NFX|aj{3&p zWXotAJv@7tzoiRt=~~;T((XzRiH$VloOJo5+Iijr6toat!EL_DmXS2k2OJEK$jp;)@68DN2 z2+-o|hltn(f2~aZv%lF@Mg2O2>LCw|W6Of~=}w)fR9Q-R0w{X@wP_*OptZ$K#ETb{8rD6r!hCBm4|A>eLSG?f zMdCg1Lond58>N>~8@?}R5uLU=lQ%`x!F475csR(KO$ZQNg|r}WQpPfNOxGb09p*`r zs*=ub+CFnFf{;ABWmN`wac(KTDa|?H z1@fVyZoDD!m3!~i_i7QKm`Q*c!?e5!bC8JuW#@u-$GDr$sCAi#wkzPX9K}h=s(J*1 zw%0|BQ-cEbi$C9<5r1f-k9{X<`z1?p3ZebufIk`#J$9}rSZC53egecbd=T=qYeX0B z0`rAlrbc#xX%|#Ue4P3Ga_Sj62~PE@@*iF;I$K*Xng5$%Y*tyef!McLaHDIawoB{b z_#LWf$3PX-Xvx6jk7Ehf2cSiRZmjlV9CUa%X|JI09*I=%#6oPQ1xetXTaVWjkI*FA zb!cHDna5$Cmy*gbc;-k~kD>9kqOHLp`HXRf*VQVSZ;Y=XF67SrAUBXKM=od1IGLlu zw-*~~$^8^oSA$%3F^A@*^S)A5J!6{Z2DwD5JLoQ}s*nFCfCM3*0|w|~QYaivFFNwW zT*Bowt_|OY%Z{o`ZCn_w{#-wE+lDL7Ozdt|L(LNdeR&on zmQlk=3xAqMQc;PolfhG(!KFxTkm@|Y%qz_EG+rDiNcd`L1Dl?_`e$TP(>!BZP|=8u z06KRstTI`t8c>WKHD}?Y2q{q?dM=yCaoYg@?E75GWuk}gwxDEkqsPevQ*T?QM|j?y zOe#m-`r}zMnA3R&DvEYM)>DZ^qIwgLfX6gg{1BQ^R+)HC&NT(fE-m-imb-mNFlVh}Vx3opTF z2|EbVLQ=ij1!&Z4y&jE%{OelwVstjwha2airi<`v*qb!P-s$a-EPqL~LjV|%ya$Lc zs5hoC0Qqt$f|y&DcI^G=QNhBd_AIz>}nEUqblhmoe{hjzd1dL|d4T33#~! ziTO}9(m9$9{$~+{A|k)xpV$?9z|dUHR(!4A1qSXYsbW~9KT+l|8+}fzi>gl3HGu4f z$o2u__td?86;(7+_kl+mc!LdViMKt+#Q(JuAXU9OqX~$MfW~#o5Gh*psvrkvqp|CF zTo!yBmUjap`kjemPNLoX@~}5R0#Hw*$w7a5JiKj&1zr7fr^ClbTvz^;mQE0{U8RJ( zJFwoDSgoG`k~gIq?7HwJ8ATuit4+VtS|@9fKwsdb)Ul+=K>>U#_9#2W<)?z_prd6X zvP;Obcg6EyI_6qK00N1dUB<$ZbY(e}19Bj`e{`o)=@0|n<^O((V*sr{b z08X?yqxMv+73JjR6vb$5o{U6|2pL*dS(_!%!leQ087dVow7@JX@f{Ad>c%JAoajlb zF6X)H$c73-a`H`MtlUPU#8{*8+Y^O{e%{AYQbKLue4C_Z39b0Kt z^O9#$KKdd*&PDpL0X0gGIP2-xSc@%1J+4f$5qgFh^@(4nr0xEt53i6FH*-E)_fDjD zN;kE7x!aEc=M@C|U-PS$dL2JL`?KeoZZc~3b~1bC75@Q9Z^Z{pUNGob(dmsR;OOA$ z?{SJ>>m}|EvSZsv%cuMEdw8ZCL zq9Oqcd(oNm(w3=rr-Q%vX(2)uH%9A7lcxw{`mTTPo1R!`mNsl;&)C?MeOlvcvOAEr zYLk{ch^zL9yBiU~PX?0`X5-?j!~Y)L;F478S1da!zRu+Rk)9bane3*WFgqdjO|JUc zBAU4`wT%E-HM%qe9Y?tU`#*_~bCU7Lb9Vnjt{ zss*rN)cQr1SW-VS!qcqRDDYY%G0MaZiyF5te=ygwnhkubVA6A9G> zO6w3?NQ~3uUW%y=;-hb2ULsD#ll74+OMuWCYnQIIu!;NYcEXRJA1#NQ+3?!M$02?v zsHRo}&<6}8c$vR}sGX6>9uf`nX`C~vx#cpzW6 z$QPl@@`irSG)iJtbr#*eqy@Cv&HfDsqIKY-DuMc{K#cL>8b=MWm|XmytwO=KVuRG| z;E||9iIoKuc;R3w;f%owZLEgOU&9(1+pcYc{#mCkCRxkw$dUr6 zy6hmmu4+N$9VLEKv(2X@A@jO6|XFwwLxH;B;}(6 zmN}xwtR5AayHkB5d?{AE9x`*PX7D|g z)DDu``B)3%8?!h0tqM`xsjCewa7O;eIVX$yvUdIczB;nQ5TcjwEuN`i!H#Sz!qlRo z8x5jj9P3`|o9za;yDnt}vhpdlNTRtr&f%gU+*SQNbVy@gzomWKl51lw@9kooIeWuu1*fXWBI*r8CXS__`4hoq6QMt=&9}vn3dJw2# zcmZdM9c#*FXc4S_e1Lw^tk9l%$BcE67ylIEnWuaF!7K{fhCF^GE1Lz$JB8Sl-w`^I zcuMvw9R!6p%^@SqyeTIrbT52wm%_0N9eo*?UWA3cp|ThHeag8V@3!gf-wgp96mxCL z*{%cxaEdllC%Q47bIlP5qw^T`)YEa{!R?GaKV^g^^5gK2b9czxNEMUEiXe!{5{q(V6+`iZoZ&q|~ zF<|#g%8+Z;Y0%-IEFgYD>5%JCpHyxNt!+XIQU|AW4i2xd+qnDOyKwQ0>x>X#Zb{B` z=T&K>$&D*X5L4JnSXYDv0E@2jICdx|-8neg|^=VgPhz(*d zNi-QP@#Y77-P2nFc(^f{(XYb$y3MbvV;aHjk6@0O{}%zR>S7YD(a5<GfTw73(5`wv0G0Z@SN%KGNQ0ldkm{{K6ik?vIk}1u1nA zhr!}lgkK{xP)?cFaMl+i?NkL{Y-iZrAx)#R$_Z~vFD|7K4V^u>y(w1g;_0?&a#ZUJ znc9t+AMJ{Z{Xt|k!r{aDc#sfTiS0UexQD(Z3k38Y^66pit(Anvgr#h~F0kFvniqvX z6q{3dM)V2%JR%;A-(f+l=VdGs?^!Pn=`a4+QVREN3+>QA1a{0{IEnORxvngdW^1i^ zz8e@aI%x*KAH(zN?Lg`C+zVpA1gBzNW75Pz)k?ImF7$tf>_$XJvNZZ=^?vBeFzHIy zVJiN&=11IO0zn%L$iyr%{Gopgq1W9w2;&0C|H|! z3nNar;i3wxR^3tns6+!{N0imkPo*$}qlq#}O|}QiH7$gg4!tD+ITMO(%hH$H;LeFP z+&3|%;zNF?ylFvTxD_(oXJw$B&s78>9;>g`lp}^0376Lj_t*SOk$RWO91LELT}`vj zB}8C}>GqF8sK%{Ph-Hbh(hKf}R|>1yf{#BSxt6Ge1>1+hMnJF-5o3KO(A#yj%{b6} zD#V5lQqzDxvW+Q^!k#U9L_^4;*>9R3_XNLQylWk!^MosUcENz{DKjY<7xab8^&QPO z1IrA?!Wcq=jnu2W;L%dNQAoJ=C_(x~uTg6L^cQ5k`Z=(S7#cviAjR=yPLq5rzvi!6 z({ZlEq+cceV2Gl?cM56#pLRw@&m~}1qA!=~1cSx}aqqu+YnF)?0p_2Mw3kg*QgWAf z^e@nezxdZ25oQ1S*q}iK0;|hAA>khi`Jlg(kbOv-KC?mNtUw#y1f;@_bmT_WtTQEU zG(`Q>IK|bxT&$f-onfAzB!PsEH@5Sj%2PUMcyzPec=vKp-!&l|PU>5sL(mp`spBY# z-o&qPFz|x7K}#tIh%o^&*ZMjntA}M*^-BkGM}XR*4N4EX5W>kh@(i|bboSR*K^$@# z7|oKDMODfgOlb?ebD5G8l(S(k>c}fqSupJDb&0w>|FndQ;Ki7Nx6u>{o#7rCtJ0?} zvL7HoA|w%kt?kEJE)hiu<-}9B1CYvPkWo;s-G*f^tu~3Z2)K@TsFx>ie;n?PVtSuV zSvTf|HTQQiOz}6S#D^o$foOskmtM0BuMWG$uX2`%U|zH^=(Vw>r4L)vnctc?AY%< z(7_9%b_4mUwY_z7{&e^D%1Q1%ajSomJ71UB zFN(k5>}Q1Q_pgjop>SAP6P15$kc%|C_QaZIYHWC7cwF}eMT*}M2aBo}2F8!B;l6P` zqL{VSmwAN~jqYF4S?QmZLtmL2!@gR>kh4^=Dc7PWclVBx%zzvJ6HW>^2abzEJHSv# z!!xFS0!~G~?{T^h4LA1&v=^MMBr%oI(UYvlzEEWs$6u9G(WIQ`swkuFs+~)qyvQ}ND8KQXOBTT zY&h#e$|W9o5%AO_!R%-&JS}|;Hd$Sc&m6XU&$}d(t@Pa?px+gDtr*-(^dD;#AKSN$ zDUniNSite!uCCR2LyZX!ucQ{z0r)=Qa-YRuzpf{06%1q=??2C06yWh4MDqIZKG2+D ztZdTlU2xIMBkRg6fCriVCyFW}q{<4rYCZE+RQNiP$qtLocf=u#M>*KUV z!f@6SrPjW(kqc&L>Pk9L_^(#i&kC>4?upzGn@r8krU=|&g-3(X{KYxJd)I|_{ii8x z{P>fkH(=jaLm4meBnjf0uDj1rg?62Suj5k8t5A6c;-hVdr$XWV!g|GhVDmwlLR=&^P~g8)4NOt? zPzU6At!5Dn;9{7EQA1%w9rl@{U(#lBp9%l-Blkf&Q%+UWF{6Oi2!h{yvSm>%Quys$ zt@+e<0n0bk3D*=i9h|*bVJwYo8{HClD=T8r(qHGVD7{8FA33M9i%xlo01;v_GZD!~ zBDMO+f335)aWtF`%hS6Ae4jc6+xoJw(b=t<&2x&jPrI3f_DAIMkb=00#_l*_Mv~Ms zB5t5Q?h(#DDM$t?1nv}w9^ZDaPLzBetPo7?ju?zH9STj!^l9}bW=3IzfG5)!%p--` z2OW2a!+fSuYNh^z{q*70DAVg!FWF#T0R%>aawar6I-pGJ$b;WVU!A3kg$XD7Te54jFif z8Bu-+wlo^trzMX;$@-kw) zmpT;UIx%F7w3ln4}}8vyERu$w^4}44FMFZ z!bXJllCyV^-4Flrv57^LofP#{3$zt`QNA5$$ll%>AY21Z?Y_vWaye` z>k8Q;eX#~rQnjwZV_oMnsGp&45l=M$=Q5@kta)|Y?kccpU}Khzk#xev{0$jyd3t~9 z(6@l`=t`;|1}+O=DMO}TZtsmy*+1*yYC+_BIzOVwm%{mTHc{|qU=f;Md-dU)d+$%& zQPW`bR0zVnD&T__KRV>WjRh;Nsmc9-VSM9DrfXP1i&ZWeb{<+0WUmNLuoOk9< z0^yV#)CvBKDQ4grmbc?O&C!uJH=pdsCQEK%CE9Y(Jmh5I-%pq~c3$Rm_;Z(1&OvV% z#JIongq!w%vCN31HuXd@`!VkG5hzEZPVR@6D|mpAGEG^yAkU5%-1Bls}6ouwTgK}9*S5VTE}9G znsK~A(u{z=(1Eb8M#fpTaD8OCxFu0KhdCxbZF!i1I;zY%WW4FCpEq|7g#HNq>hn++(^wEqFP`aQB2nKswXwGmh;!zC#U7*vQb^Ot0HFx=DwT# zv`YfnJuRZqn(QT0JM%AH3*E%d&8sHhlAG@h!^T%E8gm2@_kfPSBZ-l^;*4knd6oj_ z+Yef^^>-k_efV8ucG?i0G)1U?d!Jh@OYtj28OB+k})crg5g;18K>K7!h`d^2z6V zUQrHY*kYAgMz=h~VG?S2(O3?5~_RSF%wFUl||#H_?m^(SD3&czhPVU3~N0;Ce7P``}RY z+LZE~4QT!N`da*I(cJf%#1$M6&AxImruxg{mhkgC9fH(XtT@pR&((sL2ysPBk>yia z*9fLTXAxz`z_JHZxWa_7+{xn=H!?nStMaE$XCEJ#OoPnHCM%;V)&Rn{I%fI}Y8)|E z*s=oU#v`NJDA8F4o^iO)w!yg?Txqy`r?KWQ8uyW)aL@QvwLP15gylPE`y!Yb-x&a@!-C&PrXJ@nXI^n6QD37UkiO zY>{&nwn1Y213XNLAb4XG=f3a0XLQ*FVRCMyUp8^L5#K7idXndPqS?i%sd;%^iPrx> zgA{g}(WV3b9mz|ajsK^_uKQa&ds$h3g`D*PB`}cGH=_@t;o+k;No)chQZQ4d1wN?` zI=K{2(I5NJ$ze#SKu<#ImP$<#Y`Wu#uxX_VK*IR1u zGw_{`0iDaL+s8|kc>vL9=ec^M`C+^q+4}zbGh5?dZ4U`9!pOzS!V0*BGIm(IWP3B* zU{GNZBI4D7mzsM_8dkI?9rX7Ky7|?E2HMP=nAUza@ky3E4TDmT>$G7X&2N8$Katv$ zjabbcCnfB@Nsi#DjLf<(=}mfi#YOQWMVlmh&q%&&F1tckf$1(}Vb-4}j@ zhex^p=MfjJji1DtfEa0Y={{_rzOK{aMpVi9MIaxLGZLCFI+bXPXp{)mr)870FEMc4Mx)Kp;#CN)9Ju@-z+4yKVqn`MrBxt!q zLk;l>LfnGQd5iJy!6pC9@4k+KHl9wFY+EjeBXXw*imBqt09ba^X? z+2n=8RWm`*rW=SNmW`3T!ackea`OJVZJ$*5v~H&G$myT+Ppe_i*bQ$!shAjhIf^7= zga1Zhle>VyNVb9)^YO=!&@rlXH!^Sud^WrAv;02=Y162V-TE15xqKrb?Zwb5*$xBW zcHo)73@*TtJh^P>T_;_|Jjaz4tt!U3f#XVISqf`<$#3*@l*z8vNK?wAWHI* znihqmFVLtwy4>wfp5>9EU<8D2`^$1dA$^EUbHMV^z(8&9X$A`}^S!~S+wRkJCREtA#kbF? z>4+Ietsn!LY=;W)1Pu^Ld*9*@%UR@g!S%q>MINNYOmg_bl;luUtlMZ&s&Q@+H zSgD(NXyp}QbV=piMA;&=SW~foE-I)gg$0@?tWcO?5FrfTa1*NAxp0+pe$j46u2 zMvdTPC{948Epo}sQ-{c#m@EQRbw8?Zi;(TkdDf`Vcj6Y9tRtiDrbXBsIB4^koxXC{ zSLrFVc%8_)dwftVkuI9Bzs)e^hvl$$7@M)8w@&RhB73|#PYiDDzVTPpMMXkh5qx*N zXqGxwb`WI`F`2|jwP!SGOaO!-B$I6HjJC>aG>7}?20LVB#a4+IOG?t%gMMyz>uach z_}3ZgUoa-_bK6f+y4{MNDs^O@;PP;n>*}*s_MG8}9aX{($e<|a|2TC_F>Krb20e^d zQV=1mMEpP@ghU5Gjcu27mxf_w@a(W;^_EDwc~jcVb4nY>aP?qSBSTmXM&`eI5BVdK zCmn;&@aFiRtS%H0?y<9&JXp_UBkDx1Y8=J3YTZfN4zx`nw$RK+zygT>O0{(Vc^Uqj z!_8dOO57jKa&=g+-p&J^CN?N=wQ}X$ra!3JNS9J$wK?8o%_>k#7RO(HWkQr?sx8-u zz^I&4#%|a17OIdD89d^d0sbV5~e%2b14|! zSn~Bg+lP45UMJ%%2eMSsKQ%`n+h$JG?d(kD-E<0XtFGi=2+x|Vm4P2PAhxr_qD$RD z$Ly8X7w>~nnweadYG?32Ob?4_{^~D(k2rmGaB;5E8n2W6i8q^g=8u92p8(dV_ZtAs zaTyUYG=oYZiUG>%V21m+T^CjjEV99RHcAF!1SfK1!IV#2DURL-a{tV2M{H}@9}8BB z{RBQTRcVCmFst}5wxbb-P&pC!X+}Ku_%cx|3mH<6A|Q#wl54&CSTgAxl(KvFa^S9z zksKA-NT;??mB`Kr{M$GjBUBaB#;)P6jh~P?(Ou_XgCazdPSiBQx>Az^P(sb|3XUDU z5Ci}-F~!!Yg(fgq`!f+$+c?sHumcAw&1jB!@{vKu))q7kt0sq)YQmDe*4;eJ3|a}PSWcz9%nPu;I_LT zvGdiABoN`1^chQznWb9vVBmsND5QJXmnlx=x%gsYbVnZLVcn?raPp_LxVfWEURhL# zQ3gc2<*FFZSHM~E&buS{f1u^_n)v4GOjEmFrBffg``QO#^UQo}nbSnpHz?S`2=SQsb>aLLW@UZX)a+8nHi^{}y z3HQ7}?gXi!6|xECA8Zjp2T_zZ*pW^C_=phA=m6v%N>w-igFS3`8kMK%#WPxc^Pg7x z>f*xRyYH@DFHWln5Pz;xN)D7`(I{6sUlUmtyd&WKtPk(~5%b^1@+6P5p@Ekwj=f|4 zIv>cCp+r#ypL*c)?|~tq7djHs;6}1j%rO3FpqndvR64n;B6n%N_6yC6nPO$i5nMGf|>$wyP5E>@arvb4w@OOXN?*U1RdlTWaM~$Bg0uJH z{10WS@M99inEWiw>sG|*BA4h9FajPS9?x$2SUs-dO4@ZOvj0`?nWY^`EWXpE(*m^H zu&HrL+#6Ad8K;=d=s)+hhIcSz*D1XMQhAQqj4H{Ki2B;AObGkd06_LSPgjlv5~Yu1 zl8mh}8c1^i96D*LL;gbd=)WVAD){I=(>Ti#Y{Fkc!!*8of0&nh;soM?jwa^)i)%L= z3;}p}@K3b(9u;#5aUmm!1Ht%b*76iUZvMDPMx@bw0*O>D#xJ^f(`H+7))|m$oQoC% zKDxfdB7a@N+M;FnlLB%A0rP-kKFvjjg*w86$`@{Ud_wC%!K3Vdo0(kJ8n>=b>@((S zK7xIRk=?B)yz5-^PiVWFV4wuPc{vtf* z85%*T1D;lFQNWfOg$vF3N4*80Mtr>Zxxh`J7{yYh+y;RdsG}NVnqZ8-6wONvc|P!c zny%5Vzd{C_L7rXk=-ndcEdO6edu2oJWJF!1y1n*UP{;HahDpLuKc~w1^6xDCHw{@H zTnJg<=-Rah)<($aJ&|#i7MB%f-EE5QxzbcX*zT6&S8?0!h|^iED+yZJ3fZ~XDQpzF z52$|N^4$23j1)+2R8Hbv%$tFxmQ39j)e6=xw6(i-STISzObH;(zpQ0~jgR@{uyB|m zZSanlJK+Ll#d=ZJ&NM;ec3d#cLboaJF$X8BrU&-ZsrzfJ zS3O@B%L3j)G)JmJysj-nrCfkxKUt#s?^YOizro=gA~pvbOXmPoS&dihkmRlx=x zXC`$E?5dmIgCFD|8iCLqo~5h7hs8b`-g?UXY2_3&6EMW6C#Y+#+v=lU5i;p1ljZ_G zo@SW$^|>yy(NHJ-Si%2#tWVa~>50C0V?;8grRZ9ncL8)xFB$O?w*)B%=X{}GL<7VLk{zFnI$c836k$JQ$q|3x(87_jVVDM(k zV7|v#IDUm;o-8*!)rsuKPDI_C+{%L2Q(5ky_CBz0ms*90gQB28y&)&bq@*|N_b<3C z0ZXz=B3A_Dz0OW%A9J$({?2^=S+PKpZnVV2ItROOUmiH`7D#8Nv>R*yHK}Y9{YUK2 zEaDVVPwlFI_piC);G6>4)!ja)%)*Ssw2BK;r@35L4@O;!o#5@u>H3I9fHky(n}I20 zMN|FczbO^-o&95x$7qC{6R}Nih43i@HmsiktrZl>txMQ*%S|>=h-pSo%s!Qq z2_0Lso7Y?yt;-(f&YJ?8$tq_edAdTZxGqa9c?Ii}Eda^{I*qWH~B$gjlD=-gM_4C>XB06ll z;oz6LdO4AuK+~xmT*jlV?xHR_%`~+u>r*_xVXLI(r@q2S&YD?AE*9;V6&tByCZuSg z6*?FTeHJIK&ri>#0Kz(lBZ@3+KMhT#5(2pNQ`5i~K(6;eMZ< zblj0@HxNq%dH4w*$H-_DUt52!W~DTk4TBsmTJi%e-YQ!ILL^h|6|r+5?Oaj5o$Tuo zz8-QIwO)%5-iSS`f&Dg9-}iBhch;8p>s~tSM}PFb#m{mDeB0^pi|su9>RtF0ft>*V zz;jq5-G&F_`0l=5uLy|wzzA*n5@qV?J$r1nejRBTg$crgTOOzKXGP!a&Usoni4>mks$WV>3=WJDn6gRk9=%UH9@PaXvnvz1I&8L< zN}P4HVzJ)$UOfU1gkp8)xGUIY81ruvw^&i4ec`zATSUR=m25P2DQ?&{B~y>aaSI&c z*=PtFvcH<{c4#}fdPf=mEyM6?*$GT-kXWL84XWnO+{F`4_1t|YNEmxX`F zc^>gAv-|fxT%(jqF+8~|3lA?7Dz&hP$M|oKqv!)W35v9g*nB)OokeVRPic*~6-ab3 ziOPk%WGj_+%QB`Ax8m=7{%za-JONvwT3X=y?V@*P5<%^GWK6OF|F?7s&VbOfv9Vt<^g$-U;kGz-sfX)+MSgu9rrNJs>g_-cpTDlb;qsV71dzh6xYz<=$mg5$R!ZzA!Y5_3=MyW2Bd%7s(AP$|0E4ZJ1DB0mA!FOnCH(C>kneC@z4)e zB(>i?D}#~0SyRkPfP9cqN3W{HmCEIlqxg_`=0rH`y@3}!h)n2Jn;REze6aL3y= zO&qUx4`l&V=Xk!O78HjY&&tDhMTOc+K1PBE9|9WbPZ61`c;GLgH3raqgjbm$E)(=8 z$~gD?4EDJBqh4DT*YmpsD{xh&!?yhkE7oOAzh`!J*!8~N{ih9EpCZyR(e0k?E4ON9 zr%s{oi)Bwbdkr{;m%;rAA1$)0T^1RqEmyNvJLtCdRY zthr}MM%6OS;)eA^ilm5KldS&N*W}PjBDT;Zrd~e?loJ1@RLUfJGQYrvd5D!#lq>oS zkLe1d2Gc$cA=fD)Ud;$=k)=d(s+D<(kx{a~(goK7{LH9#kK!-$Mswr~qpDzJHmp zX)9&rf+OsQBip?z1S?zyt%mrUso=u5SretWD+KFbkonga)^r9oKkC-yZ=DDGny|QWy<2W*d@LbW?Oz?rO z$uNfil_U!|V0vLTrFjo1CC08wTQyP@5s=wtR603u9QpAlhhk9Ewyrh$O3auB!ikE) z9U_JfdK7{{A6sO)IL1=y>2a+SqNkYa;j|jipk&_n>!{+zQ1ls;K4m88YsA&Z(qFmE zu@LmMc2oRoA&-aFv+Ogae%dj>=Ya@d8vQY#QGw@&>4E)09_yY@?m3X*BW?8u{*PJp z?N$+PVV>yWyh`OPUNd3zV~X5*%fhhdn#o0#QceRr{caQxhpCv{mK9e_-Oex|le%P& zWynx>q-6b@H`EP&_G}g;JgsC(X;wuNnnCj*U?P{w;A7-49kmHJlNriG$znhFF}iqj z4Mv#a-&VnkC0QMYZ^!ETpO_WUli`r-bt-g@;b4}>4Ami6`G#iSp55|U7*S>t2 z{rg()Yx{L@&hQ_p%yzUlyF0e}y(t+!x}OEjIq1z2y)rb#Ub$XO+vT{s+Pd!Q>HQxN z`X5Siv_|U=dqVVY@miU#uJ-8!ZwA~|)QXV?du)22kO47xBi}-4@Hf~V4TZxS)>wjnJeQw`nzs0=0UAH8{62iZQE?v z*iITYHg;^Ajk6m&jn&vX`5pWL=kvQ}eR*frtXa=}Uzg9h%i^z@fb&2zRAMjlvG-!z zJ`ywd-G63Uo+3CUtI$n$-{K*q#xeHCrL`O6$`BoNw&4}S#!bc_wPC7MK8FHXzSVpd z;GX+yCcUm$iV!GmAN$QswGDa4o9O-eu^yImYa>5PJl=XnL!Mk`+nkZG#8+!%GL2j1 z={g`bHTIg9`4EBHx19DptmMXtzh@Es7)LwVKw8309>)l`=d;dlcU;%Rk!X^|#2k0Y z@S*2_8K}SVM!RkWa=uO`P`+P|fA*B1{mDI6~U9*uD@%kZF$Y~t>| zDHRMhePf^9>6N3815sCrBBZg><)9Eevc0F6?p27)C*DshY6p(wS|v~6vlj8>^2j6s zJ%PUQqEIGOU0+f}Jlx8Y55)Ql*U&_}U`gZ4{mx;FCyC0DzpZ zHOnCBs4Vug%=3DavAXsD4mR8RVZ$$p3X3z{8Ow*z+|EIWd{ZcnbiXrF*}@zT*MBNQ z+Zy0$nK0$4x`;rc7B2P(s`Xv&Dp1^g;aaccD+g7m0Drd>wbRH;Fok=q(y zxHIu#0}sNJ`&K6I4z@l7-aWo#T^7MGWoshD7g@RfcumfNrjQgINBdR;i5q5|PFY?8 z^}M8(k>(c2tR(W>Sh-j!UKkK*Qle7(BAAsyuAjBv#YiV+06urr=EmeqYHgS2y1Oir z(S=m(0apu&Hd*uwsx$r1rbOR+ck)Oc)HLX27Fft1d?hXGN#H%2F_v@Rnsi?!vsnT21zWI4mOvxhpfOTI_{u>E44SMGW_FxP z6K`@tJJh;R=HM!!&Hw`Eq;9!b6o0CGTPs?-(9ca-e3wq48R+@*=<{dkH} z^)y&nODATq{;7J3S@QbipasFpb;gkL&+pha6{;D!uA=Mu>O}E{m$FR7o!k=X1@&g% zObcTXmOPe%nwrWkYFvW7B}Tn&mnr8PE{pRpDa5~z*LRtk#rx0DKgFDj!JTq z8gW$@`!zivGKCOdD9>vfy8~JMwI3K`4zI)TRAc{0^W;O!oKwpGYalcPju0 z8!}^xF{`^)&)*Q+Y9G38=ii)Fvzv5`#O1l1E4FKYRi;0uIpP79J&Exnuy&p`vpQk#%c zT2e5p9jkQ=vO85d?{G64?6};hOB0s;HXT#p&O^EdPNwP(p!{RjS)` z&I50{etJ}0an5GWioH+qsn45f=1?~Fe&m&~`WG)z2d$&n_~t99bmehyEY4tLs`eYfr zh<-__<^^Hv?o+p;VK(BB$-yO3{G4LNaVah^PUodLSN2R@WU#aGc@8zY|EaV=uM$r= zZm_WnR?-{&Q?Tij(Wt~_7}Aby_D%iu>G4+x*Y{|W*)R;RZyPo1zJB+IFU)NB+-ET% z5*~k7*zmfd#L&68(tBXss8L0j5k$DDH?11s{n4lfKur#2j+%%yK$~6kf@vsudFs@$ zsIpv)z=4Ols4vfkTP(Jnvn|(+2ZAfAW<|_F`6i9!K1#A7_Gkwd9QX61@#og9-rBBK zN+}t=SiUG+)*NPuMLGg@FP#sZ?ek;Is#kV90(oqj55jELy+(_6Mmh2=PQnY3Oau>O zUVomjJb!Jpb`DDVMZq68DB5(-CmZ*0-!Uxa-}-8vm)#o`d{i&ntae&_;ig?y;!}qX zybka4wAYv-urt#~64rnrL4amh5U4dBX*opJW5Bu5RWj#w(W(9o>93K=PpB+^#!NFU z@SC^&VA5~mp1rfC4ZEIsJ+fiTcd*x0=L3geaXl|gjt!MC59T1Y$fo1r*^L{Yk z_#$vil6UjL-+hbx?|G|rm0@06ZrFr9bCMk@*JZDcUXreNdc!%mzY zn)k$3LEhWZOQpxkbAPp){TuD;OXuNIs9WpXG5XFs#^&Qz&j*U~h&G*oZ4%u3nP*iU z(`spc1rs@WC0n^Fs=AE$zBiKeBX#U8J#w@Z&F*>ShhHZH6rLWLN$=e~yQYlKpc};r zeUoPZvzZbqg`&S0RSf*d#;Zo*mpU@+jn8I>ky=XLu-q}ni|3v8nsW`&a(qzd|unbNh<%BzTAnHIO`o@WSq4qvXl<6ZxZ7&%nM!>HkDs2W-e8TDTlKk zFeojZ{DJWQeTtd#?++(;eVO z`W1-ZMkDg~RZTSu>(!Jq(skPu9o2A9R6Wu6Yn;jkroT*DLEF-N!n&q0^&|&HkFk0b zv+;8M?7fQ@h%@SB!6>V&l3$%!G}Dh~_$^h;{8KV2NVdC)*z5-QU7`$a7WQ4#QQhT&(BmS&_g z)3hf+D$kE~H3rn*)ZoevSisp{zH4nER%cIC9P#k727RPEjH!&cinng6gTb)LB&~K6 z++0QZ$PVd_k0cr|nAH);?F*DYvS}BDWXbkAiS5zA zc61$v>IVJ_u*<~0S;YWQ_At9cN*Pt`vmNVq5)pBX1OVO6S=ZP|z|!Xb0)yP0Ag;&1 z%R^{JSB>Xh?&!lGsqN{WHE>YgkS%+!{M%WPMLPJCgTuv=X&F)Jb9N}B78RsI^%lKl z18Qucp(Bea%5BN&>2Ey02<05SQkUhxELvHr_9#s_TE11N|E1pvk^5zyP0Nc`Z$rr( zuTEQRLiSrsS+Jzxd+a{Btw2Yjd9wOUVm_$J@@oVZ1SD&X_dip{3Q$^2XZa7r5g8PZa4tO4_jvssg-yP`(F{`02jiN~BJ?c%`Z2*#DyWY% z-C?DT-jf7PP;PGa6w?Z*9W;R-lTpMB4kQ*nHUeZE7R(#0SAq< zy)s3_JegDgtqPv~UHuQh!vbfn$sa!eLX)U1t7&f}Gul+?stA&UQXcC_{;~#Uyhl8B zrz8YAh%W|OQ-fZIa?6zqdLV4PPzszzKX@YXVL_U&Rly!rtRJ<*$rnkAJVRH_M*9iW z8hv6rOQ#091-w35_~MgPIl40lU9V$^u!oX9D6DmsC$giFzuJ3-#VIfPMLo$6%yoPv zkc%$2n;c_}RZnH$>jjz5FUBvQ-uT$Na^k+hkMQkkmdp+dC7Y$>b{R`S^wZPwof|86W}%UY86i{ViEYVOI+lUc{0zdWZ`#okS!*mxE% zV0ZHm>f8Ptrh;MyOGL@c3JDyb9Rm+eDfZH~^xG0aa<$7vK)L$JfoG zYaUW}^9Wt9Qza`ad z$7FEY5b(zG^Tp;D8=!6a7jn97iqdcgTa%}vN7~8}0UttNW(KBiug0ooCOvQQ1ueBH zCb(AF9dGg^;ct5znIv zwK@ZIsDfN^&`~X2;6KP{j*D2dzKoo*kJ)g{7pwL_QK&XpJ1sh~YHkx4gPk7ZBbg&n?rPOLU1s-d9QRh}dGGp8HY7ZI$C1+v5kyL_39sqE&KOyCOe|Rb^2f{UREvTb zC@M$G+!M8B?#AlLIRPvKqZ2{9`>{dxUm8GFC8x&9sUq?t zx?ih(!F7Owg27lB7!Z5lUrKnizbV-b6UrBEsJ0?_r|sIUzPR;#e7PK{o@sd{dEZE| z@3Kv=Ci_qKYvX>)JipBYg{$U$f#EizRiOW}as3%T*f-*RLqPKA;XjPP){E13*DZ&4 z&Ac%GfQQ#C6uec$?g(Ai8^1;r@pHUSN+HIo?*yD9`(x{WHhJkh?_E(WsGjw$D`|Nr zdbK=#*g3zHwEp-;);6`h^{iFP-y9%}obtDjvUTY_@0HdD&PLOa-vle=fw)r|+Tv%n zQZ^F7N|ZI$Sca`UaVt#({n<$Jiepk-p9~V>biGL8Xywr;EUHntKrWLAEL~*`dv>!k zPa51v2=Yj%ZDZkyO;+YGI$zuk!=Th?U*aOLAp2_4&!1m=&a9dzCZ{Z)49%IbC;njd z=cy)Rukj*p>07$YXyDvmboXgGbcuL(Q`Wzw-T-tp87`L`7!1G{KCBat3RwCqP^3E~ z_Pqe^6TuMKD3fSCHhf431)RN+0dv!z@13h!=4J|evk5S@Ida>SZTgh*E1ZGEDZRv$ z4J!e^WW^c7rvv2%Wa#?529#qFC31|RBk8D2c$*W(SuE9bn>gGE>s!5uP8298VZ+kk zGud68d8k}QcC{mu+5O@Tv(tI(cIBcL!9evB`UsU8PnDDs^{0Y>WYu7*RCx&uUPVmZ z94T{Eivo>J71V-Q{Cx#EY{iA}8qd9zGhRp7@vhil^H@WK-0Fh0?O`5en;GD}UD53^ zOD5erMom-(hGyL#6DD3Bn^J{M~apWfLzRVJHrlSDi7f>vAm|G?0 zpDTE?Xj}nFn?Fyu$Cl1B_%=yRQSNk@v`Af$A$=v6+bKgT4%E~RD+iI!Q_U2}Ra`?5 z3DCqpp?XAxlg-M@(YK82#^Fq5%ZH_Vt`e7JHN24UiZJu zsNB&k>aJ;|@mm|q0a(I6xW;Irwts+Qq`wa6W%Jtz8_rwAu^@kif_UMlyk;)PW_VMc zQ1%E%_~+u*Ft$xLGyuPixMswUG9W86eU z8XXPHA1Pbeb_4i$VY${h>2x+Y==41$jZX$F?^A>` zyon)`8iW)!D!Qj_P&F1KQP2dn?5@wMVZcore&*syRN(9W2;4e{yf`;*kAN`%ukoUkfgJPLb< zzK&wr2l&;#{a_l%QXjb%mGT+Jvq`|hxag6w9m78lgHlyhwEdlN%ho0~qQ@CHrUcfq zm5I;}O}+9P_YAm8-H69%=|NVjjunE#l2()lBuW=<)=x&zt9)~Ig7Fx=Cf}8s3FQ^K zAZLUnKE!SMDk)=>H28DlsL^kq#l$(o=fF8$3XEQI$JPt>&%~3dP>w-Zc1yB7FuU9B zV_X_@;S@25M)q}h#Agl^D;z3Ye9l3g_2ORe>{yPd&u$#G`lbH+*s+KW&y0^&+b5z&&)?S#?S9SF;mzy-pz_P-kAj{_Wxbi#+6yR& zjv?Dlc66YCV+FX$V(PC$kiBlGZ;dQifFID2B^FOrX@}&v+N3w9$nmoXfK4}p5gAeHc6R-g#fx}kC_!e>cD_1IX{R5ziRI+6pH6+mjk+8!(XH6 z02IhU*S1Fjw=76p&gP1mOz#7+JGWTv19TS>swOl2x+<5My43-F5eogw@Y6e0 zr&Xh_*IE!gch{B_ku%=u9e^iJPmJQLOL$IA>@#$2O?Vy-4jGU;5nu??FKZ@;6!ln! zsZL#57*K`8GGZa@h4DS@rYBUeuAn*os^~-mV^VaEg+YL2j{}4GE?IT(^&n#q8vgTN zH+;RIC9ETaR$>0hIBg9M*-dO9d@Ag%~w;Z&ANPx9iCZ!Ka0jA3;-*C6>i!wKy(|RrN@v;`x6E4A z4KRr^@_@APubLl1d>qKX$~a0SEtaIX7osWNU75Z+xfJ6u%0Ru|YT$kk6;i!y^-giI zkklRpB21uTNH!3!>2*_l_0rjR_&t)|b>k4A18BZoFVv>u5F#yg)#Y-y{UxVv;sbk6 zv4qoWH7HweB=yU;%=k-9ax|lfCF>HjJFfvgy;VsvW@-LU)!=!L6Fdu)x&dvd=Q87) z%Z-tNP{I56UJp0RTJR~7VG$x)!ti-M>Y<^YkaJdl?_I5-`?+52)SH-I!NV+R)Ws#ECw=5^2-@_Ti_U5D?xF@M|eKTW$(UB=rUg#0d_t!x^dF|V5>3FuzpUEP?jz)V*iZuWTbMfdy{v7$_q5jBRaptj@g7GrN?Ht(Mh?Iq8{eFndiE!x$eQlbkZIY#xaz8T zFwL3ws~?je;*0Zn;LU4YN4)4^MP{3zi!G#(mto26b_yz6kPV~j_tX0AI1{L_b8+@z z@H(&w6~@puBb0c-7nwg|=cB3Y*U4{=M%|iVN9^`(cK1%*|JC=au&BHv86+H~Md{c1 z3^nH~(UTwYnoRpdawxQ-QzTs_gj5~15<91YQi4|bQ%uchmcKODk4Hirxzx%~kMq?T z^WQuJyunqSzf!Ph-(1y;>(PNbM~aLZTi@ONd665S45{h$0YL=8%@LCfrwe$T{7cp~-DZ@bF2aOYrP`1gF5eCupkCi=`T#zP3C*CZ!v$mQ!7 zlsU2^2TO#ltJ?_pZ~y`S6N*FUUYLi;WhZ4wmWb@DT4VcO_uLcMsp8GP^2DMqT%pSB zsjw(AK>YfFJTCSYbR-Gl*(ffoIK%iV)p#-IQg9>(sI1hu5gxhI&a+c67U~8?{ojmG z&UuEaDP&X^~gw` zpA(tImnY&krXqX_q}+7Y3SSD z+eiPb(d5SqFZ(iHmDLCry;dEHT_6dXCcc1jKC?pq2oQ1;-DAeD1I`k@;G@eG!_Bu^t?BD! zn7!;dlf3rW`JRF8s`pW3Tl$Xqda`4YR66K_7gM_%t$?Vij2Wuehr z6tX7l&=wf;!=g_nTE9?KNjXBn-D~u4RRD;gJbjQePO>S4$3MD%P2#|-Mrj<~%cnD> z5S{yL&zNkg3p%vskNaghFl+by%7l754+b1jWWB5~2s0`hmNzenmnQ;XLCCG!Sq#Sx zhh7BV%Yi#^jh5Mkqu$H(s)md-^`}FVrzBW8~3oK zw#g;`W_-q8SAAXisxbT1h5hP6{aS+H>$#v8X64=My!pCEL5&U*C=ZtsC}qN zjEC;nOTNH}rX@;zBQX2b95YDyt1m^UaUbDZQM8JXhyubhy*0W9Fbs#j-C}g6Zx5k4 zsKN{U6a91~DorRFzik@TIq+w|k8swq=7ef+0x14q*(bK>QvQJGt@w82w3@f?78VP`<8u zF1>HA)8z6!=)O3Cr&(Gc`%v|(5>mwl-NAqY7E4u-P9MQVvIAnu6GV1brp;Q?h7)ft z%y%RlwB2(cjqe{A&@;xDSX#7tPZiPQxnhYIF5eS1;bq$%N3w^pN0^kUu&3+y=on3+ zVO`m#bFJw`?X5<2SW~B&c07fqGZTLA_P$c1B8c_`J@*47 zJ?}G9qec%GQz2RbTaOIeay5qc_YWVP%^xU$am&{ARVePei#sfC-^i_Z2+9yUw>tfg z`HwZ6+Pv>?4NFD_0ZSj_-EI?W5O9tGALsYI9ojJQIe{3MUXS5ROEc~7C*fF+ga5PO zJ%BL44=M{8Th-b)e@KHy@86|iVALk+Q{JQiFZWFWvk7erYjASdM zskPzo7&S^C-}iVMU#DXWyHDubg;4K;4UIH92kc4n#0?1mu-swU>$RklMcMJuOt#2N z!yXDRAx}3#1{)b`jW%GQR&2s(Ffv;xQ|MXL{0VJy05X>{p?3qM6J46PCSWD7*%`Ce zHK6zRLch81{xBK(y4SCq&$#0Xhe6mEMRQRrb=q%dm_=h02%J2EaS8+Q(+&K0zv3H9A9K_DGDy+98PXP7oCAHu ztC3j6BtptE^Ru7=X?1lyK0-+*Pg>%izI29i{T5JnKdzUikwi7a_I_nx2i~_?@#0*4 zKdlSN=WH!r6%%yme;aXSr1Hg`f+l`=VxfFBJgkaSS#+;QqRM)SqNTWKcZZDbUg`k+ zuzaTdCFc`&eR3G#BF&;`E&!_9^h0aWq4+$-0`u3rh!hc$1igcqB%hgMDlbH7}87_27h+p`V^?whOXP1zVl!ZFT z$w*ie%6L9KUqArc$n>r)`Z8vI;E@9Xh%=N_I@2;N4tSc;mNNxRhke|hmTj+>qpb5c z`8)xs*S`&{$`#p%%2<3BgiHk^hx3295=<~%mwWUrW46(j%5tdGRdo@h`*6zG=m@6& z2_qlx45A`4h_ZGu=$-PSs%|8`5=4IZd*1YYB%j2Qxg`lpg@>Stk6f1)Lx{l9wPUZU z;s*$qMB;(n?(iGG_o^hz-lz$8c~{Fp9S9f-aFtLAqlmVCz{I$fuu0q&k-GqHc9yS# ze(rN<@)Yxamc7LC3jN{BWy0t#PrqB4?1irUVbSaH=$e*zPp(Wm!{VRLhk=}k{bMv7 z^iFw9$6kzCl{B!Ewb*9nVs3u~&D)vz0eZx4bwKf0MqY)jcSMCX8=^Vz{YGG=c5I5o zGiCxX*HJH+C0-lO7?>{$b?oYPFPX<>T~7udDh%|4P4J1O09kjXgdqZ?C$~>K_Nu!8 zpGS<@7U>0a)}f)1pPD}pS`*%}6Du4Ut|br!27|>`tmp zC|ypaD;(tE#r$Rf<6K>Z-+*@D>DG?&(wEd-M}|*hkwQ6hUb1%*?$_KnAUSF#<5WzE zHC+Id_nTt)}?rJcL+P&ES6z3G?8HXRpzvwLGf-b2!P zj@?+LtqH02W%yWh)9-vy%4{8CnNnIHP`-Rk;DF+IhqkAekV;j-?q1xg@xj@p<%mDD zIM+lh0smY4MI$>U7a__zqn$l;g(CG}uBF2q*w@kRQ80u{=$vwck<(SQRtS(Y)J#|$BmPBK|;AF2k=y}z{6cj zG9fPt9jN*i+F)s+IlZ#k<#?HoQb{&pJPtc(dHl&k&+md}zkacrn-OywZx(+0djTgj z*@Z_0G~Rq)CzVb;ErzvRVbP^TDBj(>TAzYC4m_x01v?u1p;*18&9*&jRiRG_S10K9 za(Yrf|5H-g@jJ)_OHfCX7T+c2kK{(cokg87z5JHWHIpnCOLDhNY57ZH7JK<5P5H3O zoT5tiE$+yH=-&5%tkIzugBe^flXyLs@P|ndl-7?-G(cfEY=}XQX%H4aQlse0;XOtu zbMsUdV~w(qj4SRtOis_hwZaiQiUL9VJMcX06}L|+V1G|(wKj!|vXt09fIG!P%8-$Z z>_v!b6~;=PS}N5zO;e`VEDmE=I%9s17P5+D{gl>WD8Lj>A~n-tYtw2CQ}X;$q@X!Z z?kZ$C79j|M2r{)T-5OHH391Ppg4L(3wXX!u7IpIb;OeneHro>BMWTmR*3@=nUYEM{ z%~TUsV4X5VNB;`^P>(*MKCWrgLm#{>+QUF>sDcb<`Au9qm#3mJ@%=Dqqj38}2I=zq zwrNgOH!X!j+>FMQQMm5)y+tJ>zK4(4?x?Xei&0~L2SXseU zO`T>j(jhCcqw&aLq4aYMuqZn(*1to0j+POhbY2xawep|x`@%@w? zmI4qjr|ZFE%M5h}kDEeDz%iTox zb`f9fyDa0*0<0`CIRE`_i6V9Pw1!I!-J~`shL9B_DwtP4Ja4;SeSNv}HxfR4uFRTo zyI8;W=!5v)13RxE3}Ll?JAEfyo9FhnaOe3}yUyR*MdG|Iyz*cKqYO#7Z5y-Uvv*Ls z=cMSmfcjoi;Bq73`2Mm=?c}$+NVTNc^B$b+7$A)GV7sL#FU+xGCws#rL#Qp&---c#3hRYWnqEP*fy#CSw}I=VUnBaAt7dj zNpj=puvdH-g=0Sog=9Wk$(I-obKM%7+_rPT3jO_9CW92N!$yz*dS(p%T8P0tdQz2> z@_6(47ua}YBV=v@2<_84&d9MH@+#x8u_)Dj00QB2xR;S4PL_$lG5Ig`Y8h(C^hl}= zNV&4}5}+0rZzRgy?>q-!nJ5N&30c*u4)EGvY@x7TGge3>g{4qXwmNCdLUbooFnFg^ zUh%w@sM6u+(+CA$RgPUp4_6*NhG-m-4N36eTB@{*J}kCfs-u=-S`K3Z5i4^o(d;fek@2!fhFi-I6aIA)KcG+rM@^4n zs_ejPOUCdxHBJFDu|4G|X?A<4zIx+8juHZCDe8<1 zB%tl83&R${VZnDqYV{!P;VaCszgY!j z`N9l^7x=VtbOzBk&8W$;9b_Hx z*)c2!qN=<$JOHPdxJ`rHBP)j%?9UYwg;}-jB?m#?o~&FpH5YAYkUPOL}I!T z3grA_SS(SeHMk!1gsFmUuE8H+&%I#mJaFG}QU7+@QqM1uNkQw@Lc68ZL+*brSquBPdidO>)7wYV7m>`uQ2rdxo zfRc}c=Q9c(97Wgr;mAW4K{Wt2J@}TZUTr}?;x$K@6jBPpqQ9U|`okjftLm0+Et9f; zPnmx%)oBp(xA_kz<6^rvm91lf)PI!d9%gAq-7_=ws6qZ9{St;3PC8ojut1hAw0dwD4#dm9ULkkboJ{H$O<_70 z8kv6Mnu+=$ZjX3(#U1R_M83$Bh-J@d$H4-`dRu%tr|FZHfbe~{vtN*^(9&kW2xBFM zgpTnMyrQ{`K!wOUTF0cEOu6D~C2WmYX(S-hp_g0|lpZ+x@Vr-DpRyWdM@J;>pHHA@ zU=H~`l-6?w=e$I3!2-MNo5P?0X0=gjI&^8|)UNC;==@f^v*JsoJLBFMLh+(@H2G?_ zdigCL=&L~#w1|We0^j=}&bOltuQS?*FH&y~fNY7t!8{Ykd*PW!iNS63Q_t_O8Gfg( zadElOEU>XM)tGHvv1&vPNY)}1_BEf3qcb(C%e?GQ5?)49tlqOi0MGW%-2xU!=p$NH zCIDYpv#*q9uS0@pVoHxJFhyIOlTcPoA7rGz~&6tph@@EjLp~ zLomhin4(*pyFR>B1$segv#EP-6Y|A6p7x2XHC^|n#J%vrE-i;Y4BaIX#iRc*6IK^{ zH~ciRxE8&G(1CWPg#$C*Vvy4%pjffUe^8&b?08~mgv71p_-a?7nq_L7GA^7Q)?RAsEm}2!ze|B++4<9I2@ZjHT^`n+q2G>)VR6G>|X#em}nsthkOh**QKtjAmd5 zJJ#u717$2XCu)v(hjkZ<`cO_Sz9(*Y=MQd1^qoQj-pbIg$-KD0L2V6k(B@ST_c5%* zvZ*A^{j%IMQ3|Dqk-Y}$g z0=qM;jwY5eSrr_fLme7d7w||_zW5h>+iHwPc*Z9Fq3A#z{Op$Gj~leRyCqKLSA)W= z>yP!!1-BBet@0ymSQJGcpMQ+Vf#_gL<&84k5Hu0sNC@%H z7=5EBaffu&6b#Yu(fhK}zq;W@M&2_GS9yt&bf&0mCWQLmJX#X5`PA_{ffi>v4eRrC zL{Q3!$}4QV6LL_%P1f8L)D*v&;J5u6pV*}DFBJO^J#WdnqwyGOViys=>GoB$qGC^^ zvZ`p8=w&`_5Cs4-HrPG$usQFkG*^_d1q}F}MGCi4+z%mxlyLgtj_#O&$Vmk-#49Bb zy~zbabFP(lS{?8%ygs?cS?9MrvyvQtwTIM3(T(+O4^~)J^`lgHrIz7;S+Hv+aUpZ^ z5S*|KgyAc(77tG#zu2m4<$554Rocm!ioQ@jeuqQ$15;i}iqnkE@AozW^u67!|Fg&`rR#fj7*#SXJn{ zyqUWY^hW%5nf+YdT8Fe?iuHY%QgvL;i%U|sI7698GcjS-Tro?5lH&;tuVrPA+Vx;q zHaNH!Qcw@aPagyrDJWb3aTSlX_eBn?fSEf8vcNR?23+$S*KX$OS6#Mh5GE47rDMPU z(U4GYmYSrlr*Bwzsvd859^L+UaE|l#C9n$Q>L7DM%NuP&5{=T{m7AC3(B%X6<&W0PMldQHiUe~eXh{55DHBhMtng{~7r6rneJ z5!hZ!s7+acmair!o}Yv)d;>0YLIg+DDr*v7GW29L;r(k)Q^%JeFme$4lHy*A0@hlm z9PRA~ZZ@YKOBMbBPdNvY-x#7+^t##L9^})@!a#UPONrd9Lgd|+Fc2ZZmtBFHxLM)6 zSl%LFVPOTrpV(|zCb|W_nK9M;m@-XH)9?8(<+mep_!fuGOZ7*bM^SBZ4mg5nre&j5 zxK!C8{eyC$fOd;fGCZ&?IE4LP(fS_G>gV}F%y3IAYH|;={3-sNiN>mo!U7?&EmgI( z%+48&TF0}X`c&q%_gRISojVT%Gv`{a{VO4PP98>h58aq2dpmW+OACU3bopo9^f14S7>oEaFRByNDL+gXfPsKWI7#wUE||T0%gUb4L!;ohdaeB9SITvE2q7(e`lJbDUry=;zUM z{nm#K@9BDi4}_6T=->mBIm`)3t0E*8RH+Onw>~45mu_uE-%Z3yQ2qp=c8nqM^yiV% z@C)z{uiSNhAyIzIH46|XJB(Rg*3e9E)Ws1~PV-W^s85Q!^uo)bHfEG zjF}u~It1H_7GUwrs@oX`t6iw8R0PeSBbgFWhrXBmv8ej4J7YHCcYoPp2^s>f)l*5- zSslBLU=9L0!upqayeK7#cYQ$;O9g*JOMt(cK`U=T&nB{Ed9{JT$0Gl7#jeLv7E!X(getKCt_^IXy;t zaNlsHm4TF?)f2a6b%{VqEB=cK+|>^mNMHstAQUmpJ9-YPqxJV@dN?lf!`;fCPWx*O z5e=B1){XP@x1c7tg1HhwX+kl~rp3(L8>Q^V?m+%tG@iAm8FB}A37*lx6j*6=2FRBR zj|p<>!u0c_ebe4q<{M!SDfwQ4mkVse{L?d~EZeG6xQ#gX|6>6VKvFHk*b2kEHz^X{ z7lGmL0xlj?a^6SCY;c+lRlOQyc6N|I5^(WaXB|n~j=7zja?b(?oXIcMJ+(iqXLhn` zay=$ii2E3qyS@ZCZ{I(_wd}>!LJ`h>-daSqfBma_Z{K|9F201$eR1;Xe)7xeg%bL) zTM*a_X$YJB{-=ArYw_SBuyWmtcDpt(9ir^Rote9~`H6p=tbcCtn}b|qx-p4jqCq!tB;fPH+YSG$b9Rh&AY_}<=SdtlXFhXLsCp%reOH>hepm4(CAVQ6gAkC*kSAlEpBlfi0gMDK3u~x+^}o7)NQ`nV9|E<%AHp+eJ-T zNK1LXmb+!6mXfKVE;${w=uvW0N7Z&zCufjYKdq$A^h^n3$RNphr>skcBd_UzPP0>a zRB+oRGECM7m)iDn={M>i#bFdK{_vo@X*7VJ8Dz4<?o-b zxk9~@;#q04P#+Un_)02!ON}@yt$~88*Yp9PoX-%f|6u#HP)Yl;q9%?)urR!J2}`^* zR0v&~N4)7DBo=Lbpk=@$Z&=eB&vrI=+n@9UWz|}0bus8T)l#O3ML7d%ps1fQ7|Gs~ zmkob>qc&&owdaOR{myeonwWFQ@}>8ckUC_h-o|vheFNiAhW_do{IaG6+6o7%s4w@) z6VJpcl72jGovMwwd>AB(rzUDK7Np|8w5x^QwELsXP<3tQ6m;=z9MQ@*i zwK0tDj#w=&l?4Yx^F-|tjE$*da%95NHtd1B+UyrH#$AfZ-%{D&Vt2OeN77uDiJ;<@ zXz}2qio(lyl~bcMyh!NO>wtD%RlbTzdzaBPnWVax01LnlrB?2*=yEA~XIl~R1#FfGXC!Qp1;9+rb1{ZI z83N>4ze&l{JPgUD5>*w#sm!tY0oHl$E1tBlRKWi^oahCdDW%VpQvOyIsX8V}ha@N< zy%+EWo1TCr0$8JTAf>J-V(#XEo|*-YTXl0?LZabmI)^o!JxY6FCb~KAs<>q96HS^hLi+^V_`dqU9-SG?&d1%a3o9t0jUu% zcOucwm`8_Yj0!W0J@hGB9U+X>NajgK-aKP?-A37alosYmTDN-u3*H4gbpg@pe}MUP zt|iyzd)P;4bET{v0DudT<=j+QQ0!5qXz|p;$o$69?Y3~1@usy*tI$2K+PDH0J#+%kC}%kec3&v$45h8DE^1R| z%@r6AptaBq(|~!!Avb3PgnIzowosz|7tp`zSq``Vd32}Eo}jJiN2OjmJto;dIP-ZA z&Dlq*F9OU4+erd36M!dCte>Yyc{=xE4ryC3Apw=QJu+%<%DG})l=W^UkWfqotbMW5 z_6n@bA3k60s`PAl7^jth05u3i!&<%ALHf#N#uWRKSguONRGN$CaA;nz9ek+M3A-P8 zw%D!ZV#Y?JjWq|`cb)w|n9pSFUT}Rl(MO62V?|mxGq5uRo+w>KRf!7sO#?PlzMOV{ zLv}Y91L>-V_uA|k_Dk9&Vk=Xb_L6OuknOcQOF9sVPH;lHtKGB%>zOo?*s7cWcRCgO z^6AUY;S?LGCoPn@ueqwKQ(BL-mPA&uPXe0G^s|@r@g?jLk*u5XnO^n-zyp9WrGqKe zXF*zR0+9GGtT4nLf2_&g!}WUrrT+}grlIp_hA@{T0G_*R);?HSudlE#beG!2XGmSc z9??B|N@1E99>xBZ9`;Zo@3D13 z_WpZL*}X)fx>peGJG#fFS}55^1SbrKW0@$dCU#t* dYR9I*iB~#Hh3|Jzq(P%ro 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 *iz8Q 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�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℞}|AjN} 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