|
3 | 3 | import numpy as np |
4 | 4 |
|
5 | 5 | from aspire.image import Image |
6 | | -from aspire.volume import Volume |
7 | 6 | from aspire.utils import gaussian_2d, utest_tolerance |
8 | | -from aspire.utils.coor_trans import grid_2d |
| 7 | +from aspire.utils.coor_trans import grid_2d, grid_3d |
| 8 | +from aspire.utils.misc import gaussian_3d |
9 | 9 | from aspire.utils.random import randn |
| 10 | +from aspire.volume import Volume |
10 | 11 |
|
11 | 12 |
|
12 | 13 | class SteerableMixin: |
@@ -139,4 +140,90 @@ def testModulated(self): |
139 | 140 |
|
140 | 141 |
|
141 | 142 | class Steerable3DMixin(SteerableMixin): |
142 | | - pass |
| 143 | + def testIndices(self): |
| 144 | + ell_max = self.basis.ell_max |
| 145 | + k_max = self.basis.k_max |
| 146 | + |
| 147 | + indices = self.basis.indices() |
| 148 | + |
| 149 | + i = 0 |
| 150 | + |
| 151 | + for ell in range(ell_max + 1): |
| 152 | + for m in range(-ell, ell + 1): |
| 153 | + for k in range(k_max[ell]): |
| 154 | + self.assertTrue(indices["ells"][i] == ell) |
| 155 | + self.assertTrue(indices["ms"][i] == m) |
| 156 | + self.assertTrue(indices["ks"][i] == k) |
| 157 | + |
| 158 | + i += 1 |
| 159 | + |
| 160 | + def testGaussianExpand(self): |
| 161 | + # Offset slightly |
| 162 | + x0 = 0.50 |
| 163 | + y0 = 0.75 |
| 164 | + z0 = 0.25 |
| 165 | + |
| 166 | + # Want sigma to be as large as possible without the Gaussian |
| 167 | + # spilling too much outside the central disk. |
| 168 | + sigma = self.L / 8 |
| 169 | + vol1 = gaussian_3d( |
| 170 | + self.L, mu=(x0, y0, z0), sigma=(sigma, sigma, sigma), dtype=self.dtype |
| 171 | + ) |
| 172 | + |
| 173 | + coef = self.basis.expand(vol1) |
| 174 | + vol2 = self.basis.evaluate(coef) |
| 175 | + |
| 176 | + if isinstance(vol2, Volume): |
| 177 | + vol2 = vol2.asnumpy() |
| 178 | + |
| 179 | + # For small L there's too much clipping at high freqs to get 1e-3 |
| 180 | + # accuracy. |
| 181 | + if self.L < 16: |
| 182 | + atol = 1e-1 |
| 183 | + elif self.L < 32: |
| 184 | + atol = 1e-2 |
| 185 | + else: |
| 186 | + atol = 1e-3 |
| 187 | + |
| 188 | + self.assertTrue(vol1.shape == vol2.shape) |
| 189 | + self.assertTrue(np.allclose(vol1, vol2, atol=atol)) |
| 190 | + |
| 191 | + def testIsotropic(self): |
| 192 | + sigma = self.L / 8 |
| 193 | + im = gaussian_3d(self.L, sigma=(sigma, sigma, sigma), dtype=self.dtype) |
| 194 | + |
| 195 | + coef = self.basis.expand(im) |
| 196 | + |
| 197 | + ells = self.basis.indices()["ells"] |
| 198 | + |
| 199 | + energy_outside = np.sum(np.abs(coef[ells != 0]) ** 2) |
| 200 | + energy_total = np.sum(np.abs(coef) ** 2) |
| 201 | + |
| 202 | + energy_ratio = energy_outside / energy_total |
| 203 | + |
| 204 | + self.assertTrue(energy_ratio < 0.01) |
| 205 | + |
| 206 | + def testModulated(self): |
| 207 | + if self.L < 32: |
| 208 | + raise SkipTest |
| 209 | + |
| 210 | + ell = 1 |
| 211 | + |
| 212 | + sigma = self.L / 8 |
| 213 | + vol = gaussian_3d(self.L, sigma=(sigma, sigma, sigma), dtype=self.dtype) |
| 214 | + |
| 215 | + g3d = grid_3d(self.L) |
| 216 | + |
| 217 | + for trig_fun in (np.sin, np.cos): |
| 218 | + vol1 = vol * trig_fun(ell * g3d["phi"]) |
| 219 | + |
| 220 | + coef = self.basis.expand(vol1) |
| 221 | + |
| 222 | + ells = self.basis.indices()["ells"] |
| 223 | + |
| 224 | + energy_outside = np.sum(np.abs(coef[ells != ell]) ** 2) |
| 225 | + energy_total = np.sum(np.abs(coef) ** 2) |
| 226 | + |
| 227 | + energy_ratio = energy_outside / energy_total |
| 228 | + |
| 229 | + self.assertTrue(energy_ratio < 0.10) |
0 commit comments