@@ -150,15 +150,15 @@ class BoxcarExtract(SpecreduceOperation):
150150 cross-dispersion axis
151151 mask_treatment
152152 The method for handling masked or non-finite data. Choice of ``filter``,
153- ``omit``, or ``zero-fill ``. If `filter` is chosen, the mask is ignored
153+ ``omit``, or ``exclude ``. If `` filter` ` is chosen, the mask is ignored
154154 and the non-finite data will passed to the extraction as is. If ``omit``
155155 is chosen, columns along disp_axis with any masked or non-finite data
156156 values will be fully masked (i.e, 2D mask is collapsed to 1D and applied).
157- If ``zero-fill `` is chosen, masked and non-finite data will be replaced
158- with 0.0 in the input image, and the mask will then be dropped.
159- For all three options, the input mask (optional on input NDData object)
160- will be combined with a mask generated from any non-finite values in the
161- image data.
157+ If ``exclude `` is chosen, masked and non-finite data will be excluded
158+ from the extraction and the spectrum extraction is carried out as a
159+ weighted sum. For all three options, the input mask (optional on input
160+ NDData object) will be combined with a mask generated from any non-finite
161+ values in the image data.
162162
163163 Returns
164164 -------
@@ -171,8 +171,9 @@ class BoxcarExtract(SpecreduceOperation):
171171 disp_axis : int = 1
172172 crossdisp_axis : int = 0
173173 # TODO: should disp_axis and crossdisp_axis be defined in the Trace object?
174- mask_treatment : Literal ['filter' , 'omit' , 'zero-fill' ] = 'filter'
175- _valid_mask_treatment_methods = ('filter' , 'omit' , 'zero-fill' )
174+ # TODO: should the 'filter' option be changed to 'propagate'
175+ mask_treatment : Literal ['filter' , 'omit' , 'exclude' ] = 'exclude'
176+ _valid_mask_treatment_methods = ('filter' , 'omit' , 'exclude' )
176177
177178 @property
178179 def spectrum (self ):
@@ -182,61 +183,63 @@ def __call__(self, image: NDData | None = None,
182183 trace : Trace | None = None ,
183184 width : float | None = None ,
184185 disp_axis : int | None = None ,
185- crossdisp_axis : int | None = None ):
186+ crossdisp_axis : int | None = None ) -> Spectrum1D :
186187 """
187188 Extract the 1D spectrum using the boxcar method.
188189
189190 Parameters
190191 ----------
191- image : `~astropy.nddata.NDData`-like or array-like, required
192- image with 2-D spectral image data
193- trace : Trace, required
194- trace object
195- width : float, optional
196- width of extraction aperture in pixels [default: 5]
197- disp_axis : int, optional
198- dispersion axis [default: 1]
199- crossdisp_axis : int, optional
200- cross-dispersion axis [default: 0]
192+ image
193+ The image with 2-D spectral image data
194+ trace
195+ The trace object
196+ width
197+ The width of extraction aperture in pixels
198+ disp_axis
199+ The dispersion axis
200+ crossdisp_axis
201+ The cross-dispersion axis
201202
202203 Returns
203204 -------
204- spec : `~specutils.Spectrum1D`
205+ spec
205206 The extracted 1d spectrum with flux expressed in the same
206207 units as the input image, or u.DN, and pixel units
207208 """
208209 image = image if image is not None else self .image
209- trace = trace if trace is not None else self .trace_object
210- width = width if width is not None else self .width
211- disp_axis = disp_axis if disp_axis is not None else self .disp_axis
212- crossdisp_axis = crossdisp_axis if crossdisp_axis is not None else self .crossdisp_axis
210+ trace = trace or self .trace_object
211+ width = width or self .width
212+ disp_axis = disp_axis or self .disp_axis
213+ cdisp_axis = crossdisp_axis or self .crossdisp_axis
214+ mask_mapping = {'filter' : 'filter' , 'exclude' : 'filter' , 'omit' : 'omit' }
215+
216+ if width <= 0 :
217+ raise ValueError ("The window width must be positive" )
213218
214219 # Parse image, including masked/nonfinite data handling based on
215- # choice of `mask_treatment`, which for BoxcarExtract can be filter, zero-fill , or
216- # omit . non-finite data will be masked, always. Returns a Spectrum1D.
220+ # choice of `mask_treatment`, which for BoxcarExtract can be filter, omit , or
221+ # exclude . non-finite data will be masked, always. Returns a Spectrum1D.
217222 self .image = self ._parse_image (image , disp_axis = disp_axis ,
218- mask_treatment = self .mask_treatment )
219-
220- # # _parse_image returns a Spectrum1D. convert this to a masked array
221- # # for ease of calculations here (even if there is no masked data).
222- # img = np.ma.masked_array(self.image.data, self.image.mask)
223+ mask_treatment = mask_mapping [self .mask_treatment ])
224+
225+ # Spectrum extraction
226+ # ===================
227+ # Assign no weight to non-finite pixels outside the window. Non-finite pixels inside
228+ # the window will be propagated to the sum if mask treatment is either ``filter`` or
229+ # ``omit`` or excluded if the chosen mask treatment option is ``exclude``. In the
230+ # latter case, the flux is calculated as the average of the non-masked pixels inside
231+ # the window multiplied by the window width.
232+ window_weights = _ap_weight_image (trace , width , disp_axis , cdisp_axis , self .image .shape )
233+
234+ if self .mask_treatment == 'exclude' :
235+ image_cleaned = np .where (~ self .image .mask , self .image .data * window_weights , 0.0 )
236+ weights = np .where (~ self .image .mask , window_weights , 0.0 )
237+ spectrum = image_cleaned .sum (axis = cdisp_axis ) / weights .sum (axis = cdisp_axis ) * width
238+ else :
239+ image_windowed = np .where (window_weights , self .image .data * window_weights , 0.0 )
240+ spectrum = np .sum (image_windowed , axis = cdisp_axis )
223241
224- if width <= 0 :
225- raise ValueError ("width must be positive" )
226-
227- # weight image to use for extraction
228- wimg = _ap_weight_image (trace ,
229- width ,
230- disp_axis ,
231- crossdisp_axis ,
232- self .image .shape )
233-
234- # extract, assigning no weight to non-finite pixels outside the window
235- # (non-finite pixels inside the window will still make it into the sum)
236- image_windowed = np .where (wimg , self .image .data * wimg , 0 )
237- ext1d = np .sum (image_windowed , axis = crossdisp_axis )
238- return Spectrum1D (ext1d * self .image .unit ,
239- spectral_axis = self .image .spectral_axis )
242+ return Spectrum1D (spectrum * self .image .unit , spectral_axis = self .image .spectral_axis )
240243
241244
242245@dataclass
0 commit comments