Skip to content

Commit 47f9dd7

Browse files
authored
Merge pull request #432 from timedilatesme/supernovae_microlensing
notebook to generate visit images faster with precomputed backgrounds
2 parents e4ff156 + 290d629 commit 47f9dd7

6 files changed

Lines changed: 1722 additions & 28 deletions

File tree

notebooks/ERL_lensed_AGN_sims.ipynb renamed to notebooks/MainNotebooks/ERL_lensed_AGN_sims.ipynb

Lines changed: 34 additions & 8 deletions
Large diffs are not rendered by default.

notebooks/ERL_lensed_SNe_sims.ipynb renamed to notebooks/MainNotebooks/ERL_lensed_SNe_sims.ipynb

Lines changed: 47 additions & 8 deletions
Large diffs are not rendered by default.

notebooks/MainNotebooks/visualize_lSNe_lAGN_visit_images_with_microlensing.ipynb

Lines changed: 1472 additions & 0 deletions
Large diffs are not rendered by default.

notebooks/MainNotebooks/Euclid_Roman_LSST_LAGN_pop_with_variability.ipynb renamed to notebooks/lensed-AGN-Notebooks/Euclid_Roman_LSST_LAGN_pop_with_variability.ipynb

File renamed without changes.

slsim/ImageSimulation/roman_image_simulation.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def simulate_roman_image(
5050
dec=None,
5151
date=datetime.datetime(year=2027, month=7, day=7, hour=0, minute=0, second=0),
5252
psf_directory=None,
53+
precomputed_background=None,
5354
):
5455
"""Creates a Roman-simulated image of a selected lens with noise, using
5556
galsim's noise settings and PSFs from STPSF.
@@ -101,6 +102,9 @@ def simulate_roman_image(
101102
Otherwise, the psf will be generated by stpsf on the fly, which is very slow.
102103
See the note in the ``get_psf`` method's docstring for details on the PSF file naming convention.
103104
:type psf_directory: str
105+
:param precomputed_background: A 2-D numpy array of background counts, shape (num_pix+6, num_pix+6), to be added to the image before noise realizations. This can be computed by calling precompute_roman_background with the same band, num_pix, oversample, exposure_time, and date parameters as passed into this function. If None, the background will be computed on the fly.
106+
:type precomputed_background: numpy.ndarray, optional
107+
104108
:return: simulated image in units of flux per second.
105109
:rtype: numpy.ndarray
106110
"""
@@ -182,18 +186,25 @@ def simulate_roman_image(
182186
image_no_noise = convolved.drawImage(im)
183187

184188
if add_noise:
185-
# Obtain sky and thermal background corresponding to certain band and add it to the image
186-
# Poisson noise realization is not handled until later
187-
image_with_background = add_sky_plus_thermal_background(
188-
image_no_noise,
189-
band,
190-
detector,
191-
num_pix,
192-
_exposure_time,
193-
ra,
194-
dec,
195-
date,
196-
)
189+
if precomputed_background is not None:
190+
# Convert precomputed numpy array to a galsim Image with the same
191+
# scale and origin as image_no_noise so the addition is element-wise.
192+
bg = galsim.ImageF(precomputed_background.astype(np.float32), scale=0.11)
193+
bg.setOrigin(0, 0)
194+
image_with_background = image_no_noise + bg
195+
else:
196+
# Obtain sky and thermal background corresponding to certain band and add it to the image
197+
# Poisson noise realization is not handled until later
198+
image_with_background = add_sky_plus_thermal_background(
199+
image_no_noise,
200+
band,
201+
detector,
202+
num_pix,
203+
_exposure_time,
204+
ra,
205+
dec,
206+
date,
207+
)
197208

198209
# Add noise realizations and detector effects
199210
# Need to handle each exposure separately to properly take into account read noise and persistence
@@ -420,3 +431,56 @@ def _get_wcs_dict(ra, dec, date):
420431

421432
# NB targ_pos indicates the position to observe at the center of the focal plane array
422433
return roman.getWCS(world_pos=targ_pos, date=date)
434+
435+
436+
def precompute_roman_background(
437+
band,
438+
num_pix,
439+
exposure_time,
440+
detector=None,
441+
detector_pos=None,
442+
ra=None,
443+
dec=None,
444+
date=datetime.datetime(year=2027, month=7, day=7),
445+
oversample=3,
446+
):
447+
"""Precompute the Roman sky + thermal background as a numpy array.
448+
449+
Calls add_sky_plus_thermal_background with a zero image so no lens
450+
computation is involved. Pass the returned array to
451+
simulate_roman_image via precomputed_background to skip the WCS /
452+
sky-level computation on every call.
453+
454+
:param band: imaging band (e.g. 'F106').
455+
:param num_pix: pixels per axis — must match simulate_roman_image.
456+
:param exposure_time: seconds — must match simulate_roman_image
457+
internally.
458+
:param detector: WFI detector (1–18). Random if None.
459+
:param detector_pos: (x, y) pixel position. Random if None.
460+
:param ra: RA in degrees. Random if None.
461+
:param dec: Dec in degrees. Random if None.
462+
:param date: observation date; affects zodiacal light level.
463+
:param oversample: must match simulate_roman_image.
464+
:return: 2-D numpy array of background counts, shape (num_pix+6,
465+
num_pix+6).
466+
"""
467+
if detector is None:
468+
detector = random.randint(1, 18)
469+
if detector_pos is None:
470+
x_pos = random.randint(4 + num_pix * oversample, 4092 - num_pix * oversample)
471+
y_pos = random.randint(4 + num_pix * oversample, 4092 - num_pix * oversample)
472+
detector_pos = (x_pos, y_pos)
473+
if ra is None:
474+
ra = random.uniform(5, 60)
475+
if dec is None:
476+
dec = random.uniform(-40, -20)
477+
478+
num_pix_buffered = num_pix + 6
479+
480+
zero_image = galsim.ImageF(num_pix_buffered, num_pix_buffered, scale=0.11)
481+
zero_image.setOrigin(0, 0)
482+
483+
background_image = add_sky_plus_thermal_background(
484+
zero_image, band, detector, num_pix_buffered, exposure_time, ra, dec, date
485+
)
486+
return background_image.array

tests/test_ImageSimulation/test_roman_image_simulation.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
# test_roman_image_simulation.py
12
import astropy.cosmology
23
import numpy as np
34
import numpy.testing as npt
45
from slsim.Lenses.lens import Lens
56
from slsim.ImageSimulation.roman_image_simulation import (
67
simulate_roman_image,
8+
precompute_roman_background,
79
)
810
from slsim.ImageSimulation import image_quality_lenstronomy
911
from slsim.ImageSimulation.image_simulation import simulate_image
@@ -294,6 +296,23 @@ def test_simulate_roman_image_with_psf_without_noise():
294296
np.sum(galsim_image), np.sum(image_ref), rtol=0, atol=0.1
295297
)
296298

