def makeCcdMosaic(dir, basename, e, c, aList, imageFactory=afwImage.MaskedImageF, verbose=0): """Return an image of all the specified amplifiers, aList, for the given CCD E.g. sl = makeCcdMosaic("/lsst/DC3root/rlp1173", "v704897", 0, 3, range(8)) """ try: aList[0] except TypeError: aList = [aList] for what in ("header", "data"): if what == "header": bbox = afwGeom.Box2I() ampBBox = {} wcs = {} else: ccdImage = imageFactory(bbox.getWidth(), bbox.getHeight()) ccdImage.set(0) ccdImage.setXY0(bbox.getLLC()) for a in aList: filename = os.path.join( dir, "IPSD", "output", "sci", "%s-e%d" % (basename, e), "%s-e%d-c%03d-a%02d.sci" % (basename, e, c, a)) if verbose and what == "header": print(filename) if what == "header": md = afwImage.readMetadata(filename + "_img.fits") xy0 = afwGeom.Point2I(md.get("CRVAL1A"), md.get("CRVAL2A")) xy1 = xy0 + afwGeom.Extent2I( md.get("NAXIS1") - 1, md.get("NAXIS2") - 1) bbox.grow(xy0) bbox.grow(xy1) ampBBox[a] = afwGeom.Box2I(xy0, xy1) wcs[a] = afwImage.Wcs(md) else: try: data = imageFactory(filename + "_img.fits") except: data = imageFactory(filename) ampImage = ccdImage.Factory(ccdImage, ampBBox[a]) ampImage[:] = data del ampImage try: ccdImage.getMask() if 0 in wcs: ccdImage = afwImage.ExposureF(ccdImage, wcs[0]) except AttributeError: pass return ccdImage
def testPeakLikelihoodFlux(self): """Test measurement with PeakLikelihoodFlux """ # make mp: a flux measurer measControl = measAlg.PeakLikelihoodFluxControl() schema = afwTable.SourceTable.makeMinimalSchema() mp = measAlg.MeasureSourcesBuilder().addAlgorithm(measControl).build( schema) # make and measure a series of exposures containing just one star, approximately centered bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 101)) kernelWidth = 35 var = 100 fwhm = 3.0 sigma = fwhm / FwhmPerSigma convolutionControl = afwMath.ConvolutionControl() psf = measAlg.SingleGaussianPsf(kernelWidth, kernelWidth, sigma) psfKernel = psf.getLocalKernel() psfImage = psf.computeKernelImage() sumPsfSq = numpy.sum(psfImage.getArray()**2) psfSqArr = psfImage.getArray()**2 for flux in (1000, 10000): ctrInd = afwGeom.Point2I(50, 51) ctrPos = afwGeom.Point2D(ctrInd) kernelBBox = psfImage.getBBox(afwImage.PARENT) kernelBBox.shift(afwGeom.Extent2I(ctrInd)) # compute predicted flux error unshMImage = makeFakeImage(bbox, [ctrPos], [flux], fwhm, var) # filter image by PSF unshFiltMImage = afwImage.MaskedImageF( unshMImage.getBBox(afwImage.PARENT)) afwMath.convolve(unshFiltMImage, unshMImage, psfKernel, convolutionControl) # compute predicted flux = value of image at peak / sum(PSF^2) # this is a sanity check of the algorithm, as much as anything predFlux = unshFiltMImage.getImage().get(ctrInd[0], ctrInd[1]) / sumPsfSq self.assertLess(abs(flux - predFlux), flux * 0.01) # compute predicted flux error based on filtered pixels # = sqrt(value of filtered variance at peak / sum(PSF^2)^2) predFluxErr = math.sqrt(unshFiltMImage.getVariance().get( ctrInd[0], ctrInd[1])) / sumPsfSq # compute predicted flux error based on unfiltered pixels # = sqrt(sum(unfiltered variance * PSF^2)) / sum(PSF^2) # and compare to that derived from filtered pixels; # again, this is a test of the algorithm varView = afwImage.ImageF(unshMImage.getVariance(), kernelBBox) varArr = varView.getArray() unfiltPredFluxErr = math.sqrt(numpy.sum( varArr * psfSqArr)) / sumPsfSq self.assertLess(abs(unfiltPredFluxErr - predFluxErr), predFluxErr * 0.01) for fracOffset in (afwGeom.Extent2D(0, 0), afwGeom.Extent2D(0.2, -0.3)): adjCenter = ctrPos + fracOffset if fracOffset == (0, 0): maskedImage = unshMImage filteredImage = unshFiltMImage else: maskedImage = makeFakeImage(bbox, [adjCenter], [flux], fwhm, var) # filter image by PSF filteredImage = afwImage.MaskedImageF( maskedImage.getBBox(afwImage.PARENT)) afwMath.convolve(filteredImage, maskedImage, psfKernel, convolutionControl) exposure = afwImage.makeExposure(filteredImage) exposure.setPsf(psf) table = afwTable.SourceTable.make(schema) source = table.makeRecord() mp.apply(source, exposure, afwGeom.Point2D(*adjCenter)) measFlux = source.get(measControl.name) measFluxErr = source.get(measControl.name + ".err") self.assertFalse(source.get(measControl.name + ".flags")) self.assertLess(abs(measFlux - flux), flux * 0.003) self.assertLess(abs(measFluxErr - predFluxErr), predFluxErr * 0.2) # try nearby points and verify that the flux is smaller; # this checks that the sub-pixel shift is performed in the correct direction for dx in (-0.2, 0, 0.2): for dy in (-0.2, 0, 0.2): if dx == dy == 0: continue offsetCtr = afwGeom.Point2D(adjCenter[0] + dx, adjCenter[1] + dy) table = afwTable.SourceTable.make(schema) source = table.makeRecord() mp.apply(source, exposure, offsetCtr) offsetFlux = source.get(measControl.name) self.assertLess(offsetFlux, measFlux) # source so near edge of image that PSF does not overlap exposure should result in failure for edgePos in ( (1, 50), (50, 1), (50, bbox.getHeight() - 1), (bbox.getWidth() - 1, 50), ): table = afwTable.SourceTable.make(schema) source = table.makeRecord() mp.apply(source, exposure, afwGeom.Point2D(*edgePos)) self.assertTrue(source.get(measControl.name + ".flags")) # no PSF should result in failure: flags set noPsfExposure = afwImage.ExposureF(filteredImage) table = afwTable.SourceTable.make(schema) source = table.makeRecord() mp.apply(source, noPsfExposure, afwGeom.Point2D(*adjCenter)) self.assertTrue(source.get(measControl.name + ".flags"))
def _makeAmpInfoCatalog(self): """Construct an amplifier info catalog """ extended = 1024 # extended register x_overscan = 40 # number of overscan pixel in x saturation = 65535 # Linearity correction is still under discussion, so this is a placeholder. linearityType = "PROPORTIONAL" linearityThreshold = 0 linearityMax = saturation linearityCoeffs = [linearityThreshold, linearityMax] schema = AmpInfoTable.makeMinimalSchema() linThreshKey = schema.addField('linearityThreshold', type=float) linMaxKey = schema.addField('linearityMaximum', type=float) linUnitsKey = schema.addField('linearityUnits', type=str, size=9) # end placeholder self.ampInfoDict = {} ampCatalog = AmpInfoCatalog(schema) for ampY in range(1, 3): for ampX in range(1, 3): record = ampCatalog.addNew() record.setName("%d%d" % (ampX, ampY)) print('Amp Name : %s, %s' % (ampX, ampY)) if ((ampX == 1) & (ampY == 1)): record.setBBox( afwGeom.Box2I( afwGeom.Point2I(0, 0), afwGeom.Extent2I(extended, extended), )) record.setRawHorizontalOverscanBBox( afwGeom.Box2I( afwGeom.Point2I(1044, 0), afwGeom.Extent2I(x_overscan, extended), )) record.setRawXYOffset(\ afwGeom.Extent2I(1084, 1024)) # bias region record.setRawBBox( afwGeom.Box2I( afwGeom.Point2I(10, 0), afwGeom.Extent2I(extended, extended), )) record.setRawDataBBox( afwGeom.Box2I( afwGeom.Point2I(10, 0), afwGeom.Extent2I(extended, extended), )) if ((ampX == 1) & (ampY == 2)): record.setBBox( afwGeom.Box2I( afwGeom.Point2I(1024, 0), afwGeom.Extent2I(extended, extended), )) record.setRawHorizontalOverscanBBox( afwGeom.Box2I( afwGeom.Point2I(1084, 0), afwGeom.Extent2I(x_overscan, extended), )) record.setRawXYOffset(\ afwGeom.Extent2I(1084, 1024)) # bias region record.setRawBBox( afwGeom.Box2I( afwGeom.Point2I(1134, 0), afwGeom.Extent2I(extended, extended), )) record.setRawDataBBox( afwGeom.Box2I( afwGeom.Point2I(1134, 0), afwGeom.Extent2I(extended, extended), )) if ((ampX == 2) & (ampY == 1)): record.setBBox( afwGeom.Box2I( afwGeom.Point2I(0, 1024), afwGeom.Extent2I(extended, extended), )) record.setRawHorizontalOverscanBBox( afwGeom.Box2I( afwGeom.Point2I(1044, 1024), afwGeom.Extent2I(x_overscan, extended), )) record.setRawXYOffset(\ afwGeom.Extent2I(1084, 1024)) # bias region record.setRawBBox( afwGeom.Box2I( afwGeom.Point2I(10, 1024), afwGeom.Extent2I(extended, extended), )) record.setRawDataBBox( afwGeom.Box2I( afwGeom.Point2I(10, 1024), afwGeom.Extent2I(extended, extended), )) if ((ampX == 2) & (ampY == 2)): record.setBBox( afwGeom.Box2I( afwGeom.Point2I(1024, 1024), afwGeom.Extent2I(extended, extended), )) record.setRawHorizontalOverscanBBox( afwGeom.Box2I( afwGeom.Point2I(1084, 1024), afwGeom.Extent2I(x_overscan, extended), )) record.setRawXYOffset(\ afwGeom.Extent2I(1084, 1024)) # bias region record.setRawBBox( afwGeom.Box2I( afwGeom.Point2I(1134, 1024), afwGeom.Extent2I(extended, extended), )) record.setRawDataBBox( afwGeom.Box2I( afwGeom.Point2I(1134, 1024), afwGeom.Extent2I(extended, extended), )) readCorner = LL # in raw frames; always LL because raws are in amp coords record.setReadoutCorner(readCorner) record.setGain(self.gain[(ampX, ampY)]) record.setReadNoise(self.readNoise[(ampX, ampY)]) record.setSaturation(saturation) record.setHasRawInfo(True) record.setRawPrescanBBox(afwGeom.Box2I()) # linearity placeholder stuff record.setLinearityCoeffs( [float(val) for val in linearityCoeffs]) record.setLinearityType(linearityType) record.set(linThreshKey, float(linearityThreshold)) record.set(linMaxKey, float(linearityMax)) record.set(linUnitsKey, "DN") return ampCatalog
def makeAmpTables(segmentsFile): """ Read the segments file from a PhoSim release and produce the appropriate AmpInfo @param segmentsFile -- String indicating where the file is located """ returnDict = {} readoutMap = { 'LL': afwTable.LL, 'LR': afwTable.LR, 'UR': afwTable.UR, 'UL': afwTable.UL } detectorName = [ ] # set to a value that is an invalid dict key, to catch bugs with open(segmentsFile) as fh: fh.readline() for l in fh: els = l.rstrip().split() detectorName = els[1] #skip focus and guiding for now: if detectorName[0] in ('F', 'G'): continue if detectorName not in returnDict: schema = afwTable.AmpInfoTable.makeMinimalSchema() returnDict[detectorName] = afwTable.AmpInfoCatalog(schema) record = returnDict[detectorName].addNew() name = els[2] gain = float(els[7]) saturation = int(els[8]) readnoise = float(els[9]) xoff = int(els[5]) yoff = int(els[6]) ndatax = int(els[3]) ndatay = int(els[4]) flipx = False flipy = False if detectorName.startswith("S") and name == "A": readCorner = readoutMap['UR'] elif detectorName.startswith("S") and name == "B": readCorner = readoutMap['UL'] elif detectorName.startswith("N") and name == "A": readCorner = readoutMap['LL'] elif detectorName.startswith("N") and name == "B": readCorner = readoutMap['LR'] else: raise RuntimeError( "Did not recognize detector name or amp name") prescan = 6 hoverscan = 50 voverscan = 50 rawBBox = afwGeom.Box2I( afwGeom.Point2I(xoff, yoff), afwGeom.Extent2I(ndatax + prescan + hoverscan, ndatay + voverscan)) # Note: I'm not particularry happy with how the data origin is derived (it neglects [xy]off), # but I don't see a better way. if readCorner is afwTable.LL: originRawData = afwGeom.Point2I(xoff + prescan + hoverscan, yoff) originData = afwGeom.Point2I(0, 0) originHOverscan = afwGeom.Point2I(xoff + prescan, yoff) originVOverscan = afwGeom.Point2I(xoff + prescan + hoverscan, yoff + ndatay) originPrescan = afwGeom.Point2I(xoff, yoff) elif readCorner is afwTable.LR: originRawData = afwGeom.Point2I(xoff, yoff) originData = afwGeom.Point2I(ndatax, 0) originHOverscan = afwGeom.Point2I(xoff + ndatax, yoff) originVOverscan = afwGeom.Point2I(xoff, yoff + ndatay) originPrescan = afwGeom.Point2I(xoff + ndatax + hoverscan, yoff) elif readCorner is afwTable.UL: originRawData = afwGeom.Point2I(xoff + prescan + hoverscan, yoff + voverscan) originData = afwGeom.Point2I(0, 0) originHOverscan = afwGeom.Point2I(xoff + prescan, yoff + voverscan) originVOverscan = afwGeom.Point2I(xoff + prescan + hoverscan, yoff) originPrescan = afwGeom.Point2I(xoff, yoff + voverscan) elif readCorner is afwTable.UR: originRawData = afwGeom.Point2I(xoff, yoff + voverscan) originData = afwGeom.Point2I(ndatax, 0) originHOverscan = afwGeom.Point2I(xoff + ndatax, yoff + voverscan) originVOverscan = afwGeom.Point2I(xoff, yoff) originPrescan = afwGeom.Point2I(xoff + ndatax + hoverscan, yoff + voverscan) else: raise RuntimeError( "Expected readout corner to be LL, LR, UL, or UR") rawDataBBox = afwGeom.Box2I(originRawData, afwGeom.Extent2I(ndatax, ndatay)) dataBBox = afwGeom.Box2I(originData, afwGeom.Extent2I(ndatax, ndatay)) rawHorizontalOverscanBBox = afwGeom.Box2I( originHOverscan, afwGeom.Extent2I(hoverscan, ndatay)) rawVerticalOverscanBBox = afwGeom.Box2I( originVOverscan, afwGeom.Extent2I(ndatax, voverscan)) rawPrescanBBox = afwGeom.Box2I(originPrescan, afwGeom.Extent2I(prescan, ndatay)) print "\nDetector=%s; Amp=%s" % (detectorName, name) print rawHorizontalOverscanBBox print rawVerticalOverscanBBox print dataBBox print rawBBox #Set the elements of the record for this amp record.setBBox( dataBBox) # This is the box for the amp in the assembled frame record.setName(name) record.setReadoutCorner(readCorner) record.setGain(gain) record.setSaturation(saturation) record.setSuspectLevel(float("nan")) record.setReadNoise(readnoise) ampIndex = dict(A=0, B=1)[name] record.setLinearityCoeffs([ampIndex, 0, 0, 0]) record.setLinearityType(LinearizeLookupTable.LinearityType) print "Linearity type=%r; coeffs=%s" % ( record.getLinearityType(), record.getLinearityCoeffs()) record.setHasRawInfo(True) record.setRawFlipX(flipx) record.setRawFlipY(flipy) record.setRawBBox(rawBBox) # I believe that xy offset is not needed if the raw data are pre-assembled record.setRawXYOffset(afwGeom.Extent2I(0, 0)) """ if readCorner is afwTable.LL: record.setRawXYOffset(afwGeom.Extent2I(xoff + prescan + hoverscan, yoff)) elif readCorner is afwTable.LR: record.setRawXYOffset(afwGeom.Extent2I(xoff, yoff)) elif readCorner is afwTable.UL: record.setRawXYOffset(afwGeom.Extent2I(xoff + prescan + hoverscan, yoff + voverscan)) elif readCorner is afwTable.UR: record.setRawXYOffset(afwGeom.Extent2I(xoff, yoff + voverscan)) """ record.setRawDataBBox(rawDataBBox) record.setRawHorizontalOverscanBBox(rawHorizontalOverscanBBox) record.setRawVerticalOverscanBBox(rawVerticalOverscanBBox) # I think of prescan as being along the bottom of the raw data. I actually # don't know how you would do a prescan in the serial direction. record.setRawPrescanBBox(afwGeom.Box2I()) return returnDict
def _makeModel(self, exposure, psf, fp, negCenter, posCenter): negPsf = psf.computeImage(negCenter).convertF() posPsf = psf.computeImage(posCenter).convertF() negPeak = psf.computePeak(negCenter) posPeak = psf.computePeak(posCenter) negPsf /= negPeak posPsf /= posPeak model = afwImage.ImageF(fp.getBBox()) negModel = afwImage.ImageF(fp.getBBox()) posModel = afwImage.ImageF(fp.getBBox()) # The center of the Psf should be at negCenter, posCenter negPsfBBox = negPsf.getBBox() posPsfBBox = posPsf.getBBox() modelBBox = model.getBBox() # Portion of the negative Psf that overlaps the montage negOverlapBBox = afwGeom.Box2I(negPsfBBox) negOverlapBBox.clip(modelBBox) self.assertFalse(negOverlapBBox.isEmpty()) # Portion of the positivePsf that overlaps the montage posOverlapBBox = afwGeom.Box2I(posPsfBBox) posOverlapBBox.clip(modelBBox) self.assertFalse(posOverlapBBox.isEmpty()) negPsfSubim = type(negPsf)(negPsf, negOverlapBBox) modelSubim = type(model)(model, negOverlapBBox) negModelSubim = type(negModel)(negModel, negOverlapBBox) modelSubim += negPsfSubim # just for debugging negModelSubim += negPsfSubim # for fitting posPsfSubim = type(posPsf)(posPsf, posOverlapBBox) modelSubim = type(model)(model, posOverlapBBox) posModelSubim = type(posModel)(posModel, posOverlapBBox) modelSubim += posPsfSubim posModelSubim += posPsfSubim data = afwImage.ImageF(exposure.getMaskedImage().getImage(), fp.getBBox()) var = afwImage.ImageF(exposure.getMaskedImage().getVariance(), fp.getBBox()) matrixNorm = 1. / np.sqrt(np.median(var.getArray())) if display: ds9.mtv(model, frame=5, title="Unfitted model") ds9.mtv(data, frame=6, title="Data") posPsfSum = np.sum(posPsf.getArray()) negPsfSum = np.sum(negPsf.getArray()) M = np.array((np.ravel(negModel.getArray()), np.ravel(posModel.getArray()))).T.astype(np.float64) B = np.array((np.ravel(data.getArray()))).astype(np.float64) M *= matrixNorm B *= matrixNorm # Numpy solution fneg0, fpos0 = np.linalg.lstsq(M, B)[0] # Afw solution lsq = afwMath.LeastSquares.fromDesignMatrix(M, B, afwMath.LeastSquares.DIRECT_SVD) fneg, fpos = lsq.getSolution() # Should be exaxtly the same as each other self.assertAlmostEqual(1e-2*fneg0, 1e-2*fneg) self.assertAlmostEqual(1e-2*fpos0, 1e-2*fpos) # Recreate model fitted = afwImage.ImageF(fp.getBBox()) negFit = type(negPsf)(negPsf, negOverlapBBox, afwImage.PARENT, True) negFit *= float(fneg) posFit = type(posPsf)(posPsf, posOverlapBBox, afwImage.PARENT, True) posFit *= float(fpos) fitSubim = type(fitted)(fitted, negOverlapBBox) fitSubim += negFit fitSubim = type(fitted)(fitted, posOverlapBBox) fitSubim += posFit if display: ds9.mtv(fitted, frame=7, title="Fitted model") fitted -= data if display: ds9.mtv(fitted, frame=8, title="Residuals") fitted *= fitted fitted /= var if display: ds9.mtv(fitted, frame=9, title="Chi2") return fneg, negPsfSum, fpos, posPsfSum, fitted
def testAssertWcssNearlyEqualOverBBox(self): """Test assertWcsNearlyEqualOverBBox and wcsNearlyEqualOverBBox""" bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(3001, 3001)) ctrPix = afwGeom.Point2I(1500, 1500) metadata = dafBase.PropertySet() metadata.set("RADECSYS", "FK5") metadata.set("EQUINOX", 2000.0) metadata.set("CTYPE1", "RA---TAN") metadata.set("CTYPE2", "DEC--TAN") metadata.set("CUNIT1", "deg") metadata.set("CUNIT2", "deg") metadata.set("CRVAL1", 215.5) metadata.set("CRVAL2", 53.0) metadata.set("CRPIX1", ctrPix[0] + 1) metadata.set("CRPIX2", ctrPix[1] + 1) metadata.set("CD1_1", 5.1e-05) metadata.set("CD1_2", 0.0) metadata.set("CD2_2", -5.1e-05) metadata.set("CD2_1", 0.0) wcs0 = afwImage.cast_TanWcs(afwImage.makeWcs(metadata)) metadata.set("CRVAL2", 53.000001) # tweak CRVAL2 for wcs1 wcs1 = afwImage.cast_TanWcs(afwImage.makeWcs(metadata)) self.assertWcsNearlyEqualOverBBox(wcs0, wcs0, bbox, maxDiffSky=1e-7 * afwGeom.arcseconds, maxDiffPix=1e-7) self.assertTrue( afwImage.wcsNearlyEqualOverBBox(wcs0, wcs0, bbox, maxDiffSky=1e-7 * afwGeom.arcseconds, maxDiffPix=1e-7)) self.assertWcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.04 * afwGeom.arcseconds, maxDiffPix=0.02) self.assertTrue( afwImage.wcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.04 * afwGeom.arcseconds, maxDiffPix=0.02)) with self.assertRaises(AssertionError): self.assertWcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.001 * afwGeom.arcseconds, maxDiffPix=0.02) self.assertFalse( afwImage.wcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.001 * afwGeom.arcseconds, maxDiffPix=0.02)) with self.assertRaises(AssertionError): self.assertWcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.04 * afwGeom.arcseconds, maxDiffPix=0.001) self.assertFalse( afwImage.wcsNearlyEqualOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.04 * afwGeom.arcseconds, maxDiffPix=0.001)) # check that doShortCircuit works in the private implementation errStr1 = _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.001 * afwGeom.arcseconds, maxDiffPix=0.001, doShortCircuit=False) errStr2 = _compareWcsOverBBox(wcs0, wcs1, bbox, maxDiffSky=0.001 * afwGeom.arcseconds, maxDiffPix=0.001, doShortCircuit=True) self.assertNotEqual(errStr1, errStr2)
def testAssignWithBBox(self): """Test assign(rhs, bbox) with non-empty bbox """ for xy0 in ( afwGeom.Point2I(*val) for val in ( (0, 0), (-100, 120), # an arbitrary value that is off the image )): destMIDim = afwGeom.Extent2I(5, 4) srcMIDim = afwGeom.Extent2I(3, 2) destMI = afwImage.MaskedImageF(destMIDim) destImage = destMI.getImage() destVariance = destMI.getVariance() destMask = destMI.getMask() destMI.setXY0(xy0) srcMI = makeRampImage(*srcMIDim) srcMI.setXY0(55, -33) # an arbitrary value that should be ignored self.assertRaises(Exception, destMI.set, srcMI) # size mismatch for validMin in (afwGeom.Point2I(*val) for val in ( (0, 0), (2, 0), (0, 1), (1, 2), )): # None to omit the argument for origin in (None, afwImage.PARENT, afwImage.LOCAL): destImage[:] = -1.0 destVariance[:] = -1.0 destMask[:] = 0xFFFF bbox = afwGeom.Box2I(validMin, srcMI.getDimensions()) if origin != afwImage.LOCAL: bbox.shift(afwGeom.Extent2I(xy0)) if origin is None: destMI.assign(srcMI, bbox) destMIView = afwImage.MaskedImageF(destMI, bbox) else: destMI.assign(srcMI, bbox, origin) destMIView = afwImage.MaskedImageF( destMI, bbox, origin) for i in range(3): self.assertListEqual( destMIView.getArrays()[i].flatten().tolist(), srcMI.getArrays()[i].flatten().tolist()) numPixNotAssigned = (destMIDim[0] * destMIDim[1]) - ( srcMIDim[0] * srcMIDim[1]) self.assertEqual(np.sum(destImage.getArray() < -0.5), numPixNotAssigned) self.assertEqual(np.sum(destVariance.getArray() < -0.5), numPixNotAssigned) self.assertEqual(np.sum(destMask.getArray() == 0xFFFF), numPixNotAssigned) for badMin in (afwGeom.Point2I(*val) + afwGeom.Extent2I(xy0) for val in ( (-1, 0), (3, 0), (0, -1), (1, 3), )): # None to omit the argument for origin in (None, afwImage.PARENT, afwImage.LOCAL): bbox = afwGeom.Box2I(validMin, srcMI.getDimensions()) if origin != afwImage.LOCAL: bbox.shift(afwGeom.Extent2I(xy0)) if origin is None: self.assertRaises(Exception, destMI.set, srcMI, bbox) else: self.assertRaises(Exception, destMI.set, srcMI, bbox, origin)
def testReadWriteFits(self): """Test readFits and writeFits. """ # This should pass without an exception mainExposure = afwImage.ExposureF(inFilePathSmall) mainExposure.setDetector(cameraGeom.Detector(cameraGeom.Id(666))) subBBox = afwGeom.Box2I(afwGeom.Point2I(10, 10), afwGeom.Extent2I(40, 50)) subExposure = mainExposure.Factory(mainExposure, subBBox, afwImage.LOCAL) self.checkWcs(mainExposure, subExposure) det = subExposure.getDetector() self.assertTrue(det) subExposure = afwImage.ExposureF(inFilePathSmall, subBBox) self.checkWcs(mainExposure, subExposure) # This should throw an exception def getExposure(): afwImage.ExposureF(inFilePathSmallImage) utilsTests.assertRaisesLsstCpp(self, lsst.afw.fits.FitsError, getExposure) mainExposure.setPsf(self.psf) # Make sure we can write without an exception mainExposure.getCalib().setExptime(10) mainExposure.getCalib().setMidTime(dafBase.DateTime()) midMjd = mainExposure.getCalib().getMidTime().get() fluxMag0, fluxMag0Err = 1e12, 1e10 mainExposure.getCalib().setFluxMag0(fluxMag0, fluxMag0Err) mainExposure.writeFits(outFile) # Check scaling of Calib scale = 2.0 calib = mainExposure.getCalib() calib *= scale self.assertEqual((fluxMag0 * scale, fluxMag0Err * scale), calib.getFluxMag0()) calib /= scale self.assertEqual((fluxMag0, fluxMag0Err), calib.getFluxMag0()) readExposure = type(mainExposure)(outFile) os.remove(outFile) # # Check the round-tripping # self.assertEqual(mainExposure.getFilter().getName(), readExposure.getFilter().getName()) self.assertEqual(mainExposure.getCalib().getExptime(), readExposure.getCalib().getExptime()) self.assertEqual(midMjd, readExposure.getCalib().getMidTime().get()) self.assertEqual((fluxMag0, fluxMag0Err), readExposure.getCalib().getFluxMag0()) psf = readExposure.getPsf() self.assert_(psf is not None) dummyPsf = DummyPsf.swigConvert(psf) self.assert_(dummyPsf is not None) self.assertEqual(dummyPsf.getValue(), self.psf.getValue())
def _makeAmpInfoCatalog(self): """Construct an amplifier info catalog """ # Much of this will need to be filled in when we know it. xDataExtent = 512 # trimmed yDataExtent = 2002 extended = 10 # extended register h_overscan = 22 # number of overscan in x v_overscan = 46 # number of overscan in y xRawExtent = extended + h_overscan + xDataExtent yRawExtent = v_overscan + yDataExtent # no prescan in vertical saturation = 65535 # Linearity correction is still under discussion, so this is a placeholder. linearityType = "PROPORTIONAL" linearityThreshold = 0 linearityMax = saturation linearityCoeffs = [linearityThreshold, linearityMax] schema = AmpInfoTable.makeMinimalSchema() linThreshKey = schema.addField('linearityThreshold', type=float) linMaxKey = schema.addField('linearityMaximum', type=float) linUnitsKey = schema.addField('linearityUnits', type=str, size=9) # end placeholder self.ampInfoDict = {} ampCatalog = AmpInfoCatalog(schema) for ampY in (0, 1): for ampX in range(8): record = ampCatalog.addNew() record.setName("%d%d" % (ampX, ampY)) if bool(ampY): record.setBBox( afwGeom.Box2I( afwGeom.Point2I(ampX * xDataExtent, ampY * yDataExtent), afwGeom.Extent2I(xDataExtent, yDataExtent), )) else: record.setBBox( afwGeom.Box2I( afwGeom.Point2I((7 - ampX) * xDataExtent, ampY * yDataExtent), afwGeom.Extent2I(xDataExtent, yDataExtent), )) readCorner = LL # in raw frames; always LL because raws are in amp coords # bias region x0Bias = extended + xDataExtent y0Data = 0 x0Data = extended record.setRawBBox( afwGeom.Box2I( afwGeom.Point2I(0, 0), afwGeom.Extent2I(xRawExtent, yRawExtent), )) record.setRawDataBBox( afwGeom.Box2I( afwGeom.Point2I(x0Data, y0Data), afwGeom.Extent2I(xDataExtent, yDataExtent), )) record.setRawHorizontalOverscanBBox( afwGeom.Box2I( afwGeom.Point2I(x0Bias, y0Data), afwGeom.Extent2I(h_overscan, yDataExtent), )) record.setRawVerticalOverscanBBox( afwGeom.Box2I( afwGeom.Point2I(x0Data, y0Data + yDataExtent), afwGeom.Extent2I(xDataExtent, v_overscan), )) record.setRawXYOffset( afwGeom.Extent2I(ampX * xRawExtent, ampY * yRawExtent)) record.setReadoutCorner(readCorner) record.setGain(self.gain[(ampX, ampY)]) record.setReadNoise(self.readNoise[(ampX, ampY)]) record.setSaturation(saturation) record.setHasRawInfo(True) record.setRawFlipX(bool(ampY)) # flip data when assembling if in top of chip record.setRawFlipY(bool(ampY)) record.setRawPrescanBBox(afwGeom.Box2I()) # linearity placeholder stuff record.setLinearityCoeffs( [float(val) for val in linearityCoeffs]) record.setLinearityType(linearityType) record.set(linThreshKey, float(linearityThreshold)) record.set(linMaxKey, float(linearityMax)) record.set(linUnitsKey, "DN") return ampCatalog
def sourceToFootprintList(candidateInList, templateExposure, scienceExposure, kernelSize, config, log): """Convert a list of sources for the PSF-matching Kernel to Footprints. Parameters ---------- candidateInList : TODO: DM-17458 Input list of Sources templateExposure : TODO: DM-17458 Template image, to be checked for Mask bits in Source Footprint scienceExposure : TODO: DM-17458 Science image, to be checked for Mask bits in Source Footprint kernelSize : TODO: DM-17458 TODO: DM-17458 config : TODO: DM-17458 Config that defines the Mask planes that indicate an invalid Source and Bbox grow radius log : TODO: DM-17458 Log for output Returns ------- candidateOutList : `list` a list of dicts having a "source" and "footprint" field, to be used for Psf-matching Raises ------ RuntimeError TODO: DM-17458 Notes ----- Takes an input list of Sources that were selected to constrain the Psf-matching Kernel and turns them into a List of Footprints, which are used to seed a set of KernelCandidates. The function checks both the template and science image for masked pixels, rejecting the Source if certain Mask bits (defined in config) are set within the Footprint. """ candidateOutList = [] fsb = diffimLib.FindSetBitsU() badBitMask = 0 for mp in config.badMaskPlanes: badBitMask |= afwImage.Mask.getPlaneBitMask(mp) bbox = scienceExposure.getBBox() # Size to grow Sources if config.scaleByFwhm: fpGrowPix = int(config.fpGrowKernelScaling * kernelSize + 0.5) else: fpGrowPix = config.fpGrowPix log.info("Growing %d kernel candidate stars by %d pixels", len(candidateInList), fpGrowPix) for kernelCandidate in candidateInList: if not type(kernelCandidate) == afwTable.SourceRecord: raise RuntimeError("Candiate not of type afwTable.SourceRecord") bm1 = 0 bm2 = 0 center = afwGeom.Point2I(scienceExposure.getWcs().skyToPixel( kernelCandidate.getCoord())) if center[0] < bbox.getMinX() or center[0] > bbox.getMaxX(): continue if center[1] < bbox.getMinY() or center[1] > bbox.getMaxY(): continue xmin = center[0] - fpGrowPix xmax = center[0] + fpGrowPix ymin = center[1] - fpGrowPix ymax = center[1] + fpGrowPix # Keep object centered if (xmin - bbox.getMinX()) < 0: xmax += (xmin - bbox.getMinX()) xmin -= (xmin - bbox.getMinX()) if (ymin - bbox.getMinY()) < 0: ymax += (ymin - bbox.getMinY()) ymin -= (ymin - bbox.getMinY()) if (bbox.getMaxX() - xmax) < 0: xmin -= (bbox.getMaxX() - xmax) xmax += (bbox.getMaxX() - xmax) if (bbox.getMaxY() - ymax) < 0: ymin -= (bbox.getMaxY() - ymax) ymax += (bbox.getMaxY() - ymax) if xmin > xmax or ymin > ymax: continue kbbox = afwGeom.Box2I(afwGeom.Point2I(xmin, ymin), afwGeom.Point2I(xmax, ymax)) try: fsb.apply( afwImage.MaskedImageF(templateExposure.getMaskedImage(), kbbox, deep=False).getMask()) bm1 = fsb.getBits() fsb.apply( afwImage.MaskedImageF(scienceExposure.getMaskedImage(), kbbox, deep=False).getMask()) bm2 = fsb.getBits() except Exception: pass else: if not ((bm1 & badBitMask) or (bm2 & badBitMask)): candidateOutList.append({ 'source': kernelCandidate, 'footprint': afwDetect.Footprint(afwGeom.SpanSet(kbbox)) }) log.info("Selected %d / %d sources for KernelCandidacy", len(candidateOutList), len(candidateInList)) return candidateOutList
def testGetSubExposure(self): """ Test that a subExposure of the original Exposure can be obtained. The MaskedImage class should throw a lsst::pex::exceptions::InvalidParameter if the requested subRegion is not fully contained within the original MaskedImage. """ # # This subExposure is valid # subBBox = afwGeom.Box2I(afwGeom.Point2I(40, 50), afwGeom.Extent2I(10, 10)) subExposure = self.exposureCrWcs.Factory(self.exposureCrWcs, subBBox, afwImage.LOCAL) self.checkWcs(self.exposureCrWcs, subExposure) # this subRegion is not valid and should trigger an exception # from the MaskedImage class and should trigger an exception # from the WCS class for the MaskedImage 871034p_1_MI. subRegion3 = afwGeom.Box2I(afwGeom.Point2I(100, 100), afwGeom.Extent2I(10, 10)) def getSubRegion(): self.exposureCrWcs.Factory(self.exposureCrWcs, subRegion3, afwImage.LOCAL) utilsTests.assertRaisesLsstCpp(self, pexExcept.LengthErrorException, getSubRegion) # this subRegion is not valid and should trigger an exception # from the MaskedImage class only for the MaskedImage small_MI. # small_MI (cols, rows) = (256, 256) subRegion4 = afwGeom.Box2I(afwGeom.Point2I(250, 250), afwGeom.Extent2I(10, 10)) def getSubRegion(): self.exposureCrWcs.Factory(self.exposureCrWcs, subRegion4, afwImage.LOCAL) utilsTests.assertRaisesLsstCpp(self, pexExcept.LengthErrorException, getSubRegion) #check the sub- and parent- exposures are using the same Wcs transformation subBBox = afwGeom.Box2I(afwGeom.Point2I(40, 50), afwGeom.Extent2I(10, 10)) subExposure = self.exposureCrWcs.Factory(self.exposureCrWcs, subBBox, afwImage.LOCAL) parentPos = self.exposureCrWcs.getWcs().pixelToSky(0, 0) parentPos = parentPos.getPosition() subExpPos = subExposure.getWcs().pixelToSky(0, 0).getPosition() for i in range(2): self.assertAlmostEqual(parentPos[i], subExpPos[i], 9, "Wcs in sub image has changed")
def makeFakeKernelSet(sizeCell=128, nCell=3, deltaFunctionCounts=1.e4, tGaussianWidth=1.0, addNoise=True, bgValue=100., display=False): """Generate test template and science images with sources. Parameters ---------- sizeCell : `int`, optional Size of the square spatial cells in pixels. nCell : `int`, optional Number of adjacent spatial cells in both direction in both images. deltaFunctionCounts : `float`, optional Flux value for the template image sources. tGaussianWidth : `float`, optional Sigma of the generated Gaussian PSF sources in the template image. addNoise : `bool`, optional If `True`, Poisson noise is added to both the generated template and science images. bgValue : `float`, optional Background level to be added to the generated science image. display : `bool`, optional If `True` displays the generated template and science images by `lsst.afw.display.Display`. Notes ----- - The generated images consist of adjacent ``nCell x nCell`` cells, each of pixel size ``sizeCell x sizeCell``. - The sources in the science image are generated by convolving the template by ``sKernel``. ``sKernel`` is a spatial `LinearCombinationKernel` of hard wired kernel bases functions. The linear combination has first order polynomial spatial dependence with polynomial parameters from ``fakeCoeffs()``. - The template image sources are generated in the center of each spatial cell from one pixel, set to `deltaFunctionCounts` counts, then convolved by a 2D Gaussian with sigma of `tGaussianWidth` along each axis. - The sources are also returned in ``kernelCellSet`` each source is "detected" exactly at the center of a cell. Returns ------- tMi : `lsst.afw.image.MaskedImage` Generated template image. sMi : `lsst.afw.image.MaskedImage` Generated science image. sKernel : `lsst.afw.math.LinearCombinationKernel` The spatial kernel used to generate the sources in the science image. kernelCellSet : `lsst.afw.math.SpatialCellSet` Cell grid of `lsst.afw.math.SpatialCell` instances, containing `lsst.ip.diffim.KernelCandidate` instances around all the generated sources in the science image. configFake : `lsst.ip.diffim.ImagePsfMatchConfig` Config instance used in the image generation. """ from . import imagePsfMatch configFake = imagePsfMatch.ImagePsfMatchConfig() configFake.kernel.name = "AL" subconfigFake = configFake.kernel.active subconfigFake.alardNGauss = 1 subconfigFake.alardSigGauss = [ 2.5, ] subconfigFake.alardDegGauss = [ 2, ] subconfigFake.sizeCellX = sizeCell subconfigFake.sizeCellY = sizeCell subconfigFake.spatialKernelOrder = 1 subconfigFake.spatialModelType = "polynomial" subconfigFake.singleKernelClipping = False # variance is a hack subconfigFake.spatialKernelClipping = False # variance is a hack if bgValue > 0.0: subconfigFake.fitForBackground = True policyFake = pexConfig.makePolicy(subconfigFake) basisList = makeKernelBasisList(subconfigFake) kSize = subconfigFake.kernelSize # This sets the final extent of each convolved delta function gaussKernelWidth = sizeCell // 2 # This sets the scale over which pixels are correlated in the # spatial convolution; should be at least as big as the kernel you # are trying to fit for spatialKernelWidth = kSize # Number of bad pixels due to convolutions border = (gaussKernelWidth + spatialKernelWidth) // 2 # Make a fake image with a matrix of delta functions totalSize = nCell * sizeCell + 2 * border tim = afwImage.ImageF(afwGeom.Extent2I(totalSize, totalSize)) for x in range(nCell): for y in range(nCell): tim[x * sizeCell + sizeCell // 2 + border - 1, y * sizeCell + sizeCell // 2 + border - 1, afwImage.LOCAL] = deltaFunctionCounts # Turn this into stars with a narrow width; conserve counts gaussFunction = afwMath.GaussianFunction2D(tGaussianWidth, tGaussianWidth) gaussKernel = afwMath.AnalyticKernel(gaussKernelWidth, gaussKernelWidth, gaussFunction) cim = afwImage.ImageF(tim.getDimensions()) afwMath.convolve(cim, tim, gaussKernel, True) tim = cim # Trim off border pixels bbox = gaussKernel.shrinkBBox(tim.getBBox(afwImage.LOCAL)) tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL) # Now make a science image which is this convolved with some # spatial function. Use input basis list. polyFunc = afwMath.PolynomialFunction2D(1) kCoeffs = fakeCoeffs() nToUse = min(len(kCoeffs), len(basisList)) # Make the full convolved science image sKernel = afwMath.LinearCombinationKernel(basisList[:nToUse], polyFunc) sKernel.setSpatialParameters(kCoeffs[:nToUse]) sim = afwImage.ImageF(tim.getDimensions()) afwMath.convolve(sim, tim, sKernel, True) # Get the good subregion bbox = sKernel.shrinkBBox(sim.getBBox(afwImage.LOCAL)) # Add background sim += bgValue # Watch out for negative values tim += 2 * np.abs(np.min(tim.getArray())) # Add noise? if addNoise: sim = makePoissonNoiseImage(sim) tim = makePoissonNoiseImage(tim) # And turn into MaskedImages sim = afwImage.ImageF(sim, bbox, afwImage.LOCAL) svar = afwImage.ImageF(sim, True) smask = afwImage.Mask(sim.getDimensions()) smask.set(0x0) sMi = afwImage.MaskedImageF(sim, smask, svar) tim = afwImage.ImageF(tim, bbox, afwImage.LOCAL) tvar = afwImage.ImageF(tim, True) tmask = afwImage.Mask(tim.getDimensions()) tmask.set(0x0) tMi = afwImage.MaskedImageF(tim, tmask, tvar) if display: import lsst.afw.display as afwDisplay afwDisplay.Display(frame=1).mtv(tMi) afwDisplay.Display(frame=2).mtv(sMi) # Finally, make a kernelSet from these 2 images kernelCellSet = afwMath.SpatialCellSet( afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(sizeCell * nCell, sizeCell * nCell)), sizeCell, sizeCell) stampHalfWidth = 2 * kSize for x in range(nCell): for y in range(nCell): xCoord = x * sizeCell + sizeCell // 2 yCoord = y * sizeCell + sizeCell // 2 p0 = afwGeom.Point2I(xCoord - stampHalfWidth, yCoord - stampHalfWidth) p1 = afwGeom.Point2I(xCoord + stampHalfWidth, yCoord + stampHalfWidth) bbox = afwGeom.Box2I(p0, p1) tsi = afwImage.MaskedImageF(tMi, bbox, origin=afwImage.LOCAL) ssi = afwImage.MaskedImageF(sMi, bbox, origin=afwImage.LOCAL) kc = diffimLib.makeKernelCandidate(xCoord, yCoord, tsi, ssi, policyFake) kernelCellSet.insertCandidate(kc) tMi.setXY0(0, 0) sMi.setXY0(0, 0) return tMi, sMi, sKernel, kernelCellSet, configFake
def plantSources(x0, y0, nx, ny, sky, nObj, wid, detector, useRandom=False): pixToTanPix = detector.getTransform(cameraGeom.PIXELS, cameraGeom.TAN_PIXELS) img0 = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) img = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) ixx0, iyy0, ixy0 = wid * wid, wid * wid, 0.0 edgeBuffer = 40.0 * wid flux = 1.0e4 nkx, nky = int(10 * wid) + 1, int(10 * wid) + 1 xhwid, yhwid = nkx // 2, nky // 2 nRow = int(math.sqrt(nObj)) xstep = (nx - 1 - 0.0 * edgeBuffer) // (nRow + 1) ystep = (ny - 1 - 0.0 * edgeBuffer) // (nRow + 1) if useRandom: nObj = nRow * nRow goodAdded0 = [] goodAdded = [] for i in range(nObj): # get our position if useRandom: xcen0, ycen0 = np.random.uniform(nx), np.random.uniform(ny) else: xcen0, ycen0 = xstep * ( (i % nRow) + 1), ystep * (int(i / nRow) + 1) ixcen0, iycen0 = int(xcen0), int(ycen0) # distort position and shape pTan = afwGeom.Point2D(xcen0, ycen0) p = pixToTanPix.applyInverse(pTan) linTransform = afwGeom.linearizeTransform(pixToTanPix, p).invert().getLinear() m = afwGeom.Quadrupole(ixx0, iyy0, ixy0) m.transform(linTransform) xcen, ycen = xcen0, ycen0 # p.getX(), p.getY() if (xcen < 1.0 * edgeBuffer or (nx - xcen) < 1.0 * edgeBuffer or ycen < 1.0 * edgeBuffer or (ny - ycen) < 1.0 * edgeBuffer): continue ixcen, iycen = int(xcen), int(ycen) ixx, iyy, ixy = m.getIxx(), m.getIyy(), m.getIxy() # plant the object tmp = 0.25 * (ixx - iyy)**2 + ixy**2 a2 = 0.5 * (ixx + iyy) + np.sqrt(tmp) b2 = 0.5 * (ixx + iyy) - np.sqrt(tmp) theta = 0.5 * np.arctan2(2.0 * ixy, ixx - iyy) a = np.sqrt(a2) b = np.sqrt(b2) c, s = math.cos(theta), math.sin(theta) good0, good = True, True for y in range(nky): iy = iycen + y - yhwid iy0 = iycen0 + y - yhwid for x in range(nkx): ix = ixcen + x - xhwid ix0 = ixcen0 + x - xhwid if ix >= 0 and ix < nx and iy >= 0 and iy < ny: dx, dy = ix - xcen, iy - ycen u = c * dx + s * dy v = -s * dx + c * dy I0 = flux / (2 * math.pi * a * b) val = I0 * math.exp(-0.5 * ((u / a)**2 + (v / b)**2)) if val < 0: val = 0 prevVal = img.get(ix, iy) img.set(ix, iy, val + prevVal) else: good = False if ix0 >= 0 and ix0 < nx and iy0 >= 0 and iy0 < ny: dx, dy = ix - xcen, iy - ycen I0 = flux / (2 * math.pi * wid * wid) val = I0 * math.exp(-0.5 * ((dx / wid)**2 + (dy / wid)**2)) if val < 0: val = 0 prevVal = img0.get(ix0, iy0) img0.set(ix0, iy0, val + prevVal) else: good0 = False if good0: goodAdded0.append([xcen, ycen]) if good: goodAdded.append([xcen, ycen]) # add sky and noise img += sky img0 += sky noise = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) noise0 = afwImage.ImageF(afwGeom.ExtentI(nx, ny)) for i in range(nx): for j in range(ny): noise.set(i, j, np.random.poisson(img.get(i, j))) noise0.set(i, j, np.random.poisson(img0.get(i, j))) edgeWidth = int(0.5 * edgeBuffer) mask = afwImage.Mask(afwGeom.ExtentI(nx, ny)) left = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.ExtentI(edgeWidth, ny)) right = afwGeom.Box2I(afwGeom.Point2I(nx - edgeWidth, 0), afwGeom.ExtentI(edgeWidth, ny)) top = afwGeom.Box2I(afwGeom.Point2I(0, ny - edgeWidth), afwGeom.ExtentI(nx, edgeWidth)) bottom = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.ExtentI(nx, edgeWidth)) for pos in [left, right, top, bottom]: msk = afwImage.Mask(mask, pos, deep=False) msk.set(msk.getPlaneBitMask('EDGE')) expos = afwImage.makeExposure( afwImage.makeMaskedImage(noise, mask, afwImage.ImageF(noise, True))) expos0 = afwImage.makeExposure( afwImage.makeMaskedImage(noise0, mask, afwImage.ImageF(noise0, True))) im = expos.getMaskedImage().getImage() im0 = expos0.getMaskedImage().getImage() im -= sky im0 -= sky return expos, goodAdded, expos0, goodAdded0
def setUp(self): np.random.seed(500) # make test repeatable self.x0, self.y0 = 0, 0 self.nx, self.ny = 512, 512 # 2048, 4096 self.sky = 100.0 self.nObj = 100 # make a detector with distortion self.detector = DetectorWrapper( bbox=afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(self.nx, self.ny)), orientation=cameraGeom.Orientation(afwGeom.Point2D(255.0, 255.0)), radialDistortion=0.925, ).detector # make a detector with no distortion self.flatDetector = DetectorWrapper( bbox=afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(self.nx, self.ny)), orientation=cameraGeom.Orientation(afwGeom.Point2D(255.0, 255.0)), radialDistortion=0.0, ).detector # detection policies detConfig = measAlg.SourceDetectionConfig() # Cannot use default background approximation order (6) for such a small image. detConfig.background.approxOrderX = 4 # measurement policies measConfig = measBase.SingleFrameMeasurementConfig() measConfig.algorithms.names = [ "base_SdssCentroid", "base_SdssShape", "base_GaussianFlux", "base_PsfFlux", ] measConfig.slots.centroid = "base_SdssCentroid" measConfig.slots.shape = "base_SdssShape" measConfig.slots.psfFlux = "base_PsfFlux" measConfig.plugins["base_SdssCentroid"].doFootprintCheck = False measConfig.slots.apFlux = None measConfig.slots.modelFlux = None measConfig.slots.instFlux = None measConfig.slots.calibFlux = None self.schema = afwTable.SourceTable.makeMinimalSchema() detConfig.validate() measConfig.validate() self.detTask = measAlg.SourceDetectionTask(config=detConfig, schema=self.schema) self.measTask = measBase.SingleFrameMeasurementTask(config=measConfig, schema=self.schema) # psf star selector starSelectorClass = measAlg.sourceSelectorRegistry["objectSize"] starSelectorConfig = starSelectorClass.ConfigClass() starSelectorConfig.fluxMin = 5000.0 starSelectorConfig.badFlags = [] self.starSelector = starSelectorClass(config=starSelectorConfig) self.makePsfCandidates = measAlg.MakePsfCandidatesTask() # psf determiner psfDeterminerFactory = measAlg.psfDeterminerRegistry["pca"] psfDeterminerConfig = psfDeterminerFactory.ConfigClass() width, height = self.nx, self.ny nEigenComponents = 3 psfDeterminerConfig.sizeCellX = width // 3 psfDeterminerConfig.sizeCellY = height // 3 psfDeterminerConfig.nEigenComponents = nEigenComponents psfDeterminerConfig.spatialOrder = 1 psfDeterminerConfig.kernelSizeMin = 31 psfDeterminerConfig.nStarPerCell = 0 psfDeterminerConfig.nStarPerCellSpatialFit = 0 # unlimited self.psfDeterminer = psfDeterminerFactory(psfDeterminerConfig)
def getBBox(self): """Get bounding box of tract (as an afwGeom.Box2I) """ return afwGeom.Box2I(self._bbox)
def testPeakRemoval(self): ''' A simple example: three overlapping blobs (detected as 1 footprint with three peaks). Additional peaks are added near the blob peaks that should be identified as degenerate. ''' H, W = 100, 100 fpbb = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Point2I(W - 1, H - 1)) afwimg = afwImage.MaskedImageF(fpbb) imgbb = afwimg.getBBox() img = afwimg.getImage().getArray() var = afwimg.getVariance().getArray() var[:, :] = 1. blob_fwhm = 10. blob_psf = doubleGaussianPsf(99, 99, blob_fwhm, 2. * blob_fwhm, 0.03) fakepsf_fwhm = 3. fakepsf = gaussianPsf(11, 11, fakepsf_fwhm) blobimgs = [] x = 75. XY = [(x, 35.), (x, 65.), (50., 50.)] flux = 1e6 for x, y in XY: bim = blob_psf.computeImage(afwGeom.Point2D(x, y)) bbb = bim.getBBox() bbb.clip(imgbb) bim = bim.Factory(bim, bbb) bim2 = bim.getArray() blobimg = np.zeros_like(img) blobimg[bbb.getMinY():bbb.getMaxY() + 1, bbb.getMinX():bbb.getMaxX() + 1] += flux * bim2 blobimgs.append(blobimg) img[bbb.getMinY():bbb.getMaxY() + 1, bbb.getMinX():bbb.getMaxX() + 1] += flux * bim2 # Run the detection code to get a ~ realistic footprint thresh = afwDet.createThreshold(5., 'value', True) fpSet = afwDet.FootprintSet(afwimg, thresh, 'DETECTED', 1) fps = fpSet.getFootprints() self.assertTrue(len(fps) == 1) # Add new peaks near to the first peaks that will be degenerate fp0 = fps[0] for x, y in XY: fp0.addPeak(x - 10, y + 6, 10) deb = deblend(fp0, afwimg, fakepsf, fakepsf_fwhm, verbose=True, removeDegenerateTemplates=True) self.assertTrue(deb.deblendedParents[0].peaks[3].degenerate) self.assertTrue(deb.deblendedParents[0].peaks[4].degenerate) self.assertTrue(deb.deblendedParents[0].peaks[5].degenerate)
def testCoaddApCorrMap(self): """Check that we can create and use a coadd ApCorrMap.""" coaddBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(100, 100)) scale = 5.0e-5*afwGeom.degrees cdMatrix = afwGeom.makeCdMatrix(scale=scale) crval = afwGeom.SpherePoint(0.0, 0.0, afwGeom.degrees) center = afwGeom.Point2D(afwGeom.Extent2D(coaddBox.getDimensions())*0.5) coaddWcs = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(0, 0), crval=crval, cdMatrix=cdMatrix) schema = afwTable.ExposureTable.makeMinimalSchema() weightKey = schema.addField("customweightname", type="D", doc="Coadd weight") catalog = afwTable.ExposureCatalog(schema) # Non-overlapping num = 5 inputBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(10, 10)) validBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(7, 7)) pointList = [] pointListValid = [] for i in range(num): value = np.array([[1]], dtype=float) # Constant with value = i+1 apCorrMap = afwImage.ApCorrMap() bf = afwMath.ChebyshevBoundedField(inputBox, value*(i + 1)) apCorrMap.set("only", bf) point = afwGeom.Point2D(0, 0) - afwGeom.Extent2D(coaddBox.getDimensions())*(i+0.5)/num wcs = afwGeom.makeSkyWcs(crpix=point, crval=crval, cdMatrix=cdMatrix) center = afwGeom.Box2D(inputBox).getCenter() pointList.append(coaddWcs.skyToPixel(wcs.pixelToSky(center))) # This point will only be valid for the second overlapping record pointValid = center + afwGeom.Extent2D(4, 4) pointListValid.append(coaddWcs.skyToPixel(wcs.pixelToSky(pointValid))) # A record with the valid polygon defining a limited region record = catalog.getTable().makeRecord() record.setWcs(wcs) record.setBBox(inputBox) record.setApCorrMap(apCorrMap) record.set(weightKey, i + 1) record['id'] = i record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(validBox))) catalog.append(record) # An overlapping record with the whole region as valid record = catalog.getTable().makeRecord() record.setWcs(wcs) record.setBBox(inputBox) apCorrMap = afwImage.ApCorrMap() bf = afwMath.ChebyshevBoundedField(inputBox, value*(i + 2)) apCorrMap.set("only", bf) record.setApCorrMap(apCorrMap) record.set(weightKey, i + 2) record['id'] = i + num record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(inputBox))) catalog.append(record) apCorrMap = measAlg.makeCoaddApCorrMap(catalog, coaddBox, coaddWcs, "customweightname") # This will test a point where both records contribute self.assertApCorrMap(apCorrMap, pointList) # Only the second record will be valid for this point self.assertApCorrMapValid(apCorrMap, pointListValid) filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), "coaddApCorrMap.fits") exposure = afwImage.ExposureF(1, 1) exposure.getInfo().setApCorrMap(apCorrMap) exposure.writeFits(filename) exposure = afwImage.ExposureF(filename) self.assertApCorrMap(exposure.getInfo().getApCorrMap(), pointList) self.assertApCorrMapValid(exposure.getInfo().getApCorrMap(), pointListValid) os.unlink(filename)
import lsst.afw.table as afwTable import lsst.afw.geom as afwGeom import numpy as np # This is copying from afw/tests/testAmpInfoTable.py: schema = afwTable.AmpInfoTable.makeMinimalSchema() catalog = afwTable.AmpInfoCatalog(schema) record = catalog.addNew() name = 'Amp1' bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(8176, 6132)) gain = 0.7 saturation = 57571 readNoise = 12.5 readoutCorner = afwTable.LL #I think this means Lower Left. linearityCoeffs = (1.0, np.nan, np.nan, np.nan) linearityType = "None" rawBBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(8176, 6132)) rawXYOffset = afwGeom.Extent2I(0, 0) rawDataBBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(8176, 6132)) #rawHorizontalOverscanBBox = afwGeom.Box2I(afwGeom.Point2I(8176, 0), afwGeom.Extent2I(0, 8176)) #rawVerticalOverscanBBox = afwGeom.Box2I(afwGeom.Point2I(6132, 0), afwGeom.Extent2I(0, 6132)) rawPrescanBBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(0, 0)) record.setHasRawInfo(True) #Sets the first Flag=True record.setRawFlipX(False) #Sets the second Flag=False record.setRawFlipY(False) #Sets the third Flag=False record.setBBox(bbox) record.setName(name) record.setGain(gain)
def testImagesOverlap(self): # make pairs of image, variance and mask planes # using the same dimensions for each so we can mix and match # while making masked images dim = afwGeom.Extent2I(10, 8) # a set of bounding boxes, some of which overlap each other # and some of which do not, and include the full image bounding box bboxes = ( afwGeom.Box2I(afwGeom.Point2I(0, 0), dim), afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(3, 3)), afwGeom.Box2I(afwGeom.Point2I(2, 2), afwGeom.Extent2I(6, 4)), afwGeom.Box2I(afwGeom.Point2I(4, 4), afwGeom.Extent2I(6, 4)), ) masks = [afwImage.Mask(dim), afwImage.Mask(dim)] variances = [afwImage.ImageF(dim), afwImage.ImageF(dim)] imageClasses = (afwImage.ImageF, afwImage.ImageD, afwImage.ImageI, afwImage.ImageU) for ImageClass1, ImageClass2 in itertools.product( imageClasses, imageClasses): images = [ImageClass1(dim), ImageClass2(dim)] for image1, mask1, variance1, image2, mask2, variance2 in itertools.product( images, masks, variances, images, masks, variances): with self.subTest(ImageClass1=ImageClass1, ImageClass2=ImageClass2, image1=image1, mask1=mask1, variance1=variance1, image2=image2, mask2=mask2, variance2=variance2): shouldOverlap = (image1 is image2) or (mask1 is mask2) or ( variance1 is variance2) mi1 = afwImage.makeMaskedImage(image=image1, mask=mask1, variance=variance1) mi2 = afwImage.makeMaskedImage(image=image2, mask=mask2, variance=variance2) self.assertEqual(afwImage.imagesOverlap(mi1, mi2), shouldOverlap) self.assertEqual(afwImage.imagesOverlap(mi2, mi1), shouldOverlap) for bbox1, bbox2 in itertools.product(bboxes, bboxes): with self.subTest(bbox1=bbox1, bbox2=bbox2): subMi1 = afwImage.makeMaskedImage( image=type(image1)(image1, bbox1), mask=afwImage.Mask(mask1, bbox1), variance=afwImage.ImageF(variance1, bbox1)) subMi2 = afwImage.makeMaskedImage( image=type(image2)(image2, bbox2), mask=afwImage.Mask(mask2, bbox2), variance=afwImage.ImageF(variance2, bbox2)) subregionsShouldOverlap = shouldOverlap and bbox1.overlaps( bbox2) self.assertEqual( afwImage.imagesOverlap(subMi1, subMi2), subregionsShouldOverlap) self.assertEqual( afwImage.imagesOverlap(subMi2, subMi1), subregionsShouldOverlap)
def testInputCounts(self, showPlot=False): # Generate a simulated coadd of four overlapping-but-offset CCDs. # Populate it with three sources. # Demonstrate that we can correctly recover the number of images which # contribute to each source. size = 20 # Size of images (pixels) value = 100.0 # Source flux ccdPositions = [ afwGeom.Point2D(8, 0), afwGeom.Point2D(10, 10), afwGeom.Point2D(-8, -8), afwGeom.Point2D(-8, 8) ] # Represent sources by a tuple of position and expected number of # contributing CCDs (based on the size/positions given above). Source = namedtuple("Source", ["pos", "count"]) sources = [ Source(pos=afwGeom.Point2D(6, 6), count=2), Source(pos=afwGeom.Point2D(10, 10), count=3), Source(pos=afwGeom.Point2D(14, 14), count=1) ] # These lines are used in the creation of WCS information scale = 1.0e-5 * afwGeom.degrees cdMatrix = afwGeom.makeCdMatrix(scale=scale) crval = afwGeom.SpherePoint(0.0, 0.0, afwGeom.degrees) # Construct the info needed to set the exposure object imageBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(size, size)) wcsRef = afwGeom.makeSkyWcs(crpix=afwGeom.Point2D(0, 0), crval=crval, cdMatrix=cdMatrix) # Create the exposure object, and set it up to be the output of a coadd exp = afwImage.ExposureF(size, size) exp.setWcs(wcsRef) exp.getInfo().setCoaddInputs( afwImage.CoaddInputs(afwTable.ExposureTable.makeMinimalSchema(), afwTable.ExposureTable.makeMinimalSchema())) # Set the fake CCDs that "went into" making this coadd, using the # differing wcs objects created above. ccds = exp.getInfo().getCoaddInputs().ccds for pos in ccdPositions: record = ccds.addNew() record.setWcs( afwGeom.makeSkyWcs(crpix=pos, crval=crval, cdMatrix=cdMatrix)) record.setBBox(imageBox) record.setValidPolygon(afwGeom.Polygon(afwGeom.Box2D(imageBox))) # Configure a SingleFrameMeasurementTask to run InputCounts. measureSourcesConfig = measBase.SingleFrameMeasurementConfig() measureSourcesConfig.plugins.names = [ "base_PeakCentroid", "base_InputCount" ] measureSourcesConfig.slots.centroid = "base_PeakCentroid" measureSourcesConfig.slots.psfFlux = None measureSourcesConfig.slots.apFlux = None measureSourcesConfig.slots.modelFlux = None measureSourcesConfig.slots.instFlux = None measureSourcesConfig.slots.calibFlux = None measureSourcesConfig.slots.shape = None measureSourcesConfig.validate() schema = afwTable.SourceTable.makeMinimalSchema() task = measBase.SingleFrameMeasurementTask(schema, config=measureSourcesConfig) catalog = afwTable.SourceCatalog(schema) # Add simulated sources to the measurement catalog. for src in sources: spans = afwGeom.SpanSet.fromShape(1) spans = spans.shiftedBy(int(src.pos.getX()), int(src.pos.getY())) foot = afwDetection.Footprint(spans) peak = foot.getPeaks().addNew() peak.setFx(src.pos[0]) peak.setFy(src.pos[1]) peak.setPeakValue(value) catalog.addNew().setFootprint(foot) task.run(catalog, exp) for src, rec in zip(sources, catalog): self.assertEqual(rec.get("base_InputCount_value"), src.count) if display: ccdVennDiagram(exp)
def makeMosaic(self, images=None, display="deferToFrame", mode=None, background=None, title="", frame=None): """Return a mosaic of all the images provided; if none are specified, use the list accumulated with Mosaic.append(). Note that this mosaic is a patchwork of the input images; if you want to make a mosaic of a set images of the sky, you probably want to use the coadd code If display or frame (deprecated) is specified, display the mosaic """ if images: if self.images: raise RuntimeError( "You have already appended %d images to this Mosaic" % len(self.images)) try: len(images) # check that it quacks like a list except TypeError: images = [images] self.images = images else: images = self.images if self.nImage == 0: raise RuntimeError("You must provide at least one image") self.xsize, self.ysize = 0, 0 for im in images: w, h = im.getWidth(), im.getHeight() if w > self.xsize: self.xsize = w if h > self.ysize: self.ysize = h if background is None: background = self.background if mode is None: mode = self.mode if mode == "square": nx, ny = 1, self.nImage while nx * im.getWidth() < ny * im.getHeight(): nx += 1 ny = self.nImage // nx if nx * ny < self.nImage: ny += 1 if nx * ny < self.nImage: nx += 1 if nx > self.nImage: nx = self.nImage assert (nx * ny >= self.nImage) elif mode == "x": nx, ny = self.nImage, 1 elif mode == "y": nx, ny = 1, self.nImage elif isinstance(mode, int): nx = mode ny = self.nImage // nx if nx * ny < self.nImage: ny += 1 else: raise RuntimeError("Unknown mosaicing mode: %s" % mode) self.nx, self.ny = nx, ny mosaic = images[0].Factory( afwGeom.Extent2I(nx * self.xsize + (nx - 1) * self.gutter, ny * self.ysize + (ny - 1) * self.gutter)) try: mosaic.set(self.background) except AttributeError: raise RuntimeError( "Attempt to mosaic images of type %s which don't support set" % type(mosaic)) for i in range(len(images)): smosaic = mosaic.Factory(mosaic, self.getBBox(i % nx, i // nx), afwImage.LOCAL) im = images[i] if smosaic.getDimensions() != im.getDimensions( ): # im is smaller than smosaic llc = afwGeom.PointI( (smosaic.getWidth() - im.getWidth()) // 2, (smosaic.getHeight() - im.getHeight()) // 2) smosaic = smosaic.Factory( smosaic, afwGeom.Box2I(llc, im.getDimensions()), afwImage.LOCAL) smosaic[:] = im display = _getDisplayFromDisplayOrFrame(display, frame) if display: display.mtv(mosaic, title=title) if images == self.images: self.drawLabels(display=display) return mosaic
display = True verbosity = 4 logUtils.traceSetAt("ip.diffim", verbosity) defDataDir = lsst.utils.getPackageDir('afwdata') imageProcDir = lsst.utils.getPackageDir('ip_diffim') if len(sys.argv) == 1: defTemplatePath = os.path.join(defDataDir, "CFHT", "D4", "cal-53535-i-797722_2_tmpl.fits") defSciencePath = os.path.join(defDataDir, "CFHT", "D4", "cal-53535-i-797722_2.fits") templateMaskedImage = afwImage.MaskedImageF(defTemplatePath) scienceMaskedImage = afwImage.MaskedImageF(defSciencePath) bbox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(512, 512)) templateMaskedImage = afwImage.MaskedImageF(templateMaskedImage, bbox, origin=afwImage.LOCAL) scienceMaskedImage = afwImage.MaskedImageF(scienceMaskedImage, bbox, origin=afwImage.LOCAL) elif len(sys.argv) == 3: defTemplatePath = sys.argv[1] defSciencePath = sys.argv[2] templateMaskedImage = afwImage.MaskedImageF(defTemplatePath) scienceMaskedImage = afwImage.MaskedImageF(defSciencePath) else: sys.exit(1)
def measureBackground(self, exposure): statsControl = afwMath.StatisticsControl( self.config.qa.flatness.clipSigma, self.config.qa.flatness.nIter) maskVal = exposure.getMaskedImage().getMask().getPlaneBitMask( ["BAD", "SAT", "DETECTED"]) statsControl.setAndMask(maskVal) maskedImage = exposure.getMaskedImage() stats = afwMath.makeStatistics(maskedImage, afwMath.MEDIAN | afwMath.STDEVCLIP, statsControl) skyLevel = stats.getValue(afwMath.MEDIAN) skySigma = stats.getValue(afwMath.STDEVCLIP) self.log.info("Flattened sky level: %f +/- %f" % (skyLevel, skySigma)) metadata = exposure.getMetadata() metadata.set('SKYLEVEL', skyLevel) metadata.set('SKYSIGMA', skySigma) # calcluating flatlevel over the subgrids stat = afwMath.MEANCLIP if self.config.qa.flatness.doClip else afwMath.MEAN meshXHalf = int(self.config.qa.flatness.meshX / 2.) meshYHalf = int(self.config.qa.flatness.meshY / 2.) nX = int( (exposure.getWidth() + meshXHalf) / self.config.qa.flatness.meshX) nY = int( (exposure.getHeight() + meshYHalf) / self.config.qa.flatness.meshY) skyLevels = numpy.zeros((nX, nY)) for j in range(nY): yc = meshYHalf + j * self.config.qa.flatness.meshY for i in range(nX): xc = meshXHalf + i * self.config.qa.flatness.meshX xLLC = xc - meshXHalf yLLC = yc - meshYHalf xURC = xc + meshXHalf - 1 yURC = yc + meshYHalf - 1 bbox = afwGeom.Box2I(afwGeom.Point2I(xLLC, yLLC), afwGeom.Point2I(xURC, yURC)) miMesh = maskedImage.Factory(exposure.getMaskedImage(), bbox, afwImage.LOCAL) skyLevels[i, j] = afwMath.makeStatistics(miMesh, stat, statsControl).getValue() good = numpy.where(numpy.isfinite(skyLevels)) skyMedian = numpy.median(skyLevels[good]) flatness = (skyLevels[good] - skyMedian) / skyMedian flatness_rms = numpy.std(flatness) flatness_pp = flatness.max() - flatness.min() if len( flatness) > 0 else numpy.nan self.log.info("Measuring sky levels in %dx%d grids: %f" % (nX, nY, skyMedian)) self.log.info("Sky flatness in %dx%d grids - pp: %f rms: %f" % (nX, nY, flatness_pp, flatness_rms)) metadata.set('FLATNESS_PP', float(flatness_pp)) metadata.set('FLATNESS_RMS', float(flatness_rms)) metadata.set('FLATNESS_NGRIDS', '%dx%d' % (nX, nY)) metadata.set('FLATNESS_MESHX', self.config.qa.flatness.meshX) metadata.set('FLATNESS_MESHY', self.config.qa.flatness.meshY)
def testStackBadPixels(self): """Check that we properly ignore masked pixels, and set noGoodPixelsMask where there are no good pixels""" mimgVec = [] DETECTED = afwImage.Mask.getPlaneBitMask("DETECTED") EDGE = afwImage.Mask.getPlaneBitMask("EDGE") INTRP = afwImage.Mask.getPlaneBitMask("INTRP") SAT = afwImage.Mask.getPlaneBitMask("SAT") sctrl = afwMath.StatisticsControl() sctrl.setNanSafe(False) sctrl.setAndMask(INTRP | SAT) sctrl.setNoGoodPixelsMask(EDGE) # set these pixels to EDGE edgeBBox = afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(20, 20)) width, height = 512, 512 dim = afwGeom.Extent2I(width, height) val, maskVal = 10, DETECTED for i in range(4): mimg = afwImage.MaskedImageF(dim) mimg.set(val, maskVal, 1) # # Set part of the image to NaN (with the INTRP bit set) # llc = afwGeom.Point2I(width // 2 * (i // 2), height // 2 * (i % 2)) bbox = afwGeom.Box2I(llc, dim // 2) smimg = mimg.Factory(mimg, bbox, afwImage.LOCAL) del smimg # # And the bottom corner to SAT # smask = mimg.getMask().Factory(mimg.getMask(), edgeBBox, afwImage.LOCAL) smask |= SAT del smask mimgVec.append(mimg) if display > 1: ds9.mtv(mimg, frame=i, title=str(i)) mimgStack = afwMath.statisticsStack(mimgVec, afwMath.MEAN, sctrl) if display: i += 1 ds9.mtv(mimgStack, frame=i, title="Stack") i += 1 ds9.mtv(mimgStack.getVariance(), frame=i, title="var(Stack)") # # Check the output, ignoring EDGE pixels # sctrl = afwMath.StatisticsControl() sctrl.setAndMask(afwImage.Mask.getPlaneBitMask("EDGE")) stats = afwMath.makeStatistics(mimgStack, afwMath.MIN | afwMath.MAX, sctrl) self.assertEqual(stats.getValue(afwMath.MIN), val) self.assertEqual(stats.getValue(afwMath.MAX), val) # # We have to clear EDGE in the known bad corner to check the mask # smask = mimgStack.getMask().Factory(mimgStack.getMask(), edgeBBox, afwImage.LOCAL) self.assertEqual(smask.get(edgeBBox.getMinX(), edgeBBox.getMinY()), EDGE) smask &= ~EDGE del smask self.assertEqual( afwMath.makeStatistics(mimgStack.getMask(), afwMath.SUM, sctrl).getValue(), maskVal)
def setUp(self): boxCorner = afwGeom.Point2I(11, 50) boxExtent = afwGeom.Extent2I(100, 99) self.bbox = afwGeom.Box2I(boxCorner, boxExtent) self.xy0 = afwGeom.Point2I(100, 251) self.kernel = self.makeKernel()
def testClipped(self): """Test that we set mask bits when pixels are clipped""" box = afwGeom.Box2I(afwGeom.Point2I(12345, 67890), afwGeom.Extent2I(3, 3)) num = 10 maskVal = 0xAD value = 0.0 images = [afwImage.MaskedImageF(box) for _ in range(num)] statsCtrl = afwMath.StatisticsControl() statsCtrl.setAndMask(maskVal) clipped = 1 << afwImage.Mask().addMaskPlane("CLIPPED") # No clipping: check that vanilla is working for img in images: img.getImage().set(value) img.getMask().set(0) stack = afwMath.statisticsStack(images, afwMath.MEANCLIP, clipped=clipped) self.assertFloatsAlmostEqual(stack.getImage().getArray(), 0.0, atol=0.0) self.assertFloatsAlmostEqual(stack.getMask().getArray(), 0, atol=0.0) # Not floats, but that's OK # Clip a pixel; the CLIPPED bit should be set images[0].getImage().set(1, 1, value + 1.0) stack = afwMath.statisticsStack(images, afwMath.MEANCLIP, clipped=clipped) self.assertFloatsAlmostEqual(stack.getImage().getArray(), 0.0, atol=0.0) self.assertEqual(stack.getMask().get(1, 1), clipped) # Mask a pixel; the CLIPPED bit should be set images[0].getMask().set(1, 1, maskVal) stack = afwMath.statisticsStack(images, afwMath.MEAN, statsCtrl, clipped=clipped) self.assertFloatsAlmostEqual(stack.getImage().getArray(), 0.0, atol=0.0) self.assertEqual(stack.getMask().get(1, 1), clipped) # Excuse that mask; the CLIPPED bit should not be set stack = afwMath.statisticsStack(images, afwMath.MEAN, statsCtrl, clipped=clipped, excuse=maskVal) self.assertFloatsAlmostEqual(stack.getImage().getArray(), 0.0, atol=0.0) self.assertEqual(stack.getMask().get(1, 1), 0) # Map that mask value to a different one. rejected = 1 << afwImage.Mask().addMaskPlane("REJECTED") maskMap = [(maskVal, rejected)] images[0].getMask().set(1, 1, 0) # only want to clip, not mask, this one images[1].getMask().set( 1, 2, maskVal) # only want to mask, not clip, this one stack = afwMath.statisticsStack(images, afwMath.MEANCLIP, statsCtrl, wvector=[], clipped=clipped, maskMap=maskMap) self.assertFloatsAlmostEqual(stack.getImage().getArray(), 0.0, atol=0.0) self.assertEqual(stack.getMask().get(1, 1), clipped) self.assertEqual(stack.getMask().get(1, 2), rejected)
def testPixelFlags(self): width, height = 100, 100 mi = afwImage.MaskedImageF(width, height) exp = afwImage.makeExposure(mi) mi.getImage().set(0) mask = mi.getMask() sat = mask.getPlaneBitMask('SAT') interp = mask.getPlaneBitMask('INTRP') edge = mask.getPlaneBitMask('EDGE') bad = mask.getPlaneBitMask('BAD') mask.set(0) mask.set(20, 20, sat) mask.set(60, 60, interp) mask.set(40, 20, bad) mask.Factory( mask, afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(3, height))).set(edge) x0, y0 = 1234, 5678 exp.setXY0(afwGeom.Point2I(x0, y0)) control = measAlg.PixelFlagControl() schema = afwTable.SourceTable.makeMinimalSchema() mp = measAlg.MeasureSourcesBuilder().addAlgorithm(control).build( schema) table = afwTable.SourceTable.make(schema) allFlags = [ "flags.pixel.edge", "flags.pixel.bad", "flags.pixel.saturated.center", "flags.pixel.saturated.any", "flags.pixel.interpolated.center", "flags.pixel.interpolated.any", ] for x, y, setFlags in [ (1, 50, ["flags.pixel.edge"]), (40, 20, ["flags.pixel.bad"]), (20, 20, ["flags.pixel.saturated.center", "flags.pixel.saturated.any"]), (20, 22, ["flags.pixel.saturated.any"]), (60, 60, [ "flags.pixel.interpolated.center", "flags.pixel.interpolated.any" ]), (60, 62, ["flags.pixel.interpolated.any"]), (float("NAN"), 50, ["flags.pixel.edge"]), ]: source = table.makeRecord() foot = afwDetection.Footprint( afwGeom.Point2I(afwGeom.Point2D(x + x0, y + y0)), 5) source.setFootprint(foot) mp.apply(source, exp, afwGeom.Point2D(x + x0, y + y0)) for flag in allFlags: value = source.get(flag) if flag in setFlags: self.assertTrue( value, "Flag %s should be set for %f,%f" % (flag, x, y)) else: self.assertFalse( value, "Flag %s should not be set for %f,%f" % (flag, x, y))
def tst(): afwImage.ImageF( self.image1, afwGeom.Box2I(afwGeom.Point2I(1, -1), afwGeom.Extent2I(10, 5)), afwImage.LOCAL )
def testCcd(self): """Test if we can build a Ccd out of Amps""" #print >> sys.stderr, "Skipping testCcd"; return ccdId = cameraGeom.Id("CCD") ccdInfo = {"ampSerial" : CameraGeomTestCase.ampSerial} ccd = cameraGeomUtils.makeCcd(self.geomPolicy, ccdId, ccdInfo=ccdInfo) if display: cameraGeomUtils.showCcd(ccd) ds9.incrDefaultFrame() trimmedImage = cameraGeomUtils.makeImageFromCcd(ccd, isTrimmed=True) cameraGeomUtils.showCcd(ccd, trimmedImage, isTrimmed=True) ds9.incrDefaultFrame() for i in range(2): self.assertEqual(ccd.getSize().getMm()[i], ccdInfo["pixelSize"]*ccd.getAllPixels(True).getDimensions()[i]) self.assertEqual(ccd.getId().getName(), ccdInfo["name"]) self.assertEqual(ccd.getAllPixels().getWidth(), ccdInfo["width"]) self.assertEqual(ccd.getAllPixels().getHeight(), ccdInfo["height"]) self.assertEqual([a.getId().getSerial() for a in ccd], range(ccdInfo["ampIdMin"], ccdInfo["ampIdMax"] + 1)) id = cameraGeom.Id("ID%d" % ccdInfo["ampIdMax"]) self.assertTrue(ccd.findAmp(id), id) self.assertEqual(ccd.findAmp(afwGeom.Point2I(10, 10)).getId().getSerial(), ccdInfo["ampIdMin"]) self.assertEqual(ccd.getAllPixels().getMin(), ccd.findAmp(afwGeom.Point2I(10, 10)).getAllPixels().getMin()) self.assertEqual(ccd.getAllPixels().getMax(), ccd.findAmp(afwGeom.Point2I(ccdInfo["width"] - 1, ccdInfo["height"] - 1)).getAllPixels().getMax()) ps = ccd.getPixelSize() # # Test mapping pixel <--> mm. Use a pixel at the middle of the top of the CCD # pix = afwGeom.Point2D(99.5, 203.5) # wrt bottom left pos = cameraGeom.FpPoint(0.00, 1.02) # pixel center wrt CCD center posll = cameraGeom.FpPoint(0.00, 1.02) # llc of pixel wrt CCD center # # Map pix into untrimmed coordinates # amp = ccd.findAmp(afwGeom.Point2I(int(pix[0]), int(pix[1]))) corrI = amp.getDataSec(False).getMin() - amp.getDataSec(True).getMin() corr = afwGeom.Extent2D(corrI.getX(), corrI.getY()) pix += corr self.assertEqual(amp.getDiskCoordSys(), cameraGeom.Amp.AMP) self.assertEqual(ccd.getPixelFromPosition(pos) + corr, pix) # # Trim the CCD and try again # trimmedImage = trimCcd(ccd) if display: ds9.mtv(trimmedImage, title='Trimmed') cameraGeomUtils.showCcd(ccd, trimmedImage) ds9.incrDefaultFrame() a = ccd.findAmp(cameraGeom.Id("ID%d" % ccdInfo["ampIdMin"])) self.assertEqual(a.getDataSec(), afwGeom.Box2I(afwGeom.Point2I(0, 0), afwGeom.Extent2I(ccdInfo["ampWidth"], ccdInfo["ampHeight"]))) self.assertEqual(ccd.getSize().getMm()[0], ccdInfo["pixelSize"]*ccdInfo["trimmedWidth"]) self.assertEqual(ccd.getSize().getMm()[1], ccdInfo["pixelSize"]*ccdInfo["trimmedHeight"]) # # Test mapping pixel <--> mm # pix = afwGeom.Point2D(99.5, 203.5) # wrt bottom left pos = cameraGeom.FpPoint(0.00, 1.02) # pixel center wrt CCD center posll = cameraGeom.FpPoint(0.00, 1.02) # llc of pixel wrt CCD center self.assertEqual(ccd.getPixelFromPosition(pos), pix) self.assertEqual(ccd.getPositionFromPixel(pix).getMm(), posll.getMm())
def rotateBBoxBy90(bbox, n90, dimensions): """!Rotate a bounding box by an integer multiple of 90 degrees @todo document dimensions better; what does it specify? @param bbox bbox to rotate @param n90 number of quarter rotations to perform @param dimensions dimensions of the parent grid @return rotated bounding box """ while n90 < 0: n90 += 4 n90 %= 4 # sin/cos of the rotation angle s = 0 c = 0 if n90 == 0: s = 0 c = 1 elif n90 == 1: s = 1 c = 0 elif n90 == 2: s = 0 c = -1 elif n90 == 3: s = -1 c = 0 else: raise ValueError("n90 must be an integer") centerPixel = afwGeom.Point2I(int(dimensions[0]/2), int(dimensions[1]/2)) xCorner = numpy.array([(corner.getX() - centerPixel[0]) for corner in bbox.getCorners()]) yCorner = numpy.array([(corner.getY() - centerPixel[1]) for corner in bbox.getCorners()]) x0 = int((c*xCorner - s*yCorner).min()) y0 = int((s*xCorner + c*yCorner).min()) x1 = int((c*xCorner - s*yCorner).max()) y1 = int((s*xCorner + c*yCorner).max()) # Fiddle things a little if the detector has an even number of pixels so that square BBoxes # will map into themselves if n90 == 1: if dimensions[0]%2 == 0: x0 -= 1 x1 -= 1 elif n90 == 2: if dimensions[0]%2 == 0: x0 -= 1 x1 -= 1 if dimensions[1]%2 == 0: y0 -= 1 y1 -= 1 elif n90 == 3: if dimensions[1]%2 == 0: y0 -= 1 y1 -= 1 LLC = afwGeom.Point2I(centerPixel[0] + x0, centerPixel[1] + y0) URC = afwGeom.Point2I(centerPixel[0] + x1, centerPixel[1] + y1) newBbox = afwGeom.Box2I(LLC, URC) dxy0 = centerPixel[0] - centerPixel[1] if n90%2 == 1 and not dxy0 == 0: newBbox.shift(afwGeom.Extent2I(-dxy0, dxy0)) return newBbox