Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JP-3463: Dark current noise floor #236

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2bf0d49
Update ols_fit.py
mwregan2 Oct 30, 2023
cd07af9
Update ols_fit.py
mwregan2 Oct 31, 2023
a89a88f
add avg_dark_current parameter
mwregan2 Nov 1, 2023
05de5ac
Update ramp_fit.py
mwregan2 Nov 1, 2023
bebf811
Update ols_fit.py
mwregan2 Nov 1, 2023
87ed46e
Update test_ramp_fitting.py
mwregan2 Nov 1, 2023
df8350e
Update test_ramp_fitting.py
mwregan2 Nov 1, 2023
c3294eb
Update ols_fit.py
mwregan2 Nov 1, 2023
1ac03ac
Update ols_fit.py
mwregan2 Nov 1, 2023
06f2b2d
Update ols_fit.py
mwregan2 Nov 6, 2023
43a0163
Update ols_fit.py
mwregan2 Nov 6, 2023
9e7ba5c
Update ols_fit.py
mwregan2 Nov 6, 2023
5237fd9
add fits
mwregan2 Nov 7, 2023
99faff7
Update ols_fit.py
mwregan2 Nov 8, 2023
af7cac3
updates
mwregan2 Dec 4, 2023
9fb0069
Update ols_fit.py
mwregan2 Dec 27, 2023
b6114da
Merge branch 'dark-current-noise-floor' of https://github.com/mwregan…
mwregan2 Dec 27, 2023
563a0f1
Add Unit test
mwregan2 Dec 27, 2023
b4e6433
Update test_ramp_fitting.py
mwregan2 Dec 28, 2023
0f827d2
Update CHANGES.rst
mwregan2 Dec 28, 2023
dd5974a
Update jump.py
mwregan2 Jan 30, 2024
df7689e
move avg_dark_current to ramp_fit_class
mwregan2 Jan 31, 2024
3ed79cb
Update ramp_fit.py
mwregan2 Jan 31, 2024
4259f39
Update test_jump.py
mwregan2 Feb 1, 2024
9996de4
clean_up_from_moving_avg_dark_current
mwregan2 Feb 1, 2024
c00eac5
Update test_ramp_fitting.py
mwregan2 Feb 1, 2024
76a8422
Update test_ramp_fitting.py
mwregan2 Feb 1, 2024
4ed2a1f
Update CHANGES.rst
mwregan2 Feb 1, 2024
bc33f79
add dark current parameter
mwregan2 Feb 1, 2024
47dc8c7
Update ramp_fit parameter definitions
mwregan2 Feb 5, 2024
af6fbca
clean up parameters
mwregan2 Feb 5, 2024
7c11971
clean up extra parameters
mwregan2 Feb 5, 2024
6155385
Merge branch 'spacetelescope:main' into dark-current-noise-floor
mwregan2 Feb 5, 2024
baa1e45
Update test_ramp_fitting.py
mwregan2 Feb 5, 2024
23e3562
Merge branch 'dark-current-noise-floor' of https://github.com/mwregan…
mwregan2 Feb 5, 2024
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
5 changes: 4 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
1.5.3 (unreleased)
==================

