LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
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 180 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 190 of file imageMapReduce.py.

190  def run(self, mapperResults, exposure, **kwargs):
191  """Reduce a list of items produced by `ImageMapper`.
192 
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').
196 
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.
203 
204  Parameters
205  ----------
206  mapperResults : `list`
207  list of `lsst.pipe.base.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')
212  kwargs :
213  additional keyword arguments propagated from
214  `ImageMapReduceTask.run`.
215 
216  Returns
217  -------
218  A `lsst.pipe.base.Struct` containing either an `lsst.afw.image.Exposure`
219  (named 'exposure') or a list (named 'result'),
220  depending on `config.reduceOperation`.
221 
222  Notes
223  -----
224  1. This currently correctly handles overlapping sub-exposures.
225  For overlapping sub-exposures, use `config.reduceOperation='average'`.
226  2. This correctly handles varying PSFs, constructing the resulting
227  exposure's PSF via CoaddPsf (DM-9629).
228 
229  Known issues
230 
231  1. To be done: correct handling of masks (nearly there)
232  2. This logic currently makes *two* copies of the original exposure
233  (one here and one in `mapper.run()`). Possibly of concern
234  for large images on memory-constrained systems.
235  """
236  # No-op; simply pass mapperResults directly to ImageMapReduceTask.run
237  if self.config.reduceOperation == 'none':
238  return pipeBase.Struct(result=mapperResults)
239 
240  if self.config.reduceOperation == 'coaddPsf':
241  # Each element of `mapperResults` should contain 'psf' and 'bbox'
242  coaddPsf = self._constructPsf(mapperResults, exposure)
243  return pipeBase.Struct(result=coaddPsf)
244 
245  newExp = exposure.clone()
246  newMI = newExp.getMaskedImage()
247 
248  reduceOp = self.config.reduceOperation
249  if reduceOp == 'copy':
250  weights = None
251  newMI.getImage()[:, :] = np.nan
252  newMI.getVariance()[:, :] = np.nan
253  else:
254  newMI.getImage()[:, :] = 0.
255  newMI.getVariance()[:, :] = 0.
256  if reduceOp == 'average': # make an array to keep track of weights
257  weights = afwImage.ImageI(newMI.getBBox())
258 
259  for item in mapperResults:
260  item = item.subExposure # Expected named value in the pipeBase.Struct
261  if not (isinstance(item, afwImage.ExposureF) or isinstance(item, afwImage.ExposureI)
262  or isinstance(item, afwImage.ExposureU) or isinstance(item, afwImage.ExposureD)):
263  raise TypeError("""Expecting an Exposure type, got %s.
264  Consider using `reduceOperation="none".""" % str(type(item)))
265  subExp = newExp.Factory(newExp, item.getBBox())
266  subMI = subExp.getMaskedImage()
267  patchMI = item.getMaskedImage()
268  isValid = ~np.isnan(patchMI.getImage().getArray() * patchMI.getVariance().getArray())
269 
270  if reduceOp == 'copy':
271  subMI.getImage().getArray()[isValid] = patchMI.getImage().getArray()[isValid]
272  subMI.getVariance().getArray()[isValid] = patchMI.getVariance().getArray()[isValid]
273  subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
274 
275  if reduceOp == 'sum' or reduceOp == 'average': # much of these two options is the same
276  subMI.getImage().getArray()[isValid] += patchMI.getImage().getArray()[isValid]
277  subMI.getVariance().getArray()[isValid] += patchMI.getVariance().getArray()[isValid]
278  subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray()
279  if reduceOp == 'average':
280  # wtsView is a view into the `weights` Image
281  wtsView = afwImage.ImageI(weights, item.getBBox())
282  wtsView.getArray()[isValid] += 1
283 
284  # New mask plane - for debugging map-reduced images
285  mask = newMI.getMask()
286  for m in self.config.badMaskPlanes:
287  mask.addMaskPlane(m)
288  bad = mask.getPlaneBitMask(self.config.badMaskPlanes)
289 
290  isNan = np.where(np.isnan(newMI.getImage().getArray() * newMI.getVariance().getArray()))
291  if len(isNan[0]) > 0:
292  # set mask to INVALID for pixels where produced exposure is NaN
293  mask.getArray()[isNan[0], isNan[1]] |= bad
294 
295  if reduceOp == 'average':
296  wts = weights.getArray().astype(np.float)
297  self.log.info('AVERAGE: Maximum overlap: %f', np.nanmax(wts))
298  self.log.info('AVERAGE: Average overlap: %f', np.nanmean(wts))
299  self.log.info('AVERAGE: Minimum overlap: %f', np.nanmin(wts))
300  wtsZero = np.equal(wts, 0.)
301  wtsZeroInds = np.where(wtsZero)
302  wtsZeroSum = len(wtsZeroInds[0])
303  self.log.info('AVERAGE: Number of zero pixels: %f (%f%%)', wtsZeroSum,
304  wtsZeroSum * 100. / wtsZero.size)
305  notWtsZero = ~wtsZero
306  tmp = newMI.getImage().getArray()
307  np.divide(tmp, wts, out=tmp, where=notWtsZero)
308  tmp = newMI.getVariance().getArray()
309  np.divide(tmp, wts, out=tmp, where=notWtsZero)
310  if len(wtsZeroInds[0]) > 0:
311  newMI.getImage().getArray()[wtsZeroInds] = np.nan
312  newMI.getVariance().getArray()[wtsZeroInds] = np.nan
313  # set mask to something for pixels where wts == 0.
314  # happens sometimes if operation failed on a certain subexposure
315  mask.getArray()[wtsZeroInds] |= bad
316 
317  # Not sure how to construct a PSF when reduceOp=='copy'...
318  if reduceOp == 'sum' or reduceOp == 'average':
319  psf = self._constructPsf(mapperResults, exposure)
320  newExp.setPsf(psf)
321 
322  return pipeBase.Struct(exposure=newExp)
323 
table::Key< int > type
Definition: Detector.cc:163
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)

Member Data Documentation

◆ ConfigClass

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

Definition at line 187 of file imageMapReduce.py.


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