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)
129 kwargs[
'butler'] = butler
130 if self.doReturnResults:
131 return pipeBase.Struct(result=result, exitStatus=exitStatus)
133 return pipeBase.Struct(exitStatus=exitStatus)
137 """Config 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 writeInitMatrix = pexConfig.Field(
296 doc=
"Write the pre/post-initialization Hessian and gradient to text files, for debugging." 297 "The output files will be of the form 'astrometry_preinit-mat.txt', in the current directory." 298 "Note that these files are the dense versions of the matrix, and so may be very large.",
301 writeChi2FilesInitialFinal = pexConfig.Field(
303 doc=
"Write .csv files containing the contributions to chi2 for the initialization and final fit.",
306 writeChi2FilesOuterLoop = pexConfig.Field(
308 doc=
"Write .csv files containing the contributions to chi2 for the outer fit loop.",
311 sourceFluxType = pexConfig.Field(
313 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
320 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary." 321 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
325 sourceSelector.setDefaults()
327 sourceSelector.badFlags.extend([
"slot_Shape_flag"])
333 """Jointly astrometrically and photometrically calibrate a group of images.""" 335 ConfigClass = JointcalConfig
336 RunnerClass = JointcalRunner
337 _DefaultName =
"jointcal" 339 def __init__(self, butler=None, profile_jointcal=False, **kwargs):
341 Instantiate a JointcalTask. 345 butler : `lsst.daf.persistence.Butler` 346 The butler is passed to the refObjLoader constructor in case it is 347 needed. Ignored if the refObjLoader argument provides a loader directly. 348 Used to initialize the astrometry and photometry refObjLoaders. 349 profile_jointcal : `bool` 350 Set to True to profile different stages of this jointcal run. 352 pipeBase.CmdLineTask.__init__(self, **kwargs)
354 self.makeSubtask(
"sourceSelector")
355 if self.config.doAstrometry:
356 self.makeSubtask(
'astrometryRefObjLoader', butler=butler)
357 self.makeSubtask(
"astrometryReferenceSelector")
360 if self.config.doPhotometry:
361 self.makeSubtask(
'photometryRefObjLoader', butler=butler)
362 self.makeSubtask(
"photometryReferenceSelector")
367 self.
job = Job.load_metrics_package(subset=
'jointcal')
372 def _getMetadataName(self):
376 def _makeArgumentParser(cls):
377 """Create an argument parser""" 379 parser.add_argument(
"--profile_jointcal", default=
False, action=
"store_true",
380 help=
"Profile steps of jointcal separately.")
381 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=6789 ccd=0..9",
382 ContainerClass=PerTractCcdDataIdContainer)
385 def _build_ccdImage(self, dataRef, associations, jointcalControl):
387 Extract the necessary things from this dataRef to add a new ccdImage. 391 dataRef : `lsst.daf.persistence.ButlerDataRef` 392 DataRef to extract info from. 393 associations : `lsst.jointcal.Associations` 394 Object to add the info to, to construct a new CcdImage 395 jointcalControl : `jointcal.JointcalControl` 396 Control object for associations management 402 The TAN WCS of this image, read from the calexp 403 (`lsst.afw.geom.SkyWcs`). 405 A key to identify this dataRef by its visit and ccd ids 408 This calexp's filter (`str`). 410 if "visit" in dataRef.dataId.keys():
411 visit = dataRef.dataId[
"visit"]
413 visit = dataRef.getButler().queryMetadata(
"calexp", (
"visit"), dataRef.dataId)[0]
415 src = dataRef.get(
"src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS, immediate=
True)
417 visitInfo = dataRef.get(
'calexp_visitInfo')
418 detector = dataRef.get(
'calexp_detector')
419 ccdId = detector.getId()
420 calib = dataRef.get(
'calexp_calib')
421 tanWcs = dataRef.get(
'calexp_wcs')
422 bbox = dataRef.get(
'calexp_bbox')
423 filt = dataRef.get(
'calexp_filter')
424 filterName = filt.getName()
425 fluxMag0 = calib.getFluxMag0()
427 referenceFlux = 1e23 * 10**(48.6 / -2.5) * 1e9
428 photoCalib = afwImage.PhotoCalib(referenceFlux/fluxMag0[0],
429 referenceFlux*fluxMag0[1]/fluxMag0[0]**2, bbox)
431 goodSrc = self.sourceSelector.run(src)
433 if len(goodSrc.sourceCat) == 0:
434 self.log.warn(
"No sources selected in visit %s ccd %s", visit, ccdId)
436 self.log.info(
"%d sources selected in visit %d ccd %d", len(goodSrc.sourceCat), visit, ccdId)
437 associations.createCcdImage(goodSrc.sourceCat,
448 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key',
'filter'))
449 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
450 return Result(tanWcs, Key(visit, ccdId), filterName)
455 Jointly calibrate the astrometry and photometry across a set of images. 459 dataRefs : `list` of `lsst.daf.persistence.ButlerDataRef` 460 List of data references to the exposures to be fit. 461 profile_jointcal : `bool` 462 Profile the individual steps of jointcal. 466 result : `lsst.pipe.base.Struct` 467 Struct of metadata from the fit, containing: 470 The provided data references that were fit (with updated WCSs) 472 The original WCS from each dataRef 474 Dictionary of internally-computed metrics for testing/validation. 476 if len(dataRefs) == 0:
477 raise ValueError(
'Need a non-empty list of data references!')
481 sourceFluxField =
"slot_%sFlux" % (self.config.sourceFluxType,)
485 visit_ccd_to_dataRef = {}
488 load_cat_prof_file =
'jointcal_build_ccdImage.prof' if profile_jointcal
else '' 489 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
492 camera = dataRefs[0].get(
'camera', immediate=
True)
496 oldWcsList.append(result.wcs)
497 visit_ccd_to_dataRef[result.key] = ref
498 filters.append(result.filter)
499 filters = collections.Counter(filters)
501 associations.computeCommonTangentPoint()
506 bbox = associations.getRaDecBBox()
507 bboxCenter = bbox.getCenter()
508 center = afwGeom.SpherePoint(bboxCenter[0], bboxCenter[1], afwGeom.degrees)
509 bboxMax = bbox.getMax()
510 corner = afwGeom.SpherePoint(bboxMax[0], bboxMax[1], afwGeom.degrees)
511 radius = center.separation(corner).asRadians()
516 raise RuntimeError(
"astrometry_net_data is not setup")
519 defaultFilter = filters.most_common(1)[0][0]
520 self.log.debug(
"Using %s band for reference flux", defaultFilter)
523 tract = dataRefs[0].dataId[
'tract']
525 if self.config.doAstrometry:
529 referenceSelector=self.astrometryReferenceSelector,
531 profile_jointcal=profile_jointcal,
537 if self.config.doPhotometry:
541 referenceSelector=self.photometryReferenceSelector,
543 profile_jointcal=profile_jointcal,
546 reject_bad_fluxes=
True)
551 return pipeBase.Struct(dataRefs=dataRefs,
552 oldWcsList=oldWcsList,
556 defaultFilter=defaultFilter,
557 exitStatus=exitStatus)
559 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
560 name="", refObjLoader=None, referenceSelector=None,
561 filters=[], fit_function=None,
562 tract=None, profile_jointcal=False, match_cut=3.0,
563 reject_bad_fluxes=False):
564 """Load reference catalog, perform the fit, and return the result. 568 associations : `lsst.jointcal.Associations` 569 The star/reference star associations to fit. 570 defaultFilter : `str` 571 filter to load from reference catalog. 572 center : `lsst.afw.geom.SpherePoint` 573 ICRS center of field to load from reference catalog. 574 radius : `lsst.afw.geom.Angle` 575 On-sky radius to load from reference catalog. 577 Name of thing being fit: "Astrometry" or "Photometry". 578 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 579 Reference object loader to load from for fit. 580 filters : `list` of `str`, optional 581 List of filters to load from the reference catalog. 582 fit_function : callable 583 Function to call to perform fit (takes associations object). 585 Name of tract currently being fit. 586 profile_jointcal : `bool`, optional 587 Separately profile the fitting step. 588 match_cut : `float`, optional 589 Radius in arcseconds to find cross-catalog matches to during 590 associations.associateCatalogs. 591 reject_bad_fluxes : `bool`, optional 592 Reject refCat sources with NaN/inf flux or NaN/0 fluxErr. 596 result : `Photometry` or `Astrometry` 597 Result of `fit_function()` 599 self.log.info(
"====== Now processing %s...", name)
602 associations.associateCatalogs(match_cut)
604 associations.fittedStarListSize())
606 applyColorterms =
False if name ==
"Astrometry" else self.config.applyColorTerms
607 if name ==
"Astrometry":
608 referenceSelector = self.config.astrometryReferenceSelector
609 elif name ==
"Photometry":
610 referenceSelector = self.config.photometryReferenceSelector
612 center, radius, defaultFilter,
613 applyColorterms=applyColorterms)
615 associations.collectRefStars(refCat, self.config.matchCut*afwGeom.arcseconds,
616 fluxField, reject_bad_fluxes)
618 associations.refStarListSize())
620 associations.prepareFittedStars(self.config.minMeasurements)
624 associations.nFittedStarsWithAssociatedRefStar())
626 associations.fittedStarListSize())
628 associations.nCcdImagesValidForFit())
630 load_cat_prof_file =
'jointcal_fit_%s.prof'%name
if profile_jointcal
else '' 631 dataName =
"{}_{}".format(tract, defaultFilter)
632 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
633 result = fit_function(associations, dataName)
636 if self.config.writeChi2FilesInitialFinal:
637 baseName = f
"{name}_final_chi2-{dataName}" 638 result.fit.saveChi2Contributions(baseName+
"{type}")
642 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterName,
643 applyColorterms=False):
644 """Load the necessary reference catalog sources, convert fluxes to 645 correct units, and apply color term corrections if requested. 649 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 650 The reference catalog loader to use to get the data. 651 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask` 652 Source selector to apply to loaded reference catalog. 653 center : `lsst.geom.SpherePoint` 654 The center around which to load sources. 655 radius : `lsst.geom.Angle` 656 The radius around ``center`` to load sources in. 658 The name of the camera filter to load fluxes for. 659 applyColorterms : `bool` 660 Apply colorterm corrections to the refcat for ``filterName``? 664 refCat : `lsst.afw.table.SimpleCatalog` 665 The loaded reference catalog. 667 The name of the reference catalog flux field appropriate for ``filterName``. 669 skyCircle = refObjLoader.loadSkyCircle(center,
670 afwGeom.Angle(radius, afwGeom.radians),
673 selected = referenceSelector.run(skyCircle.refCat)
675 if not selected.sourceCat.isContiguous():
676 refCat = selected.sourceCat.copy(deep=
True)
678 refCat = selected.sourceCat
682 refCatName = refObjLoader.ref_dataset_name
683 except AttributeError:
685 raise RuntimeError(
"Cannot perform colorterm corrections with a.net refcats.")
686 self.log.info(
"Applying color terms for filterName=%r reference catalog=%s",
687 filterName, refCatName)
688 colorterm = self.config.colorterms.getColorterm(
689 filterName=filterName, photoCatName=refCatName, doRaise=
True)
691 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat, filterName)
692 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
694 refCat[skyCircle.fluxField+
'Err'] = fluxErrFromABMagErr(refMagErr, refMag) * 1e9
697 refCat[skyCircle.fluxField] *= 1e9
699 refCat[skyCircle.fluxField+
'Err'] *= 1e9
704 return refCat, skyCircle.fluxField
706 def _check_star_lists(self, associations, name):
708 if associations.nCcdImagesValidForFit() == 0:
709 raise RuntimeError(
'No images in the ccdImageList!')
710 if associations.fittedStarListSize() == 0:
711 raise RuntimeError(
'No stars in the {} fittedStarList!'.format(name))
712 if associations.refStarListSize() == 0:
713 raise RuntimeError(
'No stars in the {} reference star list!'.format(name))
715 def _logChi2AndValidate(self, associations, fit, model, chi2Label="Model",
717 """Compute chi2, log it, validate the model, and return chi2. 721 associations : `lsst.jointcal.Associations` 722 The star/reference star associations to fit. 723 fit : `lsst.jointcal.FitterBase` 724 The fitter to use for minimization. 725 model : `lsst.jointcal.Model` 727 chi2Label : str, optional 728 Label to describe the chi2 (e.g. "Initialized", "Final"). 729 writeChi2Name : `str`, optional 730 Filename prefix to write the chi2 contributions to. 731 Do not supply an extension: an appropriate one will be added. 735 chi2: `lsst.jointcal.Chi2Accumulator` 736 The chi2 object for the current fitter and model. 741 Raised if chi2 is infinite or NaN. 743 Raised if the model is not valid. 745 if writeChi2Name
is not None:
746 fit.saveChi2Contributions(writeChi2Name+
"{type}")
747 self.log.info(
"Wrote chi2 contributions files: %s", writeChi2Name)
749 chi2 = fit.computeChi2()
750 self.log.info(
"%s %s", chi2Label, chi2)
752 if not np.isfinite(chi2.chi2):
753 raise FloatingPointError(
'%s chi2 is invalid: %s', chi2Label, chi2)
754 if not model.validate(associations.getCcdImageList()):
755 raise ValueError(
"Model is not valid: check log messages for warnings.")
758 def _fit_photometry(self, associations, dataName=None):
760 Fit the photometric data. 764 associations : `lsst.jointcal.Associations` 765 The star/reference star associations to fit. 767 Name of the data being processed (e.g. "1234_HSC-Y"), for 768 identifying debugging files. 772 fit_result : `namedtuple` 773 fit : `lsst.jointcal.PhotometryFit` 774 The photometric fitter used to perform the fit. 775 model : `lsst.jointcal.PhotometryModel` 776 The photometric model that was fit. 778 self.log.info(
"=== Starting photometric fitting...")
781 if self.config.photometryModel ==
"constrainedFlux":
784 visitOrder=self.config.photometryVisitOrder,
785 errorPedestal=self.config.photometryErrorPedestal)
787 doLineSearch = self.config.allowLineSearch
788 elif self.config.photometryModel ==
"constrainedMagnitude":
791 visitOrder=self.config.photometryVisitOrder,
792 errorPedestal=self.config.photometryErrorPedestal)
794 doLineSearch = self.config.allowLineSearch
795 elif self.config.photometryModel ==
"simpleFlux":
797 errorPedestal=self.config.photometryErrorPedestal)
799 elif self.config.photometryModel ==
"simpleMagnitude":
801 errorPedestal=self.config.photometryErrorPedestal)
807 if self.config.writeChi2FilesInitialFinal:
808 baseName = f
"photometry_initial_chi2-{dataName}" 813 def getChi2Name(whatToFit):
814 if self.config.writeChi2FilesOuterLoop:
815 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
821 dumpMatrixFile =
"photometry_preinit" if self.config.writeInitMatrix
else "" 822 if self.config.photometryModel.startswith(
"constrained"):
825 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
826 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"ModelVisit"))
829 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
832 fit.minimize(
"Fluxes")
835 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
837 writeChi2Name=getChi2Name(
"ModelFluxes"))
839 model.freezeErrorTransform()
840 self.log.debug(
"Photometry error scales are frozen.")
844 self.config.maxPhotometrySteps,
847 doRankUpdate=self.config.photometryDoRankUpdate,
848 doLineSearch=doLineSearch,
855 def _fit_astrometry(self, associations, dataName=None):
857 Fit the astrometric data. 861 associations : `lsst.jointcal.Associations` 862 The star/reference star associations to fit. 864 Name of the data being processed (e.g. "1234_HSC-Y"), for 865 identifying debugging files. 869 fit_result : `namedtuple` 870 fit : `lsst.jointcal.AstrometryFit` 871 The astrometric fitter used to perform the fit. 872 model : `lsst.jointcal.AstrometryModel` 873 The astrometric model that was fit. 874 sky_to_tan_projection : `lsst.jointcal.ProjectionHandler` 875 The model for the sky to tangent plane projection that was used in the fit. 878 self.log.info(
"=== Starting astrometric fitting...")
880 associations.deprojectFittedStars()
887 if self.config.astrometryModel ==
"constrained":
889 sky_to_tan_projection,
890 chipOrder=self.config.astrometryChipOrder,
891 visitOrder=self.config.astrometryVisitOrder)
892 elif self.config.astrometryModel ==
"simple":
894 sky_to_tan_projection,
895 self.config.useInputWcs,
897 order=self.config.astrometrySimpleOrder)
902 if self.config.writeChi2FilesInitialFinal:
903 baseName = f
"astrometry_initial_chi2-{dataName}" 908 def getChi2Name(whatToFit):
909 if self.config.writeChi2FilesOuterLoop:
910 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
914 dumpMatrixFile =
"astrometry_preinit" if self.config.writeInitMatrix
else "" 917 if self.config.astrometryModel ==
"constrained":
918 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
919 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"DistortionsVisit"))
922 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
923 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"Distortions"))
925 fit.minimize(
"Positions")
928 fit.minimize(
"Distortions Positions")
930 writeChi2Name=getChi2Name(
"DistortionsPositions"))
934 self.config.maxAstrometrySteps,
936 "Distortions Positions",
937 doRankUpdate=self.config.astrometryDoRankUpdate,
943 return Astrometry(fit, model, sky_to_tan_projection)
945 def _check_stars(self, associations):
946 """Count measured and reference stars per ccd and warn/log them.""" 947 for ccdImage
in associations.getCcdImageList():
948 nMeasuredStars, nRefStars = ccdImage.countStars()
949 self.log.debug(
"ccdImage %s has %s measured and %s reference stars",
950 ccdImage.getName(), nMeasuredStars, nRefStars)
951 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
952 self.log.warn(
"ccdImage %s has only %s measuredStars (desired %s)",
953 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
954 if nRefStars < self.config.minRefStarsPerCcd:
955 self.log.warn(
"ccdImage %s has only %s RefStars (desired %s)",
956 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
958 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
962 """Run fitter.minimize up to max_steps times, returning the final chi2. 966 associations : `lsst.jointcal.Associations` 967 The star/reference star associations to fit. 968 fitter : `lsst.jointcal.FitterBase` 969 The fitter to use for minimization. 971 Maximum number of steps to run outlier rejection before declaring 973 name : {'photometry' or 'astrometry'} 974 What type of data are we fitting (for logs and debugging files). 976 Passed to ``fitter.minimize()`` to define the parameters to fit. 977 dataName : `str`, optional 978 Descriptive name for this dataset (e.g. tract and filter), 980 doRankUpdate : `bool`, optional 981 Do an Eigen rank update during minimization, or recompute the full 983 doLineSearch : `bool`, optional 984 Do a line search for the optimum step during minimization? 988 chi2: `lsst.jointcal.Chi2Statistic` 989 The final chi2 after the fit converges, or is forced to end. 994 Raised if the fitter fails with a non-finite value. 996 Raised if the fitter fails for some other reason; 997 log messages will provide further details. 999 dumpMatrixFile =
"%s_postinit" % name
if self.config.writeInitMatrix
else "" 1000 for i
in range(max_steps):
1001 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}" 1002 result = fitter.minimize(whatToFit,
1003 self.config.outlierRejectSigma,
1004 doRankUpdate=doRankUpdate,
1005 doLineSearch=doLineSearch,
1006 dumpMatrixFile=dumpMatrixFile)
1009 writeChi2Name=writeChi2Name)
1011 if result == MinimizeResult.Converged:
1013 self.log.debug(
"fit has converged - no more outliers - redo minimization " 1014 "one more time in case we have lost accuracy in rank update.")
1016 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma)
1020 if chi2.chi2/chi2.ndof >= 4.0:
1021 self.log.error(
"Potentially bad fit: High chi-squared/ndof.")
1024 elif result == MinimizeResult.Chi2Increased:
1025 self.log.warn(
"still some outliers but chi2 increases - retry")
1026 elif result == MinimizeResult.NonFinite:
1027 filename =
"{}_failure-nonfinite_chi2-{}.csv".format(name, dataName)
1029 fitter.saveChi2Contributions(filename)
1030 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}" 1031 raise FloatingPointError(msg.format(filename))
1032 elif result == MinimizeResult.Failed:
1033 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1035 raise RuntimeError(
"Unxepected return code from minimize().")
1037 self.log.error(
"%s failed to converge after %d steps"%(name, max_steps))
1041 def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef):
1043 Write the fitted astrometric results to a new 'jointcal_wcs' dataRef. 1047 associations : `lsst.jointcal.Associations` 1048 The star/reference star associations to fit. 1049 model : `lsst.jointcal.AstrometryModel` 1050 The astrometric model that was fit. 1051 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1052 Dict of ccdImage identifiers to dataRefs that were fit. 1055 ccdImageList = associations.getCcdImageList()
1056 for ccdImage
in ccdImageList:
1058 ccd = ccdImage.ccdId
1059 visit = ccdImage.visit
1060 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1061 self.log.info(
"Updating WCS for visit: %d, ccd: %d", visit, ccd)
1062 skyWcs = model.makeSkyWcs(ccdImage)
1064 dataRef.put(skyWcs,
'jointcal_wcs')
1065 except pexExceptions.Exception
as e:
1066 self.log.fatal(
'Failed to write updated Wcs: %s', str(e))
1069 def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef):
1071 Write the fitted photometric results to a new 'jointcal_photoCalib' dataRef. 1075 associations : `lsst.jointcal.Associations` 1076 The star/reference star associations to fit. 1077 model : `lsst.jointcal.PhotometryModel` 1078 The photoometric model that was fit. 1079 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1080 Dict of ccdImage identifiers to dataRefs that were fit. 1083 ccdImageList = associations.getCcdImageList()
1084 for ccdImage
in ccdImageList:
1086 ccd = ccdImage.ccdId
1087 visit = ccdImage.visit
1088 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1089 self.log.info(
"Updating PhotoCalib for visit: %d, ccd: %d", visit, ccd)
1090 photoCalib = model.toPhotoCalib(ccdImage)
1092 dataRef.put(photoCalib,
'jointcal_photoCalib')
1093 except pexExceptions.Exception
as e:
1094 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", writeChi2Name=None)
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)