LSSTApplications  1.1.2+25,10.0+13,10.0+132,10.0+133,10.0+224,10.0+41,10.0+8,10.0-1-g0f53050+14,10.0-1-g4b7b172+19,10.0-1-g61a5bae+98,10.0-1-g7408a83+3,10.0-1-gc1e0f5a+19,10.0-1-gdb4482e+14,10.0-11-g3947115+2,10.0-12-g8719d8b+2,10.0-15-ga3f480f+1,10.0-2-g4f67435,10.0-2-gcb4bc6c+26,10.0-28-gf7f57a9+1,10.0-3-g1bbe32c+14,10.0-3-g5b46d21,10.0-4-g027f45f+5,10.0-4-g86f66b5+2,10.0-4-gc4fccf3+24,10.0-40-g4349866+2,10.0-5-g766159b,10.0-5-gca2295e+25,10.0-6-g462a451+1
LSSTDataManagementBasePackage
loadAstrometryNetObjects.py
Go to the documentation of this file.
1 from __future__ import absolute_import, division, print_function
2 
3 import os
4 
5 import lsst.pipe.base as pipeBase
6 from lsst.meas.algorithms import LoadReferenceObjectsTask, getRefFluxField
7 from . import astrometry_net as astromNet
8 from .astrometryNetDataConfig import AstrometryNetDataConfig
9 
10 __all__ = ["LoadAstrometryNetObjectsTask", "LoadAstrometryNetObjectsConfig"]
11 
12 LoadAstrometryNetObjectsConfig = LoadReferenceObjectsTask.ConfigClass
13 
14 # The following block adds links to this task from the Task Documentation page.
15 ## \addtogroup LSST_task_documentation
16 ## \{
17 ## \page measAstrom_loadAstrometryNetObjectsTask
18 ## \ref LoadAstrometryNetObjectsTask "LoadAstrometryNetObjectsTask"
19 ## Load reference objects from astrometry.net index files
20 ## \}
21 
22 class LoadAstrometryNetObjectsTask(LoadReferenceObjectsTask):
23  """!Load reference objects from astrometry.net index files
24 
25  @anchor LoadAstrometryNetObjectsTask_
26 
27  @section meas_astrom_loadAstrometryNetObjects_Contents Contents
28 
29  - @ref meas_astrom_loadAstrometryNetObjects_Purpose
30  - @ref meas_astrom_loadAstrometryNetObjects_Initialize
31  - @ref meas_astrom_loadAstrometryNetObjects_IO
32  - @ref meas_algorithms_loadReferenceObjects_Schema
33  - @ref meas_astrom_loadAstrometryNetObjects_Config
34  - @ref meas_astrom_loadAstrometryNetObjects_Example
35  - @ref meas_astrom_loadAstrometryNetObjects_Debug
36 
37  @section meas_astrom_loadAstrometryNetObjects_Purpose Description
38 
39  Load reference objects from astrometry.net index files.
40 
41  @section meas_astrom_loadAstrometryNetObjects_Initialize Task initialisation
42 
43  @copydoc \_\_init\_\_
44 
45  @section meas_astrom_loadAstrometryNetObjects_IO Invoking the Task
46 
47  @copydoc loadObjectsInBBox
48 
49  @section meas_astrom_loadAstrometryNetObjects_Config Configuration parameters
50 
51  See @ref LoadAstrometryNetObjectsConfig
52 
53  @section meas_astrom_loadAstrometryNetObjects_Example A complete example of using LoadAstrometryNetObjectsTask
54 
55  LoadAstrometryNetObjectsTask is a subtask of AstrometryTask, which is called by PhotoCalTask.
56  See \ref meas_photocal_photocal_Example.
57 
58  @section meas_astrom_loadAstrometryNetObjects_Debug Debug variables
59 
60  LoadAstrometryNetObjectsTask does not support any debug variables.
61  """
62  ConfigClass = LoadAstrometryNetObjectsConfig
63 
64  def __init__(self, config, andConfig=None, **kwargs):
65  """!Create a LoadAstrometryNetObjectsTask
66 
67  @param[in] config configuration (an instance of self.ConfigClass)
68  @param[in] andConfig astrometry.net data config (an instance of AstromNetDataConfig, or None);
69  if None then use andConfig.py in the astrometry_net_data product (which must be setup)
70 
71  @throw RuntimeError if andConfig is None and the configuration cannot be found,
72  either because astrometry_net_data is not setup in eups
73  or because the setup version does not include the file "andConfig.py"
74  """
75  LoadReferenceObjectsTask.__init__(self, config=config, **kwargs)
76  self.andConfig = andConfig
77  self.haveIndexFiles = False # defer reading index files until we know they are needed
78  # because astrometry may not be used, in which case it may not be properly configured
79 
80  @pipeBase.timeMethod
81  def loadSkyCircle(self, ctrCoord, radius, filterName=None):
82  """!Load reference objects that overlap a circular sky region
83 
84  @param[in] ctrCoord center of search region (an afwGeom.Coord)
85  @param[in] radius radius of search region (an afwGeom.Angle)
86  @param[in] filterName name of filter, or None for the default filter;
87  used for flux values in case we have flux limits (which are not yet implemented)
88 
89  @return an lsst.pipe.base.Struct containing:
90  - refCat a catalog of reference objects with the
91  \link meas_algorithms_loadReferenceObjects_Schema standard schema \endlink
92  as documented in LoadReferenceObjects, including photometric, resolved and variable;
93  hasCentroid is False for all objects.
94  - fluxField = name of flux field for specified filterName
95  """
96  self._readIndexFiles()
97 
98  names = []
99  mcols = []
100  ecols = []
101  for col, mcol in self.andConfig.magColumnMap.items():
102  names.append(col)
103  mcols.append(mcol)
104  ecols.append(self.andConfig.magErrorColumnMap.get(col, ''))
105  margs = (names, mcols, ecols)
106 
107  solver = self._getSolver()
108 
109  # Find multi-index files within range
110  multiInds = self._getMIndexesWithinRange(ctrCoord, radius)
111 
112  # compute solver.getCatalog arguments that follow the list of star kd-trees:
113  # - center equatorial angle (e.g. RA) in deg
114  # - center polar angle (e.g. Dec) in deg
115  # - radius, in deg
116  # - idColumn
117  # - (margs)
118  # - star-galaxy column
119  # - variability column
120  fixedArgTuple = (
121  ctrCoord,
122  radius,
123  self.andConfig.idColumn,
124  ) + margs + (
125  self.andConfig.starGalaxyColumn,
126  self.andConfig.variableColumn,
127  True, # eliminate duplicate IDs
128  )
129 
130  self.log.info("search for objects at %s with radius %s deg" % (ctrCoord, radius.asDegrees()))
131  with LoadMultiIndexes(multiInds):
132  # We just want to pass the star kd-trees, so just pass the
133  # first element of each multi-index.
134  inds = tuple(mi[0] for mi in multiInds)
135  refCat = solver.getCatalog(inds, *fixedArgTuple)
136 
137  self._addFluxAliases(schema=refCat.schema)
138 
139  fluxField = getRefFluxField(schema=refCat.schema, filterName=filterName)
140 
141  self.log.info("found %d objects" % (len(refCat),))
142  return pipeBase.Struct(
143  refCat = refCat,
144  fluxField = fluxField,
145  )
146 
147  @pipeBase.timeMethod
148  def _readIndexFiles(self):
149  """!Read all astrometry.net index files, if not already read
150  """
151  if self.haveIndexFiles:
152  return
153 
154  self.log.info("read index files")
155 
156  self.multiInds = []
157  self.haveIndexFiles = True # just try once
158 
159  if self.andConfig is None:
160  # use andConfig.py in the astrometry_net product setup in eups
161  anDir = os.environ.get('ASTROMETRY_NET_DATA_DIR')
162  if anDir is None:
163  raise RuntimeError("astrometry_net_data is not setup")
164 
165  andConfig = AstrometryNetDataConfig()
166  andConfigPath = os.path.join(anDir, "andConfig.py")
167  if not os.path.exists(andConfigPath):
168  raise RuntimeError("astrometry_net_data config file \"%s\" required but not found" %
169  andConfigPath)
170  andConfig.load(andConfigPath)
171  self.andConfig = andConfig
172 
173  # merge indexFiles and multiIndexFiles; we'll treat both as multiindex for simplicity.
174  mifiles = [(True, [fn,fn]) for fn in self.andConfig.indexFiles] + \
175  [(False, fns) for fns in self.andConfig.multiIndexFiles]
176 
177  nMissing = 0
178  for single, fns in mifiles:
179  # First filename in "fns" is star kdtree, the rest are index files.
180  fn = fns[0]
181  if single:
182  self.log.log(self.log.DEBUG, 'Adding index file %s' % fns[0])
183  else:
184  self.log.log(self.log.DEBUG, 'Adding multiindex files %s' % str(fns))
185  fn2 = self._getIndexPath(fn)
186  if fn2 is None:
187  if single:
188  self.log.logdebug('Unable to find index file %s' % fn)
189  else:
190  self.log.logdebug('Unable to find star part of multiindex file %s' % fn)
191  nMissing += 1
192  continue
193  fn = fn2
194  self.log.log(self.log.DEBUG, 'Path: %s' % fn)
195 
196  mi = astromNet.multiindex_new(fn)
197  if mi is None:
198  raise RuntimeError('Failed to read objects from multiindex filename "%s"' % fn)
199  for i,fn in enumerate(fns[1:]):
200  self.log.log(self.log.DEBUG, 'Reading index from multiindex file "%s"' % fn)
201  fn2 = self._getIndexPath(fn)
202  if fn2 is None:
203  self.log.logdebug('Unable to find index part of multiindex file %s' % fn)
204  nMissing += 1
205  continue
206  fn = fn2
207  self.log.log(self.log.DEBUG, 'Path: %s' % fn)
208  if astromNet.multiindex_add_index(mi, fn, astromNet.INDEX_ONLY_LOAD_METADATA):
209  raise RuntimeError('Failed to read index from multiindex filename "%s"' % fn)
210  ind = mi[i]
211  self.log.log(self.log.DEBUG, ' index %i, hp %i (nside %i), nstars %i, nquads %i' %
212  (ind.indexid, ind.healpix, ind.hpnside, ind.nstars, ind.nquads))
213  astromNet.multiindex_unload_starkd(mi)
214  self.multiInds.append(mi)
215 
216  if len(self.multiInds) == 0:
217  self.log.warn('Unable to find any index files')
218  elif nMissing > 0:
219  self.log.warn('Unable to find %d index files' % (nMissing,))
220 
221  def _getIndexPath(self, fn):
222  """!Get the path to the specified astrometry.net index file
223 
224  @param[in] fn path to index file; if relative, then relative to astrometry_net_data
225  if that product is setup, else relative to the current working directory
226  @return the absolute path to the index file, or None if the file was not found
227  """
228  if os.path.isabs(fn):
229  absFn = fn
230  else:
231  anDir = os.environ.get('ASTROMETRY_NET_DATA_DIR')
232  if anDir is not None:
233  absFn = os.path.join(anDir, fn)
234 
235  if os.path.exists(absFn):
236  return os.path.abspath(absFn)
237  else:
238  return None
239 
240  def _getMIndexesWithinRange(self, ctrCoord, radius):
241  """!Get list of muti-index objects within range
242 
243  @param[in] ctrCoord center of search region (an afwGeom.Coord)
244  @param[in] radius radius of search region (an afwGeom.Angle)
245 
246  @return list of multiindex objects
247  """
248  longDeg = ctrCoord.getLongitude().asDegrees()
249  latDeg = ctrCoord.getLatitude().asDegrees()
250  multiIndexList = []
251  for mi in self.multiInds:
252  if mi.isWithinRange(longDeg, latDeg, radius.asDegrees()):
253  multiIndexList.append(mi)
254  return multiIndexList
255 
256  def _getSolver(self):
257  solver = astromNet.solver_new()
258  # HACK, set huge default pixel scale range.
259  lo,hi = 0.01, 3600.
260  solver.setPixelScaleRange(lo, hi)
261  return solver
262 
263 
264 class LoadMultiIndexes(object):
265  """Context manager for loading astrometry.net multi-index files, then unloading and finalizing a.net
266  """
267  def __init__(self, multiInds):
268  self.multiInds = multiInds
269  def __enter__(self):
270  for mi in self.multiInds:
271  mi.reload()
272  return self.multiInds
273  def __exit__(self, typ, val, trace):
274  for mi in self.multiInds:
275  mi.unload()
276  astromNet.finalize()
def _getIndexPath
Get the path to the specified astrometry.net index file.
def getRefFluxField
Get name of flux field in schema.
Load reference objects from astrometry.net index files.
def _readIndexFiles
Read all astrometry.net index files, if not already read.
def loadSkyCircle
Load reference objects that overlap a circular sky region.