From 3c1d65a4715e3759e6e709b19e6ff88a9e8880ff Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 8 Feb 2022 11:23:20 -0500 Subject: [PATCH 01/19] utils.coor_trans methods accessible from second level --- gallery/tutorials/lecture_feature_demo.py | 4 ++-- gallery/tutorials/orient3d_simulation.py | 6 +----- src/aspire/abinitio/commonline_base.py | 2 +- src/aspire/basis/basis_utils.py | 3 +-- src/aspire/ctf/ctf_estimator.py | 3 +-- src/aspire/denoising/adaptive_support.py | 2 +- src/aspire/image/image.py | 3 +-- src/aspire/noise/noise.py | 2 +- src/aspire/operators/filters.py | 3 +-- src/aspire/source/image.py | 3 +-- src/aspire/source/simulation.py | 11 +++++++++-- src/aspire/utils/__init__.py | 13 ++++++++++++- src/aspire/utils/misc.py | 2 +- src/aspire/volume/__init__.py | 3 +-- tests/test_coor_trans.py | 4 ++-- tests/test_grids.py | 3 +-- tests/test_preprocess_pipeline.py | 3 +-- tests/test_volume.py | 3 +-- 18 files changed, 39 insertions(+), 34 deletions(-) diff --git a/gallery/tutorials/lecture_feature_demo.py b/gallery/tutorials/lecture_feature_demo.py index eb8f4a48d4..f3b156e79a 100644 --- a/gallery/tutorials/lecture_feature_demo.py +++ b/gallery/tutorials/lecture_feature_demo.py @@ -42,8 +42,8 @@ from aspire.noise import AnisotropicNoiseEstimator, WhiteNoiseEstimator from aspire.operators import FunctionFilter, RadialCTFFilter, ScalarFilter from aspire.source import RelionSource, Simulation -from aspire.utils import Rotation -from aspire.utils.coor_trans import ( +from aspire.utils import ( + Rotation, get_aligned_rotations, get_rots_mse, register_rotations, diff --git a/gallery/tutorials/orient3d_simulation.py b/gallery/tutorials/orient3d_simulation.py index ce23ee5f87..c3719f0a0d 100644 --- a/gallery/tutorials/orient3d_simulation.py +++ b/gallery/tutorials/orient3d_simulation.py @@ -15,11 +15,7 @@ from aspire.abinitio import CLSyncVoting from aspire.operators import RadialCTFFilter from aspire.source.simulation import Simulation -from aspire.utils.coor_trans import ( - get_aligned_rotations, - get_rots_mse, - register_rotations, -) +from aspire.utils import get_aligned_rotations, get_rots_mse, register_rotations from aspire.volume import Volume logger = logging.getLogger(__name__) diff --git a/src/aspire/abinitio/commonline_base.py b/src/aspire/abinitio/commonline_base.py index f46302c577..beb2bbd1d3 100644 --- a/src/aspire/abinitio/commonline_base.py +++ b/src/aspire/abinitio/commonline_base.py @@ -6,7 +6,7 @@ from aspire.abinitio.orientation_src import OrientEstSource from aspire.basis import PolarBasis2D -from aspire.utils.coor_trans import common_line_from_rots +from aspire.utils import common_line_from_rots from aspire.utils.random import choice logger = logging.getLogger(__name__) diff --git a/src/aspire/basis/basis_utils.py b/src/aspire/basis/basis_utils.py index 1303edc019..d47533a5f8 100644 --- a/src/aspire/basis/basis_utils.py +++ b/src/aspire/basis/basis_utils.py @@ -10,8 +10,7 @@ from numpy.polynomial.legendre import leggauss from scipy.special import jn, jv, sph_harm -from aspire.utils import ensure -from aspire.utils.coor_trans import grid_2d, grid_3d +from aspire.utils import ensure, grid_2d, grid_3d logger = logging.getLogger(__name__) diff --git a/src/aspire/ctf/ctf_estimator.py b/src/aspire/ctf/ctf_estimator.py index 46e2ff9682..d7b933b9f9 100644 --- a/src/aspire/ctf/ctf_estimator.py +++ b/src/aspire/ctf/ctf_estimator.py @@ -21,8 +21,7 @@ from aspire.numeric import fft from aspire.operators import voltage_to_wavelength from aspire.storage import StarFile -from aspire.utils import abs2, complex_type -from aspire.utils.coor_trans import grid_1d, grid_2d +from aspire.utils import abs2, complex_type, grid_1d, grid_2d logger = logging.getLogger(__name__) diff --git a/src/aspire/denoising/adaptive_support.py b/src/aspire/denoising/adaptive_support.py index c95afdc9fa..fa9d9bc608 100644 --- a/src/aspire/denoising/adaptive_support.py +++ b/src/aspire/denoising/adaptive_support.py @@ -5,7 +5,7 @@ from aspire.noise import WhiteNoiseEstimator from aspire.numeric import fft from aspire.source import ImageSource -from aspire.utils.coor_trans import grid_2d +from aspire.utils import grid_2d logger = logging.getLogger(__name__) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index 6f720d5849..9fe2d4a5fd 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -9,8 +9,7 @@ import aspire.volume from aspire.nufft import anufft from aspire.numeric import fft, xp -from aspire.utils import ensure -from aspire.utils.coor_trans import grid_2d +from aspire.utils import ensure, grid_2d from aspire.utils.matrix import anorm logger = logging.getLogger(__name__) diff --git a/src/aspire/noise/noise.py b/src/aspire/noise/noise.py index b99a43f665..af04a00c0f 100644 --- a/src/aspire/noise/noise.py +++ b/src/aspire/noise/noise.py @@ -4,7 +4,7 @@ from aspire.numeric import fft, xp from aspire.operators import ArrayFilter, ScalarFilter -from aspire.utils.coor_trans import grid_2d +from aspire.utils import grid_2d logger = logging.getLogger(__name__) diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index f9b0fc7e8f..820cd07d68 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -5,8 +5,7 @@ import numpy as np from scipy.interpolate import RegularGridInterpolator -from aspire.utils import ensure -from aspire.utils.coor_trans import grid_2d +from aspire.utils import ensure, grid_2d from aspire.utils.filter_to_fb_mat import filter_to_fb_mat logger = logging.getLogger(__name__) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 9a5fe0e2ea..bef63b8f3c 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -22,8 +22,7 @@ PowerFilter, ) from aspire.storage import MrcStats, StarFile -from aspire.utils import Rotation, ensure -from aspire.utils.coor_trans import grid_2d +from aspire.utils import Rotation, ensure, grid_2d logger = logging.getLogger(__name__) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 24f3db7a4d..b3d6f6fa8b 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -7,8 +7,15 @@ from aspire.image.xform import NoiseAdder from aspire.operators import ZeroFilter from aspire.source import ImageSource -from aspire.utils import acorr, ainner, anorm, ensure, make_symmat, vecmat_to_volmat -from aspire.utils.coor_trans import uniform_random_angles +from aspire.utils import ( + acorr, + ainner, + anorm, + ensure, + make_symmat, + uniform_random_angles, + vecmat_to_volmat, +) from aspire.utils.random import rand, randi, randn from aspire.volume import Volume, gaussian_blob_vols diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 0f30572d7e..0959dd545a 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -1,4 +1,13 @@ -from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip +from .coor_trans import ( + common_line_from_rots, + get_aligned_rotations, + get_rots_mse, + grid_1d, + grid_2d, + grid_3d, + register_rotations, + uniform_random_angles, +) from .matrix import ( acorr, ainner, @@ -26,3 +35,5 @@ from .misc import circ, gaussian_2d, inverse_r from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance + +from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip diff --git a/src/aspire/utils/misc.py b/src/aspire/utils/misc.py index 82581e82d3..65cf74b24c 100644 --- a/src/aspire/utils/misc.py +++ b/src/aspire/utils/misc.py @@ -9,7 +9,7 @@ import numpy as np -from aspire.utils.coor_trans import grid_1d, grid_2d, grid_3d +from aspire.utils import grid_1d, grid_2d, grid_3d logger = logging.getLogger(__name__) diff --git a/src/aspire/volume/__init__.py b/src/aspire/volume/__init__.py index c25c570239..0cf3652115 100644 --- a/src/aspire/volume/__init__.py +++ b/src/aspire/volume/__init__.py @@ -7,8 +7,7 @@ import aspire.image from aspire.nufft import nufft from aspire.numeric import fft, xp -from aspire.utils import Rotation, ensure, mat_to_vec, vec_to_mat -from aspire.utils.coor_trans import grid_2d, grid_3d +from aspire.utils import Rotation, ensure, grid_2d, grid_3d, mat_to_vec, vec_to_mat from aspire.utils.matlab_compat import m_reshape from aspire.utils.random import Random, randn from aspire.utils.types import complex_type diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 167e58bf74..8f0adf721e 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -3,8 +3,8 @@ import numpy as np -from aspire.utils import Rotation -from aspire.utils.coor_trans import ( +from aspire.utils import ( + Rotation, get_aligned_rotations, grid_2d, grid_3d, diff --git a/tests/test_grids.py b/tests/test_grids.py index 87e6372829..c2a4b657c3 100644 --- a/tests/test_grids.py +++ b/tests/test_grids.py @@ -4,8 +4,7 @@ import numpy as np -from aspire.utils import utest_tolerance -from aspire.utils.coor_trans import grid_2d, grid_3d +from aspire.utils import grid_2d, grid_3d, utest_tolerance logger = logging.getLogger(__name__) diff --git a/tests/test_preprocess_pipeline.py b/tests/test_preprocess_pipeline.py index c1a4c6d8fa..a2efcbcf1f 100644 --- a/tests/test_preprocess_pipeline.py +++ b/tests/test_preprocess_pipeline.py @@ -7,8 +7,7 @@ from aspire.operators.filters import FunctionFilter, RadialCTFFilter, ScalarFilter from aspire.source import ArrayImageSource from aspire.source.simulation import Simulation -from aspire.utils import utest_tolerance -from aspire.utils.coor_trans import grid_2d, grid_3d +from aspire.utils import grid_2d, grid_3d, utest_tolerance from aspire.utils.matrix import anorm from aspire.volume import Volume diff --git a/tests/test_volume.py b/tests/test_volume.py index c990c22fe7..a360641f67 100644 --- a/tests/test_volume.py +++ b/tests/test_volume.py @@ -8,8 +8,7 @@ from parameterized import parameterized from pytest import raises -from aspire.utils import Rotation, powerset -from aspire.utils.coor_trans import grid_3d +from aspire.utils import Rotation, grid_3d, powerset from aspire.utils.types import utest_tolerance from aspire.volume import Volume, gaussian_blob_vols From 04dee024ae8a282102b243e414885440b1f18777 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 8 Feb 2022 11:44:15 -0500 Subject: [PATCH 02/19] crop_2d method --- src/aspire/utils/coor_trans.py | 19 +++++++++++++++++++ tests/test_coor_trans.py | 7 ++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index d03f4345ff..864c835e0c 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -2,6 +2,8 @@ General purpose math functions, mostly geometric in nature. """ +import math + import numpy as np from numpy.linalg import norm from scipy.linalg import svd @@ -302,3 +304,20 @@ def common_line_from_rots(r1, r2, ell): ell_ji = int(np.mod(np.round(ell_ji), ell)) return ell_ij, ell_ji + + +def crop_2d(mat, size): + """ + :param mat: A 2-dimensional numpy array + :param size: Integer size of cropped/padded output + :return: A numpy array of shape (size, size) + """ + + mat_x, mat_y = mat.shape + # shift terms + start_x, start_y = math.floor(0.5 * (mat_x - size)), math.floor( + 0.5 * (mat_y - size) + ) + + if start_x >= 0 and start_y >= 0: + return mat[start_x : start_x + size, start_y : start_y + size] diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 8f0adf721e..e6400be37d 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -1,10 +1,11 @@ -import os.path from unittest import TestCase import numpy as np +import os.path from aspire.utils import ( Rotation, + crop_2d, get_aligned_rotations, grid_2d, grid_3d, @@ -77,3 +78,7 @@ def testRegisterRots(self): q_mat_est, flag_est = register_rotations(rots_ref, regrots_ref) self.assertTrue(np.allclose(flag_est, flag) and np.allclose(q_mat_est, q_mat)) + + def testCrop2D(self): + cropped = crop_2d(np.eye(60, 30)) + self.assertTrue(np.array_equal(cropped, np.eye(30))) From 7144d8c3350375ece3d84c9bdba5a0440e49985d Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 8 Feb 2022 11:55:46 -0500 Subject: [PATCH 03/19] crop2d test --- src/aspire/utils/__init__.py | 5 +++-- tests/test_coor_trans.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 0959dd545a..44213bae08 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -1,5 +1,6 @@ from .coor_trans import ( common_line_from_rots, + crop_2d, get_aligned_rotations, get_rots_mse, grid_1d, @@ -7,7 +8,8 @@ grid_3d, register_rotations, uniform_random_angles, -) +) # isort:skip +from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip from .matrix import ( acorr, ainner, @@ -36,4 +38,3 @@ from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance -from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index e6400be37d..68653f4e5e 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -1,7 +1,7 @@ +import os.path from unittest import TestCase import numpy as np -import os.path from aspire.utils import ( Rotation, @@ -80,5 +80,5 @@ def testRegisterRots(self): self.assertTrue(np.allclose(flag_est, flag) and np.allclose(q_mat_est, q_mat)) def testCrop2D(self): - cropped = crop_2d(np.eye(60, 30)) + cropped = crop_2d(np.eye(60), 30) self.assertTrue(np.array_equal(cropped, np.eye(30))) From 66f5015b5e22a6516ac77b5e9967a3bf7f9f96e9 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 8 Feb 2022 13:38:20 -0500 Subject: [PATCH 04/19] clarify flooring behavior for centering and add apdding --- src/aspire/utils/__init__.py | 5 ++--- src/aspire/utils/coor_trans.py | 15 ++++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 44213bae08..52eeddbfa6 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -8,8 +8,8 @@ grid_3d, register_rotations, uniform_random_angles, -) # isort:skip -from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip +) # isort:skip +from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip from .matrix import ( acorr, ainner, @@ -37,4 +37,3 @@ from .misc import circ, gaussian_2d, inverse_r from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance - diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index 864c835e0c..ed716c3409 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -1,4 +1,4 @@ -""" +"""OA General purpose math functions, mostly geometric in nature. """ @@ -306,7 +306,7 @@ def common_line_from_rots(r1, r2, ell): return ell_ij, ell_ji -def crop_2d(mat, size): +def crop_2d(mat, size, fill_value=0): """ :param mat: A 2-dimensional numpy array :param size: Integer size of cropped/padded output @@ -315,9 +315,14 @@ def crop_2d(mat, size): mat_x, mat_y = mat.shape # shift terms - start_x, start_y = math.floor(0.5 * (mat_x - size)), math.floor( - 0.5 * (mat_y - size) - ) + start_x = math.floor(mat_x / 2) - math.floor(size / 2) + start_y = math.floor(mat_y / 2) - math.floor(size / 2) + # cropping if start_x >= 0 and start_y >= 0: return mat[start_x : start_x + size, start_y : start_y + size] + # padding + elif start_x < 0 and start_y < 0: + to_return = fill_value * np.ones((size, size), dtype="complex") + to_return[start_x : start_x + size, start_y : start_y + size] = mat + return to_return From c0c21953375bea7ec66da69acc578c20ff53b9a5 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Wed, 9 Feb 2022 13:19:52 -0500 Subject: [PATCH 05/19] tests finished --- src/aspire/utils/coor_trans.py | 6 +-- tests/test_coor_trans.py | 82 +++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index ed716c3409..aeeb85c8c7 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -2,6 +2,7 @@ General purpose math functions, mostly geometric in nature. """ +import pdb import math import numpy as np @@ -317,12 +318,11 @@ def crop_2d(mat, size, fill_value=0): # shift terms start_x = math.floor(mat_x / 2) - math.floor(size / 2) start_y = math.floor(mat_y / 2) - math.floor(size / 2) - # cropping if start_x >= 0 and start_y >= 0: - return mat[start_x : start_x + size, start_y : start_y + size] + return mat[start_x : start_x + size, start_y : start_y + size].astype("complex") # padding elif start_x < 0 and start_y < 0: to_return = fill_value * np.ones((size, size), dtype="complex") - to_return[start_x : start_x + size, start_y : start_y + size] = mat + to_return[-start_x : mat_x - start_x, -start_y : mat_y - start_y] = mat return to_return diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 68653f4e5e..8c80053876 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -80,5 +80,83 @@ def testRegisterRots(self): self.assertTrue(np.allclose(flag_est, flag) and np.allclose(q_mat_est, q_mat)) def testCrop2D(self): - cropped = crop_2d(np.eye(60), 30) - self.assertTrue(np.array_equal(cropped, np.eye(30))) + # test even/odd cases + # based on the choice that the center of a sequence of length n is (n+1)/2 + # if n is odd and n/2 + 1 if even. + + # even to even + # the center is preserved + a = np.zeros((8,8)) + np.fill_diagonal(a, np.arange(8)) + test_a = np.zeros((6,6)) + np.fill_diagonal(test_a, np.arange(1,7)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 6))) + + # even to odd + # the crop gives us a[1:,1:] since we shift towards + # higher x and y values due to the centering convention + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(8)) + test_a = np.zeros((7, 7)) + np.fill_diagonal(test_a, np.arange(1, 8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + + # odd to odd + # the center is preserved + a = np.zeros((9,9)) + np.fill_diagonal(a, np.arange(9)) + test_a = np.zeros((7,7)) + np.fill_diagonal(test_a, np.arange(1,8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + + # odd to even + # the crop gives us a[:8, :8] since we shift towards + # lower x and y values due to the centering convention + a = np.zeros((9,9)) + np.fill_diagonal(a, np.arange(9)) + test_a = np.zeros((8,8)) + np.fill_diagonal(test_a, np.arange(8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) + + def testPad2D(self): + # test even/odd cases of padding operation of crop_2d + + # even to even + # the center is preserved + a = np.zeros((8,8)) + np.fill_diagonal(a, np.arange(1,9)) + test_a = np.zeros((10,10)) + np.fill_diagonal(test_a, [0,1,2,3,4,5,6,7,8,0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a,10))) + + # even to odd + # the shift is towards lower x and y values + # due to the centering convention + a = np.zeros((8,8)) + np.fill_diagonal(a, np.arange(1,9)) + test_a = np.zeros((11,11)) + np.fill_diagonal(test_a, [0,1,2,3,4,5,6,7,8,0,0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a,11))) + + # odd to odd + # the center is preserved + a = np.zeros((9,9)) + np.fill_diagonal(a, np.arange(1,10)) + test_a = np.zeros((11,11)) + np.fill_diagonal(test_a, [0,1,2,3,4,5,6,7,8,9,0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a,11))) + + # odd to even + # the shift is towards higher x and y values + # due to the centering convention + a = np.zeros((9,9)) + np.fill_diagonal(a, np.arange(1,10)) + test_a = np.zeros((12,12)) + np.fill_diagonal(test_a, [0,0,1,2,3,4,5,6,7,8,9,0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 12))) + + def testCrop2DComplex(self): + # The output of crop2D must be complex because + # of its use in Fourier downsampling methods + self.assertEqual(crop_2d(np.eye(10),5).dtype, np.dtype("complex128")) + From 86f1592aa6004165f0c2233b84489ef8df281738 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Wed, 9 Feb 2022 13:30:25 -0500 Subject: [PATCH 06/19] isort shenanigans --- src/aspire/utils/__init__.py | 17 +++++++-- src/aspire/utils/coor_trans.py | 1 - tests/test_coor_trans.py | 69 +++++++++++++++++----------------- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 52eeddbfa6..edcf3faaf6 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -1,4 +1,4 @@ -from .coor_trans import ( +from .coor_trans import ( # isort:skip common_line_from_rots, crop_2d, get_aligned_rotations, @@ -8,8 +8,18 @@ grid_3d, register_rotations, uniform_random_angles, -) # isort:skip -from .misc import abs2, ensure, get_full_version, powerset, sha256sum # isort:skip +) +from .misc import ( # isort:skip + abs2, + circ, + ensure, + gaussian_2d, + get_full_version, + inverse_r, + powerset, + sha256sum, +) + from .matrix import ( acorr, ainner, @@ -34,6 +44,5 @@ vol_to_vec, volmat_to_vecmat, ) -from .misc import circ, gaussian_2d, inverse_r from .rotation import Rotation from .types import complex_type, real_type, utest_tolerance diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index aeeb85c8c7..ad39668e32 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -2,7 +2,6 @@ General purpose math functions, mostly geometric in nature. """ -import pdb import math import numpy as np diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 8c80053876..0c27bfe6c4 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -82,16 +82,16 @@ def testRegisterRots(self): def testCrop2D(self): # test even/odd cases # based on the choice that the center of a sequence of length n is (n+1)/2 - # if n is odd and n/2 + 1 if even. - + # if n is odd and n/2 + 1 if even. + # even to even # the center is preserved - a = np.zeros((8,8)) + a = np.zeros((8, 8)) np.fill_diagonal(a, np.arange(8)) - test_a = np.zeros((6,6)) - np.fill_diagonal(test_a, np.arange(1,7)) + test_a = np.zeros((6, 6)) + np.fill_diagonal(test_a, np.arange(1, 7)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 6))) - + # even to odd # the crop gives us a[1:,1:] since we shift towards # higher x and y values due to the centering convention @@ -100,21 +100,21 @@ def testCrop2D(self): test_a = np.zeros((7, 7)) np.fill_diagonal(test_a, np.arange(1, 8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) - + # odd to odd # the center is preserved - a = np.zeros((9,9)) + a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(9)) - test_a = np.zeros((7,7)) - np.fill_diagonal(test_a, np.arange(1,8)) + test_a = np.zeros((7, 7)) + np.fill_diagonal(test_a, np.arange(1, 8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) - + # odd to even # the crop gives us a[:8, :8] since we shift towards # lower x and y values due to the centering convention - a = np.zeros((9,9)) + a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(9)) - test_a = np.zeros((8,8)) + test_a = np.zeros((8, 8)) np.fill_diagonal(test_a, np.arange(8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) @@ -123,40 +123,39 @@ def testPad2D(self): # even to even # the center is preserved - a = np.zeros((8,8)) - np.fill_diagonal(a, np.arange(1,9)) - test_a = np.zeros((10,10)) - np.fill_diagonal(test_a, [0,1,2,3,4,5,6,7,8,0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a,10))) + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(1, 9)) + test_a = np.zeros((10, 10)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) # even to odd # the shift is towards lower x and y values # due to the centering convention - a = np.zeros((8,8)) - np.fill_diagonal(a, np.arange(1,9)) - test_a = np.zeros((11,11)) - np.fill_diagonal(test_a, [0,1,2,3,4,5,6,7,8,0,0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a,11))) + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(1, 9)) + test_a = np.zeros((11, 11)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) # odd to odd # the center is preserved - a = np.zeros((9,9)) - np.fill_diagonal(a, np.arange(1,10)) - test_a = np.zeros((11,11)) - np.fill_diagonal(test_a, [0,1,2,3,4,5,6,7,8,9,0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a,11))) + a = np.zeros((9, 9)) + np.fill_diagonal(a, np.arange(1, 10)) + test_a = np.zeros((11, 11)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) # odd to even # the shift is towards higher x and y values # due to the centering convention - a = np.zeros((9,9)) - np.fill_diagonal(a, np.arange(1,10)) - test_a = np.zeros((12,12)) - np.fill_diagonal(test_a, [0,0,1,2,3,4,5,6,7,8,9,0]) + a = np.zeros((9, 9)) + np.fill_diagonal(a, np.arange(1, 10)) + test_a = np.zeros((12, 12)) + np.fill_diagonal(test_a, [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 12))) - + def testCrop2DComplex(self): # The output of crop2D must be complex because # of its use in Fourier downsampling methods - self.assertEqual(crop_2d(np.eye(10),5).dtype, np.dtype("complex128")) - + self.assertEqual(crop_2d(np.eye(10), 5).dtype, np.dtype("complex128")) From 05a203057a6f0899ed60085aba72cef29babec82 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Thu, 10 Feb 2022 11:00:53 -0500 Subject: [PATCH 07/19] fix even to odd n->n+1 padding bug --- src/aspire/utils/coor_trans.py | 10 ++++++++-- tests/test_coor_trans.py | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index ad39668e32..2db608bfa9 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -317,11 +317,17 @@ def crop_2d(mat, size, fill_value=0): # shift terms start_x = math.floor(mat_x / 2) - math.floor(size / 2) start_y = math.floor(mat_y / 2) - math.floor(size / 2) + # cropping - if start_x >= 0 and start_y >= 0: + # start_x == 0 and start_y == 0 can be true for padding from n to n+1 + # when n is odd. Checking the size ensures that this situation is + # passed to the padding code below rather than the cropping code + if start_x >= 0 and start_y >= 0 and size < min(mat_x, mat_y): return mat[start_x : start_x + size, start_y : start_y + size].astype("complex") # padding - elif start_x < 0 and start_y < 0: + elif start_x <= 0 and start_y <= 0: to_return = fill_value * np.ones((size, size), dtype="complex") to_return[-start_x : mat_x - start_x, -start_y : mat_y - start_y] = mat return to_return + else: + raise ValueError("Cannot crop and pad an image at the same time.") diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 0c27bfe6c4..a6e79406bd 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -151,9 +151,9 @@ def testPad2D(self): # due to the centering convention a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(1, 10)) - test_a = np.zeros((12, 12)) - np.fill_diagonal(test_a, [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 12))) + test_a = np.zeros((10, 10)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) def testCrop2DComplex(self): # The output of crop2D must be complex because From 4152c8b44e14e4c68dc3d0a2c7c8e6e471431ff0 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Thu, 10 Feb 2022 11:11:55 -0500 Subject: [PATCH 08/19] slightly better comment --- src/aspire/utils/coor_trans.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index 2db608bfa9..4ff34d0d3d 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -320,8 +320,8 @@ def crop_2d(mat, size, fill_value=0): # cropping # start_x == 0 and start_y == 0 can be true for padding from n to n+1 - # when n is odd. Checking the size ensures that this situation is - # passed to the padding code below rather than the cropping code + # when n is odd and for n->n-1 when n is odd. Adding the size check + # ensures that we discriminate between these two cases if start_x >= 0 and start_y >= 0 and size < min(mat_x, mat_y): return mat[start_x : start_x + size, start_y : start_y + size].astype("complex") # padding From ae42f7f8c7a67188bc97044ac44f284be1185e30 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Thu, 10 Feb 2022 11:16:05 -0500 Subject: [PATCH 09/19] better behavior for dtype --- src/aspire/utils/coor_trans.py | 5 +++-- tests/test_coor_trans.py | 15 +++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index 4ff34d0d3d..b6156ee2f3 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -323,10 +323,11 @@ def crop_2d(mat, size, fill_value=0): # when n is odd and for n->n-1 when n is odd. Adding the size check # ensures that we discriminate between these two cases if start_x >= 0 and start_y >= 0 and size < min(mat_x, mat_y): - return mat[start_x : start_x + size, start_y : start_y + size].astype("complex") + return mat[start_x : start_x + size, start_y : start_y + size] # padding elif start_x <= 0 and start_y <= 0: - to_return = fill_value * np.ones((size, size), dtype="complex") + # ensure that we return in the same dtype as the input + to_return = fill_value * np.ones((size, size), dtype=mat.dtype) to_return[-start_x : mat_x - start_x, -start_y : mat_y - start_y] = mat return to_return else: diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index a6e79406bd..8f6c4d1f6a 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -79,7 +79,7 @@ def testRegisterRots(self): self.assertTrue(np.allclose(flag_est, flag) and np.allclose(q_mat_est, q_mat)) - def testCrop2D(self): + def testSquareCrop2D(self): # test even/odd cases # based on the choice that the center of a sequence of length n is (n+1)/2 # if n is odd and n/2 + 1 if even. @@ -118,7 +118,7 @@ def testCrop2D(self): np.fill_diagonal(test_a, np.arange(8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) - def testPad2D(self): + def testSquarePad2D(self): # test even/odd cases of padding operation of crop_2d # even to even @@ -155,7 +155,10 @@ def testPad2D(self): np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) - def testCrop2DComplex(self): - # The output of crop2D must be complex because - # of its use in Fourier downsampling methods - self.assertEqual(crop_2d(np.eye(10), 5).dtype, np.dtype("complex128")) + def testCrop2DDtype(self): + # crop_2d must return an array of the same dtype it was given + # in particular, because the method is used for Fourier downsampling + # methods involving cropping complex arrays + self.assertEqual( + crop_2d(np.eye(10).astype("complex"), 5).dtype, np.dtype("complex128") + ) From 6975459240eb82ff8f6b472839496167b93534b3 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Fri, 11 Feb 2022 17:10:44 -0500 Subject: [PATCH 10/19] remove mistaken characters --- src/aspire/utils/coor_trans.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index b6156ee2f3..bf0f88cdee 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -1,4 +1,4 @@ -"""OA +""" General purpose math functions, mostly geometric in nature. """ From 3b5db0bd33ed4ea07f874cb08d546eac6246be1c Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 1 Mar 2022 11:57:59 -0500 Subject: [PATCH 11/19] fixes after merge --- src/aspire/image/image.py | 2 +- src/aspire/operators/filters.py | 2 +- src/aspire/source/image.py | 2 +- src/aspire/source/simulation.py | 1 - src/aspire/utils/__init__.py | 1 - src/aspire/volume/__init__.py | 3 +-- 6 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/aspire/image/image.py b/src/aspire/image/image.py index 299acab162..2f8e2a8a6d 100644 --- a/src/aspire/image/image.py +++ b/src/aspire/image/image.py @@ -9,7 +9,7 @@ import aspire.volume from aspire.nufft import anufft from aspire.numeric import fft, xp -from aspire.utils import ensure, grid_2d +from aspire.utils import grid_2d from aspire.utils.matrix import anorm logger = logging.getLogger(__name__) diff --git a/src/aspire/operators/filters.py b/src/aspire/operators/filters.py index 589e793cf6..8bf95bd935 100644 --- a/src/aspire/operators/filters.py +++ b/src/aspire/operators/filters.py @@ -5,7 +5,7 @@ import numpy as np from scipy.interpolate import RegularGridInterpolator -from aspire.utils import ensure, grid_2d +from aspire.utils import grid_2d from aspire.utils.filter_to_fb_mat import filter_to_fb_mat logger = logging.getLogger(__name__) diff --git a/src/aspire/source/image.py b/src/aspire/source/image.py index 1850431143..8967d5dfc3 100644 --- a/src/aspire/source/image.py +++ b/src/aspire/source/image.py @@ -22,7 +22,7 @@ PowerFilter, ) from aspire.storage import MrcStats, StarFile -from aspire.utils import Rotation, ensure, grid_2d +from aspire.utils import Rotation, grid_2d logger = logging.getLogger(__name__) diff --git a/src/aspire/source/simulation.py b/src/aspire/source/simulation.py index 2718b65bf7..a9453ab632 100644 --- a/src/aspire/source/simulation.py +++ b/src/aspire/source/simulation.py @@ -12,7 +12,6 @@ acorr, ainner, anorm, - ensure, make_symmat, uniform_random_angles, vecmat_to_volmat, diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 426dcff505..46140bc15e 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -12,7 +12,6 @@ from .misc import ( # isort:skip abs2, circ, - ensure, gaussian_2d, get_full_version, inverse_r, diff --git a/src/aspire/volume/__init__.py b/src/aspire/volume/__init__.py index 20bbe2c6e1..16128246a2 100644 --- a/src/aspire/volume/__init__.py +++ b/src/aspire/volume/__init__.py @@ -7,8 +7,7 @@ import aspire.image from aspire.nufft import nufft from aspire.numeric import fft, xp -from aspire.utils import Rotation, ensure, grid_2d, grid_3d, mat_to_vec, vec_to_mat - +from aspire.utils import Rotation, grid_2d, grid_3d, mat_to_vec, vec_to_mat from aspire.utils.matlab_compat import m_reshape from aspire.utils.random import Random, randn from aspire.utils.types import complex_type From 6131ec9528ec0cd0744c6148e1c6a541315125b0 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 1 Mar 2022 12:21:19 -0500 Subject: [PATCH 12/19] last ensure removed --- src/aspire/basis/basis_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aspire/basis/basis_utils.py b/src/aspire/basis/basis_utils.py index 1416bf8701..8219e350b2 100644 --- a/src/aspire/basis/basis_utils.py +++ b/src/aspire/basis/basis_utils.py @@ -10,7 +10,7 @@ from numpy.polynomial.legendre import leggauss from scipy.special import jn, jv, sph_harm -from aspire.utils import ensure, grid_2d, grid_3d +from aspire.utils import grid_2d, grid_3d logger = logging.getLogger(__name__) From 8498780371c143de2a2016c2091ce6123519e55e Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 1 Mar 2022 13:24:42 -0500 Subject: [PATCH 13/19] rect tests --- src/aspire/utils/coor_trans.py | 4 +- tests/test_coor_trans.py | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index bf0f88cdee..aa5cdb1413 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -322,10 +322,10 @@ def crop_2d(mat, size, fill_value=0): # start_x == 0 and start_y == 0 can be true for padding from n to n+1 # when n is odd and for n->n-1 when n is odd. Adding the size check # ensures that we discriminate between these two cases - if start_x >= 0 and start_y >= 0 and size < min(mat_x, mat_y): + if size <= min(mat_x, mat_y): return mat[start_x : start_x + size, start_y : start_y + size] # padding - elif start_x <= 0 and start_y <= 0: + elif size >= max(mat_x, mat_y): # ensure that we return in the same dtype as the input to_return = fill_value * np.ones((size, size), dtype=mat.dtype) to_return[-start_x : mat_x - start_x, -start_y : mat_y - start_y] = mat diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 8f6c4d1f6a..9f514388d7 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -155,6 +155,87 @@ def testSquarePad2D(self): np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + def testRectCrop2D(self): + # test even/odd cases + # based on the choice that the center of a sequence of length n is (n+1)/2 + # if n is odd and n/2 + 1 if even. + + # even to even + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(8)) + # expand number of rows + a = np.vstack([a, np.zeros(8)]) + a = np.vstack([np.zeros(8), a]) + test_a = np.zeros((8, 8)) + np.fill_diagonal(test_a, np.arange(8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) + + # even to odd + # the crop gives us a[2:9,1:8] since we shift towards + # higher x and y values due to the centering convention + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(8)) + # expand number of rows + a = np.vstack([a, np.zeros(8)]) + a = np.vstack([np.zeros(8), a]) + test_a = np.zeros((7, 7)) + np.fill_diagonal(test_a, np.arange(1,8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + + # odd to odd + # the center is preserved + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(8)) + a = np.vstack([a, np.zeros(8)]) + test_a = np.zeros((7, 7)) + np.fill_diagonal(test_a, np.arange(1, 8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + + # odd to even + # the crop gives us a[:-1, :] since we shift towards + # lower x and y values due to the centering convention + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(9)) + a = np.vstack([a, np.zeros(8)]) + test_a = np.zeros((8, 8)) + np.fill_diagonal(test_a, np.arange(8)) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) + + def testRectPad2D(self): + # test even/odd cases of padding operation of crop_2d + # even to even + # the center is preserved + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(1, 9)) + test_a = np.zeros((10, 10)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + + # even to odd + # the shift is towards lower x and y values + # due to the centering convention + a = np.zeros((8, 8)) + np.fill_diagonal(a, np.arange(1, 9)) + test_a = np.zeros((11, 11)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) + # odd to odd + # the center is preserved + a = np.zeros((9, 9)) + np.fill_diagonal(a, np.arange(1, 10)) + test_a = np.zeros((11, 11)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) + + # odd to even + # the shift is towards higher x and y values + # due to the centering convention + a = np.zeros((9, 9)) + np.fill_diagonal(a, np.arange(1, 10)) + test_a = np.zeros((10, 10)) + np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + def testCrop2DDtype(self): # crop_2d must return an array of the same dtype it was given # in particular, because the method is used for Fourier downsampling From e194a63078145b615052117876c73a1dea6d1acd Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Tue, 1 Mar 2022 15:35:22 -0500 Subject: [PATCH 14/19] padding tests and fill value test --- tests/test_coor_trans.py | 169 ++++++++++++++++++++++----------------- 1 file changed, 95 insertions(+), 74 deletions(-) diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 9f514388d7..47daabf9e7 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -130,7 +130,7 @@ def testSquarePad2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) # even to odd - # the shift is towards lower x and y values + # the shift is towards the bottom and right # due to the centering convention a = np.zeros((8, 8)) np.fill_diagonal(a, np.arange(1, 9)) @@ -147,7 +147,7 @@ def testSquarePad2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) # odd to even - # the shift is towards higher x and y values + # the extra padding is to the top and left # due to the centering convention a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(1, 10)) @@ -156,85 +156,98 @@ def testSquarePad2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) def testRectCrop2D(self): - # test even/odd cases - # based on the choice that the center of a sequence of length n is (n+1)/2 - # if n is odd and n/2 + 1 if even. - - # even to even - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(8)) - # expand number of rows - a = np.vstack([a, np.zeros(8)]) - a = np.vstack([np.zeros(8), a]) - test_a = np.zeros((8, 8)) - np.fill_diagonal(test_a, np.arange(8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) + # Additional sanity checks for rectangular cropping case - # even to odd - # the crop gives us a[2:9,1:8] since we shift towards - # higher x and y values due to the centering convention - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(8)) - # expand number of rows - a = np.vstack([a, np.zeros(8)]) - a = np.vstack([np.zeros(8), a]) - test_a = np.zeros((7, 7)) - np.fill_diagonal(test_a, np.arange(1,8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + # 12x10 -> 10x10 + a = np.zeros((10, 10)) + np.fill_diagonal(a, np.arange(1, 11)) + # augment to 12 rows + aug = np.vstack([a, np.zeros(10)]) + aug = np.vstack([np.zeros(10), aug]) + # make sure the top and bottom rows are stripped + self.assertTrue(np.array_equal(a, crop_2d(aug, 10))) - # odd to odd - # the center is preserved - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(8)) - a = np.vstack([a, np.zeros(8)]) - test_a = np.zeros((7, 7)) - np.fill_diagonal(test_a, np.arange(1, 8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + # 10x12 -> 10x10 + a = np.zeros((10, 10)) + np.fill_diagonal(a, np.arange(1, 11)) + # augment to 12 columns + aug = np.column_stack([a, np.zeros(10)]) + aug = np.column_stack([np.zeros(10), aug]) + # make sure the left and right columns are stripped + self.assertTrue(np.array_equal(a, crop_2d(aug, 10))) - # odd to even - # the crop gives us a[:-1, :] since we shift towards - # lower x and y values due to the centering convention - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(9)) - a = np.vstack([a, np.zeros(8)]) - test_a = np.zeros((8, 8)) - np.fill_diagonal(test_a, np.arange(8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) + # 9x7 -> 7x7 + a = np.zeros((7, 7)) + np.fill_diagonal(a, np.arange(1, 8)) + # augment to 9 rows + aug = np.vstack([a, np.zeros(7)]) + aug = np.vstack([np.zeros(7), aug]) + # make sure the top and bottom rows are stripped + self.assertTrue(np.array_equal(a, crop_2d(aug, 7))) + + # 7x9 -> 7x7 + a = np.zeros((7, 7)) + np.fill_diagonal(a, np.arange(1, 8)) + # augment to 9 columns + aug = np.column_stack([a, np.zeros(7)]) + aug = np.column_stack([np.zeros(7), aug]) + # make sure the left and right columns are stripped + self.assertTrue(np.array_equal(a, crop_2d(aug, 7))) def testRectPad2D(self): - # test even/odd cases of padding operation of crop_2d - # even to even - # the center is preserved - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(1, 9)) - test_a = np.zeros((10, 10)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + # Additional sanity checks for rectangular padding case - # even to odd - # the shift is towards lower x and y values - # due to the centering convention - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(1, 9)) - test_a = np.zeros((11, 11)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) - # odd to odd - # the center is preserved - a = np.zeros((9, 9)) - np.fill_diagonal(a, np.arange(1, 10)) - test_a = np.zeros((11, 11)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) + # 12x10 -> 12x12 + a = np.zeros((10, 10)) + np.fill_diagonal(a, np.arange(1, 11)) + # augment to 12 rows + aug = np.vstack([a, np.zeros(10)]) + aug = np.vstack([np.zeros(10), aug]) + # expected result + padded = np.column_stack([aug, np.zeros(12)]) + padded = np.column_stack([np.zeros(12), padded]) + # make sure columns of fill value (0) are added to the + # left and right + self.assertTrue(np.array_equal(padded, crop_2d(aug, 12))) - # odd to even - # the shift is towards higher x and y values - # due to the centering convention - a = np.zeros((9, 9)) - np.fill_diagonal(a, np.arange(1, 10)) - test_a = np.zeros((10, 10)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + # 10x12 -> 12x12 + a = np.zeros((10, 10)) + np.fill_diagonal(a, np.arange(1, 11)) + # augment to 12 columns + aug = np.column_stack([a, np.zeros(10)]) + aug = np.column_stack([np.zeros(10), aug]) + # expected result + padded = np.vstack([aug, np.zeros(12)]) + padded = np.vstack([np.zeros(12), padded]) + # make sure rows of fill value (0) are added to the + # top and bottom + self.assertTrue(np.array_equal(padded, crop_2d(aug, 12))) + + # 9x7 -> 9x9 + a = np.zeros((7, 7)) + np.fill_diagonal(a, np.arange(1, 8)) + # augment to 9 rows + aug = np.vstack([a, np.zeros(7)]) + aug = np.vstack([np.zeros(7), aug]) + # expected result + padded = np.column_stack([aug, np.zeros(9)]) + padded = np.column_stack([np.zeros(9), padded]) + # make sure columns of fill value (0) are added to the + # left and right + self.assertTrue(np.array_equal(padded, crop_2d(aug, 9))) + + # 7x9 -> 9x9 + a = np.zeros((7, 7)) + np.fill_diagonal(a, np.arange(1, 8)) + # augment to 9 columns + aug = np.column_stack([a, np.zeros(7)]) + aug = np.column_stack([np.zeros(7), aug]) + # expected result + padded = np.vstack([aug, np.zeros(9)]) + padded = np.vstack([np.zeros(9), padded]) + # make sure rows of fill value (0) are added to the + # top and bottom + self.assertTrue(np.array_equal(padded, crop_2d(aug, 9))) def testCrop2DDtype(self): # crop_2d must return an array of the same dtype it was given @@ -243,3 +256,11 @@ def testCrop2DDtype(self): self.assertEqual( crop_2d(np.eye(10).astype("complex"), 5).dtype, np.dtype("complex128") ) + + def testCrop2DFillValue(self): + # make sure the fill value is as expected + # we are cropping from an odd to an even dimension + # so the padded column is added to the left + a = np.ones((4, 3)) + b = crop_2d(a, 4, fill_value=-1) + self.assertTrue(np.array_equal(b[:, 0], np.array([-1, -1, -1, -1]))) From e426e3f06764668eafdc8b1e320e09cc33388d70 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Wed, 2 Mar 2022 13:33:39 -0500 Subject: [PATCH 15/19] better comments --- src/aspire/utils/coor_trans.py | 4 +--- tests/test_coor_trans.py | 39 +++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index aa5cdb1413..6ee24d87fa 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -319,9 +319,6 @@ def crop_2d(mat, size, fill_value=0): start_y = math.floor(mat_y / 2) - math.floor(size / 2) # cropping - # start_x == 0 and start_y == 0 can be true for padding from n to n+1 - # when n is odd and for n->n-1 when n is odd. Adding the size check - # ensures that we discriminate between these two cases if size <= min(mat_x, mat_y): return mat[start_x : start_x + size, start_y : start_y + size] # padding @@ -331,4 +328,5 @@ def crop_2d(mat, size, fill_value=0): to_return[-start_x : mat_x - start_x, -start_y : mat_y - start_y] = mat return to_return else: + # target size is between mat_x and mat_y raise ValueError("Cannot crop and pad an image at the same time.") diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 47daabf9e7..d46ca1613c 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -80,12 +80,18 @@ def testRegisterRots(self): self.assertTrue(np.allclose(flag_est, flag) and np.allclose(q_mat_est, q_mat)) def testSquareCrop2D(self): - # test even/odd cases - # based on the choice that the center of a sequence of length n is (n+1)/2 - # if n is odd and n/2 + 1 if even. + # Test even/odd cases based on the convention that the center of a sequence of length n + # is (n+1)/2 if n is odd and n/2 + 1 if even. + # Cropping is done to keep the center of the sequence the same value before and after. + # Therefore the following apply: + # Cropping even to odd will result in the 0-index (beginning) + # of the sequence being chopped off (x marks the center, ~ marks deleted data): + # ---x-- => ~--x-- + # Cropping odd to even will result in the -1-index (end) + # of the sequence being chopped off: + # ---x--- => ---x--~ # even to even - # the center is preserved a = np.zeros((8, 8)) np.fill_diagonal(a, np.arange(8)) test_a = np.zeros((6, 6)) @@ -93,8 +99,8 @@ def testSquareCrop2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 6))) # even to odd - # the crop gives us a[1:,1:] since we shift towards - # higher x and y values due to the centering convention + # the extra row/column cut off are the top and left + # due to the centering convention a = np.zeros((8, 8)) np.fill_diagonal(a, np.arange(8)) test_a = np.zeros((7, 7)) @@ -102,7 +108,6 @@ def testSquareCrop2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) # odd to odd - # the center is preserved a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(9)) test_a = np.zeros((7, 7)) @@ -110,8 +115,8 @@ def testSquareCrop2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) # odd to even - # the crop gives us a[:8, :8] since we shift towards - # lower x and y values due to the centering convention + # the extra row/column cut off are the bottom and right + # due to the centering convention a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(9)) test_a = np.zeros((8, 8)) @@ -120,6 +125,13 @@ def testSquareCrop2D(self): def testSquarePad2D(self): # test even/odd cases of padding operation of crop_2d + # In general when padding from even to odd, the spare padding + # is added to the -1-end (end) of the sequence so that the center is preserved: + # (X represents the center, + represents padding) + # ---x-- => ---x--+ + # When padding from odd to even, the spare padding + # is added to the 0-end (beginning) of the sequence: + # --x-- => +--x-- # even to even # the center is preserved @@ -130,7 +142,7 @@ def testSquarePad2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) # even to odd - # the shift is towards the bottom and right + # the extra padding is to the bottom and right # due to the centering convention a = np.zeros((8, 8)) np.fill_diagonal(a, np.arange(1, 9)) @@ -249,6 +261,13 @@ def testRectPad2D(self): # top and bottom self.assertTrue(np.array_equal(padded, crop_2d(aug, 9))) + def testCropPad2DError(self): + with self.assertRaises(ValueError) as e: + _ = crop_2d(np.zeros((6, 10)), 8) + self.assertTrue( + "Cannot crop and pad an image at the same time.", str(e.exception) + ) + def testCrop2DDtype(self): # crop_2d must return an array of the same dtype it was given # in particular, because the method is used for Fourier downsampling From 2b2cc8f916b3740f7447f7fea6f669c0efa48b5b Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Wed, 2 Mar 2022 13:38:29 -0500 Subject: [PATCH 16/19] even better comments --- tests/test_coor_trans.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index d46ca1613c..3d9206cebd 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -124,17 +124,18 @@ def testSquareCrop2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) def testSquarePad2D(self): - # test even/odd cases of padding operation of crop_2d - # In general when padding from even to odd, the spare padding - # is added to the -1-end (end) of the sequence so that the center is preserved: - # (X represents the center, + represents padding) + # Test even/odd cases based on the convention that the center of a sequence of length n + # is (n+1)/2 if n is odd and n/2 + 1 if even. + # Padding is done to keep the center of the sequence the same value before and after. + # Therefore the following apply: + # Padding from even to odd results in the spare padding being added to the -1-index (end) + # of the sequence (x represents the center, + represents padding): # ---x-- => ---x--+ - # When padding from odd to even, the spare padding - # is added to the 0-end (beginning) of the sequence: + # Padding from odd to even results in the spare padding being added to the 0-index (beginning) + # of the sequence: # --x-- => +--x-- # even to even - # the center is preserved a = np.zeros((8, 8)) np.fill_diagonal(a, np.arange(1, 9)) test_a = np.zeros((10, 10)) @@ -151,7 +152,6 @@ def testSquarePad2D(self): self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) # odd to odd - # the center is preserved a = np.zeros((9, 9)) np.fill_diagonal(a, np.arange(1, 10)) test_a = np.zeros((11, 11)) @@ -278,7 +278,7 @@ def testCrop2DDtype(self): def testCrop2DFillValue(self): # make sure the fill value is as expected - # we are cropping from an odd to an even dimension + # we are padding from an odd to an even dimension # so the padded column is added to the left a = np.ones((4, 3)) b = crop_2d(a, 4, fill_value=-1) From 15bf644189e45a58e2af69ad51819899a7f2cd3b Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Fri, 4 Mar 2022 09:43:30 -0500 Subject: [PATCH 17/19] np.diag --- tests/test_coor_trans.py | 72 ++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 48 deletions(-) diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index 3d9206cebd..b5d7fe586d 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -92,35 +92,27 @@ def testSquareCrop2D(self): # ---x--- => ---x--~ # even to even - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(8)) - test_a = np.zeros((6, 6)) - np.fill_diagonal(test_a, np.arange(1, 7)) + a = np.diag(np.arange(8)) + test_a = np.diag(np.arange(1, 7)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 6))) # even to odd # the extra row/column cut off are the top and left # due to the centering convention - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(8)) - test_a = np.zeros((7, 7)) - np.fill_diagonal(test_a, np.arange(1, 8)) + a = np.diag(np.arange(8)) + test_a = np.diag(np.arange(1, 8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) # odd to odd - a = np.zeros((9, 9)) - np.fill_diagonal(a, np.arange(9)) - test_a = np.zeros((7, 7)) - np.fill_diagonal(test_a, np.arange(1, 8)) + a = np.diag(np.arange(9)) + test_a = np.diag(np.arange(1, 8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) # odd to even # the extra row/column cut off are the bottom and right # due to the centering convention - a = np.zeros((9, 9)) - np.fill_diagonal(a, np.arange(9)) - test_a = np.zeros((8, 8)) - np.fill_diagonal(test_a, np.arange(8)) + a = np.diag(np.arange(9)) + test_a = np.diag(np.arange(8)) self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) def testSquarePad2D(self): @@ -136,43 +128,34 @@ def testSquarePad2D(self): # --x-- => +--x-- # even to even - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(1, 9)) - test_a = np.zeros((10, 10)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0]) + a = np.diag(np.arange(1, 9)) + test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 0]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) # even to odd # the extra padding is to the bottom and right # due to the centering convention - a = np.zeros((8, 8)) - np.fill_diagonal(a, np.arange(1, 9)) - test_a = np.zeros((11, 11)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0]) + a = np.diag(np.arange(1, 9)) + test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) # odd to odd - a = np.zeros((9, 9)) - np.fill_diagonal(a, np.arange(1, 10)) - test_a = np.zeros((11, 11)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) + a = np.diag(np.arange(1, 10)) + test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) # odd to even # the extra padding is to the top and left # due to the centering convention - a = np.zeros((9, 9)) - np.fill_diagonal(a, np.arange(1, 10)) - test_a = np.zeros((10, 10)) - np.fill_diagonal(test_a, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + a = np.diag(np.arange(1, 10)) + test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) def testRectCrop2D(self): # Additional sanity checks for rectangular cropping case # 12x10 -> 10x10 - a = np.zeros((10, 10)) - np.fill_diagonal(a, np.arange(1, 11)) + a = np.diag(np.arange(1, 11)) # augment to 12 rows aug = np.vstack([a, np.zeros(10)]) aug = np.vstack([np.zeros(10), aug]) @@ -180,8 +163,7 @@ def testRectCrop2D(self): self.assertTrue(np.array_equal(a, crop_2d(aug, 10))) # 10x12 -> 10x10 - a = np.zeros((10, 10)) - np.fill_diagonal(a, np.arange(1, 11)) + a = np.diag(np.arange(1, 11)) # augment to 12 columns aug = np.column_stack([a, np.zeros(10)]) aug = np.column_stack([np.zeros(10), aug]) @@ -189,8 +171,7 @@ def testRectCrop2D(self): self.assertTrue(np.array_equal(a, crop_2d(aug, 10))) # 9x7 -> 7x7 - a = np.zeros((7, 7)) - np.fill_diagonal(a, np.arange(1, 8)) + a = np.diag(np.arange(1, 8)) # augment to 9 rows aug = np.vstack([a, np.zeros(7)]) aug = np.vstack([np.zeros(7), aug]) @@ -198,8 +179,7 @@ def testRectCrop2D(self): self.assertTrue(np.array_equal(a, crop_2d(aug, 7))) # 7x9 -> 7x7 - a = np.zeros((7, 7)) - np.fill_diagonal(a, np.arange(1, 8)) + a = np.diag(np.arange(1, 8)) # augment to 9 columns aug = np.column_stack([a, np.zeros(7)]) aug = np.column_stack([np.zeros(7), aug]) @@ -210,8 +190,7 @@ def testRectPad2D(self): # Additional sanity checks for rectangular padding case # 12x10 -> 12x12 - a = np.zeros((10, 10)) - np.fill_diagonal(a, np.arange(1, 11)) + a = np.diag(np.arange(1, 11)) # augment to 12 rows aug = np.vstack([a, np.zeros(10)]) aug = np.vstack([np.zeros(10), aug]) @@ -223,8 +202,7 @@ def testRectPad2D(self): self.assertTrue(np.array_equal(padded, crop_2d(aug, 12))) # 10x12 -> 12x12 - a = np.zeros((10, 10)) - np.fill_diagonal(a, np.arange(1, 11)) + a = np.diag(np.arange(1, 11)) # augment to 12 columns aug = np.column_stack([a, np.zeros(10)]) aug = np.column_stack([np.zeros(10), aug]) @@ -236,8 +214,7 @@ def testRectPad2D(self): self.assertTrue(np.array_equal(padded, crop_2d(aug, 12))) # 9x7 -> 9x9 - a = np.zeros((7, 7)) - np.fill_diagonal(a, np.arange(1, 8)) + a = np.diag(np.arange(1, 8)) # augment to 9 rows aug = np.vstack([a, np.zeros(7)]) aug = np.vstack([np.zeros(7), aug]) @@ -249,8 +226,7 @@ def testRectPad2D(self): self.assertTrue(np.array_equal(padded, crop_2d(aug, 9))) # 7x9 -> 9x9 - a = np.zeros((7, 7)) - np.fill_diagonal(a, np.arange(1, 8)) + a = np.diag(np.arange(1, 8)) # augment to 9 columns aug = np.column_stack([a, np.zeros(7)]) aug = np.column_stack([np.zeros(7), aug]) From bff3e646c41f9a0dd7184eb9b36110dbfd9f031e Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Thu, 10 Mar 2022 13:06:53 -0500 Subject: [PATCH 18/19] crop_pad_2d --- src/aspire/utils/__init__.py | 2 +- src/aspire/utils/coor_trans.py | 20 ++++++++-------- tests/test_coor_trans.py | 42 +++++++++++++++++----------------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/aspire/utils/__init__.py b/src/aspire/utils/__init__.py index 46140bc15e..8f88da3f0f 100644 --- a/src/aspire/utils/__init__.py +++ b/src/aspire/utils/__init__.py @@ -1,6 +1,6 @@ from .coor_trans import ( # isort:skip common_line_from_rots, - crop_2d, + crop_pad_2d, get_aligned_rotations, get_rots_mse, grid_1d, diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index 6ee24d87fa..324771a453 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -306,26 +306,26 @@ def common_line_from_rots(r1, r2, ell): return ell_ij, ell_ji -def crop_2d(mat, size, fill_value=0): +def crop_pad_2d(im, size, fill_value=0): """ - :param mat: A 2-dimensional numpy array + :param im: A 2-dimensional numpy array :param size: Integer size of cropped/padded output :return: A numpy array of shape (size, size) """ - mat_x, mat_y = mat.shape + im_y, im_x = im.shape # shift terms - start_x = math.floor(mat_x / 2) - math.floor(size / 2) - start_y = math.floor(mat_y / 2) - math.floor(size / 2) + start_x = math.floor(im_x / 2) - math.floor(size / 2) + start_y = math.floor(im_y / 2) - math.floor(size / 2) # cropping - if size <= min(mat_x, mat_y): - return mat[start_x : start_x + size, start_y : start_y + size] + if size <= min(im_y, im_x): + return im[start_y : start_y + size, start_x : start_x + size] # padding - elif size >= max(mat_x, mat_y): + elif size >= max(im_y, im_x): # ensure that we return in the same dtype as the input - to_return = fill_value * np.ones((size, size), dtype=mat.dtype) - to_return[-start_x : mat_x - start_x, -start_y : mat_y - start_y] = mat + to_return = fill_value * np.ones((size, size), dtype=im.dtype) + to_return[-start_y : im_y - start_y, -start_x : im_x - start_x] = im return to_return else: # target size is between mat_x and mat_y diff --git a/tests/test_coor_trans.py b/tests/test_coor_trans.py index b5d7fe586d..56603bb702 100644 --- a/tests/test_coor_trans.py +++ b/tests/test_coor_trans.py @@ -5,7 +5,7 @@ from aspire.utils import ( Rotation, - crop_2d, + crop_pad_2d, get_aligned_rotations, grid_2d, grid_3d, @@ -94,26 +94,26 @@ def testSquareCrop2D(self): # even to even a = np.diag(np.arange(8)) test_a = np.diag(np.arange(1, 7)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 6))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 6))) # even to odd # the extra row/column cut off are the top and left # due to the centering convention a = np.diag(np.arange(8)) test_a = np.diag(np.arange(1, 8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 7))) # odd to odd a = np.diag(np.arange(9)) test_a = np.diag(np.arange(1, 8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 7))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 7))) # odd to even # the extra row/column cut off are the bottom and right # due to the centering convention a = np.diag(np.arange(9)) test_a = np.diag(np.arange(8)) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 8))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 8))) def testSquarePad2D(self): # Test even/odd cases based on the convention that the center of a sequence of length n @@ -130,26 +130,26 @@ def testSquarePad2D(self): # even to even a = np.diag(np.arange(1, 9)) test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 10))) # even to odd # the extra padding is to the bottom and right # due to the centering convention a = np.diag(np.arange(1, 9)) test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 11))) # odd to odd a = np.diag(np.arange(1, 10)) test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 11))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 11))) # odd to even # the extra padding is to the top and left # due to the centering convention a = np.diag(np.arange(1, 10)) test_a = np.diag([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - self.assertTrue(np.array_equal(test_a, crop_2d(a, 10))) + self.assertTrue(np.array_equal(test_a, crop_pad_2d(a, 10))) def testRectCrop2D(self): # Additional sanity checks for rectangular cropping case @@ -160,7 +160,7 @@ def testRectCrop2D(self): aug = np.vstack([a, np.zeros(10)]) aug = np.vstack([np.zeros(10), aug]) # make sure the top and bottom rows are stripped - self.assertTrue(np.array_equal(a, crop_2d(aug, 10))) + self.assertTrue(np.array_equal(a, crop_pad_2d(aug, 10))) # 10x12 -> 10x10 a = np.diag(np.arange(1, 11)) @@ -168,7 +168,7 @@ def testRectCrop2D(self): aug = np.column_stack([a, np.zeros(10)]) aug = np.column_stack([np.zeros(10), aug]) # make sure the left and right columns are stripped - self.assertTrue(np.array_equal(a, crop_2d(aug, 10))) + self.assertTrue(np.array_equal(a, crop_pad_2d(aug, 10))) # 9x7 -> 7x7 a = np.diag(np.arange(1, 8)) @@ -176,7 +176,7 @@ def testRectCrop2D(self): aug = np.vstack([a, np.zeros(7)]) aug = np.vstack([np.zeros(7), aug]) # make sure the top and bottom rows are stripped - self.assertTrue(np.array_equal(a, crop_2d(aug, 7))) + self.assertTrue(np.array_equal(a, crop_pad_2d(aug, 7))) # 7x9 -> 7x7 a = np.diag(np.arange(1, 8)) @@ -184,7 +184,7 @@ def testRectCrop2D(self): aug = np.column_stack([a, np.zeros(7)]) aug = np.column_stack([np.zeros(7), aug]) # make sure the left and right columns are stripped - self.assertTrue(np.array_equal(a, crop_2d(aug, 7))) + self.assertTrue(np.array_equal(a, crop_pad_2d(aug, 7))) def testRectPad2D(self): # Additional sanity checks for rectangular padding case @@ -199,7 +199,7 @@ def testRectPad2D(self): padded = np.column_stack([np.zeros(12), padded]) # make sure columns of fill value (0) are added to the # left and right - self.assertTrue(np.array_equal(padded, crop_2d(aug, 12))) + self.assertTrue(np.array_equal(padded, crop_pad_2d(aug, 12))) # 10x12 -> 12x12 a = np.diag(np.arange(1, 11)) @@ -211,7 +211,7 @@ def testRectPad2D(self): padded = np.vstack([np.zeros(12), padded]) # make sure rows of fill value (0) are added to the # top and bottom - self.assertTrue(np.array_equal(padded, crop_2d(aug, 12))) + self.assertTrue(np.array_equal(padded, crop_pad_2d(aug, 12))) # 9x7 -> 9x9 a = np.diag(np.arange(1, 8)) @@ -223,7 +223,7 @@ def testRectPad2D(self): padded = np.column_stack([np.zeros(9), padded]) # make sure columns of fill value (0) are added to the # left and right - self.assertTrue(np.array_equal(padded, crop_2d(aug, 9))) + self.assertTrue(np.array_equal(padded, crop_pad_2d(aug, 9))) # 7x9 -> 9x9 a = np.diag(np.arange(1, 8)) @@ -235,21 +235,21 @@ def testRectPad2D(self): padded = np.vstack([np.zeros(9), padded]) # make sure rows of fill value (0) are added to the # top and bottom - self.assertTrue(np.array_equal(padded, crop_2d(aug, 9))) + self.assertTrue(np.array_equal(padded, crop_pad_2d(aug, 9))) def testCropPad2DError(self): with self.assertRaises(ValueError) as e: - _ = crop_2d(np.zeros((6, 10)), 8) + _ = crop_pad_2d(np.zeros((6, 10)), 8) self.assertTrue( "Cannot crop and pad an image at the same time.", str(e.exception) ) def testCrop2DDtype(self): - # crop_2d must return an array of the same dtype it was given + # crop_pad_2d must return an array of the same dtype it was given # in particular, because the method is used for Fourier downsampling # methods involving cropping complex arrays self.assertEqual( - crop_2d(np.eye(10).astype("complex"), 5).dtype, np.dtype("complex128") + crop_pad_2d(np.eye(10).astype("complex"), 5).dtype, np.dtype("complex128") ) def testCrop2DFillValue(self): @@ -257,5 +257,5 @@ def testCrop2DFillValue(self): # we are padding from an odd to an even dimension # so the padded column is added to the left a = np.ones((4, 3)) - b = crop_2d(a, 4, fill_value=-1) + b = crop_pad_2d(a, 4, fill_value=-1) self.assertTrue(np.array_equal(b[:, 0], np.array([-1, -1, -1, -1]))) From 4f02b160def187554cbc375ae11f84c13473b512 Mon Sep 17 00:00:00 2001 From: Chris Langfield Date: Fri, 18 Mar 2022 11:57:24 -0400 Subject: [PATCH 19/19] clarifying comment when padding --- src/aspire/utils/coor_trans.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/aspire/utils/coor_trans.py b/src/aspire/utils/coor_trans.py index 324771a453..a8ad8f2d40 100644 --- a/src/aspire/utils/coor_trans.py +++ b/src/aspire/utils/coor_trans.py @@ -325,6 +325,9 @@ def crop_pad_2d(im, size, fill_value=0): elif size >= max(im_y, im_x): # ensure that we return in the same dtype as the input to_return = fill_value * np.ones((size, size), dtype=im.dtype) + # when padding, start_x and start_y are negative since size is larger + # than im_x and im_y; the below line calculates where the original image + # is placed in relation to the (now-larger) box size to_return[-start_y : im_y - start_y, -start_x : im_x - start_x] = im return to_return else: