24 import astropy.units
as u
35 from lsst.pipe.tasks.colorterms
import ColortermLibrary
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)
128 if self.doReturnResults:
129 return pipeBase.Struct(result=result, exitStatus=exitStatus)
131 return pipeBase.Struct(exitStatus=exitStatus)
135 """Config for JointcalTask""" 137 doAstrometry = pexConfig.Field(
138 doc=
"Fit astrometry and write the fitted result.",
142 doPhotometry = pexConfig.Field(
143 doc=
"Fit photometry and write the fitted result.",
147 coaddName = pexConfig.Field(
148 doc=
"Type of coadd, typically deep or goodSeeing",
152 positionErrorPedestal = pexConfig.Field(
153 doc=
"Systematic term to apply to the measured position error (pixels)",
157 photometryErrorPedestal = pexConfig.Field(
158 doc=
"Systematic term to apply to the measured error on flux or magnitude as a " 159 "fraction of source flux or magnitude delta (e.g. 0.05 is 5% of flux or +50 millimag).",
164 matchCut = pexConfig.Field(
165 doc=
"Matching radius between fitted and reference stars (arcseconds)",
169 minMeasurements = pexConfig.Field(
170 doc=
"Minimum number of associated measured stars for a fitted star to be included in the fit",
174 minMeasuredStarsPerCcd = pexConfig.Field(
175 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
179 minRefStarsPerCcd = pexConfig.Field(
180 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
184 allowLineSearch = pexConfig.Field(
185 doc=
"Allow a line search during minimization, if it is reasonable for the model" 186 " (models with a significant non-linear component, e.g. constrainedPhotometry).",
190 astrometrySimpleOrder = pexConfig.Field(
191 doc=
"Polynomial order for fitting the simple astrometry model.",
195 astrometryChipOrder = pexConfig.Field(
196 doc=
"Order of the per-chip transform for the constrained astrometry model.",
200 astrometryVisitOrder = pexConfig.Field(
201 doc=
"Order of the per-visit transform for the constrained astrometry model.",
205 useInputWcs = pexConfig.Field(
206 doc=
"Use the input calexp WCSs to initialize a SimpleAstrometryModel.",
210 astrometryModel = pexConfig.ChoiceField(
211 doc=
"Type of model to fit to astrometry",
213 default=
"constrained",
214 allowed={
"simple":
"One polynomial per ccd",
215 "constrained":
"One polynomial per ccd, and one polynomial per visit"}
217 photometryModel = pexConfig.ChoiceField(
218 doc=
"Type of model to fit to photometry",
220 default=
"constrainedMagnitude",
221 allowed={
"simpleFlux":
"One constant zeropoint per ccd and visit, fitting in flux space.",
222 "constrainedFlux":
"Constrained zeropoint per ccd, and one polynomial per visit," 223 " fitting in flux space.",
224 "simpleMagnitude":
"One constant zeropoint per ccd and visit," 225 " fitting in magnitude space.",
226 "constrainedMagnitude":
"Constrained zeropoint per ccd, and one polynomial per visit," 227 " fitting in magnitude space.",
230 applyColorTerms = pexConfig.Field(
231 doc=
"Apply photometric color terms to reference stars?" 232 "Requires that colorterms be set to a ColortermLibrary",
236 colorterms = pexConfig.ConfigField(
237 doc=
"Library of photometric reference catalog name to color term dict.",
238 dtype=ColortermLibrary,
240 photometryVisitOrder = pexConfig.Field(
241 doc=
"Order of the per-visit polynomial transform for the constrained photometry model.",
245 photometryDoRankUpdate = pexConfig.Field(
246 doc=
"Do the rank update step during minimization. " 247 "Skipping this can help deal with models that are too non-linear.",
251 astrometryDoRankUpdate = pexConfig.Field(
252 doc=
"Do the rank update step during minimization (should not change the astrometry fit). " 253 "Skipping this can help deal with models that are too non-linear.",
257 outlierRejectSigma = pexConfig.Field(
258 doc=
"How many sigma to reject outliers at during minimization.",
262 maxPhotometrySteps = pexConfig.Field(
263 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
267 maxAstrometrySteps = pexConfig.Field(
268 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
272 astrometryRefObjLoader = pexConfig.ConfigurableField(
273 target=LoadIndexedReferenceObjectsTask,
274 doc=
"Reference object loader for astrometric fit",
276 photometryRefObjLoader = pexConfig.ConfigurableField(
277 target=LoadIndexedReferenceObjectsTask,
278 doc=
"Reference object loader for photometric fit",
280 sourceSelector = sourceSelectorRegistry.makeField(
281 doc=
"How to select sources for cross-matching",
284 astrometryReferenceSelector = pexConfig.ConfigurableField(
285 target=ReferenceSourceSelectorTask,
286 doc=
"How to down-select the loaded astrometry reference catalog.",
288 photometryReferenceSelector = pexConfig.ConfigurableField(
289 target=ReferenceSourceSelectorTask,
290 doc=
"How to down-select the loaded photometry reference catalog.",
292 writeInitMatrix = pexConfig.Field(
294 doc=
"Write the pre/post-initialization Hessian and gradient to text files, for debugging." 295 "The output files will be of the form 'astrometry_preinit-mat.txt', in the current directory." 296 "Note that these files are the dense versions of the matrix, and so may be very large.",
299 writeChi2ContributionFiles = pexConfig.Field(
301 doc=
"Write initial/final fit files containing the contributions to chi2.",
304 sourceFluxType = pexConfig.Field(
306 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
313 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary." 314 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
318 sourceSelector.setDefaults()
320 sourceSelector.badFlags.extend([
"slot_Shape_flag"])
326 """Jointly astrometrically and photometrically calibrate a group of images.""" 328 ConfigClass = JointcalConfig
329 RunnerClass = JointcalRunner
330 _DefaultName =
"jointcal" 332 def __init__(self, butler=None, profile_jointcal=False, **kwargs):
334 Instantiate a JointcalTask. 338 butler : `lsst.daf.persistence.Butler` 339 The butler is passed to the refObjLoader constructor in case it is 340 needed. Ignored if the refObjLoader argument provides a loader directly. 341 Used to initialize the astrometry and photometry refObjLoaders. 342 profile_jointcal : `bool` 343 Set to True to profile different stages of this jointcal run. 345 pipeBase.CmdLineTask.__init__(self, **kwargs)
347 self.makeSubtask(
"sourceSelector")
348 if self.config.doAstrometry:
349 self.makeSubtask(
'astrometryRefObjLoader', butler=butler)
350 self.makeSubtask(
"astrometryReferenceSelector")
353 if self.config.doPhotometry:
354 self.makeSubtask(
'photometryRefObjLoader', butler=butler)
355 self.makeSubtask(
"photometryReferenceSelector")
360 self.
job = Job.load_metrics_package(subset=
'jointcal')
365 def _getMetadataName(self):
369 def _makeArgumentParser(cls):
370 """Create an argument parser""" 372 parser.add_argument(
"--profile_jointcal", default=
False, action=
"store_true",
373 help=
"Profile steps of jointcal separately.")
374 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=6789 ccd=0..9",
375 ContainerClass=PerTractCcdDataIdContainer)
378 def _build_ccdImage(self, dataRef, associations, jointcalControl):
380 Extract the necessary things from this dataRef to add a new ccdImage. 384 dataRef : `lsst.daf.persistence.ButlerDataRef` 385 DataRef to extract info from. 386 associations : `lsst.jointcal.Associations` 387 Object to add the info to, to construct a new CcdImage 388 jointcalControl : `jointcal.JointcalControl` 389 Control object for associations management 395 The TAN WCS of this image, read from the calexp 396 (`lsst.afw.geom.SkyWcs`). 398 A key to identify this dataRef by its visit and ccd ids 401 This calexp's filter (`str`). 403 if "visit" in dataRef.dataId.keys():
404 visit = dataRef.dataId[
"visit"]
406 visit = dataRef.getButler().queryMetadata(
"calexp", (
"visit"), dataRef.dataId)[0]
408 src = dataRef.get(
"src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS, immediate=
True)
410 visitInfo = dataRef.get(
'calexp_visitInfo')
411 detector = dataRef.get(
'calexp_detector')
412 ccdId = detector.getId()
413 calib = dataRef.get(
'calexp_calib')
414 tanWcs = dataRef.get(
'calexp_wcs')
415 bbox = dataRef.get(
'calexp_bbox')
416 filt = dataRef.get(
'calexp_filter')
417 filterName = filt.getName()
418 fluxMag0 = calib.getFluxMag0()
420 referenceFlux = 1e23 * 10**(48.6 / -2.5) * 1e9
421 photoCalib = afwImage.PhotoCalib(referenceFlux/fluxMag0[0],
422 referenceFlux*fluxMag0[1]/fluxMag0[0]**2, bbox)
424 goodSrc = self.sourceSelector.run(src)
426 if len(goodSrc.sourceCat) == 0:
427 self.log.warn(
"No sources selected in visit %s ccd %s", visit, ccdId)
429 self.log.info(
"%d sources selected in visit %d ccd %d", len(goodSrc.sourceCat), visit, ccdId)
430 associations.createCcdImage(goodSrc.sourceCat,
441 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key',
'filter'))
442 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
443 return Result(tanWcs, Key(visit, ccdId), filterName)
448 Jointly calibrate the astrometry and photometry across a set of images. 452 dataRefs : `list` of `lsst.daf.persistence.ButlerDataRef` 453 List of data references to the exposures to be fit. 454 profile_jointcal : `bool` 455 Profile the individual steps of jointcal. 459 result : `lsst.pipe.base.Struct` 460 Struct of metadata from the fit, containing: 463 The provided data references that were fit (with updated WCSs) 465 The original WCS from each dataRef 467 Dictionary of internally-computed metrics for testing/validation. 469 if len(dataRefs) == 0:
470 raise ValueError(
'Need a non-empty list of data references!')
474 sourceFluxField =
"slot_%sFlux" % (self.config.sourceFluxType,)
478 visit_ccd_to_dataRef = {}
481 load_cat_prof_file =
'jointcal_build_ccdImage.prof' if profile_jointcal
else '' 482 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
485 camera = dataRefs[0].get(
'camera', immediate=
True)
489 oldWcsList.append(result.wcs)
490 visit_ccd_to_dataRef[result.key] = ref
491 filters.append(result.filter)
492 filters = collections.Counter(filters)
494 associations.computeCommonTangentPoint()
499 bbox = associations.getRaDecBBox()
500 bboxCenter = bbox.getCenter()
501 center = afwGeom.SpherePoint(bboxCenter[0], bboxCenter[1], afwGeom.degrees)
502 bboxMax = bbox.getMax()
503 corner = afwGeom.SpherePoint(bboxMax[0], bboxMax[1], afwGeom.degrees)
504 radius = center.separation(corner).asRadians()
509 raise RuntimeError(
"astrometry_net_data is not setup")
512 defaultFilter = filters.most_common(1)[0][0]
513 self.log.debug(
"Using %s band for reference flux", defaultFilter)
516 tract = dataRefs[0].dataId[
'tract']
518 if self.config.doAstrometry:
522 referenceSelector=self.astrometryReferenceSelector,
524 profile_jointcal=profile_jointcal,
530 if self.config.doPhotometry:
534 referenceSelector=self.photometryReferenceSelector,
536 profile_jointcal=profile_jointcal,
539 reject_bad_fluxes=
True)
544 return pipeBase.Struct(dataRefs=dataRefs,
545 oldWcsList=oldWcsList,
549 defaultFilter=defaultFilter,
550 exitStatus=exitStatus)
552 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
553 name="", refObjLoader=None, referenceSelector=None,
554 filters=[], fit_function=None,
555 tract=None, profile_jointcal=False, match_cut=3.0,
556 reject_bad_fluxes=False):
557 """Load reference catalog, perform the fit, and return the result. 561 associations : `lsst.jointcal.Associations` 562 The star/reference star associations to fit. 563 defaultFilter : `str` 564 filter to load from reference catalog. 565 center : `lsst.afw.geom.SpherePoint` 566 ICRS center of field to load from reference catalog. 567 radius : `lsst.afw.geom.Angle` 568 On-sky radius to load from reference catalog. 570 Name of thing being fit: "Astrometry" or "Photometry". 571 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 572 Reference object loader to load from for fit. 573 filters : `list` of `str`, optional 574 List of filters to load from the reference catalog. 575 fit_function : callable 576 Function to call to perform fit (takes associations object). 578 Name of tract currently being fit. 579 profile_jointcal : `bool`, optional 580 Separately profile the fitting step. 581 match_cut : `float`, optional 582 Radius in arcseconds to find cross-catalog matches to during 583 associations.associateCatalogs. 584 reject_bad_fluxes : `bool`, optional 585 Reject refCat sources with NaN/inf flux or NaN/0 fluxErr. 589 result : `Photometry` or `Astrometry` 590 Result of `fit_function()` 592 self.log.info(
"====== Now processing %s...", name)
595 associations.associateCatalogs(match_cut)
597 associations.fittedStarListSize())
599 applyColorterms =
False if name ==
"Astrometry" else self.config.applyColorTerms
600 if name ==
"Astrometry":
601 referenceSelector = self.config.astrometryReferenceSelector
602 elif name ==
"Photometry":
603 referenceSelector = self.config.photometryReferenceSelector
605 center, radius, defaultFilter,
606 applyColorterms=applyColorterms)
608 associations.collectRefStars(refCat, self.config.matchCut*afwGeom.arcseconds,
609 fluxField, reject_bad_fluxes)
611 associations.refStarListSize())
613 associations.prepareFittedStars(self.config.minMeasurements)
617 associations.nFittedStarsWithAssociatedRefStar())
619 associations.fittedStarListSize())
621 associations.nCcdImagesValidForFit())
623 load_cat_prof_file =
'jointcal_fit_%s.prof'%name
if profile_jointcal
else '' 624 dataName =
"{}_{}".format(tract, defaultFilter)
625 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
626 result = fit_function(associations, dataName)
629 if self.config.writeChi2ContributionFiles:
630 baseName =
"{}_final_chi2-{}.csv".format(name, dataName)
631 result.fit.saveChi2Contributions(baseName)
635 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterName,
636 applyColorterms=False):
637 """Load the necessary reference catalog sources, convert fluxes to 638 correct units, and apply color term corrections if requested. 642 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 643 The reference catalog loader to use to get the data. 644 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask` 645 Source selector to apply to loaded reference catalog. 646 center : `lsst.geom.SpherePoint` 647 The center around which to load sources. 648 radius : `lsst.geom.Angle` 649 The radius around ``center`` to load sources in. 651 The name of the camera filter to load fluxes for. 652 applyColorterms : `bool` 653 Apply colorterm corrections to the refcat for ``filterName``? 657 refCat : `lsst.afw.table.SimpleCatalog` 658 The loaded reference catalog. 660 The name of the reference catalog flux field appropriate for ``filterName``. 662 skyCircle = refObjLoader.loadSkyCircle(center,
663 afwGeom.Angle(radius, afwGeom.radians),
666 selected = referenceSelector.run(skyCircle.refCat)
668 if not selected.sourceCat.isContiguous():
669 refCat = selected.sourceCat.copy(deep=
True)
671 refCat = selected.sourceCat
675 refCatName = refObjLoader.ref_dataset_name
676 except AttributeError:
678 raise RuntimeError(
"Cannot perform colorterm corrections with a.net refcats.")
679 self.log.info(
"Applying color terms for filterName=%r reference catalog=%s",
680 filterName, refCatName)
681 colorterm = self.config.colorterms.getColorterm(
682 filterName=filterName, photoCatName=refCatName, doRaise=
True)
684 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat, filterName)
685 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
687 refCat[skyCircle.fluxField+
'Err'] = fluxErrFromABMagErr(refMagErr, refMag) * 1e9
690 refCat[skyCircle.fluxField] *= 1e9
692 refCat[skyCircle.fluxField+
'Err'] *= 1e9
697 return refCat, skyCircle.fluxField
699 def _check_star_lists(self, associations, name):
701 if associations.nCcdImagesValidForFit() == 0:
702 raise RuntimeError(
'No images in the ccdImageList!')
703 if associations.fittedStarListSize() == 0:
704 raise RuntimeError(
'No stars in the {} fittedStarList!'.format(name))
705 if associations.refStarListSize() == 0:
706 raise RuntimeError(
'No stars in the {} reference star list!'.format(name))
708 def _logChi2AndValidate(self, associations, fit, model, chi2Label="Model"):
709 """Compute chi2, log it, validate the model, and return chi2.""" 710 chi2 = fit.computeChi2()
711 self.log.info(
"%s %s", chi2Label, chi2)
713 if not np.isfinite(chi2.chi2):
714 raise FloatingPointError(
'%s chi2 is invalid: %s', chi2Label, chi2)
715 if not model.validate(associations.getCcdImageList()):
716 raise ValueError(
"Model is not valid: check log messages for warnings.")
719 def _fit_photometry(self, associations, dataName=None):
721 Fit the photometric data. 725 associations : `lsst.jointcal.Associations` 726 The star/reference star associations to fit. 728 Name of the data being processed (e.g. "1234_HSC-Y"), for 729 identifying debugging files. 733 fit_result : `namedtuple` 734 fit : `lsst.jointcal.PhotometryFit` 735 The photometric fitter used to perform the fit. 736 model : `lsst.jointcal.PhotometryModel` 737 The photometric model that was fit. 739 self.log.info(
"=== Starting photometric fitting...")
742 if self.config.photometryModel ==
"constrainedFlux":
745 visitOrder=self.config.photometryVisitOrder,
746 errorPedestal=self.config.photometryErrorPedestal)
748 doLineSearch = self.config.allowLineSearch
749 elif self.config.photometryModel ==
"constrainedMagnitude":
752 visitOrder=self.config.photometryVisitOrder,
753 errorPedestal=self.config.photometryErrorPedestal)
755 doLineSearch = self.config.allowLineSearch
756 elif self.config.photometryModel ==
"simpleFlux":
758 errorPedestal=self.config.photometryErrorPedestal)
760 elif self.config.photometryModel ==
"simpleMagnitude":
762 errorPedestal=self.config.photometryErrorPedestal)
770 if self.config.writeChi2ContributionFiles:
771 baseName =
"photometry_initial_chi2-{}.csv".format(dataName)
772 fit.saveChi2Contributions(baseName)
776 dumpMatrixFile =
"photometry_preinit" if self.config.writeInitMatrix
else "" 777 if self.config.photometryModel.startswith(
"constrained"):
780 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
784 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
787 fit.minimize(
"Fluxes")
790 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
793 model.freezeErrorTransform()
794 self.log.debug(
"Photometry error scales are frozen.")
798 self.config.maxPhotometrySteps,
801 doRankUpdate=self.config.photometryDoRankUpdate,
802 doLineSearch=doLineSearch,
809 def _fit_astrometry(self, associations, dataName=None):
811 Fit the astrometric data. 815 associations : `lsst.jointcal.Associations` 816 The star/reference star associations to fit. 818 Name of the data being processed (e.g. "1234_HSC-Y"), for 819 identifying debugging files. 823 fit_result : `namedtuple` 824 fit : `lsst.jointcal.AstrometryFit` 825 The astrometric fitter used to perform the fit. 826 model : `lsst.jointcal.AstrometryModel` 827 The astrometric model that was fit. 828 sky_to_tan_projection : `lsst.jointcal.ProjectionHandler` 829 The model for the sky to tangent plane projection that was used in the fit. 832 self.log.info(
"=== Starting astrometric fitting...")
834 associations.deprojectFittedStars()
841 if self.config.astrometryModel ==
"constrained":
843 sky_to_tan_projection,
844 chipOrder=self.config.astrometryChipOrder,
845 visitOrder=self.config.astrometryVisitOrder)
846 elif self.config.astrometryModel ==
"simple":
848 sky_to_tan_projection,
849 self.config.useInputWcs,
851 order=self.config.astrometrySimpleOrder)
858 if self.config.writeChi2ContributionFiles:
859 baseName =
"astrometry_initial_chi2-{}.csv".format(dataName)
860 fit.saveChi2Contributions(baseName)
862 dumpMatrixFile =
"astrometry_preinit" if self.config.writeInitMatrix
else "" 865 if self.config.astrometryModel ==
"constrained":
866 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
870 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
873 fit.minimize(
"Positions")
876 fit.minimize(
"Distortions Positions")
881 self.config.maxAstrometrySteps,
883 "Distortions Positions",
884 doRankUpdate=self.config.astrometryDoRankUpdate,
890 return Astrometry(fit, model, sky_to_tan_projection)
892 def _check_stars(self, associations):
893 """Count measured and reference stars per ccd and warn/log them.""" 894 for ccdImage
in associations.getCcdImageList():
895 nMeasuredStars, nRefStars = ccdImage.countStars()
896 self.log.debug(
"ccdImage %s has %s measured and %s reference stars",
897 ccdImage.getName(), nMeasuredStars, nRefStars)
898 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
899 self.log.warn(
"ccdImage %s has only %s measuredStars (desired %s)",
900 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
901 if nRefStars < self.config.minRefStarsPerCcd:
902 self.log.warn(
"ccdImage %s has only %s RefStars (desired %s)",
903 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
905 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
909 """Run fitter.minimize up to max_steps times, returning the final chi2. 913 associations : `lsst.jointcal.Associations` 914 The star/reference star associations to fit. 915 fitter : `lsst.jointcal.FitterBase` 916 The fitter to use for minimization. 918 Maximum number of steps to run outlier rejection before declaring 920 name : {'photometry' or 'astrometry'} 921 What type of data are we fitting (for logs and debugging files). 923 Passed to ``fitter.minimize()`` to define the parameters to fit. 924 dataName : `str`, optional 925 Descriptive name for this dataset (e.g. tract and filter), 927 doRankUpdate : `bool`, optional 928 Do an Eigen rank update during minimization, or recompute the full 930 doLineSearch : `bool`, optional 931 Do a line search for the optimum step during minimization? 935 chi2: `lsst.jointcal.Chi2Statistic` 936 The final chi2 after the fit converges, or is forced to end. 941 Raised if the fitter fails with a non-finite value. 943 Raised if the fitter fails for some other reason; 944 log messages will provide further details. 946 dumpMatrixFile =
"%s_postinit" % name
if self.config.writeInitMatrix
else "" 947 for i
in range(max_steps):
948 result = fitter.minimize(whatToFit,
949 self.config.outlierRejectSigma,
950 doRankUpdate=doRankUpdate,
951 doLineSearch=doLineSearch,
952 dumpMatrixFile=dumpMatrixFile)
956 if result == MinimizeResult.Converged:
958 self.log.debug(
"fit has converged - no more outliers - redo minimization " 959 "one more time in case we have lost accuracy in rank update.")
961 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma)
965 if chi2.chi2/chi2.ndof >= 4.0:
966 self.log.error(
"Potentially bad fit: High chi-squared/ndof.")
969 elif result == MinimizeResult.Chi2Increased:
970 self.log.warn(
"still some outliers but chi2 increases - retry")
971 elif result == MinimizeResult.NonFinite:
972 filename =
"{}_failure-nonfinite_chi2-{}.csv".format(name, dataName)
974 fitter.saveChi2Contributions(filename)
975 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}" 976 raise FloatingPointError(msg.format(filename))
977 elif result == MinimizeResult.Failed:
978 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
980 raise RuntimeError(
"Unxepected return code from minimize().")
982 self.log.error(
"%s failed to converge after %d steps"%(name, max_steps))
986 def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef):
988 Write the fitted astrometric results to a new 'jointcal_wcs' dataRef. 992 associations : `lsst.jointcal.Associations` 993 The star/reference star associations to fit. 994 model : `lsst.jointcal.AstrometryModel` 995 The astrometric model that was fit. 996 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 997 Dict of ccdImage identifiers to dataRefs that were fit. 1000 ccdImageList = associations.getCcdImageList()
1001 for ccdImage
in ccdImageList:
1003 ccd = ccdImage.ccdId
1004 visit = ccdImage.visit
1005 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1006 self.log.info(
"Updating WCS for visit: %d, ccd: %d", visit, ccd)
1007 skyWcs = model.makeSkyWcs(ccdImage)
1009 dataRef.put(skyWcs,
'jointcal_wcs')
1010 except pexExceptions.Exception
as e:
1011 self.log.fatal(
'Failed to write updated Wcs: %s', str(e))
1014 def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef):
1016 Write the fitted photometric results to a new 'jointcal_photoCalib' dataRef. 1020 associations : `lsst.jointcal.Associations` 1021 The star/reference star associations to fit. 1022 model : `lsst.jointcal.PhotometryModel` 1023 The photoometric model that was fit. 1024 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1025 Dict of ccdImage identifiers to dataRefs that were fit. 1028 ccdImageList = associations.getCcdImageList()
1029 for ccdImage
in ccdImageList:
1031 ccd = ccdImage.ccdId
1032 visit = ccdImage.visit
1033 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1034 self.log.info(
"Updating PhotoCalib for visit: %d, ccd: %d", visit, ccd)
1035 photoCalib = model.toPhotoCalib(ccdImage)
1037 dataRef.put(photoCalib,
'jointcal_photoCalib')
1038 except pexExceptions.Exception
as e:
1039 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 _build_ccdImage(self, dataRef, associations, jointcalControl)
def _fit_photometry(self, associations, dataName=None)
def getTargetList(parsedCmd, kwargs)
def _fit_astrometry(self, associations, dataName=None)
def _check_star_lists(self, associations, name)
The class that implements the relations between MeasuredStar and FittedStar.
A projection handler in which all CCDs from the same visit have the same tangent point.
std::string getPackageDir(std::string const &packageName)
def _logChi2AndValidate(self, associations, fit, model, chi2Label="Model")
this is the model used to fit independent CCDs, meaning that there is no instrument model...
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.
Class that handles the astrometric least squares problem.
def add_measurement(job, name, value)
def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius, name="", refObjLoader=None, referenceSelector=None, filters=[], fit_function=None, tract=None, profile_jointcal=False, match_cut=3.0, reject_bad_fluxes=False)
This is the model used to fit mappings as the combination of a transformation depending on the chip n...
def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef)
def __init__(self, butler=None, profile_jointcal=False, kwargs)