-
Notifications
You must be signed in to change notification settings - Fork 167
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-2256: Apply median filter to IVM weight #7563
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,10 @@ | |
import logging | ||
import warnings | ||
|
||
import gwcs | ||
import numpy as np | ||
from scipy.ndimage import median_filter | ||
from astropy import units as u | ||
import gwcs | ||
|
||
from stdatamodels.dqflags import interpret_bit_flags | ||
from stdatamodels.jwst.datamodels.dqflags import pixel | ||
|
@@ -170,23 +171,86 @@ | |
""" | ||
dqmask = build_mask(model.dq, good_bits) | ||
|
||
if weight_type == 'ivm': | ||
if weight_type and weight_type.startswith('ivm'): | ||
weight_type = weight_type.strip() | ||
selective_median = weight_type.startswith('ivm-smed') | ||
|
||
bitvalue = interpret_bit_flags(good_bits, mnemonic_map=pixel) | ||
if bitvalue is None: | ||
bitvalue = 0 | ||
saturation = pixel['SATURATED'] | ||
|
||
if selective_median and not (bitvalue & saturation): | ||
selective_median = False | ||
weight_type = 'ivm' | ||
|
||
if (model.hasattr("var_rnoise") and model.var_rnoise is not None and | ||
model.var_rnoise.shape == model.data.shape): | ||
with np.errstate(divide="ignore", invalid="ignore"): | ||
inv_variance = model.var_rnoise**-1 | ||
|
||
inv_variance[~np.isfinite(inv_variance)] = 1 | ||
|
||
if weight_type != 'ivm': | ||
ny, nx = inv_variance.shape | ||
|
||
# apply a median filter to smooth the weight at saturated | ||
# (or high read-out noise) single pixels. keep kernel size | ||
# small to still give lower weight to extended CRs, etc. | ||
ksz = weight_type[8 if selective_median else 7 :] | ||
if ksz: | ||
kernel_size = int(ksz) | ||
if not (kernel_size % 2): | ||
raise ValueError( | ||
'Kernel size of the median filter in IVM weighting' | ||
' must be an odd integer.' | ||
) | ||
else: | ||
kernel_size = 3 | ||
|
||
ivm_copy = inv_variance.copy() | ||
|
||
if selective_median: | ||
# apply median filter selectively only at | ||
# points of partially saturated sources: | ||
jumps = np.where(model.dq & saturation) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks good to me. For the fully saturated case, do we set the saturation bit and the do_not_use bit, or only the do_not_use bit? If the former, we probably want this selection to be (model.dq & saturation) & ~(model.dq & do_not_use) to avoid treating fully saturated pixels as good. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. That would be an oversight on my part. Thanks for catching this. Let me check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Everything is fine. On line 238 below, this is handled via # line 187:
dqmask = build_mask(model.dq, good_bits)
# line 238:
data = ivm_copy[y1:y2, x1:x2][dqmask[y1:y2, x1:x2]] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. However, if user says that fully saturated pixels are "good" (i.e., should not be ignored), then it will be exactly the situation that you describe but that's what the user wants... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the delay. I'm missing something, though. Say there's a fully saturated pixel that has SATURATED & DO_NOT_USE set. It gets an entry in jumps, and so we loop over it. As you point out, we don't include the fully bad pixels in the median we use to replace it (because dqmask doesn't include them), but it does look to me like we would replace the fully bad pixel with the median of its neighbors? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I see what you mean (initially I thought you were referring to using DO_NOT_USE pixels in the computation of the median). I still do not think this is an issue because as long as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, right, I missed that. Thumbs up! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Latest commit should fix this. Please review |
||
w2 = kernel_size // 2 | ||
for r, c in zip(*jumps): | ||
x1 = max(0, c - w2) | ||
x2 = min(nx, c + w2 + 1) | ||
y1 = max(0, r - w2) | ||
y2 = min(ny, r + w2 + 1) | ||
data = ivm_copy[y1:y2, x1:x2][dqmask[y1:y2, x1:x2]] | ||
if data.size: | ||
inv_variance[r, c] = np.median(data) | ||
# else: leave it as is | ||
|
||
else: | ||
# apply median to the entire inv-var array: | ||
inv_variance = median_filter( | ||
inv_variance, | ||
size=kernel_size | ||
) | ||
bad_dqmask = np.logical_not(dqmask) | ||
inv_variance[bad_dqmask] = ivm_copy[bad_dqmask] | ||
|
||
else: | ||
warnings.warn("var_rnoise array not available. Setting drizzle weight map to 1", | ||
RuntimeWarning) | ||
warnings.warn( | ||
"var_rnoise array not available. " | ||
"Setting drizzle weight map to 1", | ||
RuntimeWarning | ||
) | ||
inv_variance = 1.0 | ||
|
||
result = inv_variance * dqmask | ||
|
||
elif weight_type == 'exptime': | ||
if _check_for_tmeasure(model): | ||
exptime = model.meta.exposure.measurement_time | ||
else: | ||
exptime = model.meta.exposure.exposure_time | ||
result = exptime * dqmask | ||
|
||
else: | ||
result = np.ones(model.data.shape, dtype=model.data.dtype) * dqmask | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
now that #8671 is merged (switching change log handling to
towncrier
) this change log entry should be a file inchanges/
instead:(new PRs will include instructions on how to do this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(ignore this if this PR is no longer active or needed)