LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
Classes | Public Member Functions | Public Attributes | Static Public Attributes | Private Member Functions | Static Private Member Functions | List of all members
lsst.meas.astrom.astrom.Astrometry Class Reference
Inheritance diagram for lsst.meas.astrom.astrom.Astrometry:

Classes

class  _LoadedMIndexes
 

Public Member Functions

def __init__
 
def memusage
 
def setAndConfig
 
def useKnownWcs
 
def determineWcs
 
def determineWcs2
 
def getBlindWcsSolution
 
def getSipWcsFromWcs
 
def getSipWcsFromCorrespondences
 
def plotSolution
 
def getColumnName
 
def getCatalogFilterName
 
def getReferenceSourcesForWcs
 
def getReferenceSources
 
def joinMatchListWithCatalog
 

Public Attributes

 config
 
 log
 
 andConfig
 
 multiInds
 

Static Public Attributes

 ConfigClass = MeasAstromConfig
 

Private Member Functions

def _readIndexFiles
 
def _debug
 
def _warn
 
def _getImageParams
 
def _calculateSipTerms
 
def _getMatchList
 
def _solve
 
def _isGoodSource
 
def _getIndexPath
 
def _getMIndexesWithinRange
 
def _getSolver
 

Static Private Member Functions

def _trimBadPoints
 

Detailed Description

Definition at line 74 of file astrom.py.

Constructor & Destructor Documentation

def lsst.meas.astrom.astrom.Astrometry.__init__ (   self,
  config,
  andConfig = None,
  log = None,
  logLevel = pexLog.Log.INFO 
)
conf: an AstromConfig object.
andConfig: an AstromNetDataConfig object
log: a pexLogging.Log
logLevel: if log is None, the log level to use

Definition at line 119 of file astrom.py.

120  logLevel=pexLog.Log.INFO):
121  '''
122  conf: an AstromConfig object.
123  andConfig: an AstromNetDataConfig object
124  log: a pexLogging.Log
125  logLevel: if log is None, the log level to use
126  '''
127  self.config = config
128  if log is not None:
129  self.log = log
130  else:
131  self.log = pexLog.Log(pexLog.Log.getDefaultLog(),
132  'meas.astrom',
133  logLevel)
134  if andConfig is None:
135  # ASSUME SETUP IN EUPS
136  dirnm = os.environ.get('ASTROMETRY_NET_DATA_DIR')
137  if dirnm is None:
138  raise RuntimeError("astrometry_net_data is not setup")
139 
140  andConfig = AstrometryNetDataConfig()
141  fn = os.path.join(dirnm, 'andConfig.py')
142  if not os.path.exists(fn):
143  raise RuntimeError('astrometry_net_data config file \"%s\" required but not found' % fn)
144  andConfig.load(fn)
146  self.andConfig = andConfig
147  self._readIndexFiles()
a place to record messages and descriptions of the state of processing.
Definition: Log.h:154

Member Function Documentation

def lsst.meas.astrom.astrom.Astrometry._calculateSipTerms (   self,
  origWcs,
  cat,
  sources,
  matchList,
  imageSize,
  x0 = 0,
  y0 = 0 
)
private
Iteratively calculate SIP distortions and regenerate matchList based on improved WCS.

origWcs: original WCS object, probably (but not necessarily) a TAN WCS;
   this is used to set the baseline when determining whether a SIP
   solution is any better; it will be returned if no better SIP solution
   can be found.

matchList: list of supposedly matched sources, using the "origWcs".

cat: reference source catalog

sources: sources in the image to be solved

imageSize, x0, y0: these determine the bounding-box of the image,
   which is used when finding reverse SIP coefficients.

Definition at line 608 of file astrom.py.

609  x0=0, y0=0):
610  '''
611  Iteratively calculate SIP distortions and regenerate matchList based on improved WCS.
612 
613  origWcs: original WCS object, probably (but not necessarily) a TAN WCS;
614  this is used to set the baseline when determining whether a SIP
615  solution is any better; it will be returned if no better SIP solution
616  can be found.
617 
618  matchList: list of supposedly matched sources, using the "origWcs".
619 
620  cat: reference source catalog
621 
622  sources: sources in the image to be solved
623 
624  imageSize, x0, y0: these determine the bounding-box of the image,
625  which is used when finding reverse SIP coefficients.
626  '''
627  sipOrder = self.config.sipOrder
628  wcs = origWcs
629  bbox = afwGeom.Box2I(afwGeom.Point2I(x0,y0),
630  afwGeom.Extent2I(imageSize[0], imageSize[1]))
631 
632  i=0
633  lastScatPix = None
634  while True:
635  try:
636  sipObject = astromSip.makeCreateWcsWithSip(matchList, wcs, sipOrder, bbox)
637  if lastScatPix is None:
638  lastScatPix = sipObject.getLinearScatterInPixels()
639  proposedWcs = sipObject.getNewWcs()
640  scatPix = sipObject.getScatterInPixels()
641  self.plotSolution(matchList, proposedWcs, imageSize)
642  except pexExceptions.Exception as e:
643  self._warn('Failed to calculate distortion terms. Error: ' + str(e))
644  break
645 
646  matchSize = len(matchList)
647  # use new WCS to get new matchlist.
648  proposedMatchlist = self._getMatchList(sources, cat, proposedWcs)
649 
650  self._debug('SIP iteration %i: %i objects match. Median scatter is %g arcsec = %g pixels (vs previous: %i matches, %g pixels)' %
651  (i, len(proposedMatchlist), sipObject.getScatterOnSky().asArcseconds(), scatPix, matchSize, lastScatPix))
652  #self._debug('Proposed WCS: ' + proposedWcs.getFitsMetadata().toString())
653  # Hack convergence tests
654  if len(proposedMatchlist) < matchSize:
655  break
656  if len(proposedMatchlist) == matchSize and scatPix >= lastScatPix:
657  break
658 
659  wcs = proposedWcs
660  matchList = proposedMatchlist
661  lastScatPix = scatPix
662  matchSize = len(matchList)
663  i += 1
664 
665  return wcs, matchList
An integer coordinate rectangle.
Definition: Box.h:53
def lsst.meas.astrom.astrom.Astrometry._debug (   self,
  s 
)
private

Definition at line 202 of file astrom.py.

203  def _debug(self, s):
self.log.log(self.log.DEBUG, s)
def lsst.meas.astrom.astrom.Astrometry._getImageParams (   self,
  wcs,
  exposure,
  filterName = None,
  imageSize = None,
  x0 = None,
  y0 = None 
)
private

Definition at line 227 of file astrom.py.

228  x0=None, y0=None):
229  if exposure is not None:
230  ex0,ey0 = exposure.getX0(), exposure.getY0()
231  if x0 is None:
232  x0 = ex0
233  if y0 is None:
234  y0 = ey0
235  self._debug('Got exposure x0,y0 = %i,%i' % (ex0,ey0))
236  if filterName is None:
237  filterName = exposure.getFilter().getName()
238  self._debug('Setting filterName = "%s" from exposure metadata' % str(filterName))
239  if imageSize is None:
240  imageSize = (exposure.getWidth(), exposure.getHeight())
241  self._debug('Setting image size = (%i, %i) from exposure metadata' % (imageSize))
242  if x0 is None:
243  x0 = 0
244  if y0 is None:
245  y0 = 0
246  return filterName, imageSize, x0, y0
def lsst.meas.astrom.astrom.Astrometry._getIndexPath (   self,
  fn 
)
private

