24 import astropy.units
as u
34 from lsst.pipe.tasks.colorterms
import ColortermLibrary
35 from lsst.verify
import Job, Measurement
40 from .dataIds
import PerTractCcdDataIdContainer
45 __all__ = [
"JointcalConfig",
"JointcalRunner",
"JointcalTask"]
47 Photometry = collections.namedtuple(
'Photometry', (
'fit',
'model'))
48 Astrometry = collections.namedtuple(
'Astrometry', (
'fit',
'model',
'sky_to_tan_projection'))
53 meas = Measurement(job.metrics[name], value)
54 job.measurements.insert(meas)
58 """Subclass of TaskRunner for jointcalTask 60 jointcalTask.runDataRef() takes a number of arguments, one of which is a list of dataRefs 61 extracted from the command line (whereas most CmdLineTasks' runDataRef methods take 62 single dataRef, are are called repeatedly). This class transforms the processed 63 arguments generated by the ArgumentParser into the arguments expected by 64 Jointcal.runDataRef(). 66 See pipeBase.TaskRunner for more information. 72 Return a list of tuples per tract, each containing (dataRefs, kwargs). 74 Jointcal operates on lists of dataRefs simultaneously. 76 kwargs[
'profile_jointcal'] = parsedCmd.profile_jointcal
77 kwargs[
'butler'] = parsedCmd.butler
81 for ref
in parsedCmd.id.refList:
82 refListDict.setdefault(ref.dataId[
"tract"], []).append(ref)
84 result = [(refListDict[tract], kwargs)
for tract
in sorted(refListDict.keys())]
92 Arguments for Task.runDataRef() 97 if self.doReturnResults is False: 99 - ``exitStatus``: 0 if the task completed successfully, 1 otherwise. 101 if self.doReturnResults is True: 103 - ``result``: the result of calling jointcal.runDataRef() 104 - ``exitStatus``: 0 if the task completed successfully, 1 otherwise. 109 dataRefList, kwargs = args
110 butler = kwargs.pop(
'butler')
111 task = self.TaskClass(config=self.config, log=self.log, butler=butler)
114 result = task.runDataRef(dataRefList, **kwargs)
115 exitStatus = result.exitStatus
116 job_path = butler.get(
'verify_job_filename')
117 result.job.write(job_path[0])
118 except Exception
as e:
123 eName = type(e).__name__
124 tract = dataRefList[0].dataId[
'tract']
125 task.log.fatal(
"Failed processing tract %s, %s: %s", tract, eName, e)
128 kwargs[
'butler'] = butler
129 if self.doReturnResults:
130 return pipeBase.Struct(result=result, exitStatus=exitStatus)
132 return pipeBase.Struct(exitStatus=exitStatus)
136 """Config for JointcalTask""" 138 doAstrometry = pexConfig.Field(
139 doc=
"Fit astrometry and write the fitted result.",
143 doPhotometry = pexConfig.Field(
144 doc=
"Fit photometry and write the fitted result.",
148 coaddName = pexConfig.Field(
149 doc=
"Type of coadd, typically deep or goodSeeing",
153 positionErrorPedestal = pexConfig.Field(
154 doc=
"Systematic term to apply to the measured position error (pixels)",
158 photometryErrorPedestal = pexConfig.Field(
159 doc=
"Systematic term to apply to the measured error on flux or magnitude as a " 160 "fraction of source flux or magnitude delta (e.g. 0.05 is 5% of flux or +50 millimag).",
165 matchCut = pexConfig.Field(
166 doc=
"Matching radius between fitted and reference stars (arcseconds)",
170 minMeasurements = pexConfig.Field(
171 doc=
"Minimum number of associated measured stars for a fitted star to be included in the fit",
175 minMeasuredStarsPerCcd = pexConfig.Field(
176 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
180 minRefStarsPerCcd = pexConfig.Field(
181 doc=
"Minimum number of measuredStars per ccdImage before printing warnings",
185 allowLineSearch = pexConfig.Field(
186 doc=
"Allow a line search during minimization, if it is reasonable for the model" 187 " (models with a significant non-linear component, e.g. constrainedPhotometry).",
191 astrometrySimpleOrder = pexConfig.Field(
192 doc=
"Polynomial order for fitting the simple astrometry model.",
196 astrometryChipOrder = pexConfig.Field(
197 doc=
"Order of the per-chip transform for the constrained astrometry model.",
201 astrometryVisitOrder = pexConfig.Field(
202 doc=
"Order of the per-visit transform for the constrained astrometry model.",
206 useInputWcs = pexConfig.Field(
207 doc=
"Use the input calexp WCSs to initialize a SimpleAstrometryModel.",
211 astrometryModel = pexConfig.ChoiceField(
212 doc=
"Type of model to fit to astrometry",
214 default=
"constrained",
215 allowed={
"simple":
"One polynomial per ccd",
216 "constrained":
"One polynomial per ccd, and one polynomial per visit"}
218 photometryModel = pexConfig.ChoiceField(
219 doc=
"Type of model to fit to photometry",
221 default=
"constrainedMagnitude",
222 allowed={
"simpleFlux":
"One constant zeropoint per ccd and visit, fitting in flux space.",
223 "constrainedFlux":
"Constrained zeropoint per ccd, and one polynomial per visit," 224 " fitting in flux space.",
225 "simpleMagnitude":
"One constant zeropoint per ccd and visit," 226 " fitting in magnitude space.",
227 "constrainedMagnitude":
"Constrained zeropoint per ccd, and one polynomial per visit," 228 " fitting in magnitude space.",
231 applyColorTerms = pexConfig.Field(
232 doc=
"Apply photometric color terms to reference stars?" 233 "Requires that colorterms be set to a ColortermLibrary",
237 colorterms = pexConfig.ConfigField(
238 doc=
"Library of photometric reference catalog name to color term dict.",
239 dtype=ColortermLibrary,
241 photometryVisitOrder = pexConfig.Field(
242 doc=
"Order of the per-visit polynomial transform for the constrained photometry model.",
246 photometryDoRankUpdate = pexConfig.Field(
247 doc=
"Do the rank update step during minimization. " 248 "Skipping this can help deal with models that are too non-linear.",
252 astrometryDoRankUpdate = pexConfig.Field(
253 doc=
"Do the rank update step during minimization (should not change the astrometry fit). " 254 "Skipping this can help deal with models that are too non-linear.",
258 outlierRejectSigma = pexConfig.Field(
259 doc=
"How many sigma to reject outliers at during minimization.",
263 maxPhotometrySteps = pexConfig.Field(
264 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
268 maxAstrometrySteps = pexConfig.Field(
269 doc=
"Maximum number of minimize iterations to take when fitting photometry.",
273 astrometryRefObjLoader = pexConfig.ConfigurableField(
274 target=LoadIndexedReferenceObjectsTask,
275 doc=
"Reference object loader for astrometric fit",
277 photometryRefObjLoader = pexConfig.ConfigurableField(
278 target=LoadIndexedReferenceObjectsTask,
279 doc=
"Reference object loader for photometric fit",
281 sourceSelector = sourceSelectorRegistry.makeField(
282 doc=
"How to select sources for cross-matching",
285 astrometryReferenceSelector = pexConfig.ConfigurableField(
286 target=ReferenceSourceSelectorTask,
287 doc=
"How to down-select the loaded astrometry reference catalog.",
289 photometryReferenceSelector = pexConfig.ConfigurableField(
290 target=ReferenceSourceSelectorTask,
291 doc=
"How to down-select the loaded photometry reference catalog.",
293 writeInitMatrix = pexConfig.Field(
295 doc=
"Write the pre/post-initialization Hessian and gradient to text files, for debugging." 296 "The output files will be of the form 'astrometry_preinit-mat.txt', in the current directory." 297 "Note that these files are the dense versions of the matrix, and so may be very large.",
300 writeChi2FilesInitialFinal = pexConfig.Field(
302 doc=
"Write .csv files containing the contributions to chi2 for the initialization and final fit.",
305 writeChi2FilesOuterLoop = pexConfig.Field(
307 doc=
"Write .csv files containing the contributions to chi2 for the outer fit loop.",
310 sourceFluxType = pexConfig.Field(
312 doc=
"Source flux field to use in source selection and to get fluxes from the catalog.",
319 msg =
"applyColorTerms=True requires the `colorterms` field be set to a ColortermLibrary." 320 raise pexConfig.FieldValidationError(JointcalConfig.colorterms, self, msg)
333 fluxField = f
"slot_{self.sourceFluxType}Flux_instFlux" 334 self.
sourceSelector[
'science'].signalToNoise.fluxField = fluxField
335 self.
sourceSelector[
'science'].signalToNoise.errField = fluxField +
"Err" 341 badFlags = [
'base_PixelFlags_flag_edge',
'base_PixelFlags_flag_saturated',
342 'base_PixelFlags_flag_interpolatedCenter',
'base_SdssCentroid_flag',
343 'base_PsfFlux_flag',
'base_PixelFlags_flag_suspectCenter']
348 """Jointly astrometrically and photometrically calibrate a group of images.""" 350 ConfigClass = JointcalConfig
351 RunnerClass = JointcalRunner
352 _DefaultName =
"jointcal" 354 def __init__(self, butler=None, profile_jointcal=False, **kwargs):
356 Instantiate a JointcalTask. 360 butler : `lsst.daf.persistence.Butler` 361 The butler is passed to the refObjLoader constructor in case it is 362 needed. Ignored if the refObjLoader argument provides a loader directly. 363 Used to initialize the astrometry and photometry refObjLoaders. 364 profile_jointcal : `bool` 365 Set to True to profile different stages of this jointcal run. 367 pipeBase.CmdLineTask.__init__(self, **kwargs)
369 self.makeSubtask(
"sourceSelector")
370 if self.config.doAstrometry:
371 self.makeSubtask(
'astrometryRefObjLoader', butler=butler)
372 self.makeSubtask(
"astrometryReferenceSelector")
375 if self.config.doPhotometry:
376 self.makeSubtask(
'photometryRefObjLoader', butler=butler)
377 self.makeSubtask(
"photometryReferenceSelector")
382 self.
job = Job.load_metrics_package(subset=
'jointcal')
387 def _getMetadataName(self):
391 def _makeArgumentParser(cls):
392 """Create an argument parser""" 394 parser.add_argument(
"--profile_jointcal", default=
False, action=
"store_true",
395 help=
"Profile steps of jointcal separately.")
396 parser.add_id_argument(
"--id",
"calexp", help=
"data ID, e.g. --id visit=6789 ccd=0..9",
397 ContainerClass=PerTractCcdDataIdContainer)
400 def _build_ccdImage(self, dataRef, associations, jointcalControl):
402 Extract the necessary things from this dataRef to add a new ccdImage. 406 dataRef : `lsst.daf.persistence.ButlerDataRef` 407 DataRef to extract info from. 408 associations : `lsst.jointcal.Associations` 409 Object to add the info to, to construct a new CcdImage 410 jointcalControl : `jointcal.JointcalControl` 411 Control object for associations management 417 The TAN WCS of this image, read from the calexp 418 (`lsst.afw.geom.SkyWcs`). 420 A key to identify this dataRef by its visit and ccd ids 423 This calexp's filter (`str`). 425 if "visit" in dataRef.dataId.keys():
426 visit = dataRef.dataId[
"visit"]
428 visit = dataRef.getButler().queryMetadata(
"calexp", (
"visit"), dataRef.dataId)[0]
430 src = dataRef.get(
"src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS, immediate=
True)
432 visitInfo = dataRef.get(
'calexp_visitInfo')
433 detector = dataRef.get(
'calexp_detector')
434 ccdId = detector.getId()
435 photoCalib = dataRef.get(
'calexp_photoCalib')
436 tanWcs = dataRef.get(
'calexp_wcs')
437 bbox = dataRef.get(
'calexp_bbox')
438 filt = dataRef.get(
'calexp_filter')
439 filterName = filt.getName()
441 goodSrc = self.sourceSelector.run(src)
443 if len(goodSrc.sourceCat) == 0:
444 self.log.warn(
"No sources selected in visit %s ccd %s", visit, ccdId)
446 self.log.info(
"%d sources selected in visit %d ccd %d", len(goodSrc.sourceCat), visit, ccdId)
447 associations.createCcdImage(goodSrc.sourceCat,
458 Result = collections.namedtuple(
'Result_from_build_CcdImage', (
'wcs',
'key',
'filter'))
459 Key = collections.namedtuple(
'Key', (
'visit',
'ccd'))
460 return Result(tanWcs, Key(visit, ccdId), filterName)
465 Jointly calibrate the astrometry and photometry across a set of images. 469 dataRefs : `list` of `lsst.daf.persistence.ButlerDataRef` 470 List of data references to the exposures to be fit. 471 profile_jointcal : `bool` 472 Profile the individual steps of jointcal. 476 result : `lsst.pipe.base.Struct` 477 Struct of metadata from the fit, containing: 480 The provided data references that were fit (with updated WCSs) 482 The original WCS from each dataRef 484 Dictionary of internally-computed metrics for testing/validation. 486 if len(dataRefs) == 0:
487 raise ValueError(
'Need a non-empty list of data references!')
491 sourceFluxField =
"slot_%sFlux" % (self.config.sourceFluxType,)
495 visit_ccd_to_dataRef = {}
498 load_cat_prof_file =
'jointcal_build_ccdImage.prof' if profile_jointcal
else '' 499 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
502 camera = dataRefs[0].get(
'camera', immediate=
True)
506 oldWcsList.append(result.wcs)
507 visit_ccd_to_dataRef[result.key] = ref
508 filters.append(result.filter)
509 filters = collections.Counter(filters)
511 associations.computeCommonTangentPoint()
516 bbox = associations.getRaDecBBox()
517 bboxCenter = bbox.getCenter()
518 center = afwGeom.SpherePoint(bboxCenter[0], bboxCenter[1], afwGeom.degrees)
519 bboxMax = bbox.getMax()
520 corner = afwGeom.SpherePoint(bboxMax[0], bboxMax[1], afwGeom.degrees)
521 radius = center.separation(corner).asRadians()
526 raise RuntimeError(
"astrometry_net_data is not setup")
529 defaultFilter = filters.most_common(1)[0][0]
530 self.log.debug(
"Using %s band for reference flux", defaultFilter)
533 tract = dataRefs[0].dataId[
'tract']
535 if self.config.doAstrometry:
539 referenceSelector=self.astrometryReferenceSelector,
541 profile_jointcal=profile_jointcal,
547 if self.config.doPhotometry:
551 referenceSelector=self.photometryReferenceSelector,
553 profile_jointcal=profile_jointcal,
556 reject_bad_fluxes=
True)
561 return pipeBase.Struct(dataRefs=dataRefs,
562 oldWcsList=oldWcsList,
566 defaultFilter=defaultFilter,
567 exitStatus=exitStatus)
569 def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
570 name="", refObjLoader=None, referenceSelector=None,
571 filters=[], fit_function=None,
572 tract=None, profile_jointcal=False, match_cut=3.0,
573 reject_bad_fluxes=False):
574 """Load reference catalog, perform the fit, and return the result. 578 associations : `lsst.jointcal.Associations` 579 The star/reference star associations to fit. 580 defaultFilter : `str` 581 filter to load from reference catalog. 582 center : `lsst.afw.geom.SpherePoint` 583 ICRS center of field to load from reference catalog. 584 radius : `lsst.afw.geom.Angle` 585 On-sky radius to load from reference catalog. 587 Name of thing being fit: "Astrometry" or "Photometry". 588 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 589 Reference object loader to load from for fit. 590 filters : `list` of `str`, optional 591 List of filters to load from the reference catalog. 592 fit_function : callable 593 Function to call to perform fit (takes associations object). 595 Name of tract currently being fit. 596 profile_jointcal : `bool`, optional 597 Separately profile the fitting step. 598 match_cut : `float`, optional 599 Radius in arcseconds to find cross-catalog matches to during 600 associations.associateCatalogs. 601 reject_bad_fluxes : `bool`, optional 602 Reject refCat sources with NaN/inf flux or NaN/0 fluxErr. 606 result : `Photometry` or `Astrometry` 607 Result of `fit_function()` 609 self.log.info(
"====== Now processing %s...", name)
612 associations.associateCatalogs(match_cut)
614 associations.fittedStarListSize())
616 applyColorterms =
False if name ==
"Astrometry" else self.config.applyColorTerms
617 if name ==
"Astrometry":
618 referenceSelector = self.config.astrometryReferenceSelector
619 elif name ==
"Photometry":
620 referenceSelector = self.config.photometryReferenceSelector
622 center, radius, defaultFilter,
623 applyColorterms=applyColorterms)
625 associations.collectRefStars(refCat, self.config.matchCut*afwGeom.arcseconds,
626 fluxField, reject_bad_fluxes)
628 associations.refStarListSize())
630 associations.prepareFittedStars(self.config.minMeasurements)
634 associations.nFittedStarsWithAssociatedRefStar())
636 associations.fittedStarListSize())
638 associations.nCcdImagesValidForFit())
640 load_cat_prof_file =
'jointcal_fit_%s.prof'%name
if profile_jointcal
else '' 641 dataName =
"{}_{}".format(tract, defaultFilter)
642 with pipeBase.cmdLineTask.profile(load_cat_prof_file):
643 result = fit_function(associations, dataName)
646 if self.config.writeChi2FilesInitialFinal:
647 baseName = f
"{name}_final_chi2-{dataName}" 648 result.fit.saveChi2Contributions(baseName+
"{type}")
652 def _load_reference_catalog(self, refObjLoader, referenceSelector, center, radius, filterName,
653 applyColorterms=False):
654 """Load the necessary reference catalog sources, convert fluxes to 655 correct units, and apply color term corrections if requested. 659 refObjLoader : `lsst.meas.algorithms.LoadReferenceObjectsTask` 660 The reference catalog loader to use to get the data. 661 referenceSelector : `lsst.meas.algorithms.ReferenceSourceSelectorTask` 662 Source selector to apply to loaded reference catalog. 663 center : `lsst.geom.SpherePoint` 664 The center around which to load sources. 665 radius : `lsst.geom.Angle` 666 The radius around ``center`` to load sources in. 668 The name of the camera filter to load fluxes for. 669 applyColorterms : `bool` 670 Apply colorterm corrections to the refcat for ``filterName``? 674 refCat : `lsst.afw.table.SimpleCatalog` 675 The loaded reference catalog. 677 The name of the reference catalog flux field appropriate for ``filterName``. 679 skyCircle = refObjLoader.loadSkyCircle(center,
680 afwGeom.Angle(radius, afwGeom.radians),
683 selected = referenceSelector.run(skyCircle.refCat)
685 if not selected.sourceCat.isContiguous():
686 refCat = selected.sourceCat.copy(deep=
True)
688 refCat = selected.sourceCat
692 refCatName = refObjLoader.ref_dataset_name
693 except AttributeError:
695 raise RuntimeError(
"Cannot perform colorterm corrections with a.net refcats.")
696 self.log.info(
"Applying color terms for filterName=%r reference catalog=%s",
697 filterName, refCatName)
698 colorterm = self.config.colorterms.getColorterm(
699 filterName=filterName, photoCatName=refCatName, doRaise=
True)
701 refMag, refMagErr = colorterm.getCorrectedMagnitudes(refCat, filterName)
702 refCat[skyCircle.fluxField] = u.Magnitude(refMag, u.ABmag).to_value(u.nJy)
704 refCat[skyCircle.fluxField+
'Err'] = fluxErrFromABMagErr(refMagErr, refMag) * 1e9
706 return refCat, skyCircle.fluxField
708 def _check_star_lists(self, associations, name):
710 if associations.nCcdImagesValidForFit() == 0:
711 raise RuntimeError(
'No images in the ccdImageList!')
712 if associations.fittedStarListSize() == 0:
713 raise RuntimeError(
'No stars in the {} fittedStarList!'.format(name))
714 if associations.refStarListSize() == 0:
715 raise RuntimeError(
'No stars in the {} reference star list!'.format(name))
717 def _logChi2AndValidate(self, associations, fit, model, chi2Label="Model",
719 """Compute chi2, log it, validate the model, and return chi2. 723 associations : `lsst.jointcal.Associations` 724 The star/reference star associations to fit. 725 fit : `lsst.jointcal.FitterBase` 726 The fitter to use for minimization. 727 model : `lsst.jointcal.Model` 729 chi2Label : str, optional 730 Label to describe the chi2 (e.g. "Initialized", "Final"). 731 writeChi2Name : `str`, optional 732 Filename prefix to write the chi2 contributions to. 733 Do not supply an extension: an appropriate one will be added. 737 chi2: `lsst.jointcal.Chi2Accumulator` 738 The chi2 object for the current fitter and model. 743 Raised if chi2 is infinite or NaN. 745 Raised if the model is not valid. 747 if writeChi2Name
is not None:
748 fit.saveChi2Contributions(writeChi2Name+
"{type}")
749 self.log.info(
"Wrote chi2 contributions files: %s", writeChi2Name)
751 chi2 = fit.computeChi2()
752 self.log.info(
"%s %s", chi2Label, chi2)
754 if not np.isfinite(chi2.chi2):
755 raise FloatingPointError(
'%s chi2 is invalid: %s', chi2Label, chi2)
756 if not model.validate(associations.getCcdImageList(), chi2.ndof):
757 raise ValueError(
"Model is not valid: check log messages for warnings.")
760 def _fit_photometry(self, associations, dataName=None):
762 Fit the photometric data. 766 associations : `lsst.jointcal.Associations` 767 The star/reference star associations to fit. 769 Name of the data being processed (e.g. "1234_HSC-Y"), for 770 identifying debugging files. 774 fit_result : `namedtuple` 775 fit : `lsst.jointcal.PhotometryFit` 776 The photometric fitter used to perform the fit. 777 model : `lsst.jointcal.PhotometryModel` 778 The photometric model that was fit. 780 self.log.info(
"=== Starting photometric fitting...")
783 if self.config.photometryModel ==
"constrainedFlux":
786 visitOrder=self.config.photometryVisitOrder,
787 errorPedestal=self.config.photometryErrorPedestal)
789 doLineSearch = self.config.allowLineSearch
790 elif self.config.photometryModel ==
"constrainedMagnitude":
793 visitOrder=self.config.photometryVisitOrder,
794 errorPedestal=self.config.photometryErrorPedestal)
796 doLineSearch = self.config.allowLineSearch
797 elif self.config.photometryModel ==
"simpleFlux":
799 errorPedestal=self.config.photometryErrorPedestal)
801 elif self.config.photometryModel ==
"simpleMagnitude":
803 errorPedestal=self.config.photometryErrorPedestal)
809 if self.config.writeChi2FilesInitialFinal:
810 baseName = f
"photometry_initial_chi2-{dataName}" 815 def getChi2Name(whatToFit):
816 if self.config.writeChi2FilesOuterLoop:
817 return f
"photometry_init-%s_chi2-{dataName}" % whatToFit
823 dumpMatrixFile =
"photometry_preinit" if self.config.writeInitMatrix
else "" 824 if self.config.photometryModel.startswith(
"constrained"):
827 fit.minimize(
"ModelVisit", dumpMatrixFile=dumpMatrixFile)
828 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"ModelVisit"))
831 fit.minimize(
"Model", doLineSearch=doLineSearch, dumpMatrixFile=dumpMatrixFile)
834 fit.minimize(
"Fluxes")
837 fit.minimize(
"Model Fluxes", doLineSearch=doLineSearch)
839 writeChi2Name=getChi2Name(
"ModelFluxes"))
841 model.freezeErrorTransform()
842 self.log.debug(
"Photometry error scales are frozen.")
846 self.config.maxPhotometrySteps,
849 doRankUpdate=self.config.photometryDoRankUpdate,
850 doLineSearch=doLineSearch,
857 def _fit_astrometry(self, associations, dataName=None):
859 Fit the astrometric data. 863 associations : `lsst.jointcal.Associations` 864 The star/reference star associations to fit. 866 Name of the data being processed (e.g. "1234_HSC-Y"), for 867 identifying debugging files. 871 fit_result : `namedtuple` 872 fit : `lsst.jointcal.AstrometryFit` 873 The astrometric fitter used to perform the fit. 874 model : `lsst.jointcal.AstrometryModel` 875 The astrometric model that was fit. 876 sky_to_tan_projection : `lsst.jointcal.ProjectionHandler` 877 The model for the sky to tangent plane projection that was used in the fit. 880 self.log.info(
"=== Starting astrometric fitting...")
882 associations.deprojectFittedStars()
889 if self.config.astrometryModel ==
"constrained":
891 sky_to_tan_projection,
892 chipOrder=self.config.astrometryChipOrder,
893 visitOrder=self.config.astrometryVisitOrder)
894 elif self.config.astrometryModel ==
"simple":
896 sky_to_tan_projection,
897 self.config.useInputWcs,
899 order=self.config.astrometrySimpleOrder)
904 if self.config.writeChi2FilesInitialFinal:
905 baseName = f
"astrometry_initial_chi2-{dataName}" 910 def getChi2Name(whatToFit):
911 if self.config.writeChi2FilesOuterLoop:
912 return f
"astrometry_init-%s_chi2-{dataName}" % whatToFit
916 dumpMatrixFile =
"astrometry_preinit" if self.config.writeInitMatrix
else "" 919 if self.config.astrometryModel ==
"constrained":
920 fit.minimize(
"DistortionsVisit", dumpMatrixFile=dumpMatrixFile)
921 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"DistortionsVisit"))
924 fit.minimize(
"Distortions", dumpMatrixFile=dumpMatrixFile)
925 self.
_logChi2AndValidate(associations, fit, model, writeChi2Name=getChi2Name(
"Distortions"))
927 fit.minimize(
"Positions")
930 fit.minimize(
"Distortions Positions")
932 writeChi2Name=getChi2Name(
"DistortionsPositions"))
936 self.config.maxAstrometrySteps,
938 "Distortions Positions",
939 doRankUpdate=self.config.astrometryDoRankUpdate,
945 return Astrometry(fit, model, sky_to_tan_projection)
947 def _check_stars(self, associations):
948 """Count measured and reference stars per ccd and warn/log them.""" 949 for ccdImage
in associations.getCcdImageList():
950 nMeasuredStars, nRefStars = ccdImage.countStars()
951 self.log.debug(
"ccdImage %s has %s measured and %s reference stars",
952 ccdImage.getName(), nMeasuredStars, nRefStars)
953 if nMeasuredStars < self.config.minMeasuredStarsPerCcd:
954 self.log.warn(
"ccdImage %s has only %s measuredStars (desired %s)",
955 ccdImage.getName(), nMeasuredStars, self.config.minMeasuredStarsPerCcd)
956 if nRefStars < self.config.minRefStarsPerCcd:
957 self.log.warn(
"ccdImage %s has only %s RefStars (desired %s)",
958 ccdImage.getName(), nRefStars, self.config.minRefStarsPerCcd)
960 def _iterate_fit(self, associations, fitter, max_steps, name, whatToFit,
964 """Run fitter.minimize up to max_steps times, returning the final chi2. 968 associations : `lsst.jointcal.Associations` 969 The star/reference star associations to fit. 970 fitter : `lsst.jointcal.FitterBase` 971 The fitter to use for minimization. 973 Maximum number of steps to run outlier rejection before declaring 975 name : {'photometry' or 'astrometry'} 976 What type of data are we fitting (for logs and debugging files). 978 Passed to ``fitter.minimize()`` to define the parameters to fit. 979 dataName : `str`, optional 980 Descriptive name for this dataset (e.g. tract and filter), 982 doRankUpdate : `bool`, optional 983 Do an Eigen rank update during minimization, or recompute the full 985 doLineSearch : `bool`, optional 986 Do a line search for the optimum step during minimization? 990 chi2: `lsst.jointcal.Chi2Statistic` 991 The final chi2 after the fit converges, or is forced to end. 996 Raised if the fitter fails with a non-finite value. 998 Raised if the fitter fails for some other reason; 999 log messages will provide further details. 1001 dumpMatrixFile =
"%s_postinit" % name
if self.config.writeInitMatrix
else "" 1002 for i
in range(max_steps):
1003 if self.config.writeChi2FilesOuterLoop:
1004 writeChi2Name = f
"{name}_iterate_{i}_chi2-{dataName}" 1006 writeChi2Name =
None 1007 result = fitter.minimize(whatToFit,
1008 self.config.outlierRejectSigma,
1009 doRankUpdate=doRankUpdate,
1010 doLineSearch=doLineSearch,
1011 dumpMatrixFile=dumpMatrixFile)
1014 writeChi2Name=writeChi2Name)
1016 if result == MinimizeResult.Converged:
1018 self.log.debug(
"fit has converged - no more outliers - redo minimization " 1019 "one more time in case we have lost accuracy in rank update.")
1021 result = fitter.minimize(whatToFit, self.config.outlierRejectSigma)
1025 if chi2.chi2/chi2.ndof >= 4.0:
1026 self.log.error(
"Potentially bad fit: High chi-squared/ndof.")
1029 elif result == MinimizeResult.Chi2Increased:
1030 self.log.warn(
"still some outliers but chi2 increases - retry")
1031 elif result == MinimizeResult.NonFinite:
1032 filename =
"{}_failure-nonfinite_chi2-{}.csv".format(name, dataName)
1034 fitter.saveChi2Contributions(filename)
1035 msg =
"Nonfinite value in chi2 minimization, cannot complete fit. Dumped star tables to: {}" 1036 raise FloatingPointError(msg.format(filename))
1037 elif result == MinimizeResult.Failed:
1038 raise RuntimeError(
"Chi2 minimization failure, cannot complete fit.")
1040 raise RuntimeError(
"Unxepected return code from minimize().")
1042 self.log.error(
"%s failed to converge after %d steps"%(name, max_steps))
1046 def _write_astrometry_results(self, associations, model, visit_ccd_to_dataRef):
1048 Write the fitted astrometric results to a new 'jointcal_wcs' dataRef. 1052 associations : `lsst.jointcal.Associations` 1053 The star/reference star associations to fit. 1054 model : `lsst.jointcal.AstrometryModel` 1055 The astrometric model that was fit. 1056 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1057 Dict of ccdImage identifiers to dataRefs that were fit. 1060 ccdImageList = associations.getCcdImageList()
1061 for ccdImage
in ccdImageList:
1063 ccd = ccdImage.ccdId
1064 visit = ccdImage.visit
1065 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1066 self.log.info(
"Updating WCS for visit: %d, ccd: %d", visit, ccd)
1067 skyWcs = model.makeSkyWcs(ccdImage)
1069 dataRef.put(skyWcs,
'jointcal_wcs')
1070 except pexExceptions.Exception
as e:
1071 self.log.fatal(
'Failed to write updated Wcs: %s', str(e))
1074 def _write_photometry_results(self, associations, model, visit_ccd_to_dataRef):
1076 Write the fitted photometric results to a new 'jointcal_photoCalib' dataRef. 1080 associations : `lsst.jointcal.Associations` 1081 The star/reference star associations to fit. 1082 model : `lsst.jointcal.PhotometryModel` 1083 The photoometric model that was fit. 1084 visit_ccd_to_dataRef : `dict` of Key: `lsst.daf.persistence.ButlerDataRef` 1085 Dict of ccdImage identifiers to dataRefs that were fit. 1088 ccdImageList = associations.getCcdImageList()
1089 for ccdImage
in ccdImageList:
1091 ccd = ccdImage.ccdId
1092 visit = ccdImage.visit
1093 dataRef = visit_ccd_to_dataRef[(visit, ccd)]
1094 self.log.info(
"Updating PhotoCalib for visit: %d, ccd: %d", visit, ccd)
1095 photoCalib = model.toPhotoCalib(ccdImage)
1097 dataRef.put(photoCalib,
'jointcal_photoCalib')
1098 except pexExceptions.Exception
as e:
1099 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)
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.
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)
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)