28 import astropy.units
as u
40 from lsst.verify
import Job, Measurement
43 ReferenceObjectLoader)
46 from .dataIds
import PerTractCcdDataIdContainer
51 __all__ = [
"JointcalConfig",
"JointcalRunner",
"JointcalTask"]
53 Photometry = collections.namedtuple(
'Photometry', (
'fit',
'model'))
54 Astrometry = collections.namedtuple(
'Astrometry', (
'fit',
'model',
'sky_to_tan_projection'))
59 meas = Measurement(job.metrics[name], value)
60 job.measurements.insert(meas)
64 """Subclass of TaskRunner for jointcalTask (gen2)
66 jointcalTask.runDataRef() takes a number of arguments, one of which is a list of dataRefs
67 extracted from the command line (whereas most CmdLineTasks' runDataRef methods take
68 single dataRef, are are called repeatedly). This class transforms the processed
69 arguments generated by the ArgumentParser into the arguments expected by
70 Jointcal.runDataRef().
72 See pipeBase.TaskRunner for more information.
78 Return a list of tuples per tract, each containing (dataRefs, kwargs).
80 Jointcal operates on lists of dataRefs simultaneously.
82 kwargs[
'butler'] = parsedCmd.butler
86 for ref
in parsedCmd.id.refList:
87 refListDict.setdefault(ref.dataId[
"tract"], []).
append(ref)
89 result = [(refListDict[tract], kwargs)
for tract
in sorted(refListDict.keys())]
97 Arguments for Task.runDataRef()
102 if self.doReturnResults is False:
104 - ``exitStatus``: 0 if the task completed successfully, 1 otherwise.
106 if self.doReturnResults is True:
108 - ``result``: the result of calling jointcal.runDataRef()
109 - ``exitStatus``: 0 if the task completed successfully, 1 otherwise.
114 dataRefList, kwargs = args
115 butler = kwargs.pop(
'butler')
116 task = self.TaskClass(config=self.config, log=self.log, butler=butler)
119 result = task.runDataRef(dataRefList, **kwargs)
120 exitStatus = result.exitStatus
121 job_path = butler.get(
'verify_job_filename')
122 result.job.write(job_path[0])
123 except Exception
as e:
128 eName =
type(e).__name__
129 tract = dataRefList[0].dataId[
'tract']
130 task.log.fatal(
"Failed processing tract %s, %s: %s", tract, eName, e)
133 kwargs[
'butler'] = butler
134 if self.doReturnResults:
135 return pipeBase.Struct(result=result, exitStatus=exitStatus)
137 return pipeBase.Struct(exitStatus=exitStatus)
141 dimensions=(
"skymap",
"tract",
"instrument",
"physical_filter")):
142 """Middleware input/output connections for jointcal data."""
143 inputCamera = pipeBase.connectionTypes.Input(
144 doc=
"The camera instrument that took these observations.",
146 storageClass=
"Camera",
147 dimensions=(
"instrument",),
150 inputSourceTableVisit = pipeBase.connectionTypes.Input(
151 doc=
"Source table in parquet format, per visit",
152 name=
"sourceTable_visit",
153 storageClass=
"DataFrame",
154 dimensions=(
"instrument",
"visit"),
158 inputVisitSummary = pipeBase.connectionTypes.Input(
159 doc=(
"Per-visit consolidated exposure metadata built from calexps. "
160 "These catalogs use detector id for the id and must be sorted for "
161 "fast lookups of a detector."),
163 storageClass=
"ExposureCatalog",
164 dimensions=(
"instrument",
"visit"),
171 astrometryRefCat = pipeBase.connectionTypes.Input(
172 doc=
"The astrometry reference catalog to match to loaded input catalog sources.",
173 name=
"gaia_dr2_20200414",
174 storageClass=
"SimpleCatalog",
175 dimensions=(
"htm7",),
179 photometryRefCat = pipeBase.connectionTypes.Input(
180 doc=
"The photometry reference catalog to match to loaded input catalog sources.",
181 name=
"ps1_pv3_3pi_20170110",
182 storageClass=
"SimpleCatalog",
183 dimensions=(
"htm7",),
188 outputWcs = pipeBase.connectionTypes.Output(
189 doc=(
"Per-tract, per-visit world coordinate systems derived from the fitted model."
190 " These catalogs only contain entries for detectors with an output, and use"
191 " the detector id for the catalog id, sorted on id for fast lookups of a detector."),
192 name=
"jointcalSkyWcsCatalog",
193 storageClass=
"ExposureCatalog",
194 dimensions=(
"instrument",
"visit",
"skymap",
"tract"),
197 outputPhotoCalib = pipeBase.connectionTypes.Output(
198 doc=(
"Per-tract, per-visit photometric calibrations derived from the fitted model."
199 " These catalogs only contain entries for detectors with an output, and use"
200 " the detector id for the catalog id, sorted on id for fast lookups of a detector."),
201 name=
"jointcalPhotoCalibCatalog",
202 storageClass=
"ExposureCatalog",
203 dimensions=(
"instrument",
"visit",
"skymap",
"tract"),
209 pipelineConnections=JointcalTaskConnections):
210 """Configuration for JointcalTask"""
212 doAstrometry = pexConfig.Field(
213 doc=
"Fit astrometry and write the fitted result.",
217 doPhotometry = pexConfig.Field(
218 doc=
"Fit photometry and write the fitted result.",
222 coaddName = pexConfig.Field(
223 doc=
"Type of coadd, typically deep or goodSeeing",
228 sourceFluxType = pexConfig.Field(
230 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
233 positionErrorPedestal = pexConfig.Field(
234 doc=
"Systematic term to apply to the measured position error (pixels)",
238 photometryErrorPedestal = pexConfig.Field(
239 doc=
"Systematic term to apply to the measured error on flux or magnitude as a "
240 "fraction of source flux or magnitude delta (e.g. 0.05 is 5% of flux or +50 millimag).",
245 matchCut = pexConfig.Field(
246 doc=
"Matching radius between fitted and reference stars (arcseconds)",
250 minMeasurements = pexConfig.Field(
251 doc=
"Minimum number of associated measured stars for a fitted star to be included in the fit",
255 minMeasuredStarsPerCcd = pexConfig.Field(
256 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
260 minRefStarsPerCcd = pexConfig.Field(
261 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
265 allowLineSearch = pexConfig.Field(
266 doc=
"Allow a line search during minimization, if it is reasonable for the model"
267 " (models with a significant non-linear component, e.g. constrainedPhotometry).",
271 astrometrySimpleOrder = pexConfig.Field(
272 doc=
"Polynomial order for fitting the simple astrometry model.",
276 astrometryChipOrder = pexConfig.Field(
277 doc=
"Order of the per-chip transform for the constrained astrometry model.",
281 astrometryVisitOrder = pexConfig.Field(
282 doc=
"Order of the per-visit transform for the constrained astrometry model.",
286 useInputWcs = pexConfig.Field(
287 doc=
"Use the input calexp WCSs to initialize a SimpleAstrometryModel.",
291 astrometryModel = pexConfig.ChoiceField(
292 doc=
"Type of model to fit to astrometry",
294 default=
"constrained",
295 allowed={
"simple":
"One polynomial per ccd",
296 "constrained":
"One polynomial per ccd, and one polynomial per visit"}
298 photometryModel = pexConfig.ChoiceField(
299 doc=
"Type of model to fit to photometry",
301 default=
"constrainedMagnitude",
302 allowed={
"simpleFlux":
"One constant zeropoint per ccd and visit, fitting in flux space.",
303 "constrainedFlux":
"Constrained zeropoint per ccd, and one polynomial per visit,"
304 " fitting in flux space.",
305 "simpleMagnitude":
"One constant zeropoint per ccd and visit,"
306 " fitting in magnitude space.",
307 "constrainedMagnitude":
"Constrained zeropoint per ccd, and one polynomial per visit,"
308 " fitting in magnitude space.",
311 applyColorTerms = pexConfig.Field(
312 doc=
"Apply photometric color terms to reference stars?"
313 "Requires that colorterms be set to a ColortermLibrary",
317 colorterms = pexConfig.ConfigField(
318 doc=
"Library of photometric reference catalog name to color term dict.",
319 dtype=ColortermLibrary,
321 photometryVisitOrder = pexConfig.Field(
322 doc=
"Order of the per-visit polynomial transform for the constrained photometry model.",
326 photometryDoRankUpdate = pexConfig.Field(
327 doc=(
"Do the rank update step during minimization. "
328 "Skipping this can help deal with models that are too non-linear."),
332 astrometryDoRankUpdate = pexConfig.Field(
333 doc=(
"Do the rank update step during minimization (should not change the astrometry fit). "
334 "Skipping this can help deal with models that are too non-linear."),
338 outlierRejectSigma = pexConfig.Field(
339 doc=
"How many sigma to reject outliers at during minimization.",
343 maxPhotometrySteps = pexConfig.Field(
344 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
348 maxAstrometrySteps = pexConfig.Field(
349 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
353 astrometryRefObjLoader = pexConfig.ConfigurableField(
354 target=LoadIndexedReferenceObjectsTask,
355 doc=
"Reference object loader for astrometric fit",
357 photometryRefObjLoader = pexConfig.ConfigurableField(
358 target=LoadIndexedReferenceObjectsTask,
359 doc=
"Reference object loader for photometric fit",
361 sourceSelector = sourceSelectorRegistry.makeField(
362 doc=
"How to select sources for cross-matching",
365 astrometryReferenceSelector = pexConfig.ConfigurableField(
366 target=ReferenceSourceSelectorTask,
367 doc=
"How to down-select the loaded astrometry reference catalog.",
369 photometryReferenceSelector = pexConfig.ConfigurableField(
370 target=ReferenceSourceSelectorTask,
371 doc=
"How to down-select the loaded photometry reference catalog.",
373 astrometryReferenceErr = pexConfig.Field(
374 doc=(
"Uncertainty on reference catalog coordinates [mas] to use in place of the `coord_*Err` fields. "
375 "If None, then raise an exception if the reference catalog is missing coordinate errors. "
376 "If specified, overrides any existing `coord_*Err` values."),
383 writeInitMatrix = pexConfig.Field(
385 doc=(
"Write the pre/post-initialization Hessian and gradient to text files, for debugging. "
386 "The output files will be of the form 'astrometry_preinit-mat.txt', in the current directory. "
387 "Note that these files are the dense versions of the matrix, and so may be very large."),
390 writeChi2FilesInitialFinal = pexConfig.Field(
392 doc=
"Write .csv files containing the contributions to chi2 for the initialization and final fit.",
395 writeChi2FilesOuterLoop = pexConfig.Field(
397 doc=
"Write .csv files containing the contributions to chi2 for the outer fit loop.",
400 writeInitialModel = pexConfig.Field(
402 doc=(
"Write the pre-initialization model to text files, for debugging."
403 " Output is written to `initial[Astro|Photo]metryModel.txt` in the current working directory."),
406 debugOutputPath = pexConfig.Field(
409 doc=(
"Path to write debug output files to. Used by "
410 "`writeInitialModel`, `writeChi2Files*`, `writeInitMatrix`.")
412 detailedProfile = pexConfig.Field(
415 doc=
"Output separate profiling information for different parts of jointcal, e.g. data read, fitting"
421 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary."
422 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
424 msg = (
"Only doing astrometry, but Colorterms are not applied for astrometry;"
425 "applyColorTerms=True will be ignored.")
434 self.
sourceSelectorsourceSelector[
'science'].doSignalToNoise =
True
437 self.
sourceSelectorsourceSelector[
'science'].signalToNoise.minimum = 10.
439 fluxField = f
"slot_{self.sourceFluxType}Flux_instFlux"
440 self.
sourceSelectorsourceSelector[
'science'].signalToNoise.fluxField = fluxField
441 self.
sourceSelectorsourceSelector[
'science'].signalToNoise.errField = fluxField +
"Err"
447 badFlags = [
'base_PixelFlags_flag_edge',
'base_PixelFlags_flag_saturated',
448 'base_PixelFlags_flag_interpolatedCenter',
'base_SdssCentroid_flag',
449 'base_PsfFlux_flag',
'base_PixelFlags_flag_suspectCenter']
461 """Write model to outfile."""
462 with open(filename,
"w")
as file:
463 file.write(repr(model))
464 log.info(
"Wrote %s to file: %s", model, filename)
467 @dataclasses.dataclass
469 """The input data jointcal needs for each detector/visit."""
471 """The visit identifier of this exposure."""
473 """The catalog derived from this exposure."""
475 """The VisitInfo of this exposure."""
477 """The detector of this exposure."""
479 """The photometric calibration of this exposure."""
481 """The WCS of this exposure."""
483 """The bounding box of this exposure."""
485 """The filter of this exposure."""
489 """Astrometricly and photometricly calibrate across multiple visits of the
494 butler : `lsst.daf.persistence.Butler`
495 The butler is passed to the refObjLoader constructor in case it is
496 needed. Ignored if the refObjLoader argument provides a loader directly.
497 Used to initialize the astrometry and photometry refObjLoaders.
498 initInputs : `dict`, optional
499 Dictionary used to initialize PipelineTasks (empty for jointcal).
502 ConfigClass = JointcalConfig
503 RunnerClass = JointcalRunner
504 _DefaultName =
"jointcal"
506 def __init__(self, butler=None, initInputs=None, **kwargs):
508 self.makeSubtask(
"sourceSelector")
509 if self.config.doAstrometry:
510 if initInputs
is None:
512 self.makeSubtask(
'astrometryRefObjLoader', butler=butler)
513 self.makeSubtask(
"astrometryReferenceSelector")
516 if self.config.doPhotometry:
517 if initInputs
is None:
519 self.makeSubtask(
'photometryRefObjLoader', butler=butler)
520 self.makeSubtask(
"photometryReferenceSelector")
525 self.
jobjob = Job.load_metrics_package(subset=
'jointcal')
530 inputs = butlerQC.get(inputRefs)
532 tract = butlerQC.quantum.dataId[
'tract']
533 if self.config.doAstrometry:
535 dataIds=[ref.datasetRef.dataId
536 for ref
in inputRefs.astrometryRefCat],
537 refCats=inputs.pop(
'astrometryRefCat'),
538 config=self.config.astrometryRefObjLoader,
540 if self.config.doPhotometry:
542 dataIds=[ref.datasetRef.dataId
543 for ref
in inputRefs.photometryRefCat],
544 refCats=inputs.pop(
'photometryRefCat'),
545 config=self.config.photometryRefObjLoader,
547 outputs = self.
runrun(**inputs, tract=tract)
548 if self.config.doAstrometry:
549 self.
_put_output_put_output(butlerQC, outputs.outputWcs, outputRefs.outputWcs,
550 inputs[
'inputCamera'],
"setWcs")
551 if self.config.doPhotometry:
552 self.
_put_output_put_output(butlerQC, outputs.outputPhotoCalib, outputRefs.outputPhotoCalib,
553 inputs[
'inputCamera'],
"setPhotoCalib")
555 def _put_output(self, butlerQC, outputs, outputRefs, camera, setter):
556 """Persist the output datasets to their appropriate datarefs.
560 butlerQC : `ButlerQuantumContext`
561 A butler which is specialized to operate in the context of a
562 `lsst.daf.butler.Quantum`; This is the input to `runQuantum`.
563 outputs : `dict` [`tuple`, `lsst.afw.geom.SkyWcs`] or
564 `dict` [`tuple, `lsst.afw.image.PhotoCalib`]
565 The fitted objects to persist.
566 outputRefs : `list` [`OutputQuantizedConnection`]
567 The DatasetRefs to persist the data to.
568 camera : `lsst.afw.cameraGeom.Camera`
569 The camera for this instrument, to get detector ids from.
571 The method to call on the ExposureCatalog to set each output.
574 schema.addField(
'visit', type=
'I', doc=
'Visit number')
576 def new_catalog(visit, size):
577 """Return an catalog ready to be filled with appropriate output."""
580 catalog[
'visit'] = visit
582 metadata.add(
"COMMENT",
"Catalog id is detector id, sorted.")
583 metadata.add(
"COMMENT",
"Only detectors with data have entries.")
587 detectors_per_visit = collections.defaultdict(int)
590 detectors_per_visit[key[0]] += 1
592 for ref
in outputRefs:
593 visit = ref.dataId[
'visit']
594 catalog = new_catalog(visit, detectors_per_visit[visit])
597 for detector
in camera:
598 detectorId = detector.getId()
599 key = (ref.dataId[
'visit'], detectorId)
600 if key
not in outputs:
602 self.log.
debug(
"No %s output for detector %s in visit %s",
603 setter[3:], detectorId, visit)
606 catalog[i].setId(detectorId)
607 getattr(catalog[i], setter)(outputs[key])
611 butlerQC.put(catalog, ref)
612 self.log.
info(
"Wrote %s detectors to %s", i, ref)
614 def run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None):
620 sourceFluxField =
"flux"
624 oldWcsList, bands = self.
_load_data_load_data(inputSourceTableVisit,
630 boundingCircle, center, radius, defaultFilter = self.
_prep_sky_prep_sky(associations, bands)
633 if self.config.doAstrometry:
637 referenceSelector=self.astrometryReferenceSelector,
641 astrometry_output = self.
_make_output_make_output(associations.getCcdImageList(),
645 astrometry_output =
None
647 if self.config.doPhotometry:
651 referenceSelector=self.photometryReferenceSelector,
655 reject_bad_fluxes=
True)
656 photometry_output = self.
_make_output_make_output(associations.getCcdImageList(),
660 photometry_output =
None
662 return pipeBase.Struct(outputWcs=astrometry_output,
663 outputPhotoCalib=photometry_output,
668 def _make_schema_table(self):
669 """Return an afw SourceTable to use as a base for creating the
670 SourceCatalog to insert values from the dataFrame into.
674 table : `lsst.afw.table.SourceTable`
675 Table with schema and slots to use to make SourceCatalogs.
678 schema.addField(
"centroid_x",
"D")
679 schema.addField(
"centroid_y",
"D")
680 schema.addField(
"centroid_xErr",
"F")
681 schema.addField(
"centroid_yErr",
"F")
682 schema.addField(
"shape_xx",
"D")
683 schema.addField(
"shape_yy",
"D")
684 schema.addField(
"shape_xy",
"D")
685 schema.addField(
"flux_instFlux",
"D")
686 schema.addField(
"flux_instFluxErr",
"D")
688 table.defineCentroid(
"centroid")
689 table.defineShape(
"shape")
692 def _extract_detector_catalog_from_visit_catalog(self, table, visitCatalog, detectorId):
693 """Return an afw SourceCatalog extracted from a visit-level dataframe,
694 limited to just one detector.
698 table : `lsst.afw.table.SourceTable`
699 Table factory to use to make the SourceCatalog that will be
700 populated with data from ``visitCatalog``.
701 visitCatalog : `pandas.DataFrame`
702 DataFrame to extract a detector catalog from.
704 Numeric id of the detector to extract from ``visitCatalog``.
708 catalog : `lsst.afw.table.SourceCatalog`
709 Detector-level catalog extracted from ``visitCatalog``.
712 mapping = {
'sourceId':
'id',
715 'xErr':
'centroid_xErr',
716 'yErr':
'centroid_yErr',
720 f
'{self.config.sourceFluxType}_instFlux':
'flux_instFlux',
721 f
'{self.config.sourceFluxType}_instFluxErr':
'flux_instFluxErr',
724 matched = visitCatalog[
'ccd'] == detectorId
725 catalog.resize(sum(matched))
726 view = visitCatalog.loc[matched]
727 for dfCol, afwCol
in mapping.items():
728 catalog[afwCol] = view[dfCol]
730 self.log.
debug(
"%d sources selected in visit %d detector %d",
732 view[
'visit'].iloc[0],
736 def _load_data(self, inputSourceTableVisit, inputVisitSummary, associations,
737 jointcalControl, camera):
738 """Read the data that jointcal needs to run. (Gen3 version)
740 Modifies ``associations`` in-place with the loaded data.
744 inputSourceTableVisit : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
745 References to visit-level DataFrames to load the catalog data from.
746 inputVisitSummary : `list` [`lsst.daf.butler.DeferredDatasetHandle`]
747 Visit-level exposure summary catalog with metadata.
748 associations : `lsst.jointcal.Associations`
749 Object to add the loaded data to by constructing new CcdImages.
750 jointcalControl : `jointcal.JointcalControl`
751 Control object for C++ associations management.
752 camera : `lsst.afw.cameraGeom.Camera`
753 Camera object for detector geometry.
757 oldWcsList: `list` [`lsst.afw.geom.SkyWcs`]
758 The original WCS of the input data, to aid in writing tests.
759 bands : `list` [`str`]
760 The filter bands of each input dataset.
764 load_cat_prof_file =
'jointcal_load_data.prof' if self.config.detailedProfile
else ''
765 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
768 catalogMap = {ref.dataId[
'visit']: i
for i, ref
in enumerate(inputSourceTableVisit)}
769 detectorDict = {detector.getId(): detector
for detector
in camera}
771 for visitSummaryRef
in inputVisitSummary:
772 visitSummary = visitSummaryRef.get()
773 visitCatalog = inputSourceTableVisit[catalogMap[visitSummaryRef.dataId[
'visit']]].get()
774 selected = self.sourceSelector.
run(visitCatalog)
777 detectors = {id: index
for index, id
in enumerate(visitSummary[
'id'])}
778 for id, index
in detectors.items():
780 data = self.
_make_one_input_data_make_one_input_data(visitSummary[index], catalog, detectorDict)
781 result = self.
_build_ccdImage_build_ccdImage(data, associations, jointcalControl)
782 if result
is not None:
783 oldWcsList.append(result.wcs)
785 filters.append(data.filter)
786 if len(filters) == 0:
787 raise RuntimeError(
"No data to process: did source selector remove all sources?")
788 filters = collections.Counter(filters)
790 return oldWcsList, filters
792 def _make_one_input_data(self, visitRecord, catalog, detectorDict):
793 """Return a data structure for this detector+visit."""
796 visitInfo=visitRecord.getVisitInfo(),
797 detector=detectorDict[visitRecord.getId()],
798 photoCalib=visitRecord.getPhotoCalib(),
799 wcs=visitRecord.getWcs(),
800 bbox=visitRecord.getBBox(),
804 physical=visitRecord[
'physical_filter']))
809 def _getMetadataName(self):
813 def _makeArgumentParser(cls):
814 """Create an argument parser"""
815 parser = pipeBase.ArgumentParser(name=cls.
_DefaultName_DefaultName)
816 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=6789 ccd=0..9",
817 ContainerClass=PerTractCcdDataIdContainer)
820 def _build_ccdImage(self, data, associations, jointcalControl):
822 Extract the necessary things from this catalog+metadata to add a new
827 data : `JointcalInputData`
828 The loaded input data.
829 associations : `lsst.jointcal.Associations`
830 Object to add the info to, to construct a new CcdImage
831 jointcalControl : `jointcal.JointcalControl`
832 Control object for associations management
838 The TAN WCS of this image, read from the calexp
839 (`lsst.afw.geom.SkyWcs`).
841 A key to identify this dataRef by its visit and ccd ids
844 if there are no sources in the loaded catalog.
846 if len(data.catalog) == 0:
847 self.log.
warn(
"No sources selected in visit %s ccd %s", data.visit, data.detector.getId())
850 associations.createCcdImage(data.catalog,
854 data.filter.physicalLabel,
858 data.detector.getId(),
861 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key'))
862 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
863 return Result(data.wcs, Key(data.visit, data.detector.getId()))
865 def _readDataId(self, butler, dataId):
866 """Read all of the data for one dataId from the butler. (gen2 version)"""
868 if "visit" in dataId.keys():
869 visit = dataId[
"visit"]
871 visit = butler.getButler().queryMetadata(
"calexp", (
"visit"), butler.dataId)[0]
872 detector = butler.get(
'calexp_detector', dataId=dataId)
874 catalog = butler.get(
'src',
875 flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS,
877 goodSrc = self.sourceSelector.
run(catalog)
878 self.log.
debug(
"%d sources selected in visit %d detector %d",
879 len(goodSrc.sourceCat),
883 catalog=goodSrc.sourceCat,
884 visitInfo=butler.get(
'calexp_visitInfo', dataId=dataId),
886 photoCalib=butler.get(
'calexp_photoCalib', dataId=dataId),
887 wcs=butler.get(
'calexp_wcs', dataId=dataId),
888 bbox=butler.get(
'calexp_bbox', dataId=dataId),
889 filter=butler.get(
'calexp_filterLabel', dataId=dataId))
891 def loadData(self, dataRefs, associations, jointcalControl):
892 """Read the data that jointcal needs to run. (Gen2 version)"""
893 visit_ccd_to_dataRef = {}
896 load_cat_prof_file =
'jointcal_loadData.prof' if self.config.detailedProfile
else ''
897 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
899 camera = dataRefs[0].get(
'camera', immediate=
True)
901 for dataRef
in dataRefs:
902 data = self.
_readDataId_readDataId(dataRef.getButler(), dataRef.dataId)
903 result = self.
_build_ccdImage_build_ccdImage(data, associations, jointcalControl)
906 oldWcsList.append(result.wcs)
907 visit_ccd_to_dataRef[result.key] = dataRef
908 filters.append(data.filter)
909 if len(filters) == 0:
910 raise RuntimeError(
"No data to process: did source selector remove all sources?")
911 filters = collections.Counter(filters)
913 return oldWcsList, filters, visit_ccd_to_dataRef
915 def _getDebugPath(self, filename):
916 """Constructs a path to filename using the configured debug path.
918 return os.path.join(self.config.debugOutputPath, filename)
920 def _prep_sky(self, associations, filters):
921 """Prepare on-sky and other data that must be computed after data has
924 associations.computeCommonTangentPoint()
926 boundingCircle = associations.computeBoundingCircle()
928 radius =
lsst.geom.Angle(boundingCircle.getOpeningAngle().asRadians(), lsst.geom.radians)
930 self.log.
info(f
"Data has center={center} with radius={radius.asDegrees()} degrees.")
933 defaultFilter = filters.most_common(1)[0][0]
934 self.log.
debug(
"Using '%s' filter for reference flux", defaultFilter.physicalLabel)
936 return boundingCircle, center, radius, defaultFilter
941 Jointly calibrate the astrometry and photometry across a set of images.
943 NOTE: this is for gen2 middleware only.
947 dataRefs : `list` of `lsst.daf.persistence.ButlerDataRef`
948 List of data references to the exposures to be fit.
952 result : `lsst.pipe.base.Struct`
953 Struct of metadata from the fit, containing:
956 The provided data references that were fit (with updated WCSs)
958 The original WCS from each dataRef
960 Dictionary of internally-computed metrics for testing/validation.
962 if len(dataRefs) == 0:
963 raise ValueError(
'Need a non-empty list of data references!')
967 sourceFluxField =
"slot_%sFlux" % (self.config.sourceFluxType,)
971 oldWcsList, filters, visit_ccd_to_dataRef = self.
loadDataloadData(dataRefs,
975 boundingCircle, center, radius, defaultFilter = self.
_prep_sky_prep_sky(associations, filters)
978 tract = dataRefs[0].dataId[
'tract']
980 if self.config.doAstrometry:
984 referenceSelector=self.astrometryReferenceSelector,
992 if self.config.doPhotometry:
996 referenceSelector=self.photometryReferenceSelector,
1000 reject_bad_fluxes=
True)
1005 return pipeBase.Struct(dataRefs=dataRefs,
1006 oldWcsList=oldWcsList,
1010 defaultFilter=defaultFilter,
1012 exitStatus=exitStatus)
1014 def _get_refcat_coordinate_error_override(self, refCat, name):
1015 """Check whether we should override the refcat coordinate errors, and
1016 return the overridden error if necessary.
1020 refCat : `lsst.afw.table.SimpleCatalog`
1021 The reference catalog to check for a ``coord_raErr`` field.
1023 Whether we are doing "astrometry" or "photometry".
1027 refCoordErr : `float`
1028 The refcat coordinate error to use, or NaN if we are not overriding
1033 lsst.pex.config.FieldValidationError
1034 Raised if the refcat does not contain coordinate errors and
1035 ``config.astrometryReferenceErr`` is not set.
1039 if name.lower() ==
"photometry":
1040 if 'coord_raErr' not in refCat.schema:
1045 if self.config.astrometryReferenceErr
is None and 'coord_raErr' not in refCat.schema:
1046 msg = (
"Reference catalog does not contain coordinate errors, "
1047 "and config.astrometryReferenceErr not supplied.")
1048 raise pexConfig.FieldValidationError(JointcalConfig.astrometryReferenceErr,
1052 if self.config.astrometryReferenceErr
is not None and 'coord_raErr' in refCat.schema:
1053 self.log.
warn(
"Overriding reference catalog coordinate errors with %f/coordinate [mas]",
1054 self.config.astrometryReferenceErr)
1056 if self.config.astrometryReferenceErr
is None:
1059 return self.config.astrometryReferenceErr
1061 def _compute_proper_motion_epoch(self, ccdImageList):
1062 """Return the proper motion correction epoch of the provided images.
1066 ccdImageList : `list` [`lsst.jointcal.CcdImage`]
1067 The images to compute the appropriate epoch for.
1071 epoch : `astropy.time.Time`
1072 The date to use for proper motion corrections.
1074 mjds = [ccdImage.getMjd()
for ccdImage
in ccdImageList]
1075 return astropy.time.Time(np.mean(mjds), format=
'mjd', scale=
"tai")
1077 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
1078 tract="", match_cut=3.0,
1079 reject_bad_fluxes=False, *,
1080 name="", refObjLoader=None, referenceSelector=None,
1081 fit_function=None, epoch=None):
1082 """Load reference catalog, perform the fit, and return the result.
1086 associations : `lsst.jointcal.Associations`
1087 The star/reference star associations to fit.
1088 defaultFilter : `lsst.afw.image.FilterLabel`
1089 filter to load from reference catalog.
1090 center : `lsst.geom.SpherePoint`
1091 ICRS center of field to load from reference catalog.
1092 radius : `lsst.geom.Angle`
1093 On-sky radius to load from reference catalog.
1095 Name of thing being fit: "astrometry" or "photometry".
1096 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask`
1097 Reference object loader to use to load a reference catalog.
1098 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask`
1099 Selector to use to pick objects from the loaded reference catalog.
1100 fit_function : callable
1101 Function to call to perform fit (takes Associations object).
1102 tract : `str`, optional
1103 Name of tract currently being fit.
1104 match_cut : `float`, optional
1105 Radius in arcseconds to find cross-catalog matches to during
1106 associations.associateCatalogs.
1107 reject_bad_fluxes : `bool`, optional
1108 Reject refCat sources with NaN/inf flux or NaN/0 fluxErr.
1109 epoch : `astropy.time.Time`, optional
1110 Epoch to which to correct refcat proper motion and parallax,
1111 or `None` to not apply such corrections.
1115 result : `Photometry` or `Astrometry`
1116 Result of `fit_function()`
1118 self.log.
info(
"====== Now processing %s...", name)
1121 associations.associateCatalogs(match_cut)
1123 associations.fittedStarListSize())
1125 applyColorterms =
False if name.lower() ==
"astrometry" else self.config.applyColorTerms
1127 center, radius, defaultFilter,
1128 applyColorterms=applyColorterms,
1132 associations.collectRefStars(refCat,
1133 self.config.matchCut*lsst.geom.arcseconds,
1135 refCoordinateErr=refCoordErr,
1136 rejectBadFluxes=reject_bad_fluxes)
1138 associations.refStarListSize())
1140 associations.prepareFittedStars(self.config.minMeasurements)
1144 associations.nFittedStarsWithAssociatedRefStar())
1146 associations.fittedStarListSize())
1148 associations.nCcdImagesValidForFit())
1150 load_cat_prof_file =
'jointcal_fit_%s.prof'%name
if self.config.detailedProfile
else ''
1151 dataName =
"{}_{}".
format(tract, defaultFilter.bandLabel)
1152 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
1153 result = fit_function(associations, dataName)
1156 if self.config.writeChi2FilesInitialFinal:
1157 baseName = self.
_getDebugPath_getDebugPath(f
"{name}_final_chi2-{dataName}")
1158 result.fit.saveChi2Contributions(baseName+
"{type}")
1159 self.log.
info(
"Wrote chi2 contributions files: %s", baseName)
1163 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterLabel,
1164 applyColorterms=False, epoch=None):
1165 """Load the necessary reference catalog sources, convert fluxes to
1166 correct units, and apply color term corrections if requested.
1170 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask`
1171 The reference catalog loader to use to get the data.
1172 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask`
1173 Source selector to apply to loaded reference catalog.
1174 center : `lsst.geom.SpherePoint`
1175 The center around which to load sources.
1176 radius : `lsst.geom.Angle`
1177 The radius around ``center`` to load sources in.
1178 filterLabel : `lsst.afw.image.FilterLabel`
1179 The camera filter to load fluxes for.
1180 applyColorterms : `bool`
1181 Apply colorterm corrections to the refcat for ``filterName``?
1182 epoch : `astropy.time.Time`, optional
1183 Epoch to which to correct refcat proper motion and parallax,
1184 or `None` to not apply such corrections.
1188 refCat : `lsst.afw.table.SimpleCatalog`
1189 The loaded reference catalog.
1191 The name of the reference catalog flux field appropriate for ``filterName``.
1193 skyCircle = refObjLoader.loadSkyCircle(center,
1195 filterLabel.bandLabel,
1198 selected = referenceSelector.run(skyCircle.refCat)
1200 if not selected.sourceCat.isContiguous():
1201 refCat = selected.sourceCat.copy(deep=
True)
1203 refCat = selected.sourceCat
1206 refCatName = refObjLoader.ref_dataset_name
1207 self.log.
info(
"Applying color terms for physical filter=%r reference catalog=%s",
1208 filterLabel.physicalLabel, refCatName)
1209 colorterm = self.config.colorterms.getColorterm(filterLabel.physicalLabel,
1213 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat)
1214 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
1218 return refCat, skyCircle.fluxField
1220 def _check_star_lists(self, associations, name):
1222 if associations.nCcdImagesValidForFit() == 0:
1223 raise RuntimeError(
'No images in the ccdImageList!')
1224 if associations.fittedStarListSize() == 0:
1225 raise RuntimeError(
'No stars in the {} fittedStarList!'.
format(name))
1226 if associations.refStarListSize() == 0:
1227 raise RuntimeError(
'No stars in the {} reference star list!'.
format(name))
1229 def _logChi2AndValidate(self, associations, fit, model, chi2Label, writeChi2Name=None):
1230 """Compute chi2, log it, validate the model, and return chi2.
1234 associations : `lsst.jointcal.Associations`
1235 The star/reference star associations to fit.
1236 fit : `lsst.jointcal.FitterBase`
1237 The fitter to use for minimization.
1238 model : `lsst.jointcal.Model`
1239 The model being fit.
1241 Label to describe the chi2 (e.g. "Initialized", "Final").
1242 writeChi2Name : `str`, optional
1243 Filename prefix to write the chi2 contributions to.
1244 Do not supply an extension: an appropriate one will be added.
1248 chi2: `lsst.jointcal.Chi2Accumulator`
1249 The chi2 object for the current fitter and model.
1254 Raised if chi2 is infinite or NaN.
1256 Raised if the model is not valid.
1258 if writeChi2Name
is not None:
1260 fit.saveChi2Contributions(fullpath+
"{type}")
1261 self.log.
info(
"Wrote chi2 contributions files: %s", fullpath)
1263 chi2 = fit.computeChi2()
1264 self.log.
info(
"%s %s", chi2Label, chi2)
1266 if not np.isfinite(chi2.chi2):
1267 raise FloatingPointError(f
'{chi2Label} chi2 is invalid: {chi2}')
1268 if not model.validate(associations.getCcdImageList(), chi2.ndof):
1269 raise ValueError(
"Model is not valid: check log messages for warnings.")
1272 def _fit_photometry(self, associations, dataName=None):
1274 Fit the photometric data.
1278 associations : `lsst.jointcal.Associations`
1279 The star/reference star associations to fit.
1281 Name of the data being processed (e.g. "1234_HSC-Y"), for
1282 identifying debugging files.
1286 fit_result : `namedtuple`
1287 fit : `lsst.jointcal.PhotometryFit`
1288 The photometric fitter used to perform the fit.
1289 model : `lsst.jointcal.PhotometryModel`
1290 The photometric model that was fit.
1292 self.log.
info(
"=== Starting photometric fitting...")
1295 if self.config.photometryModel ==
"constrainedFlux":
1298 visitOrder=self.config.photometryVisitOrder,
1299 errorPedestal=self.config.photometryErrorPedestal)
1301 doLineSearch = self.config.allowLineSearch
1302 elif self.config.photometryModel ==
"constrainedMagnitude":
1305 visitOrder=self.config.photometryVisitOrder,
1306 errorPedestal=self.config.photometryErrorPedestal)
1308 doLineSearch = self.config.allowLineSearch
1309 elif self.config.photometryModel ==
"simpleFlux":
1311 errorPedestal=self.config.photometryErrorPedestal)
1312 doLineSearch =
False
1313 elif self.config.photometryModel ==
"simpleMagnitude":
1315 errorPedestal=self.config.photometryErrorPedestal)
1316 doLineSearch =
False
1321 if self.config.writeChi2FilesInitialFinal:
1322 baseName = f
"photometry_initial_chi2-{dataName}"
1325 if self.config.writeInitialModel:
1326 fullpath = self.
_getDebugPath_getDebugPath(
"initialPhotometryModel.txt")
1328 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialized", writeChi2Name=baseName)
1330 def getChi2Name(whatToFit):
1331 if self.config.writeChi2FilesOuterLoop:
1332 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
1338 dumpMatrixFile = self.
_getDebugPath_getDebugPath(
"photometry_preinit")
if self.config.writeInitMatrix
else ""
1339 if self.config.photometryModel.startswith(
"constrained"):
1342 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
1343 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize ModelVisit",
1344 writeChi2Name=getChi2Name(
"ModelVisit"))
1347 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
1349 writeChi2Name=getChi2Name(
"Model"))
1351 fit.minimize(
"Fluxes")
1352 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize Fluxes",
1353 writeChi2Name=getChi2Name(
"Fluxes"))
1355 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
1356 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize ModelFluxes",
1357 writeChi2Name=getChi2Name(
"ModelFluxes"))
1359 model.freezeErrorTransform()
1360 self.log.
debug(
"Photometry error scales are frozen.")
1364 self.config.maxPhotometrySteps,
1367 doRankUpdate=self.config.photometryDoRankUpdate,
1368 doLineSearch=doLineSearch,
1375 def _fit_astrometry(self, associations, dataName=None):
1377 Fit the astrometric data.
1381 associations : `lsst.jointcal.Associations`
1382 The star/reference star associations to fit.
1384 Name of the data being processed (e.g. "1234_HSC-Y"), for
1385 identifying debugging files.
1389 fit_result : `namedtuple`
1390 fit : `lsst.jointcal.AstrometryFit`
1391 The astrometric fitter used to perform the fit.
1392 model : `lsst.jointcal.AstrometryModel`
1393 The astrometric model that was fit.
1394 sky_to_tan_projection : `lsst.jointcal.ProjectionHandler`
1395 The model for the sky to tangent plane projection that was used in the fit.
1398 self.log.
info(
"=== Starting astrometric fitting...")
1400 associations.deprojectFittedStars()
1407 if self.config.astrometryModel ==
"constrained":
1409 sky_to_tan_projection,
1410 chipOrder=self.config.astrometryChipOrder,
1411 visitOrder=self.config.astrometryVisitOrder)
1412 elif self.config.astrometryModel ==
"simple":
1414 sky_to_tan_projection,
1415 self.config.useInputWcs,
1417 order=self.config.astrometrySimpleOrder)
1422 if self.config.writeChi2FilesInitialFinal:
1423 baseName = f
"astrometry_initial_chi2-{dataName}"
1426 if self.config.writeInitialModel:
1427 fullpath = self.
_getDebugPath_getDebugPath(
"initialAstrometryModel.txt")
1429 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initial", writeChi2Name=baseName)
1431 def getChi2Name(whatToFit):
1432 if self.config.writeChi2FilesOuterLoop:
1433 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
1437 dumpMatrixFile = self.
_getDebugPath_getDebugPath(
"astrometry_preinit")
if self.config.writeInitMatrix
else ""
1440 if self.config.astrometryModel ==
"constrained":
1441 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
1442 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize DistortionsVisit",
1443 writeChi2Name=getChi2Name(
"DistortionsVisit"))
1446 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
1447 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize Distortions",
1448 writeChi2Name=getChi2Name(
"Distortions"))
1450 fit.minimize(
"Positions")
1451 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize Positions",
1452 writeChi2Name=getChi2Name(
"Positions"))
1454 fit.minimize(
"Distortions Positions")
1455 self.
_logChi2AndValidate_logChi2AndValidate(associations, fit, model,
"Initialize DistortionsPositions",
1456 writeChi2Name=getChi2Name(
"DistortionsPositions"))
1460 self.config.maxAstrometrySteps,
1462 "Distortions Positions",
1463 doRankUpdate=self.config.astrometryDoRankUpdate,
1469 return Astrometry(fit, model, sky_to_tan_projection)
1471 def _check_stars(self, associations):
1472 """Count measured and reference stars per ccd and warn/log them."""
1473 for ccdImage
in associations.getCcdImageList():
1474 nMeasuredStars, nRefStars = ccdImage.countStars()
1475 self.log.
debug(
"ccdImage %s has %s measured and %s reference stars",
1476 ccdImage.getName(), nMeasuredStars, nRefStars)
1477 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
1478 self.log.
warn(
"ccdImage %s has only %s measuredStars (desired %s)",
1479 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
1480 if nRefStars < self.config.minRefStarsPerCcd:
1481 self.log.
warn(
"ccdImage %s has only %s RefStars (desired %s)",
1482 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
1484 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
1487 doLineSearch=False):
1488 """Run fitter.minimize up to max_steps times, returning the final chi2.
1492 associations : `lsst.jointcal.Associations`
1493 The star/reference star associations to fit.
1494 fitter : `lsst.jointcal.FitterBase`
1495 The fitter to use for minimization.
1497 Maximum number of steps to run outlier rejection before declaring
1498 convergence failure.
1499 name : {'photometry' or 'astrometry'}
1500 What type of data are we fitting (for logs and debugging files).
1502 Passed to ``fitter.minimize()`` to define the parameters to fit.
1503 dataName : `str`, optional
1504 Descriptive name for this dataset (e.g. tract and filter),
1506 doRankUpdate : `bool`, optional
1507 Do an Eigen rank update during minimization, or recompute the full
1508 matrix and gradient?
1509 doLineSearch : `bool`, optional
1510 Do a line search for the optimum step during minimization?
1514 chi2: `lsst.jointcal.Chi2Statistic`
1515 The final chi2 after the fit converges, or is forced to end.
1520 Raised if the fitter fails with a non-finite value.
1522 Raised if the fitter fails for some other reason;
1523 log messages will provide further details.
1525 dumpMatrixFile = self.
_getDebugPath_getDebugPath(f
"{name}_postinit")
if self.config.writeInitMatrix
else ""
1527 oldChi2.chi2 = float(
"inf")
1528 for i
in range(max_steps):
1529 if self.config.writeChi2FilesOuterLoop:
1530 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}"
1532 writeChi2Name =
None
1533 result = fitter.minimize(whatToFit,
1534 self.config.outlierRejectSigma,
1535 doRankUpdate=doRankUpdate,
1536 doLineSearch=doLineSearch,
1537 dumpMatrixFile=dumpMatrixFile)
1539 chi2 = self.
_logChi2AndValidate_logChi2AndValidate(associations, fitter, fitter.getModel(),
1540 f
"Fit iteration {i}", writeChi2Name=writeChi2Name)
1542 if result == MinimizeResult.Converged:
1544 self.log.
debug(
"fit has converged - no more outliers - redo minimization "
1545 "one more time in case we have lost accuracy in rank update.")
1547 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma)
1548 chi2 = self.
_logChi2AndValidate_logChi2AndValidate(associations, fitter, fitter.getModel(),
"Fit completed")
1551 if chi2.chi2/chi2.ndof >= 4.0:
1552 self.log.
error(
"Potentially bad fit: High chi-squared/ndof.")
1555 elif result == MinimizeResult.Chi2Increased:
1556 self.log.
warn(
"Still some outliers remaining but chi2 increased - retry")
1558 chi2Ratio = chi2.chi2 / oldChi2.chi2
1560 self.log.
warn(
'Significant chi2 increase by a factor of %.4g / %.4g = %.4g',
1561 chi2.chi2, oldChi2.chi2, chi2Ratio)
1568 msg = (
"Large chi2 increase between steps: fit likely cannot converge."
1569 " Try setting one or more of the `writeChi2*` config fields and looking"
1570 " at how individual star chi2-values evolve during the fit.")
1571 raise RuntimeError(msg)
1573 elif result == MinimizeResult.NonFinite:
1574 filename = self.
_getDebugPath_getDebugPath(
"{}_failure-nonfinite_chi2-{}.csv".
format(name, dataName))
1576 fitter.saveChi2Contributions(filename+
"{type}")
1577 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}"
1578 raise FloatingPointError(msg.format(filename))
1579 elif result == MinimizeResult.Failed:
1580 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1582 raise RuntimeError(
"Unxepected return code from minimize().")
1584 self.log.
error(
"%s failed to converge after %d steps"%(name, max_steps))
1588 def _make_output(self, ccdImageList, model, func):
1589 """Return the internal jointcal models converted to the afw
1590 structures that will be saved to disk.
1594 ccdImageList : `lsst.jointcal.CcdImageList`
1595 The list of CcdImages to get the output for.
1596 model : `lsst.jointcal.AstrometryModel` or `lsst.jointcal.PhotometryModel`
1597 The internal jointcal model to convert for each `lsst.jointcal.CcdImage`.
1599 The name of the function to call on ``model`` to get the converted
1600 structure. Must accept an `lsst.jointcal.CcdImage`.
1604 output : `dict` [`tuple`, `lsst.jointcal.AstrometryModel`] or
1605 `dict` [`tuple`, `lsst.jointcal.PhotometryModel`]
1606 The data to be saved, keyed on (visit, detector).
1609 for ccdImage
in ccdImageList:
1610 ccd = ccdImage.ccdId
1611 visit = ccdImage.visit
1612 self.log.
debug(
"%s for visit: %d, ccd: %d", func, visit, ccd)
1613 output[(visit, ccd)] = getattr(model, func)(ccdImage)
1616 def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef):
1618 Write the fitted astrometric results to a new 'jointcal_wcs' dataRef.
1622 associations : `lsst.jointcal.Associations`
1623 The star/reference star associations to fit.
1624 model : `lsst.jointcal.AstrometryModel`
1625 The astrometric model that was fit.
1626 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef`
1627 Dict of ccdImage identifiers to dataRefs that were fit.
1629 ccdImageList = associations.getCcdImageList()
1630 output = self.
_make_output_make_output(ccdImageList, model,
"makeSkyWcs")
1631 for key, skyWcs
in output.items():
1632 dataRef = visit_ccd_to_dataRef[key]
1634 dataRef.put(skyWcs,
'jointcal_wcs')
1636 self.log.
fatal(
'Failed to write updated Wcs: %s', str(e))
1639 def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef):
1641 Write the fitted photometric results to a new 'jointcal_photoCalib' dataRef.
1645 associations : `lsst.jointcal.Associations`
1646 The star/reference star associations to fit.
1647 model : `lsst.jointcal.PhotometryModel`
1648 The photoometric model that was fit.
1649 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef`
1650 Dict of ccdImage identifiers to dataRefs that were fit.
1653 ccdImageList = associations.getCcdImageList()
1654 output = self.
_make_output_make_output(ccdImageList, model,
"toPhotoCalib")
1655 for key, photoCalib
in output.items():
1656 dataRef = visit_ccd_to_dataRef[key]
1658 dataRef.put(photoCalib,
'jointcal_photoCalib')
1660 self.log.
fatal(
'Failed to write updated PhotoCalib: %s', str(e))
A representation of a detector in a mosaic camera.
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.
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.
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 getTargetList(parsedCmd, **kwargs)
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 _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef)
def _check_stars(self, associations)
def _prep_sky(self, associations, filters)
def _readDataId(self, butler, dataId)
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 _build_ccdImage(self, data, associations, jointcalControl)
def runDataRef(self, dataRefs)
def __init__(self, butler=None, initInputs=None, **kwargs)
def _extract_detector_catalog_from_visit_catalog(self, table, visitCatalog, detectorId)
def _make_schema_table(self)
def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit, dataName="", doRankUpdate=True, doLineSearch=False)
def run(self, inputSourceTableVisit, inputVisitSummary, inputCamera, tract=None)
def _fit_photometry(self, associations, dataName=None)
def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef)
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 loadData(self, dataRefs, associations, jointcalControl)
Provides consistent interface for LSST exceptions.
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
double fluxErrFromABMagErr(double magErr, double mag) noexcept
Compute flux error in Janskys from AB magnitude error and AB magnitude.
def add_measurement(job, name, value)
def writeModel(model, filename, log)
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations.
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)