Definition at line 1002 of file astrom.py.

1003  def _getIndexPath(self, fn):
1004  if os.path.isabs(fn):
1005  return fn
1006  andir = os.getenv('ASTROMETRY_NET_DATA_DIR')
1007  if andir is not None:
1008  fn2 = os.path.join(andir, fn)
1009  if os.path.exists(fn2):
1010  return fn2
1011 
1012  if os.path.exists(fn):
1013  return os.path.abspath(fn)
1014  else:
1015  return None
def lsst.meas.astrom.astrom.Astrometry._getMatchList (   self,
  sources,
  cat,
  wcs 
)
private

Definition at line 744 of file astrom.py.

745  def _getMatchList(self, sources, cat, wcs):
746  dist = self.config.catalogMatchDist * afwGeom.arcseconds
747  clean = self.config.cleaningParameter
748  matcher = astromSip.MatchSrcToCatalogue(cat, sources, wcs, dist)
749  matchList = matcher.getMatches()
750  if matchList is None:
751  # Produce debugging stats...
752  X = [src.getX() for src in sources]
753  Y = [src.getY() for src in sources]
754  R1 = [src.getRa().asDegrees() for src in sources]
755  D1 = [src.getDec().asDegrees() for src in sources]
756  R2 = [src.getRa().asDegrees() for src in cat]
757  D2 = [src.getDec().asDegrees() for src in cat]
758  # for src in sources:
759  #self._debug("source: x,y (%.1f, %.1f), RA,Dec (%.3f, %.3f)" %
760  #(src.getX(), src.getY(), src.getRa().asDegrees(), src.getDec().asDegrees()))
761  #for src in cat:
762  #self._debug("ref: RA,Dec (%.3f, %.3f)" %
763  #(src.getRa().asDegrees(), src.getDec().asDegrees()))
764  self.loginfo('_getMatchList: %i sources, %i reference sources' % (len(sources), len(cat)))
765  if len(sources):
766  self.loginfo('Source range: x [%.1f, %.1f], y [%.1f, %.1f], RA [%.3f, %.3f], Dec [%.3f, %.3f]' %
767  (min(X), max(X), min(Y), max(Y), min(R1), max(R1), min(D1), max(D1)))
768  if len(cat):
769  self.loginfo('Reference range: RA [%.3f, %.3f], Dec [%.3f, %.3f]' %
770  (min(R2), max(R2), min(D2), max(D2)))
771  raise RuntimeError('No matches found between image and catalogue')
772  matchList = astromSip.cleanBadPoints.clean(matchList, wcs, nsigma=clean)
773  return matchList
double min
Definition: attributes.cc:216
double max
Definition: attributes.cc:218
def lsst.meas.astrom.astrom.Astrometry._getMIndexesWithinRange (   self,
  ra,
  dec,
  radius 
)
private
ra,dec,radius: [deg], spatial cut based on the healpix of the index

Returns list of multiindex objects within range.

Definition at line 1016 of file astrom.py.

1017  def _getMIndexesWithinRange(self, ra, dec, radius):
1018  '''
1019  ra,dec,radius: [deg], spatial cut based on the healpix of the index
1020 
1021  Returns list of multiindex objects within range.
1022  '''
1023  good = []
1024  for mi in self.multiInds:
1025  if mi.isWithinRange(ra, dec, radius):
1026  good.append(mi)
1027  return good
def lsst.meas.astrom.astrom.Astrometry._getSolver (   self)
private

Definition at line 1028 of file astrom.py.

1029  def _getSolver(self):
1030  import astrometry_net as an
1031  solver = an.solver_new()
1032  # HACK, set huge default pixel scale range.
1033  lo,hi = 0.01, 3600.
1034  solver.setPixelScaleRange(lo, hi)
1035  return solver
def lsst.meas.astrom.astrom.Astrometry._isGoodSource (   self,
  candsource,
  keys 
)
private

Definition at line 996 of file astrom.py.

997  def _isGoodSource(self, candsource, keys):
998  for k in keys:
999  if candsource.get(k):
1000  return False
1001  return True
def lsst.meas.astrom.astrom.Astrometry._readIndexFiles (   self)
private

Definition at line 148 of file astrom.py.

149  def _readIndexFiles(self):
150  import astrometry_net as an
151  # .multiInds: multi-index objects
152  self.multiInds = []
153 
154  # merge indexFiles and multiIndexFiles; we'll treat both as
155  # multiindex for simplicity.
156  mifiles = ([(True,[fn,fn]) for fn in self.andConfig.indexFiles] +
157  [(False,fns) for fns in self.andConfig.multiIndexFiles])
158 
159  nMissing = 0
160  for single,fns in mifiles:
161  # First filename in "fns" is star kdtree, the rest are index files.
162  fn = fns[0]
163  if single:
164  self.log.log(self.log.DEBUG, 'Adding index file %s' % fns[0])
165  else:
166  self.log.log(self.log.DEBUG, 'Adding multiindex files %s' % str(fns))
167  fn2 = self._getIndexPath(fn)
168  if fn2 is None:
169  if single:
170  self.log.logdebug('Unable to find index file %s' % fn)
171  else:
172  self.log.logdebug('Unable to find star part of multiindex file %s' % fn)
173  nMissing += 1
174  continue
175  fn = fn2
176  self.log.log(self.log.DEBUG, 'Path: %s' % fn)
177 
178  mi = an.multiindex_new(fn)
179  if mi is None:
180  raise RuntimeError('Failed to read stars from multiindex filename "%s"' % fn)
181  for i,fn in enumerate(fns[1:]):
182  self.log.log(self.log.DEBUG, 'Reading index from multiindex file "%s"' % fn)
183  fn2 = self._getIndexPath(fn)
184  if fn2 is None:
185  self.log.logdebug('Unable to find index part of multiindex file %s' % fn)
186  nMissing += 1
187  continue
188  fn = fn2
189  self.log.log(self.log.DEBUG, 'Path: %s' % fn)
190  if an.multiindex_add_index(mi, fn, an.INDEX_ONLY_LOAD_METADATA):
191  raise RuntimeError('Failed to read index from multiindex filename "%s"' % fn)
192  ind = mi[i]
193  self.log.log(self.log.DEBUG, ' index %i, hp %i (nside %i), nstars %i, nquads %i' %
194  (ind.indexid, ind.healpix, ind.hpnside, ind.nstars, ind.nquads))
195  an.multiindex_unload_starkd(mi)
196  self.multiInds.append(mi)
197 
198  if len(self.multiInds) == 0:
199  self.log.warn('Unable to find any index files')
200  elif nMissing > 0:
201  self.log.warn('Unable to find %d index files' % nMissing)
def lsst.meas.astrom.astrom.Astrometry._solve (   self,
  sources,
  wcs,
  imageSize,
  pixelScale,
  radecCenter,
  searchRadius,
  parity,
  filterName = None,
  xy0 = None 
)
private

Definition at line 891 of file astrom.py.

