2323
2424import lsstDebug
2525import numpy as np
26- from lsst .afw .image import LOCAL , PARENT , Mask , MaskedImageF
26+ from lsst .afw .image import LOCAL , PARENT , ImageF , Mask , MaskedImageF
2727from lsst .afw .math import (
2828 MEAN ,
2929 MEANCLIP ,
@@ -250,12 +250,11 @@ def run(self, warps):
250250 raise TaskError ("No exposures to match" )
251251
252252 # Define a reference warp; 'warps' is modified in-place to exclude it
253- refWarp = self ._defineWarps (warps = warps , refWarpVisit = self .config .refWarpVisit )
254- breakpoint ()
253+ refWarp , refInd = self ._defineWarps (warps = warps , refWarpVisit = self .config .refWarpVisit )
255254
256255 # Images must be scaled to a common ZP
257256 # Converting everything to nJy to accomplish this
258- refExposure = refMatchedWarp .get ()
257+ refExposure = refWarp .get ()
259258 refMI = self ._fluxScale (refExposure ) # Also modifies refExposure
260259
261260 # TODO: figure out what this was creating and why
@@ -270,7 +269,7 @@ def run(self, warps):
270269
271270 # TODO: refactor below to construct blank bg model
272271 im = refExposure .getMaskedImage ()
273- blankIm = im .Factory (im , True ) # Don't do this
272+ blankIm = im .Factory (im , True )
274273 blankIm .image .array *= 0
275274
276275 width = blankIm .getWidth ()
@@ -286,53 +285,43 @@ def run(self, warps):
286285 bctrl .setUndersampleStyle (self .config .undersampleStyle )
287286
288287 bkgd = makeBackground (blankIm , bctrl )
288+ blank = BackgroundList (
289+ (
290+ bkgd ,
291+ stringToInterpStyle (self .config .interpStyle ),
292+ stringToUndersampleStyle (self .config .undersampleStyle ),
293+ ApproximateControl .UNKNOWN ,
294+ 0 ,
295+ 0 ,
296+ False ,
297+ )
298+ )
289299
290300 backgroundInfoList = []
291301 for ind , exp in enumerate (warps ):
292- if ind in refIndSet :
293- backgroundInfoStruct = BackgroundList (
294- (
295- bkgd ,
296- stringToInterpStyle (self .config .interpStyle ),
297- stringToUndersampleStyle (self .config .undersampleStyle ),
298- ApproximateControl .UNKNOWN ,
299- 0 ,
300- 0 ,
301- False ,
302- )
302+ # TODO: simplify this maybe, using only visit IDs?
303+ self .log .info ("Matching background of %s to %s" , exp .dataId , refWarp .dataId )
304+ toMatchExposure = exp .get ()
305+ try :
306+ # TODO: adjust logic to avoid creating spurious MIs like this
307+ toMatchMI = self ._fluxScale (toMatchExposure )
308+
309+ # store a string specifying the visit to label debug plot
310+ # self.debugDataIdString = ''.join([str(toMatchRef.dataId[vk]) for vk in debugIdKeyList])
311+
312+ backgroundInfoStruct = self .matchBackgrounds (
313+ refExposure = refExposure ,
314+ sciExposure = toMatchExposure ,
303315 )
304- else :
305- # TODO: simplify this maybe, using only visit IDs?
306- self .log .info ("Matching background of %s to %s" , exp .dataId , refMatchedWarp .dataId )
307- toMatchExposure = exp .get ()
308- try :
309- # Seems to require exposure, not masked exposure.
310- toMatchMI = self ._fluxScale (toMatchExposure )
311-
312- # store a string specifying the visit to label debug plot
313- # self.debugDataIdString = ''.join([str(toMatchRef.dataId[vk]) for vk in debugIdKeyList])
314-
315- backgroundInfoStruct = self .matchBackgrounds (
316- refExposure = refExposure ,
317- sciExposure = toMatchExposure ,
318- )
319- backgroundInfoStruct .isReference = False
320- except Exception as e :
321- self .log .warning ("Failed to fit background %s: %s" , exp .dataId , e )
322- backgroundInfoStruct = BackgroundList (
323- (
324- bkgd ,
325- stringToInterpStyle (self .config .interpStyle ),
326- stringToUndersampleStyle (self .config .undersampleStyle ),
327- ApproximateControl .UNKNOWN ,
328- 0 ,
329- 0 ,
330- False ,
331- )
332- )
316+ backgroundInfoStruct .isReference = False
317+ except Exception as e :
318+ self .log .warning ("Failed to fit background %s: %s" , exp .dataId , e )
319+ backgroundInfoStruct = blank
333320
334321 backgroundInfoList .append (backgroundInfoStruct )
335322
323+ # TODO: more elegant solution than inserting blank model at ref ind?
324+ backgroundInfoList .insert (refInd , blank )
336325 return Struct (backgroundInfoList = backgroundInfoList )
337326
338327 @timeMethod
@@ -377,15 +366,38 @@ def _defineWarps(self, warps, refWarpVisit=None):
377366 warpNPoints = []
378367 for warpDDH in warps :
379368 warp = warpDDH .get ()
369+
370+ # First check if any image edge is all NaN
371+ # If so, don't use
372+ leftBool = np .all (np .isnan (warp .image .array [:, 0 ]))
373+ rightBool = np .all (np .isnan (warp .image .array [:, warp .image .getHeight () - 1 ]))
374+ bottomBool = np .all (np .isnan (warp .image .array [0 , :]))
375+ topBool = np .all (np .isnan (warp .image .array [warp .image .getWidth () - 1 , :]))
376+ if np .any ([leftBool , rightBool , bottomBool , topBool ]):
377+ continue
378+
380379 warp .image .array *= warp .getPhotoCalib ().instFluxToNanojansky (1 ) # Convert image plane to nJy
381- warpStats = makeStatistics (warp .image , warp .mask , MEAN | VARIANCE | NPOINT , self .statsCtrl )
380+
381+ # Select reference based on BG of plane sky-subtracted images
382+ bkgd , __ , __ , __ = self ._setupBackground (warp )
383+
384+ weightByInverseVariance = self .config .approxWeighting
385+ actrl = ApproximateControl (ApproximateControl .CHEBYSHEV , 1 , 1 , weightByInverseVariance )
386+ undersampleStyle = stringToUndersampleStyle (self .config .undersampleStyle )
387+ approx = bkgd .getApproximate (actrl , undersampleStyle )
388+ bgSubImage = ImageF (warp .image .array - approx .getImage ().array )
389+
390+ warpStats = makeStatistics (bgSubImage , warp .mask , MEAN | VARIANCE | NPOINT , self .statsCtrl )
382391 warpMean , _ = warpStats .getResult (MEAN )
383392 warpVar , _ = warpStats .getResult (VARIANCE )
384393 warpNPoint , _ = warpStats .getResult (NPOINT )
385394 warpMeans .append (warpMean )
386395 warpVars .append (warpVar )
387396 warpNPoints .append (int (warpNPoint ))
388397
398+ if len (warpNPoints ) == 0 :
399+ raise TaskError ("No suitable reference visit found in list of warps." )
400+
389401 # Normalize mean/var/npoints to range from 0 to 1
390402 warpMeansFrac = np .array (warpMeans ) / np .nanmax (warpMeans )
391403 warpVarsFrac = np .array (warpVars ) / np .nanmax (warpVars )
@@ -396,21 +408,22 @@ def _defineWarps(self, warps, refWarpVisit=None):
396408 costFunctionVals += self .config .bestRefWeightVariance * warpVarsFrac
397409 costFunctionVals += self .config .bestRefWeightCoverage * warpNPointsFrac
398410
399- refWarp = warps .pop (np .nanargmin (costFunctionVals ))
411+ ind = np .nanargmin (costFunctionVals )
412+ refWarp = warps .pop (ind )
400413 self .log .info ("Using best reference visit %d" , refWarp .dataId ["visit" ])
401- return refWarp
414+ return refWarp , ind
402415
403416 def _fluxScale (self , exposure ):
404417 """Scales image to nJy flux using photometric calibration.
405418
406419 Parameters
407420 ----------
408- exposure: ``
421+ exposure: `lsst.afw.image._exposure.ExposureF `
409422 Exposure to scale.
410423
411424 Returns
412425 -------
413- maskedImage: ``
426+ maskedImage: `lsst.afw.image._maskedImage.MaskedImageF `
414427 Flux-scaled masked exposure.
415428 """
416429 maskedImage = exposure .getMaskedImage ()
@@ -419,6 +432,56 @@ def _fluxScale(self, exposure):
419432
420433 return maskedImage
421434
435+ def _setupBackground (self , warp ):
436+ """Set up and return a background model container and associated images
437+ and controllers.
438+
439+ Uses the following config parameters:
440+ - ``gridStatistic``
441+ - ``numSigmaClip``
442+ - ``numIter``
443+ - ``binSize``
444+ - ``undersampleStyle``
445+
446+ Parameters
447+ ----------
448+ warp: `lsst.afw.image._exposure.ExposureF`
449+ Warped exposure or difference image for which to estimate
450+ background.
451+
452+ Returns
453+ -------
454+ bkgd: `lsst.afw.math.BackgroundMI`
455+ Background model container.
456+ bctrl: `lsst.afw.math.BackgroundControl`
457+ Background model control
458+ warpMI: `lsst.afw.image._maskedImage.MaskedImageF`
459+ Masked image associated with warp
460+ statsFlag: `lsst.afw.math.Property`
461+ Flag for grid statistic property (self.config.gridStatistic)
462+ """
463+ statsFlag = stringToStatisticsProperty (self .config .gridStatistic )
464+ self .statsCtrl .setNumSigmaClip (self .config .numSigmaClip )
465+ self .statsCtrl .setNumIter (self .config .numIter )
466+
467+ warpMI = warp .getMaskedImage ()
468+
469+ width = warpMI .getWidth ()
470+ height = warpMI .getHeight ()
471+ nx = width // self .config .binSize
472+ if width % self .config .binSize != 0 :
473+ nx += 1
474+ ny = height // self .config .binSize
475+ if height % self .config .binSize != 0 :
476+ ny += 1
477+
478+ bctrl = BackgroundControl (nx , ny , self .statsCtrl , statsFlag )
479+ bctrl .setUndersampleStyle (self .config .undersampleStyle )
480+
481+ bkgd = makeBackground (warpMI , bctrl )
482+
483+ return bkgd , bctrl , warpMI , statsFlag
484+
422485 @timeMethod
423486 def matchBackgrounds (self , refExposure , sciExposure ):
424487 """Match science exposure's background level to that of reference
@@ -489,27 +552,7 @@ def matchBackgrounds(self, refExposure, sciExposure):
489552 "Exposures are different dimensions. sci:(%i, %i) vs. ref:(%i, %i)" % (wSci , hSci , wRef , hRef )
490553 )
491554
492- statsFlag = stringToStatisticsProperty (self .config .gridStatistic )
493- self .statsCtrl .setNumSigmaClip (self .config .numSigmaClip )
494- self .statsCtrl .setNumIter (self .config .numIter )
495-
496- im = refExposure .getMaskedImage ()
497- diffMI = im .Factory (im , True )
498- diffMI -= sciExposure .getMaskedImage ()
499-
500- width = diffMI .getWidth ()
501- height = diffMI .getHeight ()
502- nx = width // self .config .binSize
503- if width % self .config .binSize != 0 :
504- nx += 1
505- ny = height // self .config .binSize
506- if height % self .config .binSize != 0 :
507- ny += 1
508-
509- bctrl = BackgroundControl (nx , ny , self .statsCtrl , statsFlag )
510- bctrl .setUndersampleStyle (self .config .undersampleStyle )
511-
512- bkgd = makeBackground (diffMI , bctrl )
555+ bkgd , bctrl , diffMI , statsFlag = self ._setupBackground (refExposure )
513556
514557 # Some config and input checks if config.usePolynomial:
515558 # 1) Check that order/bin size make sense:
0 commit comments