-
ramp_fitting
------------
- Added the avg_dark_current parameter that is used to inlcude the
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
dark current in the calculation of the Poisson noise [#236]

1.5.2 (2023-12-13)
==================
Expand Down
18 changes: 14 additions & 4 deletions src/stcal/jump/jump.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import logging
import multiprocessing
import time

import cv2 as cv
import numpy as np
from astropy import stats
from astropy.convolution import Ring2DKernel, convolve
import cv2 as cv
import astropy.stats as stats
from astropy.io import fits
from astropy.convolution import Ring2DKernel
from astropy.convolution import convolve


from . import constants
from . import twopoint_difference as twopt
Expand Down Expand Up @@ -834,6 +836,9 @@
Total number of showers detected.

"""
log.info('Flagging Showers')
fits.writeto("shower_input_data.fits", indata, overwrite=True)
fits.writeto("shower_input_gdq.fits", gdq, overwrite=True)
read_noise_2 = readnoise_2d**2
data = indata.copy()
data[gdq == sat_flag] = np.nan
Expand All @@ -842,6 +847,7 @@
all_ellipses = []
first_diffs = np.diff(data, axis=1)
first_diffs_masked = np.ma.masked_array(first_diffs, mask=np.isnan(first_diffs))
fits.writeto("first_diffs.fits", first_diffs, overwrite=True)
nints = data.shape[0]
if nints > minimum_sigclip_groups:
mean, median, stddev = stats.sigma_clipped_stats(first_diffs_masked, sigma=5, axis=0)
Expand Down Expand Up @@ -881,6 +887,10 @@
extended_emission = np.zeros(shape=(nrows, ncols), dtype=np.uint8)
exty, extx = np.where(masked_smoothed_ratio > snr_threshold)
extended_emission[exty, extx] = 1
if grp == 12:
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
fits.writeto("masked_ratio.fits", masked_ratio.filled(np.nan), overwrite=True)
fits.writeto("masked_smoothed_ratio.fits", masked_smoothed_ratio, overwrite=True)
fits.writeto("extended_emission.fits", extended_emission, overwrite=True)

Check warning on line 893 in src/stcal/jump/jump.py

View check run for this annotation

Codecov / codecov/patch

src/stcal/jump/jump.py#L891-L893

Added lines #L891 - L893 were not covered by tests
# find the contours of the extended emission
contours, hierarchy = cv.findContours(extended_emission, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)
# get the contours that are above the minimum size
Expand Down
53 changes: 37 additions & 16 deletions src/stcal/ramp_fitting/ols_fit.py
Copy link
Collaborator

Choose a reason for hiding this comment

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

The only places in ols_fit.py that avg_dark_current should appear is at lines 1157 and 1199.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
log.setLevel(logging.DEBUG)


def ols_ramp_fit_multi(ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, max_cores):
def ols_ramp_fit_multi(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, max_cores,
avg_dark_current):
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
"""
Setup the inputs to ols_ramp_fit with and without multiprocessing. The
inputs will be sliced into the number of cores that are being used for
Expand Down Expand Up @@ -57,6 +59,9 @@
'half', and 'all'. This is the fraction of cores to use for multi-proc. The
total number of cores includes the SMT cores (Hyper Threading for Intel).

avg_dark_current: float (electrons)
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
The average dark current for this detector

Returns
-------
image_info : tuple
Expand Down Expand Up @@ -96,15 +101,17 @@
if number_slices == 1:
# Single threaded computation
image_info, integ_info, opt_info = ols_ramp_fit_single(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting
)
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting,
avg_dark_current)
if image_info is None or integ_info is None:
return None, None, None

return image_info, integ_info, opt_info

