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,
606 name=self.config.connections.astrometryRefCat,
608 if self.config.doPhotometry:
610 dataIds=[ref.datasetRef.dataId
611 for ref
in inputRefs.photometryRefCat],
612 refCats=inputs.pop(
'photometryRefCat'),
613 config=self.config.photometryRefObjLoader,
614 name=self.config.connections.photometryRefCat,
616 outputs = self.
run(**inputs, tract=tract)
618 if self.config.doAstrometry:
619 self.
_put_output(butlerQC, outputs.outputWcs, outputRefs.outputWcs,
620 inputs[
'inputCamera'],
"setWcs")
621 if self.config.doPhotometry:
622 self.
_put_output(butlerQC, outputs.outputPhotoCalib, outputRefs.outputPhotoCalib,
623 inputs[
'inputCamera'],
"setPhotoCalib")
625 def _put_metrics(self, butlerQC, job, outputRefs):
626 """Persist all measured metrics stored in a job.
630 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
631 A butler which is specialized to operate
in the context of a
632 `lsst.daf.butler.Quantum`; This
is the input to `runQuantum`.
633 job : `lsst.verify.job.Job`
634 Measurements of metrics to persist.
635 outputRefs : `list` [`lsst.pipe.base.connectionTypes.OutputQuantizedConnection`]
636 The DatasetRefs to persist the data to.
638 for key
in job.measurements.keys():
639 butlerQC.put(job.measurements[key], getattr(outputRefs, key.fqn.replace(
'jointcal.',
'')))
641 def _put_output(self, butlerQC, outputs, outputRefs, camera, setter):
642 """Persist the output datasets to their appropriate datarefs.
646 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
647 A butler which is specialized to operate
in the context of a
648 `lsst.daf.butler.Quantum`; This
is the input to `runQuantum`.
651 The fitted objects to persist.
652 outputRefs : `list` [`lsst.pipe.base.connectionTypes.OutputQuantizedConnection`]
653 The DatasetRefs to persist the data to.
655 The camera
for this instrument, to get detector ids
from.
657 The method to call on the ExposureCatalog to set each output.
660 schema.addField('visit', type=
'L', doc=
'Visit number')
662 def new_catalog(visit, size):
663 """Return an catalog ready to be filled with appropriate output."""
666 catalog[
'visit'] = visit
668 metadata.add(
"COMMENT",
"Catalog id is detector id, sorted.")
669 metadata.add(
"COMMENT",
"Only detectors with data have entries.")
673 detectors_per_visit = collections.defaultdict(int)
676 detectors_per_visit[key[0]] += 1
678 for ref
in outputRefs:
679 visit = ref.dataId[
'visit']
680 catalog = new_catalog(visit, detectors_per_visit[visit])
683 for detector
in camera:
684 detectorId = detector.getId()
685 key = (ref.dataId[
'visit'], detectorId)
686 if key
not in outputs:
688 self.log.debug(
"No %s output for detector %s in visit %s",
689 setter[3:], detectorId, visit)
692 catalog[i].setId(detectorId)
693 getattr(catalog[i], setter)(outputs[key])
697 butlerQC.put(catalog, ref)
698 self.log.info(
"Wrote %s detectors to %s", i, ref)
700 def run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None):
706 sourceFluxField =
"flux"
710 oldWcsList, bands = self.
_load_data(inputSourceTableVisit,
716 boundingCircle, center, radius, defaultFilter, epoch = self.
_prep_sky(associations, bands)
718 if self.config.doAstrometry:
722 referenceSelector=self.astrometryReferenceSelector,
726 astrometry_output = self.
_make_output(associations.getCcdImageList(),
730 astrometry_output =
None
732 if self.config.doPhotometry:
736 referenceSelector=self.photometryReferenceSelector,
740 reject_bad_fluxes=
True)
741 photometry_output = self.
_make_output(associations.getCcdImageList(),
745 photometry_output =
None
747 return pipeBase.Struct(outputWcs=astrometry_output,
748 outputPhotoCalib=photometry_output,
753 def _load_data(self, inputSourceTableVisit, inputVisitSummary, associations,
754 jointcalControl, camera):
755 """Read the data that jointcal needs to run.
757 Modifies ``associations`` in-place
with the loaded data.
761 inputSourceTableVisit : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
762 References to visit-level DataFrames to load the catalog data
from.
763 inputVisitSummary : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
764 Visit-level exposure summary catalog
with metadata.
766 Object to add the loaded data to by constructing new CcdImages.
768 Control object
for C++ associations management.
770 Camera object
for detector geometry.
775 The original WCS of the input data, to aid
in writing tests.
776 bands : `list` [`str`]
777 The filter bands of each input dataset.
781 load_cat_profile_file = 'jointcal_load_data.prof' if self.config.detailedProfile
else ''
782 with lsst.utils.timer.profile(load_cat_profile_file):
785 catalogMap = {ref.dataId[
'visit']: i
for i, ref
in enumerate(inputSourceTableVisit)}
786 detectorDict = {detector.getId(): detector
for detector
in camera}
790 for visitSummaryRef
in inputVisitSummary:
791 visitSummary = visitSummaryRef.get()
793 dataRef = inputSourceTableVisit[catalogMap[visitSummaryRef.dataId[
'visit']]]
795 inColumns = dataRef.get(component=
'columns')
799 visitCatalog = dataRef.get(parameters={
'columns': columns})
801 selected = self.sourceSelector.
run(visitCatalog)
804 detectors = {id: index
for index, id
in enumerate(visitSummary[
'id'])}
805 for id, index
in detectors.items():
811 self.config.sourceFluxType,
817 if result
is not None:
818 oldWcsList.append(result.wcs)
820 filters.append(data.filter)
821 if len(filters) == 0:
822 raise RuntimeError(
"No data to process: did source selector remove all sources?")
823 filters = collections.Counter(filters)
825 return oldWcsList, filters
827 def _make_one_input_data(self, visitRecord, catalog, detectorDict):
828 """Return a data structure for this detector+visit."""
831 visitInfo=visitRecord.getVisitInfo(),
832 detector=detectorDict[visitRecord.getId()],
833 photoCalib=visitRecord.getPhotoCalib(),
834 wcs=visitRecord.getWcs(),
835 bbox=visitRecord.getBBox(),
839 physical=visitRecord[
'physical_filter']))
841 def _build_ccdImage(self, data, associations, jointcalControl):
843 Extract the necessary things from this catalog+metadata to add a new
848 data : `JointcalInputData`
849 The loaded input data.
851 Object to add the info to, to construct a new CcdImage
853 Control object
for associations management
859 The TAN WCS of this image, read
from the calexp
862 A key to identify this dataRef by its visit
and ccd ids
865 if there are no sources
in the loaded catalog.
867 if len(data.catalog) == 0:
868 self.log.warning(
"No sources selected in visit %s ccd %s", data.visit, data.detector.getId())
871 associations.createCcdImage(data.catalog,
875 data.filter.physicalLabel,
879 data.detector.getId(),
882 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key'))
883 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
884 return Result(data.wcs, Key(data.visit, data.detector.getId()))
886 def _getDebugPath(self, filename):
887 """Constructs a path to filename using the configured debug path.
889 return os.path.join(self.config.debugOutputPath, filename)
891 def _prep_sky(self, associations, filters):
892 """Prepare on-sky and other data that must be computed after data has
895 associations.computeCommonTangentPoint()
897 boundingCircle = associations.computeBoundingCircle()
899 radius = lsst.geom.Angle(boundingCircle.getOpeningAngle().asRadians(), lsst.geom.radians)
901 self.log.info(f"Data has center={center} with radius={radius.asDegrees()} degrees.")
904 defaultFilter = filters.most_common(1)[0][0]
905 self.log.debug(
"Using '%s' filter for reference flux", defaultFilter.physicalLabel)
909 associations.setEpoch(epoch.jyear)
911 return boundingCircle, center, radius, defaultFilter, epoch
913 def _get_refcat_coordinate_error_override(self, refCat, name):
914 """Check whether we should override the refcat coordinate errors, and
915 return the overridden error
if necessary.
920 The reference catalog to check
for a ``coord_raErr`` field.
922 Whether we are doing
"astrometry" or "photometry".
926 refCoordErr : `float`
927 The refcat coordinate error to use,
or NaN
if we are
not overriding
932 lsst.pex.config.FieldValidationError
933 Raised
if the refcat does
not contain coordinate errors
and
934 ``config.astrometryReferenceErr``
is not set.
938 if name.lower() ==
"photometry":
939 if 'coord_raErr' not in refCat.schema:
944 if self.config.astrometryReferenceErr
is None and 'coord_raErr' not in refCat.schema:
945 msg = (
"Reference catalog does not contain coordinate errors, "
946 "and config.astrometryReferenceErr not supplied.")
947 raise pexConfig.FieldValidationError(JointcalConfig.astrometryReferenceErr,
951 if self.config.astrometryReferenceErr
is not None and 'coord_raErr' in refCat.schema:
952 self.log.warning(
"Overriding reference catalog coordinate errors with %f/coordinate [mas]",
953 self.config.astrometryReferenceErr)
955 if self.config.astrometryReferenceErr
is None:
958 return self.config.astrometryReferenceErr
960 def _compute_proper_motion_epoch(self, ccdImageList):
961 """Return the proper motion correction epoch of the provided images.
966 The images to compute the appropriate epoch for.
970 epoch : `astropy.time.Time`
971 The date to use
for proper motion corrections.
973 return astropy.time.Time(np.mean([ccdImage.getEpoch()
for ccdImage
in ccdImageList]),
977 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
978 tract="", match_cut=3.0,
979 reject_bad_fluxes=False, *,
980 name="", refObjLoader=None, referenceSelector=None,
981 fit_function=None, epoch=None):
982 """Load reference catalog, perform the fit, and return the result.
987 The star/reference star associations to fit.
989 filter to load from reference catalog.
991 ICRS center of field to load
from reference catalog.
993 On-sky radius to load
from reference catalog.
995 Name of thing being fit:
"astrometry" or "photometry".
996 refObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader`
997 Reference object loader to use to load a reference catalog.
998 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask`
999 Selector to use to pick objects
from the loaded reference catalog.
1000 fit_function : callable
1001 Function to call to perform fit (takes Associations object).
1002 tract : `str`, optional
1003 Name of tract currently being fit.
1004 match_cut : `float`, optional
1005 Radius
in arcseconds to find cross-catalog matches to during
1006 associations.associateCatalogs.
1007 reject_bad_fluxes : `bool`, optional
1008 Reject refCat sources
with NaN/inf flux
or NaN/0 fluxErr.
1009 epoch : `astropy.time.Time`, optional
1010 Epoch to which to correct refcat proper motion
and parallax,
1011 or `
None` to
not apply such corrections.
1015 result : `Photometry`
or `Astrometry`
1016 Result of `fit_function()`
1018 self.log.info("====== Now processing %s...", name)
1021 associations.associateCatalogs(match_cut)
1023 associations.fittedStarListSize())
1025 applyColorterms =
False if name.lower() ==
"astrometry" else self.config.applyColorTerms
1027 center, radius, defaultFilter,
1028 applyColorterms=applyColorterms,
1032 associations.collectRefStars(refCat,
1033 self.config.matchCut*lsst.geom.arcseconds,
1035 refCoordinateErr=refCoordErr,
1036 rejectBadFluxes=reject_bad_fluxes)
1038 associations.refStarListSize())
1040 associations.prepareFittedStars(self.config.minMeasurements)
1044 associations.nFittedStarsWithAssociatedRefStar())
1046 associations.fittedStarListSize())
1048 associations.nCcdImagesValidForFit())
1050 fit_profile_file =
'jointcal_fit_%s.prof'%name
if self.config.detailedProfile
else ''
1051 dataName =
"{}_{}".format(tract, defaultFilter.physicalLabel)
1052 with lsst.utils.timer.profile(fit_profile_file):
1053 result = fit_function(associations, dataName)
1056 if self.config.writeChi2FilesInitialFinal:
1057 baseName = self.
_getDebugPath(f
"{name}_final_chi2-{dataName}")
1058 result.fit.saveChi2Contributions(baseName+
"{type}")
1059 self.log.info(
"Wrote chi2 contributions files: %s", baseName)
1063 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterLabel,
1064 applyColorterms=False, epoch=None):
1065 """Load the necessary reference catalog sources, convert fluxes to
1066 correct units, and apply color term corrections
if requested.
1070 refObjLoader : `lsst.meas.algorithms.ReferenceObjectLoader`
1071 The reference catalog loader to use to get the data.
1072 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask`
1073 Source selector to apply to loaded reference catalog.
1075 The center around which to load sources.
1077 The radius around ``center`` to load sources
in.
1079 The camera filter to load fluxes
for.
1080 applyColorterms : `bool`
1081 Apply colorterm corrections to the refcat
for ``filterName``?
1082 epoch : `astropy.time.Time`, optional
1083 Epoch to which to correct refcat proper motion
and parallax,
1084 or `
None` to
not apply such corrections.
1089 The loaded reference catalog.
1091 The name of the reference catalog flux field appropriate
for ``filterName``.
1093 skyCircle = refObjLoader.loadSkyCircle(center,
1095 filterLabel.bandLabel,
1098 selected = referenceSelector.run(skyCircle.refCat)
1100 if not selected.sourceCat.isContiguous():
1101 refCat = selected.sourceCat.copy(deep=
True)
1103 refCat = selected.sourceCat
1106 refCatName = refObjLoader.name
1107 self.log.info(
"Applying color terms for physical filter=%r reference catalog=%s",
1108 filterLabel.physicalLabel, refCatName)
1109 colorterm = self.config.colorterms.getColorterm(filterLabel.physicalLabel,
1113 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat)
1114 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
1116 refCat[skyCircle.fluxField+
'Err'] = fluxErrFromABMagErr(refMagErr, refMag) * 1e9
1118 return refCat, skyCircle.fluxField
1120 def _check_star_lists(self, associations, name):
1122 if associations.nCcdImagesValidForFit() == 0:
1123 raise RuntimeError(
'No images in the ccdImageList!')
1124 if associations.fittedStarListSize() == 0:
1125 raise RuntimeError(
'No stars in the {} fittedStarList!'.format(name))
1126 if associations.refStarListSize() == 0:
1127 raise RuntimeError(
'No stars in the {} reference star list!'.format(name))
1129 def _logChi2AndValidate(self, associations, fit, model, chi2Label, writeChi2Name=None):
1130 """Compute chi2, log it, validate the model, and return chi2.
1135 The star/reference star associations to fit.
1137 The fitter to use for minimization.
1138 model : `lsst.jointcal.Model`
1139 The model being fit.
1141 Label to describe the chi2 (e.g.
"Initialized",
"Final").
1142 writeChi2Name : `str`, optional
1143 Filename prefix to write the chi2 contributions to.
1144 Do
not supply an extension: an appropriate one will be added.
1149 The chi2 object
for the current fitter
and model.
1154 Raised
if chi2
is infinite
or NaN.
1156 Raised
if the model
is not valid.
1158 if writeChi2Name
is not None:
1160 fit.saveChi2Contributions(fullpath+
"{type}")
1161 self.log.info(
"Wrote chi2 contributions files: %s", fullpath)
1163 chi2 = fit.computeChi2()
1164 self.log.info(
"%s %s", chi2Label, chi2)
1166 if not np.isfinite(chi2.chi2):
1167 raise FloatingPointError(f
'{chi2Label} chi2 is invalid: {chi2}')
1168 if not model.validate(associations.getCcdImageList(), chi2.ndof):
1169 raise ValueError(
"Model is not valid: check log messages for warnings.")
1172 def _fit_photometry(self, associations, dataName=None):
1174 Fit the photometric data.
1179 The star/reference star associations to fit.
1181 Name of the data being processed (e.g. "1234_HSC-Y"),
for
1182 identifying debugging files.
1186 fit_result : `namedtuple`
1188 The photometric fitter used to perform the fit.
1190 The photometric model that was fit.
1192 self.log.info("=== Starting photometric fitting...")
1195 if self.config.photometryModel ==
"constrainedFlux":
1198 visitOrder=self.config.photometryVisitOrder,
1199 errorPedestal=self.config.photometryErrorPedestal)
1201 doLineSearch = self.config.allowLineSearch
1202 elif self.config.photometryModel ==
"constrainedMagnitude":
1205 visitOrder=self.config.photometryVisitOrder,
1206 errorPedestal=self.config.photometryErrorPedestal)
1208 doLineSearch = self.config.allowLineSearch
1209 elif self.config.photometryModel ==
"simpleFlux":
1211 errorPedestal=self.config.photometryErrorPedestal)
1212 doLineSearch =
False
1213 elif self.config.photometryModel ==
"simpleMagnitude":
1215 errorPedestal=self.config.photometryErrorPedestal)
1216 doLineSearch =
False
1221 if self.config.writeChi2FilesInitialFinal:
1222 baseName = f
"photometry_initial_chi2-{dataName}"
1225 if self.config.writeInitialModel:
1226 fullpath = self.
_getDebugPath(f
"initial_photometry_model-{dataName}.txt")
1230 def getChi2Name(whatToFit):
1231 if self.config.writeChi2FilesOuterLoop:
1232 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
1238 if self.config.writeInitMatrix:
1239 dumpMatrixFile = self.
_getDebugPath(f
"photometry_preinit-{dataName}")
1242 if self.config.photometryModel.startswith(
"constrained"):
1245 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
1247 writeChi2Name=getChi2Name(
"ModelVisit"))
1250 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
1252 writeChi2Name=getChi2Name(
"Model"))
1254 fit.minimize(
"Fluxes")
1256 writeChi2Name=getChi2Name(
"Fluxes"))
1258 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
1260 writeChi2Name=getChi2Name(
"ModelFluxes"))
1262 model.freezeErrorTransform()
1263 self.log.debug(
"Photometry error scales are frozen.")
1267 self.config.maxPhotometrySteps,
1270 doRankUpdate=self.config.photometryDoRankUpdate,
1271 doLineSearch=doLineSearch,
1278 def _fit_astrometry(self, associations, dataName=None):
1280 Fit the astrometric data.
1285 The star/reference star associations to fit.
1287 Name of the data being processed (e.g. "1234_HSC-Y"),
for
1288 identifying debugging files.
1292 fit_result : `namedtuple`
1294 The astrometric fitter used to perform the fit.
1296 The astrometric model that was fit.
1298 The model
for the sky to tangent plane projection that was used
in the fit.
1301 self.log.info("=== Starting astrometric fitting...")
1303 associations.deprojectFittedStars()
1310 if self.config.astrometryModel ==
"constrained":
1312 sky_to_tan_projection,
1313 chipOrder=self.config.astrometryChipOrder,
1314 visitOrder=self.config.astrometryVisitOrder)
1315 elif self.config.astrometryModel ==
"simple":
1317 sky_to_tan_projection,
1318 self.config.useInputWcs,
1320 order=self.config.astrometrySimpleOrder)
1325 if self.config.writeChi2FilesInitialFinal:
1326 baseName = f
"astrometry_initial_chi2-{dataName}"
1329 if self.config.writeInitialModel:
1330 fullpath = self.
_getDebugPath(f
"initial_astrometry_model-{dataName}.txt")
1334 def getChi2Name(whatToFit):
1335 if self.config.writeChi2FilesOuterLoop:
1336 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
1340 if self.config.writeInitMatrix:
1341 dumpMatrixFile = self.
_getDebugPath(f
"astrometry_preinit-{dataName}")
1346 if self.config.astrometryModel ==
"constrained":
1347 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
1349 writeChi2Name=getChi2Name(
"DistortionsVisit"))
1352 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
1354 writeChi2Name=getChi2Name(
"Distortions"))
1356 fit.minimize(
"Positions")
1358 writeChi2Name=getChi2Name(
"Positions"))
1360 fit.minimize(
"Distortions Positions")
1362 writeChi2Name=getChi2Name(
"DistortionsPositions"))
1366 self.config.maxAstrometrySteps,
1368 "Distortions Positions",
1369 sigmaRelativeTolerance=self.config.astrometryOutlierRelativeTolerance,
1370 doRankUpdate=self.config.astrometryDoRankUpdate,
1376 return Astrometry(fit, model, sky_to_tan_projection)
1378 def _check_stars(self, associations):
1379 """Count measured and reference stars per ccd and warn/log them."""
1380 for ccdImage
in associations.getCcdImageList():
1381 nMeasuredStars, nRefStars = ccdImage.countStars()
1382 self.log.debug(
"ccdImage %s has %s measured and %s reference stars",
1383 ccdImage.getName(), nMeasuredStars, nRefStars)
1384 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
1385 self.log.warning(
"ccdImage %s has only %s measuredStars (desired %s)",
1386 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
1387 if nRefStars < self.config.minRefStarsPerCcd:
1388 self.log.warning(
"ccdImage %s has only %s RefStars (desired %s)",
1389 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
1391 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
1393 sigmaRelativeTolerance=0,
1395 doLineSearch=False):
1396 """Run fitter.minimize up to max_steps times, returning the final chi2.
1401 The star/reference star associations to fit.
1403 The fitter to use for minimization.
1405 Maximum number of steps to run outlier rejection before declaring
1406 convergence failure.
1407 name : {
'photometry' or 'astrometry'}
1408 What type of data are we fitting (
for logs
and debugging files).
1410 Passed to ``fitter.minimize()`` to define the parameters to fit.
1411 dataName : `str`, optional
1412 Descriptive name
for this dataset (e.g. tract
and filter),
1414 sigmaRelativeTolerance : `float`, optional
1415 Convergence tolerance
for the fractional change
in the chi2 cut
1416 level
for determining outliers. If set to zero, iterations will
1417 continue until there are no outliers.
1418 doRankUpdate : `bool`, optional
1419 Do an Eigen rank update during minimization,
or recompute the full
1420 matrix
and gradient?
1421 doLineSearch : `bool`, optional
1422 Do a line search
for the optimum step during minimization?
1427 The final chi2 after the fit converges,
or is forced to end.
1432 Raised
if the fitter fails
with a non-finite value.
1434 Raised
if the fitter fails
for some other reason;
1435 log messages will provide further details.
1437 if self.config.writeInitMatrix:
1438 dumpMatrixFile = self.
_getDebugPath(f
"{name}_postinit-{dataName}")
1442 oldChi2.chi2 = float(
"inf")
1443 for i
in range(max_steps):
1444 if self.config.writeChi2FilesOuterLoop:
1445 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}"
1447 writeChi2Name =
None
1448 result = fitter.minimize(whatToFit,
1449 self.config.outlierRejectSigma,
1450 sigmaRelativeTolerance=sigmaRelativeTolerance,
1451 doRankUpdate=doRankUpdate,
1452 doLineSearch=doLineSearch,
1453 dumpMatrixFile=dumpMatrixFile)
1456 f
"Fit iteration {i}", writeChi2Name=writeChi2Name)
1458 if result == MinimizeResult.Converged:
1460 self.log.debug(
"fit has converged - no more outliers - redo minimization "
1461 "one more time in case we have lost accuracy in rank update.")
1463 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma,
1464 sigmaRelativeTolerance=sigmaRelativeTolerance)
1468 if chi2.chi2/chi2.ndof >= 4.0:
1469 self.log.error(
"Potentially bad fit: High chi-squared/ndof.")
1472 elif result == MinimizeResult.Chi2Increased:
1473 self.log.warning(
"Still some outliers remaining but chi2 increased - retry")
1475 chi2Ratio = chi2.chi2 / oldChi2.chi2
1477 self.log.warning(
'Significant chi2 increase by a factor of %.4g / %.4g = %.4g',
1478 chi2.chi2, oldChi2.chi2, chi2Ratio)
1485 msg = (
"Large chi2 increase between steps: fit likely cannot converge."
1486 " Try setting one or more of the `writeChi2*` config fields and looking"
1487 " at how individual star chi2-values evolve during the fit.")
1488 raise RuntimeError(msg)
1490 elif result == MinimizeResult.NonFinite:
1491 filename = self.
_getDebugPath(
"{}_failure-nonfinite_chi2-{}.csv".format(name, dataName))
1493 fitter.saveChi2Contributions(filename+
"{type}")
1494 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}"
1495 raise FloatingPointError(msg.format(filename))
1496 elif result == MinimizeResult.Failed:
1497 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1499 raise RuntimeError(
"Unxepected return code from minimize().")
1501 self.log.error(
"%s failed to converge after %d steps"%(name, max_steps))
1505 def _make_output(self, ccdImageList, model, func):
1506 """Return the internal jointcal models converted to the afw
1507 structures that will be saved to disk.
1512 The list of CcdImages to get the output for.
1516 The name of the function to call on ``model`` to get the converted
1523 The data to be saved, keyed on (visit, detector).
1526 for ccdImage
in ccdImageList:
1527 ccd = ccdImage.ccdId
1528 visit = ccdImage.visit
1529 self.log.debug(
"%s for visit: %d, ccd: %d", func, visit, ccd)
1530 output[(visit, ccd)] = getattr(model, func)(ccdImage)
1535 """Return an afw SourceTable to use as a base for creating the
1536 SourceCatalog to insert values from the dataFrame into.
1541 Table
with schema
and slots to use to make SourceCatalogs.
1544 schema.addField("centroid_x",
"D")
1545 schema.addField(
"centroid_y",
"D")
1546 schema.addField(
"centroid_xErr",
"F")
1547 schema.addField(
"centroid_yErr",
"F")
1548 schema.addField(
"shape_xx",
"D")
1549 schema.addField(
"shape_yy",
"D")
1550 schema.addField(
"shape_xy",
"D")
1551 schema.addField(
"flux_instFlux",
"D")
1552 schema.addField(
"flux_instFluxErr",
"D")
1554 table.defineCentroid(
"centroid")
1555 table.defineShape(
"shape")
1561 Get the sourceTable_visit columns to load from the catalogs.
1566 List of columns known to be available
in the sourceTable_visit.
1567 config : `JointcalConfig`
1568 A filled-
in config to to help define column names.
1569 sourceSelector : `lsst.meas.algorithms.BaseSourceSelectorTask`
1570 A configured source selector to define column names to load.
1575 List of columns to read
from sourceTable_visit.
1576 detectorColumn : `str`
1577 Name of the detector column.
1579 Name of the ixx/iyy/ixy columns.
1581 if 'detector' in inColumns:
1583 detectorColumn =
'detector'
1586 detectorColumn =
'ccd'
1588 columns = [
'visit', detectorColumn,
1589 'sourceId',
'x',
'xErr',
'y',
'yErr',
1590 config.sourceFluxType +
'_instFlux', config.sourceFluxType +
'_instFluxErr']
1592 if 'ixx' in inColumns:
1594 ixxColumns = [
'ixx',
'iyy',
'ixy']
1597 ixxColumns = [
'Ixx',
'Iyy',
'Ixy']
1598 columns.extend(ixxColumns)
1600 if sourceSelector.config.doFlags:
1601 columns.extend(sourceSelector.config.flags.bad)
1602 if sourceSelector.config.doUnresolved:
1603 columns.append(sourceSelector.config.unresolved.name)
1604 if sourceSelector.config.doIsolated:
1605 columns.append(sourceSelector.config.isolated.parentName)
1606 columns.append(sourceSelector.config.isolated.nChildName)
1608 return columns, detectorColumn, ixxColumns
1612 detectorColumn, ixxColumns, sourceFluxType, log):
1613 """Return an afw SourceCatalog extracted from a visit-level dataframe,
1614 limited to just one detector.
1619 Table factory to use to make the SourceCatalog that will be
1620 populated with data
from ``visitCatalog``.
1621 visitCatalog : `pandas.DataFrame`
1622 DataFrame to extract a detector catalog
from.
1624 Numeric id of the detector to extract
from ``visitCatalog``.
1625 detectorColumn : `str`
1626 Name of the detector column
in the catalog.
1627 ixxColumns : `list` [`str`]
1628 Names of the ixx/iyy/ixy columns
in the catalog.
1629 sourceFluxType : `str`
1630 Name of the catalog field to load instFluxes
from.
1631 log : `logging.Logger`
1632 Logging instance to log to.
1637 Detector-level catalog extracted
from ``visitCatalog``,
or `
None`
1638 if there was no data to load.
1641 mapping = {
'x':
'centroid_x',
1643 'xErr':
'centroid_xErr',
1644 'yErr':
'centroid_yErr',
1645 ixxColumns[0]:
'shape_xx',
1646 ixxColumns[1]:
'shape_yy',
1647 ixxColumns[2]:
'shape_xy',
1648 f
'{sourceFluxType}_instFlux':
'flux_instFlux',
1649 f
'{sourceFluxType}_instFluxErr':
'flux_instFluxErr',
1653 matched = visitCatalog[detectorColumn] == detectorId
1657 catalog.resize(sum(matched))
1658 view = visitCatalog.loc[matched]
1659 catalog[
'id'] = view.index
1660 for dfCol, afwCol
in mapping.items():
1661 catalog[afwCol] = view[dfCol]
1663 log.debug(
"%d sources selected in visit %d detector %d",
1665 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...