Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions changes/2316.general.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update the pixel timing pattern in ``frame_read_times`` to account for gaps in the science pixel times due to reading out the guide window.
2 changes: 1 addition & 1 deletion romancal/dark_decay/tests/test_dark_decay.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def create_ramp_model(nresultants, nrows=4096, ncols=4096):
)

# add necessary metadata
ramp_model.meta.exposure.frame_time = 1.0
ramp_model.meta.exposure.frame_time = 3.0

# add required cal steps
ramp_model.meta.cal_step = {}
Expand Down
56 changes: 48 additions & 8 deletions romancal/lib/basic_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,18 @@ def parse_visitID(visit_id):
return visit_id_parts


def frame_read_times(frame_time, sca, frame_number=0):
def frame_read_times(
frame_time, sca, frame_number=0, stride_gw=256, gw_pseudotime_factor=1.5
):
"""
Compute the pixel read times for a single frame.

This is a placeholder function that assumes a uniform read
across each channel within the frame time. A more careful
treatment will need to account for time spent reading out the
guide window.
This is a provisional routine that approximately accounts for
time spent reading out the guide window. The routine is primarily
intended for the WFI18 first read correction, and has a fudge
factor for the guide window time due to the fact that the guide
window reads reduce the the WFI18 transient more than science pixel
reads.

Data shape for the frame is assumed to be 4096 x 4096, with
32 channels along the columns.
Expand All @@ -53,6 +57,16 @@ def frame_read_times(frame_time, sca, frame_number=0):
frame_number : float, optional
The frame number. Default of zero means that pixels start
reading out at t=0.
stride_gw : int, optional
The number of science rows read out between guide window
excursions. Defined in the guide window MA tables. The
default of 256 is currently used in all guide window MA
tables.
gw_pseudotime_factor : float, optional
The factor by which to inflate the relative time spent
reading out the guide window. Accounts for the fact that
the WFI18 transient appears to decay slightly faster when
reading out the guide window. Default 1.5.

Returns
-------
Expand All @@ -62,9 +76,35 @@ def frame_read_times(frame_time, sca, frame_number=0):
nchannel = 32
nrow = 4096
ncol = 128
one_channel_read_time = np.linspace(
0, frame_time, nrow * ncol, endpoint=False
).reshape(nrow, ncol)

# The "padding," or clock cycles between rows, will likely never change.
npad = 14

# The clock cycle time, in seconds; this will likely never change.
pixtime = 4.915263e-06

# Define a 2D timing pattern for a single readout channel
icol, irow = np.meshgrid(np.arange(ncol), np.arange(nrow))
science_clocknum = (icol + irow * (ncol + npad)).astype(float)

# The number of clock cycles spent in guide window mode can be
# very closely figured as the total number of clock cycles minus
# those spent reading out science pixels.
clockcycles_tot = frame_time / pixtime
clockcycles_gw = clockcycles_tot - np.amax(science_clocknum)

num_gw_readouts = nrow // stride_gw
cycles_per_gw = clockcycles_gw / num_gw_readouts * gw_pseudotime_factor

for i in range(1, num_gw_readouts):
science_clocknum[i * stride_gw :] += cycles_per_gw

# Scale the clock cycles to the appropriate frame time, leaving
# one last guide window excursion at the end. We cannot just
# multiply by the pixel time because gw_pseudotime_factor may not
# be unity.
one_channel_read_time = frame_time * science_clocknum
one_channel_read_time /= np.amax(science_clocknum) + cycles_per_gw

# WFI channels alternate readout direction in the +x and -x directions
# we implement this by flipping the x direction of every other channel
Expand Down
47 changes: 47 additions & 0 deletions romancal/lib/tests/test_timing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import numpy as np

from romancal.lib.basic_utils import frame_read_times


def test_timing():

frametime_ref = 3.16 # in seconds
readtimes_nogw = frame_read_times(
frametime_ref, 1, frame_number=0, stride_gw=256, gw_pseudotime_factor=0
)

readtimes_nominal = frame_read_times(
frametime_ref, 1, frame_number=0, stride_gw=256, gw_pseudotime_factor=1
)

readtimes_default = frame_read_times(
frametime_ref, 1, frame_number=0, stride_gw=256, gw_pseudotime_factor=1.5
)

# Row-to-row differences in time
rowdiffs_nogw = np.diff(readtimes_nogw, axis=0)[:, 0]
rowdiffs_nominal = np.diff(readtimes_nominal, axis=0)[:, 0]
rowdiffs_default = np.diff(readtimes_default, axis=0)[:, 0]

# Uniform gaps between rows without the guide window
assert np.std(rowdiffs_nogw) < 1e-6

# Large gap at Row 256 with the guide window
assert rowdiffs_nominal[255] > 100 * rowdiffs_nominal[254]

# Slightly less than a factor of 1.5 between the guide window gaps
# when using a factor of 1.5 for gw_pseudotime_factor. This is not
# exactly 1.5 because of the normalization to the total frame time.
assert np.isclose(rowdiffs_default[255] / rowdiffs_nominal[255], 1.45, 0.05)

# Smaller time spread within rows when allocating some of the time
# to the guide window
assert np.all(np.std(readtimes_nominal, axis=1) < np.std(readtimes_nogw, axis=1))

# All frame times check out: pixel read times go from zero to
# the reference frame time.
for arr in [readtimes_nogw, readtimes_default, readtimes_nominal]:
assert np.amax(arr) <= frametime_ref
assert np.amax(arr) > 0.95 * frametime_ref
assert np.amin(arr) < 0.05 * frametime_ref
assert np.amin(arr) >= 0
12 changes: 7 additions & 5 deletions romancal/wfi18_transient/tests/test_wfi18_transient.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def create_ramp_model(nresultants, nrows=4096, ncols=4096):

# add necessary metadata
ramp_model.meta.instrument.detector = "WFI18"
ramp_model.meta.exposure.frame_time = 1.0
ramp_model.meta.exposure.frame_time = 3.0

# add required cal steps
ramp_model.meta.cal_step = {}
Expand All @@ -41,9 +41,9 @@ def create_ramp_model(nresultants, nrows=4096, ncols=4096):
return ramp_model


def transient_glow():
read_times = frame_read_times(1.0, 18)
glow = _double_exp(read_times, 0.1, 0.01, 0.01, 0.01)
def transient_glow(frame_time):
read_times = frame_read_times(frame_time, 18)
glow = _double_exp(read_times, -0.5, 0.1, 0.1, 0.5)
return glow


Expand All @@ -55,7 +55,9 @@ def test_wfi18_transient(caplog):

# Add a glow to the bottom of the detector in the first read,
# not including the reference pixels
model.data[0, 4:-4, 4:-4] += transient_glow()[4:-4, 4:-4]
model.data[0, 4:-4, 4:-4] += transient_glow(model.meta.exposure.frame_time)[
4:-4, 4:-4
]
assert not np.allclose(model.data, 1.0, atol=1e-5)

# Correct out the glow
Expand Down
Loading