# Call ramp fitting for multi-processor (multiple data slices) case
image_info, integ_info, opt_info = ols_ramp_fit_multiprocessing(

else:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This else is unnecessary.

image_info, integ_info, opt_info = ols_ramp_fit_multiprocessing(

Check warning on line 114 in src/stcal/ramp_fitting/ols_fit.py

View check run for this annotation

Codecov / codecov/patch

src/stcal/ramp_fitting/ols_fit.py#L114

Added line #L114 was not covered by tests
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, number_slices
)

Expand Down Expand Up @@ -144,6 +151,8 @@
number_slices: int
The number of slices to partition the data into for multiprocessing.

avg_dark_current: float
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
The average dark current in electrons
Return
------
image_info: tuple
Expand All @@ -157,8 +166,8 @@
"""
log.info("Number of processors used for multiprocessing: %s", number_slices)
slices, rows_per_slice = compute_slices_for_starmap(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, number_slices
)
ramp_data, buffsize, save_opt,
readnoise_2d, gain_2d, weighting, avg_dark_current, number_slices)

pool = Pool(processes=number_slices)
pool_results = pool.starmap(ols_ramp_fit_single, slices)
Expand Down Expand Up @@ -432,8 +441,8 @@


def compute_slices_for_starmap(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, number_slices
):
ramp_data, buffsize, save_opt,
readnoise_2d, gain_2d, weighting, avg_dark_current, number_slices):
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
"""
Creates the slices needed for each process for multiprocessing. The slices
for the arguments needed for ols_ramp_fit_single.
Expand Down Expand Up @@ -473,9 +482,11 @@
start_row = 0
for k in range(len(rslices)):
ramp_slice = slice_ramp_data(ramp_data, start_row, rslices[k])
rnoise_slice = readnoise_2d[start_row : start_row + rslices[k], :].copy()
gain_slice = gain_2d[start_row : start_row + rslices[k], :].copy()
slices.insert(k, (ramp_slice, buffsize, save_opt, rnoise_slice, gain_slice, weighting))

rnoise_slice = readnoise_2d[start_row:start_row + rslices[k], :].copy()
gain_slice = gain_2d[start_row:start_row + rslices[k], :].copy()
slices.insert(

Check warning on line 488 in src/stcal/ramp_fitting/ols_fit.py

View check run for this annotation

Codecov / codecov/patch

src/stcal/ramp_fitting/ols_fit.py#L486-L488

Added lines #L486 - L488 were not covered by tests
k, (ramp_slice, buffsize, save_opt, rnoise_slice, gain_slice, weighting, avg_dark_current))
Copy link
Collaborator

Choose a reason for hiding this comment

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

The avg_dark_current is not needed as a part of this tuple. I am surprised this doesn't cause problems with the multiprocessing, since this tuple is a list of parameters to be used by ols_ramp_fit_single.

start_row = start_row + rslices[k]

return slices, rslices
Expand Down Expand Up @@ -618,7 +629,8 @@
ramp_data.one_groups_time = (ramp_data.nframes + 1) * ramp_data.frame_time / 2


def ols_ramp_fit_single(ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting):
def ols_ramp_fit_single(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, avg_dark_current):
"""
Fit a ramp using ordinary least squares. Calculate the count rate for each
pixel in all data cube sections and all integrations, equal to the weighted
Expand All @@ -645,6 +657,9 @@
weighting : str
'optimal' is the only valid value

avg_dark_current : float (electrons)
hbushouse marked this conversation as resolved.
Show resolved Hide resolved
The average dark current for this detector

Return
------
image_info : tuple
Expand Down Expand Up @@ -697,7 +712,7 @@
# noise only, read noise only, and the combination of Poisson noise and
# read noise. The integration-specific variances are 3D arrays, and the
# segment-specific variances are 4D arrays.
variances_ans = ramp_fit_compute_variances(ramp_data, gain_2d, readnoise_2d, fit_slopes_ans)
variances_ans = ramp_fit_compute_variances( ramp_data, gain_2d, readnoise_2d, fit_slopes_ans, avg_dark_current)

# Now that the segment-specific and integration-specific variances have
# been calculated, the segment-specific, integration-specific, and
Expand Down Expand Up @@ -1017,7 +1032,8 @@
)


def ramp_fit_compute_variances(ramp_data, gain_2d, readnoise_2d, fit_slopes_ans):
def ramp_fit_compute_variances(ramp_data, gain_2d, readnoise_2d, fit_slopes_ans,
avg_dark_current):
"""
In this 'Second Pass' over the data, loop over integrations and data
sections to calculate the variances of the slope using the estimated
Expand Down Expand Up @@ -1049,6 +1065,8 @@
fit_slopes_ans : tuple
Contains intermediate values computed in the first pass over the data.

avg_dark_current : float (electrons)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The avg_dark_current parameter needs to be removed from the docstring.

The average dark current for this detector
Return
------
var_p3 : ndarray
Expand Down Expand Up @@ -1138,7 +1156,7 @@
# Suppress harmless arithmetic warnings for now
warnings.filterwarnings("ignore", ".*invalid value.*", RuntimeWarning)
warnings.filterwarnings("ignore", ".*divide by zero.*", RuntimeWarning)
var_p4[num_int, :, rlo:rhi, :] = den_p3 * med_rates[rlo:rhi, :]
var_p4[num_int, :, rlo:rhi, :] = (den_p3 * med_rates[rlo:rhi, :]) + avg_dark_current

# Find the segment variance due to read noise and convert back to DN
var_r4[num_int, :, rlo:rhi, :] = num_r3 * den_r3 / gain_sect**2
Expand Down Expand Up @@ -1178,7 +1196,10 @@
var_p3[:, med_rates <= 0.0] = 0.0
warnings.resetwarnings()

var_p4[num_int, :, med_rates <= 0.0] = 0.0
# For pixels with zero or negative rates set the Poisson variance to the
# dark current rate.
var_p4[num_int, :, med_rates <= 0.0] = avg_dark_current

var_both4[num_int, :, :, :] = var_r4[num_int, :, :, :] + var_p4[num_int, :, :, :]
inv_var_both4[num_int, :, :, :] = 1.0 / var_both4[num_int, :, :, :]

Expand Down
16 changes: 9 additions & 7 deletions src/stcal/ramp_fitting/ramp_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,9 @@ def ramp_fit(
weighting,
max_cores,
dqflags,
avg_dark_current=0.0,
suppress_one_group=False,
):
):
"""
Calculate the count rate for each pixel in all data cube sections and all
integrations, equal to the slope for all sections (intervals between
Expand Down Expand Up @@ -167,13 +168,12 @@ def ramp_fit(
ramp_data = create_ramp_fit_class(model, dqflags, suppress_one_group)
hbushouse marked this conversation as resolved.
Show resolved Hide resolved

return ramp_fit_data(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, algorithm, weighting, max_cores, dqflags
)
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d,
algorithm, weighting, max_cores, dqflags, avg_dark_current)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The avg_dark_current needs to be removed from the function parameters, since it is now a member parameter in ramp_data.



def ramp_fit_data(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, algorithm, weighting, max_cores, dqflags
):
def ramp_fit_data(ramp_data, buffsize, save_opt, readnoise_2d, gain_2d,
algorithm, weighting, max_cores, dqflags, avg_dark_current):
"""
This function begins the ramp fit computation after the creation of the
RampData class. It determines the proper path for computation to take
Expand Down Expand Up @@ -215,6 +215,8 @@ def ramp_fit_data(
A dictionary with at least the following keywords:
DO_NOT_USE, SATURATED, JUMP_DET, NO_GAIN_VALUE, UNRELIABLE_SLOPE

avg_dark_current : float (electrons)
The average dark current for this detector
Returns
-------
image_info : tuple
Expand Down Expand Up @@ -247,7 +249,7 @@ def ramp_fit_data(

# Compute ramp fitting using ordinary least squares.
image_info, integ_info, opt_info = ols_fit.ols_ramp_fit_multi(
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, max_cores
ramp_data, buffsize, save_opt, readnoise_2d, gain_2d, weighting, max_cores, avg_dark_current
)
gls_opt_info = None

Expand Down
26 changes: 24 additions & 2 deletions tests/test_jump.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
import pytest

from astropy.io import fits
from stcal.jump.jump import (
calc_num_slices,
extend_saturation,
Expand All @@ -10,6 +10,7 @@
point_inside_ellipse,
)


DQFLAGS = {"JUMP_DET": 4, "SATURATED": 2, "DO_NOT_USE": 1, "GOOD": 0, "NO_GAIN_VALUE": 8}


Expand Down Expand Up @@ -351,4 +352,25 @@
assert calc_num_slices(n_rows, "all", max_available_cores) == 10
assert calc_num_slices(n_rows, "3/4", max_available_cores) == 1
n_rows = 9
assert calc_num_slices(n_rows, "21", max_available_cores) == 9
assert (calc_num_slices(n_rows, '21', max_available_cores) == 9)
Copy link
Collaborator

Choose a reason for hiding this comment

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

The parentheses are unnecessary and should be removed.


def test_shower_slowmode():
data = fits.getdata("shower_input_data.fits")
gdq = fits.getdata("shower_input_gdq.fits")
nint = data.shape[0]
ngrps = data.shape[1]
ncols = data.shape[2]
nrows = data.shape[3]
gain = 5
readnoise = fits.getdata("/grp/crds/jwst/references/jwst/jwst_miri_readnoise_0086.fits")
# readnoise = np.ones(shape=(nrows, ncols), dtype=np.float32) * 6.0 * gain
rng = np.random.default_rng(12345)

Check warning on line 367 in tests/test_jump.py

View check run for this annotation

Codecov / codecov/patch

tests/test_jump.py#L367

Added line #L367 was not covered by tests
# data[0, 1:, 14:20, 15:20] = 6 * gain * 1.7
# data = data + rng.normal(size=(nint, ngrps, nrows, ncols)) * readnoise
gdq, num_showers = find_faint_extended(data, gdq, readnoise, 1, 100,

Check warning on line 370 in tests/test_jump.py

View check run for this annotation

Codecov / codecov/patch

tests/test_jump.py#L370

Added line #L370 was not covered by tests
snr_threshold=1.3,
min_shower_area=40, inner=1,
outer=2, sat_flag=2, jump_flag=4,
ellipse_expand=1.1, num_grps_masked=3)
print("number of showers", num_showers)
fits.writeto("outgdq.fits", gdq, overwrite=True)

Check warning on line 376 in tests/test_jump.py

View check run for this annotation

Codecov / codecov/patch

tests/test_jump.py#L375-L376

Added lines #L375 - L376 were not covered by tests
Loading
Loading