5
5
6
6
import numpy as np
7
7
from astropy .nddata import NDData
8
+ from astropy .utils .decorators import deprecated_attribute
8
9
from astropy import units as u
10
+ from specutils import Spectrum1D
9
11
10
- from specreduce .extract import _ap_weight_image , _to_spectrum1d_pixels
12
+ from specreduce .core import _ImageParser
13
+ from specreduce .extract import _ap_weight_image
11
14
from specreduce .tracing import Trace , FlatTrace
12
15
13
16
__all__ = ['Background' ]
14
17
15
18
16
19
@dataclass
17
- class Background :
20
+ class Background ( _ImageParser ) :
18
21
"""
19
22
Determine the background from an image for subtraction.
20
23
@@ -27,7 +30,7 @@ class Background:
27
30
28
31
Parameters
29
32
----------
30
- image : `~astropy.nddata.NDData` or array-like
33
+ image : `~astropy.nddata.NDData`-like or array-like
31
34
image with 2-D spectral image data
32
35
traces : List
33
36
list of trace objects (or integers to define FlatTraces) to
@@ -54,13 +57,16 @@ class Background:
54
57
disp_axis : int = 1
55
58
crossdisp_axis : int = 0
56
59
60
+ # TO-DO: update bkg_array with Spectrum1D alternative (is bkg_image enough?)
61
+ bkg_array = deprecated_attribute ('bkg_array' , '1.3' )
62
+
57
63
def __post_init__ (self ):
58
64
"""
59
65
Determine the background from an image for subtraction.
60
66
61
67
Parameters
62
68
----------
63
- image : `~astropy.nddata.NDData` or array-like
69
+ image : `~astropy.nddata.NDData`-like or array-like
64
70
image with 2-D spectral image data
65
71
traces : List
66
72
list of trace objects (or integers to define FlatTraces) to
@@ -86,17 +92,18 @@ def _to_trace(trace):
86
92
raise ValueError ('trace_object.trace_pos must be >= 1' )
87
93
return trace
88
94
95
+ self .image = self ._parse_image (self .image )
96
+
89
97
if self .width < 0 :
90
98
raise ValueError ("width must be positive" )
91
-
92
99
if self .width == 0 :
93
- self .bkg_array = np .zeros (self .image .shape [self .disp_axis ])
100
+ self ._bkg_array = np .zeros (self .image .shape [self .disp_axis ])
94
101
return
95
102
96
103
if isinstance (self .traces , Trace ):
97
104
self .traces = [self .traces ]
98
105
99
- bkg_wimage = np .zeros_like (self .image , dtype = np .float64 )
106
+ bkg_wimage = np .zeros_like (self .image . data , dtype = np .float64 )
100
107
for trace in self .traces :
101
108
trace = _to_trace (trace )
102
109
windows_max = trace .trace .data .max () + self .width / 2
@@ -127,12 +134,13 @@ def _to_trace(trace):
127
134
self .bkg_wimage = bkg_wimage
128
135
129
136
if self .statistic == 'average' :
130
- self .bkg_array = np .average (self .image , weights = self .bkg_wimage ,
131
- axis = self .crossdisp_axis )
137
+ self ._bkg_array = np .average (self .image .data ,
138
+ weights = self .bkg_wimage ,
139
+ axis = self .crossdisp_axis )
132
140
elif self .statistic == 'median' :
133
- med_image = self .image .copy ()
141
+ med_image = self .image .data . copy ()
134
142
med_image [np .where (self .bkg_wimage ) == 0 ] = np .nan
135
- self .bkg_array = np .nanmedian (med_image , axis = self .crossdisp_axis )
143
+ self ._bkg_array = np .nanmedian (med_image , axis = self .crossdisp_axis )
136
144
else :
137
145
raise ValueError ("statistic must be 'average' or 'median'" )
138
146
@@ -150,9 +158,11 @@ def two_sided(cls, image, trace_object, separation, **kwargs):
150
158
151
159
Parameters
152
160
----------
153
- image : nddata-compatible image
154
- image with 2-D spectral image data
155
- trace_object: Trace
161
+ image : `~astropy.nddata.NDData`-like or array-like
162
+ Image with 2-D spectral image data. Assumes cross-dispersion
163
+ (spatial) direction is axis 0 and dispersion (wavelength)
164
+ direction is axis 1.
165
+ trace_object: `~specreduce.tracing.Trace`
156
166
estimated trace of the spectrum to center the background traces
157
167
separation: float
158
168
separation from ``trace_object`` for the background regions
@@ -167,6 +177,7 @@ def two_sided(cls, image, trace_object, separation, **kwargs):
167
177
crossdisp_axis : int
168
178
cross-dispersion axis
169
179
"""
180
+ image = cls ._parse_image (cls , image )
170
181
kwargs ['traces' ] = [trace_object - separation , trace_object + separation ]
171
182
return cls (image = image , ** kwargs )
172
183
@@ -183,9 +194,11 @@ def one_sided(cls, image, trace_object, separation, **kwargs):
183
194
184
195
Parameters
185
196
----------
186
- image : nddata-compatible image
187
- image with 2-D spectral image data
188
- trace_object: Trace
197
+ image : `~astropy.nddata.NDData`-like or array-like
198
+ Image with 2-D spectral image data. Assumes cross-dispersion
199
+ (spatial) direction is axis 0 and dispersion (wavelength)
200
+ direction is axis 1.
201
+ trace_object: `~specreduce.tracing.Trace`
189
202
estimated trace of the spectrum to center the background traces
190
203
separation: float
191
204
separation from ``trace_object`` for the background, positive will be
@@ -201,6 +214,7 @@ def one_sided(cls, image, trace_object, separation, **kwargs):
201
214
crossdisp_axis : int
202
215
cross-dispersion axis
203
216
"""
217
+ image = cls ._parse_image (cls , image )
204
218
kwargs ['traces' ] = [trace_object + separation ]
205
219
return cls (image = image , ** kwargs )
206
220
@@ -210,28 +224,32 @@ def bkg_image(self, image=None):
210
224
211
225
Parameters
212
226
----------
213
- image : nddata-compatible image or None
214
- image with 2-D spectral image data. If None, will extract
215
- the background from ``image`` used to initialize the class.
227
+ image : `~astropy.nddata.NDData`-like or array-like, optional
228
+ Image with 2-D spectral image data. Assumes cross-dispersion
229
+ (spatial) direction is axis 0 and dispersion (wavelength)
230
+ direction is axis 1. If None, will extract the background
231
+ from ``image`` used to initialize the class. [default: None]
216
232
217
233
Returns
218
234
-------
219
- array with same shape as ``image``.
235
+ Spectrum1D object with same shape as ``image``.
220
236
"""
221
- if image is None :
222
- image = self .image
223
-
224
- return np . tile ( self . bkg_array , ( image .shape [ 0 ], 1 ) )
237
+ image = self . _parse_image ( image )
238
+ return Spectrum1D ( np . tile ( self ._bkg_array ,
239
+ ( image . shape [ 0 ], 1 )) * image . unit ,
240
+ spectral_axis = image .spectral_axis )
225
241
226
242
def bkg_spectrum (self , image = None ):
227
243
"""
228
244
Expose the 1D spectrum of the background.
229
245
230
246
Parameters
231
247
----------
232
- image : nddata-compatible image or None
233
- image with 2-D spectral image data. If None, will extract
234
- the background from ``image`` used to initialize the class.
248
+ image : `~astropy.nddata.NDData`-like or array-like, optional
249
+ Image with 2-D spectral image data. Assumes cross-dispersion
250
+ (spatial) direction is axis 0 and dispersion (wavelength)
251
+ direction is axis 1. If None, will extract the background
252
+ from ``image`` used to initialize the class. [default: None]
235
253
236
254
Returns
237
255
-------
@@ -240,10 +258,15 @@ def bkg_spectrum(self, image=None):
240
258
units as the input image (or u.DN if none were provided) and
241
259
the spectral axis expressed in pixel units.
242
260
"""
243
- bkg_image = self .bkg_image (image = image )
261
+ bkg_image = self .bkg_image (image )
244
262
245
- ext1d = np .sum (bkg_image , axis = self .crossdisp_axis )
246
- return _to_spectrum1d_pixels (ext1d * getattr (image , 'unit' , u .DN ))
263
+ try :
264
+ return bkg_image .collapse (np .sum , axis = self .crossdisp_axis )
265
+ except u .UnitTypeError :
266
+ # can't collapse with a spectral axis in pixels because
267
+ # SpectralCoord only allows frequency/wavelength equivalent units...
268
+ ext1d = np .sum (bkg_image .flux , axis = self .crossdisp_axis )
269
+ return Spectrum1D (ext1d , bkg_image .spectral_axis )
247
270
248
271
def sub_image (self , image = None ):
249
272
"""
@@ -259,14 +282,16 @@ def sub_image(self, image=None):
259
282
-------
260
283
array with same shape as ``image``
261
284
"""
262
- if image is None :
263
- image = self .image
285
+ image = self ._parse_image (image )
264
286
265
- if isinstance (image , NDData ):
266
- # https://docs.astropy.org/en/stable/nddata/mixins/ndarithmetic.html
267
- return image .subtract (self .bkg_image (image )* image .unit )
268
- else :
269
- return image - self .bkg_image (image )
287
+ # a compare_wcs argument is needed for Spectrum1D.subtract() in order to
288
+ # avoid a TypeError from SpectralCoord when image's spectral axis is in
289
+ # pixels. it is not needed when image's spectral axis has physical units
290
+ kwargs = ({'compare_wcs' : None } if image .spectral_axis .unit == u .pix
291
+ else {})
292
+
293
+ # https://docs.astropy.org/en/stable/nddata/mixins/ndarithmetic.html
294
+ return image .subtract (self .bkg_image (image ), ** kwargs )
270
295
271
296
def sub_spectrum (self , image = None ):
272
297
"""
@@ -287,8 +312,13 @@ def sub_spectrum(self, image=None):
287
312
"""
288
313
sub_image = self .sub_image (image = image )
289
314
290
- ext1d = np .sum (sub_image , axis = self .crossdisp_axis )
291
- return _to_spectrum1d_pixels (ext1d * getattr (image , 'unit' , u .DN ))
315
+ try :
316
+ return sub_image .collapse (np .sum , axis = self .crossdisp_axis )
317
+ except u .UnitTypeError :
318
+ # can't collapse with a spectral axis in pixels because
319
+ # SpectralCoord only allows frequency/wavelength equivalent units...
320
+ ext1d = np .sum (sub_image .flux , axis = self .crossdisp_axis )
321
+ return Spectrum1D (ext1d , spectral_axis = sub_image .spectral_axis )
292
322
293
323
def __rsub__ (self , image ):
294
324
"""
0 commit comments