def testString(self): imageF = afwImage.ImageF(100, 100) imageDSmall = afwImage.ImageD(2, 2) imageISmall = afwImage.ImageI(2, 2) imageU = afwImage.ImageU(100, 100) # NumPy's string representation varies depending on the size of the # array; we test both large and small. self.assertIn(str(np.zeros((100, 100), dtype=imageF.dtype)), str(imageF)) self.assertIn("bbox=%s" % str(imageF.getBBox()), str(imageF)) self.assertIn(str(np.zeros((2, 2), dtype=imageDSmall.dtype)), str(imageDSmall)) self.assertIn(str(np.zeros((2, 2), dtype=imageISmall.dtype)), str(imageISmall)) self.assertIn("ImageF=", repr(imageF)) self.assertIn("ImageU=", repr(imageU))
def mtv(self, data, title="", wcs=None): """!Display an Image or Mask on a DISPLAY display Historical note: the name "mtv" comes from Jim Gunn's forth imageprocessing system, Mirella (named after Mirella Freni); The "m" stands for Mirella. """ if hasattr(data, "getXY0"): self._xy0 = data.getXY0() else: self._xy0 = None # it's an Exposure; display the MaskedImage with the WCS if isinstance(data, afwImage.Exposure): if wcs: raise RuntimeError( "You may not specify a wcs with an Exposure") data, wcs = data.getMaskedImage(), data.getWcs() elif isinstance( data, afwImage.DecoratedImage): # it's a DecoratedImage; display it try: wcs = afwGeom.makeSkyWcs(data.getMetadata()) except TypeError: wcs = None data = data.image self._xy0 = data.getXY0() # DecoratedImage doesn't have getXY0() if isinstance(data, afwImage.Image): # it's an Image; display it self._impl._mtv(data, None, wcs, title) # it's a Mask; display it, bitplane by bitplane elif isinstance(data, afwImage.Mask): # # Some displays can't display a Mask without an image; so display an Image too, # with pixel values set to the mask # self._impl._mtv(afwImage.ImageI(data.getArray()), data, wcs, title) # it's a MaskedImage; display Image and overlay Mask elif isinstance(data, afwImage.MaskedImage): self._impl._mtv(data.getImage(), data.getMask(), wcs, title) else: raise RuntimeError("Unsupported type %s" % repr(data))
def testSubtractImages(self): "Test subtraction" # subtract an image self.mimage2 -= self.mimage self.assertEqual(self.mimage2.get(0, 0), (self.imgVal2 - self.imgVal1, self.EDGE, self.varVal2 + self.varVal1)) # Subtract an Image<int> from a MaskedImage<int> mimage_i = afwImage.MaskedImageI(self.mimage2.getDimensions()) mimage_i.set(900, 0x0, 1000.0) image_i = afwImage.ImageI(mimage_i.getDimensions(), 2) mimage_i -= image_i self.assertEqual(mimage_i.get(0, 0), (898, 0x0, 1000.0)) # subtract a scalar self.mimage -= self.imgVal1 self.assertEqual(self.mimage.get(0, 0), (0.0, self.EDGE, self.varVal1))
def run(self, mapperResults, exposure, **kwargs): """Reduce a list of items produced by `ImageMapperSubtask`. 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 `pipeBase.Struct` returned by `ImageMapperSubtask.run`. exposure : lsst.afw.image.Exposure the original exposure which is cloned to use as the basis for the resulting exposure (if self.config.mapperSubtask.reduceOperation is not 'none') kwargs : additional keyword arguments propagated from `ImageMapReduceTask.run`. Returns ------- A `pipeBase.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 `mapperSubtask.run()`). Possibly of concern for large images on memory-constrained systems. """ # No-op; simply pass mapperResults directly to ImageMapReduceTask.run if self.config.reduceOperation == 'none': return pipeBase.Struct(result=mapperResults) if self.config.reduceOperation == 'coaddPsf': # Each element of `mapperResults` should contain 'psf' and 'bbox' coaddPsf = self._constructPsf(mapperResults, exposure) return pipeBase.Struct(result=coaddPsf) newExp = exposure.clone() newMI = newExp.getMaskedImage() reduceOp = self.config.reduceOperation if reduceOp == 'copy': weights = None newMI.getImage()[:, :] = np.nan newMI.getVariance()[:, :] = np.nan else: newMI.getImage()[:, :] = 0. newMI.getVariance()[:, :] = 0. if reduceOp == 'average': # make an array to keep track of weights weights = afwImage.ImageI(newMI.getBBox()) for item in mapperResults: item = item.subExposure # Expected named value in the pipeBase.Struct if not (isinstance(item, afwImage.ExposureF) or isinstance(item, afwImage.ExposureI) or isinstance(item, afwImage.ExposureU) or isinstance(item, afwImage.ExposureD)): raise TypeError("""Expecting an Exposure type, got %s. Consider using `reduceOperation="none".""" % str(type(item))) subExp = newExp.Factory(newExp, item.getBBox()) subMI = subExp.getMaskedImage() patchMI = item.getMaskedImage() isValid = ~np.isnan(patchMI.getImage().getArray() * patchMI.getVariance().getArray()) if reduceOp == 'copy': subMI.getImage().getArray()[isValid] = patchMI.getImage().getArray()[isValid] subMI.getVariance().getArray()[isValid] = patchMI.getVariance().getArray()[isValid] subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray() if reduceOp == 'sum' or reduceOp == 'average': # much of these two options is the same subMI.getImage().getArray()[isValid] += patchMI.getImage().getArray()[isValid] subMI.getVariance().getArray()[isValid] += patchMI.getVariance().getArray()[isValid] subMI.getMask().getArray()[:, :] |= patchMI.getMask().getArray() if reduceOp == 'average': # wtsView is a view into the `weights` Image wtsView = afwImage.ImageI(weights, item.getBBox()) wtsView.getArray()[isValid] += 1 # New mask plane - for debugging map-reduced images mask = newMI.getMask() for m in self.config.badMaskPlanes: mask.addMaskPlane(m) bad = mask.getPlaneBitMask(self.config.badMaskPlanes) isNan = np.where(np.isnan(newMI.getImage().getArray() * newMI.getVariance().getArray())) if len(isNan[0]) > 0: # set mask to INVALID for pixels where produced exposure is NaN mask.getArray()[isNan[0], isNan[1]] |= bad if reduceOp == 'average': wts = weights.getArray().astype(np.float) self.log.info('AVERAGE: Maximum overlap: %f', np.nanmax(wts)) self.log.info('AVERAGE: Average overlap: %f', np.nanmean(wts)) self.log.info('AVERAGE: Minimum overlap: %f', np.nanmin(wts)) wtsZero = np.equal(wts, 0.) wtsZeroInds = np.where(wtsZero) wtsZeroSum = len(wtsZeroInds[0]) self.log.info('AVERAGE: Number of zero pixels: %f (%f%%)', wtsZeroSum, wtsZeroSum * 100. / wtsZero.size) notWtsZero = ~wtsZero tmp = newMI.getImage().getArray() np.divide(tmp, wts, out=tmp, where=notWtsZero) tmp = newMI.getVariance().getArray() np.divide(tmp, wts, out=tmp, where=notWtsZero) if len(wtsZeroInds[0]) > 0: newMI.getImage().getArray()[wtsZeroInds] = np.nan newMI.getVariance().getArray()[wtsZeroInds] = np.nan # set mask to something for pixels where wts == 0. # happens sometimes if operation failed on a certain subexposure mask.getArray()[wtsZeroInds] |= bad # Not sure how to construct a PSF when reduceOp=='copy'... if reduceOp == 'sum' or reduceOp == 'average': psf = self._constructPsf(mapperResults, exposure) newExp.setPsf(psf) return pipeBase.Struct(exposure=newExp)