892  searchRadius, parity, filterName=None, xy0=None):
893  solver = self._getSolver()
894 
895  x0,y0 = 0,0
896  if xy0 is not None:
897  x0,y0 = xy0
898 
899  # select sources with valid x,y, flux
900  xybb = afwGeom.Box2D()
901  goodsources = afwTable.SourceCatalog(sources.table)
902  badkeys = [goodsources.getSchema().find(name).key for name in self.config.badFlags]
903 
904  for s in sources:
905  if np.isfinite(s.getX()) and np.isfinite(s.getY()) and np.isfinite(s.getPsfFlux()) and self._isGoodSource(s, badkeys) :
906  goodsources.append(s)
907  xybb.include(afwGeom.Point2D(s.getX() - x0, s.getY() - y0))
908  self.log.info("Number of selected sources for astrometry : %d" %(len(goodsources)))
909  if len(goodsources) < len(sources):
910  self.log.logdebug('Keeping %i of %i sources with finite X,Y positions and PSF flux' %
911  (len(goodsources), len(sources)))
912  self._debug(('Feeding sources in range x=[%.1f, %.1f], y=[%.1f, %.1f] ' +
913  '(after subtracting x0,y0 = %.1f,%.1f) to Astrometry.net') %
914  (xybb.getMinX(), xybb.getMaxX(), xybb.getMinY(), xybb.getMaxY(), x0, y0))
915  # setStars sorts them by PSF flux.
916  solver.setStars(goodsources, x0, y0)
917  solver.setMaxStars(self.config.maxStars)
918  solver.setImageSize(*imageSize)
919  solver.setMatchThreshold(self.config.matchThreshold)
920  raDecRadius = None
921  if radecCenter is not None:
922  raDecRadius = (radecCenter.getLongitude().asDegrees(),
923  radecCenter.getLatitude().asDegrees(),
924  searchRadius.asDegrees())
925  solver.setRaDecRadius(*raDecRadius)
926  self.log.logdebug('Searching for match around RA,Dec = (%g, %g) with radius %g deg' %
927  raDecRadius)
928 
929  if pixelScale is not None:
930  dscale = self.config.pixelScaleUncertainty
931  scale = pixelScale.asArcseconds()
932  lo = scale / dscale
933  hi = scale * dscale
934  solver.setPixelScaleRange(lo, hi)
935  self.log.logdebug('Searching for matches with pixel scale = %g +- %g %% -> range [%g, %g] arcsec/pix' %
936  (scale, 100.*(dscale-1.), lo, hi))
937 
938  if parity is not None:
939  solver.setParity(parity)
940  self.log.logdebug('Searching for match with parity = ' + str(parity))
941 
942  # Find and load index files within RA,Dec range and scale range.
943  if raDecRadius is not None:
944  multiInds = self._getMIndexesWithinRange(*raDecRadius)
945  else:
946  multiInds = self.multiInds
947  qlo,qhi = solver.getQuadSizeLow(), solver.getQuadSizeHigh()
948 
949  toload_multiInds = set()
950  toload_inds = []
951  for mi in multiInds:
952  for i in range(len(mi)):
953  ind = mi[i]
954  if not ind.overlapsScaleRange(qlo, qhi):
955  continue
956  toload_multiInds.add(mi)
957  toload_inds.append(ind)
958 
959  with Astrometry._LoadedMIndexes(toload_multiInds):
960  solver.addIndices(toload_inds)
961  self.memusage('Index files loaded: ')
962 
963  cpulimit = self.config.maxCpuTime
964  solver.run(cpulimit)
965 
966  self.memusage('Solving finished: ')
967 
968  self.memusage('Index files unloaded: ')
969 
970  if solver.didSolve():
971  self.log.logdebug('Solved!')
972  wcs = solver.getWcs()
973  self.log.logdebug('WCS: %s' % wcs.getFitsMetadata().toString())
974 
975  if x0 != 0 or y0 != 0:
976  wcs.shiftReferencePixel(x0, y0)
977  self.log.logdebug('After shifting reference pixel by x0,y0 = (%i,%i), WCS is: %s' %
978  (x0, y0, wcs.getFitsMetadata().toString()))
979 
980  else:
981  self.log.warn('Did not get an astrometric solution from Astrometry.net')
982  wcs = None
983  # Gather debugging info...
984 
985  # -are there any reference stars in the proposed search area?
986  if radecCenter is not None:
987  ra = radecCenter.getLongitude()
988  dec = radecCenter.getLatitude()
989  refs = self.getReferenceSources(ra, dec, searchRadius, filterName)
990  self.log.info('Searching around RA,Dec = (%g,%g) with radius %g deg yields %i reference-catalog sources' %
991  (ra.asDegrees(), dec.asDegrees(), searchRadius.asDegrees(), len(refs)))
992 
993  qa = solver.getSolveStats()
994  self.log.logdebug('qa: %s' % qa.toString())
995  return wcs, qa
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
def lsst.meas.astrom.astrom.Astrometry._trimBadPoints (   sources,
  bbox,
  wcs = None 
)
staticprivate
Remove elements from catalog whose xy positions are not within the given bbox.

sources:  a Catalog of SimpleRecord or SourceRecord objects
bbox: an afwImage.Box2D
wcs:  if not None, will be used to compute the xy positions on-the-fly;
      this is required when sources actually contains SimpleRecords.

Returns:
a list of Source objects with xAstrom,yAstrom within the bbox.

Definition at line 1037 of file astrom.py.

1038  def _trimBadPoints(sources, bbox, wcs=None):
1039  '''Remove elements from catalog whose xy positions are not within the given bbox.
1040 
1041  sources: a Catalog of SimpleRecord or SourceRecord objects
1042  bbox: an afwImage.Box2D
1043  wcs: if not None, will be used to compute the xy positions on-the-fly;
1044  this is required when sources actually contains SimpleRecords.
1045 
1046  Returns:
1047  a list of Source objects with xAstrom,yAstrom within the bbox.
1048  '''
1049  keep = type(sources)(sources.table)
1050  for s in sources:
1051  point = s.getCentroid() if wcs is None else wcs.skyToPixel(s.getCoord())
1052  if bbox.contains(point):
1053  keep.append(s)
1054  return keep
def lsst.meas.astrom.astrom.Astrometry._warn (   self,
  s 
)
private

Definition at line 204 of file astrom.py.

205  def _warn(self, s):
206  self.log.log(self.log.WARN, s)
def lsst.meas.astrom.astrom.Astrometry.determineWcs (   self,
  sources,
  exposure,
  kwargs 
)
Finds a WCS solution for the given 'sources' in the given
'exposure', getting other parameters from config.

Valid kwargs include:

'radecCenter', an afw.coord.Coord giving the RA,Dec position
   of the center of the field.  This is used to limit the
   search done by Astrometry.net (to make it faster and load
   fewer index files, thereby using less memory).  Defaults to
   the RA,Dec center from the exposure's WCS; turn that off
   with the boolean kwarg 'useRaDecCenter' or config option
   'useWcsRaDecCenter'

'useRaDecCenter', a boolean.  Don't use the RA,Dec center from
   the exposure's initial WCS.

