32 from lsst.utils.timer
import timeMethod
34 __all__ = (
"ImageMapReduceTask",
"ImageMapReduceConfig",
35 "ImageMapper",
"ImageMapperConfig",
36 "ImageReducer",
"ImageReducerConfig")
39 """Tasks for processing an exposure via processing on
40 multiple sub-exposures and then collecting the results
41 to either re-stitch the sub-exposures back into a new
42 exposure, or return summary results for each sub-exposure.
44 This provides a framework for arbitrary mapper-reducer
45 operations on an exposure by implementing simple operations in
46 subTasks. It currently is not parallelized, although it could be in
47 the future. It does enable operations such as spatially-mapped
48 processing on a grid across an image, processing regions surrounding
49 centroids (such as for PSF processing), etc.
51 It is implemented as primary Task, `ImageMapReduceTask` which contains
52 two subtasks, `ImageMapper` and `ImageReducer`.
53 `ImageMapReduceTask` configures the centroids and sub-exposure
54 dimensions to be processed, and then calls the `run` methods of the
55 `ImageMapper` and `ImageReducer` on those sub-exposures.
56 `ImageMapReduceTask` may be configured with a list of sub-exposure
57 centroids (`config.cellCentroidsX` and `config.cellCentroidsY`) and a
58 single pair of bounding boxes defining their dimensions, or a set of
59 parameters defining a regular grid of centroids (`config.gridStepX`
60 and `config.gridStepY`).
62 `ImageMapper` is an abstract class and must be subclassed with
63 an implemented `run` method to provide the desired operation for
64 processing individual sub-exposures. It is called from
65 `ImageMapReduceTask.run`, and may return a new, processed sub-exposure
66 which is to be "stitched" back into a new resulting larger exposure
67 (depending on the configured `ImageMapReduceTask.mapper`);
68 otherwise if it does not return an lsst.afw.image.Exposure, then the results are
69 passed back directly to the caller.
71 `ImageReducer` will either stitch the `mapperResults` list of
72 results generated by the `ImageMapper` together into a new
73 Exposure (by default) or pass it through to the
74 caller. `ImageReducer` has an implemented `run` method for
75 basic reducing operations (`reduceOperation`) such as `average` (which
76 will average all overlapping pixels from sub-exposures produced by the
77 `ImageMapper` into the new exposure). Another notable
78 implemented `reduceOperation` is 'none', in which case the
79 `mapperResults` list is simply returned directly.
84 """Configuration parameters for ImageMapper
90 """Abstract base class for any task that is to be
91 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.geom.Box2I`
136 the bounding box of the original exposure
138 additional keyword arguments propagated from
139 `ImageMapReduceTask.run`.
143 result : `lsst.pipe.base.Struct`
144 A structure containing the result of the `subExposure` processing,
145 which may itself be of any type. See above for details. If it is an
146 `lsst.afw.image.Exposure` (processed sub-exposure), then the name in
147 the Struct should be 'subExposure'. This is implemented here as a
148 pass-through example only.
150 return pipeBase.Struct(subExposure=subExposure)
154 """Configuration parameters for the ImageReducer
156 reduceOperation = pexConfig.ChoiceField(
158 doc=
"""Operation to use for reducing subimages into new image.""",
161 "none":
"""simply return a list of values and don't re-map results into
162 a new image (noop operation)""",
163 "copy":
"""copy pixels directly from subimage into correct location in
164 new exposure (potentially non-deterministic for overlaps)""",
165 "sum":
"""add pixels from overlaps (probably never wanted; used for testing)
166 into correct location in new exposure""",
167 "average":
"""same as copy, but also average pixels from overlapped regions
169 "coaddPsf":
"""Instead of constructing an Exposure, take a list of returned
170 PSFs and use CoaddPsf to construct a single PSF that covers the
171 entire input exposure""",
174 badMaskPlanes = pexConfig.ListField(
176 doc=
"""Mask planes to set for invalid pixels""",
177 default=(
'INVALID_MAPREDUCE',
'BAD',
'NO_DATA')
182 """Base class for any 'reduce' task that is to be
183 used as `ImageMapReduceConfig.reducer`.
185 Basic reduce operations are provided by the `run` method
186 of this class, to be selected by its config.
188 ConfigClass = ImageReducerConfig
189 _DefaultName =
"ip_diffim_ImageReducer"
191 def run(self, mapperResults, exposure, **kwargs):
192 """Reduce a list of items produced by `ImageMapper`.
194 Either stitch the passed `mapperResults` list
195 together into a new Exposure (default) or pass it through
196 (if `self.config.reduceOperation` is 'none').
198 If `self.config.reduceOperation` is not 'none', then expect
199 that the `pipeBase.Struct`s in the `mapperResults` list
200 contain sub-exposures named 'subExposure', to be stitched back
201 into a single Exposure with the same dimensions, PSF, and mask
202 as the input `exposure`. Otherwise, the `mapperResults` list
203 is simply returned directly.
207 mapperResults : `list`
208 list of `lsst.pipe.base.Struct` returned by `ImageMapper.run`.
209 exposure : `lsst.afw.image.Exposure`
210 the original exposure which is cloned to use as the
211 basis for the resulting exposure (if
212 ``self.config.mapper.reduceOperation`` is not 'None')
214 additional keyword arguments propagated from
215 `ImageMapReduceTask.run`.
219 A `lsst.pipe.base.Struct` containing either an `lsst.afw.image.Exposure`
220 (named 'exposure') or a list (named 'result'),
221 depending on `config.reduceOperation`.
225 1. This currently correctly handles overlapping sub-exposures.
226 For overlapping sub-exposures, use `config.reduceOperation='average'`.
227 2. This correctly handles varying PSFs, constructing the resulting
228 exposure's PSF via CoaddPsf (DM-9629).
232 1. To be done: correct handling of masks (nearly there)
233 2. This logic currently makes *two* copies of the original exposure
234 (one here and one in `mapper.run()`). Possibly of concern
235 for large images on memory-constrained systems.
238 if self.config.reduceOperation ==
'none':
239 return pipeBase.Struct(result=mapperResults)
241 if self.config.reduceOperation ==
'coaddPsf':
243 coaddPsf = self.
_constructPsf_constructPsf(mapperResults, exposure)
244 return pipeBase.Struct(result=coaddPsf)
246 newExp = exposure.clone()
247 newMI = newExp.getMaskedImage()
249 reduceOp = self.config.reduceOperation
250 if reduceOp ==
'copy':
252 newMI.getImage()[:, :] = np.nan
253 newMI.getVariance()[:, :] = np.nan
255 newMI.getImage()[:, :] = 0.
256 newMI.getVariance()[:, :] = 0.
257 if reduceOp ==
'average':
258 weights = afwImage.ImageI(newMI.getBBox())
260 for item
in mapperResults:
261 item = item.subExposure
262 if not (isinstance(item, afwImage.ExposureF)
or isinstance(item, afwImage.ExposureI)
263 or isinstance(item, afwImage.ExposureU)
or isinstance(item, afwImage.ExposureD)):
264 raise TypeError(
"""Expecting an Exposure type, got %s.
265 Consider using `reduceOperation="none".""" % str(
type(item)))
266 subExp = newExp.Factory(newExp, item.getBBox())
267 subMI = subExp.getMaskedImage()
268 patchMI = item.getMaskedImage()
269 isValid = ~np.isnan(patchMI.getImage().getArray() * patchMI.getVariance().getArray())
271 if reduceOp ==
'copy':
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()
276 if reduceOp ==
'sum' or reduceOp ==
'average':
277 subMI.getImage().getArray()[isValid] += patchMI.getImage().getArray()[isValid]
278 subMI.getVariance().getArray()[isValid] += patchMI.getVariance().getArray()[isValid]
279 subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
280 if reduceOp ==
'average':
282 wtsView = afwImage.ImageI(weights, item.getBBox())
283 wtsView.getArray()[isValid] += 1
286 mask = newMI.getMask()
287 for m
in self.config.badMaskPlanes:
289 bad = mask.getPlaneBitMask(self.config.badMaskPlanes)
291 isNan = np.where(np.isnan(newMI.getImage().getArray() * newMI.getVariance().getArray()))
292 if len(isNan[0]) > 0:
294 mask.getArray()[isNan[0], isNan[1]] |= bad
296 if reduceOp ==
'average':
297 wts = weights.getArray().astype(np.float)
298 self.log.
info(
'AVERAGE: Maximum overlap: %f', np.nanmax(wts))
299 self.log.
info(
'AVERAGE: Average overlap: %f', np.nanmean(wts))
300 self.log.
info(
'AVERAGE: Minimum overlap: %f', np.nanmin(wts))
301 wtsZero = np.equal(wts, 0.)
302 wtsZeroInds = np.where(wtsZero)
303 wtsZeroSum = len(wtsZeroInds[0])
304 self.log.
info(
'AVERAGE: Number of zero pixels: %f (%f%%)', wtsZeroSum,
305 wtsZeroSum * 100. / wtsZero.size)
306 notWtsZero = ~wtsZero
307 tmp = newMI.getImage().getArray()
308 np.divide(tmp, wts, out=tmp, where=notWtsZero)
309 tmp = newMI.getVariance().getArray()
310 np.divide(tmp, wts, out=tmp, where=notWtsZero)
311 if len(wtsZeroInds[0]) > 0:
312 newMI.getImage().getArray()[wtsZeroInds] = np.nan
313 newMI.getVariance().getArray()[wtsZeroInds] = np.nan
316 mask.getArray()[wtsZeroInds] |= bad
319 if reduceOp ==
'sum' or reduceOp ==
'average':
320 psf = self.
_constructPsf_constructPsf(mapperResults, exposure)
323 return pipeBase.Struct(exposure=newExp)
325 def _constructPsf(self, mapperResults, exposure):
326 """Construct a CoaddPsf based on PSFs from individual subExposures
328 Currently uses (and returns) a CoaddPsf. TBD if we want to
329 create a custom subclass of CoaddPsf to differentiate it.
333 mapperResults : `list`
334 list of `pipeBase.Struct` returned by `ImageMapper.run`.
335 For this to work, each element of `mapperResults` must contain
336 a `subExposure` element, from which the component Psfs are
337 extracted (thus the reducerTask cannot have
338 `reduceOperation = 'none'`.
339 exposure : `lsst.afw.image.Exposure`
340 the original exposure which is used here solely for its
341 bounding-box and WCS.
345 psf : `lsst.meas.algorithms.CoaddPsf`
346 A psf constructed from the PSFs of the individual subExposures.
348 schema = afwTable.ExposureTable.makeMinimalSchema()
349 schema.addField(
"weight", type=
"D", doc=
"Coadd weight")
354 wcsref = exposure.getWcs()
355 for i, res
in enumerate(mapperResults):
356 record = mycatalog.getTable().makeRecord()
357 if 'subExposure' in res.getDict():
358 subExp = res.subExposure
359 if subExp.getWcs() != wcsref:
360 raise ValueError(
'Wcs of subExposure is different from exposure')
361 record.setPsf(subExp.getPsf())
362 record.setWcs(subExp.getWcs())
363 record.setBBox(subExp.getBBox())
364 elif 'psf' in res.getDict():
365 record.setPsf(res.psf)
366 record.setWcs(wcsref)
367 record.setBBox(res.bbox)
368 record[
'weight'] = 1.0
370 mycatalog.append(record)
373 psf = measAlg.CoaddPsf(mycatalog, wcsref,
'weight')
378 """Configuration parameters for the ImageMapReduceTask
380 mapper = pexConfig.ConfigurableField(
381 doc=
"Task to run on each subimage",
385 reducer = pexConfig.ConfigurableField(
386 doc=
"Task to combine results of mapper task",
394 cellCentroidsX = pexConfig.ListField(
396 doc=
"""Input X centroids around which to place subimages.
397 If None, use grid config options below.""",
402 cellCentroidsY = pexConfig.ListField(
404 doc=
"""Input Y centroids around which to place subimages.
405 If None, use grid config options below.""",
410 cellSizeX = pexConfig.Field(
412 doc=
"""Dimensions of each grid cell in x direction""",
414 check=
lambda x: x > 0.
417 cellSizeY = pexConfig.Field(
419 doc=
"""Dimensions of each grid cell in y direction""",
421 check=
lambda x: x > 0.
424 gridStepX = pexConfig.Field(
426 doc=
"""Spacing between subsequent grid cells in x direction. If equal to
427 cellSizeX, then there is no overlap in the x direction.""",
429 check=
lambda x: x > 0.
432 gridStepY = pexConfig.Field(
434 doc=
"""Spacing between subsequent grid cells in y direction. If equal to
435 cellSizeY, then there is no overlap in the y direction.""",
437 check=
lambda x: x > 0.
440 borderSizeX = pexConfig.Field(
442 doc=
"""Dimensions of grid cell border in +/- x direction, to be used
443 for generating `expandedSubExposure`.""",
445 check=
lambda x: x > 0.
448 borderSizeY = pexConfig.Field(
450 doc=
"""Dimensions of grid cell border in +/- y direction, to be used
451 for generating `expandedSubExposure`.""",
453 check=
lambda x: x > 0.
456 adjustGridOption = pexConfig.ChoiceField(
458 doc=
"""Whether and how to adjust grid to fit evenly within, and cover entire
462 "spacing":
"adjust spacing between centers of grid cells (allowing overlaps)",
463 "size":
"adjust the sizes of the grid cells (disallowing overlaps)",
464 "none":
"do not adjust the grid sizes or spacing"
468 scaleByFwhm = pexConfig.Field(
470 doc=
"""Scale cellSize/gridStep/borderSize/overlapSize by PSF FWHM rather
475 returnSubImages = pexConfig.Field(
477 doc=
"""Return the input subExposures alongside the processed ones (for debugging)""",
481 ignoreMaskPlanes = pexConfig.ListField(
483 doc=
"""Mask planes to ignore for sigma-clipped statistics""",
484 default=(
"INTRP",
"EDGE",
"DETECTED",
"SAT",
"CR",
"BAD",
"NO_DATA",
"DETECTED_NEGATIVE")
489 """Split an Exposure into subExposures (optionally on a grid) and
490 perform the same operation on each.
492 Perform 'simple' operations on a gridded set of subExposures of a
493 larger Exposure, and then (by default) have those subExposures
494 stitched back together into a new, full-sized image.
496 Contrary to the expectation given by its name, this task does not
497 perform these operations in parallel, although it could be updatd
498 to provide such functionality.
500 The actual operations are performed by two subTasks passed to the
501 config. The exposure passed to this task's `run` method will be
502 divided, and those subExposures will be passed to the subTasks,
503 along with the original exposure. The reducing operation is
504 performed by the second subtask.
506 ConfigClass = ImageMapReduceConfig
507 _DefaultName =
"ip_diffim_imageMapReduce"
510 """Create the image map-reduce task
515 arguments to be passed to
516 `lsst.pipe.base.task.Task.__init__`
518 additional keyword arguments to be passed to
519 `lsst.pipe.base.task.Task.__init__`
521 pipeBase.Task.__init__(self, *args, **kwargs)
524 self.makeSubtask(
"mapper")
525 self.makeSubtask(
"reducer")
528 def run(self, exposure, **kwargs):
529 """Perform a map-reduce operation on the given exposure.
531 Split the exposure into sub-expposures on a grid (parameters
532 given by `ImageMapReduceConfig`) and perform
533 `config.mapper.run()` on each. Reduce the resulting
534 sub-exposures by running `config.reducer.run()`.
538 exposure : `lsst.afw.image.Exposure`
539 the full exposure to process
541 additional keyword arguments to be passed to
542 subtask `run` methods
546 output of `reducer.run()`
549 self.log.
info(
"Mapper sub-task: %s", self.mapper._DefaultName)
550 mapperResults = self.
_runMapper_runMapper(exposure, **kwargs)
551 self.log.
info(
"Reducer sub-task: %s", self.reducer._DefaultName)
552 result = self.
_reduceImage_reduceImage(mapperResults, exposure, **kwargs)
555 def _runMapper(self, exposure, doClone=False, **kwargs):
556 """Perform `mapper.run` on each sub-exposure
558 Perform `mapper.run` on each sub-exposure across a
559 grid on `exposure` generated by `_generateGrid`. Also pass to
560 `mapper.run` an 'expanded sub-exposure' containing the
561 same region as the sub-exposure but with an expanded bounding box.
565 exposure : `lsst.afw.image.Exposure`
566 the original exposure which is used as the template
568 if True, clone the subimages before passing to subtask;
569 in that case, the sub-exps do not have to be considered as read-only
571 additional keyword arguments to be passed to
572 `mapper.run` and `self._generateGrid`, including `forceEvenSized`.
576 a list of `pipeBase.Struct`s as returned by `mapper.run`.
578 if self.
boxes0boxes0
is None:
581 raise ValueError(
'Bounding boxes list and expanded bounding boxes list are of different lengths')
583 self.log.
info(
"Processing %d sub-exposures", len(self.
boxes0boxes0))
585 for box0, box1
in zip(self.
boxes0boxes0, self.
boxes1boxes1):
586 subExp = exposure.Factory(exposure, box0)
587 expandedSubExp = exposure.Factory(exposure, box1)
589 subExp = subExp.clone()
590 expandedSubExp = expandedSubExp.clone()
591 result = self.mapper.
run(subExp, expandedSubExp, exposure.getBBox(), **kwargs)
592 if self.config.returnSubImages:
593 toAdd = pipeBase.Struct(inputSubExposure=subExp,
594 inputExpandedSubExposure=expandedSubExp)
595 result.mergeItems(toAdd,
'inputSubExposure',
'inputExpandedSubExposure')
596 mapperResults.append(result)
600 def _reduceImage(self, mapperResults, exposure, **kwargs):
601 """Reduce/merge a set of sub-exposures into a final result
603 Return an exposure of the same dimensions as `exposure`.
604 `mapperResults` is expected to have been produced by `runMapper`.
608 mapperResults : `list`
609 `list` of `lsst.pipe.base.Struct`, each of which was produced by
611 exposure : `lsst.afw.image.Exposure`
612 the original exposure
614 additional keyword arguments
618 Output of `reducer.run` which is a `pipeBase.Struct`.
620 result = self.reducer.
run(mapperResults, exposure, **kwargs)
623 def _generateGrid(self, exposure, forceEvenSized=False, **kwargs):
624 """Generate two lists of bounding boxes that evenly grid `exposure`
626 Unless the config was provided with `cellCentroidsX` and
627 `cellCentroidsY`, grid (subimage) centers are spaced evenly
628 by gridStepX/Y. Then the grid is adjusted as little as
629 possible to evenly cover the input exposure (if
630 adjustGridOption is not 'none'). Then the second set of
631 bounding boxes is expanded by borderSizeX/Y. The expanded
632 bounding boxes are adjusted to ensure that they intersect the
633 exposure's bounding box. The resulting lists of bounding boxes
634 and corresponding expanded bounding boxes are set to
635 `self.boxes0`, `self.boxes1`.
639 exposure : `lsst.afw.image.Exposure`
640 input exposure whose full bounding box is to be evenly gridded.
641 forceEvenSized : `bool`
642 force grid elements to have even-valued x- and y- dimensions?
643 (Potentially useful if doing Fourier transform of subExposures.)
647 bbox = exposure.getBBox()
650 cellCentroidsX = self.config.cellCentroidsX
651 cellCentroidsY = self.config.cellCentroidsY
652 cellSizeX = self.config.cellSizeX
653 cellSizeY = self.config.cellSizeY
654 gridStepX = self.config.gridStepX
655 gridStepY = self.config.gridStepY
656 borderSizeX = self.config.borderSizeX
657 borderSizeY = self.config.borderSizeY
658 adjustGridOption = self.config.adjustGridOption
659 scaleByFwhm = self.config.scaleByFwhm
661 if cellCentroidsX
is None or len(cellCentroidsX) <= 0:
664 psfFwhm = (exposure.getPsf().computeShape().getDeterminantRadius()
665 * 2.*np.sqrt(2.*np.log(2.)))
667 self.log.
info(
"Scaling grid parameters by %f", psfFwhm)
669 def rescaleValue(val):
671 return np.rint(val*psfFwhm).astype(int)
673 return np.rint(val).astype(int)
675 cellSizeX = rescaleValue(cellSizeX)
676 cellSizeY = rescaleValue(cellSizeY)
677 gridStepX = rescaleValue(gridStepX)
678 gridStepY = rescaleValue(gridStepY)
679 borderSizeX = rescaleValue(borderSizeX)
680 borderSizeY = rescaleValue(borderSizeY)
682 nGridX = bbox.getWidth()//gridStepX
683 nGridY = bbox.getHeight()//gridStepY
685 if adjustGridOption ==
'spacing':
687 nGridX = bbox.getWidth()//cellSizeX + 1
688 nGridY = bbox.getHeight()//cellSizeY + 1
689 xLinSpace = np.linspace(cellSizeX//2, bbox.getWidth() - cellSizeX//2, nGridX)
690 yLinSpace = np.linspace(cellSizeY//2, bbox.getHeight() - cellSizeY//2, nGridY)
692 elif adjustGridOption ==
'size':
693 cellSizeX = gridStepX
694 cellSizeY = gridStepY
695 xLinSpace = np.arange(cellSizeX//2, bbox.getWidth() + cellSizeX//2, cellSizeX)
696 yLinSpace = np.arange(cellSizeY//2, bbox.getHeight() + cellSizeY//2, cellSizeY)
701 xLinSpace = np.arange(cellSizeX//2, bbox.getWidth() + cellSizeX//2, gridStepX)
702 yLinSpace = np.arange(cellSizeY//2, bbox.getHeight() + cellSizeY//2, gridStepY)
704 cellCentroids = [(x, y)
for x
in xLinSpace
for y
in yLinSpace]
708 cellCentroids = [(cellCentroidsX[i], cellCentroidsY[i])
for i
in range(len(cellCentroidsX))]
719 def _makeBoxEvenSized(bb):
720 """Force a bounding-box to have dimensions that are modulo 2."""
722 if bb.getWidth() % 2 == 1:
725 if bb.getWidth() % 2 == 1:
728 if bb.getHeight() % 2 == 1:
731 if bb.getHeight() % 2 == 1:
734 if bb.getWidth() % 2 == 1
or bb.getHeight() % 2 == 1:
735 raise RuntimeError(
'Cannot make bounding box even-sized. Probably too big.')
740 if cellCentroids
is not None and len(cellCentroids) > 0:
741 for x, y
in cellCentroids:
744 xoff = int(np.floor(centroid.getX())) - bb0.getWidth()//2
745 yoff = int(np.floor(centroid.getY())) - bb0.getHeight()//2
749 bb0 = _makeBoxEvenSized(bb0)
754 bb1 = _makeBoxEvenSized(bb1)
756 if bb0.getArea() > 1
and bb1.getArea() > 1:
763 """Plot both grids of boxes using matplotlib.
765 Will compute the grid via `_generateGrid` if
766 `self.boxes0` and `self.boxes1` have not already been set.
770 exposure : `lsst.afw.image.Exposure`
771 Exposure whose bounding box is gridded by this task.
773 Plot every skip-ped box (help make plots less confusing)
775 import matplotlib.pyplot
as plt
777 if self.
boxes0boxes0
is None:
778 raise RuntimeError(
'Cannot plot boxes. Run _generateGrid first.')
782 plt.gca().set_prop_cycle(
None)
785 def _plotBoxGrid(self, boxes, bbox, **kwargs):
786 """Plot a grid of boxes using matplotlib.
790 boxes : `list` of `lsst.geom.Box2I`
791 a list of bounding boxes.
792 bbox : `lsst.geom.Box2I`
793 an overall bounding box
795 additional keyword arguments for matplotlib
797 import matplotlib.pyplot
as plt
800 corners = np.array([np.array([pt.getX(), pt.getY()])
for pt
in box.getCorners()])
801 corners = np.vstack([corners, corners[0, :]])
802 plt.plot(corners[:, 0], corners[:, 1], **kwargs)
806 plt.xlim(bbox.getBeginX(), bbox.getEndX())
807 plt.ylim(bbox.getBeginY(), bbox.getEndY())
Custom catalog class for ExposureRecord/Table.
An integer coordinate rectangle.
def plotBoxes(self, fullBBox, skip=3)
def _runMapper(self, exposure, doClone=False, **kwargs)
def _generateGrid(self, exposure, forceEvenSized=False, **kwargs)
def run(self, exposure, **kwargs)
def _reduceImage(self, mapperResults, exposure, **kwargs)
def __init__(self, *args, **kwargs)
def _plotBoxGrid(self, boxes, bbox, **kwargs)
def run(self, subExposure, expandedSubExposure, fullBBox, **kwargs)
def _constructPsf(self, mapperResults, exposure)
def run(self, mapperResults, exposure, **kwargs)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.