299+
# precomputed_background is only used inside the add_noise branch, so a dummy
300+
# array must not affect the noiseless result
301+
dummy_background = np.ones((45 + 6, 45 + 6), dtype=np.float32)
302+
galsim_image_with_dummy_bg = simulate_roman_image(
303+
lens_class=LENS,
304+
band=BAND,
305+
num_pix=45,
306+
oversample=3,
307+
seed=42,
308+
add_noise=False,
309+
psf_directory=PSF_DIRECTORY,
310+
detector=1,
311+
detector_pos=(2000, 2000),
312+
precomputed_background=dummy_background,
313+
)
314+
npt.assert_array_equal(galsim_image_with_dummy_bg, galsim_image)
315+
297316

298317
def test_simulate_roman_image_with_time_variable_source():
299318
detector_kwargs = {
@@ -350,5 +369,79 @@ def test_simulate_roman_image_with_time_variable_source():
350369
assert not np.allclose(lens_image, lens_image3, atol=1e-16, rtol=1e-16)
351370

352371

372+
def test_precompute_roman_background():
373+
kwargs_single_band = image_quality_lenstronomy.kwargs_single_band(
374+
observatory="Roman", band=BAND
375+
)
376+
377+
background = precompute_roman_background(
378+
band=BAND,
379+
num_pix=45,
380+
exposure_time=kwargs_single_band["exposure_time"],
381+
**DETECTOR_KWARGS,
382+
)
383+
384+
assert isinstance(background, np.ndarray)
385+
assert background.shape == (45 + 6, 45 + 6)
386+
assert np.all(np.isfinite(background))
387+
assert np.all(background >= 0)
388+
389+
# testing the randomised-defaults (detector, detector_pos, ra, dec all None)
390+
background_random = precompute_roman_background(
391+
band=BAND,
392+
num_pix=45,
393+
exposure_time=kwargs_single_band["exposure_time"],
394+
)
395+
assert background_random.shape == (45 + 6, 45 + 6)
396+
397+
398+
def test_simulate_roman_image_with_precomputed_background():
399+
kwargs_single_band = image_quality_lenstronomy.kwargs_single_band(
400+
observatory="Roman", band=BAND
401+
)
402+
exposure_time = kwargs_single_band["exposure_time"]
403+
404+
background = precompute_roman_background(
405+
band=BAND,
406+
num_pix=45,
407+
exposure_time=exposure_time,
408+
**DETECTOR_KWARGS,
409+
)
410+
411+
image_precomputed = simulate_roman_image(
412+
lens_class=LENS,
413+
band=BAND,
414+
num_pix=45,
415+
oversample=3,
416+
add_noise=True,
417+
subtract_mean_background=True,
418+
seed=42,
419+
exposure_time=exposure_time,
420+
psf_directory=PSF_DIRECTORY,
421+
precomputed_background=background,
422+
**DETECTOR_KWARGS,
423+
)
424+
425+
image_direct = simulate_roman_image(
426+
lens_class=LENS,
427+
band=BAND,
428+
num_pix=45,
429+
oversample=3,
430+
add_noise=True,
431+
subtract_mean_background=True,
432+
seed=42,
433+
exposure_time=exposure_time,
434+
psf_directory=PSF_DIRECTORY,
435+
**DETECTOR_KWARGS,
436+
)
437+
438+
assert image_precomputed.shape == (45, 45)
439+
assert image_direct.shape == (45, 45)
440+
diff = np.abs(image_precomputed - image_direct) / (
441+
np.abs(image_precomputed) + np.abs(image_direct) + 1e-10
442+
)
443+
npt.assert_array_less(np.mean(diff), 0.1)
444+
445+
353446
if __name__ == "__main__":
354447
pytest.main()

0 commit comments

Comments
 (0)