31from .sfm
import SingleFrameMeasurementTask
32from .forcedMeasurement
import ForcedMeasurementTask
33from .
import CentroidResultKey
35__all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
36 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
37 "FluxTransformTestCase",
"CentroidTransformTestCase")
41 """Context manager which adds multiple overlapping sources and a parent.
45 This is used
as the
return value
for `TestDataset.addBlend`,
and this
is
46 the only way it should be used.
59 def addChild(self, instFlux, centroid, shape=None):
60 """Add a child to the blend; return corresponding truth catalog record.
63 Total instFlux of the source to be added.
65 Position of the source to be added.
66 shape : `lsst.afw.geom.Quadrupole`
67 Second moments of the source before PSF convolution. Note that
68 the truth catalog records post-convolution moments)
70 record, image = self.owner.addSource(instFlux, centroid, shape)
73 self.
children.append((record, image))
89 instFlux += record.get(self.
owner.keys[
"instFlux"])
95 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
96 x += record.get(self.
owner.keys[
"centroid"].getX())*w
97 y += record.get(self.
owner.keys[
"centroid"].getY())*w
104 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
105 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
106 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
107 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
108 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
109 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
110 self.
parentRecord.set(self.
owner.keys[
"shape"], lsst.afw.geom.Quadrupole(xx, yy, xy))
115 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.getMaskedImage(),
True)
117 deblend.getImage().getArray()[:, :] = image.getArray()
118 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
119 record.setFootprint(heavyFootprint)
123 """A simulated dataset consisuting of test image and truth catalog.
125 TestDataset creates an idealized image made of pure Gaussians (including a
126 Gaussian PSF), with simple noise
and idealized Footprints/HeavyFootprints
127 that simulated the outputs of detection
and deblending. Multiple noise
128 realizations can be created
from the same underlying sources, allowing
129 uncertainty estimates to be verified via Monte Carlo.
134 Bounding box of the test image.
136 Threshold absolute value used to determine footprints
for
137 simulated sources. This thresholding will be applied before noise
is
138 actually added to images (
or before the noise level
is even known), so
139 this will necessarily produce somewhat artificial footprints.
140 exposure : `lsst.afw.image.ExposureF`
141 The image to which test sources should be added. Ownership should
142 be considered transferred
from the caller to the TestDataset.
143 Must have a Gaussian PSF
for truth catalog shapes to be exact.
145 Keyword arguments forwarded to makeEmptyExposure
if exposure
is `
None`.
158 shape=lsst.afw.geom.Quadrupole(8, 7, 2))
159 with dataset.addBlend()
as family:
162 exposure, catalog = dataset.realize(noise=100.0,
163 schema=TestDataset.makeMinimalSchema())
168 """Return the minimal schema needed to hold truth catalog fields.
172 When `TestDataset.realize` is called, the schema must include at least
173 these fields. Usually it will include additional fields
for
174 measurement algorithm outputs, allowing the same catalog to be used
175 for both truth values (the fields
from the minimal schema)
and the
178 if not hasattr(cls,
"_schema"):
181 cls.
keys[
"parent"] = schema.find(
"parent").key
182 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
183 cls.
keys[
"instFlux"] = schema.addField(
"truth_instFlux", type=np.float64,
184 doc=
"true instFlux", units=
"count")
185 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
186 schema,
"truth",
"true simulated centroid",
"pixel"
188 cls.
keys[
"centroid_sigma"] = lsst.afw.table.CovarianceMatrix2fKey.addFields(
189 schema,
"truth", [
'x',
'y'],
"pixel"
191 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
192 doc=
"set if the object is a star")
194 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
196 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
197 doc=
"set if the object is a star")
198 schema.getAliasMap().set(
"slot_Shape",
"truth")
199 schema.getAliasMap().set(
"slot_Centroid",
"truth")
200 schema.getAliasMap().set(
"slot_ModelFlux",
"truth")
203 schema.disconnectAliases()
208 minRotation=None, maxRotation=None,
209 minRefShift=None, maxRefShift=None,
210 minPixShift=2.0, maxPixShift=4.0, randomSeed=1):
211 """Return a perturbed version of the input WCS.
213 Create a new undistorted TAN WCS that is similar but
not identical to
214 another,
with random scaling, rotation,
and offset (
in both pixel
215 position
and reference position).
221 minScaleFactor : `float`
222 Minimum scale factor to apply to the input WCS.
223 maxScaleFactor : `float`
224 Maximum scale factor to apply to the input WCS.
226 Minimum rotation to apply to the input WCS. If `
None`, defaults to
229 Minimum rotation to apply to the input WCS. If `
None`, defaults to
232 Miniumum shift to apply to the input WCS reference value. If
233 `
None`, defaults to 0.5 arcsec.
235 Miniumum shift to apply to the input WCS reference value. If
236 `
None`, defaults to 1.0 arcsec.
237 minPixShift : `float`
238 Minimum shift to apply to the input WCS reference pixel.
239 maxPixShift : `float`
240 Maximum shift to apply to the input WCS reference pixel.
247 A perturbed version of the input WCS.
251 The maximum
and minimum arguments are interpreted
as absolute values
252 for a split range that covers both positive
and negative values (
as
253 this method
is used
in testing, it
is typically most important to
254 avoid perturbations near zero). Scale factors are treated somewhat
255 differently: the actual scale factor
is chosen between
256 ``minScaleFactor``
and ``maxScaleFactor`` OR (``1/maxScaleFactor``)
257 and (``1/minScaleFactor``).
259 The default range
for rotation
is 30-60 degrees,
and the default range
260 for reference shift
is 0.5-1.0 arcseconds (these cannot be safely
261 included directly
as default values because Angle objects are
264 The random number generator
is primed
with the seed given. If
265 `
None`, a seed
is automatically chosen.
267 random_state = np.random.RandomState(randomSeed)
268 if minRotation
is None:
269 minRotation = 30.0*lsst.geom.degrees
270 if maxRotation
is None:
271 maxRotation = 60.0*lsst.geom.degrees
272 if minRefShift
is None:
273 minRefShift = 0.5*lsst.geom.arcseconds
274 if maxRefShift
is None:
275 maxRefShift = 1.0*lsst.geom.arcseconds
277 def splitRandom(min1, max1, min2=None, max2=None):
282 if random_state.uniform() > 0.5:
283 return float(random_state.uniform(min1, max1))
285 return float(random_state.uniform(min2, max2))
287 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
288 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.geom.radians
289 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
290 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
291 pixShiftX = splitRandom(minPixShift, maxPixShift)
292 pixShiftY = splitRandom(minPixShift, maxPixShift)
297 newTransform = oldTransform*rTransform*sTransform
298 matrix = newTransform.getMatrix()
300 oldSkyOrigin = oldWcs.getSkyOrigin()
302 oldSkyOrigin.getDec() + refShiftDec)
304 oldPixOrigin = oldWcs.getPixelOrigin()
306 oldPixOrigin.getY() + pixShiftY)
310 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, calibration=4):
311 """Create an Exposure, with a PhotoCalib, Wcs, and Psf, but no pixel values.
316 Bounding box of the image
in image coordinates.
318 New WCS
for the exposure (created
from CRVAL
and CDELT
if `
None`).
319 crval : `lsst.afw.geom.SpherePoint`, optional
320 ICRS center of the TAN WCS attached to the image. If `
None`, (45
321 degrees, 45 degrees)
is assumed.
323 Pixel scale of the image. If `
None`, 0.2 arcsec
is assumed.
324 psfSigma : `float`, optional
325 Radius (sigma) of the Gaussian PSF attached to the image
326 psfDim : `int`, optional
327 Width
and height of the image
's Gaussian PSF attached to the image
328 calibration : `float`, optional
329 The spatially-constant calibration (in nJy/count) to set the
330 PhotoCalib of the exposure.
334 exposure : `lsst.age.image.ExposureF`
341 cdelt = 0.2*lsst.geom.arcseconds
345 exposure = lsst.afw.image.ExposureF(bbox)
350 exposure.setPhotoCalib(photoCalib)
355 """Create an image of an elliptical Gaussian.
360 Bounding box of image to create.
362 Total instrumental flux of the Gaussian (normalized analytically,
363 not using pixel values).
364 ellipse : `lsst.afw.geom.Ellipse`
365 Defines the centroid
and shape.
369 image : `lsst.afw.image.ImageF`
370 An image of the Gaussian.
372 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
373 np.arange(bbox.getBeginY(), bbox.getEndY()))
374 t = ellipse.getGridTransform()
375 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
376 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
377 image = lsst.afw.image.ImageF(bbox)
378 image.getArray()[:, :] = np.exp(-0.5*(xt**2 + yt**2))*instFlux/(2.0*ellipse.getCore().getArea())
381 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
390 def _installFootprint(self, record, image, setPeakSignificance=True):
391 """Create simulated Footprint and add it to a truth catalog record.
394 if setPeakSignificance:
395 schema.addField(
"significance", type=float,
396 doc=
"Ratio of peak value to configured standard deviation.")
401 if setPeakSignificance:
404 for footprint
in fpSet.getFootprints():
405 footprint.updatePeakSignificance(self.
threshold.getValue())
407 fpSet.setMask(self.
exposure.getMaskedImage().getMask(),
"DETECTED")
409 if len(fpSet.getFootprints()) > 1:
410 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
411 if len(fpSet.getFootprints()) == 0:
412 raise RuntimeError(
"Threshold value results in zero Footprints for object")
413 record.setFootprint(fpSet.getFootprints()[0])
415 def addSource(self, instFlux, centroid, shape=None, setPeakSignificance=True):
416 """Add a source to the simulation.
421 Total instFlux of the source to be added.
423 Position of the source to be added.
424 shape : `lsst.afw.geom.Quadrupole`
425 Second moments of the source before PSF convolution. Note that the
426 truth catalog records post-convolution moments. If `None`, a point
427 source will be added.
428 setPeakSignificance : `bool`
429 Set the ``significance`` field
for peaks
in the footprints?
430 See ``lsst.meas.algorithms.SourceDetectionTask.setPeakSignificance``
431 for how this field
is computed
for real datasets.
436 A truth catalog record.
437 image : `lsst.afw.image.ImageF`
438 Single-source image corresponding to the new source.
442 record.set(self.
keys[
"instFlux"], instFlux)
443 record.set(self.
keys[
"centroid"], centroid)
444 covariance = np.random.normal(0, 0.1, 4).reshape(2, 2)
445 covariance[0, 1] = covariance[1, 0]
446 record.set(self.
keys[
"centroid_sigma"], covariance.astype(np.float32))
448 record.set(self.
keys[
"isStar"],
True)
451 record.set(self.
keys[
"isStar"],
False)
452 fullShape = shape.convolve(self.
psfShape)
453 record.set(self.
keys[
"shape"], fullShape)
456 lsst.afw.geom.Ellipse(fullShape, centroid))
460 self.
exposure.getMaskedImage().getImage().getArray()[:, :] += image.getArray()
464 """Return a context manager which can add a blend of multiple sources.
468 Note that nothing stops you from creating overlapping sources just using the
addSource() method,
469 but
addBlend()
is necesssary to create a parent object
and deblended HeavyFootprints of the type
470 produced by the detection
and deblending pipelines.
476 with d.addBlend()
as b:
477 b.addChild(flux1, centroid1)
478 b.addChild(flux2, centroid2, shape2)
483 """Copy this dataset transformed to a new WCS, with new Psf and PhotoCalib.
488 WCS for the new dataset.
490 Additional keyword arguments passed on to
491 `TestDataset.makeEmptyExposure`. If
not specified, these revert
492 to the defaults
for `~TestDataset.makeEmptyExposure`,
not the
493 values
in the current dataset.
497 newDataset : `TestDataset`
498 Transformed copy of this dataset.
506 oldPhotoCalib = self.
exposure.getPhotoCalib()
507 newPhotoCalib = result.exposure.getPhotoCalib()
508 oldPsfShape = self.
exposure.getPsf().computeShape(bboxD.getCenter())
510 if record.get(self.
keys[
"nChild"]):
511 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
512 magnitude = oldPhotoCalib.instFluxToMagnitude(record.get(self.
keys[
"instFlux"]))
513 newFlux = newPhotoCalib.magnitudeToInstFlux(magnitude)
514 oldCentroid = record.get(self.
keys[
"centroid"])
515 newCentroid = xyt.applyForward(oldCentroid)
516 if record.get(self.
keys[
"isStar"]):
517 newDeconvolvedShape =
None
520 oldFullShape = record.get(self.
keys[
"shape"])
521 oldDeconvolvedShape = lsst.afw.geom.Quadrupole(
522 oldFullShape.getIxx() - oldPsfShape.getIxx(),
523 oldFullShape.getIyy() - oldPsfShape.getIyy(),
524 oldFullShape.getIxy() - oldPsfShape.getIxy(),
527 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
528 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
531 def realize(self, noise, schema, randomSeed=1):
532 r"""Simulate an exposure and detection catalog for this dataset.
534 The simulation includes noise, and the detection catalog includes
535 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s.
540 Standard deviation of noise to be added to the exposure. The
541 noise will be Gaussian
and constant, appropriate
for the
544 Schema of the new catalog to be created. Must start
with
545 ``self.
schema`` (i.e. ``schema.contains(self.
schema)`` must be
546 `
True`), but typically contains fields
for already-configured
547 measurement algorithms
as well.
548 randomSeed : `int`, optional
549 Seed
for the random number generator.
550 If `
None`, a seed
is chosen automatically.
554 `exposure` : `lsst.afw.image.ExposureF`
557 Simulated detection catalog.
559 random_state = np.random.RandomState(randomSeed)
560 assert schema.contains(self.
schema)
562 mapper.addMinimalSchema(self.
schema,
True)
564 exposure.getMaskedImage().getVariance().getArray()[:, :] = noise**2
565 exposure.getMaskedImage().getImage().getArray()[:, :] \
566 += random_state.randn(exposure.getHeight(), exposure.getWidth())*noise
568 catalog.extend(self.
catalog, mapper=mapper)
571 for record
in catalog:
574 if record.getParent() == 0:
578 parent = catalog.find(record.getParent())
579 footprint = parent.getFootprint()
580 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
581 footprint.spans.flatten(parentFluxArrayNoNoise,
582 self.
exposure.getMaskedImage().getImage().getArray(),
584 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
585 footprint.spans.flatten(parentFluxArrayNoisy,
586 exposure.getMaskedImage().getImage().getArray(),
588 oldHeavy = record.getFootprint()
589 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
593 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
594 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
595 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
596 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
597 record.setFootprint(newHeavy)
598 return exposure, catalog
604 """Create an instance of `SingleFrameMeasurementTask.ConfigClass`.
606 Only the specified plugin and its dependencies will be run; the
607 Centroid, Shape,
and ModelFlux slots will be set to the truth fields
608 generated by the `TestDataset`
class.
613 Name of measurement plugin to enable.
614 dependencies : iterable of `str`, optional
615 Names of dependencies of the measurement plugin.
619 config : `SingleFrameMeasurementTask.ConfigClass`
620 The resulting task configuration.
622 config = SingleFrameMeasurementTask.ConfigClass()
623 config.slots.centroid = "truth"
624 config.slots.shape =
"truth"
625 config.slots.modelFlux =
None
626 config.slots.apFlux =
None
627 config.slots.psfFlux =
None
628 config.slots.gaussianFlux =
None
629 config.slots.calibFlux =
None
630 config.plugins.names = (plugin,) + tuple(dependencies)
635 """Create a configured instance of `SingleFrameMeasurementTask`.
639 plugin : `str`, optional
640 Name of measurement plugin to enable. If `None`, a configuration
641 must be supplied
as the ``config`` parameter. If both are
642 specified, ``config`` takes precedence.
643 dependencies : iterable of `str`, optional
644 Names of dependencies of the specified measurement plugin.
645 config : `SingleFrameMeasurementTask.ConfigClass`, optional
646 Configuration
for the task. If `
None`, a measurement plugin must
647 be supplied
as the ``plugin`` paramter. If both are specified,
648 ``config`` takes precedence.
650 Measurement table schema. If `
None`, a default schema
is
653 Measurement algorithm metadata. If `
None`, a default container
658 task : `SingleFrameMeasurementTask`
659 A configured instance of the measurement task.
663 raise ValueError(
"Either plugin or config argument must not be None")
666 schema = TestDataset.makeMinimalSchema()
668 schema.setAliasMap(
None)
669 if algMetadata
is None:
674 """Create an instance of `ForcedMeasurementTask.ConfigClass`.
676 In addition to the plugins specified in the plugin
and dependencies
677 arguments, the `TransformedCentroid`
and `TransformedShape` plugins
678 will be run
and used
as the centroid
and shape slots; these simply
679 transform the reference catalog centroid
and shape to the measurement
685 Name of measurement plugin to enable.
686 dependencies : iterable of `str`, optional
687 Names of dependencies of the measurement plugin.
691 config : `ForcedMeasurementTask.ConfigClass`
692 The resulting task configuration.
695 config = ForcedMeasurementTask.ConfigClass()
696 config.slots.centroid = "base_TransformedCentroid"
697 config.slots.shape =
"base_TransformedShape"
698 config.slots.modelFlux =
None
699 config.slots.apFlux =
None
700 config.slots.psfFlux =
None
701 config.slots.gaussianFlux =
None
702 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
703 "base_TransformedShape")
708 """Create a configured instance of `ForcedMeasurementTask`.
712 plugin : `str`, optional
713 Name of measurement plugin to enable. If `None`, a configuration
714 must be supplied
as the ``config`` parameter. If both are
715 specified, ``config`` takes precedence.
716 dependencies : iterable of `str`, optional
717 Names of dependencies of the specified measurement plugin.
718 config : `SingleFrameMeasurementTask.ConfigClass`, optional
719 Configuration
for the task. If `
None`, a measurement plugin must
720 be supplied
as the ``plugin`` paramter. If both are specified,
721 ``config`` takes precedence.
723 Reference table schema. If `
None`, a default schema
is
726 Measurement algorithm metadata. If `
None`, a default container
731 task : `ForcedMeasurementTask`
732 A configured instance of the measurement task.
736 raise ValueError(
"Either plugin or config argument must not be None")
738 if refSchema
is None:
739 refSchema = TestDataset.makeMinimalSchema()
740 if algMetadata
is None:
746 """Base class for testing measurement transformations.
750 We test both that the transform itself operates successfully (fluxes are
751 converted to magnitudes, flags are propagated properly) and that the
752 transform
is registered
as the default
for the appropriate measurement
755 In the simple case of one-measurement-per-transformation, the developer
756 need
not directly write any tests themselves: simply customizing the
class
757 variables
is all that
is required. More complex measurements (e.g.
758 multiple aperture fluxes) require extra effort.
760 name = "MeasurementTransformTest"
761 """The name used for the measurement algorithm (str).
765 This determines the names of the fields in the resulting catalog. This
766 default should generally be fine, but subclasses can override
if
772 algorithmClass =
None
773 transformClass =
None
775 flagNames = (
"flag",)
776 """Flags which may be set by the algorithm being tested (iterable of `str`).
782 singleFramePlugins = ()
787 self.
calexp = TestDataset.makeEmptyExposure(bbox)
788 self._setupTransform()
797 def _populateCatalog(self, baseNames):
799 for flagValue
in (
True,
False):
800 records.append(self.inputCat.addNew())
801 for baseName
in baseNames:
803 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
804 records[-1].set(records[-1].schema.join(baseName, flagName), flagValue)
805 self._setFieldsInRecords(records, baseName)
807 def _checkOutput(self, baseNames):
808 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
809 for baseName
in baseNames:
810 self._compareFieldsInRecords(inSrc, outSrc, baseName)
812 keyName = outSrc.schema.join(baseName, flagName)
813 if keyName
in inSrc.schema:
814 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
816 self.assertFalse(keyName
in outSrc.schema)
818 def _runTransform(self, doExtend=True):
820 self.outputCat.extend(self.inputCat, mapper=self.mapper)
821 self.transform(self.inputCat, self.outputCat, self.
calexp.getWcs(), self.
calexp.getPhotoCalib())
824 """Test the transformation on a catalog containing random data.
828 baseNames : iterable of `str`
829 Iterable of the initial parts of measurement field names.
835 - An appropriate exception is raised on an attempt to transform
836 between catalogs
with different numbers of rows;
837 - Otherwise, all appropriate conversions are properly appled
and that
838 flags have been propagated.
840 The ``baseNames`` argument requires some explanation. This should be
841 an iterable of the leading parts of the field names
for each
842 measurement; that
is, everything that appears before ``_instFlux``,
843 ``_flag``, etc. In the simple case of a single measurement per plugin,
844 this
is simply equal to ``self.
name`` (thus measurements are stored
as
845 ``self.
name +
"_instFlux"``, etc). More generally, the developer may
846 specify whatever iterable they require. For example, to handle
847 multiple apertures, we could have ``(self.
name +
"_0", self.
name +
850 baseNames = baseNames or [self.
name]
856 def _checkRegisteredTransform(self, registry, name):
859 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
862 """Test that the transformation is appropriately registered.
872 def _setupTransform(self):
878 inputSchema.getAliasMap().set(
"slot_Centroid",
"dummy")
879 inputSchema.getAliasMap().set(
"slot_Shape",
"dummy")
880 self.algorithmClass(self.
control, self.name, inputSchema)
881 inputSchema.getAliasMap().erase(
"slot_Centroid")
882 inputSchema.getAliasMap().erase(
"slot_Shape")
891 def _setupTransform(self):
898 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Centroid",
"dummy")
899 inputMapper.editOutputSchema().getAliasMap().set(
"slot_Shape",
"dummy")
901 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Centroid")
902 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Shape")
911 def _setFieldsInRecords(self, records, name):
912 for record
in records:
913 record[record.schema.join(name,
'instFlux')] = np.random.random()
914 record[record.schema.join(name,
'instFluxErr')] = np.random.random()
917 assert len(records) > 1
918 records[0][record.schema.join(name,
'instFlux')] = -1
920 def _compareFieldsInRecords(self, inSrc, outSrc, name):
921 instFluxName = inSrc.schema.join(name,
'instFlux')
922 instFluxErrName = inSrc.schema.join(name,
'instFluxErr')
923 if inSrc[instFluxName] > 0:
924 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
925 inSrc[instFluxErrName])
926 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag.value)
927 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
930 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
931 if np.isnan(inSrc[instFluxErrName]):
932 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
934 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
935 inSrc[instFluxErrName])
936 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
941 def _setFieldsInRecords(self, records, name):
942 for record
in records:
943 record[record.schema.join(name,
'x')] = np.random.random()
944 record[record.schema.join(name,
'y')] = np.random.random()
947 for fieldSuffix
in (
'xErr',
'yErr',
'x_y_Cov'):
948 fieldName = record.schema.join(name, fieldSuffix)
949 if fieldName
in record.schema:
950 record[fieldName] = np.random.random()
952 def _compareFieldsInRecords(self, inSrc, outSrc, name):
953 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
954 centroidResult = centroidResultKey.get(inSrc)
957 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
958 self.assertEqual(coordTruth, coord)
963 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
964 [
"ra",
"dec"]).get(outSrc)
966 self.assertFalse(centroidResultKey.getCentroidErr().isValid())
968 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.geom.radians)
969 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
970 centroidResult.getCentroidErr()),
971 transform.getLinear().getMatrix().transpose())
972 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)
static afw::table::Schema makeMinimalSchema()
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
static Schema makeMinimalSchema()
def makeSingleFrameMeasurementConfig(self, plugin=None, dependencies=())
def makeForcedMeasurementConfig(self, plugin=None, dependencies=())
def makeForcedMeasurementTask(self, plugin=None, dependencies=(), config=None, refSchema=None, algMetadata=None)
def makeSingleFrameMeasurementTask(self, plugin=None, dependencies=(), config=None, schema=None, algMetadata=None)
def addChild(self, instFlux, centroid, shape=None)
def __init__(self, owner)
def __exit__(self, type_, value, tb)
def makeMinimalSchema(cls)
def addSource(self, instFlux, centroid, shape=None, setPeakSignificance=True)
def __init__(self, bbox, threshold=10.0, exposure=None, **kwds)
def _installFootprint(self, record, image, setPeakSignificance=True)
def drawGaussian(bbox, instFlux, ellipse)
def makePerturbedWcs(oldWcs, minScaleFactor=1.2, maxScaleFactor=1.5, minRotation=None, maxRotation=None, minRefShift=None, maxRefShift=None, minPixShift=2.0, maxPixShift=4.0, randomSeed=1)
def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, calibration=4)
def realize(self, noise, schema, randomSeed=1)
def transform(self, wcs, **kwds)
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)