29import astropy.units
as u
34import lsst.pipe.base
as pipeBase
38from lsst.pipe.base
import Instrument
39from lsst.pipe.tasks.colorterms
import ColortermLibrary
40from lsst.verify
import Job, Measurement
43 LoadIndexedReferenceObjectsConfig)
49__all__ = [
"JointcalConfig",
"JointcalTask"]
51Photometry = collections.namedtuple(
'Photometry', (
'fit',
'model'))
52Astrometry = collections.namedtuple(
'Astrometry', (
'fit',
'model',
'sky_to_tan_projection'))
57 meas = Measurement(job.metrics[name], value)
58 job.measurements.insert(meas)
62 """Lookup function that asserts/hopes that a static calibration dataset
63 exists in a particular collection, since this task can
't provide a single
64 date/time to use to search for one properly.
66 This
is mostly useful
for the ``camera`` dataset,
in cases where the task
's
67 quantum dimensions do *not* include something temporal, like ``exposure``
72 datasetType : `lsst.daf.butler.DatasetType`
73 Type of dataset being searched
for.
74 registry : `lsst.daf.butler.Registry`
75 Data repository registry to search.
76 quantumDataId : `lsst.daf.butler.DataCoordinate`
77 Data ID of the quantum this camera should match.
78 collections : `Iterable` [ `str` ]
79 Collections that should be searched - but this lookup function works
80 by ignoring this
in favor of a more-
or-less hard-coded value.
84 refs : `Iterator` [ `lsst.daf.butler.DatasetRef` ]
85 Iterator over dataset references; should have only one element.
89 This implementation duplicates one
in fgcmcal,
and is at least quite
90 similar to another
in cp_pipe. This duplicate has the most documentation.
91 Fixing this
is DM-29661.
93 instrument = Instrument.fromName(quantumDataId["instrument"], registry)
94 unboundedCollection = instrument.makeUnboundedCalibrationRunName()
95 return registry.queryDatasets(datasetType,
97 collections=[unboundedCollection],
102 """Lookup function that finds all refcats for all visits that overlap a
103 tract, rather than just the refcats that directly overlap the tract.
107 datasetType : `lsst.daf.butler.DatasetType`
108 Type of dataset being searched for.
109 registry : `lsst.daf.butler.Registry`
110 Data repository registry to search.
111 quantumDataId : `lsst.daf.butler.DataCoordinate`
112 Data ID of the quantum; expected to be something we can use
as a
113 constraint to query
for overlapping visits.
114 collections : `Iterable` [ `str` ]
115 Collections to search.
119 refs : `Iterator` [ `lsst.daf.butler.DatasetRef` ]
120 Iterator over refcat references.
129 for visit_data_id
in set(registry.queryDataIds(
"visit", dataId=quantumDataId).expanded()):
131 registry.queryDatasets(
133 collections=collections,
134 dataId=visit_data_id,
142 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter")):
143 """Middleware input/output connections for jointcal data."""
144 inputCamera = pipeBase.connectionTypes.PrerequisiteInput(
145 doc=
"The camera instrument that took these observations.",
147 storageClass=
"Camera",
148 dimensions=(
"instrument",),
150 lookupFunction=lookupStaticCalibrations,
152 inputSourceTableVisit = pipeBase.connectionTypes.Input(
153 doc=
"Source table in parquet format, per visit",
154 name=
"sourceTable_visit",
155 storageClass=
"DataFrame",
156 dimensions=(
"instrument",
"visit"),
160 inputVisitSummary = pipeBase.connectionTypes.Input(
161 doc=(
"Per-visit consolidated exposure metadata built from calexps. "
162 "These catalogs use detector id for the id and must be sorted for "
163 "fast lookups of a detector."),
165 storageClass=
"ExposureCatalog",
166 dimensions=(
"instrument",
"visit"),
170 astrometryRefCat = pipeBase.connectionTypes.PrerequisiteInput(
171 doc=
"The astrometry reference catalog to match to loaded input catalog sources.",
172 name=
"gaia_dr2_20200414",
173 storageClass=
"SimpleCatalog",
174 dimensions=(
"skypix",),
177 lookupFunction=lookupVisitRefCats,
179 photometryRefCat = pipeBase.connectionTypes.PrerequisiteInput(
180 doc=
"The photometry reference catalog to match to loaded input catalog sources.",
181 name=
"ps1_pv3_3pi_20170110",
182 storageClass=
"SimpleCatalog",
183 dimensions=(
"skypix",),
186 lookupFunction=lookupVisitRefCats,
189 outputWcs = pipeBase.connectionTypes.Output(
190 doc=(
"Per-tract, per-visit world coordinate systems derived from the fitted model."
191 " These catalogs only contain entries for detectors with an output, and use"
192 " the detector id for the catalog id, sorted on id for fast lookups of a detector."),
193 name=
"jointcalSkyWcsCatalog",
194 storageClass=
"ExposureCatalog",
195 dimensions=(
"instrument",
"visit",
"skymap",
"tract"),
198 outputPhotoCalib = pipeBase.connectionTypes.Output(
199 doc=(
"Per-tract, per-visit photometric calibrations derived from the fitted model."
200 " These catalogs only contain entries for detectors with an output, and use"
201 " the detector id for the catalog id, sorted on id for fast lookups of a detector."),
202 name=
"jointcalPhotoCalibCatalog",
203 storageClass=
"ExposureCatalog",
204 dimensions=(
"instrument",
"visit",
"skymap",
"tract"),
212 for name
in (
"astrometry",
"photometry"):
213 vars()[f
"{name}_matched_fittedStars"] = pipeBase.connectionTypes.Output(
214 doc=f
"The number of cross-matched fittedStars for {name}",
215 name=f
"metricvalue_jointcal_{name}_matched_fittedStars",
216 storageClass=
"MetricValue",
217 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
219 vars()[f
"{name}_collected_refStars"] = pipeBase.connectionTypes.Output(
220 doc=f
"The number of {name} reference stars drawn from the reference catalog, before matching.",
221 name=f
"metricvalue_jointcal_{name}_collected_refStars",
222 storageClass=
"MetricValue",
223 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
225 vars()[f
"{name}_prepared_refStars"] = pipeBase.connectionTypes.Output(
226 doc=f
"The number of {name} reference stars matched to fittedStars.",
227 name=f
"metricvalue_jointcal_{name}_prepared_refStars",
228 storageClass=
"MetricValue",
229 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
231 vars()[f
"{name}_prepared_fittedStars"] = pipeBase.connectionTypes.Output(
232 doc=f
"The number of cross-matched fittedStars after cleanup, for {name}.",
233 name=f
"metricvalue_jointcal_{name}_prepared_fittedStars",
234 storageClass=
"MetricValue",
235 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
237 vars()[f
"{name}_prepared_ccdImages"] = pipeBase.connectionTypes.Output(
238 doc=f
"The number of ccdImages that will be fit for {name}, after cleanup.",
239 name=f
"metricvalue_jointcal_{name}_prepared_ccdImages",
240 storageClass=
"MetricValue",
241 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
243 vars()[f
"{name}_final_chi2"] = pipeBase.connectionTypes.Output(
244 doc=f
"The final chi2 of the {name} fit.",
245 name=f
"metricvalue_jointcal_{name}_final_chi2",
246 storageClass=
"MetricValue",
247 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
249 vars()[f
"{name}_final_ndof"] = pipeBase.connectionTypes.Output(
250 doc=f
"The number of degrees of freedom of the fitted {name}.",
251 name=f
"metricvalue_jointcal_{name}_final_ndof",
252 storageClass=
"MetricValue",
253 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter"),
264 if not config.doAstrometry:
265 self.prerequisiteInputs.remove(
"astrometryRefCat")
266 self.outputs.remove(
"outputWcs")
267 for key
in list(self.outputs):
268 if "metricvalue_jointcal_astrometry" in key:
269 self.outputs.remove(key)
270 if not config.doPhotometry:
271 self.prerequisiteInputs.remove(
"photometryRefCat")
272 self.outputs.remove(
"outputPhotoCalib")
273 for key
in list(self.outputs):
274 if "metricvalue_jointcal_photometry" in key:
275 self.outputs.remove(key)
279 pipelineConnections=JointcalTaskConnections):
280 """Configuration for JointcalTask"""
282 doAstrometry = pexConfig.Field(
283 doc=
"Fit astrometry and write the fitted result.",
287 doPhotometry = pexConfig.Field(
288 doc=
"Fit photometry and write the fitted result.",
292 sourceFluxType = pexConfig.Field(
294 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
295 default=
'apFlux_12_0'
297 positionErrorPedestal = pexConfig.Field(
298 doc=
"Systematic term to apply to the measured position error (pixels)",
302 photometryErrorPedestal = pexConfig.Field(
303 doc=
"Systematic term to apply to the measured error on flux or magnitude as a "
304 "fraction of source flux or magnitude delta (e.g. 0.05 is 5% of flux or +50 millimag).",
309 matchCut = pexConfig.Field(
310 doc=
"Matching radius between fitted and reference stars (arcseconds)",
314 minMeasurements = pexConfig.Field(
315 doc=
"Minimum number of associated measured stars for a fitted star to be included in the fit",
319 minMeasuredStarsPerCcd = pexConfig.Field(
320 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
324 minRefStarsPerCcd = pexConfig.Field(
325 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
329 allowLineSearch = pexConfig.Field(
330 doc=
"Allow a line search during minimization, if it is reasonable for the model"
331 " (models with a significant non-linear component, e.g. constrainedPhotometry).",
335 astrometrySimpleOrder = pexConfig.Field(
336 doc=
"Polynomial order for fitting the simple astrometry model.",
340 astrometryChipOrder = pexConfig.Field(
341 doc=
"Order of the per-chip transform for the constrained astrometry model.",
345 astrometryVisitOrder = pexConfig.Field(
346 doc=
"Order of the per-visit transform for the constrained astrometry model.",
350 useInputWcs = pexConfig.Field(
351 doc=
"Use the input calexp WCSs to initialize a SimpleAstrometryModel.",
355 astrometryModel = pexConfig.ChoiceField(
356 doc=
"Type of model to fit to astrometry",
358 default=
"constrained",
359 allowed={
"simple":
"One polynomial per ccd",
360 "constrained":
"One polynomial per ccd, and one polynomial per visit"}
362 photometryModel = pexConfig.ChoiceField(
363 doc=
"Type of model to fit to photometry",
365 default=
"constrainedMagnitude",
366 allowed={
"simpleFlux":
"One constant zeropoint per ccd and visit, fitting in flux space.",
367 "constrainedFlux":
"Constrained zeropoint per ccd, and one polynomial per visit,"
368 " fitting in flux space.",
369 "simpleMagnitude":
"One constant zeropoint per ccd and visit,"
370 " fitting in magnitude space.",
371 "constrainedMagnitude":
"Constrained zeropoint per ccd, and one polynomial per visit,"
372 " fitting in magnitude space.",
375 applyColorTerms = pexConfig.Field(
376 doc=
"Apply photometric color terms to reference stars?"
377 "Requires that colorterms be set to a ColortermLibrary",
381 colorterms = pexConfig.ConfigField(
382 doc=
"Library of photometric reference catalog name to color term dict.",
383 dtype=ColortermLibrary,
385 photometryVisitOrder = pexConfig.Field(
386 doc=
"Order of the per-visit polynomial transform for the constrained photometry model.",
390 photometryDoRankUpdate = pexConfig.Field(
391 doc=(
"Do the rank update step during minimization. "
392 "Skipping this can help deal with models that are too non-linear."),
396 astrometryDoRankUpdate = pexConfig.Field(
397 doc=(
"Do the rank update step during minimization (should not change the astrometry fit). "
398 "Skipping this can help deal with models that are too non-linear."),
402 outlierRejectSigma = pexConfig.Field(
403 doc=
"How many sigma to reject outliers at during minimization.",
407 astrometryOutlierRelativeTolerance = pexConfig.Field(
408 doc=(
"Convergence tolerance for outlier rejection threshold when fitting astrometry. Iterations will "
409 "stop when the fractional change in the chi2 cut level is below this value. If tolerance is set "
410 "to zero, iterations will continue until there are no more outliers. We suggest a value of 0.002"
411 "as a balance between a shorter minimization runtime and achieving a final fitted model that is"
412 "close to the solution found when removing all outliers."),
416 maxPhotometrySteps = pexConfig.Field(
417 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
421 maxAstrometrySteps = pexConfig.Field(
422 doc=
"Maximum number of minimize iterations to take when fitting astrometry.",
426 astrometryRefObjLoader = pexConfig.ConfigField(
427 dtype=LoadIndexedReferenceObjectsConfig,
428 doc=
"Reference object loader for astrometric fit",
430 photometryRefObjLoader = pexConfig.ConfigField(
431 dtype=LoadIndexedReferenceObjectsConfig,
432 doc=
"Reference object loader for photometric fit",
434 sourceSelector = sourceSelectorRegistry.makeField(
435 doc=
"How to select sources for cross-matching",
438 astrometryReferenceSelector = pexConfig.ConfigurableField(
439 target=ReferenceSourceSelectorTask,
440 doc=
"How to down-select the loaded astrometry reference catalog.",
442 photometryReferenceSelector = pexConfig.ConfigurableField(
443 target=ReferenceSourceSelectorTask,
444 doc=
"How to down-select the loaded photometry reference catalog.",
446 astrometryReferenceErr = pexConfig.Field(
447 doc=(
"Uncertainty on reference catalog coordinates [mas] to use in place of the `coord_*Err` fields. "
448 "If None, then raise an exception if the reference catalog is missing coordinate errors. "
449 "If specified, overrides any existing `coord_*Err` values."),
456 writeInitMatrix = pexConfig.Field(
458 doc=(
"Write the pre/post-initialization Hessian and gradient to text files, for debugging. "
459 "Output files will be written to `config.debugOutputPath` and will "
460 "be of the form 'astrometry_[pre|post]init-TRACT-FILTER-mat.txt'. "
461 "Note that these files are the dense versions of the matrix, and so may be very large."),
464 writeChi2FilesInitialFinal = pexConfig.Field(
466 doc=(
"Write .csv files containing the contributions to chi2 for the initialization and final fit. "
467 "Output files will be written to `config.debugOutputPath` and will "
468 "be of the form `astrometry_[initial|final]_chi2-TRACT-FILTER."),
471 writeChi2FilesOuterLoop = pexConfig.Field(
473 doc=(
"Write .csv files containing the contributions to chi2 for the outer fit loop. "
474 "Output files will be written to `config.debugOutputPath` and will "
475 "be of the form `astrometry_init-NN_chi2-TRACT-FILTER`."),
478 writeInitialModel = pexConfig.Field(
480 doc=(
"Write the pre-initialization model to text files, for debugging. "
481 "Output files will be written to `config.debugOutputPath` and will be "
482 "of the form `initial_astrometry_model-TRACT_FILTER.txt`."),
485 debugOutputPath = pexConfig.Field(
488 doc=(
"Path to write debug output files to. Used by "
489 "`writeInitialModel`, `writeChi2Files*`, `writeInitMatrix`.")
491 detailedProfile = pexConfig.Field(
494 doc=
"Output separate profiling information for different parts of jointcal, e.g. data read, fitting"
500 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary."
501 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
503 msg = (
"Only doing astrometry, but Colorterms are not applied for astrometry;"
504 "applyColorTerms=True will be ignored.")
505 logging.getLogger(
"lsst.jointcal").warning(msg)
517 self.
sourceSelector[
"science"].signalToNoise.fluxField = f
"{self.sourceFluxType}_instFlux"
518 self.
sourceSelector[
"science"].signalToNoise.errField = f
"{self.sourceFluxType}_instFluxErr"
521 self.
sourceSelector[
"science"].isolated.parentName =
"parentSourceId"
522 self.
sourceSelector[
"science"].isolated.nChildName =
"deblend_nChild"
526 badFlags = [
"pixelFlags_edge",
527 "pixelFlags_saturated",
528 "pixelFlags_interpolatedCenter",
529 "pixelFlags_interpolated",
530 "pixelFlags_crCenter",
532 "hsmPsfMoments_flag",
533 f
"{self.sourceFluxType}_flag",
544 """Write model to outfile."""
545 with open(filename,
"w")
as file:
546 file.write(repr(model))
547 log.info(
"Wrote %s to file: %s", model, filename)
550@dataclasses.dataclass
552 """The input data jointcal needs for each detector/visit."""
554 """The visit identifier of this exposure."""
556 """The catalog derived from this exposure."""
558 """The VisitInfo of this exposure."""
560 """The detector of this exposure."""
562 """The photometric calibration of this exposure."""
564 """The WCS of this exposure."""
566 """The bounding box of this exposure."""
568 """The filter of this exposure."""
572 """Astrometricly and photometricly calibrate across multiple visits of the
576 ConfigClass = JointcalConfig
577 _DefaultName = "jointcal"
581 self.makeSubtask(
"sourceSelector")
582 if self.config.doAstrometry:
583 self.makeSubtask(
"astrometryReferenceSelector")
586 if self.config.doPhotometry:
587 self.makeSubtask(
"photometryReferenceSelector")
592 self.
job = Job.load_metrics_package(subset=
'jointcal')
597 inputs = butlerQC.get(inputRefs)
599 tract = butlerQC.quantum.dataId[
'tract']
600 if self.config.doAstrometry:
602 dataIds=[ref.datasetRef.dataId
603 for ref
in inputRefs.astrometryRefCat],
604 refCats=inputs.pop(
'astrometryRefCat'),
605 config=self.config.astrometryRefObjLoader,
607 if self.config.doPhotometry:
609 dataIds=[ref.datasetRef.dataId
610 for ref
in inputRefs.photometryRefCat],
611 refCats=inputs.pop(
'photometryRefCat'),
612 config=self.config.photometryRefObjLoader,
614 outputs = self.
run(**inputs, tract=tract)
616 if self.config.doAstrometry:
617 self.
_put_output(butlerQC, outputs.outputWcs, outputRefs.outputWcs,
618 inputs[
'inputCamera'],
"setWcs")
619 if self.config.doPhotometry:
620 self.
_put_output(butlerQC, outputs.outputPhotoCalib, outputRefs.outputPhotoCalib,
621 inputs[
'inputCamera'],
"setPhotoCalib")
623 def _put_metrics(self, butlerQC, job, outputRefs):
624 """Persist all measured metrics stored in a job.
628 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
629 A butler which is specialized to operate
in the context of a
630 `lsst.daf.butler.Quantum`; This
is the input to `runQuantum`.
631 job : `lsst.verify.job.Job`
632 Measurements of metrics to persist.
633 outputRefs : `list` [`lsst.pipe.base.connectionTypes.OutputQuantizedConnection`]
634 The DatasetRefs to persist the data to.
636 for key
in job.measurements.keys():
637 butlerQC.put(job.measurements[key], getattr(outputRefs, key.fqn.replace(
'jointcal.',
'')))
639 def _put_output(self, butlerQC, outputs, outputRefs, camera, setter):
640 """Persist the output datasets to their appropriate datarefs.
644 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
645 A butler which is specialized to operate
in the context of a
646 `lsst.daf.butler.Quantum`; This
is the input to `runQuantum`.
649 The fitted objects to persist.
650 outputRefs : `list` [`lsst.pipe.base.connectionTypes.OutputQuantizedConnection`]
651 The DatasetRefs to persist the data to.
653 The camera
for this instrument, to get detector ids
from.
655 The method to call on the ExposureCatalog to set each output.
658 schema.addField('visit', type=
'L', doc=
'Visit number')
660 def new_catalog(visit, size):
661 """Return an catalog ready to be filled with appropriate output."""
664 catalog[
'visit'] = visit
666 metadata.add(
"COMMENT",
"Catalog id is detector id, sorted.")
667 metadata.add(
"COMMENT",
"Only detectors with data have entries.")
671 detectors_per_visit = collections.defaultdict(int)
674 detectors_per_visit[key[0]] += 1
676 for ref
in outputRefs:
677 visit = ref.dataId[
'visit']
678 catalog = new_catalog(visit, detectors_per_visit[visit])
681 for detector
in camera:
682 detectorId = detector.getId()
683 key = (ref.dataId[
'visit'], detectorId)
684 if key
not in outputs:
686 self.log.debug(
"No %s output for detector %s in visit %s",
687 setter[3:], detectorId, visit)
690 catalog[i].setId(detectorId)
691 getattr(catalog[i], setter)(outputs[key])
695 butlerQC.put(catalog, ref)
696 self.log.info(
"Wrote %s detectors to %s", i, ref)
698 def run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None):
704 sourceFluxField =
"flux"
708 oldWcsList, bands = self.
_load_data(inputSourceTableVisit,
714 boundingCircle, center, radius, defaultFilter, epoch = self.
_prep_sky(associations, bands)
716 if self.config.doAstrometry:
720 referenceSelector=self.astrometryReferenceSelector,
724 astrometry_output = self.
_make_output(associations.getCcdImageList(),
728 astrometry_output =
None
730 if self.config.doPhotometry:
734 referenceSelector=self.photometryReferenceSelector,
738 reject_bad_fluxes=
True)
739 photometry_output = self.
_make_output(associations.getCcdImageList(),
743 photometry_output =
None
745 return pipeBase.Struct(outputWcs=astrometry_output,
746 outputPhotoCalib=photometry_output,
751 def _load_data(self, inputSourceTableVisit, inputVisitSummary, associations,
752 jointcalControl, camera):
753 """Read the data that jointcal needs to run.
755 Modifies ``associations`` in-place
with the loaded data.
759 inputSourceTableVisit : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
760 References to visit-level DataFrames to load the catalog data
from.
761 inputVisitSummary : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
762 Visit-level exposure summary catalog
with metadata.
764 Object to add the loaded data to by constructing new CcdImages.
766 Control object
for C++ associations management.
768 Camera object
for detector geometry.
773 The original WCS of the input data, to aid
in writing tests.
774 bands : `list` [`str`]
775 The filter bands of each input dataset.
779 load_cat_profile_file = 'jointcal_load_data.prof' if self.config.detailedProfile
else ''
780 with lsst.utils.timer.profile(load_cat_profile_file):
783 catalogMap = {ref.dataId[
'visit']: i
for i, ref
in enumerate(inputSourceTableVisit)}
784 detectorDict = {detector.getId(): detector
for detector
in camera}
788 for visitSummaryRef
in inputVisitSummary:
789 visitSummary = visitSummaryRef.get()
791 dataRef = inputSourceTableVisit[catalogMap[visitSummaryRef.dataId[
'visit']]]
793 inColumns = dataRef.get(component=
'columns')
797 visitCatalog = dataRef.get(parameters={
'columns': columns})
799 selected = self.sourceSelector.
run(visitCatalog)
802 detectors = {id: index
for index, id
in enumerate(visitSummary[
'id'])}
803 for id, index
in detectors.items():
809 self.config.sourceFluxType,
815 if result
is not None:
816 oldWcsList.append(result.wcs)
818 filters.append(data.filter)
819 if len(filters) == 0:
820 raise RuntimeError(
"No data to process: did source selector remove all sources?")
821 filters = collections.Counter(filters)
823 return oldWcsList, filters
825 def _make_one_input_data(self, visitRecord, catalog, detectorDict):
826 """Return a data structure for this detector+visit."""
829 visitInfo=visitRecord.getVisitInfo(),
830 detector=detectorDict[visitRecord.getId()],
831 photoCalib=visitRecord.getPhotoCalib(),
832 wcs=visitRecord.getWcs(),
833 bbox=visitRecord.getBBox(),
837 physical=visitRecord[
'physical_filter']))
839 def _build_ccdImage(self, data, associations, jointcalControl):
841 Extract the necessary things from this catalog+metadata to add a new
846 data : `JointcalInputData`
847 The loaded input data.
849 Object to add the info to, to construct a new CcdImage
851 Control object
for associations management
857 The TAN WCS of this image, read
from the calexp
860 A key to identify this dataRef by its visit
and ccd ids
863 if there are no sources
in the loaded catalog.
865 if len(data.catalog) == 0:
866 self.log.warning(
"No sources selected in visit %s ccd %s", data.visit, data.detector.getId())
869 associations.createCcdImage(data.catalog,
873 data.filter.physicalLabel,
877 data.detector.getId(),
880 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key'))
881 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
882 return Result(data.wcs, Key(data.visit, data.detector.getId()))
884 def _getDebugPath(self, filename):
885 """Constructs a path to filename using the configured debug path.
887 return os.path.join(self.config.debugOutputPath, filename)
889 def _prep_sky(self, associations, filters):
890 """Prepare on-sky and other data that must be computed after data has
893 associations.computeCommonTangentPoint()
895 boundingCircle = associations.computeBoundingCircle()
897 radius = lsst.geom.Angle(boundingCircle.getOpeningAngle().asRadians(), lsst.geom.radians)
899 self.log.info(f"Data has center={center} with radius={radius.asDegrees()} degrees.")
902 defaultFilter = filters.most_common(1)[0][0]
903 self.log.debug(
"Using '%s' filter for reference flux", defaultFilter.physicalLabel)
907 associations.setEpoch(epoch.jyear)
909 return boundingCircle, center, radius, defaultFilter, epoch
911 def _get_refcat_coordinate_error_override(self, refCat, name):
912 """Check whether we should override the refcat coordinate errors, and
913 return the overridden error
if necessary.
918 The reference catalog to check
for a ``coord_raErr`` field.
920 Whether we are doing
"astrometry" or "photometry".
924 refCoordErr : `float`
925 The refcat coordinate error to use,
or NaN
if we are
not overriding
930 lsst.pex.config.FieldValidationError
931 Raised
if the refcat does
not contain coordinate errors
and
932 ``config.astrometryReferenceErr``
is not set.
936 if name.lower() ==
"photometry":
937 if 'coord_raErr' not in refCat.schema:
942 if self.config.astrometryReferenceErr
is None and 'coord_raErr' not in refCat.schema:
943 msg = (
"Reference catalog does not contain coordinate errors, "
944 "and config.astrometryReferenceErr not supplied.")
945 raise pexConfig.FieldValidationError(JointcalConfig.astrometryReferenceErr,
949 if self.config.astrometryReferenceErr
is not None and 'coord_raErr' in refCat.schema:
950 self.log.warning(
"Overriding reference catalog coordinate errors with %f/coordinate [mas]",
951 self.config.astrometryReferenceErr)
953 if self.config.astrometryReferenceErr
is None:
956 return self.config.astrometryReferenceErr
958 def _compute_proper_motion_epoch(self, ccdImageList):
959 """Return the proper motion correction epoch of the provided images.
964 The images to compute the appropriate epoch for.
968 epoch : `astropy.time.Time`
969 The date to use
for proper motion corrections.
971 return astropy.time.Time(np.mean([ccdImage.getEpoch()
for ccdImage
in ccdImageList]),
975 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
976 tract="", match_cut=3.0,
977 reject_bad_fluxes=False, *,
978 name="", refObjLoader=None, referenceSelector=None,
979 fit_function=None, epoch=None):
980 """Load reference catalog, perform the fit, and return the result.
985 The star/reference star associations to fit.
987 filter to load from reference catalog.
989 ICRS center of field to load
from reference catalog.
991 On-sky radius to load
from reference catalog.
993 Name of thing being fit:
"astrometry" or "photometry".
994 refObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader`
995 Reference object loader to use to load a reference catalog.
996 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask`
997 Selector to use to pick objects
from the loaded reference catalog.
998 fit_function : callable
999 Function to call to perform fit (takes Associations object).
1000 tract : `str`, optional
1001 Name of tract currently being fit.
1002 match_cut : `float`, optional
1003 Radius
in arcseconds to find cross-catalog matches to during
1004 associations.associateCatalogs.
1005 reject_bad_fluxes : `bool`, optional
1006 Reject refCat sources
with NaN/inf flux
or NaN/0 fluxErr.
1007 epoch : `astropy.time.Time`, optional
1008 Epoch to which to correct refcat proper motion
and parallax,
1009 or `
None` to
not apply such corrections.
1013 result : `Photometry`
or `Astrometry`
1014 Result of `fit_function()`
1016 self.log.info("====== Now processing %s...", name)
1019 associations.associateCatalogs(match_cut)
1021 associations.fittedStarListSize())
1023 applyColorterms =
False if name.lower() ==
"astrometry" else self.config.applyColorTerms
1025 center, radius, defaultFilter,
1026 applyColorterms=applyColorterms,
1030 associations.collectRefStars(refCat,
1031 self.config.matchCut*lsst.geom.arcseconds,
1033 refCoordinateErr=refCoordErr,
1034 rejectBadFluxes=reject_bad_fluxes)
1036 associations.refStarListSize())
1038 associations.prepareFittedStars(self.config.minMeasurements)
1042 associations.nFittedStarsWithAssociatedRefStar())
1044 associations.fittedStarListSize())
1046 associations.nCcdImagesValidForFit())
1048 fit_profile_file =
'jointcal_fit_%s.prof'%name
if self.config.detailedProfile
else ''
1049 dataName =
"{}_{}".format(tract, defaultFilter.physicalLabel)
1050 with lsst.utils.timer.profile(fit_profile_file):
1051 result = fit_function(associations, dataName)
1054 if self.config.writeChi2FilesInitialFinal:
1055 baseName = self.
_getDebugPath(f
"{name}_final_chi2-{dataName}")
1056 result.fit.saveChi2Contributions(baseName+
"{type}")
1057 self.log.info(
"Wrote chi2 contributions files: %s", baseName)
1061 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterLabel,
1062 applyColorterms=False, epoch=None):
1063 """Load the necessary reference catalog sources, convert fluxes to
1064 correct units, and apply color term corrections
if requested.
1068 refObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader`
1069 The reference catalog loader to use to get the data.
1070 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask`
1071 Source selector to apply to loaded reference catalog.
1073 The center around which to load sources.
1075 The radius around ``center`` to load sources
in.
1077 The camera filter to load fluxes
for.
1078 applyColorterms : `bool`
1079 Apply colorterm corrections to the refcat
for ``filterName``?
1080 epoch : `astropy.time.Time`, optional
1081 Epoch to which to correct refcat proper motion
and parallax,
1082 or `
None` to
not apply such corrections.
1087 The loaded reference catalog.
1089 The name of the reference catalog flux field appropriate
for ``filterName``.
1091 skyCircle = refObjLoader.loadSkyCircle(center,
1093 filterLabel.bandLabel,
1096 selected = referenceSelector.run(skyCircle.refCat)
1098 if not selected.sourceCat.isContiguous():
1099 refCat = selected.sourceCat.copy(deep=
True)
1101 refCat = selected.sourceCat
1104 refCatName = self.config.connections.photometryRefCat
1105 self.log.info(
"Applying color terms for physical filter=%r reference catalog=%s",
1106 filterLabel.physicalLabel, refCatName)
1107 colorterm = self.config.colorterms.getColorterm(filterLabel.physicalLabel,
1111 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat)
1112 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
1114 refCat[skyCircle.fluxField+
'Err'] = fluxErrFromABMagErr(refMagErr, refMag) * 1e9
1116 return refCat, skyCircle.fluxField
1118 def _check_star_lists(self, associations, name):
1120 if associations.nCcdImagesValidForFit() == 0:
1121 raise RuntimeError(
'No images in the ccdImageList!')
1122 if associations.fittedStarListSize() == 0:
1123 raise RuntimeError(
'No stars in the {} fittedStarList!'.format(name))
1124 if associations.refStarListSize() == 0:
1125 raise RuntimeError(
'No stars in the {} reference star list!'.format(name))
1127 def _logChi2AndValidate(self, associations, fit, model, chi2Label, writeChi2Name=None):
1128 """Compute chi2, log it, validate the model, and return chi2.
1133 The star/reference star associations to fit.
1135 The fitter to use for minimization.
1136 model : `lsst.jointcal.Model`
1137 The model being fit.
1139 Label to describe the chi2 (e.g.
"Initialized",
"Final").
1140 writeChi2Name : `str`, optional
1141 Filename prefix to write the chi2 contributions to.
1142 Do
not supply an extension: an appropriate one will be added.
1147 The chi2 object
for the current fitter
and model.
1152 Raised
if chi2
is infinite
or NaN.
1154 Raised
if the model
is not valid.
1156 if writeChi2Name
is not None:
1158 fit.saveChi2Contributions(fullpath+
"{type}")
1159 self.log.info(
"Wrote chi2 contributions files: %s", fullpath)
1161 chi2 = fit.computeChi2()
1162 self.log.info(
"%s %s", chi2Label, chi2)
1164 if not np.isfinite(chi2.chi2):
1165 raise FloatingPointError(f
'{chi2Label} chi2 is invalid: {chi2}')
1166 if not model.validate(associations.getCcdImageList(), chi2.ndof):
1167 raise ValueError(
"Model is not valid: check log messages for warnings.")
1170 def _fit_photometry(self, associations, dataName=None):
1172 Fit the photometric data.
1177 The star/reference star associations to fit.
1179 Name of the data being processed (e.g. "1234_HSC-Y"),
for
1180 identifying debugging files.
1184 fit_result : `namedtuple`
1186 The photometric fitter used to perform the fit.
1188 The photometric model that was fit.
1190 self.log.info("=== Starting photometric fitting...")
1193 if self.config.photometryModel ==
"constrainedFlux":
1196 visitOrder=self.config.photometryVisitOrder,
1197 errorPedestal=self.config.photometryErrorPedestal)
1199 doLineSearch = self.config.allowLineSearch
1200 elif self.config.photometryModel ==
"constrainedMagnitude":
1203 visitOrder=self.config.photometryVisitOrder,
1204 errorPedestal=self.config.photometryErrorPedestal)
1206 doLineSearch = self.config.allowLineSearch
1207 elif self.config.photometryModel ==
"simpleFlux":
1209 errorPedestal=self.config.photometryErrorPedestal)
1210 doLineSearch =
False
1211 elif self.config.photometryModel ==
"simpleMagnitude":
1213 errorPedestal=self.config.photometryErrorPedestal)
1214 doLineSearch =
False
1219 if self.config.writeChi2FilesInitialFinal:
1220 baseName = f
"photometry_initial_chi2-{dataName}"
1223 if self.config.writeInitialModel:
1224 fullpath = self.
_getDebugPath(f
"initial_photometry_model-{dataName}.txt")
1228 def getChi2Name(whatToFit):
1229 if self.config.writeChi2FilesOuterLoop:
1230 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
1236 if self.config.writeInitMatrix:
1237 dumpMatrixFile = self.
_getDebugPath(f
"photometry_preinit-{dataName}")
1240 if self.config.photometryModel.startswith(
"constrained"):
1243 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
1245 writeChi2Name=getChi2Name(
"ModelVisit"))
1248 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
1250 writeChi2Name=getChi2Name(
"Model"))
1252 fit.minimize(
"Fluxes")
1254 writeChi2Name=getChi2Name(
"Fluxes"))
1256 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
1258 writeChi2Name=getChi2Name(
"ModelFluxes"))
1260 model.freezeErrorTransform()
1261 self.log.debug(
"Photometry error scales are frozen.")
1265 self.config.maxPhotometrySteps,
1268 doRankUpdate=self.config.photometryDoRankUpdate,
1269 doLineSearch=doLineSearch,
1276 def _fit_astrometry(self, associations, dataName=None):
1278 Fit the astrometric data.
1283 The star/reference star associations to fit.
1285 Name of the data being processed (e.g. "1234_HSC-Y"),
for
1286 identifying debugging files.
1290 fit_result : `namedtuple`
1292 The astrometric fitter used to perform the fit.
1294 The astrometric model that was fit.
1296 The model
for the sky to tangent plane projection that was used
in the fit.
1299 self.log.info("=== Starting astrometric fitting...")
1301 associations.deprojectFittedStars()
1308 if self.config.astrometryModel ==
"constrained":
1310 sky_to_tan_projection,
1311 chipOrder=self.config.astrometryChipOrder,
1312 visitOrder=self.config.astrometryVisitOrder)
1313 elif self.config.astrometryModel ==
"simple":
1315 sky_to_tan_projection,
1316 self.config.useInputWcs,
1318 order=self.config.astrometrySimpleOrder)
1323 if self.config.writeChi2FilesInitialFinal:
1324 baseName = f
"astrometry_initial_chi2-{dataName}"
1327 if self.config.writeInitialModel:
1328 fullpath = self.
_getDebugPath(f
"initial_astrometry_model-{dataName}.txt")
1332 def getChi2Name(whatToFit):
1333 if self.config.writeChi2FilesOuterLoop:
1334 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
1338 if self.config.writeInitMatrix:
1339 dumpMatrixFile = self.
_getDebugPath(f
"astrometry_preinit-{dataName}")
1344 if self.config.astrometryModel ==
"constrained":
1345 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
1347 writeChi2Name=getChi2Name(
"DistortionsVisit"))
1350 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
1352 writeChi2Name=getChi2Name(
"Distortions"))
1354 fit.minimize(
"Positions")
1356 writeChi2Name=getChi2Name(
"Positions"))
1358 fit.minimize(
"Distortions Positions")
1360 writeChi2Name=getChi2Name(
"DistortionsPositions"))
1364 self.config.maxAstrometrySteps,
1366 "Distortions Positions",
1367 sigmaRelativeTolerance=self.config.astrometryOutlierRelativeTolerance,
1368 doRankUpdate=self.config.astrometryDoRankUpdate,
1374 return Astrometry(fit, model, sky_to_tan_projection)
1376 def _check_stars(self, associations):
1377 """Count measured and reference stars per ccd and warn/log them."""
1378 for ccdImage
in associations.getCcdImageList():
1379 nMeasuredStars, nRefStars = ccdImage.countStars()
1380 self.log.debug(
"ccdImage %s has %s measured and %s reference stars",
1381 ccdImage.getName(), nMeasuredStars, nRefStars)
1382 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
1383 self.log.warning(
"ccdImage %s has only %s measuredStars (desired %s)",
1384 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
1385 if nRefStars < self.config.minRefStarsPerCcd:
1386 self.log.warning(
"ccdImage %s has only %s RefStars (desired %s)",
1387 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
1389 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
1391 sigmaRelativeTolerance=0,
1393 doLineSearch=False):
1394 """Run fitter.minimize up to max_steps times, returning the final chi2.
1399 The star/reference star associations to fit.
1401 The fitter to use for minimization.
1403 Maximum number of steps to run outlier rejection before declaring
1404 convergence failure.
1405 name : {
'photometry' or 'astrometry'}
1406 What type of data are we fitting (
for logs
and debugging files).
1408 Passed to ``fitter.minimize()`` to define the parameters to fit.
1409 dataName : `str`, optional
1410 Descriptive name
for this dataset (e.g. tract
and filter),
1412 sigmaRelativeTolerance : `float`, optional
1413 Convergence tolerance
for the fractional change
in the chi2 cut
1414 level
for determining outliers. If set to zero, iterations will
1415 continue until there are no outliers.
1416 doRankUpdate : `bool`, optional
1417 Do an Eigen rank update during minimization,
or recompute the full
1418 matrix
and gradient?
1419 doLineSearch : `bool`, optional
1420 Do a line search
for the optimum step during minimization?
1425 The final chi2 after the fit converges,
or is forced to end.
1430 Raised
if the fitter fails
with a non-finite value.
1432 Raised
if the fitter fails
for some other reason;
1433 log messages will provide further details.
1435 if self.config.writeInitMatrix:
1436 dumpMatrixFile = self.
_getDebugPath(f
"{name}_postinit-{dataName}")
1440 oldChi2.chi2 = float(
"inf")
1441 for i
in range(max_steps):
1442 if self.config.writeChi2FilesOuterLoop:
1443 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}"
1445 writeChi2Name =
None
1446 result = fitter.minimize(whatToFit,
1447 self.config.outlierRejectSigma,
1448 sigmaRelativeTolerance=sigmaRelativeTolerance,
1449 doRankUpdate=doRankUpdate,
1450 doLineSearch=doLineSearch,
1451 dumpMatrixFile=dumpMatrixFile)
1454 f
"Fit iteration {i}", writeChi2Name=writeChi2Name)
1456 if result == MinimizeResult.Converged:
1458 self.log.debug(
"fit has converged - no more outliers - redo minimization "
1459 "one more time in case we have lost accuracy in rank update.")
1461 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma,
1462 sigmaRelativeTolerance=sigmaRelativeTolerance)
1466 if chi2.chi2/chi2.ndof >= 4.0:
1467 self.log.error(
"Potentially bad fit: High chi-squared/ndof.")
1470 elif result == MinimizeResult.Chi2Increased:
1471 self.log.warning(
"Still some outliers remaining but chi2 increased - retry")
1473 chi2Ratio = chi2.chi2 / oldChi2.chi2
1475 self.log.warning(
'Significant chi2 increase by a factor of %.4g / %.4g = %.4g',
1476 chi2.chi2, oldChi2.chi2, chi2Ratio)
1483 msg = (
"Large chi2 increase between steps: fit likely cannot converge."
1484 " Try setting one or more of the `writeChi2*` config fields and looking"
1485 " at how individual star chi2-values evolve during the fit.")
1486 raise RuntimeError(msg)
1488 elif result == MinimizeResult.NonFinite:
1489 filename = self.
_getDebugPath(
"{}_failure-nonfinite_chi2-{}.csv".format(name, dataName))
1491 fitter.saveChi2Contributions(filename+
"{type}")
1492 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}"
1493 raise FloatingPointError(msg.format(filename))
1494 elif result == MinimizeResult.Failed:
1495 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1497 raise RuntimeError(
"Unxepected return code from minimize().")
1499 self.log.error(
"%s failed to converge after %d steps"%(name, max_steps))
1503 def _make_output(self, ccdImageList, model, func):
1504 """Return the internal jointcal models converted to the afw
1505 structures that will be saved to disk.
1510 The list of CcdImages to get the output for.
1514 The name of the function to call on ``model`` to get the converted
1521 The data to be saved, keyed on (visit, detector).
1524 for ccdImage
in ccdImageList:
1525 ccd = ccdImage.ccdId
1526 visit = ccdImage.visit
1527 self.log.debug(
"%s for visit: %d, ccd: %d", func, visit, ccd)
1528 output[(visit, ccd)] = getattr(model, func)(ccdImage)
1533 """Return an afw SourceTable to use as a base for creating the
1534 SourceCatalog to insert values from the dataFrame into.
1539 Table
with schema
and slots to use to make SourceCatalogs.
1542 schema.addField("centroid_x",
"D")
1543 schema.addField(
"centroid_y",
"D")
1544 schema.addField(
"centroid_xErr",
"F")
1545 schema.addField(
"centroid_yErr",
"F")
1546 schema.addField(
"shape_xx",
"D")
1547 schema.addField(
"shape_yy",
"D")
1548 schema.addField(
"shape_xy",
"D")
1549 schema.addField(
"flux_instFlux",
"D")
1550 schema.addField(
"flux_instFluxErr",
"D")
1552 table.defineCentroid(
"centroid")
1553 table.defineShape(
"shape")
1559 Get the sourceTable_visit columns to load from the catalogs.
1564 List of columns known to be available
in the sourceTable_visit.
1565 config : `JointcalConfig`
1566 A filled-
in config to to help define column names.
1567 sourceSelector : `lsst.meas.algorithms.BaseSourceSelectorTask`
1568 A configured source selector to define column names to load.
1573 List of columns to read
from sourceTable_visit.
1574 detectorColumn : `str`
1575 Name of the detector column.
1577 Name of the ixx/iyy/ixy columns.
1579 if 'detector' in inColumns:
1581 detectorColumn =
'detector'
1584 detectorColumn =
'ccd'
1586 columns = [
'visit', detectorColumn,
1587 'sourceId',
'x',
'xErr',
'y',
'yErr',
1588 config.sourceFluxType +
'_instFlux', config.sourceFluxType +
'_instFluxErr']
1590 if 'ixx' in inColumns:
1592 ixxColumns = [
'ixx',
'iyy',
'ixy']
1595 ixxColumns = [
'Ixx',
'Iyy',
'Ixy']
1596 columns.extend(ixxColumns)
1598 if sourceSelector.config.doFlags:
1599 columns.extend(sourceSelector.config.flags.bad)
1600 if sourceSelector.config.doUnresolved:
1601 columns.append(sourceSelector.config.unresolved.name)
1602 if sourceSelector.config.doIsolated:
1603 columns.append(sourceSelector.config.isolated.parentName)
1604 columns.append(sourceSelector.config.isolated.nChildName)
1606 return columns, detectorColumn, ixxColumns
1610 detectorColumn, ixxColumns, sourceFluxType, log):
1611 """Return an afw SourceCatalog extracted from a visit-level dataframe,
1612 limited to just one detector.
1617 Table factory to use to make the SourceCatalog that will be
1618 populated with data
from ``visitCatalog``.
1619 visitCatalog : `pandas.DataFrame`
1620 DataFrame to extract a detector catalog
from.
1622 Numeric id of the detector to extract
from ``visitCatalog``.
1623 detectorColumn : `str`
1624 Name of the detector column
in the catalog.
1625 ixxColumns : `list` [`str`]
1626 Names of the ixx/iyy/ixy columns
in the catalog.
1627 sourceFluxType : `str`
1628 Name of the catalog field to load instFluxes
from.
1629 log : `logging.Logger`
1630 Logging instance to log to.
1635 Detector-level catalog extracted
from ``visitCatalog``,
or `
None`
1636 if there was no data to load.
1639 mapping = {
'x':
'centroid_x',
1641 'xErr':
'centroid_xErr',
1642 'yErr':
'centroid_yErr',
1643 ixxColumns[0]:
'shape_xx',
1644 ixxColumns[1]:
'shape_yy',
1645 ixxColumns[2]:
'shape_xy',
1646 f
'{sourceFluxType}_instFlux':
'flux_instFlux',
1647 f
'{sourceFluxType}_instFluxErr':
'flux_instFluxErr',
1651 matched = visitCatalog[detectorColumn] == detectorId
1655 catalog.resize(sum(matched))
1656 view = visitCatalog.loc[matched]
1657 catalog[
'id'] = view.index
1658 for dfCol, afwCol
in mapping.items():
1659 catalog[afwCol] = view[dfCol]
1661 log.debug(
"%d sources selected in visit %d detector %d",
1663 view[
'visit'].iloc[0],
static Schema makeMinimalSchema()
static std::shared_ptr< SourceTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
static Schema makeMinimalSchema()
The class that implements the relations between MeasuredStar and FittedStar.
Class that handles the astrometric least squares problem.
Interface between AstrometryFit and the combinations of Mappings from pixels to some tangent plane (a...
Handler of an actual image from a single CCD.
Base class for Chi2Statistic and Chi2List, to allow addEntry inside Fitter for either class.
Simple structure to accumulate chi2 and ndof.
A multi-component model, fitting mappings for sensors and visits simultaneously.
A projection handler in which all CCDs from the same visit have the same tangent point.
Class that handles the photometric least squares problem.
A model where there is one independent transform per CcdImage.
def __init__(self, *config=None)
def _load_data(self, inputSourceTableVisit, inputVisitSummary, associations, jointcalControl, camera)
def _compute_proper_motion_epoch(self, ccdImageList)
def _get_refcat_coordinate_error_override(self, refCat, name)
def _getDebugPath(self, filename)
def _check_star_lists(self, associations, name)
def _make_one_input_data(self, visitRecord, catalog, detectorDict)
def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit, dataName="", sigmaRelativeTolerance=0, doRankUpdate=True, doLineSearch=False)
def _check_stars(self, associations)
def _prep_sky(self, associations, filters)
def runQuantum(self, butlerQC, inputRefs, outputRefs)
def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius, tract="", match_cut=3.0, reject_bad_fluxes=False, *name="", refObjLoader=None, referenceSelector=None, fit_function=None, epoch=None)
def _put_metrics(self, butlerQC, job, outputRefs)
def _build_ccdImage(self, data, associations, jointcalControl)
def __init__(self, **kwargs)
def run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None)
def _fit_photometry(self, associations, dataName=None)
def _logChi2AndValidate(self, associations, fit, model, chi2Label, writeChi2Name=None)
def _make_output(self, ccdImageList, model, func)
def _put_output(self, butlerQC, outputs, outputRefs, camera, setter)
def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterLabel, applyColorterms=False, epoch=None)
def _fit_astrometry(self, associations, dataName=None)
def lookupStaticCalibrations(datasetType, registry, quantumDataId, collections)
def add_measurement(job, name, value)
def get_sourceTable_visit_columns(inColumns, config, sourceSelector)
def lookupVisitRefCats(datasetType, registry, quantumDataId, collections)
def extract_detector_catalog_from_visit_catalog(table, visitCatalog, detectorId, detectorColumn, ixxColumns, sourceFluxType, log)
def writeModel(model, filename, log)
This is a virtual class that allows a lot of freedom in the choice of the projection from "Sky" (wher...