Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3e34f7b
rearrange functions in detector_physics.py to sca.py and detector_eff…
yuedong0607 Oct 16, 2024
48b3560
fix clip calls in add_persistence
yuedong0607 Oct 18, 2024
4540ad0
remove some commented codes in sca.py
yuedong0607 May 28, 2025
9aef8d2
Start refactoring the detector_effects class:
yuedong0607 Jun 29, 2025
2532065
Start refactoring the detector effects in a subfolder
yuedong0607 Jul 8, 2025
1f291cd
remove debugging code
yuedong0607 Jul 8, 2025
23305b1
Add cross_refer method to roman_effects for internal cross-referencin…
yuedong0607 Jul 8, 2025
c560726
add some logging to bfe and recip_failure
yuedong0607 Jul 8, 2025
56e4962
add dead_pix, interpix_cap, read_noise, vtpe
yuedong0607 Jul 8, 2025
52d3cd7
add gain and bias model to effects
yuedong0607 Jul 8, 2025
b0a8713
add setup_sky class for generating sky as well as sky subtracted images
yuedong0607 Jul 11, 2025
cc56da0
modify config and sca.py for generating sky subtracted images
yuedong0607 Jul 11, 2025
12b1c1e
rearrange functions in detector_physics.py to sca.py and detector_eff…
yuedong0607 Oct 16, 2024
c3b6474
add .gitignore file
yuedong0607 Jul 29, 2025
bab7272
rearrange functions in detector_physics.py to sca.py and detector_eff…
yuedong0607 Oct 16, 2024
212c0bf
rearrange functions in detector_physics.py to sca.py and detector_eff…
yuedong0607 Oct 16, 2024
98ff4c3
reformat to meet pep8
yuedong0607 Jul 29, 2025
bb9168a
black reformated
yuedong0607 Jul 29, 2025
f727aac
black reformated
yuedong0607 Jul 29, 2025
7255582
black formatted
yuedong0607 Jul 30, 2025
a765773
* renaming the classes using CamelCase
yuedong0607 Aug 1, 2025
7c6bf21
move model validity checking into base (RomanEffects) class
yuedong0607 Aug 1, 2025
24a84ed
renamed the files for effects
yuedong0607 Aug 26, 2025
fe411a8
fix the import in sca.py
yuedong0607 Aug 26, 2025
0346889
bug fix in read_noise.py: missed assignment for simple model
yuedong0607 Aug 27, 2025
1ff7ee3
* remove duplicates in detector_physics.py
yuedong0607 Aug 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/build/*
/dist/*
/roman_imsim.egg-info/*
49 changes: 36 additions & 13 deletions config/was.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,42 @@ image:
# read_noise: False
# sky_subtract: False

ignore_noise: False
stray_light: True
thermal_background: True
reciprocity_failure: True
dark_current: True
nonlinearity: True
ipc: True
read_noise: True
add_effects:
Background:
model: simple_model
thermal_background: True
stray_light: True
QuantumEfficiency:
model: lab_model
BrighterFatter:
model: lab_model
Persistence:
model: lab_model
# ReciprocityFailure:
# model: simple_model
DarkCurrent:
model: lab_model
Saturation:
model: lab_model
Nonlinearity:
model: lab_model
IPC:
model: lab_model
DeadPixel:
model: lab_model
VTPE:
model: lab_model
ReadNoise:
model: lab_model
Gain:
model: lab_model
Bias:
model: lab_model
sky_subtract: False

sca_filepath: /hpc/group/cosmology/phy-lsst/roman_sensors
save_diff: False

# nobjects: 500

stamp:
Expand All @@ -124,17 +150,14 @@ gal:

input:
obseq_data:
# file_name: /lus/grand/projects/RomanDESC/final_roman_runfiles/was/Roman_WAS_obseq_11_1_23.fits
file_name: /hpc/group/cosmology/OpenUniverse2024/RomanWAS/Roman_WAS_obseq_11_1_23.fits
visit: 1
visit: 4906
SCA: '@image.SCA'
roman_psf:
SCA: '@image.SCA'
n_waves: 5
sky_catalog:
# file_name: /lus/grand/projects/RomanDESC/roman_rubin_cats_v1.1.2_faint/skyCatalog.yaml
# file_name: /hpc/group/cosmology/OpenUniverse2024/roman_rubin_cats_v1.1.2_faint/skyCatalog.yaml
file_name: /hpc/home/yf194/Work/projects/roman_imsim/config/skyCatalog.yaml
file_name: /hpc/group/cosmology/OpenUniverse2024/roman_rubin_cats_v1.1.2_faint/skyCatalog.yaml
edge_pix: 512
mjd: { type: ObSeqData, field: mjd }
exptime: { type: ObSeqData, field: exptime }
Expand Down
3 changes: 3 additions & 0 deletions roman_imsim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
from .skycat import *
from .stamp import *
from .wcs import *
from .skycat import *
from .photonOps import *
from .bandpass import *
51 changes: 0 additions & 51 deletions roman_imsim/detector_physics.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,6 @@ def __init__(self, params, visit, SCA):
)[self.sca]
self.radec = self.WCS.toWorld(galsim.PositionI(int(roman.n_pix / 2), int(roman.n_pix / 2)))

self.WCS = roman.getWCS(
world_pos=galsim.CelestialCoord(ra=obseq_data.ob["ra"], dec=obseq_data.ob["dec"]),
PA=obseq_data.ob["pa"],
date=self.date,
SCAs=self.sca,
PA_is_FPA=True,
)[self.sca]
self.radec = self.WCS.toWorld(galsim.PositionI(int(roman.n_pix / 2), int(roman.n_pix / 2)))


class modify_image(object):
"""
Expand Down Expand Up @@ -121,8 +112,6 @@ def __init__(self, params, visit, sca, dither_from_file, sca_filepath=None, use_
if sca_filepath is not None:
self.df = fio.FITS(sca_filepath + "/" + sca_number_to_file[self.pointing.sca])
print("------- Using SCA files --------")
self.df = fio.FITS(sca_filepath + "/" + sca_number_to_file[self.pointing.sca])
print("------- Using SCA files --------")
else:
self.df = None
print("------- Using simple detector model --------")
Expand All @@ -139,16 +128,6 @@ def __init__(self, params, visit, sca, dither_from_file, sca_filepath=None, use_
"truth", self.get_path_name(use_galsim=use_galsim)
)

b = galsim.BoundsI(xmin=1, ymin=1, xmax=roman.n_pix, ymax=roman.n_pix)
old_filename = os.path.join(self.params["output"]["dir"], imfilename)
if not os.path.exists(
self.params["output"]["dir"].replace("truth", self.get_path_name(use_galsim=use_galsim))
):
os.mkdir(self.params["output"]["dir"].replace("truth", self.get_path_name(use_galsim=use_galsim)))
new_filename = os.path.join(self.params["output"]["dir"], imfilename).replace(
"truth", self.get_path_name(use_galsim=use_galsim)
)

b = galsim.BoundsI(xmin=1, ymin=1, xmax=roman.n_pix, ymax=roman.n_pix)
im = fio.FITS(old_filename)[-1].read()
im = galsim.Image(im, bounds=b, wcs=self.pointing.WCS)
Expand All @@ -160,7 +139,6 @@ def __init__(self, params, visit, sca, dither_from_file, sca_filepath=None, use_
force_cvz = True
self.setup_sky(im, self.pointing, rng, visit * sca, force_cvz=force_cvz)

img, err, dq, sky_mean, sky_noise = self.add_effects(im, None, self.pointing, use_galsim=use_galsim)
img, err, dq, sky_mean, sky_noise = self.add_effects(im, None, self.pointing, use_galsim=use_galsim)

write_fits(
Expand All @@ -172,7 +150,6 @@ def __init__(self, params, visit, sca, dither_from_file, sca_filepath=None, use_
self.pointing.sca,
sky_mean=sky_mean,
)
write_fits(old_filename, new_filename, img, sky_noise, dq, self.pointing.sca, sky_mean=sky_mean)

def get_path_name(self, use_galsim=False):

Expand Down Expand Up @@ -562,10 +539,6 @@ def bfe(self, im):
# The img is clipped by the saturation level here to cap the brighter fatter effect
# and avoid unphysical behavior

array_pad = self.saturate(im.copy()).array[4:-4, 4:-4] # img of interest 4088x4088
array_pad = np.pad(
array_pad, [(4 + nbfe, 4 + nbfe), (4 + nbfe, 4 + nbfe)], mode="symmetric"
) # 4100x4100 array
array_pad = self.saturate(im.copy()).array[4:-4, 4:-4] # img of interest 4088x4088
array_pad = np.pad(
array_pad, [(4 + nbfe, 4 + nbfe), (4 + nbfe, 4 + nbfe)], mode="symmetric"
Expand Down Expand Up @@ -697,8 +670,6 @@ def get_eff_sky_bg(self, pointing, radec):
radec : World coordinate position of image
"""

sky_level = roman.getSkyLevel(pointing.bpass, world_pos=radec, date=pointing.date)
sky_level *= (1.0 + roman.stray_light_fraction) * roman.pixel_scale**2
sky_level = roman.getSkyLevel(pointing.bpass, world_pos=radec, date=pointing.date)
sky_level *= (1.0 + roman.stray_light_fraction) * roman.pixel_scale**2

Expand Down Expand Up @@ -739,9 +710,6 @@ def setup_sky(self, im, pointing, rng, rng_iter, force_cvz=False):
self.dark_current_ = (
roman.dark_current * roman.exptime + self.df["DARK"][:, :].flatten() * roman.exptime
)
self.dark_current_ = (
roman.dark_current * roman.exptime + self.df["DARK"][:, :].flatten() * roman.exptime
)
if self.df is None:
self.gain = roman.gain
else:
Expand All @@ -755,8 +723,6 @@ def setup_sky(self, im, pointing, rng, rng_iter, force_cvz=False):
radec = pointing.radec
sky_level = roman.getSkyLevel(pointing.bpass, world_pos=radec, date=pointing.date)
sky_level *= 1.0 + roman.stray_light_fraction
sky_level = roman.getSkyLevel(pointing.bpass, world_pos=radec, date=pointing.date)
sky_level *= 1.0 + roman.stray_light_fraction
# Make a image of the sky that takes into account the spatially variable pixel scale. Note
# that makeSkyImage() takes a bit of time. If you do not care about the variable pixel
# scale, you could simply compute an approximate sky level in e-/pix by multiplying
Expand Down Expand Up @@ -864,8 +830,6 @@ def dark_current(self, im):
# opt for numpy random geneator instead for speed
self.im_dark = self.rng_np.poisson(dark_current_).reshape(im.array.shape).astype(im.dtype)
im.array[:, :] += self.im_dark
self.im_dark = self.rng_np.poisson(dark_current_).reshape(im.array.shape).astype(im.dtype)
im.array[:, :] += self.im_dark

# NOTE: Sky level and dark current might appear like a constant background that can be
# simply subtracted. However, these contribute to the shot noise and matter for the
Expand Down Expand Up @@ -960,11 +924,6 @@ def add_persistence(self, im, pointing):
p_list = np.array([get_pointing(self.params, i, pointing.sca) for i in dither_list_selected])
dt_list = np.array([(pointing.date - p.date).total_seconds() for p in p_list])
p_pers = p_list[np.where((dt_list > 0) & (dt_list < roman.exptime * 10))]
dither_list_selected = dither_sca_array[dither_sca_array[:, 1] == pointing.sca, 0]
dither_list_selected = dither_list_selected[np.abs(dither_list_selected - pointing.visit) < 10]
p_list = np.array([get_pointing(self.params, i, pointing.sca) for i in dither_list_selected])
dt_list = np.array([(pointing.date - p.date).total_seconds() for p in p_list])
p_pers = p_list[np.where((dt_list > 0) & (dt_list < roman.exptime * 10))]

if self.df is None:
# iterate over previous exposures
Expand All @@ -986,7 +945,6 @@ def add_persistence(self, im, pointing):
x = x.clip(0) # remove negative stimulus

im.array[:, :] += galsim.roman.roman_detectors.fermi_linear(x.array, dt) * roman.exptime
im.array[:, :] += galsim.roman.roman_detectors.fermi_linear(x.array, dt) * roman.exptime

else:

Expand Down Expand Up @@ -1106,8 +1064,6 @@ def interpix_cap(self, im, kernel=roman.ipc_kernel):
num_grids = 4 # num_grids <= 8
grid_size = 4096 // num_grids

array_pad = im.array[4:-4, 4:-4] # it's an array instead of img
array_pad = np.pad(array_pad, [(5, 5), (5, 5)], mode="symmetric") # 4098x4098 array
array_pad = im.array[4:-4, 4:-4] # it's an array instead of img
array_pad = np.pad(array_pad, [(5, 5), (5, 5)], mode="symmetric") # 4098x4098 array

Expand All @@ -1127,8 +1083,6 @@ def interpix_cap(self, im, kernel=roman.ipc_kernel):
for i in range(3):
tmp = (t.dot(K[j, i, :, :])).dot(t.T) # grid_sizexgrid_size
K_pad[j, i, :, :] = np.pad(tmp, [(1, 1), (1, 1)], mode="symmetric")
tmp = (t.dot(K[j, i, :, :])).dot(t.T) # grid_sizexgrid_size
K_pad[j, i, :, :] = np.pad(tmp, [(1, 1), (1, 1)], mode="symmetric")

for dy in range(-1, 2):
for dx in range(-1, 2):
Expand Down Expand Up @@ -1177,11 +1131,6 @@ def add_read_noise(self, im):
self.rng_np.normal(loc=0.0, scale=read_noise).reshape(im.array.shape).astype(im.dtype)
)
im.array[:, :] += self.im_read
read_noise = self.df["READ"][2, :, :].flatten() # flattened 4096x4096 array
self.im_read = (
self.rng_np.normal(loc=0.0, scale=read_noise).reshape(im.array.shape).astype(im.dtype)
)
im.array[:, :] += self.im_read

# noise_array = self.rng_np.normal(loc=0., scale=read_noise)
# 4088x4088 img
Expand Down
15 changes: 15 additions & 0 deletions roman_imsim/effects/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .roman_effects import RomanEffects, setup_sky
from .brighter_fatter import BrighterFatter
from .nonlinearity import Nonlinearity
from .background import Background
from .quantum_efficiency import QuantumEfficiency
from .persistence import Persistence
from .reciprocity_failure import ReciprocityFailure
from .dark_current import DarkCurrent
from .saturation import Saturation
from .ipc import IPC
from .dead_pixel import DeadPixel
from .vtpe import VTPE
from .read_noise import ReadNoise
from .gain import Gain
from .bias import Bias
54 changes: 54 additions & 0 deletions roman_imsim/effects/background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import os
import galsim
from . import RomanEffects
import galsim.roman as roman


class Background(RomanEffects):
def __init__(self, params, base, logger, rng, rng_iter=None):
super().__init__(params, base, logger, rng, rng_iter)
self.thermal_background = (
self.params["thermal_background"] if "thermal_background" in self.params else False
)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would read cleaner as

self.thermal_background = self.params.get("thermal_background", False)

and similarly others.

self.stray_light = self.params["stray_light"] if "stray_light" in self.params else False

self.is_model_valid()

def simple_model(self, image):
if self.save_diff:
orig = image.copy()

self.logger.warning("Simple model will be applied for background.")
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these be warning-level logs? I get why we want to emit a warning if we use simple model when a model is not specified. But if the user specifically asked for it, then we should apply it without a warning.

pointing = self.pointing
# Build current specification sky level if sky level not given
if self.force_cvz:
radec = self.translate_cvz(pointing.radec)
else:
radec = pointing.radec
sky_level = roman.getSkyLevel(pointing.bpass, world_pos=radec, date=pointing.date)
self.logger.debug("Adding sky_level = %s", sky_level)

if self.stray_light:
self.logger.debug("Stray light fraction = %s", roman.stray_light_fraction)
sky_level *= 1.0 + roman.stray_light_fraction
# Create sky image
self.sky = galsim.Image(bounds=image.bounds, wcs=pointing.WCS)
pointing.WCS.makeSkyImage(self.sky, sky_level)
if self.thermal_background:
tb = roman.thermal_backgrounds[pointing.filter] * pointing.exptime
self.logger.debug("Adding thermal background: %s", tb)
self.sky += tb
self.sky.addNoise(self.noise)

# [TODO] Not entirely sure about this block, since the 'auto' option is meant to
# let the software choose which drawing method to use based on the total flux.
if self.base["image"]["draw_method"] not in ["phot", "auto"]:
image.addNoise(self.noise)

# Adding sky level to the image.
image += self.sky[self.sky.bounds & image.bounds]
if self.save_diff:
prev = image.copy()
diff = prev - orig
diff.write(os.path.join(self.diff_dir, "sky_a.fits"))
return image
27 changes: 27 additions & 0 deletions roman_imsim/effects/bias.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import os
import fitsio as fio
from . import RomanEffects
from .utils import sca_number_to_file


class Bias(RomanEffects):
def __init__(self, params, base, logger, rng, rng_iter=None):
super().__init__(params, base, logger, rng, rng_iter)

self.is_model_valid()

def simple_model(self, image):
self.logger.warning("No bias will be applied.")
return image

def lab_model(self, image):
if self.sca_filepath is None:
self.logger.warning("No bias data provided; no bias will be applied.")
return image
self.df = fio.FITS(os.path.join(self.sca_filepath, sca_number_to_file[self.sca]))

self.logger.warning("Lab measured model will be applied for bias.")
bias = self.df["BIAS"][:, :] # 4096x4096 img

image.array[:, :] += bias
return image
Loading
Loading