def testObjective(self): eps = 1E-6 ctrl = ms.FitPsfControl() image = lsst.afw.image.ImageD(5, 5) nParameters = 3 nData = image.getBBox().getArea() nTests = 10 center = geom.Point2D(2.0, 2.0) xGrid, yGrid = numpy.meshgrid(numpy.arange(-2, 3), numpy.arange(-2, 3)) image.getArray()[:, :] = 1.0 * numpy.exp(-0.5 * (xGrid**2 + yGrid**2)) image.getArray()[:, :] += numpy.random.randn(5, 5) * 0.1 inputs = ms.ModelInputHandler(image, center, image.getBBox(lsst.afw.image.PARENT)) obj = ms.FitPsfAlgorithm.makeObjective(ctrl, inputs) parameters = numpy.random.rand(nTests, nParameters) * 0.5 for i in range(nTests): f0 = numpy.zeros(nData, dtype=float) obj.computeFunction(parameters[i, :], f0) f1 = obj.getModel() * obj.getAmplitude() - inputs.getData() model = ms.FitPsfModel(ctrl, obj.getAmplitude(), parameters[i, :]) self.assertClose( model.outer[0], ctrl.peakRatio * model.inner[0] * ctrl.radiusRatio**2) self.assertEqual(model.radiusRatio, ctrl.radiusRatio) image2 = lsst.afw.image.ImageD(5, 5) multiShapeletFunc = model.asMultiShapelet(center) multiShapeletFunc.evaluate().addToImage(image2) f2 = (image2.getArray().ravel() - inputs.getData()) multiGaussian = model.getMultiGaussian() builder1 = ms.GaussianModelBuilder(inputs.getX(), inputs.getY(), multiGaussian[0].flux, multiGaussian[0].radius) builder2 = ms.GaussianModelBuilder(inputs.getX(), inputs.getY(), multiGaussian[1].flux, multiGaussian[1].radius) builder1.update(model.ellipse) builder2.update(model.ellipse) f3 = builder1.getModel() + builder2.getModel() - inputs.getData() self.assertClose(f0, f1) self.assertClose(f0, f2) self.assertClose(f0, f3) d0 = numpy.zeros((nParameters, nData), dtype=float).transpose() d1 = numpy.zeros((nParameters, nData), dtype=float).transpose() obj.computeDerivative(parameters[i, :], f0, d0) for j in range(nParameters): parameters[i, j] += eps f1a = numpy.zeros(nData, dtype=float) obj.computeFunction(parameters[i, :], f1a) parameters[i, j] -= 2.0 * eps f1b = numpy.zeros(nData, dtype=float) obj.computeFunction(parameters[i, :], f1b) d1[:, j] = (f1a - f1b) / (2.0 * eps) parameters[i, j] += eps self.assertClose(d0, d1, rtol=1E-10, atol=1E-8)
def testResized(self): for pad in [0, -10, 10]: newLen = self.kernelSize - pad resizedPsf = self.psf.resized(newLen, newLen) newBBox = resizedPsf.computeBBox(resizedPsf.getAveragePosition()) self.assertEqual(newBBox.getWidth(), newLen) self.assertEqual(newBBox.getHeight(), newLen) self.assertEqual(resizedPsf.getSigma(), self.psf.getSigma()) image = resizedPsf.computeKernelImage(resizedPsf.getAveragePosition()) check = makeGaussianImage(newBBox, self.psf.getSigma()) self.assertFloatsAlmostEqual(image.getArray(), check.getArray()) # tolerance same as in self.testKernelImage self.assertFloatsAlmostEqual(image.getArray().sum(), 1.0, atol=1E-14)
def testObjective(self): eps = 1E-6 ctrl = ms.FitPsfControl() image = lsst.afw.image.ImageD(5, 5) nParameters = 3 nData = image.getBBox().getArea() nTests = 10 center = geom.Point2D(2.0, 2.0) xGrid, yGrid = numpy.meshgrid(numpy.arange(-2, 3), numpy.arange(-2, 3)) image.getArray()[:,:] = 1.0 * numpy.exp(-0.5*(xGrid**2 + yGrid**2)) image.getArray()[:,:] += numpy.random.randn(5, 5) * 0.1 inputs = ms.ModelInputHandler(image, center, image.getBBox()) obj = ms.FitPsfAlgorithm.makeObjective(ctrl, inputs) parameters = numpy.random.rand(nTests, nParameters) * 0.5 for i in range(nTests): f0 = numpy.zeros(nData, dtype=float) obj.computeFunction(parameters[i,:], f0) f1 = obj.getModel() * obj.getAmplitude() - inputs.getData() model = ms.FitPsfModel(ctrl, obj.getAmplitude(), parameters[i,:]) self.assertClose(model.outer[0], ctrl.peakRatio * model.inner[0] * ctrl.radiusRatio**2) self.assertEqual(model.radiusRatio, ctrl.radiusRatio) image2 = lsst.afw.image.ImageD(5, 5) multiShapeletFunc = model.asMultiShapelet(center) multiShapeletFunc.evaluate().addToImage(image2) f2 = (image2.getArray().ravel() - inputs.getData()) multiGaussian = model.getMultiGaussian() builder1 = ms.GaussianModelBuilder(inputs.getX(), inputs.getY(), multiGaussian[0].flux, multiGaussian[0].radius) builder2 = ms.GaussianModelBuilder(inputs.getX(), inputs.getY(), multiGaussian[1].flux, multiGaussian[1].radius) builder1.update(model.ellipse) builder2.update(model.ellipse) f3 = builder1.getModel() + builder2.getModel() - inputs.getData() self.assertClose(f0, f1) self.assertClose(f0, f2) self.assertClose(f0, f3) d0 = numpy.zeros((nParameters, nData), dtype=float).transpose() d1 = numpy.zeros((nParameters, nData), dtype=float).transpose() obj.computeDerivative(parameters[i,:], f0, d0) for j in range(nParameters): parameters[i,j] += eps f1a = numpy.zeros(nData, dtype=float) obj.computeFunction(parameters[i,:], f1a) parameters[i,j] -= 2.0*eps f1b = numpy.zeros(nData, dtype=float) obj.computeFunction(parameters[i,:], f1b) d1[:,j] = (f1a - f1b) / (2.0 * eps) parameters[i,j] += eps self.assertClose(d0, d1, rtol=1E-10, atol=1E-8)
def testAddToImage(self): bbox = geom.Box2I(geom.Point2I(5, 6), geom.Extent2I(20, 30)) image = lsst.afw.image.ImageD(bbox) x = numpy.arange(bbox.getBeginX(), bbox.getEndX(), dtype=float) y = numpy.arange(bbox.getBeginY(), bbox.getEndY(), dtype=float) array = numpy.zeros((bbox.getHeight(), bbox.getWidth()), dtype=float) for f in self.functions: image.getArray()[:] = 0.0 array[:] = 0.0 ev = f.evaluate() ev.addToImage(image) ev.addToImage(array, bbox.getMin()) check = self.makeImage(f, x, y) self.assertClose(image.getArray(), check) self.assertClose(array, check)
def _plotImage(image, title=None, ellipses=(), vmin=None, vmax=None): bbox = image.getBBox() array = image.getArray() if vmin is None or vmax is None: valid = array[numpy.isfinite(array)] if vmin is None: vmin = valid.min() - 0.1 * (valid.max() - valid.min()) if vmax is None: vmax = valid.max() + 0.1 * (valid.max() - valid.min()) pyplot.imshow(array, interpolation='nearest', origin='lower', vmin=vmin, vmax=vmax, extent=(bbox.getMinX() - 0.5, bbox.getMaxX() + 0.5, bbox.getMinY() - 0.5, bbox.getMaxY() + 0.5)) if title is not None: pyplot.title(title) for ellipse in ellipses: ellipse.plot(fill=False, rescale=False) pyplot.plot([ellipse.getCenter().getX()], [ellipse.getCenter().getY()], 'kx', scalex=False, scaley=False) return vmin, vmax
def testImage(self): """Test Polygon.createImage""" if display: import lsst.afw.display as afwDisplay for i, num in enumerate(range(3, 30)): # We need to test at different centers, because the # boost::intersection algorithm depends sensitively on # input floating-point values, and you will get different # aliasing depending on the central pixel value when # generating the polygon from numpy values. for cent in [-75, -50, -25, 0, 25, 50, 75]: poly = self.polygon(num, 25, cent, cent) box = lsst.geom.Box2I(lsst.geom.Point2I(cent - 60, cent - 60), lsst.geom.Extent2I(115, 115)) image = poly.createImage(box) if display: disp = afwDisplay.Display(frame=i + 1) disp.mtv(image, title=f"Polygon nside={num}") for p1, p2 in poly.getEdges(): disp.line((p1, p2)) # Some computations of the image area have such large aliasing # in boost::intersection that the precision required here is 0.025. self.assertFloatsAlmostEqual(image.getArray().sum(), poly.calculateArea(), rtol=0.025)
def buildExposure(self, dataRef): image = dataRef.get("image", immediate=True) exposure = lsst.afw.image.ExposureF(image.getBBox(lsst.afw.image.PARENT)) exposure.getMaskedImage().getImage().getArray()[:,:] = image.getArray() exposure.getMaskedImage().getVariance().set(self.computeVariance(image)) exposure.setPsf(self.psf.run(dataRef)) return exposure
def addSource(self, flux, centroid, shape=None): """! Add a source to the simulation @param[in] flux Total flux of the source to be added. @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). @param[in] shape 2nd moments of the source before PSF convolution (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog records post-convolution moments). If None, a point source will be added. @return a truth catalog record and single-source image corresponding to the new source. """ # Create and set the truth catalog fields record = self.catalog.addNew() record.set(self.keys["flux"], flux) record.set(self.keys["centroid"], centroid) if shape is None: record.set(self.keys["isStar"], True) fullShape = self.psfShape else: record.set(self.keys["isStar"], False) fullShape = shape.convolve(self.psfShape) record.set(self.keys["shape"], fullShape) # Create an image containing just this source image = self.drawGaussian(self.exposure.getBBox(), flux, lsst.afw.geom.ellipses.Ellipse(fullShape, centroid)) # Generate a footprint for this source self._installFootprint(record, image) # Actually add the source to the full exposure self.exposure.getMaskedImage().getImage().getArray()[:,:] += image.getArray() return record, image
def testFitMoments(self): """Test that fitMoments() preserves peakRatio and radiusRatio while setting moments correctly. """ MOMENTS_RTOL = 1E-13 image = self.psf.computeKernelImage() array = image.getArray() bbox = image.getBBox() x, y = numpy.meshgrid( numpy.arange(bbox.getBeginX(), bbox.getEndX()), numpy.arange(bbox.getBeginY(), bbox.getEndY()) ) msf = self.Algorithm.initializeResult(self.ctrl) self.Algorithm.fitMoments(msf, self.ctrl, image) self.assertFloatsAlmostEqual(msf.evaluate().integrate(), array.sum(), rtol=MOMENTS_RTOL) moments = msf.evaluate().computeMoments() q = lsst.afw.geom.ellipses.Quadrupole(moments.getCore()) cx = (x*array).sum()/array.sum() cy = (y*array).sum()/array.sum() self.assertFloatsAlmostEqual(moments.getCenter().getX(), cx, rtol=MOMENTS_RTOL) self.assertFloatsAlmostEqual(moments.getCenter().getY(), cy, rtol=MOMENTS_RTOL) self.assertFloatsAlmostEqual(q.getIxx(), ((x - cx)**2 * array).sum()/array.sum(), rtol=MOMENTS_RTOL) self.assertFloatsAlmostEqual(q.getIyy(), ((y - cy)**2 * array).sum()/array.sum(), rtol=MOMENTS_RTOL) self.assertFloatsAlmostEqual(q.getIxy(), ((x - cx)*(y - cy)*array).sum()/array.sum(), rtol=MOMENTS_RTOL) self.assertEqual(len(msf.getComponents()), 2) self.checkRatios(msf) self.checkBounds(msf)
def drawGaussian(bbox, instFlux, ellipse): """Create an image of an elliptical Gaussian. Parameters ---------- bbox : `lsst.geom.Box2I` or `lsst.geom.Box2D` Bounding box of image to create. instFlux : `float` Total instrumental flux of the Gaussian (normalized analytically, not using pixel values). ellipse : `lsst.afw.geom.Ellipse` Defines the centroid and shape. Returns ------- image : `lsst.afw.image.ImageF` An image of the Gaussian. """ x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()), np.arange(bbox.getBeginY(), bbox.getEndY())) t = ellipse.getGridTransform() xt = t[t.XX] * x + t[t.XY] * y + t[t.X] yt = t[t.YX] * x + t[t.YY] * y + t[t.Y] image = lsst.afw.image.ImageF(bbox) image.getArray()[:, :] = np.exp( -0.5 * (xt**2 + yt**2)) * instFlux / (2.0 * ellipse.getCore().getArea()) return image
def makeGaussianImage(bbox, sigma, xc=0.0, yc=0.0): image = lsst.afw.image.ImageD(bbox) array = image.getArray() for yi, yv in enumerate(range(bbox.getBeginY(), bbox.getEndY())): for xi, xv in enumerate(range(bbox.getBeginX(), bbox.getEndX())): array[yi, xi] = np.exp(-0.5*((xv - xc)**2 + (yv - yc)**2)/sigma**2) array /= array.sum() return image
def asSamples(image): bbox = image.getBBox(lsst.afw.image.PARENT) x, y = numpy.meshgrid(numpy.arange(bbox.getBeginX(), bbox.getEndX()), numpy.arange(bbox.getBeginY(), bbox.getEndY())) w = image.getArray().flatten() s = numpy.zeros((w.size, 2), dtype=float) s[:,0] = x.flatten() s[:,1] = y.flatten() return s, w
def computeNaiveApertureFlux(image, radius, xc=0.0, yc=0.0): bbox = image.getBBox() array = image.getArray() s = 0.0 for yi, yv in enumerate(range(bbox.getBeginY(), bbox.getEndY())): for xi, xv in enumerate(range(bbox.getBeginX(), bbox.getEndX())): if (xv - xc)**2 + (yv - yc)**2 < radius**2: s += array[yi, xi] return s
def testObjective(self): """Test that model evaluation agrees with derivative evaluation in the objective object. """ image = self.psf.computeKernelImage() msf = self.Algorithm.initializeResult(self.ctrl) self.Algorithm.fitMoments(msf, self.ctrl, image) moments = msf.evaluate().computeMoments() r0 = moments.getCore().getDeterminantRadius() objective = self.Algorithm.makeObjective(moments, self.ctrl, image) image, model = self.makeImages(msf) parameters = numpy.zeros(4, dtype=float) parameters[0] = msf.getComponents()[0].getCoefficients()[0] parameters[1] = msf.getComponents()[1].getCoefficients()[0] parameters[2] = msf.getComponents()[0].getEllipse().getCore( ).getDeterminantRadius() / r0 parameters[3] = msf.getComponents()[1].getEllipse().getCore( ).getDeterminantRadius() / r0 residuals = numpy.zeros(image.getArray().size, dtype=float) objective.computeResiduals(parameters, residuals) return msf self.assertFloatsAlmostEqual( residuals.reshape(image.getHeight(), image.getWidth()), image.getArray() - model.getArray()) step = 1E-6 derivatives = numpy.zeros((parameters.size, residuals.size), dtype=float).transpose() objective.differentiateResiduals(parameters, derivatives) for i in range(parameters.size): original = parameters[i] r1 = numpy.zeros(residuals.size, dtype=float) r2 = numpy.zeros(residuals.size, dtype=float) parameters[i] = original + step objective.computeResiduals(parameters, r1) parameters[i] = original - step objective.computeResiduals(parameters, r2) parameters[i] = original d = (r1 - r2) / (2.0 * step) self.assertFloatsAlmostEqual(d.reshape(image.getHeight(), image.getWidth()), derivatives[:, i].reshape( image.getHeight(), image.getWidth()), atol=1E-11)
def fitFull(image, orders, radii=None, p0=None, penalty=0.01): bbox = image.getBBox(lsst.afw.image.PARENT) x, y = numpy.meshgrid(numpy.arange(bbox.getBeginX(), bbox.getEndX(), dtype=float), numpy.arange(bbox.getBeginY(), bbox.getEndY(), dtype=float)) basisOffsets = numpy.cumsum([0] + [lsst.shapelet.computeSize(order) for order in orders]) matrix = numpy.zeros((basisOffsets[-1], bbox.getArea()), dtype=float).transpose() builder = lsst.shapelet.ModelBuilderD(x.flatten(), y.flatten()) data = image.getArray().flatten() if penalty is not None: penalty = numpy.identity(basisOffsets[-1], dtype=float) * penalty**2 for i, order in enumerate(orders): penalty[basisOffsets[i], basisOffsets[i]] = 0.0 if p0 is None: nRadii = len(radii) unitcircle = lsst.afw.geom.ellipses.Ellipse( lsst.afw.geom.ellipses.SeparableConformalShearLogTraceRadius(), lsst.afw.geom.Point2D() ) p0 = numpy.zeros(nRadii*5, dtype=float) for i, radius in enumerate(radii): ellipse = lsst.afw.geom.ellipses.Ellipse(unitcircle) ellipse.scale(radius) p0[i*5:(i+1)*5] = ellipse.getParameterVector() def solve(p, *args): matrix[:] = 0.0 for i, order in enumerate(orders): ellipse.setParameterVector(p[i*5:(i+1)*5]) builder.update(ellipse) builder.addModelMatrix(order, matrix[:,basisOffsets[i]:basisOffsets[i+1]]) f = numpy.dot(matrix.transpose(), matrix) g = numpy.dot(matrix.transpose(), data) if penalty is not None: f += penalty c, _, _, _ = numpy.linalg.lstsq(f, g) return c def func(p, *args): c = solve(p) r = numpy.dot(matrix, c) r -= data return r p1, flags = scipy.optimize.leastsq(func, p0, maxfev=10000) c1 = solve(p1) msf = lsst.shapelet.MultiShapeletFunction() for i, order in enumerate(orders): ellipse.setParameterVector(p1[i*5:(i+1)*5]) sf = lsst.shapelet.ShapeletFunction(order, lsst.shapelet.HERMITE, ellipse, c1[basisOffsets[i]:basisOffsets[i+1]]) msf.getElements().append(sf) return msf, func(p0)
def makeImage(self, ImageClass): """Create an image Parameters ---------- ImageClass : `type`, an `lsst.afw.image.Image` class Class of image to create. Returns ------- image : `ImageClass` The created image. """ image = ImageClass(self.bbox) rng = np.random.RandomState(12345) dtype = image.getArray().dtype noise = rng.normal(0.0, self.noise, image.getArray().shape).astype(dtype) image.getArray()[:] = np.array(self.background, dtype=dtype) + noise return image
def computeVariance(self, image): array = image.getArray() n = array.shape[0] / 100 assert n * 100 == array.shape[0] mask = numpy.zeros(array.shape, dtype=bool) for i in range(self.config.varianceBorderWidth): mask[i::n,:] = True mask[:,i::n] = True mask[n-i::n,:] = True mask[:,n-i::n] = True borderPixels = array[mask] return numpy.std(borderPixels, dtype=numpy.float64)**2
def checkSubtracted(self, exposure): """Check that the subtracted image is as expected Parameters ---------- exposure : `lsst.afw.image.Exposure` Crosstalk-subtracted exposure. """ image = exposure.getMaskedImage().getImage() mask = exposure.getMaskedImage().getMask() self.assertFloatsAlmostEqual(image.getArray(), self.corrected.getArray(), atol=2.0e-2) self.assertIn(self.crosstalkStr, mask.getMaskPlaneDict()) self.assertGreater((mask.getArray() & mask.getPlaneBitMask(self.crosstalkStr) > 0).sum(), 0)
def testObjective(self): """Test that model evaluation agrees with derivative evaluation in the objective object. """ image = self.psf.computeKernelImage() msf = self.Algorithm.initializeResult(self.ctrl) self.Algorithm.fitMoments(msf, self.ctrl, image) moments = msf.evaluate().computeMoments() r0 = moments.getCore().getDeterminantRadius() objective = self.Algorithm.makeObjective(moments, self.ctrl, image) image, model = self.makeImages(msf) parameters = numpy.zeros(4, dtype=float) parameters[0] = msf.getComponents()[0].getCoefficients()[0] parameters[1] = msf.getComponents()[1].getCoefficients()[0] parameters[2] = msf.getComponents()[0].getEllipse().getCore().getDeterminantRadius() / r0 parameters[3] = msf.getComponents()[1].getEllipse().getCore().getDeterminantRadius() / r0 residuals = numpy.zeros(image.getArray().size, dtype=float) objective.computeResiduals(parameters, residuals) self.assertFloatsAlmostEqual( residuals.reshape(image.getHeight(), image.getWidth()), image.getArray() - model.getArray() ) step = 1E-6 derivatives = numpy.zeros((parameters.size, residuals.size), dtype=float).transpose() objective.differentiateResiduals(parameters, derivatives) for i in range(parameters.size): original = parameters[i] r1 = numpy.zeros(residuals.size, dtype=float) r2 = numpy.zeros(residuals.size, dtype=float) parameters[i] = original + step objective.computeResiduals(parameters, r1) parameters[i] = original - step objective.computeResiduals(parameters, r2) parameters[i] = original d = (r1 - r2)/(2.0*step) self.assertFloatsAlmostEqual( d.reshape(image.getHeight(), image.getWidth()), derivatives[:, i].reshape(image.getHeight(), image.getWidth()), atol=1E-11 )
def checkAstropy(image, filename, hduNum=0): """Check that astropy can read our file We don't insist on equality for low BITPIX (8, 16) when the original type is double-precision: astropy will (quite reasonably) read that into a single-precision image and apply bscale,bzero to that so it's going to be subject to roundoff. Parameters ---------- image : `lsst.afw.image.Image` Image read by our own code. filename : `str` Filename of FITS file to read with astropy. hduNum : `int` HDU number of interest. """ print("Astropy currently doesn't read our compressed images perfectly.") return def parseVersion(version): return tuple(int(vv) for vv in np.array(version.split("."))) if parseVersion(astropy.__version__) <= parseVersion("2.0.1"): # astropy 2.0.1 and earlier have problems: # * Doesn't support GZIP_2: https://github.com/astropy/astropy/pull/6486 # * Uses the wrong array type: https://github.com/astropy/astropy/pull/6492 print( f"Refusing to check with astropy version {astropy.__version__} due to astropy bugs" ) return hdu = astropy.io.fits.open(filename)[hduNum] if hdu.header["BITPIX"] in (8, 16) and isinstance(image, lsst.afw.image.ImageD): return dtype = image.getArray().dtype theirs = hdu.data.astype(dtype) # Allow for minor differences due to arithmetic: +/- 1 in the last place np.testing.assert_array_max_ulp(theirs, image.getArray())
def construct(imageList): image = lsst.afw.image.ImageF(2*width, 2*height) image.getArray()[:height, :width] = imageList[0].getArray() image.getArray()[:height, width:] = imageList[1].getArray()[:, ::-1] # flip in x image.getArray()[height:, :width] = imageList[2].getArray()[::-1, :] # flip in y image.getArray()[height:, width:] = imageList[3].getArray()[::-1, ::-1] # flip in x and y image.getArray()[:] += self.noise return image
def testImage(self): """Test Polygon.createImage""" for i, num in enumerate(range(3, 30)): poly = self.polygon(num, 25, 75, 75) box = lsst.geom.Box2I(lsst.geom.Point2I(15, 15), lsst.geom.Extent2I(115, 115)) image = poly.createImage(box) if DEBUG: import lsst.afw.display.ds9 as ds9 ds9.mtv(image, frame=i+1, title="Polygon nside=%d" % num) for p1, p2 in poly.getEdges(): ds9.line((p1, p2), frame=i+1) self.assertAlmostEqual( image.getArray().sum()/poly.calculateArea(), 1.0, 6)
def checkCompressedImage(self, ImageClass, image, compression, scaling=None, atol=0.0): """Check that compression works on an image Parameters ---------- ImageClass : `type`, an `lsst.afw.image.Image` class Class of image. image : `lsst.afw.image.Image` Image to compress. compression : `lsst.afw.fits.ImageCompressionOptions` Compression parameters. scaling : `lsst.afw.fits.ImageScalingOptions` or `None` Scaling parameters for lossy compression (optional). atol : `float` Absolute tolerance for comparing unpersisted image. Returns ------- unpersisted : `ImageClass` The unpersisted image. """ with lsst.utils.tests.getTempFilePath(self.extension) as filename: if scaling: options = lsst.afw.fits.ImageWriteOptions(compression, scaling) else: options = lsst.afw.fits.ImageWriteOptions(compression) unpersisted = self.readWriteImage(ImageClass, image, filename, options) fileSize = os.stat(filename).st_size fitsBlockSize = 2880 # All sizes in FITS are a multiple of this numBlocks = 1 + np.ceil( self.bbox.getArea() * image.getArray().dtype.itemsize / fitsBlockSize) uncompressedSize = fitsBlockSize * numBlocks print(ImageClass, compression.algorithm, fileSize, uncompressedSize, fileSize / uncompressedSize) self.assertEqual(image.getBBox(), unpersisted.getBBox()) self.assertImagesAlmostEqual(unpersisted, image, atol=atol) checkAstropy(unpersisted, filename, 1) return unpersisted
def __exit__(self, type_, value, tb): # We're not using the context manager for any kind of exception safety # or guarantees; we just want the nice "with" statement syntax. if type_ is not None: # exception was raised; just skip all this and let it propagate return # On exit, compute and set the truth values for the parent object. self.parentRecord.set(self.owner.keys["nChild"], len(self.children)) # Compute instFlux from sum of component fluxes instFlux = 0.0 for record, image in self.children: instFlux += record.get(self.owner.keys["instFlux"]) self.parentRecord.set(self.owner.keys["instFlux"], instFlux) # Compute centroid from instFlux-weighted mean of component centroids x = 0.0 y = 0.0 for record, image in self.children: w = record.get(self.owner.keys["instFlux"]) / instFlux x += record.get(self.owner.keys["centroid"].getX()) * w y += record.get(self.owner.keys["centroid"].getY()) * w self.parentRecord.set(self.owner.keys["centroid"], lsst.geom.Point2D(x, y)) # Compute shape from instFlux-weighted mean of offset component shapes xx = 0.0 yy = 0.0 xy = 0.0 for record, image in self.children: w = record.get(self.owner.keys["instFlux"]) / instFlux dx = record.get(self.owner.keys["centroid"].getX()) - x dy = record.get(self.owner.keys["centroid"].getY()) - y xx += (record.get(self.owner.keys["shape"].getIxx()) + dx**2) * w yy += (record.get(self.owner.keys["shape"].getIyy()) + dy**2) * w xy += (record.get(self.owner.keys["shape"].getIxy()) + dx * dy) * w self.parentRecord.set(self.owner.keys["shape"], lsst.afw.geom.Quadrupole(xx, yy, xy)) # Run detection on the parent image to get the parent Footprint. self.owner._installFootprint(self.parentRecord, self.parentImage) # Create perfect HeavyFootprints for all children; these will need to # be modified later to account for the noise we'll add to the image. deblend = lsst.afw.image.MaskedImageF( self.owner.exposure.getMaskedImage(), True) for record, image in self.children: deblend.getImage().getArray()[:, :] = image.getArray() heavyFootprint = lsst.afw.detection.HeavyFootprintF( self.parentRecord.getFootprint(), deblend) record.setFootprint(heavyFootprint)
def testImage(self): """Test Polygon.createImage""" if display: import lsst.afw.display as afwDisplay for i, num in enumerate(range(3, 30)): poly = self.polygon(num, 25, 75, 75) box = lsst.geom.Box2I(lsst.geom.Point2I(15, 15), lsst.geom.Extent2I(115, 115)) image = poly.createImage(box) if display: disp = afwDisplay.Display(frame=i + 1) disp.mtv(image, title="Polygon nside=%d" % num) for p1, p2 in poly.getEdges(): disp.line((p1, p2)) self.assertAlmostEqual( image.getArray().sum()/poly.calculateArea(), 1.0, 6)
def testImage(self): """Test Polygon.createImage""" if display: import lsst.afw.display as afwDisplay for i, num in enumerate(range(3, 30)): poly = self.polygon(num, 25, 75, 75) box = lsst.geom.Box2I(lsst.geom.Point2I(15, 15), lsst.geom.Extent2I(115, 115)) image = poly.createImage(box) if display: disp = afwDisplay.Display(frame=i + 1) disp.mtv(image, title=f"Polygon nside={num}") for p1, p2 in poly.getEdges(): disp.line((p1, p2)) self.assertAlmostEqual( image.getArray().sum() / poly.calculateArea(), 1.0, 6)
def drawGaussian(bbox, flux, ellipse): """! Create an image of an elliptical Gaussian. @param[in,out] bbox Bounding box of image to create. @param[in] flux Total flux of the Gaussian (normalized analytically, not using pixel values) @param[in] ellipse lsst.afw.geom.ellipses.Ellipse holding the centroid and shape. """ x, y = numpy.meshgrid(numpy.arange(bbox.getBeginX(), bbox.getEndX()), numpy.arange(bbox.getBeginY(), bbox.getEndY())) t = ellipse.getGridTransform() xt = t[t.XX] * x + t[t.XY] * y + t[t.X] yt = t[t.YX] * x + t[t.YY] * y + t[t.Y] image = lsst.afw.image.ImageF(bbox) image.getArray()[:,:] = numpy.exp(-0.5*(xt**2 + yt**2))*flux/(2.0*ellipse.getCore().getArea()) return image
def addSource(self, instFlux, centroid, shape=None): """Add a source to the simulation. Parameters ---------- instFlux : `float` Total instFlux of the source to be added. centroid : `lsst.geom.Point2D` Position of the source to be added. shape : `lsst.afw.geom.Quadrupole` Second moments of the source before PSF convolution. Note that the truth catalog records post-convolution moments. If `None`, a point source will be added. Returns ------- record : `lsst.afw.table.SourceRecord` A truth catalog record. image : `lsst.afw.image.ImageF` Single-source image corresponding to the new source. """ # Create and set the truth catalog fields record = self.catalog.addNew() record.set(self.keys["instFlux"], instFlux) record.set(self.keys["centroid"], centroid) covariance = np.random.normal(0, 0.1, 4).reshape(2, 2) covariance[0, 1] = covariance[ 1, 0] # CovarianceMatrixKey assumes symmetric x_y_Cov record.set(self.keys["centroid_sigma"], covariance.astype(np.float32)) if shape is None: record.set(self.keys["isStar"], True) fullShape = self.psfShape else: record.set(self.keys["isStar"], False) fullShape = shape.convolve(self.psfShape) record.set(self.keys["shape"], fullShape) # Create an image containing just this source image = self.drawGaussian(self.exposure.getBBox(), instFlux, lsst.afw.geom.Ellipse(fullShape, centroid)) # Generate a footprint for this source self._installFootprint(record, image) # Actually add the source to the full exposure self.exposure.getMaskedImage().getImage().getArray( )[:, :] += image.getArray() return record, image
def _plotImage(image, title=None, ellipses=(), vmin=None, vmax=None): bbox = image.getBBox() array = image.getArray() if vmin is None or vmax is None: valid = array[numpy.isfinite(array)] if vmin is None: vmin = valid.min() - 0.1 * (valid.max() - valid.min()) if vmax is None: vmax = valid.max() + 0.1 * (valid.max() - valid.min()) pyplot.imshow(array, interpolation='nearest', origin='lower', vmin=vmin, vmax=vmax, extent=(bbox.getMinX()-0.5, bbox.getMaxX()+0.5, bbox.getMinY()-0.5, bbox.getMaxY()+0.5) ) if title is not None: pyplot.title(title) for ellipse in ellipses: ellipse.plot(fill=False, rescale=False) pyplot.plot([ellipse.getCenter().getX()], [ellipse.getCenter().getY()], 'kx', scalex=False, scaley=False) return vmin, vmax
def __exit__(self, type_, value, tb): # We're not using the context manager for any kind of exception safety or guarantees; # we just want the nice "with" statement syntax. if type_ is not None: # exception was raised; just skip all this and let it propagate return # On exit, we need to compute and set the truth values for the parent object. self.parentRecord.set(self.owner.keys["nChild"], len(self.children)) # Compute flux from sum of component fluxes flux = 0.0 for record, image in self.children: flux += record.get(self.owner.keys["flux"]) self.parentRecord.set(self.owner.keys["flux"], flux) # Compute centroid from flux-weighted mean of component centroids x = 0.0 y = 0.0 for record, image in self.children: w = record.get(self.owner.keys["flux"])/flux x += record.get(self.owner.keys["centroid"].getX())*w y += record.get(self.owner.keys["centroid"].getY())*w self.parentRecord.set(self.owner.keys["centroid"], lsst.afw.geom.Point2D(x, y)) # Compute shape from flux-weighted mean of offset component shapes xx = 0.0 yy = 0.0 xy = 0.0 for record, image in self.children: w = record.get(self.owner.keys["flux"])/flux dx = record.get(self.owner.keys["centroid"].getX()) - x dy = record.get(self.owner.keys["centroid"].getY()) - y xx += (record.get(self.owner.keys["shape"].getIxx()) + dx**2)*w yy += (record.get(self.owner.keys["shape"].getIyy()) + dy**2)*w xy += (record.get(self.owner.keys["shape"].getIxy()) + dx*dy)*w self.parentRecord.set(self.owner.keys["shape"], lsst.afw.geom.ellipses.Quadrupole(xx, yy, xy)) # Run detection on the parent image to get the parent Footprint. self.owner._installFootprint(self.parentRecord, self.parentImage) # Create perfect HeavyFootprints for all children; these will need to be modified later to account # for the noise we'll add to the image. deblend = lsst.afw.image.MaskedImageF(self.owner.exposure.getMaskedImage(), True) for record, image in self.children: deblend.getImage().getArray()[:,:] = image.getArray() heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.parentRecord.getFootprint(), deblend) record.setFootprint(heavyFootprint)
def fitShapelets(image, msf): bbox = image.getBBox(lsst.afw.image.PARENT) x, y = numpy.meshgrid(numpy.arange(bbox.getBeginX(), bbox.getEndX(), dtype=float), numpy.arange(bbox.getBeginY(), bbox.getEndY(), dtype=float)) matrix = numpy.zeros((sum(lsst.shapelet.computeSize(element.getOrder()) for element in msf.getElements()), bbox.getArea()), dtype=float).transpose() offset = 0 for element in msf.getElements(): builder = lsst.shapelet.ModelBuilderD(x.flatten(), y.flatten()) size = lsst.shapelet.computeSize(element.getOrder()) builder.addModelMatrix(element.getOrder(), matrix[:,offset:offset+size]) offset += size data = image.getArray().flatten() coefficients, _, _, _ = numpy.linalg.lstsq(matrix, data) offset = 0 for element in msf.getElements(): size = lsst.shapelet.computeSize(element.getOrder()) element.getCoefficients()[:] = coefficients[offset:offset+size] offset += size
def load(cls, sourceID, dataRef=None, catalog="deepCoadd_meas", exposure="deepCoadd", config="measureCoaddSources_config"): if not isinstance(catalog, lsst.afw.table.SourceCatalog): catalog = dataRef.get(catalog, immediate=True) record = catalog.find(sourceID) if not isinstance(exposure, lsst.afw.image.ExposureF): exposure = dataRef.get(exposure, immediate=True) if not isinstance(config, lsst.pex.config.Config): config = dataRef.get(config, immediate=True) if not isinstance(config, lsst.meas.algorithms.SourceMeasurementConfig): config = config.measurement image = lsst.afw.image.ImageF(os.path.join(config.algorithms["cmodel"].diagnostics.root, "%s.fits" % sourceID)) exposure = lsst.afw.image.ExposureF(exposure, image.getBBox(lsst.afw.image.PARENT), lsst.afw.image.PARENT, True) exposure.getMaskedImage().getImage().getArray()[:,:] = image.getArray() psfModel = lsst.meas.extensions.multiShapelet.FitPsfModel( config.algorithms["multishapelet.psf"].makeControl(), record ) psf = psfModel.asMultiShapelet() return cls(config.algorithms["cmodel"], exposure, record.getFootprint(), psf, record.getCentroid(), record.getShape(), record.getPsfFlux(), record=record)
def setUp(self): width, height = 250, 500 self.numAmps = 4 numPixelsPerAmp = 1000 # crosstalk[i][j] is the fraction of the j-th amp present on the i-th # amp. self.crosstalk = [[0.0, 1e-4, 2e-4, 3e-4], [3e-4, 0.0, 2e-4, 1e-4], [4e-4, 5e-4, 0.0, 6e-4], [7e-4, 8e-4, 9e-4, 0.0]] self.value = 12345 self.crosstalkStr = "XTLK" # A bit of noise is important, because otherwise the pixel # distributions are razor-thin and then rejection doesn't work. rng = np.random.RandomState(12345) self.noise = rng.normal(0.0, 0.1, (2 * height, 2 * width)) # Create amp images withoutCrosstalk = [ lsst.afw.image.ImageF(width, height) for _ in range(self.numAmps) ] for image in withoutCrosstalk: image.set(0) xx = rng.randint(0, width, numPixelsPerAmp) yy = rng.randint(0, height, numPixelsPerAmp) image.getArray()[yy, xx] = self.value # Add in crosstalk withCrosstalk = [ image.Factory(image, True) for image in withoutCrosstalk ] for ii, iImage in enumerate(withCrosstalk): for jj, jImage in enumerate(withoutCrosstalk): value = self.crosstalk[ii][jj] iImage.scaledPlus(value, jImage) # Put amp images together def construct(imageList): image = lsst.afw.image.ImageF(2 * width, 2 * height) image.getArray()[:height, :width] = imageList[0].getArray() image.getArray()[:height, width:] = imageList[1].getArray()[:, :: -1] # flip in x image.getArray()[height:, :width] = imageList[2].getArray( )[::-1, :] # flip in y image.getArray()[height:, width:] = imageList[3].getArray( )[::-1, ::-1] # flip in x and y image.getArray()[:] += self.noise return image # Construct detector detName = 'detector 1' detId = 1 detSerial = 'serial 1' orientation = cameraGeom.Orientation() pixelSize = lsst.geom.Extent2D(1, 1) bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(2 * width, 2 * height)) crosstalk = np.array(self.crosstalk, dtype=np.float32) camBuilder = cameraGeom.Camera.Builder("fakeCam") detBuilder = camBuilder.add(detName, detId) detBuilder.setSerial(detSerial) detBuilder.setBBox(bbox) detBuilder.setOrientation(orientation) detBuilder.setPixelSize(pixelSize) detBuilder.setCrosstalk(crosstalk) # Construct second detector in this fake camera detName = 'detector 2' detId = 2 detSerial = 'serial 2' orientation = cameraGeom.Orientation() pixelSize = lsst.geom.Extent2D(1, 1) bbox = lsst.geom.Box2I(lsst.geom.Point2I(0, 0), lsst.geom.Extent2I(2 * width, 2 * height)) crosstalk = np.array(self.crosstalk, dtype=np.float32) detBuilder2 = camBuilder.add(detName, detId) detBuilder2.setSerial(detSerial) detBuilder2.setBBox(bbox) detBuilder2.setOrientation(orientation) detBuilder2.setPixelSize(pixelSize) detBuilder2.setCrosstalk(crosstalk) # Create amp info for ii, (xx, yy, corner) in enumerate([ (0, 0, lsst.afw.cameraGeom.ReadoutCorner.LL), (width, 0, lsst.afw.cameraGeom.ReadoutCorner.LR), (0, height, lsst.afw.cameraGeom.ReadoutCorner.UL), (width, height, lsst.afw.cameraGeom.ReadoutCorner.UR) ]): amp = cameraGeom.Amplifier.Builder() amp.setName("amp %d" % ii) amp.setBBox( lsst.geom.Box2I(lsst.geom.Point2I(xx, yy), lsst.geom.Extent2I(width, height))) amp.setRawDataBBox( lsst.geom.Box2I(lsst.geom.Point2I(xx, yy), lsst.geom.Extent2I(width, height))) amp.setReadoutCorner(corner) detBuilder.append(amp) detBuilder2.append(amp) cam = camBuilder.finish() ccd1 = cam.get('detector 1') ccd2 = cam.get('detector 2') self.exposure = lsst.afw.image.makeExposure( lsst.afw.image.makeMaskedImage(construct(withCrosstalk))) self.exposure.setDetector(ccd1) # Create a single ctSource that will be used for interChip CT # correction. self.ctSource = lsst.afw.image.makeExposure( lsst.afw.image.makeMaskedImage(construct(withCrosstalk))) self.ctSource.setDetector(ccd2) self.corrected = construct(withoutCrosstalk) if display: disp = lsst.afw.display.Display(frame=1) disp.mtv(self.exposure, title="exposure") disp = lsst.afw.display.Display(frame=0) disp.mtv(self.corrected, title="corrected exposure")
def testKernelImage(self): image = self.psf.computeKernelImage(self.psf.getAveragePosition()) check = makeGaussianImage(image.getBBox(), self.psf.getSigma()) self.assertFloatsAlmostEqual(image.getArray(), check.getArray()) self.assertFloatsAlmostEqual(image.getArray().sum(), 1.0, atol=1E-14)
def makeImage(self, ImageClass, scaling, addNoise=True): """Make an image for testing We create an image, persist and unpersist it, returning some data to the caller. Parameters ---------- ImageClass : `type`, an `lsst.afw.image.Image` class Class of image to create. scaling : `lsst.afw.fits.ImageScalingOptions` Scaling to apply during persistence. addNoise : `bool` Add noise to image? Returns ------- image : `lsst.afw.image.Image` (ImageClass) Created image. unpersisted : `lsst.afw.image.Image` (ImageClass) Unpersisted image. bscale, bzero : `float` FITS scale factor and zero used. minValue, maxValue : `float` Minimum and maximum value given the nominated scaling. """ image = ImageClass(self.bbox) mask = lsst.afw.image.Mask(self.bbox) mask.addMaskPlane(self.badMask) bad = mask.getPlaneBitMask(self.badMask) image.set(self.base) image[self.highPixel, LOCAL] = self.highValue image[self.lowPixel, LOCAL] = self.lowValue image[self.maskedPixel, LOCAL] = self.maskedValue mask[self.maskedPixel, LOCAL] = bad rng = np.random.RandomState(12345) dtype = image.getArray().dtype if addNoise: image.getArray()[:] += rng.normal( 0.0, self.stdev, image.getArray().shape).astype(dtype) with lsst.utils.tests.getTempFilePath(".fits") as filename: with lsst.afw.fits.Fits(filename, "w") as fits: options = lsst.afw.fits.ImageWriteOptions(scaling) header = lsst.daf.base.PropertyList() image.writeFits(fits, options, header, mask) unpersisted = ImageClass(filename) self.assertEqual(image.getBBox(), unpersisted.getBBox()) header = lsst.afw.fits.readMetadata(filename) bscale = header.getScalar("BSCALE") bzero = header.getScalar("BZERO") if scaling.algorithm != ImageScalingOptions.NONE: self.assertEqual(header.getScalar("BITPIX"), scaling.bitpix) if scaling.bitpix == 8: # unsigned, says FITS maxValue = bscale * (2**scaling.bitpix - 1) + bzero minValue = bzero else: maxValue = bscale * (2**(scaling.bitpix - 1) - 1) + bzero if scaling.bitpix == 32: # cfitsio pads 10 values, and so do we minValue = -bscale * (2**(scaling.bitpix - 1) - 10) + bzero else: minValue = -bscale * (2**(scaling.bitpix - 1)) + bzero # Convert scalars to the appropriate type maxValue = np.array(maxValue, dtype=image.getArray().dtype) minValue = np.array(minValue, dtype=image.getArray().dtype) checkAstropy(unpersisted, filename) return image, unpersisted, bscale, bzero, minValue, maxValue
def testOffsetImage(self): image = self.psf.computeImage(lsst.geom.Point2D(0.25, 0.25)) check = makeGaussianImage( image.getBBox(), self.psf.getSigma(), 0.25, 0.25) self.assertFloatsAlmostEqual(image.getArray(), check.getArray(), atol=1E-4, rtol=1E-4, plotOnFailure=True)
def setUp(self): width, height = 250, 500 self.numAmps = 4 numPixelsPerAmp = 1000 # crosstalk[i][j] is the fraction of the j-th amp present on the i-th amp. self.crosstalk = [[0.0, 1e-4, 2e-4, 3e-4], [3e-4, 0.0, 2e-4, 1e-4], [4e-4, 5e-4, 0.0, 6e-4], [7e-4, 8e-4, 9e-4, 0.0]] self.value = 12345 self.crosstalkStr = "XTLK" # A bit of noise is important, because otherwise the pixel distributions are razor-thin # and then rejection doesn't work rng = np.random.RandomState(12345) self.noise = rng.normal(0.0, 0.1, (2 * height, 2 * width)) # Create amp images withoutCrosstalk = [ lsst.afw.image.ImageF(width, height) for _ in range(self.numAmps) ] for image in withoutCrosstalk: image.set(0) xx = rng.randint(0, width, numPixelsPerAmp) yy = rng.randint(0, height, numPixelsPerAmp) image.getArray()[yy, xx] = self.value # Add in crosstalk withCrosstalk = [ image.Factory(image, True) for image in withoutCrosstalk ] for ii, iImage in enumerate(withCrosstalk): for jj, jImage in enumerate(withoutCrosstalk): value = self.crosstalk[ii][jj] iImage.scaledPlus(value, jImage) # Put amp images together def construct(imageList): image = lsst.afw.image.ImageF(2 * width, 2 * height) image.getArray()[:height, :width] = imageList[0].getArray() image.getArray()[:height, width:] = imageList[1].getArray()[:, :: -1] # flip in x image.getArray()[height:, :width] = imageList[2].getArray( )[::-1, :] # flip in y image.getArray()[height:, width:] = imageList[3].getArray( )[::-1, ::-1] # flip in x and y image.getArray()[:] += self.noise return image # Create amp info schema = lsst.afw.table.AmpInfoTable.makeMinimalSchema() amplifiers = lsst.afw.table.AmpInfoCatalog(schema) for ii, (xx, yy, corner) in enumerate([(0, 0, LL), (width, 0, LR), (0, height, UL), (width, height, UR)]): amp = amplifiers.addNew() amp.setName("amp %d" % ii) amp.setBBox( lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(xx, yy), lsst.afw.geom.Extent2I(width, height))) amp.setRawDataBBox( lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(xx, yy), lsst.afw.geom.Extent2I(width, height))) amp.setReadoutCorner(corner) # Put everything together ccd = lsst.afw.cameraGeom.Detector( "detector", 123, lsst.afw.cameraGeom.SCIENCE, "serial", lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0, 0), lsst.afw.geom.Extent2I(2 * width, 2 * height)), amplifiers, lsst.afw.cameraGeom.Orientation(), lsst.afw.geom.Extent2D(1, 1), {}, np.array(self.crosstalk, dtype=np.float32)) self.exposure = lsst.afw.image.makeExposure( lsst.afw.image.makeMaskedImage(construct(withCrosstalk))) self.exposure.setDetector(ccd) self.corrected = construct(withoutCrosstalk) if display: disp = lsst.afw.display.Display(frame=1) disp.mtv(self.exposure, title="exposure") disp = lsst.afw.display.Display(frame=0) disp.mtv(self.corrected, title="corrected exposure")
def setUp(self): width, height = 250, 500 self.numAmps = 4 numPixelsPerAmp = 1000 # crosstalk[i][j] is the fraction of the j-th amp present on the i-th amp. self.crosstalk = [[0.0, 1e-4, 2e-4, 3e-4], [3e-4, 0.0, 2e-4, 1e-4], [4e-4, 5e-4, 0.0, 6e-4], [7e-4, 8e-4, 9e-4, 0.0]] self.value = 12345 self.crosstalkStr = "XTLK" # A bit of noise is important, because otherwise the pixel distributions are razor-thin # and then rejection doesn't work rng = np.random.RandomState(12345) self.noise = rng.normal(0.0, 0.1, (2*height, 2*width)) # Create amp images withoutCrosstalk = [lsst.afw.image.ImageF(width, height) for _ in range(self.numAmps)] for image in withoutCrosstalk: image.set(0) xx = rng.randint(0, width, numPixelsPerAmp) yy = rng.randint(0, height, numPixelsPerAmp) image.getArray()[yy, xx] = self.value # Add in crosstalk withCrosstalk = [image.Factory(image, True) for image in withoutCrosstalk] for ii, iImage in enumerate(withCrosstalk): for jj, jImage in enumerate(withoutCrosstalk): value = self.crosstalk[ii][jj] iImage.scaledPlus(value, jImage) # Put amp images together def construct(imageList): image = lsst.afw.image.ImageF(2*width, 2*height) image.getArray()[:height, :width] = imageList[0].getArray() image.getArray()[:height, width:] = imageList[1].getArray()[:, ::-1] # flip in x image.getArray()[height:, :width] = imageList[2].getArray()[::-1, :] # flip in y image.getArray()[height:, width:] = imageList[3].getArray()[::-1, ::-1] # flip in x and y image.getArray()[:] += self.noise return image # Create amp info schema = lsst.afw.table.AmpInfoTable.makeMinimalSchema() amplifiers = lsst.afw.table.AmpInfoCatalog(schema) for ii, (xx, yy, corner) in enumerate([(0, 0, LL), (width, 0, LR), (0, height, UL), (width, height, UR)]): amp = amplifiers.addNew() amp.setName("amp %d" % ii) amp.setBBox(lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(xx, yy), lsst.afw.geom.Extent2I(width, height))) amp.setRawDataBBox(lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(xx, yy), lsst.afw.geom.Extent2I(width, height))) amp.setReadoutCorner(corner) # Put everything together ccd = lsst.afw.cameraGeom.Detector("detector", 123, lsst.afw.cameraGeom.SCIENCE, "serial", lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0, 0), lsst.afw.geom.Extent2I(2*width, 2*height)), amplifiers, lsst.afw.cameraGeom.Orientation(), lsst.afw.geom.Extent2D(1, 1), {}, np.array(self.crosstalk, dtype=np.float32)) self.exposure = lsst.afw.image.makeExposure(lsst.afw.image.makeMaskedImage(construct(withCrosstalk))) self.exposure.setDetector(ccd) self.corrected = construct(withoutCrosstalk) if display: disp = lsst.afw.display.Display(frame=1) disp.mtv(self.exposure, title="exposure") disp = lsst.afw.display.Display(frame=0) disp.mtv(self.corrected, title="corrected exposure")