'searchRadius', in degrees, to search for a solution around
   the given 'radecCenter'; default from config option
   'raDecSearchRadius'.

'useParity': parity is the 'flip' of the image.  Knowing it
   reduces the search space (hence time) for Astrometry.net.
   The parity can be computed from the exposure's WCS (the
   sign of the determinant of the CD matrix); this option
   controls whether we do that or force Astrometry.net to
   search both parities.  Default from config.useWcsParity.

'pixelScale': afwGeom.Angle, estimate of the angle-per-pixel
   (ie, arcseconds per pixel).  Defaults to a value derived
   from the exposure's WCS.  If enabled, this value, plus or
   minus config.pixelScaleUncertainty, will be used to limit
   Astrometry.net's search.

'usePixelScale': boolean.  Use the pixel scale to limit
   Astrometry.net's search?  Defaults to config.useWcsPixelScale.

'filterName', a string, the filter name of this image.  Will
   be mapped through the 'filterMap' config dictionary to a
   column name in the astrometry_net_data index FITS files.
   Defaults to the exposure.getFilter() value.

'imageSize', a tuple (W,H) of integers, the image size.
   Defaults to the exposure.get{Width,Height}() values.

Definition at line 346 of file astrom.py.

347  **kwargs):
348  """
349  Finds a WCS solution for the given 'sources' in the given
350  'exposure', getting other parameters from config.
351 
352  Valid kwargs include:
353 
354  'radecCenter', an afw.coord.Coord giving the RA,Dec position
355  of the center of the field. This is used to limit the
356  search done by Astrometry.net (to make it faster and load
357  fewer index files, thereby using less memory). Defaults to
358  the RA,Dec center from the exposure's WCS; turn that off
359  with the boolean kwarg 'useRaDecCenter' or config option
360  'useWcsRaDecCenter'
361 
362  'useRaDecCenter', a boolean. Don't use the RA,Dec center from
363  the exposure's initial WCS.
364 
365  'searchRadius', in degrees, to search for a solution around
366  the given 'radecCenter'; default from config option
367  'raDecSearchRadius'.
368 
369  'useParity': parity is the 'flip' of the image. Knowing it
370  reduces the search space (hence time) for Astrometry.net.
371  The parity can be computed from the exposure's WCS (the
372  sign of the determinant of the CD matrix); this option
373  controls whether we do that or force Astrometry.net to
374  search both parities. Default from config.useWcsParity.
375 
376  'pixelScale': afwGeom.Angle, estimate of the angle-per-pixel
377  (ie, arcseconds per pixel). Defaults to a value derived
378  from the exposure's WCS. If enabled, this value, plus or
379  minus config.pixelScaleUncertainty, will be used to limit
380  Astrometry.net's search.
381 
382  'usePixelScale': boolean. Use the pixel scale to limit
383  Astrometry.net's search? Defaults to config.useWcsPixelScale.
384 
385  'filterName', a string, the filter name of this image. Will
386  be mapped through the 'filterMap' config dictionary to a
387  column name in the astrometry_net_data index FITS files.
388  Defaults to the exposure.getFilter() value.
389 
390  'imageSize', a tuple (W,H) of integers, the image size.
391  Defaults to the exposure.get{Width,Height}() values.
392 
393  """
394  assert(exposure is not None)
395 
396  margs = kwargs.copy()
397  if not 'searchRadius' in margs:
398  margs.update(searchRadius = self.config.raDecSearchRadius * afwGeom.degrees)
399  if not 'usePixelScale' in margs:
400  margs.update(usePixelScale = self.config.useWcsPixelScale)
401  if not 'useRaDecCenter' in margs:
402  margs.update(useRaDecCenter = self.config.useWcsRaDecCenter)
403  if not 'useParity' in margs:
404  margs.update(useParity = self.config.useWcsParity)
405  margs.update(exposure=exposure)
406  return self.determineWcs2(sources, **margs)
def lsst.meas.astrom.astrom.Astrometry.determineWcs2 (   self,
  sources,
  kwargs 
)
Get a blind astrometric solution for the given list of sources.

We need:
  -the image size;
  -the filter

And if available, we can use:
  -an initial Wcs estimate;
     --> RA,Dec center
     --> pixel scale
     --> "parity"
     
(all of which are metadata of Exposure).

filterName: string
imageSize: (W,H) integer tuple/iterable
pixelScale: afwGeom::Angle per pixel.
radecCenter: afwCoord::Coord

Definition at line 407 of file astrom.py.

408  def determineWcs2(self, sources, **kwargs):
409  '''
410  Get a blind astrometric solution for the given list of sources.
411 
412  We need:
413  -the image size;
414  -the filter
415 
416  And if available, we can use:
417  -an initial Wcs estimate;
418  --> RA,Dec center
419  --> pixel scale
420  --> "parity"
421 
422  (all of which are metadata of Exposure).
423 
424  filterName: string
425  imageSize: (W,H) integer tuple/iterable
426  pixelScale: afwGeom::Angle per pixel.
427  radecCenter: afwCoord::Coord
428  '''
429  wcs,qa = self.getBlindWcsSolution(sources, **kwargs)
430  kw = {}
431  # Keys passed to useKnownWcs
432  for key in ['exposure', 'filterName', 'imageSize', 'x0', 'y0']:
433  if key in kwargs:
434  kw[key] = kwargs[key]
435  astrom = self.useKnownWcs(sources, wcs=wcs, **kw)
436  astrom.solveQa = qa
437  astrom.tanWcs = wcs
438  return astrom
def lsst.meas.astrom.astrom.Astrometry.getBlindWcsSolution (   self,
  sources,
  exposure = None,
  wcs = None,
  imageSize = None,
  x0 = None,
  y0 = None,
  radecCenter = None,
  searchRadius = None,
  pixelScale = None,
  filterName = None,
  doTrim = False,
  usePixelScale = True,
  useRaDecCenter = True,
  useParity = True,
  searchRadiusScale = 2. 
)

Definition at line 452 of file astrom.py.

