29 import lsst.meas.algorithms
as measAlg
31 import lsst.pipe.base
as pipeBase
33 __all__ = (
"ImageMapReduceTask",
"ImageMapReduceConfig",
34 "ImageMapper",
"ImageMapperConfig",
35 "ImageReducer",
"ImageReducerConfig")
38 """Tasks for processing an exposure via processing on 39 multiple sub-exposures and then collecting the results 40 to either re-stitch the sub-exposures back into a new 41 exposure, or return summary results for each sub-exposure. 43 This provides a framework for arbitrary mapper-reducer 44 operations on an exposure by implementing simple operations in 45 subTasks. It currently is not parallelized, although it could be in 46 the future. It does enable operations such as spatially-mapped 47 processing on a grid across an image, processing regions surrounding 48 centroids (such as for PSF processing), etc. 50 It is implemented as primary Task, `ImageMapReduceTask` which contains 51 two subtasks, `ImageMapper` and `ImageReducer`. 52 `ImageMapReduceTask` configures the centroids and sub-exposure 53 dimensions to be processed, and then calls the `run` methods of the 54 `ImageMapper` and `ImageReducer` on those sub-exposures. 55 `ImageMapReduceTask` may be configured with a list of sub-exposure 56 centroids (`config.cellCentroidsX` and `config.cellCentroidsY`) and a 57 single pair of bounding boxes defining their dimensions, or a set of 58 parameters defining a regular grid of centroids (`config.gridStepX` 59 and `config.gridStepY`). 61 `ImageMapper` is an abstract class and must be subclassed with 62 an implemented `run` method to provide the desired operation for 63 processing individual sub-exposures. It is called from 64 `ImageMapReduceTask.run`, and may return a new, processed sub-exposure 65 which is to be "stitched" back into a new resulting larger exposure 66 (depending on the configured `ImageMapReduceTask.mapper`); 67 otherwise if it does not return an lsst.afw.image.Exposure, then the results are 68 passed back directly to the caller. 70 `ImageReducer` will either stitch the `mapperResults` list of 71 results generated by the `ImageMapper` together into a new 72 Exposure (by default) or pass it through to the 73 caller. `ImageReducer` has an implemented `run` method for 74 basic reducing operations (`reduceOperation`) such as `average` (which 75 will average all overlapping pixels from sub-exposures produced by the 76 `ImageMapper` into the new exposure). Another notable 77 implemented `reduceOperation` is 'none', in which case the 78 `mapperResults` list is simply returned directly. 83 """Configuration parameters for ImageMapper 89 """Abstract base class for any task that is to be 90 used as `ImageMapReduceConfig.mapper`. 92 An `ImageMapper` is responsible for processing individual 93 sub-exposures in its `run` method, which is called from 94 `ImageMapReduceTask.run`. `run` may return a processed new 95 sub-exposure which can be be "stitched" back into a new resulting 96 larger exposure (depending on the configured 97 `ImageReducer`); otherwise if it does not return an 98 lsst.afw.image.Exposure, then the 99 `ImageReducer.config.reducer.reduceOperation` 100 should be set to 'none' and the result will be propagated 103 ConfigClass = ImageMapperConfig
104 _DefaultName =
"ip_diffim_ImageMapper" 107 def run(self, subExposure, expandedSubExposure, fullBBox, **kwargs):
108 """Perform operation on `subExposure`. 110 To be implemented by subclasses. See class docstring for more 111 details. This method is given the `subExposure` which 112 is to be operated upon, and an `expandedSubExposure` which 113 will contain `subExposure` with additional surrounding 114 pixels. This allows for, for example, convolutions (which 115 should be performed on `expandedSubExposure`), to prevent the 116 returned sub-exposure from containing invalid pixels. 118 This method may return a new, processed sub-exposure which can 119 be be "stitched" back into a new resulting larger exposure 120 (depending on the paired, configured `ImageReducer`); 121 otherwise if it does not return an lsst.afw.image.Exposure, then the 122 `ImageReducer.config.mapper.reduceOperation` 123 should be set to 'none' and the result will be propagated 128 subExposure : lsst.afw.image.Exposure 129 the sub-exposure upon which to operate 130 expandedSubExposure : lsst.afw.image.Exposure 131 the expanded sub-exposure upon which to operate 132 fullBBox : lsst.afw.geom.BoundingBox 133 the bounding box of the original exposure 135 additional keyword arguments propagated from 136 `ImageMapReduceTask.run`. 140 A `pipeBase.Struct containing the result of the `subExposure` processing, 141 which may itself be of any type. See above for details. If it is an 142 lsst.afw.image.Exposure (processed sub-exposure), then the name in the Struct 143 should be 'subExposure'. This is implemented here as a pass-through 146 return pipeBase.Struct(subExposure=subExposure)
150 """Configuration parameters for the ImageReducer 152 reduceOperation = pexConfig.ChoiceField(
154 doc=
"""Operation to use for reducing subimages into new image.""",
157 "none":
"""simply return a list of values and don't re-map results into 158 a new image (noop operation)""",
159 "copy":
"""copy pixels directly from subimage into correct location in 160 new exposure (potentially non-deterministic for overlaps)""",
161 "sum":
"""add pixels from overlaps (probably never wanted; used for testing) 162 into correct location in new exposure""",
163 "average":
"""same as copy, but also average pixels from overlapped regions 165 "coaddPsf":
"""Instead of constructing an Exposure, take a list of returned 166 PSFs and use CoaddPsf to construct a single PSF that covers the 167 entire input exposure""",
170 badMaskPlanes = pexConfig.ListField(
172 doc=
"""Mask planes to set for invalid pixels""",
173 default=(
'INVALID_MAPREDUCE',
'BAD',
'NO_DATA')
178 """Base class for any 'reduce' task that is to be 179 used as `ImageMapReduceConfig.reducer`. 181 Basic reduce operations are provided by the `run` method 182 of this class, to be selected by its config. 184 ConfigClass = ImageReducerConfig
185 _DefaultName =
"ip_diffim_ImageReducer" 187 def run(self, mapperResults, exposure, **kwargs):
188 """Reduce a list of items produced by `ImageMapper`. 190 Either stitch the passed `mapperResults` list 191 together into a new Exposure (default) or pass it through 192 (if `self.config.reduceOperation` is 'none'). 194 If `self.config.reduceOperation` is not 'none', then expect 195 that the `pipeBase.Struct`s in the `mapperResults` list 196 contain sub-exposures named 'subExposure', to be stitched back 197 into a single Exposure with the same dimensions, PSF, and mask 198 as the input `exposure`. Otherwise, the `mapperResults` list 199 is simply returned directly. 204 list of `pipeBase.Struct` returned by `ImageMapper.run`. 205 exposure : lsst.afw.image.Exposure 206 the original exposure which is cloned to use as the 207 basis for the resulting exposure (if 208 self.config.mapper.reduceOperation is not 'none') 210 additional keyword arguments propagated from 211 `ImageMapReduceTask.run`. 215 A `pipeBase.Struct` containing either an `lsst.afw.image.Exposure` (named 'exposure') 216 or a list (named 'result'), depending on `config.reduceOperation`. 220 1. This currently correctly handles overlapping sub-exposures. 221 For overlapping sub-exposures, use `config.reduceOperation='average'`. 222 2. This correctly handles varying PSFs, constructing the resulting 223 exposure's PSF via CoaddPsf (DM-9629). 227 1. To be done: correct handling of masks (nearly there) 228 2. This logic currently makes *two* copies of the original exposure 229 (one here and one in `mapper.run()`). Possibly of concern 230 for large images on memory-constrained systems. 233 if self.config.reduceOperation ==
'none':
234 return pipeBase.Struct(result=mapperResults)
236 if self.config.reduceOperation ==
'coaddPsf':
239 return pipeBase.Struct(result=coaddPsf)
241 newExp = exposure.clone()
242 newMI = newExp.getMaskedImage()
244 reduceOp = self.config.reduceOperation
245 if reduceOp ==
'copy':
247 newMI.getImage()[:, :] = np.nan
248 newMI.getVariance()[:, :] = np.nan
250 newMI.getImage()[:, :] = 0.
251 newMI.getVariance()[:, :] = 0.
252 if reduceOp ==
'average':
253 weights = afwImage.ImageI(newMI.getBBox())
255 for item
in mapperResults:
256 item = item.subExposure
257 if not (isinstance(item, afwImage.ExposureF)
or isinstance(item, afwImage.ExposureI)
or 258 isinstance(item, afwImage.ExposureU)
or isinstance(item, afwImage.ExposureD)):
259 raise TypeError(
"""Expecting an Exposure type, got %s. 260 Consider using `reduceOperation="none".""" % str(type(item)))
261 subExp = newExp.Factory(newExp, item.getBBox())
262 subMI = subExp.getMaskedImage()
263 patchMI = item.getMaskedImage()
264 isValid = ~np.isnan(patchMI.getImage().getArray() * patchMI.getVariance().getArray())
266 if reduceOp ==
'copy':
267 subMI.getImage().getArray()[isValid] = patchMI.getImage().getArray()[isValid]
268 subMI.getVariance().getArray()[isValid] = patchMI.getVariance().getArray()[isValid]
269 subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
271 if reduceOp ==
'sum' or reduceOp ==
'average':
272 subMI.getImage().getArray()[isValid] += patchMI.getImage().getArray()[isValid]
273 subMI.getVariance().getArray()[isValid] += patchMI.getVariance().getArray()[isValid]
274 subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
275 if reduceOp ==
'average':
277 wtsView = afwImage.ImageI(weights, item.getBBox())
278 wtsView.getArray()[isValid] += 1
281 mask = newMI.getMask()
282 for m
in self.config.badMaskPlanes:
284 bad = mask.getPlaneBitMask(self.config.badMaskPlanes)
286 isNan = np.where(np.isnan(newMI.getImage().getArray() * newMI.getVariance().getArray()))
287 if len(isNan[0]) > 0:
289 mask.getArray()[isNan[0], isNan[1]] |= bad
291 if reduceOp ==
'average':
292 wts = weights.getArray().astype(np.float)
293 self.log.info(
'AVERAGE: Maximum overlap: %f', np.nanmax(wts))
294 self.log.info(
'AVERAGE: Average overlap: %f', np.nanmean(wts))
295 self.log.info(
'AVERAGE: Minimum overlap: %f', np.nanmin(wts))
296 wtsZero = np.equal(wts, 0.)
297 wtsZeroInds = np.where(wtsZero)
298 wtsZeroSum = len(wtsZeroInds[0])
299 self.log.info(
'AVERAGE: Number of zero pixels: %f (%f%%)', wtsZeroSum,
300 wtsZeroSum * 100. / wtsZero.size)
301 notWtsZero = ~wtsZero
302 tmp = newMI.getImage().getArray()
303 np.divide(tmp, wts, out=tmp, where=notWtsZero)
304 tmp = newMI.getVariance().getArray()
305 np.divide(tmp, wts, out=tmp, where=notWtsZero)
306 if len(wtsZeroInds[0]) > 0:
307 newMI.getImage().getArray()[wtsZeroInds] = np.nan
308 newMI.getVariance().getArray()[wtsZeroInds] = np.nan
311 mask.getArray()[wtsZeroInds] |= bad
314 if reduceOp ==
'sum' or reduceOp ==
'average':
318 return pipeBase.Struct(exposure=newExp)
320 def _constructPsf(self, mapperResults, exposure):
321 """Construct a CoaddPsf based on PSFs from individual subExposures 323 Currently uses (and returns) a CoaddPsf. TBD if we want to 324 create a custom subclass of CoaddPsf to differentiate it. 329 list of `pipeBase.Struct` returned by `ImageMapper.run`. 330 For this to work, each element of `mapperResults` must contain 331 a `subExposure` element, from which the component Psfs are 332 extracted (thus the reducerTask cannot have 333 `reduceOperation = 'none'`. 334 exposure : lsst.afw.image.Exposure 335 the original exposure which is used here solely for its 336 bounding-box and WCS. 340 A `measAlg.CoaddPsf` constructed from the PSFs of the individual 343 schema = afwTable.ExposureTable.makeMinimalSchema()
344 schema.addField(
"weight", type=
"D", doc=
"Coadd weight")
345 mycatalog = afwTable.ExposureCatalog(schema)
349 wcsref = exposure.getWcs()
350 for i, res
in enumerate(mapperResults):
351 record = mycatalog.getTable().makeRecord()
352 if 'subExposure' in res.getDict():
353 subExp = res.subExposure
354 if subExp.getWcs() != wcsref:
355 raise ValueError(
'Wcs of subExposure is different from exposure')
356 record.setPsf(subExp.getPsf())
357 record.setWcs(subExp.getWcs())
358 record.setBBox(subExp.getBBox())
359 elif 'psf' in res.getDict():
360 record.setPsf(res.psf)
361 record.setWcs(wcsref)
362 record.setBBox(res.bbox)
363 record[
'weight'] = 1.0
365 mycatalog.append(record)
368 psf = measAlg.CoaddPsf(mycatalog, wcsref,
'weight')
373 """Configuration parameters for the ImageMapReduceTask 375 mapper = pexConfig.ConfigurableField(
376 doc=
"Task to run on each subimage",
380 reducer = pexConfig.ConfigurableField(
381 doc=
"Task to combine results of mapper task",
389 cellCentroidsX = pexConfig.ListField(
391 doc=
"""Input X centroids around which to place subimages. 392 If None, use grid config options below.""",
397 cellCentroidsY = pexConfig.ListField(
399 doc=
"""Input Y centroids around which to place subimages. 400 If None, use grid config options below.""",
405 cellSizeX = pexConfig.Field(
407 doc=
"""Dimensions of each grid cell in x direction""",
409 check=
lambda x: x > 0.
412 cellSizeY = pexConfig.Field(
414 doc=
"""Dimensions of each grid cell in y direction""",
416 check=
lambda x: x > 0.
419 gridStepX = pexConfig.Field(
421 doc=
"""Spacing between subsequent grid cells in x direction. If equal to 422 cellSizeX, then there is no overlap in the x direction.""",
424 check=
lambda x: x > 0.
427 gridStepY = pexConfig.Field(
429 doc=
"""Spacing between subsequent grid cells in y direction. If equal to 430 cellSizeY, then there is no overlap in the y direction.""",
432 check=
lambda x: x > 0.
435 borderSizeX = pexConfig.Field(
437 doc=
"""Dimensions of grid cell border in +/- x direction, to be used 438 for generating `expandedSubExposure`.""",
440 check=
lambda x: x > 0.
443 borderSizeY = pexConfig.Field(
445 doc=
"""Dimensions of grid cell border in +/- y direction, to be used 446 for generating `expandedSubExposure`.""",
448 check=
lambda x: x > 0.
451 adjustGridOption = pexConfig.ChoiceField(
453 doc=
"""Whether and how to adjust grid to fit evenly within, and cover entire 457 "spacing":
"adjust spacing between centers of grid cells (allowing overlaps)",
458 "size":
"adjust the sizes of the grid cells (disallowing overlaps)",
459 "none":
"do not adjust the grid sizes or spacing" 463 scaleByFwhm = pexConfig.Field(
465 doc=
"""Scale cellSize/gridStep/borderSize/overlapSize by PSF FWHM rather 470 returnSubImages = pexConfig.Field(
472 doc=
"""Return the input subExposures alongside the processed ones (for debugging)""",
476 ignoreMaskPlanes = pexConfig.ListField(
478 doc=
"""Mask planes to ignore for sigma-clipped statistics""",
479 default=(
"INTRP",
"EDGE",
"DETECTED",
"SAT",
"CR",
"BAD",
"NO_DATA",
"DETECTED_NEGATIVE")
492 """Split an Exposure into subExposures (optionally on a grid) and 493 perform the same operation on each. 495 Perform 'simple' operations on a gridded set of subExposures of a 496 larger Exposure, and then (by default) have those subExposures 497 stitched back together into a new, full-sized image. 499 Contrary to the expectation given by its name, this task does not 500 perform these operations in parallel, although it could be updatd 501 to provide such functionality. 503 The actual operations are performed by two subTasks passed to the 504 config. The exposure passed to this task's `run` method will be 505 divided, and those subExposures will be passed to the subTasks, 506 along with the original exposure. The reducing operation is 507 performed by the second subtask. 509 ConfigClass = ImageMapReduceConfig
510 _DefaultName =
"ip_diffim_imageMapReduce" 513 """Create the image map-reduce task 518 arguments to be passed to 519 `lsst.pipe.base.task.Task.__init__` 521 additional keyword arguments to be passed to 522 `lsst.pipe.base.task.Task.__init__` 524 pipeBase.Task.__init__(self, *args, **kwargs)
527 self.makeSubtask(
"mapper")
528 self.makeSubtask(
"reducer")
531 def run(self, exposure, **kwargs):
532 """Perform a map-reduce operation on the given exposure. 534 Split the exposure into sub-expposures on a grid (parameters 535 given by `ImageMapReduceConfig`) and perform 536 `config.mapper.run()` on each. Reduce the resulting 537 sub-exposures by running `config.reducer.run()`. 541 exposure : lsst.afw.image.Exposure 542 the full exposure to process 544 additional keyword arguments to be passed to 545 subtask `run` methods 549 output of `reducer.run()` 552 self.log.info(
"Mapper sub-task: %s", self.mapper._DefaultName)
553 mapperResults = self.
_runMapper(exposure, **kwargs)
554 self.log.info(
"Reducer sub-task: %s", self.reducer._DefaultName)
555 result = self.
_reduceImage(mapperResults, exposure, **kwargs)
558 def _runMapper(self, exposure, doClone=False, **kwargs):
559 """Perform `mapper.run` on each sub-exposure 561 Perform `mapper.run` on each sub-exposure across a 562 grid on `exposure` generated by `_generateGrid`. Also pass to 563 `mapper.run` an 'expanded sub-exposure' containing the 564 same region as the sub-exposure but with an expanded bounding box. 568 exposure : lsst.afw.image.Exposure 569 the original exposure which is used as the template 571 if True, clone the subimages before passing to subtask; 572 in that case, the sub-exps do not have to be considered as read-only 574 additional keyword arguments to be passed to 575 `mapper.run` and `self._generateGrid`, including `forceEvenSized`. 579 a list of `pipeBase.Struct`s as returned by `mapper.run`. 584 raise ValueError(
'Bounding boxes list and expanded bounding boxes list are of different lengths')
586 self.log.info(
"Processing %d sub-exposures", len(self.
boxes0))
589 subExp = exposure.Factory(exposure, box0)
590 expandedSubExp = exposure.Factory(exposure, box1)
592 subExp = subExp.clone()
593 expandedSubExp = expandedSubExp.clone()
594 result = self.mapper.
run(subExp, expandedSubExp, exposure.getBBox(), **kwargs)
595 if self.config.returnSubImages:
596 toAdd = pipeBase.Struct(inputSubExposure=subExp,
597 inputExpandedSubExposure=expandedSubExp)
598 result.mergeItems(toAdd,
'inputSubExposure',
'inputExpandedSubExposure')
599 mapperResults.append(result)
603 def _reduceImage(self, mapperResults, exposure, **kwargs):
604 """Reduce/merge a set of sub-exposures into a final result 606 Return an exposure of the same dimensions as `exposure`. 607 `mapperResults` is expected to have been produced by `runMapper`. 612 list of `pipeBase.Struct`, each of which was produced by 614 exposure : lsst.afw.image.Exposure 615 the original exposure 617 additional keyword arguments 621 Output of `reducer.run` which is a `pipeBase.Struct`. 623 result = self.reducer.
run(mapperResults, exposure, **kwargs)
626 def _generateGrid(self, exposure, forceEvenSized=False, **kwargs):
627 """Generate two lists of bounding boxes that evenly grid `exposure` 629 Unless the config was provided with `cellCentroidsX` and 630 `cellCentroidsY`, grid (subimage) centers are spaced evenly 631 by gridStepX/Y. Then the grid is adjusted as little as 632 possible to evenly cover the input exposure (if 633 adjustGridOption is not 'none'). Then the second set of 634 bounding boxes is expanded by borderSizeX/Y. The expanded 635 bounding boxes are adjusted to ensure that they intersect the 636 exposure's bounding box. The resulting lists of bounding boxes 637 and corresponding expanded bounding boxes are set to 638 `self.boxes0`, `self.boxes1`. 642 exposure : lsst.afw.image.Exposure 643 input exposure whose full bounding box is to be evenly gridded. 644 forceEvenSized : boolean 645 force grid elements to have even-valued x- and y- dimensions? 646 (Potentially useful if doing Fourier transform of subExposures.) 650 bbox = exposure.getBBox()
653 cellCentroidsX = self.config.cellCentroidsX
654 cellCentroidsY = self.config.cellCentroidsY
655 cellSizeX = self.config.cellSizeX
656 cellSizeY = self.config.cellSizeY
657 gridStepX = self.config.gridStepX
658 gridStepY = self.config.gridStepY
659 borderSizeX = self.config.borderSizeX
660 borderSizeY = self.config.borderSizeY
661 adjustGridOption = self.config.adjustGridOption
662 scaleByFwhm = self.config.scaleByFwhm
664 if cellCentroidsX
is None or len(cellCentroidsX) <= 0:
667 psfFwhm = (exposure.getPsf().computeShape().getDeterminantRadius() *
668 2.*np.sqrt(2.*np.log(2.)))
670 self.log.info(
"Scaling grid parameters by %f" % psfFwhm)
672 def rescaleValue(val):
674 return np.rint(val*psfFwhm).astype(int)
676 return np.rint(val).astype(int)
678 cellSizeX = rescaleValue(cellSizeX)
679 cellSizeY = rescaleValue(cellSizeY)
680 gridStepX = rescaleValue(gridStepX)
681 gridStepY = rescaleValue(gridStepY)
682 borderSizeX = rescaleValue(borderSizeX)
683 borderSizeY = rescaleValue(borderSizeY)
685 nGridX = bbox.getWidth()//gridStepX
686 nGridY = bbox.getHeight()//gridStepY
688 if adjustGridOption ==
'spacing':
690 nGridX = bbox.getWidth()//cellSizeX + 1
691 nGridY = bbox.getHeight()//cellSizeY + 1
692 xLinSpace = np.linspace(cellSizeX//2, bbox.getWidth() - cellSizeX//2, nGridX)
693 yLinSpace = np.linspace(cellSizeY//2, bbox.getHeight() - cellSizeY//2, nGridY)
695 elif adjustGridOption ==
'size':
696 cellSizeX = gridStepX
697 cellSizeY = gridStepY
698 xLinSpace = np.arange(cellSizeX//2, bbox.getWidth() + cellSizeX//2, cellSizeX)
699 yLinSpace = np.arange(cellSizeY//2, bbox.getHeight() + cellSizeY//2, cellSizeY)
704 xLinSpace = np.arange(cellSizeX//2, bbox.getWidth() + cellSizeX//2, gridStepX)
705 yLinSpace = np.arange(cellSizeY//2, bbox.getHeight() + cellSizeY//2, gridStepY)
707 cellCentroids = [(x, y)
for x
in xLinSpace
for y
in yLinSpace]
711 cellCentroids = [(cellCentroidsX[i], cellCentroidsY[i])
for i
in range(len(cellCentroidsX))]
714 bbox0 = afwGeom.Box2I(afwGeom.Point2I(bbox.getBegin()), afwGeom.Extent2I(cellSizeX, cellSizeY))
716 bbox1 = afwGeom.Box2I(bbox0)
717 bbox1.grow(afwGeom.Extent2I(borderSizeX, borderSizeY))
722 def _makeBoxEvenSized(bb):
723 """Force a bounding-box to have dimensions that are modulo 2.""" 725 if bb.getWidth() % 2 == 1:
726 bb.include(afwGeom.Point2I(bb.getMaxX()+1, bb.getMaxY()))
728 if bb.getWidth() % 2 == 1:
729 bb.include(afwGeom.Point2I(bb.getMinX()-1, bb.getMaxY()))
731 if bb.getHeight() % 2 == 1:
732 bb.include(afwGeom.Point2I(bb.getMaxX(), bb.getMaxY()+1))
734 if bb.getHeight() % 2 == 1:
735 bb.include(afwGeom.Point2I(bb.getMaxX(), bb.getMinY()-1))
737 if bb.getWidth() % 2 == 1
or bb.getHeight() % 2 == 1:
738 raise RuntimeError(
'Cannot make bounding box even-sized. Probably too big.')
743 if cellCentroids
is not None and len(cellCentroids) > 0:
744 for x, y
in cellCentroids:
745 centroid = afwGeom.Point2D(x, y)
746 bb0 = afwGeom.Box2I(bbox0)
747 xoff = int(np.floor(centroid.getX())) - bb0.getWidth()//2
748 yoff = int(np.floor(centroid.getY())) - bb0.getHeight()//2
749 bb0.shift(afwGeom.Extent2I(xoff, yoff))
752 bb0 = _makeBoxEvenSized(bb0)
753 bb1 = afwGeom.Box2I(bbox1)
754 bb1.shift(afwGeom.Extent2I(xoff, yoff))
757 bb1 = _makeBoxEvenSized(bb1)
759 if bb0.getArea() > 1
and bb1.getArea() > 1:
766 """Plot both grids of boxes using matplotlib. 768 Will compute the grid via `_generateGrid` if 769 `self.boxes0` and `self.boxes1` have not already been set. 773 exposure : lsst.afw.image.Exposure 774 Exposure whose bounding box is gridded by this task. 776 Plot every skip-ped box (help make plots less confusing) 778 import matplotlib.pyplot
as plt
781 raise RuntimeError(
'Cannot plot boxes. Run _generateGrid first.')
785 plt.gca().set_prop_cycle(
None)
788 def _plotBoxGrid(self, boxes, bbox, **kwargs):
789 """Plot a grid of boxes using matplotlib. 794 a list of `afwGeom.BoundingBox`es 795 bbox : afwGeom.BoundingBox 796 an overall bounding box 798 additional keyword arguments for matplotlib 800 import matplotlib.pyplot
as plt
803 corners = np.array([np.array([pt.getX(), pt.getY()])
for pt
in box.getCorners()])
804 corners = np.vstack([corners, corners[0, :]])
805 plt.plot(corners[:, 0], corners[:, 1], **kwargs)
809 plt.xlim(bbox.getBeginX(), bbox.getEndX())
810 plt.ylim(bbox.getBeginY(), bbox.getEndY())
def _generateGrid(self, exposure, forceEvenSized=False, kwargs)
def _reduceImage(self, mapperResults, exposure, kwargs)
def __init__(self, args, kwargs)
def plotBoxes(self, fullBBox, skip=3)
def _constructPsf(self, mapperResults, exposure)
def _runMapper(self, exposure, doClone=False, kwargs)
def run(self, subExposure, expandedSubExposure, fullBBox, kwargs)
def run(self, exposure, kwargs)
def _plotBoxGrid(self, boxes, bbox, kwargs)
def run(self, mapperResults, exposure, kwargs)