33from .sfm
import SingleFrameMeasurementTask
34from .forcedMeasurement
import ForcedMeasurementTask
35from .
import CentroidResultKey
37__all__ = (
"BlendContext",
"TestDataset",
"AlgorithmTestCase",
"TransformTestCase",
38 "SingleFramePluginTransformSetupHelper",
"ForcedPluginTransformSetupHelper",
39 "FluxTransformTestCase",
"CentroidTransformTestCase")
43 """Context manager which adds multiple overlapping sources and a parent.
47 This is used
as the
return value
for `TestDataset.addBlend`,
and this
is
48 the only way it should be used.
61 def addChild(self, instFlux, centroid, shape=None):
62 """Add a child to the blend; return corresponding truth catalog record.
65 Total instFlux of the source to be added.
67 Position of the source to be added.
69 Second moments of the source before PSF convolution. Note that
70 the truth catalog records post-convolution moments)
72 record, image = self.owner.addSource(instFlux, centroid, shape)
75 self.
children.append((record, image))
91 instFlux += record.get(self.
owner.keys[
"instFlux"])
97 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
98 x += record.get(self.
owner.keys[
"centroid"].getX())*w
99 y += record.get(self.
owner.keys[
"centroid"].getY())*w
106 w = record.get(self.
owner.keys[
"instFlux"])/instFlux
107 dx = record.get(self.
owner.keys[
"centroid"].getX()) - x
108 dy = record.get(self.
owner.keys[
"centroid"].getY()) - y
109 xx += (record.get(self.
owner.keys[
"shape"].getIxx()) + dx**2)*w
110 yy += (record.get(self.
owner.keys[
"shape"].getIyy()) + dy**2)*w
111 xy += (record.get(self.
owner.keys[
"shape"].getIxy()) + dx*dy)*w
117 deblend = lsst.afw.image.MaskedImageF(self.
owner.exposure.getMaskedImage(),
True)
119 deblend.getImage().getArray()[:, :] = image.getArray()
120 heavyFootprint = lsst.afw.detection.HeavyFootprintF(self.
parentRecord.getFootprint(), deblend)
121 record.setFootprint(heavyFootprint)
125 """A simulated dataset consisuting of test image and truth catalog.
127 TestDataset creates an idealized image made of pure Gaussians (including a
128 Gaussian PSF), with simple noise
and idealized Footprints/HeavyFootprints
129 that simulated the outputs of detection
and deblending. Multiple noise
130 realizations can be created
from the same underlying sources, allowing
131 uncertainty estimates to be verified via Monte Carlo.
136 Bounding box of the test image.
138 Threshold absolute value used to determine footprints
for
139 simulated sources. This thresholding will be applied before noise
is
140 actually added to images (
or before the noise level
is even known), so
141 this will necessarily produce somewhat artificial footprints.
142 exposure : `lsst.afw.image.ExposureF`
143 The image to which test sources should be added. Ownership should
144 be considered transferred
from the caller to the TestDataset.
145 Must have a Gaussian PSF
for truth catalog shapes to be exact.
147 Keyword arguments forwarded to makeEmptyExposure
if exposure
is `
None`.
161 with dataset.addBlend()
as family:
164 exposure, catalog = dataset.realize(noise=100.0,
165 schema=TestDataset.makeMinimalSchema())
170 """Return the minimal schema needed to hold truth catalog fields.
174 When `TestDataset.realize` is called, the schema must include at least
175 these fields. Usually it will include additional fields
for
176 measurement algorithm outputs, allowing the same catalog to be used
177 for both truth values (the fields
from the minimal schema)
and the
180 if not hasattr(cls,
"_schema"):
183 cls.
keys[
"parent"] = schema.find(
"parent").key
184 cls.
keys[
"nChild"] = schema.addField(
"deblend_nChild", type=np.int32)
185 cls.
keys[
"instFlux"] = schema.addField(
"truth_instFlux", type=np.float64,
186 doc=
"true instFlux", units=
"count")
187 cls.
keys[
"centroid"] = lsst.afw.table.Point2DKey.addFields(
188 schema,
"truth",
"true simulated centroid",
"pixel"
190 cls.
keys[
"centroid_sigma"] = lsst.afw.table.CovarianceMatrix2fKey.addFields(
191 schema,
"truth", [
'x',
'y'],
"pixel"
193 cls.
keys[
"centroid_flag"] = schema.addField(
"truth_flag", type=
"Flag",
194 doc=
"set if the object is a star")
196 schema,
"truth",
"true shape after PSF convolution", lsst.afw.table.CoordinateType.PIXEL
198 cls.
keys[
"isStar"] = schema.addField(
"truth_isStar", type=
"Flag",
199 doc=
"set if the object is a star")
200 schema.getAliasMap().
set(
"slot_Shape",
"truth")
201 schema.getAliasMap().
set(
"slot_Centroid",
"truth")
202 schema.getAliasMap().
set(
"slot_ModelFlux",
"truth")
205 schema.disconnectAliases()
210 minRotation=None, maxRotation=None,
211 minRefShift=None, maxRefShift=None,
212 minPixShift=2.0, maxPixShift=4.0, randomSeed=1):
213 """Return a perturbed version of the input WCS.
215 Create a new undistorted TAN WCS that is similar but
not identical to
216 another,
with random scaling, rotation,
and offset (
in both pixel
217 position
and reference position).
223 minScaleFactor : `float`
224 Minimum scale factor to apply to the input WCS.
225 maxScaleFactor : `float`
226 Maximum scale factor to apply to the input WCS.
228 Minimum rotation to apply to the input WCS. If `
None`, defaults to
231 Minimum rotation to apply to the input WCS. If `
None`, defaults to
234 Miniumum shift to apply to the input WCS reference value. If
235 `
None`, defaults to 0.5 arcsec.
237 Miniumum shift to apply to the input WCS reference value. If
238 `
None`, defaults to 1.0 arcsec.
239 minPixShift : `float`
240 Minimum shift to apply to the input WCS reference pixel.
241 maxPixShift : `float`
242 Maximum shift to apply to the input WCS reference pixel.
249 A perturbed version of the input WCS.
253 The maximum
and minimum arguments are interpreted
as absolute values
254 for a split range that covers both positive
and negative values (
as
255 this method
is used
in testing, it
is typically most important to
256 avoid perturbations near zero). Scale factors are treated somewhat
257 differently: the actual scale factor
is chosen between
258 ``minScaleFactor``
and ``maxScaleFactor`` OR (``1/maxScaleFactor``)
259 and (``1/minScaleFactor``).
261 The default range
for rotation
is 30-60 degrees,
and the default range
262 for reference shift
is 0.5-1.0 arcseconds (these cannot be safely
263 included directly
as default values because Angle objects are
266 The random number generator
is primed
with the seed given. If
267 `
None`, a seed
is automatically chosen.
269 random_state = np.random.RandomState(randomSeed)
270 if minRotation
is None:
271 minRotation = 30.0*lsst.geom.degrees
272 if maxRotation
is None:
273 maxRotation = 60.0*lsst.geom.degrees
274 if minRefShift
is None:
275 minRefShift = 0.5*lsst.geom.arcseconds
276 if maxRefShift
is None:
277 maxRefShift = 1.0*lsst.geom.arcseconds
279 def splitRandom(min1, max1, min2=None, max2=None):
284 if random_state.uniform() > 0.5:
285 return float(random_state.uniform(min1, max1))
287 return float(random_state.uniform(min2, max2))
289 scaleFactor = splitRandom(minScaleFactor, maxScaleFactor, 1.0/maxScaleFactor, 1.0/minScaleFactor)
290 rotation = splitRandom(minRotation.asRadians(), maxRotation.asRadians())*lsst.geom.radians
291 refShiftRa = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
292 refShiftDec = splitRandom(minRefShift.asRadians(), maxRefShift.asRadians())*lsst.geom.radians
293 pixShiftX = splitRandom(minPixShift, maxPixShift)
294 pixShiftY = splitRandom(minPixShift, maxPixShift)
299 newTransform = oldTransform*rTransform*sTransform
300 matrix = newTransform.getMatrix()
302 oldSkyOrigin = oldWcs.getSkyOrigin()
304 oldSkyOrigin.getDec() + refShiftDec)
306 oldPixOrigin = oldWcs.getPixelOrigin()
308 oldPixOrigin.getY() + pixShiftY)
312 def makeEmptyExposure(bbox, wcs=None, crval=None, cdelt=None, psfSigma=2.0, psfDim=17, calibration=4):
313 """Create an Exposure, with a PhotoCalib, Wcs, and Psf, but no pixel values.
318 Bounding box of the image
in image coordinates.
320 New WCS
for the exposure (created
from CRVAL
and CDELT
if `
None`).
321 crval : `lsst.afw.geom.SpherePoint`, optional
322 ICRS center of the TAN WCS attached to the image. If `
None`, (45
323 degrees, 45 degrees)
is assumed.
325 Pixel scale of the image. If `
None`, 0.2 arcsec
is assumed.
326 psfSigma : `float`, optional
327 Radius (sigma) of the Gaussian PSF attached to the image
328 psfDim : `int`, optional
329 Width
and height of the image
's Gaussian PSF attached to the image
330 calibration : `float`, optional
331 The spatially-constant calibration (in nJy/count) to set the
332 PhotoCalib of the exposure.
336 exposure : `lsst.age.image.ExposureF`
343 cdelt = 0.2*lsst.geom.arcseconds
347 exposure = lsst.afw.image.ExposureF(bbox)
352 exposure.setPhotoCalib(photoCalib)
357 """Create an image of an elliptical Gaussian.
362 Bounding box of image to create.
364 Total instrumental flux of the Gaussian (normalized analytically,
365 not using pixel values).
367 Defines the centroid
and shape.
371 image : `lsst.afw.image.ImageF`
372 An image of the Gaussian.
374 x, y = np.meshgrid(np.arange(bbox.getBeginX(), bbox.getEndX()),
375 np.arange(bbox.getBeginY(), bbox.getEndY()))
376 t = ellipse.getGridTransform()
377 xt = t[t.XX] * x + t[t.XY] * y + t[t.X]
378 yt = t[t.YX] * x + t[t.YY] * y + t[t.Y]
379 image = lsst.afw.image.ImageF(bbox)
380 image.getArray()[:, :] = np.exp(-0.5*(xt**2 + yt**2))*instFlux/(2.0*ellipse.getCore().getArea())
383 def __init__(self, bbox, threshold=10.0, exposure=None, **kwds):
392 def _installFootprint(self, record, image, setPeakSignificance=True):
393 """Create simulated Footprint and add it to a truth catalog record.
396 if setPeakSignificance:
397 schema.addField(
"significance", type=float,
398 doc=
"Ratio of peak value to configured standard deviation.")
403 if setPeakSignificance:
406 for footprint
in fpSet.getFootprints():
407 footprint.updatePeakSignificance(self.
threshold.getValue())
409 fpSet.setMask(self.
exposure.getMaskedImage().getMask(),
"DETECTED")
411 if len(fpSet.getFootprints()) > 1:
412 raise RuntimeError(
"Threshold value results in multiple Footprints for a single object")
413 if len(fpSet.getFootprints()) == 0:
414 raise RuntimeError(
"Threshold value results in zero Footprints for object")
415 record.setFootprint(fpSet.getFootprints()[0])
417 def addSource(self, instFlux, centroid, shape=None, setPeakSignificance=True):
418 """Add a source to the simulation.
423 Total instFlux of the source to be added.
425 Position of the source to be added.
427 Second moments of the source before PSF convolution. Note that the
428 truth catalog records post-convolution moments. If `None`, a point
429 source will be added.
430 setPeakSignificance : `bool`
431 Set the ``significance`` field
for peaks
in the footprints?
432 See ``lsst.meas.algorithms.SourceDetectionTask.setPeakSignificance``
433 for how this field
is computed
for real datasets.
438 A truth catalog record.
439 image : `lsst.afw.image.ImageF`
440 Single-source image corresponding to the new source.
444 record.set(self.
keys[
"instFlux"], instFlux)
445 record.set(self.
keys[
"centroid"], centroid)
446 covariance = np.random.normal(0, 0.1, 4).reshape(2, 2)
447 covariance[0, 1] = covariance[1, 0]
448 record.set(self.
keys[
"centroid_sigma"], covariance.astype(np.float32))
450 record.set(self.
keys[
"isStar"],
True)
453 record.set(self.
keys[
"isStar"],
False)
454 fullShape = shape.convolve(self.
psfShape)
455 record.set(self.
keys[
"shape"], fullShape)
462 self.
exposure.getMaskedImage().getImage().getArray()[:, :] += image.getArray()
466 """Return a context manager which can add a blend of multiple sources.
470 Note that nothing stops you from creating overlapping sources just using the
addSource() method,
471 but
addBlend()
is necesssary to create a parent object
and deblended HeavyFootprints of the type
472 produced by the detection
and deblending pipelines.
478 with d.addBlend()
as b:
479 b.addChild(flux1, centroid1)
480 b.addChild(flux2, centroid2, shape2)
485 """Copy this dataset transformed to a new WCS, with new Psf and PhotoCalib.
490 WCS for the new dataset.
492 Additional keyword arguments passed on to
493 `TestDataset.makeEmptyExposure`. If
not specified, these revert
494 to the defaults
for `~TestDataset.makeEmptyExposure`,
not the
495 values
in the current dataset.
499 newDataset : `TestDataset`
500 Transformed copy of this dataset.
508 oldPhotoCalib = self.
exposure.getPhotoCalib()
509 newPhotoCalib = result.exposure.getPhotoCalib()
510 oldPsfShape = self.
exposure.getPsf().computeShape(bboxD.getCenter())
512 if record.get(self.
keys[
"nChild"]):
513 raise NotImplementedError(
"Transforming blended sources in TestDatasets is not supported")
514 magnitude = oldPhotoCalib.instFluxToMagnitude(record.get(self.
keys[
"instFlux"]))
515 newFlux = newPhotoCalib.magnitudeToInstFlux(magnitude)
516 oldCentroid = record.get(self.
keys[
"centroid"])
517 newCentroid = xyt.applyForward(oldCentroid)
518 if record.get(self.
keys[
"isStar"]):
519 newDeconvolvedShape =
None
522 oldFullShape = record.get(self.
keys[
"shape"])
524 oldFullShape.getIxx() - oldPsfShape.getIxx(),
525 oldFullShape.getIyy() - oldPsfShape.getIyy(),
526 oldFullShape.getIxy() - oldPsfShape.getIxy(),
529 newDeconvolvedShape = oldDeconvolvedShape.transform(affine.getLinear())
530 result.addSource(newFlux, newCentroid, newDeconvolvedShape)
533 def realize(self, noise, schema, randomSeed=1):
534 r"""Simulate an exposure and detection catalog for this dataset.
536 The simulation includes noise, and the detection catalog includes
537 `~lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s.
542 Standard deviation of noise to be added to the exposure. The
543 noise will be Gaussian
and constant, appropriate
for the
546 Schema of the new catalog to be created. Must start
with
547 ``self.
schema`` (i.e. ``schema.contains(self.
schema)`` must be
548 `
True`), but typically contains fields
for already-configured
549 measurement algorithms
as well.
550 randomSeed : `int`, optional
551 Seed
for the random number generator.
552 If `
None`, a seed
is chosen automatically.
556 `exposure` : `lsst.afw.image.ExposureF`
559 Simulated detection catalog.
561 random_state = np.random.RandomState(randomSeed)
562 assert schema.contains(self.
schema)
564 mapper.addMinimalSchema(self.
schema,
True)
566 exposure.getMaskedImage().getVariance().getArray()[:, :] = noise**2
567 exposure.getMaskedImage().getImage().getArray()[:, :] \
568 += random_state.randn(exposure.getHeight(), exposure.getWidth())*noise
570 catalog.extend(self.
catalog, mapper=mapper)
573 for record
in catalog:
576 if record.getParent() == 0:
580 parent = catalog.find(record.getParent())
581 footprint = parent.getFootprint()
582 parentFluxArrayNoNoise = np.zeros(footprint.getArea(), dtype=np.float32)
583 footprint.spans.flatten(parentFluxArrayNoNoise,
584 self.
exposure.getMaskedImage().getImage().getArray(),
586 parentFluxArrayNoisy = np.zeros(footprint.getArea(), dtype=np.float32)
587 footprint.spans.flatten(parentFluxArrayNoisy,
588 exposure.getMaskedImage().getImage().getArray(),
590 oldHeavy = record.getFootprint()
591 fraction = (oldHeavy.getImageArray() / parentFluxArrayNoNoise)
595 newHeavy = lsst.afw.detection.HeavyFootprintF(oldHeavy)
596 newHeavy.getImageArray()[:] = parentFluxArrayNoisy*fraction
597 newHeavy.getMaskArray()[:] = oldHeavy.getMaskArray()
598 newHeavy.getVarianceArray()[:] = oldHeavy.getVarianceArray()
599 record.setFootprint(newHeavy)
600 return exposure, catalog
604 """Base class for tests of measurement tasks.
607 """Create an instance of `SingleFrameMeasurementTask.ConfigClass`.
609 Only the specified plugin and its dependencies will be run; the
610 Centroid, Shape,
and ModelFlux slots will be set to the truth fields
611 generated by the `TestDataset`
class.
616 Name of measurement plugin to enable.
617 dependencies : iterable of `str`, optional
618 Names of dependencies of the measurement plugin.
622 config : `SingleFrameMeasurementTask.ConfigClass`
623 The resulting task configuration.
625 config = SingleFrameMeasurementTask.ConfigClass()
626 with warnings.catch_warnings():
627 warnings.filterwarnings(
"ignore", message=
"ignoreSlotPluginChecks", category=FutureWarning)
628 config = SingleFrameMeasurementTask.ConfigClass(ignoreSlotPluginChecks=
True)
629 config.slots.centroid =
"truth"
630 config.slots.shape =
"truth"
631 config.slots.modelFlux =
None
632 config.slots.apFlux =
None
633 config.slots.psfFlux =
None
634 config.slots.gaussianFlux =
None
635 config.slots.calibFlux =
None
636 config.plugins.names = (plugin,) + tuple(dependencies)
641 """Create a configured instance of `SingleFrameMeasurementTask`.
645 plugin : `str`, optional
646 Name of measurement plugin to enable. If `None`, a configuration
647 must be supplied
as the ``config`` parameter. If both are
648 specified, ``config`` takes precedence.
649 dependencies : iterable of `str`, optional
650 Names of dependencies of the specified measurement plugin.
651 config : `SingleFrameMeasurementTask.ConfigClass`, optional
652 Configuration
for the task. If `
None`, a measurement plugin must
653 be supplied
as the ``plugin`` paramter. If both are specified,
654 ``config`` takes precedence.
656 Measurement table schema. If `
None`, a default schema
is
659 Measurement algorithm metadata. If `
None`, a default container
664 task : `SingleFrameMeasurementTask`
665 A configured instance of the measurement task.
669 raise ValueError(
"Either plugin or config argument must not be None")
672 schema = TestDataset.makeMinimalSchema()
674 schema.setAliasMap(
None)
675 if algMetadata
is None:
680 """Create an instance of `ForcedMeasurementTask.ConfigClass`.
682 In addition to the plugins specified in the plugin
and dependencies
683 arguments, the `TransformedCentroid`
and `TransformedShape` plugins
684 will be run
and used
as the centroid
and shape slots; these simply
685 transform the reference catalog centroid
and shape to the measurement
691 Name of measurement plugin to enable.
692 dependencies : iterable of `str`, optional
693 Names of dependencies of the measurement plugin.
697 config : `ForcedMeasurementTask.ConfigClass`
698 The resulting task configuration.
701 config = ForcedMeasurementTask.ConfigClass()
702 config.slots.centroid = "base_TransformedCentroid"
703 config.slots.shape =
"base_TransformedShape"
704 config.slots.modelFlux =
None
705 config.slots.apFlux =
None
706 config.slots.psfFlux =
None
707 config.slots.gaussianFlux =
None
708 config.plugins.names = (plugin,) + tuple(dependencies) + (
"base_TransformedCentroid",
709 "base_TransformedShape")
714 """Create a configured instance of `ForcedMeasurementTask`.
718 plugin : `str`, optional
719 Name of measurement plugin to enable. If `None`, a configuration
720 must be supplied
as the ``config`` parameter. If both are
721 specified, ``config`` takes precedence.
722 dependencies : iterable of `str`, optional
723 Names of dependencies of the specified measurement plugin.
724 config : `SingleFrameMeasurementTask.ConfigClass`, optional
725 Configuration
for the task. If `
None`, a measurement plugin must
726 be supplied
as the ``plugin`` paramter. If both are specified,
727 ``config`` takes precedence.
729 Reference table schema. If `
None`, a default schema
is
732 Measurement algorithm metadata. If `
None`, a default container
737 task : `ForcedMeasurementTask`
738 A configured instance of the measurement task.
742 raise ValueError(
"Either plugin or config argument must not be None")
744 if refSchema
is None:
745 refSchema = TestDataset.makeMinimalSchema()
746 if algMetadata
is None:
752 """Base class for testing measurement transformations.
756 We test both that the transform itself operates successfully (fluxes are
757 converted to magnitudes, flags are propagated properly) and that the
758 transform
is registered
as the default
for the appropriate measurement
761 In the simple case of one-measurement-per-transformation, the developer
762 need
not directly write any tests themselves: simply customizing the
class
763 variables
is all that
is required. More complex measurements (e.g.
764 multiple aperture fluxes) require extra effort.
766 name = "MeasurementTransformTest"
767 """The name used for the measurement algorithm (str).
771 This determines the names of the fields in the resulting catalog. This
772 default should generally be fine, but subclasses can override
if
778 algorithmClass =
None
779 transformClass =
None
781 flagNames = (
"flag",)
782 """Flags which may be set by the algorithm being tested (iterable of `str`).
788 singleFramePlugins = ()
793 self.
calexp = TestDataset.makeEmptyExposure(bbox)
794 self._setupTransform()
803 def _populateCatalog(self, baseNames):
805 for flagValue
in (
True,
False):
806 records.append(self.inputCat.addNew())
807 for baseName
in baseNames:
809 if records[-1].schema.join(baseName, flagName)
in records[-1].schema:
810 records[-1].
set(records[-1].schema.join(baseName, flagName), flagValue)
811 self._setFieldsInRecords(records, baseName)
813 def _checkOutput(self, baseNames):
814 for inSrc, outSrc
in zip(self.inputCat, self.outputCat):
815 for baseName
in baseNames:
816 self._compareFieldsInRecords(inSrc, outSrc, baseName)
818 keyName = outSrc.schema.join(baseName, flagName)
819 if keyName
in inSrc.schema:
820 self.assertEqual(outSrc.get(keyName), inSrc.get(keyName))
822 self.assertFalse(keyName
in outSrc.schema)
824 def _runTransform(self, doExtend=True):
826 self.outputCat.extend(self.inputCat, mapper=self.mapper)
827 self.transform(self.inputCat, self.outputCat, self.
calexp.getWcs(), self.
calexp.getPhotoCalib())
830 """Test the transformation on a catalog containing random data.
834 baseNames : iterable of `str`
835 Iterable of the initial parts of measurement field names.
841 - An appropriate exception is raised on an attempt to transform
842 between catalogs
with different numbers of rows;
843 - Otherwise, all appropriate conversions are properly appled
and that
844 flags have been propagated.
846 The ``baseNames`` argument requires some explanation. This should be
847 an iterable of the leading parts of the field names
for each
848 measurement; that
is, everything that appears before ``_instFlux``,
849 ``_flag``, etc. In the simple case of a single measurement per plugin,
850 this
is simply equal to ``self.
name`` (thus measurements are stored
as
851 ``self.
name +
"_instFlux"``, etc). More generally, the developer may
852 specify whatever iterable they require. For example, to handle
853 multiple apertures, we could have ``(self.
name +
"_0", self.
name +
856 baseNames = baseNames or [self.
name]
862 def _checkRegisteredTransform(self, registry, name):
865 self.assertEqual(registry[name].PluginClass.getTransformClass(), self.
transformClass)
868 """Test that the transformation is appropriately registered.
878 def _setupTransform(self):
884 inputSchema.getAliasMap().
set(
"slot_Centroid",
"dummy")
885 inputSchema.getAliasMap().
set(
"slot_Shape",
"dummy")
886 self.algorithmClass(self.
control, self.name, inputSchema)
887 inputSchema.getAliasMap().erase(
"slot_Centroid")
888 inputSchema.getAliasMap().erase(
"slot_Shape")
897 def _setupTransform(self):
904 inputMapper.editOutputSchema().getAliasMap().
set(
"slot_Centroid",
"dummy")
905 inputMapper.editOutputSchema().getAliasMap().
set(
"slot_Shape",
"dummy")
907 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Centroid")
908 inputMapper.editOutputSchema().getAliasMap().erase(
"slot_Shape")
917 def _setFieldsInRecords(self, records, name):
918 for record
in records:
919 record[record.schema.join(name,
'instFlux')] = np.random.random()
920 record[record.schema.join(name,
'instFluxErr')] = np.random.random()
923 assert len(records) > 1
924 records[0][record.schema.join(name,
'instFlux')] = -1
926 def _compareFieldsInRecords(self, inSrc, outSrc, name):
927 instFluxName = inSrc.schema.join(name,
'instFlux')
928 instFluxErrName = inSrc.schema.join(name,
'instFluxErr')
929 if inSrc[instFluxName] > 0:
930 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
931 inSrc[instFluxErrName])
932 self.assertEqual(outSrc[outSrc.schema.join(name,
'mag')], mag.value)
933 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
936 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'mag')]))
937 if np.isnan(inSrc[instFluxErrName]):
938 self.assertTrue(np.isnan(outSrc[outSrc.schema.join(name,
'magErr')]))
940 mag = self.
calexp.getPhotoCalib().instFluxToMagnitude(inSrc[instFluxName],
941 inSrc[instFluxErrName])
942 self.assertEqual(outSrc[outSrc.schema.join(name,
'magErr')], mag.error)
947 def _setFieldsInRecords(self, records, name):
948 for record
in records:
949 record[record.schema.join(name,
'x')] = np.random.random()
950 record[record.schema.join(name,
'y')] = np.random.random()
953 for fieldSuffix
in (
'xErr',
'yErr',
'x_y_Cov'):
954 fieldName = record.schema.join(name, fieldSuffix)
955 if fieldName
in record.schema:
956 record[fieldName] = np.random.random()
958 def _compareFieldsInRecords(self, inSrc, outSrc, name):
959 centroidResultKey = CentroidResultKey(inSrc.schema[self.
name])
960 centroidResult = centroidResultKey.get(inSrc)
963 coordTruth = self.
calexp.getWcs().pixelToSky(centroidResult.getCentroid())
964 self.assertEqual(coordTruth, coord)
969 coordErr = lsst.afw.table.CovarianceMatrix2fKey(outSrc.schema[self.
name],
970 [
"ra",
"dec"]).get(outSrc)
972 self.assertFalse(centroidResultKey.getCentroidErr().
isValid())
974 transform = self.
calexp.getWcs().linearizePixelToSky(coordTruth, lsst.geom.radians)
975 coordErrTruth = np.dot(np.dot(transform.getLinear().getMatrix(),
976 centroidResult.getCentroidErr()),
977 transform.getLinear().getMatrix().transpose())
978 np.testing.assert_array_almost_equal(np.array(coordErrTruth), coordErr)
A circularly symmetric Gaussian Psf class with no spatial variation, intended mostly for testing purp...
static afw::table::Schema makeMinimalSchema()
Return a minimal schema for Peak tables and records.
A Threshold is used to pass a threshold value to detection algorithms.
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
An ellipse defined by an arbitrary BaseCore and a center point.
An ellipse core with quadrupole moments as parameters.
The photometric calibration of an exposure.
A FunctorKey used to get or set celestial coordinates from a pair of lsst::geom::Angle keys.
static QuadrupoleKey addFields(Schema &schema, std::string const &name, std::string const &doc, CoordinateType coordType=CoordinateType::PIXEL)
Add a set of quadrupole subfields to a schema and return a QuadrupoleKey that points to them.
Defines the fields and offsets for a table.
A mapping between the keys of two Schemas, used to copy data between them.
Record class that contains measurements made on a single exposure.
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Class for storing ordered metadata with comments.
A class representing an angle.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
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)
Reports attempts to exceed implementation-defined length limits for some classes.
Reports attempts to access elements using an invalid key.
daf::base::PropertySet * set
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Eigen::Matrix2d makeCdMatrix(lsst::geom::Angle const &scale, lsst::geom::Angle const &orientation=0 *lsst::geom::degrees, bool flipX=false)
Make a WCS CD matrix.
std::shared_ptr< TransformPoint2ToPoint2 > makeWcsPairTransform(SkyWcs const &src, SkyWcs const &dst)
A Transform obtained by putting two SkyWcs objects "back to back".
lsst::geom::AffineTransform linearizeTransform(TransformPoint2ToPoint2 const &original, lsst::geom::Point2D const &inPoint)
Approximate a Transform by its local linearization.