453  searchRadiusScale=2.):
454  if not useRaDecCenter and radecCenter is not None:
455  raise RuntimeError('radecCenter is set, but useRaDecCenter is False. Make up your mind!')
456  if not usePixelScale and pixelScale is not None:
457  raise RuntimeError('pixelScale is set, but usePixelScale is False. Make up your mind!')
458 
459  filterName,imageSize,x0,y0 = self._getImageParams(exposure=exposure, wcs=wcs,
460  imageSize=imageSize,
461  filterName=filterName,
462  x0=x0, y0=y0)
463 
464  if exposure is not None:
465  if wcs is None:
466  wcs = exposure.getWcs()
467  self._debug('Setting initial WCS estimate from exposure metadata')
468 
469  if imageSize is None:
470  # Could guess from the extent of the Sources...
471  raise RuntimeError('Image size must be specified by passing "exposure" or "imageSize"')
472 
473  W,H = imageSize
474  xc, yc = W/2. + 0.5 + x0, H/2. + 0.5 + y0
475  parity = None
476 
477  if wcs is not None:
478  if pixelScale is None:
479  if usePixelScale:
480  pixelScale = wcs.pixelScale()
481  self._debug('Setting pixel scale estimate = %.3f from given WCS estimate' %
482  (pixelScale.asArcseconds()))
483 
484  if radecCenter is None:
485  if useRaDecCenter:
486  radecCenter = wcs.pixelToSky(xc, yc)
487  self._debug(('Setting RA,Dec center estimate = (%.3f, %.3f) from given WCS '
488  + 'estimate, using pixel center = (%.1f, %.1f)') %
489  (radecCenter.getLongitude().asDegrees(),
490  radecCenter.getLatitude().asDegrees(), xc, yc))
491 
492  if searchRadius is None:
493  if useRaDecCenter:
494  assert(pixelScale is not None)
495  searchRadius = (pixelScale * math.hypot(W,H)/2. *
496  searchRadiusScale)
497  self._debug(('Using RA,Dec search radius = %.3f deg, from pixel scale, '
498  + 'image size, and searchRadiusScale = %g') %
499  (searchRadius, searchRadiusScale))
500  if useParity:
501  parity = wcs.isFlipped()
502  self._debug('Using parity = %s' % (parity and 'True' or 'False'))
503 
504  if doTrim:
505  n = len(sources)
506  if exposure is not None:
507  bbox = afwGeom.Box2D(exposure.getMaskedImage().getBBox())
508  else:
509  # CHECK -- half-pixel issues here?
510  bbox = afwGeom.Box2D(afwGeom.Point2D(0.,0.), afwGeom.Point2D(W, H))
511  sources = self._trimBadPoints(sources, bbox)
512  self._debug("Trimming: kept %i of %i sources" % (n, len(sources)))
513 
514  wcs,qa = self._solve(sources, wcs, imageSize, pixelScale, radecCenter, searchRadius, parity,
515  filterName, xy0=(x0,y0))
516  if wcs is None:
517  raise RuntimeError("Unable to match sources with catalog.")
518  self.log.info('Got astrometric solution from Astrometry.net')
519 
520  rdc = wcs.pixelToSky(xc, yc)
521  self._debug('New WCS says image center pixel (%.1f, %.1f) -> RA,Dec (%.3f, %.3f)' %
522  (xc, yc, rdc.getLongitude().asDegrees(), rdc.getLatitude().asDegrees()))
523  return wcs,qa
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
def lsst.meas.astrom.astrom.Astrometry.getCatalogFilterName (   self,
  filterName 
)
Deprecated method for retrieving the magnitude column name from the filter name

Definition at line 791 of file astrom.py.

792  def getCatalogFilterName(self, filterName):
793  """Deprecated method for retrieving the magnitude column name from the filter name"""
794  return self.getColumnName(filterName, self.andConfig.magColumnMap, self.andConfig.defaultMagColumn)
795 
def lsst.meas.astrom.astrom.Astrometry.getColumnName (   self,
  filterName,
  columnMap,
  default = None 
)
Returns the column name in the astrometry_net_data index file that will be used
for the given filter name.

@param filterName   Name of filter used in exposure
@param columnMap    Dict that maps filter names to column names
@param default      Default column name

Definition at line 774 of file astrom.py.

775  def getColumnName(self, filterName, columnMap, default=None):
776  '''
777  Returns the column name in the astrometry_net_data index file that will be used
778  for the given filter name.
779 
780  @param filterName Name of filter used in exposure
781  @param columnMap Dict that maps filter names to column names
782  @param default Default column name
783  '''
784  filterName = self.config.filterMap.get(filterName, filterName) # Exposure filter --> desired filter
785  try:
786  return columnMap[filterName] # Desired filter --> a_n_d column name
787  except KeyError:
788  self.log.warn("No column in configuration for filter '%s'; using default '%s'" %
789  (filterName, default))
790  return default
def lsst.meas.astrom.astrom.Astrometry.getReferenceSources (   self,
  ra,
  dec,
  radius,
  filterName 
)
Searches for reference-catalog sources (in the
astrometry_net_data files) in the requested RA,Dec region
(afwGeom::Angle objects), with the requested radius (also an
Angle).  The flux values will be set based on the requested
filter (None => default filter).

Returns: an lsst.afw.table.SimpleCatalog of reference objects

Definition at line 816 of file astrom.py.

817  def getReferenceSources(self, ra, dec, radius, filterName):
818  '''
819  Searches for reference-catalog sources (in the
820  astrometry_net_data files) in the requested RA,Dec region
821  (afwGeom::Angle objects), with the requested radius (also an
822  Angle). The flux values will be set based on the requested
823  filter (None => default filter).
824 
825  Returns: an lsst.afw.table.SimpleCatalog of reference objects
826  '''
827  sgCol = self.andConfig.starGalaxyColumn
828  varCol = self.andConfig.variableColumn
829  idcolumn = self.andConfig.idColumn
830 
831  magCol = self.getColumnName(filterName, self.andConfig.magColumnMap, self.andConfig.defaultMagColumn)
832  magerrCol = self.getColumnName(filterName, self.andConfig.magErrorColumnMap,
833  self.andConfig.defaultMagErrorColumn)
834 
835  if self.config.allFluxes:
836  names = []
837  mcols = []
838  ecols = []
839  if magCol:
840  names.append('flux')
841  mcols.append(magCol)
842  ecols.append(magerrCol)
843 
844  for col,mcol in self.andConfig.magColumnMap.items():
845  names.append(col)
846  mcols.append(mcol)
847  ecols.append(self.andConfig.magErrorColumnMap.get(col, ''))
848  margs = (names, mcols, ecols)
849 
850  else:
851  margs = (magCol, magerrCol)
852 
853  '''
854  Note about multiple astrometry_net index files and duplicate IDs:
855 
856  -as of astrometry_net 0.30, we take a reference catalog and build
857  a set of astrometry_net index files from it, with each one covering a
858  region of sky and a range of angular scales. The index files covering
859  the same region of sky at different scales use exactly the same stars.
860  Therefore, if we search every index file, we will get multiple copies of
861  each reference star (one from each index file).
862  For now, we have the "unique_ids" option to solver.getCatalog().
863  -recall that the index files to be used are specified in the
864  AstrometryNetDataConfig.indexFiles flat list.
865 
866  -as of astrometry_net 0.40, we have the capability to share
867  the reference stars between index files (called
868  "multiindex"), so we will no longer have to repeat the
869  reference stars in each index. We will, however, have to
870  change the way the index files are configured to take
871  advantage of this functionality. Once this is in place, we
872  can eliminate the horrid ID checking and deduplication (in solver.getCatalog()).
873  '''
874 
875  solver = self._getSolver()
876 
877  # Find multi-index files within range
878  raDecRadius = (ra.asDegrees(), dec.asDegrees(), radius.asDegrees())
879  multiInds = self._getMIndexesWithinRange(*raDecRadius)
880 
881  with Astrometry._LoadedMIndexes(multiInds):
882  # We just want to pass the star kd-trees, so just pass the
883  # first element of each multi-index.
884  inds = [mi[0] for mi in multiInds]
885 
886  cat = solver.getCatalog(*((inds,) + raDecRadius + (idcolumn,)
887  + margs + (sgCol, varCol)))
888  del solver
889  return cat
def lsst.meas.astrom.astrom.Astrometry.getReferenceSourcesForWcs (   self,
  wcs,
  imageSize,
  filterName,
  pixelMargin = 50,
  x0 = 0,
  y0 = 0,
  trim = True 
)

