LSST Applications  21.0.0-172-gfb10e10a+18fedfabac,22.0.0+297cba6710,22.0.0+80564b0ff1,22.0.0+8d77f4f51a,22.0.0+a28f4c53b1,22.0.0+dcf3732eb2,22.0.1-1-g7d6de66+2a20fdde0d,22.0.1-1-g8e32f31+297cba6710,22.0.1-1-geca5380+7fa3b7d9b6,22.0.1-12-g44dc1dc+2a20fdde0d,22.0.1-15-g6a90155+515f58c32b,22.0.1-16-g9282f48+790f5f2caa,22.0.1-2-g92698f7+dcf3732eb2,22.0.1-2-ga9b0f51+7fa3b7d9b6,22.0.1-2-gd1925c9+bf4f0e694f,22.0.1-24-g1ad7a390+a9625a72a8,22.0.1-25-g5bf6245+3ad8ecd50b,22.0.1-25-gb120d7b+8b5510f75f,22.0.1-27-g97737f7+2a20fdde0d,22.0.1-32-gf62ce7b1+aa4237961e,22.0.1-4-g0b3f228+2a20fdde0d,22.0.1-4-g243d05b+871c1b8305,22.0.1-4-g3a563be+32dcf1063f,22.0.1-4-g44f2e3d+9e4ab0f4fa,22.0.1-42-gca6935d93+ba5e5ca3eb,22.0.1-5-g15c806e+85460ae5f3,22.0.1-5-g58711c4+611d128589,22.0.1-5-g75bb458+99c117b92f,22.0.1-6-g1c63a23+7fa3b7d9b6,22.0.1-6-g50866e6+84ff5a128b,22.0.1-6-g8d3140d+720564cf76,22.0.1-6-gd805d02+cc5644f571,22.0.1-8-ge5750ce+85460ae5f3,master-g6e05de7fdc+babf819c66,master-g99da0e417a+8d77f4f51a,w.2021.48
LSST Data Management Base Package
Public Member Functions | Static Public Attributes | List of all members
lsst.ip.diffim.imageMapReduce.ImageReducer Class Reference
Inheritance diagram for lsst.ip.diffim.imageMapReduce.ImageReducer:

Public Member Functions

def run (self, mapperResults, exposure, **kwargs)
 

Static Public Attributes

 ConfigClass = ImageReducerConfig
 

Detailed Description

Base class for any 'reduce' task that is to be
used as `ImageMapReduceConfig.reducer`.

Basic reduce operations are provided by the `run` method
of this class, to be selected by its config.

Definition at line 181 of file imageMapReduce.py.

Member Function Documentation

◆ run()

def lsst.ip.diffim.imageMapReduce.ImageReducer.run (   self,
  mapperResults,
  exposure,
**  kwargs 
)
Reduce a list of items produced by `ImageMapper`.

Either stitch the passed `mapperResults` list
together into a new Exposure (default) or pass it through
(if `self.config.reduceOperation` is 'none').

If `self.config.reduceOperation` is not 'none', then expect
that the `pipeBase.Struct`s in the `mapperResults` list
contain sub-exposures named 'subExposure', to be stitched back
into a single Exposure with the same dimensions, PSF, and mask
as the input `exposure`. Otherwise, the `mapperResults` list
is simply returned directly.

Parameters
----------
mapperResults : `list`
    list of `lsst.pipe.base.Struct` returned by `ImageMapper.run`.
exposure : `lsst.afw.image.Exposure`
    the original exposure which is cloned to use as the
    basis for the resulting exposure (if
    ``self.config.mapper.reduceOperation`` is not 'None')
kwargs :
    additional keyword arguments propagated from
    `ImageMapReduceTask.run`.

Returns
-------
A `lsst.pipe.base.Struct` containing either an `lsst.afw.image.Exposure`
(named 'exposure') or a list (named 'result'),
depending on `config.reduceOperation`.

Notes
-----
1. This currently correctly handles overlapping sub-exposures.
   For overlapping sub-exposures, use `config.reduceOperation='average'`.
2. This correctly handles varying PSFs, constructing the resulting
   exposure's PSF via CoaddPsf (DM-9629).

Known issues

1. To be done: correct handling of masks (nearly there)
2. This logic currently makes *two* copies of the original exposure
   (one here and one in `mapper.run()`). Possibly of concern
   for large images on memory-constrained systems.

Definition at line 191 of file imageMapReduce.py.

191  def run(self, mapperResults, exposure, **kwargs):
192  """Reduce a list of items produced by `ImageMapper`.
193 
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').
197 
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.
204 
205  Parameters
206  ----------
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')
213  kwargs :
214  additional keyword arguments propagated from
215  `ImageMapReduceTask.run`.
216 
217  Returns
218  -------
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`.
222 
223  Notes
224  -----
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).
229 
230  Known issues
231 
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.
236  """
237  # No-op; simply pass mapperResults directly to ImageMapReduceTask.run
238  if self.config.reduceOperation == 'none':
239  return pipeBase.Struct(result=mapperResults)
240 
241  if self.config.reduceOperation == 'coaddPsf':
242  # Each element of `mapperResults` should contain 'psf' and 'bbox'
243  coaddPsf = self._constructPsf(mapperResults, exposure)
244  return pipeBase.Struct(result=coaddPsf)
245 
246  newExp = exposure.clone()
247  newMI = newExp.getMaskedImage()
248 
249  reduceOp = self.config.reduceOperation
250  if reduceOp == 'copy':
251  weights = None
252  newMI.getImage()[:, :] = np.nan
253  newMI.getVariance()[:, :] = np.nan
254  else:
255  newMI.getImage()[:, :] = 0.
256  newMI.getVariance()[:, :] = 0.
257  if reduceOp == 'average': # make an array to keep track of weights
258  weights = afwImage.ImageI(newMI.getBBox())
259 
260  for item in mapperResults:
261  item = item.subExposure # Expected named value in the pipeBase.Struct
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())
270 
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()
275 
276  if reduceOp == 'sum' or reduceOp == 'average': # much of these two options is the same
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':
281  # wtsView is a view into the `weights` Image
282  wtsView = afwImage.ImageI(weights, item.getBBox())
283  wtsView.getArray()[isValid] += 1
284 
285  # New mask plane - for debugging map-reduced images
286  mask = newMI.getMask()
287  for m in self.config.badMaskPlanes:
288  mask.addMaskPlane(m)
289  bad = mask.getPlaneBitMask(self.config.badMaskPlanes)
290 
291  isNan = np.where(np.isnan(newMI.getImage().getArray() * newMI.getVariance().getArray()))
292  if len(isNan[0]) > 0:
293  # set mask to INVALID for pixels where produced exposure is NaN
294  mask.getArray()[isNan[0], isNan[1]] |= bad
295 
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
314  # set mask to something for pixels where wts == 0.
315  # happens sometimes if operation failed on a certain subexposure
316  mask.getArray()[wtsZeroInds] |= bad
317 
318  # Not sure how to construct a PSF when reduceOp=='copy'...
319  if reduceOp == 'sum' or reduceOp == 'average':
320  psf = self._constructPsf(mapperResults, exposure)
321  newExp.setPsf(psf)
322 
323  return pipeBase.Struct(exposure=newExp)
324 
table::Key< int > type
Definition: Detector.cc:163
def run(self, coaddExposures, bbox, wcs)
Definition: getTemplate.py:603

Member Data Documentation

◆ ConfigClass

lsst.ip.diffim.imageMapReduce.ImageReducer.ConfigClass = ImageReducerConfig
static

Definition at line 188 of file imageMapReduce.py.


The documentation for this class was generated from the following file: