lsst.jointcal  14.0-12-gc7bdbdc
jointcal.py
Go to the documentation of this file.
1 # See COPYRIGHT file at the top of the source tree.
2 
3 from __future__ import division, absolute_import, print_function
4 from builtins import str
5 from builtins import range
6 
7 import collections
8 
9 import lsst.utils
10 import lsst.pex.config as pexConfig
11 import lsst.pipe.base as pipeBase
12 import lsst.afw.image as afwImage
13 import lsst.afw.geom as afwGeom
14 import lsst.afw.coord as afwCoord
15 import lsst.pex.exceptions as pexExceptions
16 import lsst.afw.table
18 
19 from lsst.meas.algorithms import LoadIndexedReferenceObjectsTask
20 from lsst.meas.algorithms.sourceSelector import sourceSelectorRegistry
21 
22 from .dataIds import PerTractCcdDataIdContainer
23 
24 import lsst.jointcal
25 from lsst.jointcal import MinimizeResult
26 
27 __all__ = ["JointcalConfig", "JointcalTask"]
28 
29 Photometry = collections.namedtuple('Photometry', ('fit', 'model'))
30 Astrometry = collections.namedtuple('Astrometry', ('fit', 'model', 'sky_to_tan_projection'))
31 
32 
33 class JointcalRunner(pipeBase.ButlerInitializedTaskRunner):
34  """Subclass of TaskRunner for jointcalTask
35 
36  jointcalTask.run() takes a number of arguments, one of which is a list of dataRefs
37  extracted from the command line (whereas most CmdLineTasks' run methods take
38  single dataRef, are are called repeatedly). This class transforms the processed
39  arguments generated by the ArgumentParser into the arguments expected by
40  Jointcal.run().
41 
42  See pipeBase.TaskRunner for more information.
43  """
44 
45  @staticmethod
46  def getTargetList(parsedCmd, **kwargs):
47  """
48  Return a list of tuples per tract, each containing (dataRefs, kwargs).
49 
50  Jointcal operates on lists of dataRefs simultaneously.
51  """
52  kwargs['profile_jointcal'] = parsedCmd.profile_jointcal
53  kwargs['butler'] = parsedCmd.butler
54 
55  # organize data IDs by tract
56  refListDict = {}
57  for ref in parsedCmd.id.refList:
58  refListDict.setdefault(ref.dataId["tract"], []).append(ref)
59  # we call run() once with each tract
60  result = [(refListDict[tract], kwargs) for tract in sorted(refListDict.keys())]
61  return result
62 
63  def __call__(self, args):
64  """
65  @param args Arguments for Task.run()
66 
67  @return
68  - None if self.doReturnResults is False
69  - A pipe.base.Struct containing these fields if self.doReturnResults is True:
70  - dataRef: the provided data references, with update post-fit WCS's.
71  """
72  # NOTE: cannot call self.makeTask because that assumes args[0] is a single dataRef.
73  dataRefList, kwargs = args
74  butler = kwargs.pop('butler')
75  task = self.TaskClass(config=self.config, log=self.log, butler=butler)
76  result = task.run(dataRefList, **kwargs)
77  if self.doReturnResults:
78  return pipeBase.Struct(result=result)
79 
80 
81 class JointcalConfig(pexConfig.Config):
82  """Config for jointcalTask"""
83 
84  doAstrometry = pexConfig.Field(
85  doc="Fit astrometry and write the fitted result.",
86  dtype=bool,
87  default=True
88  )
89  doPhotometry = pexConfig.Field(
90  doc="Fit photometry and write the fitted result.",
91  dtype=bool,
92  default=True
93  )
94  coaddName = pexConfig.Field(
95  doc="Type of coadd, typically deep or goodSeeing",
96  dtype=str,
97  default="deep"
98  )
99  posError = pexConfig.Field(
100  doc="Constant term for error on position (in pixel unit)",
101  dtype=float,
102  default=0.02,
103  )
104  # TODO: DM-6885 matchCut should be an afw.geom.Angle
105  matchCut = pexConfig.Field(
106  doc="Matching radius between fitted and reference stars (arcseconds)",
107  dtype=float,
108  default=3.0,
109  )
110  minMeasurements = pexConfig.Field(
111  doc="Minimum number of associated measured stars for a fitted star to be included in the fit",
112  dtype=int,
113  default=2,
114  )
115  polyOrder = pexConfig.Field(
116  doc="Polynomial order for fitting distorsion",
117  dtype=int,
118  default=3,
119  )
120  astrometryModel = pexConfig.ChoiceField(
121  doc="Type of model to fit to astrometry",
122  dtype=str,
123  default="simplePoly",
124  allowed={"simplePoly": "One polynomial per ccd",
125  "constrainedPoly": "One polynomial per ccd, and one polynomial per visit"}
126  )
127  photometryModel = pexConfig.ChoiceField(
128  doc="Type of model to fit to photometry",
129  dtype=str,
130  default="simple",
131  allowed={"simple": "One constant zeropoint per ccd and visit",
132  "constrained": "Constrained zeropoint per ccd, and one polynomial per visit"}
133  )
134  photometryVisitDegree = pexConfig.Field(
135  doc="Degree of the per-visit polynomial transform for the constrained photometry model.",
136  dtype=int,
137  default=7,
138  )
139  astrometryRefObjLoader = pexConfig.ConfigurableField(
140  target=LoadIndexedReferenceObjectsTask,
141  doc="Reference object loader for astrometric fit",
142  )
143  photometryRefObjLoader = pexConfig.ConfigurableField(
144  target=LoadIndexedReferenceObjectsTask,
145  doc="Reference object loader for photometric fit",
146  )
147  sourceSelector = sourceSelectorRegistry.makeField(
148  doc="How to select sources for cross-matching",
149  default="astrometry"
150  )
151 
152  def setDefaults(self):
153  sourceSelector = self.sourceSelector["astrometry"]
154  sourceSelector.setDefaults()
155  # don't want to lose existing flags, just add to them.
156  sourceSelector.badFlags.extend(["slot_Shape_flag"])
157  # This should be used to set the FluxField value in jointcal::JointcalControl
158  sourceSelector.sourceFluxType = 'Calib'
159 
160 
161 class JointcalTask(pipeBase.CmdLineTask):
162  """Jointly astrometrically (photometrically later) calibrate a group of images."""
163 
164  ConfigClass = JointcalConfig
165  RunnerClass = JointcalRunner
166  _DefaultName = "jointcal"
167 
168  def __init__(self, butler=None, profile_jointcal=False, **kwargs):
169  """
170  Instantiate a JointcalTask.
171 
172  Parameters
173  ----------
174  butler : lsst.daf.persistence.Butler
175  The butler is passed to the refObjLoader constructor in case it is
176  needed. Ignored if the refObjLoader argument provides a loader directly.
177  Used to initialize the astrometry and photometry refObjLoaders.
178  profile_jointcal : bool
179  set to True to profile different stages of this jointcal run.
180  """
181  pipeBase.CmdLineTask.__init__(self, **kwargs)
182  self.profile_jointcal = profile_jointcal
183  self.makeSubtask("sourceSelector")
184  if self.config.doAstrometry:
185  self.makeSubtask('astrometryRefObjLoader', butler=butler)
186  if self.config.doPhotometry:
187  self.makeSubtask('photometryRefObjLoader', butler=butler)
188 
189  # To hold various computed metrics for use by tests
190  self.metrics = {}
191 
192  # We don't need to persist config and metadata at this stage.
193  # In this way, we don't need to put a specific entry in the camera mapper policy file
194  def _getConfigName(self):
195  return None
196 
197  def _getMetadataName(self):
198  return None
199 
200  @classmethod
201  def _makeArgumentParser(cls):
202  """Create an argument parser"""
203  parser = pipeBase.ArgumentParser(name=cls._DefaultName)
204  parser.add_argument("--profile_jointcal", default=False, action="store_true",
205  help="Profile steps of jointcal separately.")
206  parser.add_id_argument("--id", "calexp", help="data ID, e.g. --id visit=6789 ccd=0..9",
207  ContainerClass=PerTractCcdDataIdContainer)
208  return parser
209 
210  def _build_ccdImage(self, dataRef, associations, jointcalControl):
211  """
212  Extract the necessary things from this dataRef to add a new ccdImage.
213 
214  Parameters
215  ----------
216  dataRef : lsst.daf.persistence.ButlerDataRef
217  dataRef to extract info from.
218  associations : lsst.jointcal.Associations
219  object to add the info to, to construct a new CcdImage
220  jointcalControl : jointcal.JointcalControl
221  control object for associations management
222 
223  Returns
224  ------
225  namedtuple
226  wcs : lsst.afw.image.TanWcs
227  the TAN WCS of this image, read from the calexp
228  key : namedtuple
229  a key to identify this dataRef by its visit and ccd ids
230  filter : str
231  this calexp's filter
232  """
233  if "visit" in dataRef.dataId.keys():
234  visit = dataRef.dataId["visit"]
235  else:
236  visit = dataRef.getButler().queryMetadata("calexp", ("visit"), dataRef.dataId)[0]
237 
238  src = dataRef.get("src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS, immediate=True)
239 
240  visitInfo = dataRef.get('calexp_visitInfo')
241  detector = dataRef.get('calexp_detector')
242  ccdname = detector.getId()
243  calib = dataRef.get('calexp_calib')
244  tanWcs = dataRef.get('calexp_wcs')
245  bbox = dataRef.get('calexp_bbox')
246  filt = dataRef.get('calexp_filter')
247  filterName = filt.getName()
248  fluxMag0 = calib.getFluxMag0()
249  photoCalib = afwImage.PhotoCalib(1.0/fluxMag0[0], fluxMag0[1]/fluxMag0[0]**2, bbox)
250 
251  goodSrc = self.sourceSelector.selectSources(src)
252 
253  if len(goodSrc.sourceCat) == 0:
254  self.log.warn("no stars selected in ", visit, ccdname)
255  return tanWcs
256  self.log.info("%d stars selected in visit %d ccd %d", len(goodSrc.sourceCat), visit, ccdname)
257  associations.addImage(goodSrc.sourceCat, tanWcs, visitInfo, bbox, filterName, photoCalib, detector,
258  visit, ccdname, jointcalControl)
259 
260  Result = collections.namedtuple('Result_from_build_CcdImage', ('wcs', 'key', 'filter'))
261  Key = collections.namedtuple('Key', ('visit', 'ccd'))
262  return Result(tanWcs, Key(visit, ccdname), filterName)
263 
264  @pipeBase.timeMethod
265  def run(self, dataRefs, profile_jointcal=False):
266  """
267  Jointly calibrate the astrometry and photometry across a set of images.
268 
269  Parameters
270  ----------
271  dataRefs : list of lsst.daf.persistence.ButlerDataRef
272  List of data references to the exposures to be fit.
273  profile_jointcal : bool
274  Profile the individual steps of jointcal.
275 
276  Returns
277  -------
278  pipe.base.Struct
279  struct containing:
280  * dataRefs: the provided data references that were fit (with updated WCSs)
281  * oldWcsList: the original WCS from each dataRef
282  * metrics: dictionary of internally-computed metrics for testing/validation.
283  """
284  if len(dataRefs) == 0:
285  raise ValueError('Need a list of data references!')
286 
287  sourceFluxField = "slot_%sFlux" % (self.sourceSelector.config.sourceFluxType,)
288  jointcalControl = lsst.jointcal.JointcalControl(sourceFluxField)
289  associations = lsst.jointcal.Associations()
290 
291  visit_ccd_to_dataRef = {}
292  oldWcsList = []
293  filters = []
294  load_cat_prof_file = 'jointcal_build_ccdImage.prof' if profile_jointcal else ''
295  with pipeBase.cmdLineTask.profile(load_cat_prof_file):
296  # We need the bounding-box of the focal plane for photometry visit models.
297  # NOTE: we only need to read it once, because its the same for all exposures of a camera.
298  camera = dataRefs[0].get('camera', immediate=True)
299  self.focalPlaneBBox = camera.getFpBBox()
300  for ref in dataRefs:
301  result = self._build_ccdImage(ref, associations, jointcalControl)
302  oldWcsList.append(result.wcs)
303  visit_ccd_to_dataRef[result.key] = ref
304  filters.append(result.filter)
305  filters = collections.Counter(filters)
306 
307  centers = [ccdImage.getBoresightRaDec() for ccdImage in associations.getCcdImageList()]
308  commonTangentPoint = lsst.afw.coord.averageCoord(centers)
309  self.log.debug("Using common tangent point: %s", commonTangentPoint.getPosition())
310  associations.setCommonTangentPoint(commonTangentPoint.getPosition())
311 
312  # Use external reference catalogs handled by LSST stack mechanism
313  # Get the bounding box overlapping all associated images
314  # ==> This is probably a bad idea to do it this way <== To be improved
315  bbox = associations.getRaDecBBox()
316  center = afwCoord.Coord(bbox.getCenter(), afwGeom.degrees)
317  corner = afwCoord.Coord(bbox.getMax(), afwGeom.degrees)
318  radius = center.angularSeparation(corner).asRadians()
319 
320  # Get astrometry_net_data path
321  anDir = lsst.utils.getPackageDir('astrometry_net_data')
322  if anDir is None:
323  raise RuntimeError("astrometry_net_data is not setup")
324 
325  # Determine a default filter associated with the catalog. See DM-9093
326  defaultFilter = filters.most_common(1)[0][0]
327  self.log.debug("Using %s band for reference flux", defaultFilter)
328 
329  # TODO: need a better way to get the tract.
330  tract = dataRefs[0].dataId['tract']
331 
332  if self.config.doAstrometry:
333  astrometry = self._do_load_refcat_and_fit(associations, defaultFilter, center, radius,
334  name="Astrometry",
335  refObjLoader=self.astrometryRefObjLoader,
336  fit_function=self._fit_astrometry,
337  profile_jointcal=profile_jointcal,
338  tract=tract)
339  else:
340  astrometry = Astrometry(None, None, None)
341 
342  if self.config.doPhotometry:
343  photometry = self._do_load_refcat_and_fit(associations, defaultFilter, center, radius,
344  name="Photometry",
345  refObjLoader=self.photometryRefObjLoader,
346  fit_function=self._fit_photometry,
347  profile_jointcal=profile_jointcal,
348  tract=tract,
349  filters=filters)
350  else:
351  photometry = Photometry(None, None)
352 
353  load_cat_prof_file = 'jointcal_write_results.prof' if profile_jointcal else ''
354  with pipeBase.cmdLineTask.profile(load_cat_prof_file):
355  self._write_results(associations, astrometry.model, photometry.model, visit_ccd_to_dataRef)
356 
357  return pipeBase.Struct(dataRefs=dataRefs, oldWcsList=oldWcsList, metrics=self.metrics)
358 
359  def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius,
360  name="", refObjLoader=None, filters=[], fit_function=None,
361  tract=None, profile_jointcal=False, match_cut=3.0):
362  """Load reference catalog, perform the fit, and return the result.
363 
364  Parameters
365  ----------
366  associations : lsst.jointcal.Associations
367  The star/reference star associations to fit.
368  defaultFilter : str
369  filter to load from reference catalog.
370  center : lsst.afw.coord.Coord
371  Center of field to load from reference catalog.
372  radius : lsst.afw.geom.Angle
373  On-sky radius to load from reference catalog.
374  name : str
375  Name of thing being fit: "Astrometry" or "Photometry".
376  refObjLoader : lsst.meas.algorithms.LoadReferenceObjectsTask
377  Reference object loader to load from for fit.
378  filters : list of str, optional
379  List of filters to load from the reference catalog.
380  fit_function : function
381  function to call to perform fit (takes associations object).
382  tract : str
383  Name of tract currently being fit.
384  profile_jointcal : bool, optional
385  Separately profile the fitting step.
386  match_cut : float, optional
387  Radius in arcseconds to find cross-catalog matches to during
388  associations.associateCatalogs.
389 
390  Returns
391  -------
392  Result of `fit_function()`
393  """
394  self.log.info("====== Now processing %s...", name)
395  # TODO: this should not print "trying to invert a singular transformation:"
396  # if it does that, something's not right about the WCS...
397  associations.associateCatalogs(match_cut)
398  self.metrics['associated%sFittedStars' % name] = associations.fittedStarListSize()
399 
400  skyCircle = refObjLoader.loadSkyCircle(center,
401  afwGeom.Angle(radius, afwGeom.radians),
402  defaultFilter)
403 
404  # Need memory contiguity to get reference filters as a vector.
405  if not skyCircle.refCat.isContiguous():
406  refCat = skyCircle.refCat.copy(deep=True)
407  else:
408  refCat = skyCircle.refCat
409 
410  # load the reference catalog fluxes.
411  # TODO: Simon will file a ticket for making this better (and making it use the color terms)
412  refFluxes = {}
413  refFluxErrs = {}
414  for filt in filters:
415  filtKeys = lsst.meas.algorithms.getRefFluxKeys(refCat.schema, filt)
416  refFluxes[filt] = refCat.get(filtKeys[0])
417  refFluxErrs[filt] = refCat.get(filtKeys[1])
418 
419  associations.collectRefStars(refCat, self.config.matchCut*afwGeom.arcseconds,
420  skyCircle.fluxField, refFluxes, refFluxErrs)
421  self.metrics['collected%sRefStars' % name] = associations.refStarListSize()
422 
423  associations.selectFittedStars(self.config.minMeasurements)
424  self._check_star_lists(associations, name)
425  self.metrics['selected%sRefStars' % name] = associations.refStarListSize()
426  self.metrics['selected%sFittedStars' % name] = associations.fittedStarListSize()
427  self.metrics['selected%sCcdImageList' % name] = associations.nCcdImagesValidForFit()
428 
429  load_cat_prof_file = 'jointcal_fit_%s.prof'%name if profile_jointcal else ''
430  with pipeBase.cmdLineTask.profile(load_cat_prof_file):
431  result = fit_function(associations)
432  # TODO: this should probably be made optional and turned into a "butler save" somehow.
433  # Save reference and measurement n-tuples for each tract
434  tupleName = "{}_res_{}.list".format(name, tract)
435  result.fit.saveResultTuples(tupleName)
436 
437  return result
438 
439  def _check_star_lists(self, associations, name):
440  # TODO: these should be len(blah), but we need this properly wrapped first.
441  if associations.nCcdImagesValidForFit() == 0:
442  raise RuntimeError('No images in the ccdImageList!')
443  if associations.fittedStarListSize() == 0:
444  raise RuntimeError('No stars in the {} fittedStarList!'.format(name))
445  if associations.refStarListSize() == 0:
446  raise RuntimeError('No stars in the {} reference star list!'.format(name))
447 
448  def _fit_photometry(self, associations):
449  """
450  Fit the photometric data.
451 
452  Parameters
453  ----------
454  associations : lsst.jointcal.Associations
455  The star/reference star associations to fit.
456 
457  Returns
458  -------
459  namedtuple
460  fit : lsst.jointcal.PhotometryFit
461  The photometric fitter used to perform the fit.
462  model : lsst.jointcal.PhotometryModel
463  The photometric model that was fit.
464  """
465  self.log.info("=== Starting photometric fitting...")
466 
467  # TODO: should use pex.config.RegistryField here (see DM-9195)
468  if self.config.photometryModel == "constrained":
469  model = lsst.jointcal.ConstrainedPhotometryModel(associations.getCcdImageList(),
470  self.focalPlaneBBox,
471  visitDegree=self.config.photometryVisitDegree)
472  elif self.config.photometryModel == "simple":
473  model = lsst.jointcal.SimplePhotometryModel(associations.getCcdImageList())
474 
475  fit = lsst.jointcal.PhotometryFit(associations, model)
476  chi2 = fit.computeChi2()
477  self.log.info("Initialized: %s", str(chi2))
478  fit.minimize("Model")
479  chi2 = fit.computeChi2()
480  self.log.info(str(chi2))
481  fit.minimize("Fluxes")
482  chi2 = fit.computeChi2()
483  self.log.info(str(chi2))
484  fit.minimize("Model Fluxes")
485  chi2 = fit.computeChi2()
486  self.log.info("Fit prepared with %s", str(chi2))
487 
488  chi2 = self._iterate_fit(fit, model, 20, "photometry", "Model Fluxes")
489 
490  self.metrics['photometryFinalChi2'] = chi2.chi2
491  self.metrics['photometryFinalNdof'] = chi2.ndof
492  return Photometry(fit, model)
493 
494  def _fit_astrometry(self, associations):
495  """
496  Fit the astrometric data.
497 
498  Parameters
499  ----------
500  associations : lsst.jointcal.Associations
501  The star/reference star associations to fit.
502 
503  Returns
504  -------
505  namedtuple
506  fit : lsst.jointcal.AstrometryFit
507  The astrometric fitter used to perform the fit.
508  model : lsst.jointcal.AstrometryModel
509  The astrometric model that was fit.
510  sky_to_tan_projection : lsst.jointcal.ProjectionHandler
511  The model for the sky to tangent plane projection that was used in the fit.
512  """
513 
514  self.log.info("=== Starting astrometric fitting...")
515 
516  associations.deprojectFittedStars()
517 
518  # NOTE: need to return sky_to_tan_projection so that it doesn't get garbage collected.
519  # TODO: could we package sky_to_tan_projection and model together so we don't have to manage
520  # them so carefully?
521  sky_to_tan_projection = lsst.jointcal.OneTPPerVisitHandler(associations.getCcdImageList())
522 
523  if self.config.astrometryModel == "constrainedPoly":
524  model = lsst.jointcal.ConstrainedPolyModel(associations.getCcdImageList(),
525  sky_to_tan_projection, True, 0)
526  elif self.config.astrometryModel == "simplePoly":
527  model = lsst.jointcal.SimplePolyModel(associations.getCcdImageList(),
528  sky_to_tan_projection,
529  True, 0, self.config.polyOrder)
530 
531  fit = lsst.jointcal.AstrometryFit(associations, model, self.config.posError)
532  chi2 = fit.computeChi2()
533  self.log.info("Initialized: %s", str(chi2))
534  fit.minimize("Distortions")
535  chi2 = fit.computeChi2()
536  self.log.info(str(chi2))
537  fit.minimize("Positions")
538  chi2 = fit.computeChi2()
539  self.log.info(str(chi2))
540  fit.minimize("Distortions Positions")
541  chi2 = fit.computeChi2()
542  self.log.info(str(chi2))
543 
544  chi2 = self._iterate_fit(fit, model, 20, "astrometry", "Distortions Positions")
545 
546  self.metrics['astrometryFinalChi2'] = chi2.chi2
547  self.metrics['astrometryFinalNdof'] = chi2.ndof
548 
549  return Astrometry(fit, model, sky_to_tan_projection)
550 
551  def _iterate_fit(self, fit, model, max_steps, name, whatToFit):
552  """Run fit.minimize up to max_steps times, returning the final chi2."""
553 
554  for i in range(max_steps):
555  r = fit.minimize(whatToFit, 5) # outlier removal at 5 sigma.
556  chi2 = fit.computeChi2()
557  self.log.info(str(chi2))
558  if r == MinimizeResult.Converged:
559  self.log.debug("fit has converged - no more outliers - redo minimixation"
560  "one more time in case we have lost accuracy in rank update")
561  # Redo minimization one more time in case we have lost accuracy in rank update
562  r = fit.minimize(whatToFit, 5) # outliers removal at 5 sigma.
563  chi2 = fit.computeChi2()
564  self.log.info("Fit completed with: %s", str(chi2))
565  break
566  elif r == MinimizeResult.Failed:
567  self.log.warn("minimization failed")
568  break
569  elif r == MinimizeResult.Chi2Increased:
570  self.log.warn("still some ouliers but chi2 increases - retry")
571  else:
572  self.log.error("unxepected return code from minimize")
573  break
574  else:
575  self.log.error("%s failed to converge after %d steps"%(name, max_steps))
576 
577  return chi2
578 
579  def _write_results(self, associations, astrometry_model, photometry_model, visit_ccd_to_dataRef):
580  """
581  Write the fitted results (photometric and astrometric) to a new 'wcs' dataRef.
582 
583  Parameters
584  ----------
585  associations : lsst.jointcal.Associations
586  The star/reference star associations to fit.
587  astrometry_model : lsst.jointcal.AstrometryModel
588  The astrometric model that was fit.
589  photometry_model : lsst.jointcal.PhotometryModel
590  The photometric model that was fit.
591  visit_ccd_to_dataRef : dict of Key: lsst.daf.persistence.ButlerDataRef
592  dict of ccdImage identifiers to dataRefs that were fit
593  """
594 
595  ccdImageList = associations.getCcdImageList()
596  for ccdImage in ccdImageList:
597  # TODO: there must be a better way to identify this ccdImage than a visit,ccd pair?
598  ccd = ccdImage.ccdId
599  visit = ccdImage.visit
600  dataRef = visit_ccd_to_dataRef[(visit, ccd)]
601  exp = afwImage.ExposureI(0, 0)
602  if self.config.doAstrometry:
603  self.log.info("Updating WCS for visit: %d, ccd: %d", visit, ccd)
604  tanSip = astrometry_model.produceSipWcs(ccdImage)
605  tanWcs = lsst.jointcal.gtransfoToTanWcs(tanSip, ccdImage.imageFrame, False)
606  exp.setWcs(tanWcs)
607  try:
608  dataRef.put(exp, 'wcs')
609  except pexExceptions.Exception as e:
610  self.log.fatal('Failed to write updated Wcs: %s', str(e))
611  raise e
612  if self.config.doPhotometry:
613  self.log.info("Updating PhotoCalib for visit: %d, ccd: %d", visit, ccd)
614  photoCalib = photometry_model.toPhotoCalib(ccdImage)
615  try:
616  dataRef.put(photoCalib, 'photoCalib')
617  except pexExceptions.Exception as e:
618  self.log.fatal('Failed to write updated PhotoCalib: %s', str(e))
619  raise e
this is the model used to fit independent CCDs, meaning that there is no instrument model...
def _build_ccdImage(self, dataRef, associations, jointcalControl)
Definition: jointcal.py:210
def _fit_photometry(self, associations)
Definition: jointcal.py:448
def getTargetList(parsedCmd, kwargs)
Definition: jointcal.py:46
def _write_results(self, associations, astrometry_model, photometry_model, visit_ccd_to_dataRef)
Definition: jointcal.py:579
def _iterate_fit(self, fit, model, max_steps, name, whatToFit)
Definition: jointcal.py:551
def _check_star_lists(self, associations, name)
Definition: jointcal.py:439
The class that implements the relations between MeasuredStar and FittedStar.
Definition: Associations.h:28
A projection handler in which all CCDs from the same visit have the same tangent point.
std::string getPackageDir(std::string const &packageName)
This is the model used to fit mappings as the combination of a transformation depending on the chip n...
def _do_load_refcat_and_fit(self, associations, defaultFilter, center, radius, name="", refObjLoader=None, filters=[], fit_function=None, tract=None, profile_jointcal=False, match_cut=3.0)
Definition: jointcal.py:361
Class that handles the photometric least squares problem.
Definition: PhotometryFit.h:20
def _fit_astrometry(self, associations)
Definition: jointcal.py:494
boost::shared_ptr< lsst::afw::image::TanWcs > gtransfoToTanWcs(const lsst::jointcal::TanSipPix2RaDec wcsTransfo, const lsst::jointcal::Frame &ccdFrame, const bool noLowOrderSipTerms=false)
Transform the other way around.
Class that handles the astrometric least squares problem.
Definition: AstrometryFit.h:55
Photometry model with constraints, .
def run(self, dataRefs, profile_jointcal=False)
Definition: jointcal.py:265
std::shared_ptr< Coord > averageCoord(std::vector< std::shared_ptr< Coord const >> const coords, CoordSystem system=UNKNOWN)
Photometric response model which has a single photometric factor per CcdImage.
def __init__(self, butler=None, profile_jointcal=False, kwargs)
Definition: jointcal.py:168