29import astropy.units
as u
40from lsst.verify
import Job, Measurement
43 LoadReferenceObjectsConfig)
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)
61def lookupStaticCalibrations(datasetType, registry, quantumDataId, collections):
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")
268 if "metricvalue_jointcal_astrometry" in key:
270 if not config.doPhotometry:
271 self.prerequisiteInputs.
remove(
"photometryRefCat")
274 if "metricvalue_jointcal_photometry" in key:
278 return (
"inputVisitSummary",)
282 pipelineConnections=JointcalTaskConnections):
283 """Configuration for JointcalTask"""
285 doAstrometry = pexConfig.Field(
286 doc=
"Fit astrometry and write the fitted result.",
290 doPhotometry = pexConfig.Field(
291 doc=
"Fit photometry and write the fitted result.",
295 sourceFluxType = pexConfig.Field(
297 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
298 default=
'apFlux_12_0'
300 positionErrorPedestal = pexConfig.Field(
301 doc=
"Systematic term to apply to the measured position error (pixels)",
305 photometryErrorPedestal = pexConfig.Field(
306 doc=
"Systematic term to apply to the measured error on flux or magnitude as a "
307 "fraction of source flux or magnitude delta (e.g. 0.05 is 5% of flux or +50 millimag).",
312 matchCut = pexConfig.Field(
313 doc=
"Matching radius between fitted and reference stars (arcseconds)",
317 minMeasurements = pexConfig.Field(
318 doc=
"Minimum number of associated measured stars for a fitted star to be included in the fit",
322 minMeasuredStarsPerCcd = pexConfig.Field(
323 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
327 minRefStarsPerCcd = pexConfig.Field(
328 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
332 allowLineSearch = pexConfig.Field(
333 doc=
"Allow a line search during minimization, if it is reasonable for the model"
334 " (models with a significant non-linear component, e.g. constrainedPhotometry).",
338 astrometrySimpleOrder = pexConfig.Field(
339 doc=
"Polynomial order for fitting the simple astrometry model.",
343 astrometryChipOrder = pexConfig.Field(
344 doc=
"Order of the per-chip transform for the constrained astrometry model.",
348 astrometryVisitOrder = pexConfig.Field(
349 doc=
"Order of the per-visit transform for the constrained astrometry model.",
353 useInputWcs = pexConfig.Field(
354 doc=
"Use the input calexp WCSs to initialize a SimpleAstrometryModel.",
358 astrometryModel = pexConfig.ChoiceField(
359 doc=
"Type of model to fit to astrometry",
361 default=
"constrained",
362 allowed={
"simple":
"One polynomial per ccd",
363 "constrained":
"One polynomial per ccd, and one polynomial per visit"}
365 photometryModel = pexConfig.ChoiceField(
366 doc=
"Type of model to fit to photometry",
368 default=
"constrainedMagnitude",
369 allowed={
"simpleFlux":
"One constant zeropoint per ccd and visit, fitting in flux space.",
370 "constrainedFlux":
"Constrained zeropoint per ccd, and one polynomial per visit,"
371 " fitting in flux space.",
372 "simpleMagnitude":
"One constant zeropoint per ccd and visit,"
373 " fitting in magnitude space.",
374 "constrainedMagnitude":
"Constrained zeropoint per ccd, and one polynomial per visit,"
375 " fitting in magnitude space.",
378 applyColorTerms = pexConfig.Field(
379 doc=
"Apply photometric color terms to reference stars?"
380 "Requires that colorterms be set to a ColortermLibrary",
384 colorterms = pexConfig.ConfigField(
385 doc=
"Library of photometric reference catalog name to color term dict.",
386 dtype=ColortermLibrary,
388 photometryVisitOrder = pexConfig.Field(
389 doc=
"Order of the per-visit polynomial transform for the constrained photometry model.",
393 photometryDoRankUpdate = pexConfig.Field(
394 doc=(
"Do the rank update step during minimization. "
395 "Skipping this can help deal with models that are too non-linear."),
399 astrometryDoRankUpdate = pexConfig.Field(
400 doc=(
"Do the rank update step during minimization (should not change the astrometry fit). "
401 "Skipping this can help deal with models that are too non-linear."),
405 outlierRejectSigma = pexConfig.Field(
406 doc=
"How many sigma to reject outliers at during minimization.",
410 astrometryOutlierRelativeTolerance = pexConfig.Field(
411 doc=(
"Convergence tolerance for outlier rejection threshold when fitting astrometry. Iterations will "
412 "stop when the fractional change in the chi2 cut level is below this value. If tolerance is set "
413 "to zero, iterations will continue until there are no more outliers. We suggest a value of 0.002"
414 "as a balance between a shorter minimization runtime and achieving a final fitted model that is"
415 "close to the solution found when removing all outliers."),
419 maxPhotometrySteps = pexConfig.Field(
420 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
424 maxAstrometrySteps = pexConfig.Field(
425 doc=
"Maximum number of minimize iterations to take when fitting astrometry.",
429 astrometryRefObjLoader = pexConfig.ConfigField(
430 dtype=LoadReferenceObjectsConfig,
431 doc=
"Reference object loader for astrometric fit",
433 photometryRefObjLoader = pexConfig.ConfigField(
434 dtype=LoadReferenceObjectsConfig,
435 doc=
"Reference object loader for photometric fit",
437 sourceSelector = sourceSelectorRegistry.makeField(
438 doc=
"How to select sources for cross-matching",
441 astrometryReferenceSelector = pexConfig.ConfigurableField(
442 target=ReferenceSourceSelectorTask,
443 doc=
"How to down-select the loaded astrometry reference catalog.",
445 photometryReferenceSelector = pexConfig.ConfigurableField(
446 target=ReferenceSourceSelectorTask,
447 doc=
"How to down-select the loaded photometry reference catalog.",
449 astrometryReferenceErr = pexConfig.Field(
450 doc=(
"Uncertainty on reference catalog coordinates [mas] to use in place of the `coord_*Err` fields. "
451 "If None, then raise an exception if the reference catalog is missing coordinate errors. "
452 "If specified, overrides any existing `coord_*Err` values."),
459 writeInitMatrix = pexConfig.Field(
461 doc=(
"Write the pre/post-initialization Hessian and gradient to text files, for debugging. "
462 "Output files will be written to `config.debugOutputPath` and will "
463 "be of the form 'astrometry_[pre|post]init-TRACT-FILTER-mat.txt'. "
464 "Note that these files are the dense versions of the matrix, and so may be very large."),
467 writeChi2FilesInitialFinal = pexConfig.Field(
469 doc=(
"Write .csv files containing the contributions to chi2 for the initialization and final fit. "
470 "Output files will be written to `config.debugOutputPath` and will "
471 "be of the form `astrometry_[initial|final]_chi2-TRACT-FILTER."),
474 writeChi2FilesOuterLoop = pexConfig.Field(
476 doc=(
"Write .csv files containing the contributions to chi2 for the outer fit loop. "
477 "Output files will be written to `config.debugOutputPath` and will "
478 "be of the form `astrometry_init-NN_chi2-TRACT-FILTER`."),
481 writeInitialModel = pexConfig.Field(
483 doc=(
"Write the pre-initialization model to text files, for debugging. "
484 "Output files will be written to `config.debugOutputPath` and will be "
485 "of the form `initial_astrometry_model-TRACT_FILTER.txt`."),
488 debugOutputPath = pexConfig.Field(
491 doc=(
"Path to write debug output files to. Used by "
492 "`writeInitialModel`, `writeChi2Files*`, `writeInitMatrix`.")
494 detailedProfile = pexConfig.Field(
497 doc=
"Output separate profiling information for different parts of jointcal, e.g. data read, fitting"
503 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary."
504 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
506 msg = (
"Only doing astrometry, but Colorterms are not applied for astrometry;"
507 "applyColorTerms=True will be ignored.")
508 logging.getLogger(
"lsst.jointcal").warning(msg)
522 self.
sourceSelector[
"science"].signalToNoise.fluxField = f
"{self.sourceFluxType}_instFlux"
523 self.
sourceSelector[
"science"].signalToNoise.errField = f
"{self.sourceFluxType}_instFluxErr"
526 self.
sourceSelector[
"science"].isolated.parentName =
"parentSourceId"
527 self.
sourceSelector[
"science"].isolated.nChildName =
"deblend_nChild"
531 badFlags = [
"pixelFlags_edge",
532 "pixelFlags_saturated",
533 "pixelFlags_interpolatedCenter",
534 "pixelFlags_interpolated",
535 "pixelFlags_crCenter",
537 "hsmPsfMoments_flag",
538 f
"{self.sourceFluxType}_flag",
542 self.
sourceSelector[
"science"].requireFiniteRaDec.raColName =
"ra"
543 self.
sourceSelector[
"science"].requireFiniteRaDec.decColName =
"dec"
552 """Write model to outfile."""
553 with open(filename,
"w")
as file:
554 file.write(repr(model))
555 log.info(
"Wrote %s to file: %s", model, filename)
558@dataclasses.dataclass
560 """The input data jointcal needs for each detector/visit."""
562 """The visit identifier of this exposure."""
564 """The catalog derived from this exposure."""
566 """The VisitInfo of this exposure."""
568 """The detector of this exposure."""
570 """The photometric calibration of this exposure."""
572 """The WCS of this exposure."""
574 """The bounding box of this exposure."""
576 """The filter of this exposure."""
580 """Astrometricly and photometricly calibrate across multiple visits of the
584 ConfigClass = JointcalConfig
585 _DefaultName = "jointcal"
587 def __init__(self, **kwargs):
588 super().__init__(**kwargs)
589 self.makeSubtask(
"sourceSelector")
590 if self.
config.doAstrometry:
591 self.makeSubtask(
"astrometryReferenceSelector")
594 if self.
config.doPhotometry:
595 self.makeSubtask(
"photometryReferenceSelector")
600 self.
job = Job.load_metrics_package(subset=
'jointcal')
605 inputs = butlerQC.get(inputRefs)
607 tract = butlerQC.quantum.dataId[
'tract']
608 if self.
config.doAstrometry:
610 dataIds=[ref.datasetRef.dataId
611 for ref
in inputRefs.astrometryRefCat],
612 refCats=inputs.pop(
'astrometryRefCat'),
613 config=self.
config.astrometryRefObjLoader,
614 name=self.
config.connections.astrometryRefCat,
616 if self.
config.doPhotometry:
618 dataIds=[ref.datasetRef.dataId
619 for ref
in inputRefs.photometryRefCat],
620 refCats=inputs.pop(
'photometryRefCat'),
621 config=self.
config.photometryRefObjLoader,
622 name=self.
config.connections.photometryRefCat,
624 outputs = self.
run(**inputs, tract=tract)
626 if self.
config.doAstrometry:
627 self.
_put_output(butlerQC, outputs.outputWcs, outputRefs.outputWcs,
628 inputs[
'inputCamera'],
"setWcs")
629 if self.
config.doPhotometry:
630 self.
_put_output(butlerQC, outputs.outputPhotoCalib, outputRefs.outputPhotoCalib,
631 inputs[
'inputCamera'],
"setPhotoCalib")
634 """Persist all measured metrics stored in a job.
638 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
639 A butler which is specialized to operate
in the context of a
640 `lsst.daf.butler.Quantum`; This
is the input to `runQuantum`.
641 job : `lsst.verify.job.Job`
642 Measurements of metrics to persist.
643 outputRefs : `list` [`lsst.pipe.base.connectionTypes.OutputQuantizedConnection`]
644 The DatasetRefs to persist the data to.
646 for key
in job.measurements.keys():
647 butlerQC.put(job.measurements[key], getattr(outputRefs, key.fqn.replace(
'jointcal.',
'')))
649 def _put_output(self, butlerQC, outputs, outputRefs, camera, setter):
650 """Persist the output datasets to their appropriate datarefs.
654 butlerQC : `lsst.pipe.base.ButlerQuantumContext`
655 A butler which is specialized to operate
in the context of a
656 `lsst.daf.butler.Quantum`; This
is the input to `runQuantum`.
659 The fitted objects to persist.
660 outputRefs : `list` [`lsst.pipe.base.connectionTypes.OutputQuantizedConnection`]
661 The DatasetRefs to persist the data to.
663 The camera
for this instrument, to get detector ids
from.
665 The method to call on the ExposureCatalog to set each output.
668 schema.addField('visit', type=
'L', doc=
'Visit number')
670 def new_catalog(visit, size):
671 """Return an catalog ready to be filled with appropriate output."""
674 catalog[
'visit'] = visit
676 metadata.add(
"COMMENT",
"Catalog id is detector id, sorted.")
677 metadata.add(
"COMMENT",
"Only detectors with data have entries.")
681 detectors_per_visit = collections.defaultdict(int)
684 detectors_per_visit[key[0]] += 1
686 for ref
in outputRefs:
687 visit = ref.dataId[
'visit']
688 catalog = new_catalog(visit, detectors_per_visit[visit])
691 for detector
in camera:
692 detectorId = detector.getId()
693 key = (ref.dataId[
'visit'], detectorId)
694 if key
not in outputs:
696 self.
log.debug(
"No %s output for detector %s in visit %s",
697 setter[3:], detectorId, visit)
700 catalog[i].setId(detectorId)
701 getattr(catalog[i], setter)(outputs[key])
705 butlerQC.put(catalog, ref)
706 self.
log.info(
"Wrote %s detectors to %s", i, ref)
708 def run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None):
714 sourceFluxField =
"flux"
718 oldWcsList, bands = self.
_load_data(inputSourceTableVisit,
724 boundingCircle, center, radius, defaultFilter, epoch = self.
_prep_sky(associations, bands)
726 if self.
config.doAstrometry:
730 referenceSelector=self.astrometryReferenceSelector,
734 astrometry_output = self.
_make_output(associations.getCcdImageList(),
738 astrometry_output =
None
740 if self.
config.doPhotometry:
744 referenceSelector=self.photometryReferenceSelector,
748 reject_bad_fluxes=
True)
749 photometry_output = self.
_make_output(associations.getCcdImageList(),
753 photometry_output =
None
755 return pipeBase.Struct(outputWcs=astrometry_output,
756 outputPhotoCalib=photometry_output,
761 def _load_data(self, inputSourceTableVisit, inputVisitSummary, associations,
762 jointcalControl, camera):
763 """Read the data that jointcal needs to run.
765 Modifies ``associations`` in-place
with the loaded data.
769 inputSourceTableVisit : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
770 References to visit-level DataFrames to load the catalog data
from.
771 inputVisitSummary : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
772 Visit-level exposure summary catalog
with metadata.
774 Object to add the loaded data to by constructing new CcdImages.
776 Control object
for C++ associations management.
778 Camera object
for detector geometry.
783 The original WCS of the input data, to aid
in writing tests.
784 bands : `list` [`str`]
785 The filter bands of each input dataset.
789 load_cat_profile_file = 'jointcal_load_data.prof' if self.
config.detailedProfile
else ''
790 with lsst.utils.timer.profile(load_cat_profile_file):
793 catalogMap = {ref.dataId[
'visit']: i
for i, ref
in enumerate(inputSourceTableVisit)}
794 detectorDict = {detector.getId(): detector
for detector
in camera}
798 for visitSummaryRef
in inputVisitSummary:
799 visitSummary = visitSummaryRef.get()
801 dataRef = inputSourceTableVisit[catalogMap[visitSummaryRef.dataId[
'visit']]]
803 inColumns = dataRef.get(component=
'columns')
807 visitCatalog = dataRef.get(parameters={
'columns': columns})
810 if len(selected.sourceCat) == 0:
811 self.
log.warning(
"No sources selected in visit %s. Skipping...",
812 visitSummary[
"visit"][0])
816 detectors = {id: index
for index, id
in enumerate(visitSummary[
'id'])}
817 for id, index
in detectors.items():
822 self.
config.sourceFluxType,
828 if result
is not None:
829 oldWcsList.append(result.wcs)
831 filters.append(data.filter)
832 if len(filters) == 0:
833 raise RuntimeError(
"No data to process: did source selector remove all sources?")
834 filters = collections.Counter(filters)
836 return oldWcsList, filters
839 """Return a data structure for this detector+visit."""
842 visitInfo=visitRecord.getVisitInfo(),
843 detector=detectorDict[visitRecord.getId()],
844 photoCalib=visitRecord.getPhotoCalib(),
845 wcs=visitRecord.getWcs(),
846 bbox=visitRecord.getBBox(),
850 physical=visitRecord[
'physical_filter']))
854 Extract the necessary things from this catalog+metadata to add a new
859 data : `JointcalInputData`
860 The loaded input data.
862 Object to add the info to, to construct a new CcdImage
864 Control object
for associations management
870 The TAN WCS of this image, read
from the calexp
873 A key to identify this dataRef by its visit
and ccd ids
876 if there are no sources
in the loaded catalog.
878 if len(data.catalog) == 0:
879 self.
log.warning(
"No sources selected in visit %s ccd %s", data.visit, data.detector.getId())
882 associations.createCcdImage(data.catalog,
886 data.filter.physicalLabel,
890 data.detector.getId(),
893 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key'))
894 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
895 return Result(data.wcs, Key(data.visit, data.detector.getId()))
898 """Constructs a path to filename using the configured debug path.
900 return os.path.join(self.
config.debugOutputPath, filename)
903 """Prepare on-sky and other data that must be computed after data has
906 associations.computeCommonTangentPoint()
908 boundingCircle = associations.computeBoundingCircle()
910 radius = lsst.geom.Angle(boundingCircle.getOpeningAngle().asRadians(), lsst.geom.radians)
912 self.log.info(f"Data has center={center} with radius={radius.asDegrees()} degrees.")
915 defaultFilter = filters.most_common(1)[0][0]
916 self.
log.debug(
"Using '%s' filter for reference flux", defaultFilter.physicalLabel)
920 associations.setEpoch(epoch.jyear)
922 return boundingCircle, center, radius, defaultFilter, epoch
925 """Check whether we should override the refcat coordinate errors, and
926 return the overridden error
if necessary.
931 The reference catalog to check
for a ``coord_raErr`` field.
933 Whether we are doing
"astrometry" or "photometry".
937 refCoordErr : `float`
938 The refcat coordinate error to use,
or NaN
if we are
not overriding
944 Raised
if the refcat does
not contain coordinate errors
and
945 ``config.astrometryReferenceErr``
is not set.
949 if name.lower() ==
"photometry":
950 if 'coord_raErr' not in refCat.schema:
955 if self.
config.astrometryReferenceErr
is None and 'coord_raErr' not in refCat.schema:
956 msg = (
"Reference catalog does not contain coordinate errors, "
957 "and config.astrometryReferenceErr not supplied.")
958 raise pexConfig.FieldValidationError(JointcalConfig.astrometryReferenceErr,
962 if self.
config.astrometryReferenceErr
is not None and 'coord_raErr' in refCat.schema:
963 self.
log.warning(
"Overriding reference catalog coordinate errors with %f/coordinate [mas]",
964 self.
config.astrometryReferenceErr)
966 if self.
config.astrometryReferenceErr
is None:
969 return self.
config.astrometryReferenceErr
972 """Return the proper motion correction epoch of the provided images.
977 The images to compute the appropriate epoch for.
981 epoch : `astropy.time.Time`
982 The date to use
for proper motion corrections.
984 return astropy.time.Time(np.mean([ccdImage.getEpoch()
for ccdImage
in ccdImageList]),
989 tract="", match_cut=3.0,
990 reject_bad_fluxes=False, *,
991 name="", refObjLoader=None, referenceSelector=None,
992 fit_function=None, epoch=None):
993 """Load reference catalog, perform the fit, and return the result.
998 The star/reference star associations to fit.
1000 filter to load from reference catalog.
1002 ICRS center of field to load
from reference catalog.
1004 On-sky radius to load
from reference catalog.
1006 Name of thing being fit:
"astrometry" or "photometry".
1008 Reference object loader to use to load a reference catalog.
1010 Selector to use to pick objects
from the loaded reference catalog.
1011 fit_function : callable
1012 Function to call to perform fit (takes Associations object).
1013 tract : `str`, optional
1014 Name of tract currently being fit.
1015 match_cut : `float`, optional
1016 Radius
in arcseconds to find cross-catalog matches to during
1017 associations.associateCatalogs.
1018 reject_bad_fluxes : `bool`, optional
1019 Reject refCat sources
with NaN/inf flux
or NaN/0 fluxErr.
1020 epoch : `astropy.time.Time`, optional
1021 Epoch to which to correct refcat proper motion
and parallax,
1022 or `
None` to
not apply such corrections.
1026 result : `Photometry`
or `Astrometry`
1027 Result of `fit_function()`
1029 self.log.info("====== Now processing %s...", name)
1032 associations.associateCatalogs(match_cut)
1034 associations.fittedStarListSize())
1036 applyColorterms =
False if name.lower() ==
"astrometry" else self.
config.applyColorTerms
1038 center, radius, defaultFilter,
1039 applyColorterms=applyColorterms,
1043 associations.collectRefStars(refCat,
1044 self.
config.matchCut*lsst.geom.arcseconds,
1046 refCoordinateErr=refCoordErr,
1047 rejectBadFluxes=reject_bad_fluxes)
1049 associations.refStarListSize())
1051 associations.prepareFittedStars(self.
config.minMeasurements)
1055 associations.nFittedStarsWithAssociatedRefStar())
1057 associations.fittedStarListSize())
1059 associations.nCcdImagesValidForFit())
1061 fit_profile_file =
'jointcal_fit_%s.prof'%name
if self.
config.detailedProfile
else ''
1062 dataName =
"{}_{}".format(tract, defaultFilter.physicalLabel)
1063 with lsst.utils.timer.profile(fit_profile_file):
1064 result = fit_function(associations, dataName)
1067 if self.
config.writeChi2FilesInitialFinal:
1068 baseName = self.
_getDebugPath(f
"{name}_final_chi2-{dataName}")
1069 result.fit.saveChi2Contributions(baseName+
"{type}")
1070 self.
log.info(
"Wrote chi2 contributions files: %s", baseName)
1075 applyColorterms=False, epoch=None):
1076 """Load the necessary reference catalog sources, convert fluxes to
1077 correct units, and apply color term corrections
if requested.
1082 The reference catalog loader to use to get the data.
1084 Source selector to apply to loaded reference catalog.
1086 The center around which to load sources.
1088 The radius around ``center`` to load sources
in.
1090 The camera filter to load fluxes
for.
1091 applyColorterms : `bool`
1092 Apply colorterm corrections to the refcat
for ``filterName``?
1093 epoch : `astropy.time.Time`, optional
1094 Epoch to which to correct refcat proper motion
and parallax,
1095 or `
None` to
not apply such corrections.
1100 The loaded reference catalog.
1102 The name of the reference catalog flux field appropriate
for ``filterName``.
1104 skyCircle = refObjLoader.loadSkyCircle(center,
1106 filterLabel.bandLabel,
1109 selected = referenceSelector.run(skyCircle.refCat)
1111 if not selected.sourceCat.isContiguous():
1112 refCat = selected.sourceCat.copy(deep=
True)
1114 refCat = selected.sourceCat
1117 refCatName = refObjLoader.name
1118 self.
log.info(
"Applying color terms for physical filter=%r reference catalog=%s",
1119 filterLabel.physicalLabel, refCatName)
1120 colorterm = self.
config.colorterms.getColorterm(filterLabel.physicalLabel,
1124 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat)
1125 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
1127 refCat[skyCircle.fluxField+
'Err'] = fluxErrFromABMagErr(refMagErr, refMag) * 1e9
1129 return refCat, skyCircle.fluxField
1133 if associations.nCcdImagesValidForFit() == 0:
1134 raise RuntimeError(
'No images in the ccdImageList!')
1135 if associations.fittedStarListSize() == 0:
1136 raise RuntimeError(
'No stars in the {} fittedStarList!'.format(name))
1137 if associations.refStarListSize() == 0:
1138 raise RuntimeError(
'No stars in the {} reference star list!'.format(name))
1141 """Compute chi2, log it, validate the model, and return chi2.
1146 The star/reference star associations to fit.
1148 The fitter to use for minimization.
1149 model : `lsst.jointcal.Model`
1150 The model being fit.
1152 Label to describe the chi2 (e.g.
"Initialized",
"Final").
1153 writeChi2Name : `str`, optional
1154 Filename prefix to write the chi2 contributions to.
1155 Do
not supply an extension: an appropriate one will be added.
1160 The chi2 object
for the current fitter
and model.
1165 Raised
if chi2
is infinite
or NaN.
1167 Raised
if the model
is not valid.
1169 if writeChi2Name
is not None:
1171 fit.saveChi2Contributions(fullpath+
"{type}")
1172 self.
log.info(
"Wrote chi2 contributions files: %s", fullpath)
1174 chi2 = fit.computeChi2()
1175 self.
log.info(
"%s %s", chi2Label, chi2)
1177 if not np.isfinite(chi2.chi2):
1178 raise FloatingPointError(f
'{chi2Label} chi2 is invalid: {chi2}')
1179 if not model.validate(associations.getCcdImageList(), chi2.ndof):
1180 raise ValueError(
"Model is not valid: check log messages for warnings.")
1185 Fit the photometric data.
1190 The star/reference star associations to fit.
1192 Name of the data being processed (e.g. "1234_HSC-Y"),
for
1193 identifying debugging files.
1197 fit_result : `namedtuple`
1199 The photometric fitter used to perform the fit.
1201 The photometric model that was fit.
1203 self.log.info("=== Starting photometric fitting...")
1206 if self.
config.photometryModel ==
"constrainedFlux":
1209 visitOrder=self.
config.photometryVisitOrder,
1210 errorPedestal=self.
config.photometryErrorPedestal)
1212 doLineSearch = self.
config.allowLineSearch
1213 elif self.
config.photometryModel ==
"constrainedMagnitude":
1216 visitOrder=self.
config.photometryVisitOrder,
1217 errorPedestal=self.
config.photometryErrorPedestal)
1219 doLineSearch = self.
config.allowLineSearch
1220 elif self.
config.photometryModel ==
"simpleFlux":
1222 errorPedestal=self.
config.photometryErrorPedestal)
1223 doLineSearch =
False
1224 elif self.
config.photometryModel ==
"simpleMagnitude":
1226 errorPedestal=self.
config.photometryErrorPedestal)
1227 doLineSearch =
False
1232 if self.
config.writeChi2FilesInitialFinal:
1233 baseName = f
"photometry_initial_chi2-{dataName}"
1236 if self.
config.writeInitialModel:
1237 fullpath = self.
_getDebugPath(f
"initial_photometry_model-{dataName}.txt")
1241 def getChi2Name(whatToFit):
1242 if self.
config.writeChi2FilesOuterLoop:
1243 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
1249 if self.
config.writeInitMatrix:
1250 dumpMatrixFile = self.
_getDebugPath(f
"photometry_preinit-{dataName}")
1253 if self.
config.photometryModel.startswith(
"constrained"):
1256 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
1258 writeChi2Name=getChi2Name(
"ModelVisit"))
1261 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
1263 writeChi2Name=getChi2Name(
"Model"))
1265 fit.minimize(
"Fluxes")
1267 writeChi2Name=getChi2Name(
"Fluxes"))
1269 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
1271 writeChi2Name=getChi2Name(
"ModelFluxes"))
1273 model.freezeErrorTransform()
1274 self.
log.debug(
"Photometry error scales are frozen.")
1278 self.
config.maxPhotometrySteps,
1281 doRankUpdate=self.
config.photometryDoRankUpdate,
1282 doLineSearch=doLineSearch,
1291 Fit the astrometric data.
1296 The star/reference star associations to fit.
1298 Name of the data being processed (e.g. "1234_HSC-Y"),
for
1299 identifying debugging files.
1303 fit_result : `namedtuple`
1305 The astrometric fitter used to perform the fit.
1307 The astrometric model that was fit.
1309 The model
for the sky to tangent plane projection that was used
in the fit.
1312 self.log.info("=== Starting astrometric fitting...")
1314 associations.deprojectFittedStars()
1321 if self.
config.astrometryModel ==
"constrained":
1323 sky_to_tan_projection,
1324 chipOrder=self.
config.astrometryChipOrder,
1325 visitOrder=self.
config.astrometryVisitOrder)
1326 elif self.
config.astrometryModel ==
"simple":
1328 sky_to_tan_projection,
1331 order=self.
config.astrometrySimpleOrder)
1336 if self.
config.writeChi2FilesInitialFinal:
1337 baseName = f
"astrometry_initial_chi2-{dataName}"
1340 if self.
config.writeInitialModel:
1341 fullpath = self.
_getDebugPath(f
"initial_astrometry_model-{dataName}.txt")
1345 def getChi2Name(whatToFit):
1346 if self.
config.writeChi2FilesOuterLoop:
1347 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
1351 if self.
config.writeInitMatrix:
1352 dumpMatrixFile = self.
_getDebugPath(f
"astrometry_preinit-{dataName}")
1357 if self.
config.astrometryModel ==
"constrained":
1358 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
1360 writeChi2Name=getChi2Name(
"DistortionsVisit"))
1363 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
1365 writeChi2Name=getChi2Name(
"Distortions"))
1367 fit.minimize(
"Positions")
1369 writeChi2Name=getChi2Name(
"Positions"))
1371 fit.minimize(
"Distortions Positions")
1373 writeChi2Name=getChi2Name(
"DistortionsPositions"))
1377 self.
config.maxAstrometrySteps,
1379 "Distortions Positions",
1380 sigmaRelativeTolerance=self.
config.astrometryOutlierRelativeTolerance,
1381 doRankUpdate=self.
config.astrometryDoRankUpdate,
1387 return Astrometry(fit, model, sky_to_tan_projection)
1390 """Count measured and reference stars per ccd and warn/log them."""
1391 for ccdImage
in associations.getCcdImageList():
1392 nMeasuredStars, nRefStars = ccdImage.countStars()
1393 self.
log.debug(
"ccdImage %s has %s measured and %s reference stars",
1394 ccdImage.getName(), nMeasuredStars, nRefStars)
1395 if nMeasuredStars < self.
config.minMeasuredStarsPerCcd:
1396 self.
log.warning(
"ccdImage %s has only %s measuredStars (desired %s)",
1397 ccdImage.getName(), nMeasuredStars, self.
config.minMeasuredStarsPerCcd)
1398 if nRefStars < self.
config.minRefStarsPerCcd:
1399 self.
log.warning(
"ccdImage %s has only %s RefStars (desired %s)",
1400 ccdImage.getName(), nRefStars, self.
config.minRefStarsPerCcd)
1404 sigmaRelativeTolerance=0,
1406 doLineSearch=False):
1407 """Run fitter.minimize up to max_steps times, returning the final chi2.
1412 The star/reference star associations to fit.
1414 The fitter to use for minimization.
1416 Maximum number of steps to run outlier rejection before declaring
1417 convergence failure.
1418 name : {
'photometry' or 'astrometry'}
1419 What type of data are we fitting (
for logs
and debugging files).
1421 Passed to ``fitter.minimize()`` to define the parameters to fit.
1422 dataName : `str`, optional
1423 Descriptive name
for this dataset (e.g. tract
and filter),
1425 sigmaRelativeTolerance : `float`, optional
1426 Convergence tolerance
for the fractional change
in the chi2 cut
1427 level
for determining outliers. If set to zero, iterations will
1428 continue until there are no outliers.
1429 doRankUpdate : `bool`, optional
1430 Do an Eigen rank update during minimization,
or recompute the full
1431 matrix
and gradient?
1432 doLineSearch : `bool`, optional
1433 Do a line search
for the optimum step during minimization?
1438 The final chi2 after the fit converges,
or is forced to end.
1443 Raised
if the fitter fails
with a non-finite value.
1445 Raised
if the fitter fails
for some other reason;
1446 log messages will provide further details.
1448 if self.
config.writeInitMatrix:
1449 dumpMatrixFile = self.
_getDebugPath(f
"{name}_postinit-{dataName}")
1453 oldChi2.chi2 = float(
"inf")
1454 for i
in range(max_steps):
1455 if self.
config.writeChi2FilesOuterLoop:
1456 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}"
1458 writeChi2Name =
None
1459 result = fitter.minimize(whatToFit,
1460 self.
config.outlierRejectSigma,
1461 sigmaRelativeTolerance=sigmaRelativeTolerance,
1462 doRankUpdate=doRankUpdate,
1463 doLineSearch=doLineSearch,
1464 dumpMatrixFile=dumpMatrixFile)
1467 f
"Fit iteration {i}", writeChi2Name=writeChi2Name)
1469 if result == MinimizeResult.Converged:
1471 self.
log.debug(
"fit has converged - no more outliers - redo minimization "
1472 "one more time in case we have lost accuracy in rank update.")
1474 result = fitter.minimize(whatToFit, self.
config.outlierRejectSigma,
1475 sigmaRelativeTolerance=sigmaRelativeTolerance)
1479 if chi2.chi2/chi2.ndof >= 4.0:
1480 self.
log.error(
"Potentially bad fit: High chi-squared/ndof.")
1483 elif result == MinimizeResult.Chi2Increased:
1484 self.
log.warning(
"Still some outliers remaining but chi2 increased - retry")
1486 chi2Ratio = chi2.chi2 / oldChi2.chi2
1488 self.
log.warning(
'Significant chi2 increase by a factor of %.4g / %.4g = %.4g',
1489 chi2.chi2, oldChi2.chi2, chi2Ratio)
1496 msg = (
"Large chi2 increase between steps: fit likely cannot converge."
1497 " Try setting one or more of the `writeChi2*` config fields and looking"
1498 " at how individual star chi2-values evolve during the fit.")
1499 raise RuntimeError(msg)
1501 elif result == MinimizeResult.NonFinite:
1502 filename = self.
_getDebugPath(
"{}_failure-nonfinite_chi2-{}.csv".format(name, dataName))
1504 fitter.saveChi2Contributions(filename+
"{type}")
1505 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}"
1506 raise FloatingPointError(msg.format(filename))
1507 elif result == MinimizeResult.Failed:
1508 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1510 raise RuntimeError(
"Unxepected return code from minimize().")
1512 self.
log.error(
"%s failed to converge after %d steps"%(name, max_steps))
1517 """Return the internal jointcal models converted to the afw
1518 structures that will be saved to disk.
1523 The list of CcdImages to get the output for.
1527 The name of the function to call on ``model`` to get the converted
1534 The data to be saved, keyed on (visit, detector).
1537 for ccdImage
in ccdImageList:
1538 ccd = ccdImage.ccdId
1539 visit = ccdImage.visit
1540 self.
log.debug(
"%s for visit: %d, ccd: %d", func, visit, ccd)
1541 output[(visit, ccd)] = getattr(model, func)(ccdImage)
1546 """Return an afw SourceTable to use as a base for creating the
1547 SourceCatalog to insert values from the dataFrame into.
1552 Table
with schema
and slots to use to make SourceCatalogs.
1555 schema.addField("centroid_x",
"D")
1556 schema.addField(
"centroid_y",
"D")
1557 schema.addField(
"centroid_xErr",
"F")
1558 schema.addField(
"centroid_yErr",
"F")
1559 schema.addField(
"shape_xx",
"D")
1560 schema.addField(
"shape_yy",
"D")
1561 schema.addField(
"shape_xy",
"D")
1562 schema.addField(
"flux_instFlux",
"D")
1563 schema.addField(
"flux_instFluxErr",
"D")
1565 table.defineCentroid(
"centroid")
1566 table.defineShape(
"shape")
1572 Get the sourceTable_visit columns to load from the catalogs.
1577 List of columns known to be available
in the sourceTable_visit.
1578 config : `JointcalConfig`
1579 A filled-
in config to to help define column names.
1581 A configured source selector to define column names to load.
1586 List of columns to read
from sourceTable_visit.
1588 Name of the ixx/iyy/ixy columns.
1590 columns = ['visit',
'detector',
1591 'sourceId',
'x',
'xErr',
'y',
'yErr',
1592 config.sourceFluxType +
'_instFlux', config.sourceFluxType +
'_instFluxErr']
1594 if 'ixx' in inColumns:
1596 ixxColumns = [
'ixx',
'iyy',
'ixy']
1599 ixxColumns = [
'Ixx',
'Iyy',
'Ixy']
1600 columns.extend(ixxColumns)
1602 if sourceSelector.config.doFlags:
1603 columns.extend(sourceSelector.config.flags.bad)
1604 if sourceSelector.config.doUnresolved:
1605 columns.append(sourceSelector.config.unresolved.name)
1606 if sourceSelector.config.doIsolated:
1607 columns.append(sourceSelector.config.isolated.parentName)
1608 columns.append(sourceSelector.config.isolated.nChildName)
1609 if sourceSelector.config.doRequireFiniteRaDec:
1610 columns.append(sourceSelector.config.requireFiniteRaDec.raColName)
1611 columns.append(sourceSelector.config.requireFiniteRaDec.decColName)
1612 if sourceSelector.config.doRequirePrimary:
1613 columns.append(sourceSelector.config.requirePrimary.primaryColName)
1615 return columns, ixxColumns
1619 ixxColumns, sourceFluxType, log):
1620 """Return an afw SourceCatalog extracted from a visit-level dataframe,
1621 limited to just one detector.
1626 Table factory to use to make the SourceCatalog that will be
1627 populated with data
from ``visitCatalog``.
1628 visitCatalog : `pandas.DataFrame`
1629 DataFrame to extract a detector catalog
from.
1631 Numeric id of the detector to extract
from ``visitCatalog``.
1632 ixxColumns : `list` [`str`]
1633 Names of the ixx/iyy/ixy columns
in the catalog.
1634 sourceFluxType : `str`
1635 Name of the catalog field to load instFluxes
from.
1636 log : `logging.Logger`
1637 Logging instance to log to.
1642 Detector-level catalog extracted
from ``visitCatalog``,
or `
None`
1643 if there was no data to load.
1646 mapping = {
'x':
'centroid_x',
1648 'xErr':
'centroid_xErr',
1649 'yErr':
'centroid_yErr',
1650 ixxColumns[0]:
'shape_xx',
1651 ixxColumns[1]:
'shape_yy',
1652 ixxColumns[2]:
'shape_xy',
1653 f
'{sourceFluxType}_instFlux':
'flux_instFlux',
1654 f
'{sourceFluxType}_instFluxErr':
'flux_instFluxErr',
1658 matched = visitCatalog[
'detector'] == detectorId
1662 catalog.resize(sum(matched))
1663 view = visitCatalog.loc[matched]
1664 catalog[
'id'] = view.index
1665 for dfCol, afwCol
in mapping.items():
1666 catalog[afwCol] = view[dfCol]
1668 log.debug(
"%d sources selected in visit %d detector %d",
1670 view[
'visit'].iloc[0],
An immutable representation of a camera.
A representation of a detector in a mosaic camera.
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
A group of labels for a filter in an exposure or coadd.
The photometric calibration of an exposure.
Information about a single exposure of an imaging camera.
Custom catalog class for ExposureRecord/Table.
static Schema makeMinimalSchema()
Return a minimal schema for Exposure tables and records.
Custom catalog class for record/table subclasses that are guaranteed to have an ID,...
Table class that contains measurements made on a single exposure.
static std::shared_ptr< SourceTable > make(Schema const &schema, std::shared_ptr< IdFactory > const &idFactory)
Construct a new table.
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
Class for storing ordered metadata with comments.
A class representing an angle.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
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.
__init__(self, *config=None)
getSpatialBoundsConnections(self)
_build_ccdImage(self, data, associations, jointcalControl)
_iterate_fit(self, associations, fitter, max_steps, name, whatToFit, dataName="", sigmaRelativeTolerance=0, doRankUpdate=True, doLineSearch=False)
_put_metrics(self, butlerQC, job, outputRefs)
_getDebugPath(self, filename)
_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)
_check_stars(self, associations)
_prep_sky(self, associations, filters)
_compute_proper_motion_epoch(self, ccdImageList)
_make_one_input_data(self, visitRecord, catalog, detectorDict)
_check_star_lists(self, associations, name)
_fit_photometry(self, associations, dataName=None)
_put_output(self, butlerQC, outputs, outputRefs, camera, setter)
_load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterLabel, applyColorterms=False, epoch=None)
_logChi2AndValidate(self, associations, fit, model, chi2Label, writeChi2Name=None)
_get_refcat_coordinate_error_override(self, refCat, name)
_load_data(self, inputSourceTableVisit, inputVisitSummary, associations, jointcalControl, camera)
_make_output(self, ccdImageList, model, func)
runQuantum(self, butlerQC, inputRefs, outputRefs)
_fit_astrometry(self, associations, dataName=None)
run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None)
extract_detector_catalog_from_visit_catalog(table, visitCatalog, detectorId, ixxColumns, sourceFluxType, log)
add_measurement(job, name, value)
writeModel(model, filename, log)
get_sourceTable_visit_columns(inColumns, config, sourceSelector)
lookupVisitRefCats(datasetType, registry, quantumDataId, collections)
This is a virtual class that allows a lot of freedom in the choice of the projection from "Sky" (wher...