35 from .sfm
import SingleFrameMeasurementTask
36 from .forcedMeasurement
import ForcedMeasurementTask
37 from .baseLib
import CentroidResultKey
39 __all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
40 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
41 "FluxTransformTestCase",
"CentroidTransformTestCase")
45 A Python context manager used to add multiple overlapping sources along with a parent source
46 that represents all of them together.
48 This is used as the return value for TestDataset.addBlend(), and this is the only way it should
49 be used. The only public method is addChild().
55 self.
parentImage = lsst.afw.image.ImageF(self.owner.exposure.getBBox())
62 def addChild(self, flux, centroid, shape=None):
64 Add a child source to the blend, and return the truth catalog record that corresponds to it.
66 @param[in] flux Total flux of the source to be added.
67 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D).
68 @param[in] shape 2nd moments of the source before PSF convolution
69 (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog
70 records post-convolution moments)
72 record, image = self.owner.addSource(flux, centroid, shape)
73 record.set(self.owner.keys[
"parent"], self.parentRecord.getId())
75 self.children.append((record, image))
84 self.parentRecord.set(self.owner.keys[
"nChild"], len(self.
children))
88 flux += record.get(self.owner.keys[
"flux"])
89 self.parentRecord.set(self.owner.keys[
"flux"], flux)
94 w = record.get(self.owner.keys[
"flux"])/flux
95 x += record.get(self.owner.keys[
"centroid"].getX())*w
96 y += record.get(self.owner.keys[
"centroid"].getY())*w
103 w = record.get(self.owner.keys[
"flux"])/flux
104 dx = record.get(self.owner.keys[
"centroid"].getX()) - x
105 dy = record.get(self.owner.keys[
"centroid"].getY()) - y
106 xx += (record.get(self.owner.keys[
"shape"].getIxx()) + dx**2)*w
107 yy += (record.get(self.owner.keys[
"shape"].getIyy()) + dy**2)*w
108 xy += (record.get(self.owner.keys[
"shape"].getIxy()) + dx*dy)*w
114 deblend = lsst.afw.image.MaskedImageF(self.owner.exposure.getMaskedImage(),
True)
116 deblend.getImage().getArray()[:,:] = image.getArray()
117 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.parentRecord.getFootprint(), deblend)
118 record.setFootprint(heavyFootprint)
123 A simulated dataset consisting of a test image and an associated truth catalog.
125 TestDataset creates an idealized image made of pure Gaussians (including a Gaussian PSF),
126 with simple noise and idealized Footprints/HeavyFootprints that simulated the outputs
127 of detection and deblending. Multiple noise realizations can be created from the same
128 underlying sources, allowing uncertainty estimates to be verified via Monte Carlo.
132 bbox = lsst.afw.geom.Box2I(lsst.afw.geom.Point2I(0,0), lsst.afw.geom.Point2I(100, 100))
133 dataset = TestDataset(bbox)
134 dataset.addSource(flux=1E5, centroid=lsst.afw.geom.Point2D(25, 26))
135 dataset.addSource(flux=2E5, centroid=lsst.afw.geom.Point2D(75, 24),
136 shape=lsst.afw.geom.ellipses.Quadrupole(8, 7, 2))
137 with dataset.addBlend() as family:
138 family.addChild(flux=2E5, centroid=lsst.afw.geom.Point2D(50, 72))
139 family.addChild(flux=1.5E5, centroid=lsst.afw.geom.Point2D(51, 74))
140 exposure, catalog = dataset.realize(noise=100.0, schema=TestDataset.makeMinimalSchema())
146 """Return the minimal schema needed to hold truth catalog fields.
148 When TestDataset.realize() is called, the schema must include at least these fields.
149 Usually it will include additional fields for measurement algorithm outputs, allowing
150 the same catalog to be used for both truth values (the fields from the minimal schema)
151 and the measurements.
153 if not hasattr(cls,
"_schema"):
156 cls.keys[
"parent"] = schema.find(
"parent").key
157 cls.keys[
"nChild"] = schema.addField(
"deblend_nchild", type=int)
158 cls.keys[
"flux"] = schema.addField(
"truth_flux", type=float, doc=
"true flux", units=
"dn")
159 cls.keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
160 schema,
"truth",
"true simulated centroid",
"pixels"
163 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.PIXEL
165 cls.keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
166 doc=
"set if the object is a star")
167 schema.getAliasMap().set(
"slot_Shape",
"truth")
168 schema.getAliasMap().set(
"slot_Centroid",
"truth")
169 schema.getAliasMap().set(
"slot_ModelFlux",
"truth")
170 schema.getCitizen().markPersistent()
173 schema.disconnectAliases()
178 minRotation=
None, maxRotation=
None,
179 minRefShift=
None, maxRefShift=
None,
180 minPixShift=2.0, maxPixShift=4.0):
182 Create a new undistorted TanWcs that is similar but not identical to another, with random
183 scaling, rotation, and offset (in both pixel position and reference position).
185 The maximum and minimum arguments are interpreted as absolute values for a split
186 range that covers both positive and negative values (as this method is used
187 in testing, it is typically most important to avoid perturbations near zero).
188 Scale factors are treated somewhat differently: the actual scale factor is chosen between
189 minScaleFactor and maxScaleFactor OR (1/maxScaleFactor) and (1/minScaleFactor).
191 The default range for rotation is 30-60 degrees, and the default range for reference shift
192 is 0.5-1.0 arcseconds (these cannot be safely included directly as default values because Angle
193 objects are mutable).
195 if minRotation
is None: minRotation = 30.0*lsst.afw.geom.degrees
196 if maxRotation
is None: maxRotation = 60.0*lsst.afw.geom.degrees
197 if minRefShift
is None: minRefShift = 0.5*lsst.afw.geom.arcseconds
198 if maxRefShift
is None: maxRefShift = 1.0*lsst.afw.geom.arcseconds
199 def splitRandom(min1, max1, min2=None, max2=None):
200 if min2
is None: min2 = -max1
201 if max2
is None: max2 = -min1
202 if numpy.random.uniform() > 0.5:
203 return float(numpy.random.uniform(min1, max1))
205 return float(numpy.random.uniform(min2, max2))
207 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
208 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.afw.geom.radians
209 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
210 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.afw.geom.radians
211 pixShiftX = splitRandom(minPixShift, maxPixShift)
212 pixShiftY = splitRandom(minPixShift, maxPixShift)
217 newTransform = oldTransform*rTransform*sTransform
218 matrix = newTransform.getMatrix()
220 oldSkyOrigin = oldWcs.getSkyOrigin().toIcrs()
222 oldSkyOrigin.getDec() + refShiftDec)
224 oldPixOrigin = oldWcs.getPixelOrigin()
226 oldPixOrigin.getY() + pixShiftY)
228 matrix[0,0], matrix[0,1], matrix[1,0], matrix[1,1])
231 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, fluxMag0=1E12):
233 Create an Exposure, with a Calib, Wcs, and Psf, but no pixel values set.
235 @param[in] bbox Bounding box of the image (image coordinates) as returned by makeCatalog.
236 @param[in] wcs New Wcs for the exposure (created from crval and cdelt if None).
237 @param[in] crval afw.coord.Coord: center of the TAN WCS attached to the image.
238 @param[in] cdelt afw.geom.Angle: pixel scale of the image
239 @param[in] psfSigma Radius (sigma) of the Gaussian PSF attached to the image
240 @param[in] psfDim Width and height of the image's Gaussian PSF attached to the image
241 @param[in] fluxMag0 Flux at magnitude zero (in e-) used to set the Calib of the exposure.
247 cdelt = 0.2*lsst.afw.geom.arcseconds
250 exposure = lsst.afw.image.ExposureF(bbox)
253 calib.setFluxMag0(fluxMag0)
256 exposure.setCalib(calib)
262 Create an image of an elliptical Gaussian.
264 @param[in,out] bbox Bounding box of image to create.
265 @param[in] flux Total flux of the Gaussian (normalized analytically, not using pixel
267 @param[in] ellipse lsst.afw.geom.ellipses.Ellipse holding the centroid and shape.
269 x, y = numpy.meshgrid(numpy.arange(bbox.getBeginX(), bbox.getEndX()),
270 numpy.arange(bbox.getBeginY(), bbox.getEndY()))
271 t = ellipse.getGridTransform()
272 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
273 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
274 image = lsst.afw.image.ImageF(bbox)
275 image.getArray()[:,:] = numpy.exp(-0.5*(xt**2 + yt**2))*flux/(2.0*ellipse.getCore().getArea())
278 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
280 Initialize the dataset.
282 @param[in] bbox Bounding box of the test image.
283 @param[in] threshold Threshold absolute value used to determine footprints for
284 simulated sources. This thresholding will be applied before noise is
285 actually added to images (or before the noise level is even known), so
286 this will necessarily produce somewhat artificial footprints.
287 @param[in] exposure lsst.afw.image.ExposureF test sources should be added to. Ownership should
288 be considered transferred from the caller to the TestDataset.
289 Must have a Gaussian Psf for truth catalog shapes to be exact.
290 @param[in] **kwds Keyword arguments forwarded to makeEmptyExposure if exposure is None.
296 self.
psfShape = self.exposure.getPsf().computeShape()
301 """Create a Footprint for a simulated source and add it to its truth catalog record.
308 fpSet.setMask(self.exposure.getMaskedImage().getMask(),
"DETECTED")
310 if len(fpSet.getFootprints()) > 1:
311 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
312 if len(fpSet.getFootprints()) == 0:
313 raise RuntimeError(
"Threshold value results in zero Footprints for object")
314 record.setFootprint(fpSet.getFootprints()[0])
318 Add a source to the simulation
320 @param[in] flux Total flux of the source to be added.
321 @param[in] centroid Position of the source to be added (lsst.afw.geom.Point2D).
322 @param[in] shape 2nd moments of the source before PSF convolution
323 (lsst.afw.geom.ellipses.Quadrupole). Note that the truth catalog
324 records post-convolution moments). If None, a point source
327 @return a truth catalog record and single-source image corresponding to the new source.
330 record = self.catalog.addNew()
331 record.set(self.keys[
"flux"], flux)
332 record.set(self.keys[
"centroid"], centroid)
334 record.set(self.keys[
"isStar"],
True)
337 record.set(self.keys[
"isStar"],
False)
338 fullShape = shape.convolve(self.
psfShape)
339 record.set(self.keys[
"shape"], fullShape)
341 image = self.
drawGaussian(self.exposure.getBBox(), flux,
346 self.exposure.getMaskedImage().getImage().getArray()[:,:] += image.getArray()
351 Return a context manager that allows a blend of multiple sources to be added.
356 with d.addBlend() as b:
357 b.addChild(flux1, centroid1)
358 b.addChild(flux2, centroid2, shape2)
361 Note that nothing stops you from creating overlapping sources just using the addSource() method,
362 but addBlend() is necesssary to create a parent object and deblended HeavyFootprints of the type
363 produced by the detection and deblending pipelines.
369 Create a copy of the dataset transformed to a new WCS, with new Psf and Calib.
371 @param[in] wcs Wcs for the new dataset.
372 @param[in] **kwds Additional keyword arguments passed on to makeEmptyExposure. If not
373 specified, these revert to the defaults for makeEmptyExposure, not the
374 values in the current dataset.
382 oldCalib = self.exposure.getCalib()
383 newCalib = result.exposure.getCalib()
384 oldPsfShape = self.exposure.getPsf().computeShape()
386 if record.get(self.keys[
"nChild"]):
387 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
388 magnitude = oldCalib.getMagnitude(record.get(self.keys[
"flux"]))
389 newFlux = newCalib.getFlux(magnitude)
390 oldCentroid = record.get(self.keys[
"centroid"])
391 newCentroid = xyt.forwardTransform(oldCentroid)
392 if record.get(self.keys[
"isStar"]):
393 newDeconvolvedShape =
None
395 affine = xyt.linearizeForwardTransform(oldCentroid)
396 oldFullShape = record.get(self.keys[
"shape"])
398 oldFullShape.getIxx() - oldPsfShape.getIxx(),
399 oldFullShape.getIyy() - oldPsfShape.getIyy(),
400 oldFullShape.getIxy() - oldPsfShape.getIxy(),
403 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
404 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
409 Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints.
411 @param[in] noise Standard deviation of noise to be added to the exposure. The noise will be
412 Gaussian and constant, appropriate for the sky-limited regime.
413 @param[in] schema Schema of the new catalog to be created. Must start with self.schema (i.e.
414 schema.contains(self.schema) must be True), but typically contains fields for
415 already-configured measurement algorithms as well.
417 @return a tuple of (exposure, catalog)
419 assert schema.contains(self.
schema)
421 mapper.addMinimalSchema(self.
schema,
True)
422 exposure = self.exposure.clone()
423 exposure.getMaskedImage().getVariance().getArray()[:,:] = noise**2
424 exposure.getMaskedImage().getImage().getArray()[:,:] \
425 += numpy.random.randn(exposure.getHeight(), exposure.getWidth())*noise
427 catalog.extend(self.
catalog, mapper=mapper)
430 for record
in catalog:
432 if record.getParent() == 0:
continue
434 parent = catalog.find(record.getParent())
435 footprint = parent.getFootprint()
436 parentFluxArrayNoNoise = numpy.zeros(footprint.getArea(), dtype=numpy.float32)
439 self.exposure.getMaskedImage().getImage().getArray(),
440 parentFluxArrayNoNoise,
441 self.exposure.getXY0()
443 parentFluxArrayNoisy = numpy.zeros(footprint.getArea(), dtype=numpy.float32)
446 exposure.getMaskedImage().getImage().getArray(),
447 parentFluxArrayNoisy,
450 oldHeavy = lsst.afw.detection.HeavyFootprintF.cast(record.getFootprint())
451 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
454 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
455 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
456 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
457 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
458 record.setFootprint(newHeavy)
459 return exposure, catalog
476 numpy.random.seed(cls.randomSeed)
479 """Convenience function to create a Config instance for SingleFrameMeasurementTask
481 The plugin and its dependencies will be the only plugins run, while the Centroid, Shape,
482 and ModelFlux slots will be set to the truth fields generated by the TestDataset class.
484 config = SingleFrameMeasurementTask.ConfigClass()
485 config.slots.centroid =
"truth"
486 config.slots.shape =
"truth"
487 config.slots.modelFlux =
None
488 config.slots.apFlux =
None
489 config.slots.psfFlux =
None
490 config.slots.instFlux =
None
491 config.slots.calibFlux =
None
492 config.plugins.names = (plugin,) + tuple(dependencies)
497 """Convenience function to create a SingleFrameMeasurementTask with a simple configuration.
501 raise ValueError(
"Either plugin or config argument must not be None")
504 schema = TestDataset.makeMinimalSchema()
506 schema.setAliasMap(
None)
507 if algMetadata
is None:
509 return SingleFrameMeasurementTask(schema=schema, algMetadata=algMetadata, config=config)
513 """Convenience function to create a Config instance for ForcedMeasurementTask
515 In addition to the plugins specified in the plugin and dependencies arguments,
516 the TransformedCentroid and TransformedShape plugins will be run and used as the
517 Centroid and Shape slots; these simply transform the reference catalog centroid
518 and shape to the measurement coordinate system.
520 config = ForcedMeasurementTask.ConfigClass()
521 config.slots.centroid =
"base_TransformedCentroid"
522 config.slots.shape =
"base_TransformedShape"
523 config.slots.modelFlux =
None
524 config.slots.apFlux =
None
525 config.slots.psfFlux =
None
526 config.slots.instFlux =
None
527 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
528 "base_TransformedShape")
533 """Convenience function to create a ForcedMeasurementTask with a simple configuration.
537 raise ValueError(
"Either plugin or config argument must not be None")
539 if refSchema
is None:
540 refSchema = TestDataset.makeMinimalSchema()
541 if algMetadata
is None:
543 return ForcedMeasurementTask(refSchema=refSchema, algMetadata=algMetadata, config=config)
548 Base class for testing measurement transformations.
550 We test both that the transform itself operates successfully (fluxes are
551 converted to magnitudes, flags are propagated properly) and that the
552 transform is registered as the default for the appropriate measurement
555 In the simple case of one-measurement-per-transformation, the developer
556 need not directly write any tests themselves: simply customizing the class
557 variables is all that is required. More complex measurements (e.g.
558 multiple aperture fluxes) require extra effort.
563 name =
"MeasurementTransformTest"
567 algorithmClass =
None
568 transformClass =
None
572 flagNames = (
"flag",)
577 singleFramePlugins = ()
582 self.
calexp = TestDataset.makeEmptyExposure(bbox)
583 self._setupTransform()
594 for flagValue
in (
True,
False):
595 records.append(self.inputCat.addNew())
596 for baseName
in baseNames:
598 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
599 records[-1].set(records[-1].schema.join(baseName, flagName), flagValue)
600 self._setFieldsInRecords(records, baseName)
603 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
604 for baseName
in baseNames:
605 self._compareFieldsInRecords(inSrc, outSrc, baseName)
607 keyName = outSrc.schema.join(baseName, flagName)
608 if keyName
in inSrc.schema:
609 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
611 self.assertFalse(keyName
in outSrc.schema)
615 self.outputCat.extend(self.inputCat, mapper=self.mapper)
616 self.transform(self.inputCat, self.outputCat, self.calexp.getWcs(), self.calexp.getCalib())
620 Test the operation of the transformation on a catalog containing random data.
624 * An appropriate exception is raised on an attempt to transform between catalogs with different
626 * Otherwise, all appropriate conversions are properly appled and that flags have been propagated.
628 The `baseNames` argument requires some explanation. This should be an iterable of the leading parts of
629 the field names for each measurement; that is, everything that appears before `_flux`, `_flag`, etc.
630 In the simple case of a single measurement per plugin, this is simply equal to `self.name` (thus
631 measurements are stored as `self.name + "_flux"`, etc). More generally, the developer may specify
632 whatever iterable they require. For example, to handle multiple apertures, we could have
633 `(self.name + "_0", self.name + "_1", ...)`.
635 @param[in] baseNames Iterable of the initial parts of measurement field names.
637 baseNames = baseNames
or [self.
name]
639 self.assertRaises(lsst.pex.exceptions.LengthError, self.
_runTransform,
False)
646 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
650 Test that the transformation is appropriately registered with the relevant measurement algorithms.
665 inputSchema.getAliasMap().set(
"slot_Centroid",
"dummy")
666 inputSchema.getAliasMap().set(
"slot_Shape",
"dummy")
667 self.algorithmClass(self.
control, self.name, inputSchema)
668 inputSchema.getAliasMap().
erase(
"slot_Centroid")
669 inputSchema.getAliasMap().
erase(
"slot_Shape")
684 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"dummy")
685 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Shape",
"dummy")
687 inputMapper.editOutputSchema().getAliasMap().
erase(
"slot_Centroid")
688 inputMapper.editOutputSchema().getAliasMap().
erase(
"slot_Shape")
697 for record
in records:
698 record[record.schema.join(name,
'flux')] = numpy.random.random()
699 record[record.schema.join(name,
'fluxSigma')] = numpy.random.random()
702 assert len(records) > 1
703 records[0][record.schema.join(name,
'flux')] = -1
706 fluxName, fluxSigmaName = inSrc.schema.join(name,
'flux'), inSrc.schema.join(name,
'fluxSigma')
707 if inSrc[fluxName] > 0:
708 mag, magErr = self.calexp.getCalib().getMagnitude(inSrc[fluxName], inSrc[fluxSigmaName])
709 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag)
710 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], magErr)
712 self.assertTrue(numpy.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
713 self.assertTrue(numpy.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
718 for record
in records:
719 record[record.schema.join(name,
'x')] = numpy.random.random()
720 record[record.schema.join(name,
'y')] = numpy.random.random()
723 for fieldSuffix
in (
'xSigma',
'ySigma',
'x_y_Cov'):
724 fieldName = record.schema.join(name, fieldSuffix)
725 if fieldName
in record.schema:
726 record[fieldName] = numpy.random.random()
729 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
730 centroidResult = centroidResultKey.get(inSrc)
733 coordTruth = self.calexp.getWcs().pixelToSky(centroidResult.getCentroid())
734 self.assertEqual(coordTruth, coord)
739 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
740 [
"ra",
"dec"]).get(outSrc)
741 except lsst.pex.exceptions.NotFoundError:
742 self.assertFalse(centroidResultKey.getCentroidErr().isValid())
744 transform = self.calexp.getWcs().linearizePixelToSky(coordTruth, lsst.afw.geom.radians)
745 coordErrTruth = numpy.dot(numpy.dot(transform.getLinear().getMatrix(),
746 centroidResult.getCentroidErr()),
747 transform.getLinear().getMatrix().transpose())
748 numpy.testing.assert_array_almost_equal(numpy.array(coordErrTruth), coordErr)
An ellipse core with quadrupole moments as parameters.
Defines the fields and offsets for a table.
def addBlend
Return a context manager that allows a blend of multiple sources to be added.
A custom container class for records, based on std::vector.
Class for storing ordered metadata with comments.
A mapping between the keys of two Schemas, used to copy data between them.
def addSource
Add a source to the simulation.
def addChild
Add a child source to the blend, and return the truth catalog record that corresponds to it...
def drawGaussian
Create an image of an elliptical Gaussian.
def makeEmptyExposure
Create an Exposure, with a Calib, Wcs, and Psf, but no pixel values set.
A Threshold is used to pass a threshold value to detection algorithms.
An integer coordinate rectangle.
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
def transform
Create a copy of the dataset transformed to a new WCS, with new Psf and Calib.
An ellipse defined by an arbitrary BaseCore and a center point.
def makeSingleFrameMeasurementTask
Wcs::Ptr makeWcs(coord::Coord const &crval, geom::Point2D const &crpix, double CD11, double CD12, double CD21, double CD22)
Create a Wcs object from crval, crpix, CD, using CD elements (useful from python) ...
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Subclass of unittest.TestCase that adds some custom assertions for convenience.
A simulated dataset consisting of a test image and an associated truth catalog.
def makeForcedMeasurementTask
ndarray::Array< typename boost::remove_const< T >::type, N-1, N-1 > flattenArray(Footprint const &fp, ndarray::Array< T, N, C > const &src, lsst::afw::geom::Point2I const &xy0=lsst::afw::geom::Point2I())
Flatten the first two dimensions of an array Use this footprint to map 2-D points in the source to 1-...
def realize
Create a simulated with noise and a simulated post-detection catalog with (Heavy)Footprints.
def __init__
Initialize the dataset.
def makePerturbedWcs
Create a new undistorted TanWcs that is similar but not identical to another, with random scaling...
def makeForcedMeasurementConfig
A floating-point coordinate rectangle geometry.
A class to handle Icrs coordinates (inherits from Coord)
A circularly symmetric Gaussian Psf class with no spatial variation, intended mostly for testing purp...
def makeSingleFrameMeasurementConfig
A FunctorKey used to get or set celestial coordiantes from a pair of Angle keys.
A Python context manager used to add multiple overlapping sources along with a parent source that rep...