Definition at line 796 of file astrom.py.

797  def getReferenceSourcesForWcs(self, wcs, imageSize, filterName, pixelMargin=50, x0=0, y0=0, trim=True):
798  W,H = imageSize
799  xc, yc = W/2. + 0.5, H/2. + 0.5
800  rdc = wcs.pixelToSky(x0 + xc, y0 + yc)
801  ra,dec = rdc.getLongitude(), rdc.getLatitude()
802  self._debug('Getting reference sources using center: pixel (%.1f, %.1f) -> RA,Dec (%.3f, %.3f)' %
803  (xc, yc, ra.asDegrees(), dec.asDegrees()))
804  pixelScale = wcs.pixelScale()
805  rad = pixelScale * (math.hypot(W,H)/2. + pixelMargin)
806  self._debug('Getting reference sources using radius of %.3g deg' % rad.asDegrees())
807  cat = self.getReferenceSources(ra, dec, rad, filterName)
808  # NOTE: reference objects don't have (x,y) anymore, so we can't apply WCS to set x,y positions
809  if trim:
810  # cut to image bounds + margin.
811  bbox = afwGeom.Box2D(afwGeom.Point2D(x0, y0), afwGeom.Extent2D(W, H))
812  bbox.grow(pixelMargin)
813  cat = self._trimBadPoints(cat, bbox, wcs=wcs) # passing wcs says to compute x,y on-the-fly
814  return cat
815 
A floating-point coordinate rectangle geometry.
Definition: Box.h:271
def lsst.meas.astrom.astrom.Astrometry.getSipWcsFromCorrespondences (   self,
  origWcs,
  cat,
  sources,
  imageSize,
  x0 = 0,
  y0 = 0 
)
Produces a SIP solution given a list of known correspondences.
Unlike _calculateSipTerms, this does not iterate the solution;
it assumes you have given it a good sets of corresponding stars.

NOTE that "cat" and "sources" are assumed to be the same length;
entries "cat[i]" and "sources[i]" are assumed to be correspondences.

origWcs: the WCS to linearize in order to get the TAN part of the
   TAN-SIP WCS.

cat: reference source catalog

sources: image sources

imageSize, x0, y0: these describe the bounding-box of the image,
    which is used when computing reverse SIP polynomials.

Definition at line 577 of file astrom.py.

578  x0=0, y0=0):
579  '''
580  Produces a SIP solution given a list of known correspondences.
581  Unlike _calculateSipTerms, this does not iterate the solution;
582  it assumes you have given it a good sets of corresponding stars.
583 
584  NOTE that "cat" and "sources" are assumed to be the same length;
585  entries "cat[i]" and "sources[i]" are assumed to be correspondences.
586 
587  origWcs: the WCS to linearize in order to get the TAN part of the
588  TAN-SIP WCS.
589 
590  cat: reference source catalog
591 
592  sources: image sources
593 
594  imageSize, x0, y0: these describe the bounding-box of the image,
595  which is used when computing reverse SIP polynomials.
596 
597  '''
598  sipOrder = self.config.sipOrder
599  bbox = afwGeom.Box2I(afwGeom.Point2I(x0,y0),
600  afwGeom.Extent2I(imageSize[0], imageSize[1]))
601  matchList = []
602  for ci,si in zip(cat, sources):
603  matchList.append(afwTable.ReferenceMatch(ci, si, 0.))
604 
605  sipObject = astromSip.makeCreateWcsWithSip(matchList, origWcs, sipOrder, bbox)
606  return sipObject.getNewWcs()
An integer coordinate rectangle.
Definition: Box.h:53
Lightweight representation of a geometric match between two records.
Definition: fwd.h:90
def lsst.meas.astrom.astrom.Astrometry.getSipWcsFromWcs (   self,
  wcs,
  imageSize,
  x0 = 0,
  y0 = 0,
  ngrid = 20,
  linearizeAtCenter = True 
)
This function allows one to get a TAN-SIP WCS, starting from
an existing WCS.  It uses your WCS to compute a fake grid of
corresponding "stars" in pixel and sky coords, and feeds that
to the regular SIP code.

linearizeCenter: if True, get a linear approximation of the input
  WCS at the image center and use that as the TAN initialization for
  the TAN-SIP solution.  You probably want this if your WCS has its
  CRPIX outside the image bounding box.

Definition at line 525 of file astrom.py.

526  linearizeAtCenter=True):
527  '''
528  This function allows one to get a TAN-SIP WCS, starting from
529  an existing WCS. It uses your WCS to compute a fake grid of
530  corresponding "stars" in pixel and sky coords, and feeds that
531  to the regular SIP code.
532 
533  linearizeCenter: if True, get a linear approximation of the input
534  WCS at the image center and use that as the TAN initialization for
535  the TAN-SIP solution. You probably want this if your WCS has its
536  CRPIX outside the image bounding box.
537 
538  '''
539  # Ugh, build src and ref tables
540  srcSchema = afwTable.SourceTable.makeMinimalSchema()
541  key = srcSchema.addField("centroid", type="PointD")
542  srcTable = afwTable.SourceTable.make(srcSchema)
543  srcTable.defineCentroid("centroid")
544  srcs = srcTable
545  refs = afwTable.SimpleTable.make(afwTable.SimpleTable.makeMinimalSchema())
546  cref = []
547  csrc = []
548  (W,H) = imageSize
549  for xx in np.linspace(0., W, ngrid):
550  for yy in np.linspace(0, H, ngrid):
551  src = srcs.makeRecord()
552  src.set(key.getX(), x0 + xx)
553  src.set(key.getY(), y0 + yy)
554  csrc.append(src)
555  rd = wcs.pixelToSky(afwGeom.Point2D(xx + x0, yy + y0))
556  ref = refs.makeRecord()
557  ref.setCoord(rd)
558  cref.append(ref)
559 
560  if linearizeAtCenter:
561  # Linearize the original WCS around the image center to create a
562  # TAN WCS.
563  # Reference pixel in LSST coords
564  crpix = afwGeom.Point2D(x0 + W/2. - 0.5, y0 + H/2. - 0.5)
565  crval = wcs.pixelToSky(crpix)
566  crval = crval.getPosition(afwGeom.degrees)
567  # Linearize *AT* crval to get effective CD at crval.
568  # (we use the default skyUnit of degrees as per WCS standard)
569  aff = wcs.linearizePixelToSky(crval)
570  cd = aff.getLinear().getMatrix()
571  wcs = afwImage.Wcs(crval, crpix, cd)
572 
573  return self.getSipWcsFromCorrespondences(wcs, cref, csrc, (W,H),
574  x0=x0, y0=y0)
575 
Implementation of the WCS standard for a any projection.
Definition: Wcs.h:107
def lsst.meas.astrom.astrom.Astrometry.joinMatchListWithCatalog (   self,
  packedMatches,
  sourceCat 
)
This function is required to reconstitute a ReferenceMatchVector after being
unpersisted.  The persisted form of a ReferenceMatchVector is the 
normalized Catalog of IDs produced by afw.table.packMatches(), with the result of 
InitialAstrometry.getMatchMetadata() in the associated tables\' metadata.

