Skip to content

Commit c2aa3e9

Browse files
Add option to smooth data in level_match_step
This PR allows the user to pass a variable "smooth_fwhm" to level_match, which will smooth out the data before doing the level matching. This can either be passed as a value, which will be interpreted as pixels, or as "xarcsec", which will use an angular value To avoid edge effects in the convolution, a mask is applied at the edges if this is used
1 parent ea58dda commit c2aa3e9

3 files changed

Lines changed: 98 additions & 5 deletions

File tree

CHANGES.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
1.3.1 (Unreleased)
22
==================
33

4+
- Add option to smooth data in ``level_match_step``
45
- Fix backgrounds being wrongly moved in ``lv1_step``
56
- Moved ``astrometric_align`` to a combined step, to avoid list ordering issues
67
- Updated dependencies, added specific pins, dependabot, and CODEOWNERS

pjpipe/level_match/level_match_step.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ def __init__(
333333
recombine_lyot=False,
334334
combine_nircam_short=False,
335335
do_local_subtraction=True,
336+
smooth_fwhm=None,
336337
sigma=3,
337338
npixels=3,
338339
dilate_size=7,
@@ -387,6 +388,11 @@ def __init__(
387388
chips before matching in a mosaic. Defaults to False
388389
do_local_subtraction: Whether to do a sigma-clipped local median
389390
subtraction. Defaults to True
391+
smooth_fwhm: By setting this, you can smooth the data with a Gaussian
392+
kernel with FWHM of this value before doing
393+
the level matching. Can be specified either as a raw number of
394+
pixels (just pass a number) or in arcsec (pass [int]arcsec).
395+
Defaults to None, which will not smooth
390396
sigma: Sigma for sigma-clipping. Defaults to 3
391397
npixels: Pixels to grow for masking. Defaults to 5
392398
dilate_size: make_source_mask dilation size. Defaults to 7
@@ -424,6 +430,18 @@ def __init__(
424430
log.warning("Cannot do local subtraction for methods beyond simple offset. Switching off")
425431
do_local_subtraction = False
426432

433+
# If we've passed the FWHM as a string, parse that now
434+
if smooth_fwhm is not None:
435+
if isinstance(smooth_fwhm, str):
436+
437+
# Case 1: we have arcsec in there, so convert to astropy units
438+
if "arcsec" in smooth_fwhm:
439+
smooth_fwhm = float(smooth_fwhm.replace("arcsec", "")) * u.arcsec
440+
441+
# Otherwise, just parse as pixels
442+
else:
443+
smooth_fwhm = float(smooth_fwhm)
444+
427445
self.in_dir = in_dir
428446
self.out_dir = out_dir
429447
self.step_ext = step_ext
@@ -436,6 +454,7 @@ def __init__(
436454
self.recombine_lyot = recombine_lyot
437455
self.combine_nircam_short = combine_nircam_short
438456
self.do_local_subtraction = do_local_subtraction
457+
self.smooth_fwhm = smooth_fwhm
439458
self.sigma = sigma
440459
self.npixels = npixels
441460
self.dilate_size = dilate_size
@@ -2296,6 +2315,7 @@ def get_reproject(
22962315
do_sigma_clip=self.do_sigma_clip,
22972316
stacked_image=stacked_image,
22982317
reproject_func=self.reproject_func,
2318+
smooth_fwhm=self.smooth_fwhm,
22992319
),
23002320
"err": reproject_image(
23012321
i,
@@ -2305,6 +2325,7 @@ def get_reproject(
23052325
do_sigma_clip=self.do_sigma_clip,
23062326
stacked_image=stacked_image,
23072327
reproject_func=self.reproject_func,
2328+
smooth_fwhm=self.smooth_fwhm,
23082329
),
23092330
}
23102331
for i in file
@@ -2318,6 +2339,7 @@ def get_reproject(
23182339
do_sigma_clip=self.do_sigma_clip,
23192340
stacked_image=stacked_image,
23202341
reproject_func=self.reproject_func,
2342+
smooth_fwhm=self.smooth_fwhm,
23212343
),
23222344
"err": reproject_image(
23232345
file,
@@ -2327,6 +2349,7 @@ def get_reproject(
23272349
do_sigma_clip=self.do_sigma_clip,
23282350
stacked_image=stacked_image,
23292351
reproject_func=self.reproject_func,
2352+
smooth_fwhm=self.smooth_fwhm,
23302353
),
23312354
}
23322355

pjpipe/utils/utils.py

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
import os
77
import warnings
88

9+
import astropy.units as u
910
import numpy as np
10-
from astropy.convolution import convolve_fft
11+
from astropy.convolution import convolve_fft, Gaussian2DKernel
1112
from astropy.io import fits
1213
from astropy.nddata.bitmask import interpret_bit_flags, bitfield_to_boolean_mask
1314
from astropy.stats import sigma_clipped_stats, SigmaClip
@@ -19,6 +20,7 @@
1920
from reproject.mosaicking import find_optimal_celestial_wcs, reproject_and_coadd
2021
from reproject.mosaicking.subset_array import ReprojectedArraySubset
2122
from scipy.interpolate import RegularGridInterpolator
23+
from scipy.ndimage import binary_dilation
2224
from stdatamodels import util
2325
from stdatamodels.jwst import datamodels
2426
from stdatamodels.jwst.datamodels.dqflags import pixel
@@ -695,6 +697,7 @@ def reproject_image(
695697
optimal_wcs,
696698
optimal_shape,
697699
hdu_type="data",
700+
smooth_fwhm=None,
698701
do_sigma_clip=False,
699702
stacked_image=False,
700703
do_level_data=False,
@@ -707,6 +710,9 @@ def reproject_image(
707710
optimal_wcs: Optimal WCS for input image stack
708711
optimal_shape: Optimal shape for input image stack
709712
hdu_type: Type of HDU. Can either be 'data', 'err', or 'var_rnoise'
713+
smooth_fwhm: FWHM for the Gaussian kernel to smooth the data. Can
714+
be specified either in astropy units or just a number, which will
715+
be pixels
710716
do_sigma_clip: Whether to perform sigma-clipping or not.
711717
Defaults to False
712718
stacked_image: Stacked image or not? Defaults to False
@@ -834,6 +840,69 @@ def reproject_image(
834840
)
835841
data_reproj_small[dq_reproj_small == 1] = np.nan
836842

843+
# If we're smoothing, do that here
844+
if smooth_fwhm is not None:
845+
846+
# Build the smoothing kernel. Need a FWHM in pixels
847+
if isinstance(smooth_fwhm, u.Quantity):
848+
pix_scale = w_in.proj_plane_pixel_scales()[0].to(u.arcsec)
849+
smooth_fwhm_pix = smooth_fwhm / pix_scale
850+
else:
851+
smooth_fwhm_pix = smooth_fwhm
852+
853+
smooth_sig_pix = smooth_fwhm_pix / 2.355
854+
855+
# Make the kernel, remember we're going FWHM->sigma
856+
k = Gaussian2DKernel(smooth_sig_pix)
857+
858+
# Because we need to square this sometimes, just get the array out
859+
# and normalise
860+
k = k.array
861+
862+
# For error and variance, we use the square of the kernel
863+
if hdu_type in ["err", "var_rnoise"]:
864+
k = k ** 2
865+
866+
k /= np.sum(k)
867+
868+
# We also want to build a dilation kernel for later, and keep track
869+
# of non-valid values
870+
dilate_struct = np.ones([int(smooth_sig_pix), int(smooth_sig_pix)])
871+
valid_value_mask = np.logical_or(data_reproj_small == 0, ~np.isfinite(data_reproj_small))
872+
873+
# Do the convolution. This is different depending on the HDU type
874+
875+
# For data and variance, we can just do the straight convolution
876+
if hdu_type in ["data", "var_rnoise"]:
877+
data_reproj_small = convolve_fft(data_reproj_small,
878+
kernel=k,
879+
allow_huge=True,
880+
preserve_nan=True,
881+
fill_value=np.nan,
882+
)
883+
884+
# For the error, we need the square of the error (and sqrt after)
885+
elif hdu_type == "err":
886+
data_reproj_small = np.sqrt(
887+
convolve_fft(data_reproj_small ** 2,
888+
kernel=k,
889+
allow_huge=True,
890+
preserve_nan=True,
891+
fill_value=np.nan,
892+
)
893+
)
894+
895+
else:
896+
raise Warning(f"Unsure how to deal with hdu_type {hdu_type}")
897+
898+
# Finally, dilate the valid value mask by the sigma of the smoothing kernel and
899+
# apply that as a mask
900+
valid_value_dilate = binary_dilation(valid_value_mask,
901+
structure=dilate_struct,
902+
)
903+
904+
data_reproj_small[valid_value_dilate == 1] = np.nan
905+
837906
# If we're sigma-clipping, reproject the mask. This needs to use
838907
# reproject_interp, so we can keep whole numbers
839908
if do_sigma_clip:
@@ -1060,11 +1129,11 @@ def level_data(
10601129

10611130
for i in range(3):
10621131
quad_1 = data[:, i * quadrant_size: (i + 1) * quadrant_size][
1063-
:, quadrant_size - 20:
1064-
]
1132+
:, quadrant_size - 20:
1133+
]
10651134
dq_1 = dq_mask[:, i * quadrant_size: (i + 1) * quadrant_size][
1066-
:, quadrant_size - 20:
1067-
]
1135+
:, quadrant_size - 20:
1136+
]
10681137
quad_2 = data[:, (i + 1) * quadrant_size: (i + 2) * quadrant_size][:, :20]
10691138
dq_2 = dq_mask[:, (i + 1) * quadrant_size: (i + 2) * quadrant_size][:, :20]
10701139

0 commit comments

Comments
 (0)