1 from __future__
import absolute_import, division, print_function
2 from future
import standard_library
3 standard_library.install_aliases()
28 from future.utils
import with_metaclass
33 import lsst.meas.algorithms
as measAlg
35 import lsst.pipe.base
as pipeBase
37 __all__ = (
"ImageMapReduceTask",
"ImageMapReduceConfig",
38 "ImageMapper",
"ImageMapperConfig",
39 "ImageReducer",
"ImageReducerConfig")
42 """Tasks for processing an exposure via processing on 43 multiple sub-exposures and then collecting the results 44 to either re-stitch the sub-exposures back into a new 45 exposure, or return summary results for each sub-exposure. 47 This provides a framework for arbitrary mapper-reducer 48 operations on an exposure by implementing simple operations in 49 subTasks. It currently is not parallelized, although it could be in 50 the future. It does enable operations such as spatially-mapped 51 processing on a grid across an image, processing regions surrounding 52 centroids (such as for PSF processing), etc. 54 It is implemented as primary Task, `ImageMapReduceTask` which contains 55 two subtasks, `ImageMapper` and `ImageReducer`. 56 `ImageMapReduceTask` configures the centroids and sub-exposure 57 dimensions to be processed, and then calls the `run` methods of the 58 `ImageMapper` and `ImageReducer` on those sub-exposures. 59 `ImageMapReduceTask` may be configured with a list of sub-exposure 60 centroids (`config.cellCentroidsX` and `config.cellCentroidsY`) and a 61 single pair of bounding boxes defining their dimensions, or a set of 62 parameters defining a regular grid of centroids (`config.gridStepX` 63 and `config.gridStepY`). 65 `ImageMapper` is an abstract class and must be subclassed with 66 an implemented `run` method to provide the desired operation for 67 processing individual sub-exposures. It is called from 68 `ImageMapReduceTask.run`, and may return a new, processed sub-exposure 69 which is to be "stitched" back into a new resulting larger exposure 70 (depending on the configured `ImageMapReduceTask.mapper`); 71 otherwise if it does not return an lsst.afw.image.Exposure, then the results are 72 passed back directly to the caller. 74 `ImageReducer` will either stitch the `mapperResults` list of 75 results generated by the `ImageMapper` together into a new 76 Exposure (by default) or pass it through to the 77 caller. `ImageReducer` has an implemented `run` method for 78 basic reducing operations (`reduceOperation`) such as `average` (which 79 will average all overlapping pixels from sub-exposures produced by the 80 `ImageMapper` into the new exposure). Another notable 81 implemented `reduceOperation` is 'none', in which case the 82 `mapperResults` list is simply returned directly. 86 """Configuration parameters for ImageMapper 91 class ImageMapper(with_metaclass(abc.ABCMeta, pipeBase.Task)):
92 """Abstract base class for any task that is to be 93 used as `ImageMapReduceConfig.mapper`. 95 An `ImageMapper` is responsible for processing individual 96 sub-exposures in its `run` method, which is called from 97 `ImageMapReduceTask.run`. `run` may return a processed new 98 sub-exposure which can be be "stitched" back into a new resulting 99 larger exposure (depending on the configured 100 `ImageReducer`); otherwise if it does not return an 101 lsst.afw.image.Exposure, then the 102 `ImageReducer.config.reducer.reduceOperation` 103 should be set to 'none' and the result will be propagated 106 ConfigClass = ImageMapperConfig
107 _DefaultName =
"ip_diffim_ImageMapper" 110 def run(self, subExposure, expandedSubExposure, fullBBox, **kwargs):
111 """Perform operation on `subExposure`. 113 To be implemented by subclasses. See class docstring for more 114 details. This method is given the `subExposure` which 115 is to be operated upon, and an `expandedSubExposure` which 116 will contain `subExposure` with additional surrounding 117 pixels. This allows for, for example, convolutions (which 118 should be performed on `expandedSubExposure`), to prevent the 119 returned sub-exposure from containing invalid pixels. 121 This method may return a new, processed sub-exposure which can 122 be be "stitched" back into a new resulting larger exposure 123 (depending on the paired, configured `ImageReducer`); 124 otherwise if it does not return an lsst.afw.image.Exposure, then the 125 `ImageReducer.config.mapper.reduceOperation` 126 should be set to 'none' and the result will be propagated 131 subExposure : lsst.afw.image.Exposure 132 the sub-exposure upon which to operate 133 expandedSubExposure : lsst.afw.image.Exposure 134 the expanded sub-exposure upon which to operate 135 fullBBox : lsst.afw.geom.BoundingBox 136 the bounding box of the original exposure 138 additional keyword arguments propagated from 139 `ImageMapReduceTask.run`. 143 A `pipeBase.Struct containing the result of the `subExposure` processing, 144 which may itself be of any type. See above for details. If it is an 145 lsst.afw.image.Exposure (processed sub-exposure), then the name in the Struct 146 should be 'subExposure'. This is implemented here as a pass-through 149 return pipeBase.Struct(subExposure=subExposure)
153 """Configuration parameters for the ImageReducer 155 reduceOperation = pexConfig.ChoiceField(
157 doc=
"""Operation to use for reducing subimages into new image.""",
160 "none":
"""simply return a list of values and don't re-map results into 161 a new image (noop operation)""",
162 "copy":
"""copy pixels directly from subimage into correct location in 163 new exposure (potentially non-deterministic for overlaps)""",
164 "sum":
"""add pixels from overlaps (probably never wanted; used for testing) 165 into correct location in new exposure""",
166 "average":
"""same as copy, but also average pixels from overlapped regions 168 "coaddPsf":
"""Instead of constructing an Exposure, take a list of returned 169 PSFs and use CoaddPsf to construct a single PSF that covers the 170 entire input exposure""",
173 badMaskPlanes = pexConfig.ListField(
175 doc=
"""Mask planes to set for invalid pixels""",
176 default=(
'INVALID_MAPREDUCE',
'BAD',
'NO_DATA')
181 """Base class for any 'reduce' task that is to be 182 used as `ImageMapReduceConfig.reducer`. 184 Basic reduce operations are provided by the `run` method 185 of this class, to be selected by its config. 187 ConfigClass = ImageReducerConfig
188 _DefaultName =
"ip_diffim_ImageReducer" 190 def run(self, mapperResults, exposure, **kwargs):
191 """Reduce a list of items produced by `ImageMapper`. 193 Either stitch the passed `mapperResults` list 194 together into a new Exposure (default) or pass it through 195 (if `self.config.reduceOperation` is 'none'). 197 If `self.config.reduceOperation` is not 'none', then expect 198 that the `pipeBase.Struct`s in the `mapperResults` list 199 contain sub-exposures named 'subExposure', to be stitched back 200 into a single Exposure with the same dimensions, PSF, and mask 201 as the input `exposure`. Otherwise, the `mapperResults` list 202 is simply returned directly. 207 list of `pipeBase.Struct` returned by `ImageMapper.run`. 208 exposure : lsst.afw.image.Exposure 209 the original exposure which is cloned to use as the 210 basis for the resulting exposure (if 211 self.config.mapper.reduceOperation is not 'none') 213 additional keyword arguments propagated from 214 `ImageMapReduceTask.run`. 218 A `pipeBase.Struct` containing either an `lsst.afw.image.Exposure` (named 'exposure') 219 or a list (named 'result'), depending on `config.reduceOperation`. 223 1. This currently correctly handles overlapping sub-exposures. 224 For overlapping sub-exposures, use `config.reduceOperation='average'`. 225 2. This correctly handles varying PSFs, constructing the resulting 226 exposure's PSF via CoaddPsf (DM-9629). 230 1. To be done: correct handling of masks (nearly there) 231 2. This logic currently makes *two* copies of the original exposure 232 (one here and one in `mapper.run()`). Possibly of concern 233 for large images on memory-constrained systems. 236 if self.config.reduceOperation ==
'none':
237 return pipeBase.Struct(result=mapperResults)
239 if self.config.reduceOperation ==
'coaddPsf':
242 return pipeBase.Struct(result=coaddPsf)
244 newExp = exposure.clone()
245 newMI = newExp.getMaskedImage()
247 reduceOp = self.config.reduceOperation
248 if reduceOp ==
'copy':
250 newMI.getImage()[:, :] = np.nan
251 newMI.getVariance()[:, :] = np.nan
253 newMI.getImage()[:, :] = 0.
254 newMI.getVariance()[:, :] = 0.
255 if reduceOp ==
'average':
256 weights = afwImage.ImageI(newMI.getBBox())
258 for item
in mapperResults:
259 item = item.subExposure
260 if not (isinstance(item, afwImage.ExposureF)
or isinstance(item, afwImage.ExposureI)
or 261 isinstance(item, afwImage.ExposureU)
or isinstance(item, afwImage.ExposureD)):
262 raise TypeError(
"""Expecting an Exposure type, got %s. 263 Consider using `reduceOperation="none".""" % str(type(item)))
264 subExp = newExp.Factory(newExp, item.getBBox())
265 subMI = subExp.getMaskedImage()
266 patchMI = item.getMaskedImage()
267 isValid = ~np.isnan(patchMI.getImage().getArray() * patchMI.getVariance().getArray())
269 if reduceOp ==
'copy':
270 subMI.getImage().getArray()[isValid] = patchMI.getImage().getArray()[isValid]
271 subMI.getVariance().getArray()[isValid] = patchMI.getVariance().getArray()[isValid]
272 subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
274 if reduceOp ==
'sum' or reduceOp ==
'average':
275 subMI.getImage().getArray()[isValid] += patchMI.getImage().getArray()[isValid]
276 subMI.getVariance().getArray()[isValid] += patchMI.getVariance().getArray()[isValid]
277 subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
278 if reduceOp ==
'average':
280 wtsView = afwImage.ImageI(weights, item.getBBox())
281 wtsView.getArray()[isValid] += 1
284 mask = newMI.getMask()
285 for m
in self.config.badMaskPlanes:
287 bad = mask.getPlaneBitMask(self.config.badMaskPlanes)
289 isNan = np.where(np.isnan(newMI.getImage().getArray() * newMI.getVariance().getArray()))
290 if len(isNan[0]) > 0:
292 mask.getArray()[isNan[0], isNan[1]] |= bad
294 if reduceOp ==
'average':
295 wts = weights.getArray().astype(np.float)
296 self.log.info(
'AVERAGE: Maximum overlap: %f', np.nanmax(wts))
297 self.log.info(
'AVERAGE: Average overlap: %f', np.nanmean(wts))
298 self.log.info(
'AVERAGE: Minimum overlap: %f', np.nanmin(wts))
299 wtsZero = np.equal(wts, 0.)
300 wtsZeroInds = np.where(wtsZero)
301 wtsZeroSum = len(wtsZeroInds[0])
302 self.log.info(
'AVERAGE: Number of zero pixels: %f (%f%%)', wtsZeroSum,
303 wtsZeroSum * 100. / wtsZero.size)
304 notWtsZero = ~wtsZero
305 tmp = newMI.getImage().getArray()
306 np.divide(tmp, wts, out=tmp, where=notWtsZero)
307 tmp = newMI.getVariance().getArray()
308 np.divide(tmp, wts, out=tmp, where=notWtsZero)
309 if len(wtsZeroInds[0]) > 0:
310 newMI.getImage().getArray()[wtsZeroInds] = np.nan
311 newMI.getVariance().getArray()[wtsZeroInds] = np.nan
314 mask.getArray()[wtsZeroInds] |= bad
317 if reduceOp ==
'sum' or reduceOp ==
'average':
321 return pipeBase.Struct(exposure=newExp)
323 def _constructPsf(self, mapperResults, exposure):
324 """Construct a CoaddPsf based on PSFs from individual subExposures 326 Currently uses (and returns) a CoaddPsf. TBD if we want to 327 create a custom subclass of CoaddPsf to differentiate it. 332 list of `pipeBase.Struct` returned by `ImageMapper.run`. 333 For this to work, each element of `mapperResults` must contain 334 a `subExposure` element, from which the component Psfs are 335 extracted (thus the reducerTask cannot have 336 `reduceOperation = 'none'`. 337 exposure : lsst.afw.image.Exposure 338 the original exposure which is used here solely for its 339 bounding-box and WCS. 343 A `measAlg.CoaddPsf` constructed from the PSFs of the individual 346 schema = afwTable.ExposureTable.makeMinimalSchema()
347 schema.addField(
"weight", type=
"D", doc=
"Coadd weight")
348 mycatalog = afwTable.ExposureCatalog(schema)
352 wcsref = exposure.getWcs()
353 for i, res
in enumerate(mapperResults):
354 record = mycatalog.getTable().makeRecord()
355 if 'subExposure' in res.getDict():
356 subExp = res.subExposure
357 if subExp.getWcs() != wcsref:
358 raise ValueError(
'Wcs of subExposure is different from exposure')
359 record.setPsf(subExp.getPsf())
360 record.setWcs(subExp.getWcs())
361 record.setBBox(subExp.getBBox())
362 elif 'psf' in res.getDict():
363 record.setPsf(res.psf)
364 record.setWcs(wcsref)
365 record.setBBox(res.bbox)
366 record[
'weight'] = 1.0
368 mycatalog.append(record)
371 psf = measAlg.CoaddPsf(mycatalog, wcsref,
'weight')
376 """Configuration parameters for the ImageMapReduceTask 378 mapper = pexConfig.ConfigurableField(
379 doc=
"Task to run on each subimage",
383 reducer = pexConfig.ConfigurableField(
384 doc=
"Task to combine results of mapper task",
392 cellCentroidsX = pexConfig.ListField(
394 doc=
"""Input X centroids around which to place subimages. 395 If None, use grid config options below.""",
400 cellCentroidsY = pexConfig.ListField(
402 doc=
"""Input Y centroids around which to place subimages. 403 If None, use grid config options below.""",
408 cellSizeX = pexConfig.Field(
410 doc=
"""Dimensions of each grid cell in x direction""",
412 check=
lambda x: x > 0.
415 cellSizeY = pexConfig.Field(
417 doc=
"""Dimensions of each grid cell in y direction""",
419 check=
lambda x: x > 0.
422 gridStepX = pexConfig.Field(
424 doc=
"""Spacing between subsequent grid cells in x direction. If equal to 425 cellSizeX, then there is no overlap in the x direction.""",
427 check=
lambda x: x > 0.
430 gridStepY = pexConfig.Field(
432 doc=
"""Spacing between subsequent grid cells in y direction. If equal to 433 cellSizeY, then there is no overlap in the y direction.""",
435 check=
lambda x: x > 0.
438 borderSizeX = pexConfig.Field(
440 doc=
"""Dimensions of grid cell border in +/- x direction, to be used 441 for generating `expandedSubExposure`.""",
443 check=
lambda x: x > 0.
446 borderSizeY = pexConfig.Field(
448 doc=
"""Dimensions of grid cell border in +/- y direction, to be used 449 for generating `expandedSubExposure`.""",
451 check=
lambda x: x > 0.
454 adjustGridOption = pexConfig.ChoiceField(
456 doc=
"""Whether and how to adjust grid to fit evenly within, and cover entire 460 "spacing":
"adjust spacing between centers of grid cells (allowing overlaps)",
461 "size":
"adjust the sizes of the grid cells (disallowing overlaps)",
462 "none":
"do not adjust the grid sizes or spacing" 466 scaleByFwhm = pexConfig.Field(
468 doc=
"""Scale cellSize/gridStep/borderSize/overlapSize by PSF FWHM rather 473 returnSubImages = pexConfig.Field(
475 doc=
"""Return the input subExposures alongside the processed ones (for debugging)""",
479 ignoreMaskPlanes = pexConfig.ListField(
481 doc=
"""Mask planes to ignore for sigma-clipped statistics""",
482 default=(
"INTRP",
"EDGE",
"DETECTED",
"SAT",
"CR",
"BAD",
"NO_DATA",
"DETECTED_NEGATIVE")
495 """Split an Exposure into subExposures (optionally on a grid) and 496 perform the same operation on each. 498 Perform 'simple' operations on a gridded set of subExposures of a 499 larger Exposure, and then (by default) have those subExposures 500 stitched back together into a new, full-sized image. 502 Contrary to the expectation given by its name, this task does not 503 perform these operations in parallel, although it could be updatd 504 to provide such functionality. 506 The actual operations are performed by two subTasks passed to the 507 config. The exposure passed to this task's `run` method will be 508 divided, and those subExposures will be passed to the subTasks, 509 along with the original exposure. The reducing operation is 510 performed by the second subtask. 512 ConfigClass = ImageMapReduceConfig
513 _DefaultName =
"ip_diffim_imageMapReduce" 516 """Create the image map-reduce task 521 arguments to be passed to 522 `lsst.pipe.base.task.Task.__init__` 524 additional keyword arguments to be passed to 525 `lsst.pipe.base.task.Task.__init__` 527 pipeBase.Task.__init__(self, *args, **kwargs)
530 self.makeSubtask(
"mapper")
531 self.makeSubtask(
"reducer")
534 def run(self, exposure, **kwargs):
535 """Perform a map-reduce operation on the given exposure. 537 Split the exposure into sub-expposures on a grid (parameters 538 given by `ImageMapReduceConfig`) and perform 539 `config.mapper.run()` on each. Reduce the resulting 540 sub-exposures by running `config.reducer.run()`. 544 exposure : lsst.afw.image.Exposure 545 the full exposure to process 547 additional keyword arguments to be passed to 548 subtask `run` methods 552 output of `reducer.run()` 555 self.log.info(
"Mapper sub-task: %s", self.mapper._DefaultName)
556 mapperResults = self.
_runMapper(exposure, **kwargs)
557 self.log.info(
"Reducer sub-task: %s", self.reducer._DefaultName)
558 result = self.
_reduceImage(mapperResults, exposure, **kwargs)
561 def _runMapper(self, exposure, doClone=False, **kwargs):
562 """Perform `mapper.run` on each sub-exposure 564 Perform `mapper.run` on each sub-exposure across a 565 grid on `exposure` generated by `_generateGrid`. Also pass to 566 `mapper.run` an 'expanded sub-exposure' containing the 567 same region as the sub-exposure but with an expanded bounding box. 571 exposure : lsst.afw.image.Exposure 572 the original exposure which is used as the template 574 if True, clone the subimages before passing to subtask; 575 in that case, the sub-exps do not have to be considered as read-only 577 additional keyword arguments to be passed to 578 `mapper.run` and `self._generateGrid`, including `forceEvenSized`. 582 a list of `pipeBase.Struct`s as returned by `mapper.run`. 587 raise ValueError(
'Bounding boxes list and expanded bounding boxes list are of different lengths')
589 self.log.info(
"Processing %d sub-exposures", len(self.
boxes0))
592 subExp = exposure.Factory(exposure, box0)
593 expandedSubExp = exposure.Factory(exposure, box1)
595 subExp = subExp.clone()
596 expandedSubExp = expandedSubExp.clone()
597 result = self.mapper.
run(subExp, expandedSubExp, exposure.getBBox(), **kwargs)
598 if self.config.returnSubImages:
599 toAdd = pipeBase.Struct(inputSubExposure=subExp,
600 inputExpandedSubExposure=expandedSubExp)
601 result.mergeItems(toAdd,
'inputSubExposure',
'inputExpandedSubExposure')
602 mapperResults.append(result)
606 def _reduceImage(self, mapperResults, exposure, **kwargs):
607 """Reduce/merge a set of sub-exposures into a final result 609 Return an exposure of the same dimensions as `exposure`. 610 `mapperResults` is expected to have been produced by `runMapper`. 615 list of `pipeBase.Struct`, each of which was produced by 617 exposure : lsst.afw.image.Exposure 618 the original exposure 620 additional keyword arguments 624 Output of `reducer.run` which is a `pipeBase.Struct`. 626 result = self.reducer.
run(mapperResults, exposure, **kwargs)
629 def _generateGrid(self, exposure, forceEvenSized=False, **kwargs):
630 """Generate two lists of bounding boxes that evenly grid `exposure` 632 Unless the config was provided with `cellCentroidsX` and 633 `cellCentroidsY`, grid (subimage) centers are spaced evenly 634 by gridStepX/Y. Then the grid is adjusted as little as 635 possible to evenly cover the input exposure (if 636 adjustGridOption is not 'none'). Then the second set of 637 bounding boxes is expanded by borderSizeX/Y. The expanded 638 bounding boxes are adjusted to ensure that they intersect the 639 exposure's bounding box. The resulting lists of bounding boxes 640 and corresponding expanded bounding boxes are set to 641 `self.boxes0`, `self.boxes1`. 645 exposure : lsst.afw.image.Exposure 646 input exposure whose full bounding box is to be evenly gridded. 647 forceEvenSized : boolean 648 force grid elements to have even-valued x- and y- dimensions? 649 (Potentially useful if doing Fourier transform of subExposures.) 653 bbox = exposure.getBBox()
656 cellCentroidsX = self.config.cellCentroidsX
657 cellCentroidsY = self.config.cellCentroidsY
658 cellSizeX = self.config.cellSizeX
659 cellSizeY = self.config.cellSizeY
660 gridStepX = self.config.gridStepX
661 gridStepY = self.config.gridStepY
662 borderSizeX = self.config.borderSizeX
663 borderSizeY = self.config.borderSizeY
664 adjustGridOption = self.config.adjustGridOption
665 scaleByFwhm = self.config.scaleByFwhm
667 if cellCentroidsX
is None or len(cellCentroidsX) <= 0:
670 psfFwhm = (exposure.getPsf().computeShape().getDeterminantRadius() *
671 2.*np.sqrt(2.*np.log(2.)))
673 self.log.info(
"Scaling grid parameters by %f" % psfFwhm)
675 def rescaleValue(val):
677 return np.rint(val*psfFwhm).astype(int)
679 return np.rint(val).astype(int)
681 cellSizeX = rescaleValue(cellSizeX)
682 cellSizeY = rescaleValue(cellSizeY)
683 gridStepX = rescaleValue(gridStepX)
684 gridStepY = rescaleValue(gridStepY)
685 borderSizeX = rescaleValue(borderSizeX)
686 borderSizeY = rescaleValue(borderSizeY)
688 nGridX = bbox.getWidth()//gridStepX
689 nGridY = bbox.getHeight()//gridStepY
691 if adjustGridOption ==
'spacing':
693 nGridX = bbox.getWidth()//cellSizeX + 1
694 nGridY = bbox.getHeight()//cellSizeY + 1
695 xLinSpace = np.linspace(cellSizeX//2, bbox.getWidth() - cellSizeX//2, nGridX)
696 yLinSpace = np.linspace(cellSizeY//2, bbox.getHeight() - cellSizeY//2, nGridY)
698 elif adjustGridOption ==
'size':
699 cellSizeX = gridStepX
700 cellSizeY = gridStepY
701 xLinSpace = np.arange(cellSizeX//2, bbox.getWidth() + cellSizeX//2, cellSizeX)
702 yLinSpace = np.arange(cellSizeY//2, bbox.getHeight() + cellSizeY//2, cellSizeY)
707 xLinSpace = np.arange(cellSizeX//2, bbox.getWidth() + cellSizeX//2, gridStepX)
708 yLinSpace = np.arange(cellSizeY//2, bbox.getHeight() + cellSizeY//2, gridStepY)
710 cellCentroids = [(x, y)
for x
in xLinSpace
for y
in yLinSpace]
714 cellCentroids = [(cellCentroidsX[i], cellCentroidsY[i])
for i
in range(len(cellCentroidsX))]
725 def _makeBoxEvenSized(bb):
726 """Force a bounding-box to have dimensions that are modulo 2.""" 728 if bb.getWidth() % 2 == 1:
731 if bb.getWidth() % 2 == 1:
734 if bb.getHeight() % 2 == 1:
737 if bb.getHeight() % 2 == 1:
740 if bb.getWidth() % 2 == 1
or bb.getHeight() % 2 == 1:
741 raise RuntimeError(
'Cannot make bounding box even-sized. Probably too big.')
746 if cellCentroids
is not None and len(cellCentroids) > 0:
747 for x, y
in cellCentroids:
750 xoff = int(np.floor(centroid.getX())) - bb0.getWidth()//2
751 yoff = int(np.floor(centroid.getY())) - bb0.getHeight()//2
755 bb0 = _makeBoxEvenSized(bb0)
760 bb1 = _makeBoxEvenSized(bb1)
762 if bb0.getArea() > 1
and bb1.getArea() > 1:
769 """Plot both grids of boxes using matplotlib. 771 Will compute the grid via `_generateGrid` if 772 `self.boxes0` and `self.boxes1` have not already been set. 776 exposure : lsst.afw.image.Exposure 777 Exposure whose bounding box is gridded by this task. 779 Plot every skip-ped box (help make plots less confusing) 781 import matplotlib.pyplot
as plt
784 raise RuntimeError(
'Cannot plot boxes. Run _generateGrid first.')
788 plt.gca().set_prop_cycle(
None)
791 def _plotBoxGrid(self, boxes, bbox, **kwargs):
792 """Plot a grid of boxes using matplotlib. 797 a list of `afwGeom.BoundingBox`es 798 bbox : afwGeom.BoundingBox 799 an overall bounding box 801 additional keyword arguments for matplotlib 803 import matplotlib.pyplot
as plt
806 corners = np.array([np.array([pt.getX(), pt.getY()])
for pt
in box.getCorners()])
807 corners = np.vstack([corners, corners[0, :]])
808 plt.plot(corners[:, 0], corners[:, 1], **kwargs)
812 plt.xlim(bbox.getBeginX(), bbox.getEndX())
813 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)