24 from builtins
import zip
25 from builtins
import object
30 import lsst.afw.detection
32 import lsst.afw.geom.ellipses
34 import lsst.pex.exceptions
36 from .sfm
import SingleFrameMeasurementTask
37 from .forcedMeasurement
import ForcedMeasurementTask
38 from .
import CentroidResultKey
40 __all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
41 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
42 "FluxTransformTestCase",
"CentroidTransformTestCase")
47 A Python context manager used to add multiple overlapping sources along with a parent source 48 that represents all of them together. 50 This is used as the return value for TestDataset.addBlend(), and this is the only way it should 51 be used. The only public method is addChild(). 64 def addChild(self, flux, centroid, shape=None):
66 Add a child source to the blend, and return the truth catalog record that corresponds to it. 68 @param[in] flux Total flux of the source to be added. 69 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). 70 @param[in] shape 2nd moments of the source before PSF convolution 71 (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog 72 records post-convolution moments) 74 record, image = self.
owner.addSource(flux, centroid, shape)
77 self.
children.append((record, image))
90 flux += record.get(self.
owner.keys[
"flux"])
96 w = record.get(self.
owner.keys[
"flux"])/flux
97 x += record.get(self.
owner.keys[
"centroid"].getX())*w
98 y += record.get(self.
owner.keys[
"centroid"].getY())*w
105 w = record.get(self.
owner.keys[
"flux"])/flux
106 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
107 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
108 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
109 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
110 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
111 self.
parentRecord.set(self.
owner.keys[
"shape"], lsst.afw.geom.ellipses.Quadrupole(xx, yy, xy))
116 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.getMaskedImage(),
True)
118 deblend.getImage().getArray()[:, :] = image.getArray()
119 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
120 record.setFootprint(heavyFootprint)
125 A simulated dataset consisting of a test image and an associated truth catalog. 127 TestDataset creates an idealized image made of pure Gaussians (including a Gaussian PSF), 128 with simple noise and idealized Footprints/HeavyFootprints that simulated the outputs 129 of detection and deblending. Multiple noise realizations can be created from the same 130 underlying sources, allowing uncertainty estimates to be verified via Monte Carlo. 134 bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0,0), lsst.afw.geom.Point2I(100, 100)) 135 dataset = TestDataset(bbox) 136 dataset.addSource(flux=1E5, centroid=lsst.afw.geom.Point2D(25, 26)) 137 dataset.addSource(flux=2E5, centroid=lsst.afw.geom.Point2D(75, 24), 138 shape=lsst.afw.geom.ellipses.Quadrupole(8, 7, 2)) 139 with dataset.addBlend() as family: 140 family.addChild(flux=2E5, centroid=lsst.afw.geom.Point2D(50, 72)) 141 family.addChild(flux=1.5E5, centroid=lsst.afw.geom.Point2D(51, 74)) 142 exposure, catalog = dataset.realize(noise=100.0, schema=TestDataset.makeMinimalSchema()) 148 """Return the minimal schema needed to hold truth catalog fields. 150 When TestDataset.realize() is called, the schema must include at least these fields. 151 Usually it will include additional fields for measurement algorithm outputs, allowing 152 the same catalog to be used for both truth values (the fields from the minimal schema) 153 and the measurements. 155 if not hasattr(cls,
"_schema"):
156 schema = lsst.afw.table.SourceTable.makeMinimalSchema()
158 cls.
keys[
"parent"] = schema.find(
"parent").key
159 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
160 cls.
keys[
"flux"] = schema.addField(
"truth_flux", type=np.float64, doc=
"true flux", units=
"count")
161 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
162 schema,
"truth",
"true simulated centroid",
"pixel" 164 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
165 doc=
"set if the object is a star")
166 cls.
keys[
"shape"] = lsst.afw.table.QuadrupoleKey.addFields(
167 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
169 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
170 doc=
"set if the object is a star")
171 schema.getAliasMap().set(
"slot_Shape",
"truth")
172 schema.getAliasMap().set(
"slot_Centroid",
"truth")
173 schema.getAliasMap().set(
"slot_ModelFlux",
"truth")
174 schema.getCitizen().markPersistent()
176 schema = lsst.afw.table.Schema(cls.
_schema)
177 schema.disconnectAliases()
182 minRotation=None, maxRotation=None,
183 minRefShift=None, maxRefShift=None,
184 minPixShift=2.0, maxPixShift=4.0):
186 Create a new undistorted TanWcs that is similar but not identical to another, with random 187 scaling, rotation, and offset (in both pixel position and reference position). 189 The maximum and minimum arguments are interpreted as absolute values for a split 190 range that covers both positive and negative values (as this method is used 191 in testing, it is typically most important to avoid perturbations near zero). 192 Scale factors are treated somewhat differently: the actual scale factor is chosen between 193 minScaleFactor and maxScaleFactor OR (1/maxScaleFactor) and (1/minScaleFactor). 195 The default range for rotation is 30-60 degrees, and the default range for reference shift 196 is 0.5-1.0 arcseconds (these cannot be safely included directly as default values because Angle 197 objects are mutable). 199 if minRotation
is None:
200 minRotation = 30.0*lsst.afw.geom.degrees
201 if maxRotation
is None:
202 maxRotation = 60.0*lsst.afw.geom.degrees
203 if minRefShift
is None:
204 minRefShift = 0.5*lsst.afw.geom.arcseconds
205 if maxRefShift
is None:
206 maxRefShift = 1.0*lsst.afw.geom.arcseconds
208 def splitRandom(min1, max1, min2=None, max2=None):
213 if np.random.uniform() > 0.5:
214 return float(np.random.uniform(min1, max1))
216 return float(np.random.uniform(min2, max2))
218 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
219 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.afw.geom.radians
220 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
221 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
222 pixShiftX = splitRandom(minPixShift, maxPixShift)
223 pixShiftY = splitRandom(minPixShift, maxPixShift)
225 oldTransform = lsst.afw.geom.LinearTransform(oldWcs.getCDMatrix())
226 rTransform = lsst.afw.geom.LinearTransform.makeRotation(rotation)
227 sTransform = lsst.afw.geom.LinearTransform.makeScaling(scaleFactor)
228 newTransform = oldTransform*rTransform*sTransform
229 matrix = newTransform.getMatrix()
231 oldSkyOrigin = oldWcs.getSkyOrigin().toIcrs()
232 newSkyOrigin = lsst.afw.coord.IcrsCoord(oldSkyOrigin.getRa() + refShiftRa,
233 oldSkyOrigin.getDec() + refShiftDec)
235 oldPixOrigin = oldWcs.getPixelOrigin()
236 newPixOrigin = lsst.afw.geom.Point2D(oldPixOrigin.getX() + pixShiftX,
237 oldPixOrigin.getY() + pixShiftY)
238 return lsst.afw.image.makeWcs(newSkyOrigin, newPixOrigin,
239 matrix[0, 0], matrix[0, 1], matrix[1, 0], matrix[1, 1])
242 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, fluxMag0=1E12):
244 Create an Exposure, with a Calib, Wcs, and Psf, but no pixel values set. 246 @param[in] bbox Bounding box of the image (image coordinates) as returned by makeCatalog. 247 @param[in] wcs New Wcs for the exposure (created from crval and cdelt if None). 248 @param[in] crval afw.coord.Coord: center of the TAN WCS attached to the image. 249 @param[in] cdelt afw.geom.Angle: pixel scale of the image 250 @param[in] psfSigma Radius (sigma) of the Gaussian PSF attached to the image 251 @param[in] psfDim Width and height of the image's Gaussian PSF attached to the image 252 @param[in] fluxMag0 Flux at magnitude zero (in e-) used to set the Calib of the exposure. 256 crval = lsst.afw.coord.IcrsCoord(45.0*lsst.afw.geom.degrees, 45.0*lsst.afw.geom.degrees)
258 cdelt = 0.2*lsst.afw.geom.arcseconds
259 crpix = lsst.afw.geom.Box2D(bbox).getCenter()
260 wcs = lsst.afw.image.makeWcs(crval, crpix, cdelt.asDegrees(), 0.0, 0.0, cdelt.asDegrees())
261 exposure = lsst.afw.image.ExposureF(bbox)
262 psf = lsst.afw.detection.GaussianPsf(psfDim, psfDim, psfSigma)
263 calib = lsst.afw.image.Calib()
264 calib.setFluxMag0(fluxMag0)
267 exposure.setCalib(calib)
273 Create an image of an elliptical Gaussian. 275 @param[in,out] bbox Bounding box of image to create. 276 @param[in] flux Total flux of the Gaussian (normalized analytically, not using pixel 278 @param[in] ellipse lsst.afw.geom.ellipses.Ellipse holding the centroid and shape. 280 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
281 np.arange(bbox.getBeginY(), bbox.getEndY()))
282 t = ellipse.getGridTransform()
283 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
284 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
285 image = lsst.afw.image.ImageF(bbox)
286 image.getArray()[:, :] = np.exp(-0.5*(xt**2 + yt**2))*flux/(2.0*ellipse.getCore().getArea())
289 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
291 Initialize the dataset. 293 @param[in] bbox Bounding box of the test image. 294 @param[in] threshold Threshold absolute value used to determine footprints for 295 simulated sources. This thresholding will be applied before noise is 296 actually added to images (or before the noise level is even known), so 297 this will necessarily produce somewhat artificial footprints. 298 @param[in] exposure lsst.afw.image.ExposureF test sources should be added to. Ownership should 299 be considered transferred from the caller to the TestDataset. 300 Must have a Gaussian Psf for truth catalog shapes to be exact. 301 @param[in] **kwds Keyword arguments forwarded to makeEmptyExposure if exposure is None. 305 self.
threshold = lsst.afw.detection.Threshold(threshold, lsst.afw.detection.Threshold.VALUE)
311 def _installFootprint(self, record, image):
312 """Create a Footprint for a simulated source and add it to its truth catalog record. 315 fpSet = lsst.afw.detection.FootprintSet(image, self.
threshold)
317 fpSet = lsst.afw.detection.FootprintSet(fpSet, int(self.
psfShape.getDeterminantRadius() + 1.0),
True)
319 fpSet.setMask(self.
exposure.getMaskedImage().getMask(),
"DETECTED")
321 if len(fpSet.getFootprints()) > 1:
322 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
323 if len(fpSet.getFootprints()) == 0:
324 raise RuntimeError(
"Threshold value results in zero Footprints for object")
325 record.setFootprint(fpSet.getFootprints()[0])
329 Add a source to the simulation 331 @param[in] flux Total flux of the source to be added. 332 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D). 333 @param[in] shape 2nd moments of the source before PSF convolution 334 (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog 335 records post-convolution moments). If None, a point source 338 @return a truth catalog record and single-source image corresponding to the new source. 342 record.set(self.
keys[
"flux"], flux)
343 record.set(self.
keys[
"centroid"], centroid)
345 record.set(self.
keys[
"isStar"],
True)
348 record.set(self.
keys[
"isStar"],
False)
349 fullShape = shape.convolve(self.
psfShape)
350 record.set(self.
keys[
"shape"], fullShape)
353 lsst.afw.geom.ellipses.Ellipse(fullShape, centroid))
357 self.
exposure.getMaskedImage().getImage().getArray()[:, :] += image.getArray()
362 Return a context manager that allows a blend of multiple sources to be added. 367 with d.addBlend() as b: 368 b.addChild(flux1, centroid1) 369 b.addChild(flux2, centroid2, shape2) 372 Note that nothing stops you from creating overlapping sources just using the addSource() method, 373 but addBlend() is necesssary to create a parent object and deblended HeavyFootprints of the type 374 produced by the detection and deblending pipelines. 380 Create a copy of the dataset transformed to a new WCS, with new Psf and Calib. 382 @param[in] wcs Wcs for the new dataset. 383 @param[in] **kwds Additional keyword arguments passed on to makeEmptyExposure. If not 384 specified, these revert to the defaults for makeEmptyExposure, not the 385 values in the current dataset. 387 bboxD = lsst.afw.geom.Box2D()
388 xyt = lsst.afw.image.XYTransformFromWcsPair(wcs, self.
exposure.getWcs())
389 for corner
in lsst.afw.geom.Box2D(self.
exposure.getBBox()).getCorners():
390 bboxD.include(xyt.forwardTransform(lsst.afw.geom.Point2D(corner)))
391 bboxI = lsst.afw.geom.Box2I(bboxD)
394 newCalib = result.exposure.getCalib()
395 oldPsfShape = self.
exposure.getPsf().computeShape()
397 if record.get(self.
keys[
"nChild"]):
398 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
399 magnitude = oldCalib.getMagnitude(record.get(self.
keys[
"flux"]))
400 newFlux = newCalib.getFlux(magnitude)
401 oldCentroid = record.get(self.
keys[
"centroid"])
402 newCentroid = xyt.forwardTransform(oldCentroid)
403 if record.get(self.
keys[
"isStar"]):
404 newDeconvolvedShape =
None 406 affine = xyt.linearizeForwardTransform(oldCentroid)
407 oldFullShape = record.get(self.
keys[
"shape"])
408 oldDeconvolvedShape = lsst.afw.geom.ellipses.Quadrupole(
409 oldFullShape.getIxx() - oldPsfShape.getIxx(),
410 oldFullShape.getIyy() - oldPsfShape.getIyy(),
411 oldFullShape.getIxy() - oldPsfShape.getIxy(),
414 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
415 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
420 Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints. 422 @param[in] noise Standard deviation of noise to be added to the exposure. The noise will be 423 Gaussian and constant, appropriate for the sky-limited regime. 424 @param[in] schema Schema of the new catalog to be created. Must start with self.schema (i.e. 425 schema.contains(self.schema) must be True), but typically contains fields for 426 already-configured measurement algorithms as well. 428 @return a tuple of (exposure, catalog) 430 assert schema.contains(self.
schema)
431 mapper = lsst.afw.table.SchemaMapper(self.
schema)
432 mapper.addMinimalSchema(self.
schema,
True)
434 exposure.getMaskedImage().getVariance().getArray()[:, :] = noise**2
435 exposure.getMaskedImage().getImage().getArray()[:, :] \
436 += np.random.randn(exposure.getHeight(), exposure.getWidth())*noise
437 catalog = lsst.afw.table.SourceCatalog(schema)
438 catalog.extend(self.
catalog, mapper=mapper)
441 for record
in catalog:
443 if record.getParent() == 0:
446 parent = catalog.find(record.getParent())
447 footprint = parent.getFootprint()
448 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
449 footprint.spans.flatten(parentFluxArrayNoNoise,
450 self.
exposure.getMaskedImage().getImage().getArray(),
452 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
453 footprint.spans.flatten(parentFluxArrayNoisy,
454 exposure.getMaskedImage().getImage().getArray(),
456 oldHeavy = record.getFootprint()
457 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
460 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
461 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
462 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
463 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
464 record.setFootprint(newHeavy)
465 return exposure, catalog
485 """Convenience function to create a Config instance for SingleFrameMeasurementTask 487 The plugin and its dependencies will be the only plugins run, while the Centroid, Shape, 488 and ModelFlux slots will be set to the truth fields generated by the TestDataset class. 490 config = SingleFrameMeasurementTask.ConfigClass()
491 config.slots.centroid =
"truth" 492 config.slots.shape =
"truth" 493 config.slots.modelFlux =
None 494 config.slots.apFlux =
None 495 config.slots.psfFlux =
None 496 config.slots.instFlux =
None 497 config.slots.calibFlux =
None 498 config.plugins.names = (plugin,) + tuple(dependencies)
503 """Convenience function to create a SingleFrameMeasurementTask with a simple configuration. 507 raise ValueError(
"Either plugin or config argument must not be None")
510 schema = TestDataset.makeMinimalSchema()
512 schema.setAliasMap(
None)
513 if algMetadata
is None:
514 algMetadata = lsst.daf.base.PropertyList()
518 """Convenience function to create a Config instance for ForcedMeasurementTask 520 In addition to the plugins specified in the plugin and dependencies arguments, 521 the TransformedCentroid and TransformedShape plugins will be run and used as the 522 Centroid and Shape slots; these simply transform the reference catalog centroid 523 and shape to the measurement coordinate system. 525 config = ForcedMeasurementTask.ConfigClass()
526 config.slots.centroid =
"base_TransformedCentroid" 527 config.slots.shape =
"base_TransformedShape" 528 config.slots.modelFlux =
None 529 config.slots.apFlux =
None 530 config.slots.psfFlux =
None 531 config.slots.instFlux =
None 532 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
533 "base_TransformedShape")
538 """Convenience function to create a ForcedMeasurementTask with a simple configuration. 542 raise ValueError(
"Either plugin or config argument must not be None")
544 if refSchema
is None:
545 refSchema = TestDataset.makeMinimalSchema()
546 if algMetadata
is None:
547 algMetadata = lsst.daf.base.PropertyList()
553 Base class for testing measurement transformations. 555 We test both that the transform itself operates successfully (fluxes are 556 converted to magnitudes, flags are propagated properly) and that the 557 transform is registered as the default for the appropriate measurement 560 In the simple case of one-measurement-per-transformation, the developer 561 need not directly write any tests themselves: simply customizing the class 562 variables is all that is required. More complex measurements (e.g. 563 multiple aperture fluxes) require extra effort. 568 name =
"MeasurementTransformTest" 572 algorithmClass =
None 573 transformClass =
None 577 flagNames = (
"flag",)
582 singleFramePlugins = ()
586 bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0, 0), lsst.afw.geom.Point2I(200, 200))
587 self.
calexp = TestDataset.makeEmptyExposure(bbox)
588 self._setupTransform()
597 def _populateCatalog(self, baseNames):
599 for flagValue
in (
True,
False):
600 records.append(self.inputCat.addNew())
601 for baseName
in baseNames:
603 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
604 records[-1].set(records[-1].schema.join(baseName, flagName), flagValue)
605 self._setFieldsInRecords(records, baseName)
607 def _checkOutput(self, baseNames):
608 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
609 for baseName
in baseNames:
610 self._compareFieldsInRecords(inSrc, outSrc, baseName)
612 keyName = outSrc.schema.join(baseName, flagName)
613 if keyName
in inSrc.schema:
614 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
616 self.assertFalse(keyName
in outSrc.schema)
618 def _runTransform(self, doExtend=True):
620 self.outputCat.extend(self.inputCat, mapper=self.mapper)
621 self.transform(self.inputCat, self.outputCat, self.
calexp.getWcs(), self.
calexp.getCalib())
625 Test the operation of the transformation on a catalog containing random data. 629 * An appropriate exception is raised on an attempt to transform between catalogs with different 631 * Otherwise, all appropriate conversions are properly appled and that flags have been propagated. 633 The `baseNames` argument requires some explanation. This should be an iterable of the leading parts of 634 the field names for each measurement; that is, everything that appears before `_flux`, `_flag`, etc. 635 In the simple case of a single measurement per plugin, this is simply equal to `self.name` (thus 636 measurements are stored as `self.name + "_flux"`, etc). More generally, the developer may specify 637 whatever iterable they require. For example, to handle multiple apertures, we could have 638 `(self.name + "_0", self.name + "_1", ...)`. 640 @param[in] baseNames Iterable of the initial parts of measurement field names. 642 baseNames = baseNames
or [self.
name]
644 self.assertRaises(lsst.pex.exceptions.LengthError, self.
_runTransform,
False)
648 def _checkRegisteredTransform(self, registry, name):
651 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
655 Test that the transformation is appropriately registered with the relevant measurement algorithms. 665 def _setupTransform(self):
667 inputSchema = lsst.afw.table.SourceTable.makeMinimalSchema()
671 inputSchema.getAliasMap().set(
"slot_Centroid",
"dummy")
672 inputSchema.getAliasMap().set(
"slot_Shape",
"dummy")
673 self.algorithmClass(self.
control, self.name, inputSchema)
674 inputSchema.getAliasMap().erase(
"slot_Centroid")
675 inputSchema.getAliasMap().erase(
"slot_Shape")
676 self.
inputCat = lsst.afw.table.SourceCatalog(inputSchema)
677 self.
mapper = lsst.afw.table.SchemaMapper(inputSchema)
684 def _setupTransform(self):
686 inputMapper = lsst.afw.table.SchemaMapper(lsst.afw.table.SourceTable.makeMinimalSchema(),
687 lsst.afw.table.SourceTable.makeMinimalSchema())
691 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"dummy")
692 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Shape",
"dummy")
693 self.algorithmClass(self.
control, self.name, inputMapper, lsst.daf.base.PropertyList())
694 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Centroid")
695 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Shape")
696 self.
inputCat = lsst.afw.table.SourceCatalog(inputMapper.getOutputSchema())
697 self.
mapper = lsst.afw.table.SchemaMapper(inputMapper.getOutputSchema())
704 def _setFieldsInRecords(self, records, name):
705 for record
in records:
706 record[record.schema.join(name,
'flux')] = np.random.random()
707 record[record.schema.join(name,
'fluxSigma')] = np.random.random()
710 assert len(records) > 1
711 records[0][record.schema.join(name,
'flux')] = -1
713 def _compareFieldsInRecords(self, inSrc, outSrc, name):
714 fluxName, fluxSigmaName = inSrc.schema.join(name,
'flux'), inSrc.schema.join(name,
'fluxSigma')
715 if inSrc[fluxName] > 0:
716 mag, magErr = self.
calexp.getCalib().getMagnitude(inSrc[fluxName], inSrc[fluxSigmaName])
717 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag)
718 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], magErr)
720 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
721 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
726 def _setFieldsInRecords(self, records, name):
727 for record
in records:
728 record[record.schema.join(name,
'x')] = np.random.random()
729 record[record.schema.join(name,
'y')] = np.random.random()
732 for fieldSuffix
in (
'xSigma',
'ySigma',
'x_y_Cov'):
733 fieldName = record.schema.join(name, fieldSuffix)
734 if fieldName
in record.schema:
735 record[fieldName] = np.random.random()
737 def _compareFieldsInRecords(self, inSrc, outSrc, name):
738 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
739 centroidResult = centroidResultKey.get(inSrc)
741 coord = lsst.afw.table.CoordKey(outSrc.schema[self.
name]).get(outSrc)
742 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
743 self.assertEqual(coordTruth, coord)
748 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
749 [
"ra",
"dec"]).get(outSrc)
750 except lsst.pex.exceptions.NotFoundError:
751 self.assertFalse(centroidResultKey.getCentroidErr().isValid())
753 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.afw.geom.radians)
754 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
755 centroidResult.getCentroidErr()),
756 transform.getLinear().getMatrix().transpose())
757 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)
def makeSingleFrameMeasurementConfig(self, plugin=None, dependencies=())
def transform(self, wcs, kwds)
Create a copy of the dataset transformed to a new WCS, with new Psf and Calib.
def __init__(self, bbox, threshold=10.0, exposure=None, kwds)
Initialize the dataset.
A subtask for measuring the properties of sources on a single exposure, using an existing "reference"...
A subtask for measuring the properties of sources on a single exposure.
def addBlend(self)
Return a context manager that allows a blend of multiple sources to be added.
def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, fluxMag0=1E12)
Create an Exposure, with a Calib, Wcs, and Psf, but no pixel values set.
def makeSingleFrameMeasurementTask(self, plugin=None, dependencies=(), config=None, schema=None, algMetadata=None)
def __init__(self, owner)
def makePerturbedWcs(oldWcs, minScaleFactor=1.2, maxScaleFactor=1.5, minRotation=None, maxRotation=None, minRefShift=None, maxRefShift=None, minPixShift=2.0, maxPixShift=4.0)
Create a new undistorted TanWcs that is similar but not identical to another, with random scaling...
def drawGaussian(bbox, flux, ellipse)
Create an image of an elliptical Gaussian.
def _installFootprint(self, record, image)
def addSource(self, flux, centroid, shape=None)
Add a source to the simulation.
A simulated dataset consisting of a test image and an associated truth catalog.
def makeForcedMeasurementTask(self, plugin=None, dependencies=(), config=None, refSchema=None, algMetadata=None)
def realize(self, noise, schema)
Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints.
def addChild(self, flux, centroid, shape=None)
Add a child source to the blend, and return the truth catalog record that corresponds to it...
def __exit__(self, type_, value, tb)
def makeForcedMeasurementConfig(self, plugin=None, dependencies=())
def makeMinimalSchema(cls)
A Python context manager used to add multiple overlapping sources along with a parent source that rep...