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
@@ -30,15 +31,13 @@ def reproject_and_coadd(
30
31
output_footprint = None ,
31
32
block_sizes = None ,
32
33
progress_bar = None ,
33
- blank_pixel_value = np . nan ,
34
+ blank_pixel_value = 0 ,
34
35
** kwargs ,
35
36
):
36
37
"""
37
- 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
38
39
final image.
39
40
40
- This currently only works with 2-d images with celestial WCS.
41
-
42
41
Parameters
43
42
----------
44
43
input_data : iterable
@@ -149,24 +148,31 @@ def reproject_and_coadd(
149
148
150
149
wcs_out , shape_out = parse_output_projection (output_projection , shape_out = shape_out )
151
150
152
- 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 :
153
154
raise ValueError (
154
155
"If you specify an output array, it must have a shape matching "
155
156
f"the output shape { shape_out } "
156
157
)
157
- 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 :
158
162
raise ValueError (
159
163
"If you specify an output footprint array, it must have a shape matching "
160
164
f"the output shape { shape_out } "
161
165
)
162
166
163
- if output_array is None :
164
- output_array = np .zeros (shape_out )
165
- if output_footprint is None :
166
- output_footprint = np .zeros (shape_out )
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" )
167
172
168
173
# Start off by reprojecting individual images to the final projection
169
- if match_background :
174
+
175
+ if not on_the_fly :
170
176
arrays = []
171
177
172
178
for idata in progress_bar (range (len (input_data ))):
@@ -192,71 +198,42 @@ def reproject_and_coadd(
192
198
# significant distortion (when the edges of the input image become
193
199
# convex in the output projection), and transforming every edge pixel,
194
200
# which provides a lot of redundant information.
195
- if array_in .ndim == 2 :
196
- ny , nx = array_in .shape
197
- n_per_edge = 11
198
- xs = np .linspace (- 0.5 , nx - 0.5 , n_per_edge )
199
- ys = np .linspace (- 0.5 , ny - 0.5 , n_per_edge )
200
- xs = np .concatenate ((xs , np .full (n_per_edge , xs [- 1 ]), xs , np .full (n_per_edge , xs [0 ])))
201
- ys = np .concatenate ((np .full (n_per_edge , ys [0 ]), ys , np .full (n_per_edge , ys [- 1 ]), ys ))
202
- xc_out , yc_out = wcs_out .world_to_pixel (wcs_in .pixel_to_world (xs , ys ))
203
- shape_out_cel = shape_out
204
- elif array_in .ndim == 3 :
205
- # for cubes, we only handle single corners now
206
- nz , ny , nx = array_in .shape
207
- xc = np .array ([- 0.5 , nx - 0.5 , nx - 0.5 , - 0.5 ])
208
- yc = np .array ([- 0.5 , - 0.5 , ny - 0.5 , ny - 0.5 ])
209
- zc = np .array ([- 0.5 , nz - 0.5 ])
210
- # TODO: figure out what to do here if the low_level_wcs doesn't support subsetting
211
- xc_out , yc_out = wcs_out .low_level_wcs .celestial .world_to_pixel (
212
- wcs_in .celestial .pixel_to_world (xc , yc )
213
- )
214
- zc_out = wcs_out .low_level_wcs .spectral .world_to_pixel (
215
- wcs_in .spectral .pixel_to_world (zc )
216
- )
217
- shape_out_cel = shape_out [1 :]
218
- else :
219
- raise ValueError (f"Wrong number of dimensions: { array_in .ndim } " )
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 ]
220
204
221
205
# Determine the cutout parameters
222
206
223
207
# In some cases, images might not have valid coordinates in the corners,
224
208
# such as all-sky images or full solar disk views. In this case we skip
225
209
# this step and just use the full output WCS for reprojection.
226
210
227
- if np .any (np .isnan (xc_out )) or np .any (np .isnan (yc_out )):
228
- imin = 0
229
- imax = shape_out_cel [1 ]
230
- jmin = 0
231
- jmax = shape_out_cel [0 ]
232
- else :
233
- imin = max (0 , int (np .floor (xc_out .min () + 0.5 )))
234
- imax = min (shape_out_cel [1 ], int (np .ceil (xc_out .max () + 0.5 )))
235
- jmin = max (0 , int (np .floor (yc_out .min () + 0.5 )))
236
- jmax = min (shape_out_cel [0 ], int (np .ceil (yc_out .max () + 0.5 )))
211
+ ndim_out = len (shape_out )
237
212
238
- 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 :
239
227
continue
240
228
241
- if array_in .ndim == 2 :
242
- if isinstance (wcs_out , WCS ):
243
- wcs_out_indiv = wcs_out [jmin :jmax , imin :imax ]
244
- else :
245
- wcs_out_indiv = SlicedLowLevelWCS (
246
- wcs_out .low_level_wcs , (slice (jmin , jmax ), slice (imin , imax ))
247
- )
248
- shape_out_indiv = (jmax - jmin , imax - imin )
249
- kmin , kmax = None , None # for reprojectedarraysubset below
250
- elif array_in .ndim == 3 :
251
- kmin = max (0 , int (np .floor (zc_out .min () + 0.5 )))
252
- kmax = min (shape_out [0 ], int (np .ceil (zc_out .max () + 0.5 )))
253
- if isinstance (wcs_out , WCS ):
254
- wcs_out_indiv = wcs_out [kmin :kmax , jmin :jmax , imin :imax ]
255
- else :
256
- wcs_out_indiv = SlicedLowLevelWCS (
257
- wcs_out .low_level_wcs , (slice (kmin , kmax ), slice (jmin , jmax ), slice (imin , imax ))
258
- )
259
- shape_out_indiv = (kmax - kmin , jmax - jmin , imax - imin )
229
+ slice_out = tuple ([slice (imin , imax ) for (imin , imax ) in bounds ])
230
+
231
+ if isinstance (wcs_out , WCS ):
232
+ wcs_out_indiv = wcs_out [slice_out ]
233
+ else :
234
+ wcs_out_indiv = SlicedLowLevelWCS (wcs_out .low_level_wcs , slice_out )
235
+
236
+ shape_out_indiv = [imax - imin for (imin , imax ) in bounds ]
260
237
261
238
if block_sizes is not None :
262
239
if len (block_sizes ) == len (input_data ) and len (block_sizes [idata ]) == len (shape_out ):
@@ -296,22 +273,20 @@ def reproject_and_coadd(
296
273
weights [reset ] = 0.0
297
274
footprint *= weights
298
275
299
- array = ReprojectedArraySubset (array , footprint , imin , imax , jmin , jmax , kmin , kmax )
276
+ array = ReprojectedArraySubset (array , footprint , bounds )
300
277
301
278
# TODO: make sure we gracefully handle the case where the
302
279
# output image is empty (due e.g. to no overlap).
303
280
304
- if match_background :
305
- 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
306
288
else :
307
- if combine_function in ("mean" , "sum" ):
308
- # By default, values outside of the footprint are set to NaN
309
- # but we set these to 0 here to avoid getting NaNs in the
310
- # means/sums.
311
- array .array [array .footprint == 0 ] = 0
312
-
313
- output_array [array .view_in_original_array ] += array .array * array .footprint
314
- output_footprint [array .view_in_original_array ] += array .footprint
289
+ arrays .append (array )
315
290
316
291
# If requested, try and match the backgrounds.
317
292
if match_background and len (arrays ) > 1 :
@@ -322,11 +297,6 @@ def reproject_and_coadd(
322
297
for array , correction in zip (arrays , corrections , strict = True ):
323
298
array .array -= correction
324
299
325
- if combine_function == "min" :
326
- output_array [...] = np .inf
327
- elif combine_function == "max" :
328
- output_array [...] = - np .inf
329
-
330
300
if combine_function in ("mean" , "sum" ):
331
301
if match_background :
332
302
# if we're not matching the background, this part has already been done
@@ -336,37 +306,41 @@ def reproject_and_coadd(
336
306
# means/sums.
337
307
array .array [array .footprint == 0 ] = 0
338
308
339
- output_array [array .view_in_original_array ] += array .array * array .footprint
340
- 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
341
311
342
312
if combine_function == "mean" :
343
313
with np .errstate (invalid = "ignore" ):
344
314
output_array /= output_footprint
345
315
output_array [output_footprint == 0 ] = blank_pixel_value
346
316
347
317
elif combine_function in ("first" , "last" , "min" , "max" ):
348
- if match_background :
349
- for array in arrays :
350
- if combine_function == "first" :
351
- mask = output_footprint [array .view_in_original_array ] == 0
352
- elif combine_function == "last" :
353
- mask = array .footprint > 0
354
- elif combine_function == "min" :
355
- mask = (array .footprint > 0 ) & (
356
- array .array < output_array [array .view_in_original_array ]
357
- )
358
- elif combine_function == "max" :
359
- mask = (array .footprint > 0 ) & (
360
- array .array > output_array [array .view_in_original_array ]
361
- )
362
-
363
- output_footprint [array .view_in_original_array ] = np .where (
364
- mask , array .footprint , output_footprint [array .view_in_original_array ]
318
+ if combine_function == "min" :
319
+ output_array [...] = np .inf
320
+ elif combine_function == "max" :
321
+ output_array [...] = - np .inf
322
+
323
+ for array in arrays :
324
+ if combine_function == "first" :
325
+ mask = output_footprint [array .view_in_original_array ] == 0
326
+ elif combine_function == "last" :
327
+ mask = array .footprint > 0
328
+ elif combine_function == "min" :
329
+ mask = (array .footprint > 0 ) & (
330
+ array .array < output_array [array .view_in_original_array ]
365
331
)
366
- output_array [array .view_in_original_array ] = np .where (
367
- mask , array .array , output_array [array .view_in_original_array ]
332
+ elif combine_function == "max" :
333
+ mask = (array .footprint > 0 ) & (
334
+ array .array > output_array [array .view_in_original_array ]
368
335
)
369
336
337
+ output_footprint [array .view_in_original_array ] = np .where (
338
+ mask , array .footprint , output_footprint [array .view_in_original_array ]
339
+ )
340
+ output_array [array .view_in_original_array ] = np .where (
341
+ mask , array .array , output_array [array .view_in_original_array ]
342
+ )
343
+
370
344
output_array [output_footprint == 0 ] = blank_pixel_value
371
345
372
346
return output_array , output_footprint
0 commit comments