The "live" form of a matchlist has links to
the real record objects that are matched; it is "denormalized".
This function takes a normalized match catalog, along with the catalog of
sources to which the match catalog refers.  It fetches the reference
sources that are within range, and then denormalizes the matches
-- sets the "matchList[*].first" and "matchList[*].second" entries
to point to the sources in the "sources" argument, and to the
reference sources fetched from the astrometry_net_data files.
    
@param[in] packedMatches  Unpersisted match list (an lsst.afw.table.BaseCatalog).
                  packedMatches.table.getMetadata() must contain the
                  values from InitialAstrometry.getMatchMetadata()
@param[in,out] sourceCat  Source catalog used for the 'second' side of the matches
                  (an lsst.afw.table.SourceCatalog).  As a side effect,
                  the catalog will be sorted by ID.

@return An lsst.afw.table.ReferenceMatchVector of denormalized matches.

Definition at line 1055 of file astrom.py.

1056  def joinMatchListWithCatalog(self, packedMatches, sourceCat):
1057  '''
1058  This function is required to reconstitute a ReferenceMatchVector after being
1059  unpersisted. The persisted form of a ReferenceMatchVector is the
1060  normalized Catalog of IDs produced by afw.table.packMatches(), with the result of
1061  InitialAstrometry.getMatchMetadata() in the associated tables\' metadata.
1062 
1063  The "live" form of a matchlist has links to
1064  the real record objects that are matched; it is "denormalized".
1065  This function takes a normalized match catalog, along with the catalog of
1066  sources to which the match catalog refers. It fetches the reference
1067  sources that are within range, and then denormalizes the matches
1068  -- sets the "matchList[*].first" and "matchList[*].second" entries
1069  to point to the sources in the "sources" argument, and to the
1070  reference sources fetched from the astrometry_net_data files.
1071 
1072  @param[in] packedMatches Unpersisted match list (an lsst.afw.table.BaseCatalog).
1073  packedMatches.table.getMetadata() must contain the
1074  values from InitialAstrometry.getMatchMetadata()
1075  @param[in,out] sourceCat Source catalog used for the 'second' side of the matches
1076  (an lsst.afw.table.SourceCatalog). As a side effect,
1077  the catalog will be sorted by ID.
1078 
1079  @return An lsst.afw.table.ReferenceMatchVector of denormalized matches.
1080  '''
1081  matchmeta = packedMatches.table.getMetadata()
1082  version = matchmeta.getInt('SMATCHV')
1083  if version != 1:
1084  raise ValueError('SourceMatchVector version number is %i, not 1.' % version)
1085  filterName = matchmeta.getString('FILTER').strip()
1086  # all in deg.
1087  ra = matchmeta.getDouble('RA') * afwGeom.degrees
1088  dec = matchmeta.getDouble('DEC') * afwGeom.degrees
1089  rad = matchmeta.getDouble('RADIUS') * afwGeom.degrees
1090  self.log.logdebug('Searching RA,Dec %.3f,%.3f, radius %.1f arcsec, filter "%s"' %
1091  (ra.asDegrees(), dec.asDegrees(), rad.asArcseconds(), filterName))
1092  refCat = self.getReferenceSources(ra, dec, rad, filterName)
1093  self.log.logdebug('Found %i reference catalog sources in range' % len(refCat))
1094  refCat.sort()
1095  sourceCat.sort()
1096  return afwTable.unpackMatches(packedMatches, refCat, sourceCat)
1097 
std::vector< Match< typename Cat1::Record, typename Cat2::Record > > unpackMatches(BaseCatalog const &matches, Cat1 const &cat1, Cat2 const &cat2)
Reconstruct a MatchVector from a BaseCatalog representation of the matches and a pair of catalogs...
def lsst.meas.astrom.astrom.Astrometry.memusage (   self,
  prefix = '' 
)

Definition at line 207 of file astrom.py.

208  def memusage(self, prefix=''):
209  # Not logging at DEBUG: do nothing
210  if self.log.getThreshold() > pexLog.Log.DEBUG:
211  return
212  from astrometry.util.ttime import get_memusage
213  mu = get_memusage()
214  ss = []
215  for key in ['VmPeak', 'VmSize', 'VmRSS', 'VmData']:
216  if key in mu:
217  ss.append(key + ': ' + ' '.join(mu[key]))
218  if 'mmaps' in mu:
219  ss.append('Mmaps: %i' % len(mu['mmaps']))
220  if 'mmaps_total' in mu:
221  ss.append('Mmaps: %i kB' % (mu['mmaps_total'] / 1024))
222  self.log.logdebug(prefix + 'Memory: ' + ', '.join(ss))
def lsst.meas.astrom.astrom.Astrometry.plotSolution (   self,
  matchList,
  wcs,
  imageSize 
)
Plot the solution, when debugging is turned on.

@param matchList   The list of matches
@param wcs         The Wcs
@param imageSize   2-tuple with the image size (W,H)

Definition at line 666 of file astrom.py.

667  def plotSolution(self, matchList, wcs, imageSize):
668  """Plot the solution, when debugging is turned on.
669 
670  @param matchList The list of matches
671  @param wcs The Wcs
672  @param imageSize 2-tuple with the image size (W,H)
673  """
674  import lsstDebug
675  display = lsstDebug.Info(__name__).display
676  if not display:
677  return
678 
679  try:
680  import matplotlib.pyplot as plt
681  import numpy
682  except ImportError:
683  print >> sys.stderr, "Unable to import matplotlib"
684  return
685 
686  fig = plt.figure(1)
687  fig.clf()
688  try:
689  fig.canvas._tkcanvas._root().lift() # == Tk's raise, but raise is a python reserved word
690  except: # protect against API changes
691  pass
692 
693  num = len(matchList)
694  x = numpy.zeros(num)
695  y = numpy.zeros(num)
696  dx = numpy.zeros(num)
697  dy = numpy.zeros(num)
698  for i, m in enumerate(matchList):
699  x[i] = m.second.getX()
700  y[i] = m.second.getY()
701  pixel = wcs.skyToPixel(m.first.getCoord())
702  dx[i] = x[i] - pixel.getX()
703  dy[i] = y[i] - pixel.getY()
704 
705  subplots = maUtils.makeSubplots(fig, 2, 2, xgutter=0.1, ygutter=0.1, pygutter=0.04)
706 
707  def plotNext(x, y, xLabel, yLabel, xMax):
708  ax = subplots.next()
709  ax.set_autoscalex_on(False)
710  ax.set_xbound(lower=0, upper=xMax)
711  ax.scatter(x, y)
712  ax.set_xlabel(xLabel)
713  ax.set_ylabel(yLabel)
714  ax.axhline(0.0)
715 
716  plotNext(x, dx, "x", "dx", imageSize[0])
717  plotNext(x, dy, "x", "dy", imageSize[0])
718  plotNext(y, dx, "y", "dx", imageSize[1])
719  plotNext(y, dy, "y", "dy", imageSize[1])
720 
721  fig.show()
722 
723  while True:
724  try:
725  reply = raw_input("Pausing for inspection, enter to continue... [hpQ] ").strip()
726  except EOFError:
727  reply = "n"
728 
729  reply = reply.split()
730  if reply:
731  reply = reply[0]
732  else:
733  reply = ""
734 
735  if reply in ("", "h", "p", "Q"):
736  if reply == "h":
737  print "h[elp] p[db] Q[uit]"
738  continue
739  elif reply == "p":
740  import pdb; pdb.set_trace()
741  elif reply == "Q":
742  sys.exit(1)
743  break
def lsst.meas.astrom.astrom.Astrometry.setAndConfig (   self,
  andconfig 
)

