23
23
24
24
import lsstDebug
25
25
import numpy as np
26
- from lsst .afw .image import LOCAL , PARENT , ExposureF , ImageF , Mask , MaskedImageF
26
+ from lsst .afw .image import LOCAL , PARENT , ImageF , Mask , MaskedImageF
27
27
from lsst .afw .math import (
28
28
MEAN ,
29
29
MEANCLIP ,
43
43
stringToStatisticsProperty ,
44
44
stringToUndersampleStyle ,
45
45
)
46
- from lsst .geom import Box2D , Box2I , PointI
46
+ from lsst .geom import Box2I , PointI
47
47
from lsst .pex .config import ChoiceField , Field , ListField , RangeField
48
48
from lsst .pipe .base import PipelineTask , PipelineTaskConfig , PipelineTaskConnections , Struct , TaskError
49
49
from lsst .pipe .base .connectionTypes import Input , Output
@@ -137,7 +137,16 @@ class MatchBackgroundsConfig(PipelineTaskConfig, pipelineConnections=MatchBackgr
137
137
)
138
138
badMaskPlanes = ListField [str ](
139
139
doc = "Names of mask planes to ignore while estimating the background." ,
140
- default = ["NO_DATA" , "DETECTED" , "DETECTED_NEGATIVE" , "SAT" , "BAD" , "INTRP" , "CR" ],
140
+ default = [
141
+ "NO_DATA" ,
142
+ "DETECTED" ,
143
+ "DETECTED_NEGATIVE" ,
144
+ "SAT" ,
145
+ "BAD" ,
146
+ "INTRP" ,
147
+ "CR" ,
148
+ "NOT_DEBLENDED" ,
149
+ ],
141
150
itemCheck = lambda x : x in Mask ().getMaskPlaneDict (),
142
151
)
143
152
gridStatistic = ChoiceField (
@@ -237,9 +246,6 @@ def __init__(self, *args, **kwargs):
237
246
super ().__init__ (** kwargs )
238
247
self .statsFlag = stringToStatisticsProperty (self .config .gridStatistic )
239
248
self .statsCtrl = StatisticsControl ()
240
- # TODO: Check that setting the mask planes here work - these planes
241
- # can vary from exposure to exposure, I think?
242
- # Aaron: I think only the bit values vary, not the names, which this is referencing.
243
249
self .statsCtrl .setAndMask (Mask .getPlaneBitMask (self .config .badMaskPlanes ))
244
250
self .statsCtrl .setNanSafe (True )
245
251
self .statsCtrl .setNumSigmaClip (self .config .numSigmaClip )
@@ -278,7 +284,7 @@ def run(self, warps):
278
284
raise TaskError ("No exposures to match" )
279
285
280
286
# Define a reference warp; 'warps' is modified in-place to exclude it
281
- refWarp , refInd = self ._defineWarps (warps = warps , refWarpVisit = self .config .refWarpVisit )
287
+ refWarp , refInd , bkgd = self ._defineWarps (warps = warps , refWarpVisit = self .config .refWarpVisit )
282
288
283
289
# Images must be scaled to a common ZP
284
290
# Converting everything to nJy to accomplish this
@@ -287,29 +293,11 @@ def run(self, warps):
287
293
288
294
self .log .info ("Matching %d Exposures" , numExp )
289
295
290
- # Creating a null BackgroundList object by fitting a blank image
291
- statsFlag = stringToStatisticsProperty (self .config .gridStatistic )
292
- self .statsCtrl .setNumSigmaClip (self .config .numSigmaClip )
293
- self .statsCtrl .setNumIter (self .config .numIter )
294
-
295
- # TODO: refactor below to construct blank bg model
296
- im = refExposure .getMaskedImage ()
297
- blankIm = im .clone ()
298
- blankIm .image .array *= 0
299
-
300
- width = blankIm .getWidth ()
301
- height = blankIm .getHeight ()
302
- nx = width // self .config .binSize
303
- if width % self .config .binSize != 0 :
304
- nx += 1
305
- ny = height // self .config .binSize
306
- if height % self .config .binSize != 0 :
307
- ny += 1
308
-
309
- bctrl = BackgroundControl (nx , ny , self .statsCtrl , statsFlag )
310
- bctrl .setUndersampleStyle (self .config .undersampleStyle )
311
-
312
- bkgd = makeBackground (blankIm , bctrl )
296
+ # Blank ref warp background as reference background
297
+ bkgdIm = bkgd .getImageF ()
298
+ bkgdStatsIm = bkgd .getStatsImage ()
299
+ bkgdIm *= 0
300
+ bkgdStatsIm *= 0
313
301
blank = BackgroundList (
314
302
(
315
303
bkgd ,
@@ -325,7 +313,6 @@ def run(self, warps):
325
313
backgroundInfoList = []
326
314
matchedImageList = []
327
315
for exp in warps :
328
- # TODO: simplify what this prints?
329
316
self .log .info (
330
317
"Matching background of %s to %s" ,
331
318
exp .dataId ,
@@ -347,7 +334,6 @@ def run(self, warps):
347
334
toMatchExposure .image /= instFluxToNanojansky # Back to cts
348
335
matchedImageList .append (toMatchExposure )
349
336
350
- # TODO: more elegant solution than inserting blank model at ref ind?
351
337
backgroundInfoList .insert (refInd , blank )
352
338
refExposure .image /= instFluxToNanojanskyRef # Back to cts
353
339
matchedImageList .insert (refInd , refExposure )
@@ -377,6 +363,8 @@ def _defineWarps(self, warps, refWarpVisit=None):
377
363
Reference warped exposure.
378
364
refWarpIndex : `int`
379
365
Index of the reference removed from the list of warps.
366
+ warpBg : `~lsst.afw.math.BackgroundMI`
367
+ Temporary background model, used to make a blank BG for the ref
380
368
381
369
Notes
382
370
-----
@@ -454,7 +442,7 @@ def _defineWarps(self, warps, refWarpVisit=None):
454
442
ind = np .nanargmin (costFunctionVals )
455
443
refWarp = warps .pop (ind )
456
444
self .log .info ("Using best reference visit %d" , refWarp .dataId ["visit" ])
457
- return refWarp , ind
445
+ return refWarp , ind , warpBg
458
446
459
447
def _makeBackground (self , warp : MaskedImageF , binSize ) -> tuple [BackgroundMI , BackgroundControl ]:
460
448
"""Generate a simple binned background masked image for warped data.
@@ -528,11 +516,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
528
516
model : `~lsst.afw.math.BackgroundMI`
529
517
Background model of difference image, reference - science
530
518
"""
531
- # TODO: this is deprecated
532
- if lsstDebug .Info (__name__ ).savefits :
533
- refExposure .writeFits (lsstDebug .Info (__name__ ).figpath + "refExposure.fits" )
534
- sciExposure .writeFits (lsstDebug .Info (__name__ ).figpath + "sciExposure.fits" )
535
-
536
519
# Check Configs for polynomials:
537
520
if self .config .usePolynomial :
538
521
x , y = sciExposure .getDimensions ()
@@ -622,17 +605,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
622
605
resids = bgZ - modelValueArr
623
606
rms = np .sqrt (np .mean (resids [~ np .isnan (resids )] ** 2 ))
624
607
625
- # TODO: also deprecated; _gridImage() maybe can go?
626
- if lsstDebug .Info (__name__ ).savefits :
627
- sciExposure .writeFits (lsstDebug .Info (__name__ ).figpath + "sciMatchedExposure.fits" )
628
-
629
- if lsstDebug .Info (__name__ ).savefig :
630
- bbox = Box2D (refExposure .getMaskedImage ().getBBox ())
631
- try :
632
- self ._debugPlot (bgX , bgY , bgZ , bgdZ , bkgdImage , bbox , modelValueArr , resids )
633
- except Exception as e :
634
- self .log .warning ("Debug plot not generated: %s" , e )
635
-
636
608
meanVar = makeStatistics (diffMI .getVariance (), diffMI .getMask (), MEANCLIP , self .statsCtrl ).getValue ()
637
609
638
610
diffIm = diffMI .getImage ()
@@ -642,7 +614,6 @@ def matchBackgrounds(self, refExposure, sciExposure):
642
614
643
615
outBkgd = approx if self .config .usePolynomial else bkgd
644
616
# Convert this back into counts
645
- # TODO: is there a one-line way to do this?
646
617
statsIm = outBkgd .getStatsImage ()
647
618
statsIm /= instFluxToNanojansky
648
619
bkgdIm = outBkgd .getImageF ()
@@ -667,119 +638,3 @@ def matchBackgrounds(self, refExposure, sciExposure):
667
638
False ,
668
639
)
669
640
)
670
-
671
- def _debugPlot (self , X , Y , Z , dZ , modelImage , bbox , model , resids ):
672
- """
673
- Consider deleting this entirely
674
- Generate a plot showing the background fit and residuals.
675
-
676
- It is called when lsstDebug.Info(__name__).savefig = True.
677
- Saves the fig to lsstDebug.Info(__name__).figpath.
678
- Displays on screen if lsstDebug.Info(__name__).display = True.
679
-
680
- Parameters
681
- ----------
682
- X : `np.ndarray`, (N,)
683
- Array of x positions.
684
- Y : `np.ndarray`, (N,)
685
- Array of y positions.
686
- Z : `np.ndarray`
687
- Array of the grid values that were interpolated.
688
- dZ : `np.ndarray`, (len(Z),)
689
- Array of the error on the grid values.
690
- modelImage : `Unknown`
691
- Image of the model of the fit.
692
- model : `np.ndarray`, (len(Z),)
693
- Array of len(Z) containing the grid values predicted by the model.
694
- resids : `Unknown`
695
- Z - model.
696
- """
697
- import matplotlib .colors
698
- import matplotlib .pyplot as plt
699
- from mpl_toolkits .axes_grid1 import ImageGrid
700
-
701
- zeroIm = MaskedImageF (Box2I (bbox ))
702
- zeroIm += modelImage
703
- x0 , y0 = zeroIm .getXY0 ()
704
- dx , dy = zeroIm .getDimensions ()
705
- if len (Z ) == 0 :
706
- self .log .warning ("No grid. Skipping plot generation." )
707
- else :
708
- max , min = np .max (Z ), np .min (Z )
709
- norm = matplotlib .colors .normalize (vmax = max , vmin = min )
710
- maxdiff = np .max (np .abs (resids ))
711
- diffnorm = matplotlib .colors .normalize (vmax = maxdiff , vmin = - maxdiff )
712
- rms = np .sqrt (np .mean (resids ** 2 ))
713
- fig = plt .figure (1 , (8 , 6 ))
714
- meanDz = np .mean (dZ )
715
- grid = ImageGrid (
716
- fig ,
717
- 111 ,
718
- nrows_ncols = (1 , 2 ),
719
- axes_pad = 0.1 ,
720
- share_all = True ,
721
- label_mode = "L" ,
722
- cbar_mode = "each" ,
723
- cbar_size = "7%" ,
724
- cbar_pad = "2%" ,
725
- cbar_location = "top" ,
726
- )
727
- im = grid [0 ].imshow (
728
- zeroIm .getImage ().getArray (), extent = (x0 , x0 + dx , y0 + dy , y0 ), norm = norm , cmap = "Spectral"
729
- )
730
- im = grid [0 ].scatter (
731
- X , Y , c = Z , s = 15.0 * meanDz / dZ , edgecolor = "none" , norm = norm , marker = "o" , cmap = "Spectral"
732
- )
733
- im2 = grid [1 ].scatter (X , Y , c = resids , edgecolor = "none" , norm = diffnorm , marker = "s" , cmap = "seismic" )
734
- grid .cbar_axes [0 ].colorbar (im )
735
- grid .cbar_axes [1 ].colorbar (im2 )
736
- grid [0 ].axis ([x0 , x0 + dx , y0 + dy , y0 ])
737
- grid [1 ].axis ([x0 , x0 + dx , y0 + dy , y0 ])
738
- grid [0 ].set_xlabel ("model and grid" )
739
- grid [1 ].set_xlabel ("residuals. rms = %0.3f" % (rms ))
740
- if lsstDebug .Info (__name__ ).savefig :
741
- fig .savefig (lsstDebug .Info (__name__ ).figpath + self .debugDataIdString + ".png" )
742
- if lsstDebug .Info (__name__ ).display :
743
- plt .show ()
744
- plt .clf ()
745
-
746
- def _gridImage (self , maskedImage , binsize , statsFlag ):
747
- """Private method to grid an image for debugging."""
748
- width , height = maskedImage .getDimensions ()
749
- x0 , y0 = maskedImage .getXY0 ()
750
- xedges = np .arange (0 , width , binsize )
751
- yedges = np .arange (0 , height , binsize )
752
- xedges = np .hstack ((xedges , width )) # add final edge
753
- yedges = np .hstack ((yedges , height )) # add final edge
754
-
755
- # Use lists/append to protect against the case where
756
- # a bin has no valid pixels and should not be included in the fit
757
- bgX = []
758
- bgY = []
759
- bgZ = []
760
- bgdZ = []
761
-
762
- for ymin , ymax in zip (yedges [0 :- 1 ], yedges [1 :]):
763
- for xmin , xmax in zip (xedges [0 :- 1 ], xedges [1 :]):
764
- subBBox = Box2I (
765
- PointI (int (x0 + xmin ), int (y0 + ymin )),
766
- PointI (int (x0 + xmax - 1 ), int (y0 + ymax - 1 )),
767
- )
768
- subIm = MaskedImageF (maskedImage , subBBox , PARENT , False )
769
- stats = makeStatistics (
770
- subIm ,
771
- MEAN | MEANCLIP | MEDIAN | NPOINT | STDEV ,
772
- self .statsCtrl ,
773
- )
774
- npoints , _ = stats .getResult (NPOINT )
775
- if npoints >= 2 :
776
- stdev , _ = stats .getResult (STDEV )
777
- if stdev < self .config .gridStdevEpsilon :
778
- stdev = self .config .gridStdevEpsilon
779
- bgX .append (0.5 * (x0 + xmin + x0 + xmax ))
780
- bgY .append (0.5 * (y0 + ymin + y0 + ymax ))
781
- bgdZ .append (stdev / np .sqrt (npoints ))
782
- est , _ = stats .getResult (statsFlag )
783
- bgZ .append (est )
784
-
785
- return np .array (bgX ), np .array (bgY ), np .array (bgZ ), np .array (bgdZ )
0 commit comments