66import os
77import warnings
88
9+ import astropy .units as u
910import numpy as np
10- from astropy .convolution import convolve_fft
11+ from astropy .convolution import convolve_fft , Gaussian2DKernel
1112from astropy .io import fits
1213from astropy .nddata .bitmask import interpret_bit_flags , bitfield_to_boolean_mask
1314from astropy .stats import sigma_clipped_stats , SigmaClip
1920from reproject .mosaicking import find_optimal_celestial_wcs , reproject_and_coadd
2021from reproject .mosaicking .subset_array import ReprojectedArraySubset
2122from scipy .interpolate import RegularGridInterpolator
23+ from scipy .ndimage import binary_dilation
2224from stdatamodels import util
2325from stdatamodels .jwst import datamodels
2426from 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