4
4
from astropy .wcs import WCS
5
5
from astropy .wcs .wcsapi import SlicedLowLevelWCS
6
6
7
+ from ..array_utils import sample_array_edges
7
8
from ..utils import parse_input_data , parse_input_weights , parse_output_projection
8
9
from .background import determine_offset_matrix , solve_corrections_sgd
9
10
from .subset_array import ReprojectedArraySubset
10
11
11
12
__all__ = ["reproject_and_coadd" ]
12
13
13
14
15
+ def _noop (iterable ):
16
+ return iterable
17
+
18
+
14
19
def reproject_and_coadd (
15
20
input_data ,
16
21
output_projection ,
@@ -24,14 +29,15 @@ def reproject_and_coadd(
24
29
background_reference = None ,
25
30
output_array = None ,
26
31
output_footprint = None ,
32
+ block_sizes = None ,
33
+ progress_bar = None ,
34
+ blank_pixel_value = 0 ,
27
35
** kwargs ,
28
36
):
29
37
"""
30
- Given a set of input images , reproject and co-add these to a single
38
+ Given a set of input data , reproject and co-add these to a single
31
39
final image.
32
40
33
- This currently only works with 2-d images with celestial WCS.
34
-
35
41
Parameters
36
42
----------
37
43
input_data : iterable
@@ -77,7 +83,7 @@ def reproject_and_coadd(
77
83
`~astropy.io.fits.HDUList` instance, specifies the HDU to use.
78
84
reproject_function : callable
79
85
The function to use for the reprojection.
80
- combine_function : { 'mean', 'sum', 'median', ' first', 'last', 'min', 'max' }
86
+ combine_function : { 'mean', 'sum', 'first', 'last', 'min', 'max' }
81
87
The type of function to use for combining the values into the final
82
88
image. For 'first' and 'last', respectively, the reprojected images are
83
89
simply overlaid on top of each other. With respect to the order of the
@@ -92,11 +98,22 @@ def reproject_and_coadd(
92
98
output_array : array or None
93
99
The final output array. Specify this if you already have an
94
100
appropriately-shaped array to store the data in. Must match shape
95
- specified with ``shape_out`` or derived from the output projection.
101
+ specified with `shape_out` or derived from the output
102
+ projection.
96
103
output_footprint : array or None
97
104
The final output footprint array. Specify this if you already have an
98
105
appropriately-shaped array to store the data in. Must match shape
99
- specified with ``shape_out`` or derived from the output projection.
106
+ specified with `shape_out` or derived from the output projection.
107
+ block_sizes : list of tuples or None
108
+ The block size to use for each dataset. Could also be a single tuple
109
+ if you want the sample block size for all data sets.
110
+ progress_bar : callable, optional
111
+ If specified, use this as a progress_bar to track loop iterations over
112
+ data sets.
113
+ blank_pixel_value : float, optional
114
+ Value to use for areas of the resulting mosaic that do not have input
115
+ data.
116
+
100
117
**kwargs
101
118
Keyword arguments to be passed to the reprojection function.
102
119
@@ -116,34 +133,49 @@ def reproject_and_coadd(
116
133
117
134
# Validate inputs
118
135
119
- if combine_function not in ("mean" , "sum" , "median" , " first" , "last" , "min" , "max" ):
120
- raise ValueError ("combine_function should be one of mean/sum/median/ first/last/min/max" )
136
+ if combine_function not in ("mean" , "sum" , "first" , "last" , "min" , "max" ):
137
+ raise ValueError ("combine_function should be one of mean/sum/first/last/min/max" )
121
138
122
139
if reproject_function is None :
123
140
raise ValueError (
124
141
"reprojection function should be specified with the reproject_function argument"
125
142
)
126
143
144
+ if progress_bar is None :
145
+ progress_bar = _noop
146
+
127
147
# Parse the output projection to avoid having to do it for each
128
148
129
149
wcs_out , shape_out = parse_output_projection (output_projection , shape_out = shape_out )
130
150
131
- if output_array is not None and output_array .shape != shape_out :
151
+ if output_array is None :
152
+ output_array = np .zeros (shape_out )
153
+ elif output_array .shape != shape_out :
132
154
raise ValueError (
133
155
"If you specify an output array, it must have a shape matching "
134
156
f"the output shape { shape_out } "
135
157
)
136
- if output_footprint is not None and output_footprint .shape != shape_out :
158
+
159
+ if output_footprint is None :
160
+ output_footprint = np .zeros (shape_out )
161
+ elif output_footprint .shape != shape_out :
137
162
raise ValueError (
138
163
"If you specify an output footprint array, it must have a shape matching "
139
164
f"the output shape { shape_out } "
140
165
)
141
166
167
+ # Define 'on-the-fly' mode: in the case where we don't need to match
168
+ # the backgrounds and we are combining with 'mean' or 'sum', we don't
169
+ # have to keep track of the intermediate arrays and can just modify
170
+ # the output array on-the-fly
171
+ on_the_fly = not match_background and combine_function in ("mean" , "sum" )
172
+
142
173
# Start off by reprojecting individual images to the final projection
143
174
144
- arrays = []
175
+ if not on_the_fly :
176
+ arrays = []
145
177
146
- for idata in range (len (input_data )):
178
+ for idata in progress_bar ( range (len (input_data ) )):
147
179
# We need to pre-parse the data here since we need to figure out how to
148
180
# optimize/minimize the size of each output tile (see below).
149
181
array_in , wcs_in = parse_input_data (input_data [idata ], hdu_in = hdu_in )
@@ -166,42 +198,48 @@ def reproject_and_coadd(
166
198
# significant distortion (when the edges of the input image become
167
199
# convex in the output projection), and transforming every edge pixel,
168
200
# which provides a lot of redundant information.
169
- ny , nx = array_in .shape
170
- n_per_edge = 11
171
- xs = np .linspace (- 0.5 , nx - 0.5 , n_per_edge )
172
- ys = np .linspace (- 0.5 , ny - 0.5 , n_per_edge )
173
- xs = np .concatenate ((xs , np .full (n_per_edge , xs [- 1 ]), xs , np .full (n_per_edge , xs [0 ])))
174
- ys = np .concatenate ((np .full (n_per_edge , ys [0 ]), ys , np .full (n_per_edge , ys [- 1 ]), ys ))
175
- xc_out , yc_out = wcs_out .world_to_pixel (wcs_in .pixel_to_world (xs , ys ))
201
+
202
+ edges = sample_array_edges (array_in .shape , n_samples = 11 )[::- 1 ]
203
+ edges_out = wcs_out .world_to_pixel (wcs_in .pixel_to_world (* edges ))[::- 1 ]
176
204
177
205
# Determine the cutout parameters
178
206
179
207
# In some cases, images might not have valid coordinates in the corners,
180
208
# such as all-sky images or full solar disk views. In this case we skip
181
209
# this step and just use the full output WCS for reprojection.
182
210
183
- if np .any (np .isnan (xc_out )) or np .any (np .isnan (yc_out )):
184
- imin = 0
185
- imax = shape_out [1 ]
186
- jmin = 0
187
- jmax = shape_out [0 ]
188
- else :
189
- imin = max (0 , int (np .floor (xc_out .min () + 0.5 )))
190
- imax = min (shape_out [1 ], int (np .ceil (xc_out .max () + 0.5 )))
191
- jmin = max (0 , int (np .floor (yc_out .min () + 0.5 )))
192
- jmax = min (shape_out [0 ], int (np .ceil (yc_out .max () + 0.5 )))
211
+ ndim_out = len (shape_out )
193
212
194
- if imax < imin or jmax < jmin :
213
+ skip_data = False
214
+ if np .any (np .isnan (edges_out )):
215
+ bounds = list (zip ([0 ] * ndim_out , shape_out ))
216
+ else :
217
+ bounds = []
218
+ for idim in range (ndim_out ):
219
+ imin = max (0 , int (np .floor (edges_out [idim ].min () + 0.5 )))
220
+ imax = min (shape_out [idim ], int (np .ceil (edges_out [idim ].max () + 0.5 )))
221
+ bounds .append ((imin , imax ))
222
+ if imax < imin :
223
+ skip_data = True
224
+ break
225
+
226
+ if skip_data :
195
227
continue
196
228
229
+ slice_out = tuple ([slice (imin , imax ) for (imin , imax ) in bounds ])
230
+
197
231
if isinstance (wcs_out , WCS ):
198
- wcs_out_indiv = wcs_out [jmin : jmax , imin : imax ]
232
+ wcs_out_indiv = wcs_out [slice_out ]
199
233
else :
200
- wcs_out_indiv = SlicedLowLevelWCS (
201
- wcs_out .low_level_wcs , (slice (jmin , jmax ), slice (imin , imax ))
202
- )
234
+ wcs_out_indiv = SlicedLowLevelWCS (wcs_out .low_level_wcs , slice_out )
203
235
204
- shape_out_indiv = (jmax - jmin , imax - imin )
236
+ shape_out_indiv = [imax - imin for (imin , imax ) in bounds ]
237
+
238
+ if block_sizes is not None :
239
+ if len (block_sizes ) == len (input_data ) and len (block_sizes [idata ]) == len (shape_out ):
240
+ kwargs ["block_size" ] = block_sizes [idata ]
241
+ else :
242
+ kwargs ["block_size" ] = block_sizes
205
243
206
244
# TODO: optimize handling of weights by making reprojection functions
207
245
# able to handle weights, and make the footprint become the combined
@@ -235,12 +273,20 @@ def reproject_and_coadd(
235
273
weights [reset ] = 0.0
236
274
footprint *= weights
237
275
238
- array = ReprojectedArraySubset (array , footprint , imin , imax , jmin , jmax )
276
+ array = ReprojectedArraySubset (array , footprint , bounds )
239
277
240
278
# TODO: make sure we gracefully handle the case where the
241
279
# output image is empty (due e.g. to no overlap).
242
280
243
- arrays .append (array )
281
+ if on_the_fly :
282
+ # By default, values outside of the footprint are set to NaN
283
+ # but we set these to 0 here to avoid getting NaNs in the
284
+ # means/sums.
285
+ array .array [array .footprint == 0 ] = 0
286
+ output_array [array .view_in_original_array ] += array .array * array .footprint
287
+ output_footprint [array .view_in_original_array ] += array .footprint
288
+ else :
289
+ arrays .append (array )
244
290
245
291
# If requested, try and match the backgrounds.
246
292
if match_background and len (arrays ) > 1 :
@@ -251,37 +297,32 @@ def reproject_and_coadd(
251
297
for array , correction in zip (arrays , corrections , strict = True ):
252
298
array .array -= correction
253
299
254
- # At this point, the images are now ready to be co-added.
255
-
256
- if output_array is None :
257
- output_array = np .zeros (shape_out )
258
- if output_footprint is None :
259
- output_footprint = np .zeros (shape_out )
260
-
261
- if combine_function == "min" :
262
- output_array [...] = np .inf
263
- elif combine_function == "max" :
264
- output_array [...] = - np .inf
265
-
266
300
if combine_function in ("mean" , "sum" ):
267
- for array in arrays :
268
- # By default, values outside of the footprint are set to NaN
269
- # but we set these to 0 here to avoid getting NaNs in the
270
- # means/sums.
271
- array .array [array .footprint == 0 ] = 0
301
+ if match_background :
302
+ # if we're not matching the background, this part has already been done
303
+ for array in arrays :
304
+ # By default, values outside of the footprint are set to NaN
305
+ # but we set these to 0 here to avoid getting NaNs in the
306
+ # means/sums.
307
+ array .array [array .footprint == 0 ] = 0
272
308
273
- output_array [array .view_in_original_array ] += array .array * array .footprint
274
- output_footprint [array .view_in_original_array ] += array .footprint
309
+ output_array [array .view_in_original_array ] += array .array * array .footprint
310
+ output_footprint [array .view_in_original_array ] += array .footprint
275
311
276
312
if combine_function == "mean" :
277
313
with np .errstate (invalid = "ignore" ):
278
314
output_array /= output_footprint
279
- output_array [output_footprint == 0 ] = 0
315
+ output_array [output_footprint == 0 ] = blank_pixel_value
280
316
281
317
elif combine_function in ("first" , "last" , "min" , "max" ):
318
+ if combine_function == "min" :
319
+ output_array [...] = np .inf
320
+ elif combine_function == "max" :
321
+ output_array [...] = - np .inf
322
+
282
323
for array in arrays :
283
324
if combine_function == "first" :
284
- mask = ( output_footprint [array .view_in_original_array ] == 0 ) & ( array . footprint > 0 )
325
+ mask = output_footprint [array .view_in_original_array ] == 0
285
326
elif combine_function == "last" :
286
327
mask = array .footprint > 0
287
328
elif combine_function == "min" :
@@ -300,13 +341,6 @@ def reproject_and_coadd(
300
341
mask , array .array , output_array [array .view_in_original_array ]
301
342
)
302
343
303
- elif combine_function == "median" :
304
- # Here we need to operate in chunks since we could otherwise run
305
- # into memory issues
306
-
307
- raise NotImplementedError ("combine_function='median' is not yet implemented" )
308
-
309
- if combine_function in ("min" , "max" ):
310
- output_array [output_footprint == 0 ] = 0.0
344
+ output_array [output_footprint == 0 ] = blank_pixel_value
311
345
312
346
return output_array , output_footprint
0 commit comments