LSSTApplications  17.0+103,17.0+11,17.0+61,18.0.0+13,18.0.0+25,18.0.0+5,18.0.0+52,18.0.0-4-g68ffd23,18.1.0-1-g0001055+8,18.1.0-1-g03d53ef+1,18.1.0-1-g1349e88+28,18.1.0-1-g2505f39+22,18.1.0-1-g380d4d4+27,18.1.0-1-g5315e5e+1,18.1.0-1-g5e4b7ea+10,18.1.0-1-g7e8fceb+1,18.1.0-1-g85f8cd4+23,18.1.0-1-g9a6769a+13,18.1.0-1-ga1a4c1a+22,18.1.0-1-gd55f500+17,18.1.0-12-g42eabe8e+10,18.1.0-14-gd04256d+15,18.1.0-16-g430f6a53+1,18.1.0-17-gd2166b6e4,18.1.0-18-gb5d19ff+1,18.1.0-2-gfbf3545+7,18.1.0-2-gfefb8b5+16,18.1.0-3-g52aa583+13,18.1.0-3-g62b5e86+14,18.1.0-3-g8f4a2b1+17,18.1.0-3-g9bc06b8+7,18.1.0-3-gb69f684+9,18.1.0-4-g1ee41a7+1,18.1.0-5-g6dbcb01+13,18.1.0-5-gc286bb7+3,18.1.0-6-g48bdcd3+2,18.1.0-6-gd05e160+9,18.1.0-7-gc4d902b+2,18.1.0-7-gebc0338+8,18.1.0-9-gae7190a+10,w.2019.38
LSSTDataManagementBasePackage
multiindex.py
Go to the documentation of this file.
1 __all__ = ["getIndexPath", "getConfigFromEnvironment", "AstrometryNetCatalog", "generateCache"]
2 
3 import os
4 
5 import numpy as np
6 from astropy.io import fits
7 
8 import lsst.utils
9 from lsst.log import Log
10 from .astrometry_net import MultiIndex, healpixDistance
11 from .astrometryNetDataConfig import AstrometryNetDataConfig
12 
13 
14 def getIndexPath(fn):
15  """!Get the path to the specified astrometry.net index file
16 
17  No effort is made to confirm that the file exists, so it may be used to
18  locate the path to a non-existent file (e.g., to write).
19 
20  @param[in] fn path to index file; if relative, then relative to
21  astrometry_net_data if that product is setup, else relative to the
22  current working directory
23  @return the absolute path to the index file
24  """
25  if os.path.isabs(fn):
26  return fn
27  try:
28  andir = lsst.utils.getPackageDir('astrometry_net_data')
29  except Exception:
30  # Relative to cwd
31  return os.path.abspath(fn)
32  return os.path.join(andir, fn)
33 
34 
36  """Find the config file from the environment
37 
38  The andConfig.py file is in the astrometry_net_data directory.
39  """
40  try:
41  anDir = lsst.utils.getPackageDir('astrometry_net_data')
42  except Exception:
43  anDir = os.getcwd()
44  andConfigPath = "andConfig.py"
45  if not os.path.exists(andConfigPath):
46  raise RuntimeError("Unable to find andConfig.py in the current directory. "
47  "Did you forget to setup astrometry_net_data?")
48  else:
49  andConfigPath = os.path.join(anDir, "andConfig.py")
50  if not os.path.exists(andConfigPath):
51  raise RuntimeError("Unable to find andConfig.py in astrometry_net_data directory %s" % (anDir,))
52 
53  andConfig = AstrometryNetDataConfig()
54  andConfig.load(andConfigPath)
55  return andConfig
56 
57 
59  """A wrapper for the multiindex_t, which only reads the data when it
60  needs to
61 
62  The MultiIndexCache may be instantiated directly, or via the
63  'fromFilenameList' class method, which loads it from a list of filenames.
64  """
65 
66  def __init__(self, filenameList, healpix, nside):
67  """!Constructor
68 
69  @param filenameList List of filenames; first is the multiindex, then
70  follows the individual index files
71  @param healpix Healpix number
72  @param nside Healpix nside
73  """
74  if len(filenameList) < 2:
75  raise RuntimeError("Insufficient filenames provided for multiindex (%s): expected >= 2" %
76  (filenameList,))
77  self._filenameList = filenameList
78  self._healpix = int(healpix)
79  self._nside = int(nside)
80  self._mi = None
81  self._loaded = False
82  self.log = Log.getDefaultLogger()
83 
84  @classmethod
85  def fromFilenameList(cls, filenameList):
86  """Construct from a list of filenames
87 
88  The list of filenames should contain the multiindex filename first,
89  then the individual index filenames. The healpix and nside are
90  determined by reading the indices, so this is not very efficient.
91  """
92  self = cls(filenameList, 0, 0)
93  self.reload()
94  healpixes = set(self[i].healpix for i in range(len(self)))
95  nsides = set(self[i].hpnside for i in range(len(self)))
96  assert len(healpixes) == 1
97  assert len(nsides) == 1
98  self._healpix = healpixes.pop()
99  self._nside = nsides.pop()
100  return self
101 
102  def read(self):
103  """Read the indices"""
104  if self._mi is not None:
105  return
106  fn = getIndexPath(self._filenameList[0])
107  if not os.path.exists(fn):
108  raise RuntimeError(
109  "Unable to get filename for astrometry star file %s" % (self._filenameList[0],))
110  self._mi = MultiIndex(fn)
111  if self._mi is None:
112  # Can't proceed at all without stars
113  raise RuntimeError('Failed to read stars from astrometry multiindex filename "%s"' % fn)
114  for i, fn in enumerate(self._filenameList[1:]):
115  if fn is None:
116  self.log.debug('Unable to find index part of multiindex %s', fn)
117  continue
118  fn = getIndexPath(fn)
119  if not os.path.exists(fn):
120  self.log.warn("Unable to get filename for astrometry index %s", fn)
121  continue
122  self.log.debug('Reading index from multiindex file "%s"', fn)
123  self._mi.addIndex(fn, False)
124  ind = self._mi[i]
125  self.log.debug(' index %i, hp %i (nside %i), nstars %i, nquads %i',
126  ind.indexid, ind.healpix, ind.hpnside, ind.nstars, ind.nquads)
127 
128  def reload(self):
129  """Reload the indices."""
130  if self._loaded:
131  return
132  if self._mi is None:
133  self.read()
134  else:
135  self._mi.reload()
136  self._loaded = True
137 
138  def unload(self):
139  """Unload the indices"""
140  if not self._loaded:
141  return
142  self._mi.unload()
143  self._loaded = False
144 
145  def isWithinRange(self, coord, distance):
146  """!Is the index within range of the provided coordinates?
147 
148  @param coord ICRS coordinate to check (lsst.geom.SpherPoint)
149  @param distance Angular distance (lsst.geom.Angle)
150  """
151  return (self._healpix == -1 or healpixDistance(self._healpix, self._nside, coord) <= distance)
152 
153  def __getitem__(self, i):
154  self.reload()
155  return self._mi[i]
156 
157  def __len__(self):
158  return len(self._filenameList) - 1 # The first is the multiindex; the rest are the indices
159 
160  def __iter__(self):
161  self.reload()
162  return iter(self._mi)
163 
164 
166  """An interface to an astrometry.net catalog
167 
168  Behaves like a list of MultiIndexCache (or multiindex_t).
169 
170  These should usually be constructed using the 'fromEnvironment'
171  class method, which wraps the 'fromIndexFiles' and 'fromCache'
172  alternative class methods.
173  """
174  _cacheFilename = "andCache.fits"
175 
176  def __init__(self, andConfig):
177  """!Constructor
178 
179  @param andConfig Configuration (an AstrometryNetDataConfig)
180  """
181  self.config = andConfig
182  cacheName = getIndexPath(self._cacheFilename)
183  if self.config.allowCache and os.path.exists(cacheName):
184  self._initFromCache(cacheName)
185  else:
186  self._initFromIndexFiles(self.config)
187 
188  def _initFromIndexFiles(self, andConfig):
189  """Initialise from the index files in an AstrometryNetDataConfig"""
190  indexFiles = list(zip(andConfig.indexFiles, andConfig.indexFiles)) + andConfig.multiIndexFiles
191  self._multiInds = [MultiIndexCache.fromFilenameList(fnList) for fnList in indexFiles]
192 
193  def writeCache(self):
194  """Write a cache file
195 
196  The cache file is a FITS file with all the required information to
197  build the AstrometryNetCatalog quickly. The first table extension
198  contains a row for each multiindex, storing the healpix and nside
199  values. The second table extension contains a row for each filename
200  in all the multiindexes. The two may be JOINed through the 'id'
201  column.
202  """
203  outName = getIndexPath(self._cacheFilename)
204  numFilenames = sum(len(ind._filenameList) for ind in self._multiInds)
205  maxLength = max(len(fn) for ind in self._multiInds for fn in ind._filenameList) + 1
206 
207  # First table
208  first = fits.BinTableHDU.from_columns([fits.Column(name="id", format="K"),
209  fits.Column(name="healpix", format="K"),
210  fits.Column(name="nside", format="K"),
211  ], nrows=len(self._multiInds))
212  first.data.field("id")[:] = np.arange(len(self._multiInds), dtype=int)
213  first.data.field("healpix")[:] = np.array([ind._healpix for ind in self._multiInds])
214  first.data.field("nside")[:] = np.array([ind._nside for ind in self._multiInds])
215 
216  # Second table
217  second = fits.BinTableHDU.from_columns([fits.Column(name="id", format="K"),
218  fits.Column(name="filename", format="%dA" % (maxLength)),
219  ], nrows=numFilenames)
220  ident = second.data.field("id")
221  filenames = second.data.field("filename")
222  i = 0
223  for j, ind in enumerate(self._multiInds):
224  for fn in ind._filenameList:
225  ident[i] = j
226  filenames[i] = fn
227  i += 1
228 
229  fits.HDUList([fits.PrimaryHDU(), first, second]).writeto(outName, overwrite=True)
230 
231  def _initFromCache(self, filename):
232  """Initialise from a cache file
233 
234  Ingest the cache file written by the 'writeCache' method and
235  use that to quickly instantiate the AstrometryNetCatalog.
236  """
237  with fits.open(filename) as hduList:
238  first = hduList[1].data
239  second = hduList[2].data
240 
241  # first JOIN second USING(id)
242  filenames = {i: [] for i in first.field("id")}
243  for id2, fn in zip(second.field("id"), second.field("filename")):
244  filenames[id2].append(fn)
245  self._multiInds = [MultiIndexCache(filenames[i], hp, nside) for i, hp, nside in
246  zip(first.field("id"), first.field("healpix"), first.field("nside"))]
247 
248  # Check for consistency
249  cacheFiles = set(second.field("filename"))
250  configFiles = set(sum(self.config.multiIndexFiles, []) + self.config.indexFiles)
251  assert(cacheFiles == configFiles)
252 
253  def __getitem__(self, ii):
254  return self._multiInds[ii]
255 
256  def __iter__(self):
257  return iter(self._multiInds)
258 
259  def __len__(self):
260  return len(self._multiInds)
261 
262 
263 def generateCache(andConfig=None):
264  """Generate a cache file"""
265  if andConfig is None:
266  andConfig = getConfigFromEnvironment()
267  catalog = AstrometryNetCatalog(andConfig)
268  try:
269  for index in catalog:
270  index.reload()
271  catalog.writeCache()
272  finally:
273  for index in catalog:
274  index.unload()
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
daf::base::PropertySet * set
Definition: fits.cc:884
def isWithinRange(self, coord, distance)
Is the index within range of the provided coordinates?
Definition: multiindex.py:145
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package
Definition: packaging.cc:33
Definition: Log.h:706
def getIndexPath(fn)
Get the path to the specified astrometry.net index file.
Definition: multiindex.py:14
lsst::geom::Angle healpixDistance(int hp, int nside, lsst::geom::SpherePoint const &coord)
Calculate the distance from coordinates to a healpix.
def __init__(self, filenameList, healpix, nside)
Constructor.
Definition: multiindex.py:66
int max
daf::base::PropertyList * list
Definition: fits.cc:885