def test_reject_maskedpixels(self): exposure = ExposureF(1000, 1000) psfConfig = InstallGaussianPsfConfig() psfConfig.fwhm = 4 psfTask = InstallGaussianPsfTask(config=psfConfig) psfTask.run(exposure=exposure) variance_image = exposure.getMaskedImage().getVariance() variance_image += 50 add_psf_image(exposure, 200.0, 400.0, 600.0) add_psf_image(exposure, 210.0, 210.0, 300.0) exposure.getMaskedImage().getImage()[200, 400] += 10000 mask_dict = exposure.getMaskedImage().getMask().getMaskPlaneDict() exposure.getMaskedImage().getMask()[200, 400] |= mask_dict['SAT'] matrix = CrowdedFieldMatrix(exposure, np.array([200.0, 210.0]), np.array([400.0, 210.0])) status = matrix.solve() self.assertEqual(status, matrix.SUCCESS) result = matrix.result() self.assertFloatsAlmostEqual(result, np.array([600.0, 300.0]), atol=1e-3)
def test_solve(self): exposure = ExposureF(1000, 1000) psfConfig = InstallGaussianPsfConfig() psfConfig.fwhm = 4 psfTask = InstallGaussianPsfTask(config=psfConfig) psfTask.run(exposure=exposure) variance_image = exposure.getMaskedImage().getVariance() variance_image += 50 add_psf_image(exposure, 200.0, 400.0, 600.0) add_psf_image(exposure, 210.0, 210.0, 300.0) # These last two are to catch edge effects. add_psf_image(exposure, 5.0, 210.0, 400.0) add_psf_image(exposure, 300.0, 5.0, 500.0) matrix = CrowdedFieldMatrix(exposure, np.array([200.0, 210.0, 5.0, 300.0]), np.array([400.0, 210.0, 210.0, 5.0])) status = matrix.solve() self.assertEqual(status, matrix.SUCCESS) result = matrix.result() self.assertFloatsAlmostEqual(result, np.array([600.0, 300.0, 400.0, 500.0]), atol=1e-3)
def test_solve_catalog(self): exposure = ExposureF(1000, 1000) psfConfig = InstallGaussianPsfConfig() psfConfig.fwhm = 4 psfTask = InstallGaussianPsfTask(config=psfConfig) psfTask.run(exposure=exposure) variance_image = exposure.getMaskedImage().getVariance() variance_image += 50 add_psf_image(exposure, 200.0, 400.0, 600.0) add_psf_image(exposure, 210.0, 210.0, 300.0) schema = afwTable.SourceTable.makeMinimalSchema() schema.addField("centroid_x", type=np.float64) schema.addField("centroid_y", type=np.float64) flux_key = schema.addField("flux_flux", type=np.float64) schema.getAliasMap().set("slot_Centroid", "centroid") testCatalog = afwTable.SourceCatalog(schema) for x, y in zip([200.0, 210.0], [400.0, 210]): r = testCatalog.addNew() r["centroid_x"] = x r["centroid_y"] = y matrix = CrowdedFieldMatrix(exposure, testCatalog, flux_key) result = matrix.solve() self.assertFloatsAlmostEqual(testCatalog["flux_flux"], np.array([600.0, 300.0]), atol=1e-3)
def setUp(self): self.exposure = ExposureF(1000, 1000) psfConfig = InstallGaussianPsfConfig() psfConfig.fwhm = 4 psfTask = InstallGaussianPsfTask(config=psfConfig) psfTask.run(exposure=self.exposure) variance_image = self.exposure.getMaskedImage().getVariance() variance_image += 50
def test_solve_centroid(self): exposure = ExposureF(400, 400) psfConfig = InstallGaussianPsfConfig() psfConfig.fwhm = 4 psfTask = InstallGaussianPsfTask(config=psfConfig) psfTask.run(exposure=exposure) variance_image = exposure.getMaskedImage().getVariance() variance_image += 100 exposure.getMaskedImage().getVariance().getArray()[203, 203] = np.nan add_psf_image(exposure, 200.0, 200.0, 600.0) # exposure.image.array += 20*np.random.randn(*exposure.image.array.shape) schema = afwTable.SourceTable.makeMinimalSchema() simultaneousPsfFlux_key = schema.addField( "crowd_psfFlux_flux_instFlux", type=np.float64, doc="PSF Flux from simultaneous fitting") afwTable.Point2DKey.addFields(schema, "coarse_centroid", "Detection peak", "pixels") centroid_key = afwTable.Point2DKey.addFields( schema, "new_centroid", "Measurement of centroid", "pixels") schema.getAliasMap().set("slot_Centroid", "coarse_centroid") schema.getAliasMap().set("slot_PsfFlux", "crowd_psfFlux_flux") source_catalog = afwTable.SourceCatalog(schema) child = source_catalog.addNew() child['coarse_centroid_x'] = 200.0 child['coarse_centroid_y'] = 200.5 # Fit once with fixed positions to get the fluxes matrix = CrowdedFieldMatrix(exposure, source_catalog, simultaneousPsfFlux_key, fitCentroids=False, centroidKey=centroid_key) matrix.solve() # Now that we have rough fluxes, re-fit and allow # centroids to move. matrix = CrowdedFieldMatrix(exposure, source_catalog, simultaneousPsfFlux_key, fitCentroids=True, centroidKey=centroid_key) result = matrix.solve() self.assertFloatsAlmostEqual(source_catalog[0]['new_centroid_x'], 200.0, atol=5e-2) self.assertFloatsAlmostEqual(source_catalog[0]['new_centroid_y'], 200.0, atol=5e-2)
def testNoPsf(self): """Test InstallGaussianPsfTask when the input exposure has no PSF.""" for width in (21, 25): for fwhm in (2.8, 7.1): config = InstallGaussianPsfTask.ConfigClass() config.width = width config.fwhm = fwhm task = InstallGaussianPsfTask(config=config) exposure = ExposureF(100, 100) task.run(exposure=exposure) self.assertTrue(exposure.hasPsf()) psf = exposure.getPsf() psfIm = psf.computeImage() self.assertEqual(psfIm.getWidth(), width) self.assertEqual(psfIm.getHeight(), width) measFwhm = psf.computeShape().getDeterminantRadius()*FwhmPerSigma self.assertAlmostEqual(measFwhm, fwhm, delta=1e-3)
def testNoPsf(self): """Test InstallGaussianPsfTask when the input exposure has no PSF.""" for width in (21, 25): for fwhm in (2.8, 7.1): config = InstallGaussianPsfTask.ConfigClass() config.width = width config.fwhm = fwhm task = InstallGaussianPsfTask(config=config) exposure = ExposureF(100, 100) task.run(exposure=exposure) self.assertTrue(exposure.hasPsf()) psf = exposure.getPsf() psfIm = psf.computeImage() self.assertEqual(psfIm.getWidth(), width) self.assertEqual(psfIm.getHeight(), width) measFwhm = psf.computeShape().getDeterminantRadius( ) * FwhmPerSigma self.assertAlmostEqual(measFwhm, fwhm, delta=1e-3)
def testMatchSingleGaussianPsf(self): """Test InstallGaussianPsfTask when the input exposure has a single Gaussian PSF.""" config = InstallGaussianPsfTask.ConfigClass() task = InstallGaussianPsfTask(config=config) for desWidth, desHeight, desSigma in ( (21, 23, 1.2), (23, 25, 3.5), ): exposure = ExposureF(100, 100) inPsf = SingleGaussianPsf(desWidth, desHeight, desSigma) exposure.setPsf(inPsf) task.run(exposure=exposure) self.assertTrue(exposure.hasPsf()) psf = exposure.getPsf() psfIm = psf.computeImage() self.assertEqual(psfIm.getWidth(), desWidth) self.assertEqual(psfIm.getHeight(), desHeight) self.assertAlmostEqual(psf.computeShape().getDeterminantRadius(), desSigma, delta=1e-3)
def testMatchSingleGaussianPsf(self): """Test InstallGaussianPsfTask when the input exposure has a single Gaussian PSF """ config = InstallGaussianPsfTask.ConfigClass() task = InstallGaussianPsfTask(config=config) for desWidth, desHeight, desSigma in ( (21, 23, 1.2), (23, 25, 3.5), ): exposure = ExposureF(100, 100) inPsf = SingleGaussianPsf(desWidth, desHeight, desSigma) exposure.setPsf(inPsf) task.run(exposure=exposure) self.assertTrue(exposure.hasPsf()) psf = exposure.getPsf() psfIm = psf.computeImage() self.assertEqual(psfIm.getWidth(), desWidth) self.assertEqual(psfIm.getHeight(), desHeight) self.assertAlmostEqual(psf.computeShape().getDeterminantRadius(), desSigma, delta=1e-3)
def testMatchDoubleGaussianPsf(self): """Test InstallGaussianPsfTask when the input exposure has a DoubleGaussian PSF.""" config = InstallGaussianPsfTask.ConfigClass() task = InstallGaussianPsfTask(config=config) for doubleGaussParms in ( # width, height, inner sigma, outer sigma, outer/inner peak amplitude (21, 23, 1.2, 3.5, 0.02), (23, 25, 3.5, 9.0, 0.02), ): exposure = ExposureF(100, 100) inPsf = DoubleGaussianPsf(*doubleGaussParms) exposure.setPsf(inPsf) desWidth, desHeight, innerSigma = doubleGaussParms[0:3] task.run(exposure=exposure) self.assertTrue(exposure.hasPsf()) psf = exposure.getPsf() psfIm = psf.computeImage() self.assertEqual(psfIm.getWidth(), desWidth) self.assertEqual(psfIm.getHeight(), desHeight) self.assertAlmostEqual(psf.computeShape().getDeterminantRadius(), innerSigma, delta=0.1)
def testMatchDoubleGaussianPsf(self): """Test InstallGaussianPsfTask when the input exposure has a DoubleGaussian PSF """ config = InstallGaussianPsfTask.ConfigClass() task = InstallGaussianPsfTask(config=config) for doubleGaussParms in ( # width, height, inner sigma, outer sigma, outer/inner peak amplitude (21, 23, 1.2, 3.5, 0.02), (23, 25, 3.5, 9.0, 0.02), ): exposure = ExposureF(100, 100) inPsf = DoubleGaussianPsf(*doubleGaussParms) exposure.setPsf(inPsf) desWidth, desHeight, innerSigma = doubleGaussParms[0:3] task.run(exposure=exposure) self.assertTrue(exposure.hasPsf()) psf = exposure.getPsf() psfIm = psf.computeImage() self.assertEqual(psfIm.getWidth(), desWidth) self.assertEqual(psfIm.getHeight(), desHeight) self.assertAlmostEqual(psf.computeShape().getDeterminantRadius(), innerSigma, delta=0.1)
def testBadDim(self): for width in (8, 10, 20): config = InstallGaussianPsfTask.ConfigClass() config.width = width with self.assertRaises(Exception): config.validate()
from lsst.afw.image import ExposureF from lsst.meas.algorithms.installGaussianPsf import InstallGaussianPsfTask, FwhmPerSigma exposure = ExposureF(100, 100) task = InstallGaussianPsfTask() task.run(exposure=exposure) # This particular exposure had no PSF model to begin with, so the new PSF model # uses the config's FWHM. However, measured FWHM is based on the truncated # PSF image, so it does not exactly match the input measFwhm = exposure.getPsf().computeShape().getDeterminantRadius() * FwhmPerSigma assert abs(measFwhm - task.config.fwhm) < 1e-3
def run(self, ccdExposure, *, camera=None, bias=None, dark=None, flat=None, defects=None, linearizer=None, crosstalk=None, bfKernel=None, bfGains=None, ptc=None, crosstalkSources=None, isrBaseConfig=None): """Run isr and cosmic ray repair using, doing as much isr as possible. Retrieves as many calibration products as are available, and runs isr with those settings enabled, but always returns an assembled image at a minimum. Then performs cosmic ray repair if configured to. Parameters ---------- ccdExposure : `lsst.afw.image.Exposure` The raw exposure that is to be run through ISR. The exposure is modified by this method. camera : `lsst.afw.cameraGeom.Camera`, optional The camera geometry for this exposure. Required if one or more of ``ccdExposure``, ``bias``, ``dark``, or ``flat`` does not have an associated detector. bias : `lsst.afw.image.Exposure`, optional Bias calibration frame. linearizer : `lsst.ip.isr.linearize.LinearizeBase`, optional Functor for linearization. crosstalk : `lsst.ip.isr.crosstalk.CrosstalkCalib`, optional Calibration for crosstalk. crosstalkSources : `list`, optional List of possible crosstalk sources. dark : `lsst.afw.image.Exposure`, optional Dark calibration frame. flat : `lsst.afw.image.Exposure`, optional Flat calibration frame. ptc : `lsst.ip.isr.PhotonTransferCurveDataset`, optional Photon transfer curve dataset, with, e.g., gains and read noise. bfKernel : `numpy.ndarray`, optional Brighter-fatter kernel. bfGains : `dict` of `float`, optional Gains used to override the detector's nominal gains for the brighter-fatter correction. A dict keyed by amplifier name for the detector in question. defects : `lsst.ip.isr.Defects`, optional List of defects. fringes : `lsst.pipe.base.Struct`, optional Struct containing the fringe correction data, with elements: - ``fringes``: fringe calibration frame (`afw.image.Exposure`) - ``seed``: random seed derived from the ccdExposureId for random number generator (`uint32`) opticsTransmission: `lsst.afw.image.TransmissionCurve`, optional A ``TransmissionCurve`` that represents the throughput of the, optics, to be evaluated in focal-plane coordinates. filterTransmission : `lsst.afw.image.TransmissionCurve` A ``TransmissionCurve`` that represents the throughput of the filter itself, to be evaluated in focal-plane coordinates. sensorTransmission : `lsst.afw.image.TransmissionCurve` A ``TransmissionCurve`` that represents the throughput of the sensor itself, to be evaluated in post-assembly trimmed detector coordinates. atmosphereTransmission : `lsst.afw.image.TransmissionCurve` A ``TransmissionCurve`` that represents the throughput of the atmosphere, assumed to be spatially constant. detectorNum : `int`, optional The integer number for the detector to process. strayLightData : `object`, optional Opaque object containing calibration information for stray-light correction. If `None`, no correction will be performed. illumMaskedImage : `lsst.afw.image.MaskedImage`, optional Illumination correction image. isrBaseConfig : `lsst.ip.isr.IsrTaskConfig`, optional An isrTask config to act as the base configuration. Options which involve applying a calibration product are ignored, but this allows for the configuration of e.g. the number of overscan columns. Returns ------- result : `lsst.pipe.base.Struct` Result struct with component: - ``exposure`` : `afw.image.Exposure` The ISRed and cosmic-ray-repaired exposure. """ isrConfig = isrBaseConfig if isrBaseConfig else IsrTask.ConfigClass() isrConfig.doBias = False isrConfig.doDark = False isrConfig.doFlat = False isrConfig.doFringe = False isrConfig.doDefect = False isrConfig.doLinearize = False isrConfig.doCrosstalk = False isrConfig.doBrighterFatter = False isrConfig.usePtcGains = False if bias: isrConfig.doBias = True self.log.info("Running with bias correction") if dark: isrConfig.doDark = True self.log.info("Running with dark correction") if flat: isrConfig.doFlat = True self.log.info("Running with flat correction") # TODO: deal with fringes here if defects: isrConfig.doDefect = True self.log.info("Running with defect correction") if linearizer: isrConfig.doLinearize = True self.log.info("Running with linearity correction") if crosstalk: isrConfig.doCrosstalk = True self.log.info("Running with crosstalk correction") if bfKernel: isrConfig.doBrighterFatter = True self.log.info("Running with brighter-fatter correction") if ptc: isrConfig.usePtcGains = True self.log.info("Running with ptc correction") isrConfig.doWrite = False isrTask = IsrTask(config=isrConfig) result = isrTask.run( ccdExposure, camera=camera, bias=bias, dark=dark, flat=flat, # fringes=pipeBase.Struct(fringes=None), defects=defects, linearizer=linearizer, crosstalk=crosstalk, bfKernel=bfKernel, bfGains=bfGains, ptc=ptc, crosstalkSources=crosstalkSources, isGen3=True, ) postIsr = result.exposure if self.config.doRepairCosmics: try: # can fail due to too many CRs detected, and we always want an exposure back self.log.info("Repairing cosmics...") if postIsr.getPsf() is None: installPsfTask = InstallGaussianPsfTask() installPsfTask.run(postIsr) # TODO: try adding a reasonably wide Gaussian as a temp PSF # and then just running repairTask on its own without any # imChar. It should work, and be faster. repairConfig = CharacterizeImageTask.ConfigClass() repairConfig.doMeasurePsf = False repairConfig.doApCorr = False repairConfig.doDeblend = False repairConfig.doWrite = False repairConfig.repair.cosmicray.nCrPixelMax = 200000 repairTask = CharacterizeImageTask(config=repairConfig) repairTask.repair.run(postIsr) except Exception as e: self.log.warning(f"During CR repair caught: {e}") # exposure is returned for convenience to mimic isrTask's API. return pipeBase.Struct(exposure=postIsr, outputExposure=postIsr)