Coverage for src/ufig/plugins/render_stars_photon.py: 92%
144 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-12 19:08 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-12 19:08 +0000
1# Copyright (c) 2017 ETH Zurich, Cosmology Research Group
2"""
3Created on Jul 31, 2017
4@author: Joerg Herbel
5"""
7import numba as nb
8import numpy as np
9from cosmic_toolbox.logger import get_logger
10from ivy.plugin.base_plugin import BasePlugin
12from ufig import rendering_util
13from ufig.plugins import add_psf
15LOGGER = get_logger(__file__)
17NAME = "render stars photon-based"
20@nb.jit(nopython=True, nogil=True)
21def integrate_image_phot(
22 buffer,
23 image,
24 x,
25 y,
26 nphot,
27 psf_betas,
28 ellip_matrices,
29 flexion_tensors,
30 kurtoses,
31 dx_offset,
32 dy_offset,
33 flexion_suppression,
34 sort_by_y,
35):
36 sin_table, cos_table = rendering_util.sin_cos_table()
37 alpha_by_fwhm, beta_power = rendering_util.moffat_cdf_factors(psf_betas)
39 ind_stars = np.argsort(y) if sort_by_y else np.arange(len(y))
41 rng = 0
43 for i_star in ind_stars:
44 for i_beta in range(psf_betas.shape[1]):
45 flexion_suppress = rendering_util.psf_flexion_suppression(
46 psf_betas[i_star, i_beta], flexion_suppression
47 )
48 kurtosis_suppress = rendering_util.psf_kurtosis_suppression(
49 psf_betas[i_star, i_beta]
50 )
52 for _ in range(nphot[i_star, i_beta]):
53 dr, dx, dy = rendering_util.draw_photon_psf(
54 buffer,
55 sin_table,
56 cos_table,
57 alpha_by_fwhm[i_star, i_beta],
58 beta_power[i_star, i_beta],
59 rng,
60 ellip_matrices[i_star],
61 flexion_tensors[i_star],
62 kurtoses[i_star, i_beta],
63 flexion_suppress,
64 kurtosis_suppress,
65 dx_offset[i_star, i_beta],
66 dy_offset[i_star, i_beta],
67 )
69 rendering_util.add_photon(image, x[i_star], y[i_star], dx, dy)
71 rng += 2
72 if rng + 2 > 44497:
73 buffer[:21034] += buffer[(44497 - 21034) : 44497]
74 buffer[21034:44497] += buffer[: (44497 - 21034)]
75 rng = 0
78@nb.jit(nopython=True)
79def pixel_integration(
80 seed,
81 image,
82 r_max,
83 x,
84 y,
85 nphot,
86 psf_beta,
87 psf_r50,
88 psf_e1,
89 psf_e2,
90 psf_dx_offset,
91 psf_dy_offset,
92):
93 # Seed within numba-compiled code, otherwise this code won't be affected
94 np.random.seed(seed)
96 size_y, size_x = image.shape
97 alpha_sq = 1.0 / (2.0 ** (1.0 / (psf_beta - 1.0)) - 1.0)
99 # bounds for x axis
100 center_x = x.astype(np.int_)
101 offset_x = x - np.floor(x)
102 min_x = -r_max
103 idx = center_x < r_max
104 min_x[idx] = -center_x[idx]
105 max_x = (
106 r_max.copy()
107 ) # otherwise it will be a pointer, which overwritten and then re-used
108 idx = size_x - r_max <= center_x
109 max_x[idx] = size_x - 1 - center_x[idx]
111 # bounds for y axis
112 center_y = y.astype(np.int_)
113 offset_y = y - np.floor(y)
114 min_y = -r_max
115 idy = center_y < r_max
116 min_y[idy] = -center_y[idy]
117 max_y = (
118 r_max.copy()
119 ) # otherwise it will be a pointer, which overwritten and then re-used
120 idy = size_y - r_max <= center_y
121 max_y[idy] = size_y - 1 - center_y[idy]
123 for i in range(len(x)):
124 star = np.zeros(
125 (max_y[i] - min_y[i] + 1, max_x[i] - min_x[i] + 1), dtype=image.dtype
126 )
128 if star.shape == (1, 1): 128 ↛ 129line 128 didn't jump to line 129 because the condition on line 128 was never true
129 continue
131 rendering_util.integrate_pixel_psf(
132 alpha_sq[i],
133 psf_beta[i],
134 psf_r50[i],
135 psf_e1[i],
136 psf_e2[i],
137 psf_dx_offset[i],
138 psf_dy_offset[i],
139 nphot[i],
140 min_x[i],
141 max_x[i],
142 min_y[i],
143 max_y[i],
144 offset_x[i],
145 offset_y[i],
146 star,
147 )
149 image[
150 min_y[i] + center_y[i] : max_y[i] + 1 + center_y[i],
151 min_x[i] + center_x[i] : max_x[i] + 1 + center_x[i],
152 ] += star
155def integrate_image_pixel(
156 seed,
157 image,
158 gain,
159 render_stars_accuracy,
160 x,
161 y,
162 nphot,
163 psf_beta,
164 psf_flux_ratio,
165 psf_fwhm,
166 psf_e1,
167 psf_e2,
168 psf_dx_offset,
169 psf_dy_offset,
170):
171 # integrate a bigger radius for brighter stars
172 index_min = np.argmin(psf_beta, axis=1)
173 beta_min = psf_beta[np.arange(psf_beta.shape[0]), index_min]
175 select = index_min > 0
176 flux_ratio_min = psf_flux_ratio.copy()
177 flux_ratio_min[select] = 1 - flux_ratio_min[select]
179 alpha_min = add_psf.moffat_fwhm2alpha(psf_fwhm, beta_min)
180 flux = (
181 nphot
182 * flux_ratio_min
183 * (beta_min - 1)
184 / (np.pi * gain)
185 * alpha_min ** (2 * (beta_min - 1))
186 )
187 r_max = ((flux / render_stars_accuracy) ** (1.0 / (2 * beta_min))).astype(np.int_)
188 r_max += 2
190 # Distribute photons to individual Moffat profiles
191 nphot_split = rendering_util.distribute_photons_psf_profiles(
192 nphot, psf_beta.shape[1], psf_flux_ratio
193 )
195 # Get r50
196 psf_r50 = np.empty_like(psf_beta)
198 for beta_ind in range(psf_beta.shape[1]):
199 psf_r50[:, beta_ind] = add_psf.moffat_fwhm2r50(
200 psf_fwhm, psf_beta[:, beta_ind], psf_flux_ratio
201 )
203 pixel_integration(
204 seed,
205 image,
206 r_max,
207 x,
208 y,
209 nphot_split,
210 psf_beta,
211 psf_r50,
212 psf_e1,
213 psf_e2,
214 psf_dx_offset,
215 psf_dy_offset,
216 )
219@nb.jit(nopython=True)
220def integrate_cube(
221 buffer,
222 cube,
223 x,
224 y,
225 nphot,
226 psf_betas,
227 ellip_matrices,
228 flexion_tensors,
229 kurtoses,
230 dx_offset,
231 dy_offset,
232 flexion_suppression,
233 q_xx,
234 q_yy,
235 q_xy,
236):
237 sin_table, cos_table = rendering_util.sin_cos_table()
238 alpha_by_fwhm, beta_power = rendering_util.moffat_cdf_factors(psf_betas)
240 rng = 0
241 cov_cutoff = 5
243 for i_star in range(x.size):
244 n_phot_cov = 0
245 mean_phot_x = 0.0
246 mean_phot_y = 0.0
248 for i_beta in range(psf_betas.shape[1]):
249 flexion_suppress = rendering_util.psf_flexion_suppression(
250 psf_betas[i_star, i_beta], flexion_suppression
251 )
252 kurtosis_suppress = rendering_util.psf_kurtosis_suppression(
253 psf_betas[i_star, i_beta]
254 )
256 for _ in range(nphot[i_star, i_beta]):
257 dr, dx, dy = rendering_util.draw_photon_psf(
258 buffer,
259 sin_table,
260 cos_table,
261 alpha_by_fwhm[i_star, i_beta],
262 beta_power[i_star, i_beta],
263 rng,
264 ellip_matrices[i_star],
265 flexion_tensors[i_star],
266 kurtoses[i_star, i_beta],
267 flexion_suppress,
268 kurtosis_suppress,
269 dx_offset[i_star, i_beta],
270 dy_offset[i_star, i_beta],
271 )
273 rendering_util.add_photon(cube[i_star], x[i_star], y[i_star], dx, dy)
275 rng += 2
276 if rng + 2 > 44497: 276 ↛ 277line 276 didn't jump to line 277 because the condition on line 276 was never true
277 buffer[:21034] += buffer[(44497 - 21034) : 44497]
278 buffer[21034:44497] += buffer[: (44497 - 21034)]
279 rng = 0
281 # moments
282 if dr < cov_cutoff:
283 n_phot_cov += 1
284 delta_x = dx - mean_phot_x
285 delta_y = dy - mean_phot_y
286 mean_phot_x += delta_x / n_phot_cov
287 mean_phot_y += delta_y / n_phot_cov
288 delta_y_2 = dy - mean_phot_y
289 q_xx[i_star] += delta_x * (dx - mean_phot_x)
290 q_yy[i_star] += delta_y * delta_y_2
291 q_xy[i_star] += delta_x * delta_y_2
293 if n_phot_cov > 1: 293 ↛ 243line 293 didn't jump to line 243 because the condition on line 293 was always true
294 q_xx[i_star] /= n_phot_cov - 1
295 q_yy[i_star] /= n_phot_cov - 1
296 q_xy[i_star] /= n_phot_cov - 1
299def integrate_threaded(
300 ctx,
301 x,
302 y,
303 nphot,
304 psf_beta,
305 psf_ellip_matrices,
306 psf_flexion_tensors,
307 psf_kurtoses,
308 psf_dx_offset,
309 psf_dy_offset,
310 flexion_suppression,
311):
312 n_threads = ctx.parameters.n_threads_photon_rendering
314 # Split workload between threads
315 ind_split = rendering_util.split_array(x, n_threads)
316 image = ctx.image
317 ctx.image = None
319 rendering_args_split = [
320 [rendering_util.rng_buffer() for _ in range(n_threads)],
321 [image for _ in range(n_threads)],
322 [x[i_split] for i_split in ind_split],
323 [y[i_split] for i_split in ind_split],
324 [nphot[i_split] for i_split in ind_split],
325 [psf_beta[i_split] for i_split in ind_split],
326 [psf_ellip_matrices[i_split] for i_split in ind_split],
327 [psf_flexion_tensors[i_split] for i_split in ind_split],
328 [psf_kurtoses[i_split] for i_split in ind_split],
329 [psf_dx_offset[i_split] for i_split in ind_split],
330 [psf_dy_offset[i_split] for i_split in ind_split],
331 [flexion_suppression for _ in range(n_threads)],
332 [True for _ in range(n_threads)],
333 ]
335 # Run in threads
336 # print('Number of threads used for rendering stars: {}'.format(n_threads))
337 rendering_util.execute_threaded(
338 integrate_image_phot, n_threads, *rendering_args_split
339 )
340 ctx.image = image
343class Plugin(BasePlugin):
344 """
345 Render stellar profiles photon-by-photon onto a pixelated grid.
346 """
348 def __str__(self):
349 return NAME
351 def __call__(self):
352 par = self.ctx.parameters
354 LOGGER.info(f"Rendering {self.ctx.numstars} stars")
356 # Seed
357 current_seed = par.seed + par.star_render_seed_offset
358 np.random.seed(par.seed + par.star_render_seed_offset)
360 # PSF properties
361 if self.ctx.stars.psf_beta.shape[1] == 1:
362 self.ctx.stars.psf_flux_ratio = np.ones(self.ctx.numstars)
364 psf_ellip_matrices = rendering_util.compute_ellip_matrices(
365 self.ctx.stars.psf_fwhm, self.ctx.stars.psf_e1, self.ctx.stars.psf_e2
366 )
368 psf_flexion_tensors = rendering_util.compute_flexion_tensors(
369 self.ctx.stars.psf_fwhm,
370 self.ctx.stars.psf_f1,
371 self.ctx.stars.psf_f2,
372 self.ctx.stars.psf_g1,
373 self.ctx.stars.psf_g2,
374 )
376 psf_kurtoses = rendering_util.compute_kurtoses(
377 self.ctx.stars.psf_fwhm,
378 self.ctx.stars.psf_kurtosis,
379 self.ctx.stars.psf_beta,
380 )
382 # Calculate number of photons
383 nphot = rendering_util.distribute_photons_psf_profiles(
384 self.ctx.stars.nphot,
385 self.ctx.stars.psf_beta.shape[1],
386 self.ctx.stars.psf_flux_ratio,
387 )
389 if hasattr(self.ctx, "image"):
390 if hasattr(par, "mag_pixel_rendering_stars"):
391 mask_pix = self.ctx.stars.mag < par.mag_pixel_rendering_stars
392 mask_phot = ~mask_pix
394 if np.any(mask_phot): 394 ↛ 395line 394 didn't jump to line 395 because the condition on line 394 was never true
395 if par.n_threads_photon_rendering > 1:
396 integrate_threaded(
397 self.ctx,
398 self.ctx.stars.x[mask_phot],
399 self.ctx.stars.y[mask_phot],
400 nphot[mask_phot],
401 self.ctx.stars.psf_beta[mask_phot],
402 psf_ellip_matrices[mask_phot],
403 psf_flexion_tensors[mask_phot],
404 psf_kurtoses[mask_phot],
405 self.ctx.stars.psf_dx_offset[mask_phot],
406 self.ctx.stars.psf_dy_offset[mask_phot],
407 par.psf_flexion_suppression,
408 )
410 else:
411 integrate_image_phot(
412 rendering_util.rng_buffer(),
413 self.ctx.image,
414 self.ctx.stars.x[mask_phot],
415 self.ctx.stars.y[mask_phot],
416 nphot[mask_phot],
417 self.ctx.stars.psf_beta[mask_phot],
418 psf_ellip_matrices[mask_phot],
419 psf_flexion_tensors[mask_phot],
420 psf_kurtoses[mask_phot],
421 self.ctx.stars.psf_dx_offset[mask_phot],
422 self.ctx.stars.psf_dy_offset[mask_phot],
423 par.psf_flexion_suppression,
424 False,
425 )
427 if np.any(mask_pix):
428 integrate_image_pixel(
429 current_seed,
430 self.ctx.image,
431 par.gain,
432 par.render_stars_accuracy,
433 self.ctx.stars.x[mask_pix],
434 self.ctx.stars.y[mask_pix],
435 self.ctx.stars.nphot[mask_pix],
436 self.ctx.stars.psf_beta[mask_pix],
437 self.ctx.stars.psf_flux_ratio[mask_pix],
438 self.ctx.stars.psf_fwhm[mask_pix],
439 self.ctx.stars.psf_e1[mask_pix],
440 self.ctx.stars.psf_e2[mask_pix],
441 self.ctx.stars.psf_dx_offset[mask_pix],
442 self.ctx.stars.psf_dy_offset[mask_pix],
443 )
445 else:
446 if par.n_threads_photon_rendering > 1:
447 integrate_threaded(
448 self.ctx,
449 self.ctx.stars.x,
450 self.ctx.stars.y,
451 nphot,
452 self.ctx.stars.psf_beta,
453 psf_ellip_matrices,
454 psf_flexion_tensors,
455 psf_kurtoses,
456 self.ctx.stars.psf_dx_offset,
457 self.ctx.stars.psf_dy_offset,
458 par.psf_flexion_suppression,
459 )
461 else:
462 integrate_image_phot(
463 rendering_util.rng_buffer(),
464 self.ctx.image,
465 self.ctx.stars.x,
466 self.ctx.stars.y,
467 nphot,
468 self.ctx.stars.psf_beta,
469 psf_ellip_matrices,
470 psf_flexion_tensors,
471 psf_kurtoses,
472 self.ctx.stars.psf_dx_offset,
473 self.ctx.stars.psf_dy_offset,
474 par.psf_flexion_suppression,
475 False,
476 )
478 elif hasattr(self.ctx, "star_cube"): 478 ↛ 503line 478 didn't jump to line 503 because the condition on line 478 was always true
479 # Initialize moments
480 self.ctx.stars.q_xx = np.zeros(self.ctx.numstars)
481 self.ctx.stars.q_yy = np.zeros(self.ctx.numstars)
482 self.ctx.stars.q_xy = np.zeros(self.ctx.numstars)
484 integrate_cube(
485 rendering_util.rng_buffer(),
486 self.ctx.star_cube,
487 self.ctx.stars.x,
488 self.ctx.stars.y,
489 nphot,
490 self.ctx.stars.psf_beta,
491 psf_ellip_matrices,
492 psf_flexion_tensors,
493 psf_kurtoses,
494 self.ctx.stars.psf_dx_offset,
495 self.ctx.stars.psf_dy_offset,
496 par.psf_flexion_suppression,
497 self.ctx.stars.q_xx,
498 self.ctx.stars.q_yy,
499 self.ctx.stars.q_xy,
500 )
502 else:
503 raise RuntimeError(
504 "No known photon container (image or star_cube) provided in ivy context"
505 )