|
1 | 1 | import logging |
2 | 2 | import math |
3 | | -import os |
4 | 3 |
|
5 | 4 | import numpy as np |
6 | 5 | import scipy.sparse as sparse |
7 | 6 |
|
8 | 7 | from aspire.image import Image |
9 | 8 | from aspire.operators import PolarFT |
10 | | -from aspire.utils import Rotation, complex_type, fuzzy_mask, tqdm |
| 9 | +from aspire.utils import Rotation, fuzzy_mask |
11 | 10 | from aspire.utils.random import choice |
12 | 11 |
|
13 | 12 | from .commonline_utils import _generate_shift_phase_and_filter |
@@ -84,31 +83,7 @@ def __init__( |
84 | 83 | self.mask = mask |
85 | 84 | self._pf = None |
86 | 85 |
|
87 | | - # Sanity limit to match potential clmatrix dtype of int16. |
88 | | - if self.n_img > (2**15 - 1): |
89 | | - raise NotImplementedError( |
90 | | - "Commonlines implementation limited to <2**15 images." |
91 | | - ) |
92 | | - |
93 | | - # Auto configure GPU |
94 | | - self.__gpu_module = None |
95 | | - try: |
96 | | - import cupy as cp |
97 | | - |
98 | | - if cp.cuda.runtime.getDeviceCount() >= 1: |
99 | | - gpu_id = cp.cuda.runtime.getDevice() |
100 | | - logger.info( |
101 | | - f"cupy and GPU {gpu_id} found by cuda runtime; enabling cupy." |
102 | | - ) |
103 | | - self.__gpu_module = self.__init_cupy_module() |
104 | | - else: |
105 | | - logger.info("GPU not found, defaulting to numpy.") |
106 | | - |
107 | | - except ModuleNotFoundError: |
108 | | - logger.info("cupy not found, defaulting to numpy.") |
109 | | - |
110 | 86 | # Outputs |
111 | | - self.clmatrix = None |
112 | 87 | self.rotations = None |
113 | 88 | self.shifts = None |
114 | 89 |
|
@@ -172,25 +147,6 @@ def estimate_rotations(self): |
172 | 147 | """ |
173 | 148 | raise NotImplementedError("subclasses should implement this") |
174 | 149 |
|
175 | | - @property |
176 | | - def clmatrix(self): |
177 | | - """ |
178 | | - Returns Common Lines Matrix. |
179 | | -
|
180 | | - Computes if `clmatrix` is None. |
181 | | -
|
182 | | - :return: Common Lines Matrix |
183 | | - """ |
184 | | - if self._clmatrix is None: |
185 | | - self.build_clmatrix() |
186 | | - else: |
187 | | - logger.info("Using existing estimated `clmatrix`.") |
188 | | - return self._clmatrix |
189 | | - |
190 | | - @clmatrix.setter |
191 | | - def clmatrix(self, value): |
192 | | - self._clmatrix = value |
193 | | - |
194 | 150 | @property |
195 | 151 | def rotations(self): |
196 | 152 | """ |
@@ -229,228 +185,6 @@ def shifts(self): |
229 | 185 | def shifts(self, value): |
230 | 186 | self._shifts = value |
231 | 187 |
|
232 | | - def build_clmatrix(self): |
233 | | - """ |
234 | | - Build common-lines matrix from Fourier stack of 2D images |
235 | | -
|
236 | | - Wrapper for cpu/gpu dispatch. |
237 | | - """ |
238 | | - |
239 | | - logger.info("Begin building Common Lines Matrix") |
240 | | - |
241 | | - # host/gpu dispatch |
242 | | - if self.__gpu_module: |
243 | | - res = self.build_clmatrix_cu() |
244 | | - else: |
245 | | - res = self.build_clmatrix_host() |
246 | | - |
247 | | - # Unpack result |
248 | | - self._shifts_1d, self.clmatrix = res |
249 | | - |
250 | | - return self.clmatrix |
251 | | - |
252 | | - def build_clmatrix_host(self): |
253 | | - """ |
254 | | - Build common-lines matrix from Fourier stack of 2D images |
255 | | - """ |
256 | | - |
257 | | - n_img = self.n_img |
258 | | - n_check = self.n_check |
259 | | - |
260 | | - if self.n_theta % 2 == 1: |
261 | | - msg = "n_theta must be even" |
262 | | - logger.error(msg) |
263 | | - raise NotImplementedError(msg) |
264 | | - |
265 | | - n_theta_half = self.n_theta // 2 |
266 | | - |
267 | | - # need to do a copy to prevent modifying self.pf for other functions |
268 | | - pf = self.pf.copy() |
269 | | - |
270 | | - # Allocate local variables for return |
271 | | - # clmatrix represents the common lines matrix. |
272 | | - # Namely, clmatrix[i,j] contains the index in image i of |
273 | | - # the common line with image j. Note the common line index |
274 | | - # starts from 0 instead of 1 as Matlab version. -1 means |
275 | | - # there is no common line such as clmatrix[i,i]. |
276 | | - clmatrix = -np.ones((n_img, n_img), dtype=self.dtype) |
277 | | - # When cl_dist[i, j] is not -1, it stores the maximum value |
278 | | - # of correlation between image i and j for all possible 1D shifts. |
279 | | - # We will use cl_dist[i, j] = -1 (including j<=i) to |
280 | | - # represent that there is no need to check common line |
281 | | - # between i and j. Since it is symmetric, |
282 | | - # only above the diagonal entries are necessary. |
283 | | - cl_dist = -np.ones((n_img, n_img), dtype=self.dtype) |
284 | | - |
285 | | - # Allocate variables used for shift estimation |
286 | | - |
287 | | - # set maximum value of 1D shift (in pixels) to search |
288 | | - # between common-lines. |
289 | | - max_shift = self.max_shift |
290 | | - # Set resolution of shift estimation in pixels. Note that |
291 | | - # shift_step can be any positive real number. |
292 | | - shift_step = self.shift_step |
293 | | - # 1D shift between common-lines |
294 | | - shifts_1d = np.zeros((n_img, n_img)) |
295 | | - |
296 | | - # Prepare the shift phases to try and generate filter for common-line detection |
297 | | - r_max = pf.shape[2] |
298 | | - shifts, shift_phases, h = _generate_shift_phase_and_filter( |
299 | | - r_max, max_shift, shift_step, self.dtype |
300 | | - ) |
301 | | - |
302 | | - # Apply bandpass filter, normalize each ray of each image |
303 | | - # Note that only use half of each ray |
304 | | - pf = self._apply_filter_and_norm("ijk, k -> ijk", pf, r_max, h) |
305 | | - |
306 | | - # Setup a progress bar |
307 | | - _total_pairs_to_test = self.n_img * (self.n_check - 1) // 2 |
308 | | - pbar = tqdm(desc="Searching over common line pairs", total=_total_pairs_to_test) |
309 | | - |
310 | | - # Search for common lines between [i, j] pairs of images. |
311 | | - # Creating pf and building common lines are different to the Matlab version. |
312 | | - # The random selection is implemented. |
313 | | - for i in range(n_img - 1): |
314 | | - p1 = pf[i] |
315 | | - p1_real = np.real(p1) |
316 | | - p1_imag = np.imag(p1) |
317 | | - |
318 | | - # build the subset of j images if n_check < n_img |
319 | | - n_remaining = n_img - i - 1 |
320 | | - n_j = min(n_remaining, n_check) |
321 | | - subset_j = np.sort(choice(n_remaining, n_j, replace=False) + i + 1) |
322 | | - |
323 | | - for j in subset_j: |
324 | | - p2_flipped = np.conj(pf[j]) |
325 | | - |
326 | | - for shift in range(len(shifts)): |
327 | | - shift_phase = shift_phases[shift] |
328 | | - p2_shifted_flipped = (shift_phase * p2_flipped).T |
329 | | - # Compute correlations in the positive r direction |
330 | | - part1 = p1_real.dot(np.real(p2_shifted_flipped)) |
331 | | - # Compute correlations in the negative r direction |
332 | | - part2 = p1_imag.dot(np.imag(p2_shifted_flipped)) |
333 | | - |
334 | | - c1 = part1 - part2 |
335 | | - sidx = c1.argmax() |
336 | | - cl1, cl2 = np.unravel_index(sidx, c1.shape) |
337 | | - sval = c1[cl1, cl2] |
338 | | - |
339 | | - c2 = part1 + part2 |
340 | | - sidx = c2.argmax() |
341 | | - cl1_2, cl2_2 = np.unravel_index(sidx, c2.shape) |
342 | | - sval2 = c2[cl1_2, cl2_2] |
343 | | - |
344 | | - if sval2 > sval: |
345 | | - cl1 = cl1_2 |
346 | | - cl2 = cl2_2 + n_theta_half |
347 | | - sval = sval2 |
348 | | - sval = 2 * sval |
349 | | - if sval > cl_dist[i, j]: |
350 | | - clmatrix[i, j] = cl1 |
351 | | - clmatrix[j, i] = cl2 |
352 | | - cl_dist[i, j] = sval |
353 | | - shifts_1d[i, j] = shifts[shift] |
354 | | - pbar.update() |
355 | | - pbar.close() |
356 | | - |
357 | | - return shifts_1d, clmatrix |
358 | | - |
359 | | - def build_clmatrix_cu(self): |
360 | | - """ |
361 | | - Build common-lines matrix from Fourier stack of 2D images |
362 | | - """ |
363 | | - |
364 | | - import cupy as cp |
365 | | - |
366 | | - n_img = self.n_img |
367 | | - r = self.pf.shape[2] |
368 | | - |
369 | | - if self.n_theta % 2 == 1: |
370 | | - msg = "n_theta must be even" |
371 | | - logger.error(msg) |
372 | | - raise NotImplementedError(msg) |
373 | | - |
374 | | - # Copy to prevent modifying self.pf for other functions |
375 | | - # Simultaneously place on GPU |
376 | | - pf = cp.array(self.pf) |
377 | | - |
378 | | - # Allocate local variables for return |
379 | | - # clmatrix represents the common lines matrix. |
380 | | - # Namely, clmatrix[i,j] contains the index in image i of |
381 | | - # the common line with image j. Note the common line index |
382 | | - # starts from 0 instead of 1 as Matlab version. -1 means |
383 | | - # there is no common line such as clmatrix[i,i]. |
384 | | - clmatrix = -cp.ones((n_img, n_img), dtype=np.int16) |
385 | | - |
386 | | - # Allocate variables used for shift estimation |
387 | | - # |
388 | | - # Set maximum value of 1D shift (in pixels) to search |
389 | | - # between common-lines. |
390 | | - # Set resolution of shift estimation in pixels. Note that |
391 | | - # shift_step can be any positive real number. |
392 | | - # |
393 | | - # Prepare the shift phases to try and generate filter for common-line detection |
394 | | - # |
395 | | - # Note the CUDA implementation has been optimized to not |
396 | | - # compute or return diagnostic 1d shifts. |
397 | | - _, shift_phases, h = _generate_shift_phase_and_filter( |
398 | | - r, self.max_shift, self.shift_step, self.dtype |
399 | | - ) |
400 | | - # Transfer to device, dtypes must match kernel header. |
401 | | - shift_phases = cp.asarray(shift_phases, dtype=complex_type(self.dtype)) |
402 | | - |
403 | | - # Apply bandpass filter, normalize each ray of each image |
404 | | - # Note that this only uses half of each ray |
405 | | - pf = self._apply_filter_and_norm("ijk, k -> ijk", pf, r, h) |
406 | | - |
407 | | - # Tranpose `pf` for better (CUDA) memory access pattern, and cast as needed. |
408 | | - pf = cp.ascontiguousarray(pf.T, dtype=complex_type(self.dtype)) |
409 | | - |
410 | | - # Get kernel |
411 | | - if self.dtype == np.float64: |
412 | | - build_clmatrix_kernel = self.__gpu_module.get_function( |
413 | | - "build_clmatrix_kernel" |
414 | | - ) |
415 | | - elif self.dtype == np.float32: |
416 | | - build_clmatrix_kernel = self.__gpu_module.get_function( |
417 | | - "fbuild_clmatrix_kernel" |
418 | | - ) |
419 | | - else: |
420 | | - raise NotImplementedError( |
421 | | - "build_clmatrix_kernel only implemented for float32 and float64." |
422 | | - ) |
423 | | - |
424 | | - # Configure grid of blocks |
425 | | - blkszx = 32 |
426 | | - # Enough blocks to cover n_img-1 |
427 | | - nblkx = (self.n_img + blkszx - 2) // blkszx |
428 | | - blkszy = 32 |
429 | | - # Enough blocks to cover n_img |
430 | | - nblky = (self.n_img + blkszy - 1) // blkszy |
431 | | - |
432 | | - # Launch |
433 | | - logger.info("Launching `build_clmatrix_kernel`.") |
434 | | - build_clmatrix_kernel( |
435 | | - (nblkx, nblky), |
436 | | - (blkszx, blkszy), |
437 | | - ( |
438 | | - n_img, |
439 | | - pf.shape[1], |
440 | | - r, |
441 | | - pf, |
442 | | - clmatrix, |
443 | | - len(shift_phases), |
444 | | - shift_phases, |
445 | | - ), |
446 | | - ) |
447 | | - |
448 | | - # Copy result device arrays to host |
449 | | - clmatrix = clmatrix.get().astype(self.dtype, copy=False) |
450 | | - |
451 | | - # Note diagnostic 1d shifts are not computed in the CUDA implementation. |
452 | | - return None, clmatrix |
453 | | - |
454 | 188 | def estimate_shifts(self, equations_factor=1, max_memory=4000): |
455 | 189 | """ |
456 | 190 | Estimate 2D shifts in images |
@@ -757,27 +491,3 @@ def _apply_filter_and_norm(self, subscripts, pf, r_max, h): |
757 | 491 | pf /= np.linalg.norm(pf, axis=-1)[..., np.newaxis] |
758 | 492 |
|
759 | 493 | return pf |
760 | | - |
761 | | - @staticmethod |
762 | | - def __init_cupy_module(): |
763 | | - """ |
764 | | - Private utility method to read in CUDA source and return as |
765 | | - compiled CuPy module. |
766 | | - """ |
767 | | - |
768 | | - import cupy as cp |
769 | | - |
770 | | - # Read in contents of file |
771 | | - fp = os.path.join(os.path.dirname(__file__), "commonline_base.cu") |
772 | | - with open(fp, "r") as fh: |
773 | | - module_code = fh.read() |
774 | | - |
775 | | - # CuPy compile the CUDA code |
776 | | - # Note these optimizations are to steer aggresive optimization |
777 | | - # for single precision code. Fast math will potentionally |
778 | | - # reduce accuracy in single precision. |
779 | | - return cp.RawModule( |
780 | | - code=module_code, |
781 | | - backend="nvcc", |
782 | | - options=("-O3", "--use_fast_math", "--extra-device-vectorization"), |
783 | | - ) |
0 commit comments