LSST Applications g0265f82a02+0e5473021a,g02d81e74bb+0dd8ce4237,g1470d8bcf6+3ea6592b6f,g2079a07aa2+86d27d4dc4,g2305ad1205+5ca4c0b359,g295015adf3+d10818ec9d,g2a9a014e59+6f9be1b9cd,g2bbee38e9b+0e5473021a,g337abbeb29+0e5473021a,g3ddfee87b4+703ba97ebf,g487adcacf7+4fa16da234,g50ff169b8f+96c6868917,g52b1c1532d+585e252eca,g591dd9f2cf+ffa42b374e,g5a732f18d5+53520f316c,g64a986408d+0dd8ce4237,g858d7b2824+0dd8ce4237,g8a8a8dda67+585e252eca,g99cad8db69+d39438377f,g9ddcbc5298+9a081db1e4,ga1e77700b3+15fc3df1f7,ga8c6da7877+f1d96605c8,gb0e22166c9+60f28cb32d,gb6a65358fc+0e5473021a,gba4ed39666+c2a2e4ac27,gbb8dafda3b+e5339d463f,gc120e1dc64+da31e9920e,gc28159a63d+0e5473021a,gcf0d15dbbd+703ba97ebf,gdaeeff99f8+f9a426f77a,ge6526c86ff+889fc9d533,ge79ae78c31+0e5473021a,gee10cc3b42+585e252eca,gf18bd8381d+7268b93478,gff1a9f87cc+0dd8ce4237,w.2024.16
LSST Data Management Base Package
Loading...
Searching...
No Matches
Public Member Functions | Static Public Attributes | Protected Member Functions | Static Protected Attributes | List of all members
lsst.ip.diffim.imageMapReduce.ImageReducer Class Reference
Inheritance diagram for lsst.ip.diffim.imageMapReduce.ImageReducer:

Public Member Functions

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

Static Public Attributes

 ConfigClass = ImageReducerConfig
 

Protected Member Functions

 _constructPsf (self, mapperResults, exposure)
 

Static Protected Attributes

str _DefaultName = "ip_diffim_ImageReducer"
 

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

◆ _constructPsf()

lsst.ip.diffim.imageMapReduce.ImageReducer._constructPsf ( self,
mapperResults,
exposure )
protected
Construct a CoaddPsf based on PSFs from individual subExposures

Currently uses (and returns) a CoaddPsf. TBD if we want to
create a custom subclass of CoaddPsf to differentiate it.

Parameters
----------
mapperResults : `list`
    list of `pipeBase.Struct` returned by `ImageMapper.run`.
    For this to work, each element of `mapperResults` must contain
    a `subExposure` element, from which the component Psfs are
    extracted (thus the reducerTask cannot have
    `reduceOperation = 'none'`.
exposure : `lsst.afw.image.Exposure`
    the original exposure which is used here solely for its
    bounding-box and WCS.

Returns
-------
psf : `lsst.meas.algorithms.CoaddPsf`
    A psf constructed from the PSFs of the individual subExposures.

Definition at line 325 of file imageMapReduce.py.

325 def _constructPsf(self, mapperResults, exposure):
326 """Construct a CoaddPsf based on PSFs from individual subExposures
327
328 Currently uses (and returns) a CoaddPsf. TBD if we want to
329 create a custom subclass of CoaddPsf to differentiate it.
330
331 Parameters
332 ----------
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.
342
343 Returns
344 -------
345 psf : `lsst.meas.algorithms.CoaddPsf`
346 A psf constructed from the PSFs of the individual subExposures.
347 """
348 schema = afwTable.ExposureTable.makeMinimalSchema()
349 schema.addField("weight", type="D", doc="Coadd weight")
350 mycatalog = afwTable.ExposureCatalog(schema)
351
352 # We're just using the exposure's WCS (assuming that the subExposures'
353 # WCSs are the same, which they better be!).
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
369 record['id'] = i
370 mycatalog.append(record)
371
372 # create the coaddpsf
373 psf = measAlg.CoaddPsf(mycatalog, wcsref, 'weight')
374 return psf
375
376
Custom catalog class for ExposureRecord/Table.
Definition Exposure.h:311

◆ run()

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.image.array[:, :] = np.nan
253 newMI.variance.array[:, :] = np.nan
254 else:
255 newMI.image.array[:, :] = 0.
256 newMI.variance.array[:, :] = 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.image.array * patchMI.variance.array)
270
271 if reduceOp == 'copy':
272 subMI.image.array[isValid] = patchMI.image.array[isValid]
273 subMI.variance.array[isValid] = patchMI.variance.array[isValid]
274 subMI.mask.array[:, :] |= patchMI.mask.array
275
276 if reduceOp == 'sum' or reduceOp == 'average': # much of these two options is the same
277 subMI.image.array[isValid] += patchMI.image.array[isValid]
278 subMI.variance.array[isValid] += patchMI.variance.array[isValid]
279 subMI.mask.array[:, :] |= patchMI.mask.array
280 if reduceOp == 'average':
281 # wtsView is a view into the `weights` Image
282 wtsView = afwImage.ImageI(weights, item.getBBox())
283 wtsView.array[isValid] += 1
284
285 # New mask plane - for debugging map-reduced images
286 mask = newMI.mask
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.image.array * newMI.variance.array))
292 if len(isNan[0]) > 0:
293 # set mask to INVALID for pixels where produced exposure is NaN
294 mask.array[isNan[0], isNan[1]] |= bad
295
296 if reduceOp == 'average':
297 wts = weights.array.astype(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.image.array
308 np.divide(tmp, wts, out=tmp, where=notWtsZero)
309 tmp = newMI.variance.array
310 np.divide(tmp, wts, out=tmp, where=notWtsZero)
311 if len(wtsZeroInds[0]) > 0:
312 newMI.image.array[wtsZeroInds] = np.nan
313 newMI.variance.array[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.array[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

Member Data Documentation

◆ _DefaultName

str lsst.ip.diffim.imageMapReduce.ImageReducer._DefaultName = "ip_diffim_ImageReducer"
staticprotected

Definition at line 189 of file imageMapReduce.py.

◆ 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: