24 import astropy.units
as u
28 import lsst.pex.config
as pexConfig
36 from lsst.verify
import Job, Measurement
41 from .dataIds
import PerTractCcdDataIdContainer
46 __all__ = [
"JointcalConfig",
"JointcalRunner",
"JointcalTask"]
48 Photometry = collections.namedtuple(
'Photometry', (
'fit',
'model'))
49 Astrometry = collections.namedtuple(
'Astrometry', (
'fit',
'model',
'sky_to_tan_projection'))
54 meas = Measurement(job.metrics[name], value)
55 job.measurements.insert(meas)
59 """Subclass of TaskRunner for jointcalTask 61 jointcalTask.runDataRef() takes a number of arguments, one of which is a list of dataRefs 62 extracted from the command line (whereas most CmdLineTasks' runDataRef methods take 63 single dataRef, are are called repeatedly). This class transforms the processed 64 arguments generated by the ArgumentParser into the arguments expected by 65 Jointcal.runDataRef(). 67 See pipeBase.TaskRunner for more information. 73 Return a list of tuples per tract, each containing (dataRefs, kwargs). 75 Jointcal operates on lists of dataRefs simultaneously. 77 kwargs[
'profile_jointcal'] = parsedCmd.profile_jointcal
78 kwargs[
'butler'] = parsedCmd.butler
82 for ref
in parsedCmd.id.refList:
83 refListDict.setdefault(ref.dataId[
"tract"], []).
append(ref)
85 result = [(refListDict[tract], kwargs)
for tract
in sorted(refListDict.keys())]
93 Arguments for Task.runDataRef() 98 if self.doReturnResults is False: 100 - ``exitStatus``: 0 if the task completed successfully, 1 otherwise. 102 if self.doReturnResults is True: 104 - ``result``: the result of calling jointcal.runDataRef() 105 - ``exitStatus``: 0 if the task completed successfully, 1 otherwise. 110 dataRefList, kwargs = args
111 butler = kwargs.pop(
'butler')
112 task = self.TaskClass(config=self.config, log=self.log, butler=butler)
115 result = task.runDataRef(dataRefList, **kwargs)
116 exitStatus = result.exitStatus
117 job_path = butler.get(
'verify_job_filename')
118 result.job.write(job_path[0])
119 except Exception
as e:
124 eName =
type(e).__name__
125 tract = dataRefList[0].dataId[
'tract']
126 task.log.fatal(
"Failed processing tract %s, %s: %s", tract, eName, e)
129 kwargs[
'butler'] = butler
130 if self.doReturnResults:
131 return pipeBase.Struct(result=result, exitStatus=exitStatus)
133 return pipeBase.Struct(exitStatus=exitStatus)
137 """Configuration for JointcalTask""" 139 doAstrometry = pexConfig.Field(
140 doc=
"Fit astrometry and write the fitted result.",
144 doPhotometry = pexConfig.Field(
145 doc=
"Fit photometry and write the fitted result.",
149 coaddName = pexConfig.Field(
150 doc=
"Type of coadd, typically deep or goodSeeing",
154 positionErrorPedestal = pexConfig.Field(
155 doc=
"Systematic term to apply to the measured position error (pixels)",
159 photometryErrorPedestal = pexConfig.Field(
160 doc=
"Systematic term to apply to the measured error on flux or magnitude as a " 161 "fraction of source flux or magnitude delta (e.g. 0.05 is 5% of flux or +50 millimag).",
166 matchCut = pexConfig.Field(
167 doc=
"Matching radius between fitted and reference stars (arcseconds)",
171 minMeasurements = pexConfig.Field(
172 doc=
"Minimum number of associated measured stars for a fitted star to be included in the fit",
176 minMeasuredStarsPerCcd = pexConfig.Field(
177 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
181 minRefStarsPerCcd = pexConfig.Field(
182 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
186 allowLineSearch = pexConfig.Field(
187 doc=
"Allow a line search during minimization, if it is reasonable for the model" 188 " (models with a significant non-linear component, e.g. constrainedPhotometry).",
192 astrometrySimpleOrder = pexConfig.Field(
193 doc=
"Polynomial order for fitting the simple astrometry model.",
197 astrometryChipOrder = pexConfig.Field(
198 doc=
"Order of the per-chip transform for the constrained astrometry model.",
202 astrometryVisitOrder = pexConfig.Field(
203 doc=
"Order of the per-visit transform for the constrained astrometry model.",
207 useInputWcs = pexConfig.Field(
208 doc=
"Use the input calexp WCSs to initialize a SimpleAstrometryModel.",
212 astrometryModel = pexConfig.ChoiceField(
213 doc=
"Type of model to fit to astrometry",
215 default=
"constrained",
216 allowed={
"simple":
"One polynomial per ccd",
217 "constrained":
"One polynomial per ccd, and one polynomial per visit"}
219 photometryModel = pexConfig.ChoiceField(
220 doc=
"Type of model to fit to photometry",
222 default=
"constrainedMagnitude",
223 allowed={
"simpleFlux":
"One constant zeropoint per ccd and visit, fitting in flux space.",
224 "constrainedFlux":
"Constrained zeropoint per ccd, and one polynomial per visit," 225 " fitting in flux space.",
226 "simpleMagnitude":
"One constant zeropoint per ccd and visit," 227 " fitting in magnitude space.",
228 "constrainedMagnitude":
"Constrained zeropoint per ccd, and one polynomial per visit," 229 " fitting in magnitude space.",
232 applyColorTerms = pexConfig.Field(
233 doc=
"Apply photometric color terms to reference stars?" 234 "Requires that colorterms be set to a ColortermLibrary",
238 colorterms = pexConfig.ConfigField(
239 doc=
"Library of photometric reference catalog name to color term dict.",
240 dtype=ColortermLibrary,
242 photometryVisitOrder = pexConfig.Field(
243 doc=
"Order of the per-visit polynomial transform for the constrained photometry model.",
247 photometryDoRankUpdate = pexConfig.Field(
248 doc=
"Do the rank update step during minimization. " 249 "Skipping this can help deal with models that are too non-linear.",
253 astrometryDoRankUpdate = pexConfig.Field(
254 doc=
"Do the rank update step during minimization (should not change the astrometry fit). " 255 "Skipping this can help deal with models that are too non-linear.",
259 outlierRejectSigma = pexConfig.Field(
260 doc=
"How many sigma to reject outliers at during minimization.",
264 maxPhotometrySteps = pexConfig.Field(
265 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
269 maxAstrometrySteps = pexConfig.Field(
270 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
274 astrometryRefObjLoader = pexConfig.ConfigurableField(
275 target=LoadIndexedReferenceObjectsTask,
276 doc=
"Reference object loader for astrometric fit",
278 photometryRefObjLoader = pexConfig.ConfigurableField(
279 target=LoadIndexedReferenceObjectsTask,
280 doc=
"Reference object loader for photometric fit",
282 sourceSelector = sourceSelectorRegistry.makeField(
283 doc=
"How to select sources for cross-matching",
286 astrometryReferenceSelector = pexConfig.ConfigurableField(
287 target=ReferenceSourceSelectorTask,
288 doc=
"How to down-select the loaded astrometry reference catalog.",
290 photometryReferenceSelector = pexConfig.ConfigurableField(
291 target=ReferenceSourceSelectorTask,
292 doc=
"How to down-select the loaded photometry reference catalog.",
294 astrometryReferenceErr = pexConfig.Field(
295 doc=
"Uncertainty on reference catalog coordinates [mas] to use in place of the `coord_*Err` fields." 296 " If None, then raise an exception if the reference catalog is missing coordinate errors." 297 " If specified, overrides any existing `coord_*Err` values.",
302 writeInitMatrix = pexConfig.Field(
304 doc=
"Write the pre/post-initialization Hessian and gradient to text files, for debugging." 305 "The output files will be of the form 'astrometry_preinit-mat.txt', in the current directory." 306 "Note that these files are the dense versions of the matrix, and so may be very large.",
309 writeChi2FilesInitialFinal = pexConfig.Field(
311 doc=
"Write .csv files containing the contributions to chi2 for the initialization and final fit.",
314 writeChi2FilesOuterLoop = pexConfig.Field(
316 doc=
"Write .csv files containing the contributions to chi2 for the outer fit loop.",
319 sourceFluxType = pexConfig.Field(
321 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
328 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary." 329 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
331 msg = (
"Only doing astrometry, but Colorterms are not applied for astrometry;" 332 "applyColorTerms=True will be ignored.")
346 fluxField = f
"slot_{self.sourceFluxType}Flux_instFlux" 347 self.
sourceSelector[
'science'].signalToNoise.fluxField = fluxField
348 self.
sourceSelector[
'science'].signalToNoise.errField = fluxField +
"Err" 354 badFlags = [
'base_PixelFlags_flag_edge',
'base_PixelFlags_flag_saturated',
355 'base_PixelFlags_flag_interpolatedCenter',
'base_SdssCentroid_flag',
356 'base_PsfFlux_flag',
'base_PixelFlags_flag_suspectCenter']
361 """Jointly astrometrically and photometrically calibrate a group of images.""" 363 ConfigClass = JointcalConfig
364 RunnerClass = JointcalRunner
365 _DefaultName =
"jointcal" 367 def __init__(self, butler=None, profile_jointcal=False, **kwargs):
369 Instantiate a JointcalTask. 373 butler : `lsst.daf.persistence.Butler` 374 The butler is passed to the refObjLoader constructor in case it is 375 needed. Ignored if the refObjLoader argument provides a loader directly. 376 Used to initialize the astrometry and photometry refObjLoaders. 377 profile_jointcal : `bool` 378 Set to True to profile different stages of this jointcal run. 380 pipeBase.CmdLineTask.__init__(self, **kwargs)
382 self.makeSubtask(
"sourceSelector")
383 if self.config.doAstrometry:
384 self.makeSubtask(
'astrometryRefObjLoader', butler=butler)
385 self.makeSubtask(
"astrometryReferenceSelector")
388 if self.config.doPhotometry:
389 self.makeSubtask(
'photometryRefObjLoader', butler=butler)
390 self.makeSubtask(
"photometryReferenceSelector")
395 self.
job = Job.load_metrics_package(subset=
'jointcal')
400 def _getMetadataName(self):
404 def _makeArgumentParser(cls):
405 """Create an argument parser""" 407 parser.add_argument(
"--profile_jointcal", default=
False, action=
"store_true",
408 help=
"Profile steps of jointcal separately.")
409 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=6789 ccd=0..9",
410 ContainerClass=PerTractCcdDataIdContainer)
413 def _build_ccdImage(self, dataRef, associations, jointcalControl):
415 Extract the necessary things from this dataRef to add a new ccdImage. 419 dataRef : `lsst.daf.persistence.ButlerDataRef` 420 DataRef to extract info from. 421 associations : `lsst.jointcal.Associations` 422 Object to add the info to, to construct a new CcdImage 423 jointcalControl : `jointcal.JointcalControl` 424 Control object for associations management 430 The TAN WCS of this image, read from the calexp 431 (`lsst.afw.geom.SkyWcs`). 433 A key to identify this dataRef by its visit and ccd ids 436 This calexp's filter (`str`). 438 if "visit" in dataRef.dataId.keys():
439 visit = dataRef.dataId[
"visit"]
441 visit = dataRef.getButler().queryMetadata(
"calexp", (
"visit"), dataRef.dataId)[0]
443 src = dataRef.get(
"src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS, immediate=
True)
445 visitInfo = dataRef.get(
'calexp_visitInfo')
446 detector = dataRef.get(
'calexp_detector')
447 ccdId = detector.getId()
448 photoCalib = dataRef.get(
'calexp_photoCalib')
449 tanWcs = dataRef.get(
'calexp_wcs')
450 bbox = dataRef.get(
'calexp_bbox')
451 filt = dataRef.get(
'calexp_filter')
452 filterName = filt.getName()
454 goodSrc = self.sourceSelector.
run(src)
456 if len(goodSrc.sourceCat) == 0:
457 self.log.
warn(
"No sources selected in visit %s ccd %s", visit, ccdId)
459 self.log.
info(
"%d sources selected in visit %d ccd %d", len(goodSrc.sourceCat), visit, ccdId)
460 associations.createCcdImage(goodSrc.sourceCat,
471 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key',
'filter'))
472 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
473 return Result(tanWcs, Key(visit, ccdId), filterName)
478 Jointly calibrate the astrometry and photometry across a set of images. 482 dataRefs : `list` of `lsst.daf.persistence.ButlerDataRef` 483 List of data references to the exposures to be fit. 484 profile_jointcal : `bool` 485 Profile the individual steps of jointcal. 489 result : `lsst.pipe.base.Struct` 490 Struct of metadata from the fit, containing: 493 The provided data references that were fit (with updated WCSs) 495 The original WCS from each dataRef 497 Dictionary of internally-computed metrics for testing/validation. 499 if len(dataRefs) == 0:
500 raise ValueError(
'Need a non-empty list of data references!')
504 sourceFluxField =
"slot_%sFlux" % (self.config.sourceFluxType,)
508 visit_ccd_to_dataRef = {}
511 load_cat_prof_file =
'jointcal_build_ccdImage.prof' if profile_jointcal
else '' 512 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
515 camera = dataRefs[0].get(
'camera', immediate=
True)
519 oldWcsList.append(result.wcs)
520 visit_ccd_to_dataRef[result.key] = ref
521 filters.append(result.filter)
522 filters = collections.Counter(filters)
524 associations.computeCommonTangentPoint()
526 boundingCircle = associations.computeBoundingCircle()
528 radius =
lsst.geom.Angle(boundingCircle.getOpeningAngle().asRadians(), lsst.geom.radians)
531 defaultFilter = filters.most_common(1)[0][0]
532 self.log.
debug(
"Using %s band for reference flux", defaultFilter)
535 tract = dataRefs[0].dataId[
'tract']
537 if self.config.doAstrometry:
541 referenceSelector=self.astrometryReferenceSelector,
543 profile_jointcal=profile_jointcal,
549 if self.config.doPhotometry:
553 referenceSelector=self.photometryReferenceSelector,
555 profile_jointcal=profile_jointcal,
558 reject_bad_fluxes=
True)
563 return pipeBase.Struct(dataRefs=dataRefs,
564 oldWcsList=oldWcsList,
568 defaultFilter=defaultFilter,
569 exitStatus=exitStatus)
571 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
573 tract="", profile_jointcal=False, match_cut=3.0,
574 reject_bad_fluxes=False, *,
575 name="", refObjLoader=None, referenceSelector=None,
577 """Load reference catalog, perform the fit, and return the result. 581 associations : `lsst.jointcal.Associations` 582 The star/reference star associations to fit. 583 defaultFilter : `str` 584 filter to load from reference catalog. 585 center : `lsst.geom.SpherePoint` 586 ICRS center of field to load from reference catalog. 587 radius : `lsst.geom.Angle` 588 On-sky radius to load from reference catalog. 590 Name of thing being fit: "astrometry" or "photometry". 591 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 592 Reference object loader to use to load a reference catalog. 593 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask` 594 Selector to use to pick objects from the loaded reference catalog. 595 fit_function : callable 596 Function to call to perform fit (takes Associations object). 597 filters : `list` [`str`], optional 598 List of filters to load from the reference catalog. 599 tract : `str`, optional 600 Name of tract currently being fit. 601 profile_jointcal : `bool`, optional 602 Separately profile the fitting step. 603 match_cut : `float`, optional 604 Radius in arcseconds to find cross-catalog matches to during 605 associations.associateCatalogs. 606 reject_bad_fluxes : `bool`, optional 607 Reject refCat sources with NaN/inf flux or NaN/0 fluxErr. 611 result : `Photometry` or `Astrometry` 612 Result of `fit_function()` 614 self.log.
info(
"====== Now processing %s...", name)
617 associations.associateCatalogs(match_cut)
619 associations.fittedStarListSize())
621 applyColorterms =
False if name.lower() ==
"astrometry" else self.config.applyColorTerms
623 center, radius, defaultFilter,
624 applyColorterms=applyColorterms)
626 if self.config.astrometryReferenceErr
is None:
627 refCoordErr = float(
'nan')
629 refCoordErr = self.config.astrometryReferenceErr
631 associations.collectRefStars(refCat,
632 self.config.matchCut*lsst.geom.arcseconds,
634 refCoordinateErr=refCoordErr,
635 rejectBadFluxes=reject_bad_fluxes)
637 associations.refStarListSize())
639 associations.prepareFittedStars(self.config.minMeasurements)
643 associations.nFittedStarsWithAssociatedRefStar())
645 associations.fittedStarListSize())
647 associations.nCcdImagesValidForFit())
649 load_cat_prof_file =
'jointcal_fit_%s.prof'%name
if profile_jointcal
else '' 650 dataName =
"{}_{}".
format(tract, defaultFilter)
651 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
652 result = fit_function(associations, dataName)
655 if self.config.writeChi2FilesInitialFinal:
656 baseName = f
"{name}_final_chi2-{dataName}" 657 result.fit.saveChi2Contributions(baseName+
"{type}")
661 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterName,
662 applyColorterms=False):
663 """Load the necessary reference catalog sources, convert fluxes to 664 correct units, and apply color term corrections if requested. 668 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 669 The reference catalog loader to use to get the data. 670 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask` 671 Source selector to apply to loaded reference catalog. 672 center : `lsst.geom.SpherePoint` 673 The center around which to load sources. 674 radius : `lsst.geom.Angle` 675 The radius around ``center`` to load sources in. 677 The name of the camera filter to load fluxes for. 678 applyColorterms : `bool` 679 Apply colorterm corrections to the refcat for ``filterName``? 683 refCat : `lsst.afw.table.SimpleCatalog` 684 The loaded reference catalog. 686 The name of the reference catalog flux field appropriate for ``filterName``. 688 skyCircle = refObjLoader.loadSkyCircle(center,
692 selected = referenceSelector.run(skyCircle.refCat)
694 if not selected.sourceCat.isContiguous():
695 refCat = selected.sourceCat.copy(deep=
True)
697 refCat = selected.sourceCat
699 if self.config.astrometryReferenceErr
is None and 'coord_raErr' not in refCat.schema:
700 msg = (
"Reference catalog does not contain coordinate errors, " 701 "and config.astrometryReferenceErr not supplied.")
702 raise pexConfig.FieldValidationError(JointcalConfig.astrometryReferenceErr,
706 if self.config.astrometryReferenceErr
is not None and 'coord_raErr' in refCat.schema:
707 self.log.
warn(
"Overriding reference catalog coordinate errors with %f/coordinate [mas]",
708 self.config.astrometryReferenceErr)
712 refCatName = refObjLoader.ref_dataset_name
713 except AttributeError:
715 raise RuntimeError(
"Cannot perform colorterm corrections with a.net refcats.")
716 self.log.
info(
"Applying color terms for filterName=%r reference catalog=%s",
717 filterName, refCatName)
718 colorterm = self.config.colorterms.getColorterm(
719 filterName=filterName, photoCatName=refCatName, doRaise=
True)
721 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat, filterName)
722 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
726 return refCat, skyCircle.fluxField
728 def _check_star_lists(self, associations, name):
730 if associations.nCcdImagesValidForFit() == 0:
731 raise RuntimeError(
'No images in the ccdImageList!')
732 if associations.fittedStarListSize() == 0:
733 raise RuntimeError(
'No stars in the {} fittedStarList!'.
format(name))
734 if associations.refStarListSize() == 0:
735 raise RuntimeError(
'No stars in the {} reference star list!'.
format(name))
737 def _logChi2AndValidate(self, associations, fit, model, chi2Label="Model",
739 """Compute chi2, log it, validate the model, and return chi2. 743 associations : `lsst.jointcal.Associations` 744 The star/reference star associations to fit. 745 fit : `lsst.jointcal.FitterBase` 746 The fitter to use for minimization. 747 model : `lsst.jointcal.Model` 749 chi2Label : str, optional 750 Label to describe the chi2 (e.g. "Initialized", "Final"). 751 writeChi2Name : `str`, optional 752 Filename prefix to write the chi2 contributions to. 753 Do not supply an extension: an appropriate one will be added. 757 chi2: `lsst.jointcal.Chi2Accumulator` 758 The chi2 object for the current fitter and model. 763 Raised if chi2 is infinite or NaN. 765 Raised if the model is not valid. 767 if writeChi2Name
is not None:
768 fit.saveChi2Contributions(writeChi2Name+
"{type}")
769 self.log.
info(
"Wrote chi2 contributions files: %s", writeChi2Name)
771 chi2 = fit.computeChi2()
772 self.log.
info(
"%s %s", chi2Label, chi2)
774 if not np.isfinite(chi2.chi2):
775 raise FloatingPointError(f
'{chi2Label} chi2 is invalid: {chi2}')
776 if not model.validate(associations.getCcdImageList(), chi2.ndof):
777 raise ValueError(
"Model is not valid: check log messages for warnings.")
780 def _fit_photometry(self, associations, dataName=None):
782 Fit the photometric data. 786 associations : `lsst.jointcal.Associations` 787 The star/reference star associations to fit. 789 Name of the data being processed (e.g. "1234_HSC-Y"), for 790 identifying debugging files. 794 fit_result : `namedtuple` 795 fit : `lsst.jointcal.PhotometryFit` 796 The photometric fitter used to perform the fit. 797 model : `lsst.jointcal.PhotometryModel` 798 The photometric model that was fit. 800 self.log.
info(
"=== Starting photometric fitting...")
803 if self.config.photometryModel ==
"constrainedFlux":
806 visitOrder=self.config.photometryVisitOrder,
807 errorPedestal=self.config.photometryErrorPedestal)
809 doLineSearch = self.config.allowLineSearch
810 elif self.config.photometryModel ==
"constrainedMagnitude":
813 visitOrder=self.config.photometryVisitOrder,
814 errorPedestal=self.config.photometryErrorPedestal)
816 doLineSearch = self.config.allowLineSearch
817 elif self.config.photometryModel ==
"simpleFlux":
819 errorPedestal=self.config.photometryErrorPedestal)
821 elif self.config.photometryModel ==
"simpleMagnitude":
823 errorPedestal=self.config.photometryErrorPedestal)
829 if self.config.writeChi2FilesInitialFinal:
830 baseName = f
"photometry_initial_chi2-{dataName}" 835 def getChi2Name(whatToFit):
836 if self.config.writeChi2FilesOuterLoop:
837 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
843 dumpMatrixFile =
"photometry_preinit" if self.config.writeInitMatrix
else "" 844 if self.config.photometryModel.startswith(
"constrained"):
847 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
848 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"ModelVisit"))
851 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
854 fit.minimize(
"Fluxes")
857 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
859 writeChi2Name=getChi2Name(
"ModelFluxes"))
861 model.freezeErrorTransform()
862 self.log.
debug(
"Photometry error scales are frozen.")
866 self.config.maxPhotometrySteps,
869 doRankUpdate=self.config.photometryDoRankUpdate,
870 doLineSearch=doLineSearch,
877 def _fit_astrometry(self, associations, dataName=None):
879 Fit the astrometric data. 883 associations : `lsst.jointcal.Associations` 884 The star/reference star associations to fit. 886 Name of the data being processed (e.g. "1234_HSC-Y"), for 887 identifying debugging files. 891 fit_result : `namedtuple` 892 fit : `lsst.jointcal.AstrometryFit` 893 The astrometric fitter used to perform the fit. 894 model : `lsst.jointcal.AstrometryModel` 895 The astrometric model that was fit. 896 sky_to_tan_projection : `lsst.jointcal.ProjectionHandler` 897 The model for the sky to tangent plane projection that was used in the fit. 900 self.log.
info(
"=== Starting astrometric fitting...")
902 associations.deprojectFittedStars()
909 if self.config.astrometryModel ==
"constrained":
911 sky_to_tan_projection,
912 chipOrder=self.config.astrometryChipOrder,
913 visitOrder=self.config.astrometryVisitOrder)
914 elif self.config.astrometryModel ==
"simple":
916 sky_to_tan_projection,
917 self.config.useInputWcs,
919 order=self.config.astrometrySimpleOrder)
924 if self.config.writeChi2FilesInitialFinal:
925 baseName = f
"astrometry_initial_chi2-{dataName}" 930 def getChi2Name(whatToFit):
931 if self.config.writeChi2FilesOuterLoop:
932 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
936 dumpMatrixFile =
"astrometry_preinit" if self.config.writeInitMatrix
else "" 939 if self.config.astrometryModel ==
"constrained":
940 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
941 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"DistortionsVisit"))
944 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
945 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"Distortions"))
947 fit.minimize(
"Positions")
950 fit.minimize(
"Distortions Positions")
952 writeChi2Name=getChi2Name(
"DistortionsPositions"))
956 self.config.maxAstrometrySteps,
958 "Distortions Positions",
959 doRankUpdate=self.config.astrometryDoRankUpdate,
965 return Astrometry(fit, model, sky_to_tan_projection)
967 def _check_stars(self, associations):
968 """Count measured and reference stars per ccd and warn/log them.""" 969 for ccdImage
in associations.getCcdImageList():
970 nMeasuredStars, nRefStars = ccdImage.countStars()
971 self.log.
debug(
"ccdImage %s has %s measured and %s reference stars",
972 ccdImage.getName(), nMeasuredStars, nRefStars)
973 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
974 self.log.
warn(
"ccdImage %s has only %s measuredStars (desired %s)",
975 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
976 if nRefStars < self.config.minRefStarsPerCcd:
977 self.log.
warn(
"ccdImage %s has only %s RefStars (desired %s)",
978 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
980 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
984 """Run fitter.minimize up to max_steps times, returning the final chi2. 988 associations : `lsst.jointcal.Associations` 989 The star/reference star associations to fit. 990 fitter : `lsst.jointcal.FitterBase` 991 The fitter to use for minimization. 993 Maximum number of steps to run outlier rejection before declaring 995 name : {'photometry' or 'astrometry'} 996 What type of data are we fitting (for logs and debugging files). 998 Passed to ``fitter.minimize()`` to define the parameters to fit. 999 dataName : `str`, optional 1000 Descriptive name for this dataset (e.g. tract and filter), 1002 doRankUpdate : `bool`, optional 1003 Do an Eigen rank update during minimization, or recompute the full 1004 matrix and gradient? 1005 doLineSearch : `bool`, optional 1006 Do a line search for the optimum step during minimization? 1010 chi2: `lsst.jointcal.Chi2Statistic` 1011 The final chi2 after the fit converges, or is forced to end. 1016 Raised if the fitter fails with a non-finite value. 1018 Raised if the fitter fails for some other reason; 1019 log messages will provide further details. 1021 dumpMatrixFile =
"%s_postinit" % name
if self.config.writeInitMatrix
else "" 1022 for i
in range(max_steps):
1023 if self.config.writeChi2FilesOuterLoop:
1024 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}" 1026 writeChi2Name =
None 1027 result = fitter.minimize(whatToFit,
1028 self.config.outlierRejectSigma,
1029 doRankUpdate=doRankUpdate,
1030 doLineSearch=doLineSearch,
1031 dumpMatrixFile=dumpMatrixFile)
1034 writeChi2Name=writeChi2Name)
1036 if result == MinimizeResult.Converged:
1038 self.log.
debug(
"fit has converged - no more outliers - redo minimization " 1039 "one more time in case we have lost accuracy in rank update.")
1041 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma)
1045 if chi2.chi2/chi2.ndof >= 4.0:
1046 self.log.
error(
"Potentially bad fit: High chi-squared/ndof.")
1049 elif result == MinimizeResult.Chi2Increased:
1050 self.log.
warn(
"still some outliers but chi2 increases - retry")
1051 elif result == MinimizeResult.NonFinite:
1052 filename =
"{}_failure-nonfinite_chi2-{}.csv".
format(name, dataName)
1054 fitter.saveChi2Contributions(filename)
1055 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}" 1056 raise FloatingPointError(msg.format(filename))
1057 elif result == MinimizeResult.Failed:
1058 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1060 raise RuntimeError(
"Unxepected return code from minimize().")
1062 self.log.
error(
"%s failed to converge after %d steps"%(name, max_steps))
1066 def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef):
1068 Write the fitted astrometric results to a new 'jointcal_wcs' dataRef. 1072 associations : `lsst.jointcal.Associations` 1073 The star/reference star associations to fit. 1074 model : `lsst.jointcal.AstrometryModel` 1075 The astrometric model that was fit. 1076 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1077 Dict of ccdImage identifiers to dataRefs that were fit. 1080 ccdImageList = associations.getCcdImageList()
1081 for ccdImage
in ccdImageList:
1083 ccd = ccdImage.ccdId
1084 visit = ccdImage.visit
1085 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1086 self.log.
info(
"Updating WCS for visit: %d, ccd: %d", visit, ccd)
1087 skyWcs = model.makeSkyWcs(ccdImage)
1089 dataRef.put(skyWcs,
'jointcal_wcs')
1091 self.log.
fatal(
'Failed to write updated Wcs: %s', str(e))
1094 def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef):
1096 Write the fitted photometric results to a new 'jointcal_photoCalib' dataRef. 1100 associations : `lsst.jointcal.Associations` 1101 The star/reference star associations to fit. 1102 model : `lsst.jointcal.PhotometryModel` 1103 The photoometric model that was fit. 1104 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1105 Dict of ccdImage identifiers to dataRefs that were fit. 1108 ccdImageList = associations.getCcdImageList()
1109 for ccdImage
in ccdImageList:
1111 ccd = ccdImage.ccdId
1112 visit = ccdImage.visit
1113 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1114 self.log.
info(
"Updating PhotoCalib for visit: %d, ccd: %d", visit, ccd)
1115 photoCalib = model.toPhotoCalib(ccdImage)
1117 dataRef.put(photoCalib,
'jointcal_photoCalib')
1119 self.log.
fatal(
'Failed to write updated PhotoCalib: %s', str(e))
def runDataRef(self, dataRefs, profile_jointcal=False)
def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterName, applyColorterms=False)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
double fluxErrFromABMagErr(double magErr, double mag) noexcept
Compute flux error in Janskys from AB magnitude error and AB magnitude.
def _build_ccdImage(self, dataRef, associations, jointcalControl)
def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius, filters=[], tract="", profile_jointcal=False, match_cut=3.0, reject_bad_fluxes=False, name="", refObjLoader=None, referenceSelector=None, fit_function=None)
def _fit_photometry(self, associations, dataName=None)
def getTargetList(parsedCmd, kwargs)
def _fit_astrometry(self, associations, dataName=None)
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.
Fit spatial kernel using approximate fluxes for candidates, and solving a linear system of equations...
def _check_star_lists(self, associations, name)
The class that implements the relations between MeasuredStar and FittedStar.
A class representing an angle.
A projection handler in which all CCDs from the same visit have the same tangent point.
def _logChi2AndValidate(self, associations, fit, model, chi2Label="Model", writeChi2Name=None)
A model where there is one independent transform per CcdImage.
def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit, dataName="", doRankUpdate=True, doLineSearch=False)
def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef)
def _check_stars(self, associations)
Class that handles the photometric least squares problem.
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)
Point in an unspecified spherical coordinate system.
Class that handles the astrometric least squares problem.
def add_measurement(job, name, value)
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects...
A multi-component model, fitting mappings for sensors and visits simultaneously.
def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef)
def __init__(self, butler=None, profile_jointcal=False, kwargs)