Definition at line 223 of file astrom.py.

224  def setAndConfig(self, andconfig):
225  self.andConfig = andconfig
def lsst.meas.astrom.astrom.Astrometry.useKnownWcs (   self,
  sources,
  wcs = None,
  exposure = None,
  filterName = None,
  imageSize = None,
  x0 = None,
  y0 = None 
)
Returns an InitialAstrometry object, just like determineWcs,
but assuming the given input WCS is correct.

This is enabled by the pipe_tasks AstrometryConfig
'forceKnownWcs' option.  If you are using that option, you
probably also want to turn OFF 'calculateSip'.

This involves searching for reference sources within the WCS
area, and matching them to the given 'sources'.  If
'calculateSip' is set, we will try to compute a TAN-SIP
distortion correction.

sources: list of detected sources in this image.
wcs: your known WCS
exposure: the exposure holding metadata for this image.
filterName: string, filter name, eg "i"
x0,y0: image origin / offset; these coordinates along with the
   "imageSize" determine the bounding-box in pixel coordinates of
   the image in question; this is used for finding reference sources
   in the image, among other things.

You MUST provide a WCS, either by providing the 'wcs' kwarg
(an lsst.image.Wcs object), or by providing the 'exposure' on
which we will call 'exposure.getWcs()'.

You MUST provide a filter name, either by providing the
'filterName' kwarg (a string), or by setting the 'exposure';
we will call 'exposure.getFilter().getName()'.

You MUST provide the image size, either by providing the
'imageSize' kwargs, an (W,H) tuple of ints, or by providing
the 'exposure'; we will call 'exposure.getWidth()' and
'exposure.getHeight()'.

Note, when modifying this function, that it is also called by
'determineWcs' (via 'determineWcs2'), since the steps are all
the same.

Definition at line 247 of file astrom.py.

248  def useKnownWcs(self, sources, wcs=None, exposure=None, filterName=None, imageSize=None, x0=None, y0=None):
249  """
250  Returns an InitialAstrometry object, just like determineWcs,
251  but assuming the given input WCS is correct.
252 
253  This is enabled by the pipe_tasks AstrometryConfig
254  'forceKnownWcs' option. If you are using that option, you
255  probably also want to turn OFF 'calculateSip'.
256 
257  This involves searching for reference sources within the WCS
258  area, and matching them to the given 'sources'. If
259  'calculateSip' is set, we will try to compute a TAN-SIP
260  distortion correction.
261 
262  sources: list of detected sources in this image.
263  wcs: your known WCS
264  exposure: the exposure holding metadata for this image.
265  filterName: string, filter name, eg "i"
266  x0,y0: image origin / offset; these coordinates along with the
267  "imageSize" determine the bounding-box in pixel coordinates of
268  the image in question; this is used for finding reference sources
269  in the image, among other things.
270 
271  You MUST provide a WCS, either by providing the 'wcs' kwarg
272  (an lsst.image.Wcs object), or by providing the 'exposure' on
273  which we will call 'exposure.getWcs()'.
274 
275  You MUST provide a filter name, either by providing the
276  'filterName' kwarg (a string), or by setting the 'exposure';
277  we will call 'exposure.getFilter().getName()'.
278 
279  You MUST provide the image size, either by providing the
280  'imageSize' kwargs, an (W,H) tuple of ints, or by providing
281  the 'exposure'; we will call 'exposure.getWidth()' and
282  'exposure.getHeight()'.
283 
284  Note, when modifying this function, that it is also called by
285  'determineWcs' (via 'determineWcs2'), since the steps are all
286  the same.
287  """
288  # return value:
289  astrom = InitialAstrometry()
290 
291  if wcs is None:
292  if exposure is None:
293  raise RuntimeError('useKnownWcs: need either "wcs=" or "exposure=" kwarg.')
294  wcs = exposure.getWcs()
295  if wcs is None:
296  raise RuntimeError('useKnownWcs: wcs==None and exposure.getWcs()==None.')
297 
298  filterName,imageSize,x0,y0 = self._getImageParams(exposure=exposure, wcs=wcs,
299  imageSize=imageSize,
300  filterName=filterName,
301  x0=x0, y0=y0)
302  pixelMargin = 50.
303 
304  cat = self.getReferenceSourcesForWcs(wcs, imageSize, filterName, pixelMargin, x0=x0, y0=y0)
305  catids = [src.getId() for src in cat]
306  uids = set(catids)
307  self.log.logdebug('%i reference sources; %i unique IDs' % (len(catids), len(uids)))
308  matchList = self._getMatchList(sources, cat, wcs)
309  uniq = set([sm.second.getId() for sm in matchList])
310  if len(matchList) != len(uniq):
311  self._warn(('The list of matched stars contains duplicate reference source IDs ' +
312  '(%i sources, %i unique ids)') % (len(matchList), len(uniq)))
313  if len(matchList) == 0:
314  self._warn('No matches found between input sources and reference catalogue.')
315  return astrom
316 
317  self._debug('%i reference objects match input sources using input WCS' % (len(matchList)))
318  astrom.tanMatches = matchList
319  astrom.tanWcs = wcs
320 
321  srcids = [s.getId() for s in sources]
322  for m in matchList:
323  assert(m.second.getId() in srcids)
324  assert(m.second in sources)
325 
326  if self.config.calculateSip:
327  sipwcs,matchList = self._calculateSipTerms(wcs, cat, sources, matchList, imageSize, x0=x0, y0=y0)
328  if sipwcs == wcs:
329  self._debug('Failed to find a SIP WCS better than the initial one.')
330  else:
331  self._debug('%i reference objects match input sources using SIP WCS' % (len(matchList)))
332  astrom.sipWcs = sipwcs
333  astrom.sipMatches = matchList
334 
335  W,H = imageSize
336  wcs = astrom.getWcs()
337  # _getMatchList() modifies the source list RA,Dec coordinates.
338  # Here, we make them consistent with the WCS we are returning.
339  for src in sources:
340  src.updateCoord(wcs)
341  astrom.matchMetadata = _createMetadata(W, H, x0, y0, wcs, filterName)
342  return astrom

Member Data Documentation

lsst.meas.astrom.astrom.Astrometry.andConfig

Definition at line 145 of file astrom.py.

lsst.meas.astrom.astrom.Astrometry.config

Definition at line 126 of file astrom.py.

lsst.meas.astrom.astrom.Astrometry.ConfigClass = MeasAstromConfig
static

Definition at line 75 of file astrom.py.

lsst.meas.astrom.astrom.Astrometry.log

Definition at line 128 of file astrom.py.

lsst.meas.astrom.astrom.Astrometry.multiInds

Definition at line 151 of file astrom.py.


The documentation for this class was generated from the following file: