LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
mapping.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 # Copyright 2008, 2009, 2010 LSST Corporation.
4 #
5 # This product includes software developed by the
6 # LSST Project (http://www.lsst.org/).
7 #
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the LSST License Statement and
19 # the GNU General Public License along with this program. If not,
20 # see <http://www.lsstcorp.org/LegalNotices/>.
21 #
22 
23 from builtins import zip
24 from builtins import object
25 import os
26 import re
27 from lsst.daf.persistence import ButlerLocation
28 from lsst.daf.persistence.policy import Policy
29 import lsst.pex.policy as pexPolicy
30 
31 """This module defines the Mapping base class."""
32 
33 
34 class Mapping(object):
35 
36  """Mapping is a base class for all mappings. Mappings are used by
37  the Mapper to map (determine a path to some data given some
38  identifiers) and standardize (convert data into some standard
39  format or type) data, and to query the associated registry to see
40  what data is available.
41 
42  Subclasses must specify self.storage or else override self.map().
43 
44  Public methods: lookup, have, need, getKeys, map
45 
46  Mappings are specified mainly by policy. A Mapping policy should
47  consist of:
48 
49  template (string): a Python string providing the filename for that
50  particular dataset type based on some data identifiers. In the
51  case of redundancy in the path (e.g., file uniquely specified by
52  the exposure number, but filter in the path), the
53  redundant/dependent identifiers can be looked up in the registry.
54 
55  python (string): the Python type for the retrieved data (e.g.
56  lsst.afw.image.ExposureF)
57 
58  persistable (string): the Persistable registration for the on-disk data
59  (e.g. ImageU)
60 
61  storage (string, optional): Storage type for this dataset type (e.g.
62  "BoostStorage")
63 
64  level (string, optional): the level in the camera hierarchy at which the
65  data is stored (Amp, Ccd or skyTile), if relevant
66 
67  tables (string, optional): a whitespace-delimited list of tables in the
68  registry that can be NATURAL JOIN-ed to look up additional
69  information. """
70 
71  def __init__(self, datasetType, policy, registry, root, provided=None):
72  """Constructor for Mapping class.
73  @param datasetType (string)
74  @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
75  Mapping Policy
76  @param registry (lsst.daf.butlerUtils.Registry) Registry for metadata lookups
77  @param root (string) Path of root directory
78  @param provided (list of strings) Keys provided by the mapper
79  """
80 
81  if policy is None:
82  raise RuntimeError("No policy provided for mapping")
83 
84  if isinstance(policy, pexPolicy.Policy):
85  policy = Policy(policy)
86 
87  self.datasetType = datasetType
88  self.registry = registry
89  self.root = root
90 
91  self.template = policy['template'] # Template path
92  self.keyDict = dict([
93  (k, _formatMap(v, k, datasetType))
94  for k, v in
95  re.findall(r'\%\((\w+)\).*?([diouxXeEfFgGcrs])', self.template)
96  ])
97  if provided is not None:
98  for p in provided:
99  if p in self.keyDict:
100  del self.keyDict[p]
101  self.python = policy['python'] # Python type
102  self.persistable = policy['persistable'] # Persistable type
103  self.storage = policy['storage']
104  if 'level' in policy:
105  self.level = policy['level'] # Level in camera hierarchy
106  if 'tables' in policy:
107  self.tables = policy.asArray('tables')
108  else:
109  self.tables = None
110  self.range = None
111  self.columns = None
112  self.obsTimeName = policy['obsTimeName'] if 'obsTimeName' in policy else None
113 
114  def keys(self):
115  """Return the dict of keys and value types required for this mapping."""
116  return self.keyDict
117 
118  def map(self, mapper, dataId, write=False):
119  """Standard implementation of map function.
120  @param mapper (lsst.daf.persistence.Mapper)
121  @param dataId (dict) Dataset identifier
122  @return (lsst.daf.persistence.ButlerLocation)"""
123 
124  actualId = self.need(iter(self.keyDict.keys()), dataId)
125  path = mapper._mapActualToPath(self.template, actualId)
126  if not os.path.isabs(path):
127  path = os.path.join(self.root, path)
128  if not write:
129  newPath = mapper._parentSearch(path)
130  if newPath:
131  path = newPath
132  assert path, "Fully-qualified filename is empty."
133 
134  addFunc = "add_" + self.datasetType # Name of method for additionalData
135  if hasattr(mapper, addFunc):
136  addFunc = getattr(mapper, addFunc)
137  additionalData = addFunc(actualId)
138  assert isinstance(additionalData, dict), "Bad type for returned data"
139  else:
140  additionalData = actualId.copy()
141 
142  return ButlerLocation(self.python, self.persistable, self.storage, path, additionalData, mapper)
143 
144  def lookup(self, properties, dataId):
145  """Look up properties for in a metadata registry given a partial
146  dataset identifier.
147  @param properties (list of strings)
148  @param dataId (dict) Dataset identifier
149  @return (list of tuples) values of properties"""
150 
151  if self.registry is None:
152  raise RuntimeError("No registry for lookup")
153 
154  where = []
155  values = []
156  fastPath = True
157  for p in properties:
158  if p not in ('filter', 'expTime', 'taiObs'):
159  fastPath = False
160  break
161  if fastPath and 'visit' in dataId and "raw" in self.tables:
162  lookupDataId = {'visit': dataId['visit']}
163  self.registry.lookup(properties, 'raw_visit', lookupDataId)
164  if dataId is not None:
165  for k, v in dataId.items():
166  if self.columns and k not in self.columns:
167  continue
168  if k == self.obsTimeName:
169  continue
170  where.append((k, '?'))
171  values.append(v)
172 
173  lookupDataId = {k[0]: v for k, v in zip(where, values)}
174  if self.range:
175  # format of self.range is ('?', isBetween-lowKey, isBetween-highKey)
176  # here we transform that to {(lowKey, highKey): value}
177  lookupDataId[(self.range[1], self.range[2])] = dataId[self.obsTimeName]
178  return self.registry.lookup(properties, self.tables, lookupDataId)
179 
180  def have(self, properties, dataId):
181  """Returns whether the provided data identifier has all
182  the properties in the provided list.
183  @param properties (list of strings) Properties required
184  @parm dataId (dict) Dataset identifier
185  @return (bool) True if all properties are present"""
186  for prop in properties:
187  if prop not in dataId:
188  return False
189  return True
190 
191  def need(self, properties, dataId):
192  """Ensures all properties in the provided list are present in
193  the data identifier, looking them up as needed. This is only
194  possible for the case where the data identifies a single
195  exposure.
196  @param properties (list of strings) Properties required
197  @param dataId (dict) Partial dataset identifier
198  @return (dict) copy of dataset identifier with enhanced values
199  """
200  newId = dataId.copy()
201  newProps = [] # Properties we don't already have
202  for prop in properties:
203  if prop not in newId:
204  newProps.append(prop)
205  if len(newProps) == 0:
206  return newId
207 
208  lookups = self.lookup(newProps, newId)
209  if len(lookups) != 1:
210  raise RuntimeError("No unique lookup for %s from %s: %d matches" %
211  (newProps, newId, len(lookups)))
212  for i, prop in enumerate(newProps):
213  newId[prop] = lookups[0][i]
214  return newId
215 
216 
217 def _formatMap(ch, k, datasetType):
218  """Convert a format character into a Python type."""
219  if ch in "diouxX":
220  return int
221  elif ch in "eEfFgG":
222  return float
223  elif ch in "crs":
224  return str
225  else:
226  raise RuntimeError("Unexpected format specifier %s"
227  " for field %s in template for dataset %s" %
228  (ch, k, datasetType))
229 
230 
232  """ImageMapping is a Mapping subclass for non-camera images."""
233 
234  def __init__(self, datasetType, policy, registry, root, **kwargs):
235  """Constructor for Mapping class.
236  @param datasetType (string)
237  @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
238  Mapping Policy
239  @param registry (lsst.daf.butlerUtils.Registry) Registry for metadata lookups
240  @param root (string) Path of root directory"""
241  if isinstance(policy, pexPolicy.Policy):
242  policy = Policy(policy)
243  Mapping.__init__(self, datasetType, policy, registry, root, **kwargs)
244  self.columns = policy.asArray('columns') if 'columns' in policy else None
245 
246 
248  """ExposureMapping is a Mapping subclass for normal exposures."""
249 
250  def __init__(self, datasetType, policy, registry, root, **kwargs):
251  """Constructor for Mapping class.
252  @param datasetType (string)
253  @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
254  Mapping Policy
255  @param registry (lsst.daf.butlerUtils.Registry) Registry for metadata lookups
256  @param root (string) Path of root directory"""
257  if isinstance(policy, pexPolicy.Policy):
258  policy = Policy(policy)
259  Mapping.__init__(self, datasetType, policy, registry, root, **kwargs)
260  self.columns = policy.asArray('columns') if 'columns' in policy else None
261 
262  def standardize(self, mapper, item, dataId):
263  return mapper._standardizeExposure(self, item, dataId)
264 
265 
267  """CalibrationMapping is a Mapping subclass for calibration-type products.
268 
269  The difference is that data properties in the query or template
270  can be looked up using a reference Mapping in addition to this one.
271 
272  CalibrationMapping Policies can contain the following:
273 
274  reference (string, optional): a list of tables for finding missing dataset
275  identifier components (including the observation time, if a validity range
276  is required) in the exposure registry; note that the "tables" entry refers
277  to the calibration registry
278 
279  refCols (string, optional): a list of dataset properties required from the
280  reference tables for lookups in the calibration registry
281 
282  validRange (bool): true if the calibration dataset has a validity range
283  specified by a column in the tables of the reference dataset in the
284  exposure registry) and two columns in the tables of this calibration
285  dataset in the calibration registry)
286 
287  obsTimeName (string, optional): the name of the column in the reference
288  dataset tables containing the observation time (default "taiObs")
289 
290  validStartName (string, optional): the name of the column in the
291  calibration dataset tables containing the start of the validity range
292  (default "validStart")
293 
294  validEndName (string, optional): the name of the column in the
295  calibration dataset tables containing the end of the validity range
296  (default "validEnd") """
297 
298  def __init__(self, datasetType, policy, registry, calibRegistry, calibRoot, **kwargs):
299  """Constructor for Mapping class.
300  @param datasetType (string)
301  @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
302  Mapping Policy
303  @param registry (lsst.daf.butlerUtils.Registry) Registry for metadata lookups
304  @param calibRegistry (lsst.daf.butlerUtils.Registry) Registry for calibration metadata lookups
305  @param calibRoot (string) Path of calibration root directory"""
306  if isinstance(policy, pexPolicy.Policy):
307  policy = Policy(policy)
308  Mapping.__init__(self, datasetType, policy, calibRegistry, calibRoot, **kwargs)
309  self.reference = policy.asArray("reference") if "reference" in policy else None
310  self.refCols = policy.asArray("refCols") if "refCols" in policy else None
311  self.refRegistry = registry
312  if "validRange" in policy and policy["validRange"]:
313  self.range = ("?", policy["validStartName"], policy["validEndName"])
314  if "columns" in policy:
315  self.columns = policy.asArray("columns")
316  if "filter" in policy:
317  self.setFilter = policy["filter"]
318  self.metadataKeys = None
319  if "metadataKey" in policy:
320  self.metadataKeys = policy.asArray("metadataKey")
321 
322  def lookup(self, properties, dataId):
323  """Look up properties for in a metadata registry given a partial
324  dataset identifier.
325  @param properties (list of strings)
326  @param dataId (dict) Dataset identifier
327  @return (list of tuples) values of properties"""
328 
329 # Either look up taiObs in reference and then all in calibRegistry
330 # Or look up all in registry
331 
332  newId = dataId.copy()
333  if self.reference is not None:
334  where = []
335  values = []
336  for k, v in dataId.items():
337  if self.refCols and k not in self.refCols:
338  continue
339  where.append(k)
340  values.append(v)
341 
342  # Columns we need from the regular registry
343  if self.columns is not None:
344  columns = set(self.columns)
345  for k in dataId.keys():
346  columns.discard(k)
347  else:
348  columns = set(properties)
349 
350  if not columns:
351  # Nothing to lookup in reference registry; continue with calib registry
352  return Mapping.lookup(self, properties, newId)
353 
354  lookupDataId = dict(zip(where, values))
355  lookups = self.refRegistry.lookup(columns, self.reference, lookupDataId)
356  if len(lookups) != 1:
357  raise RuntimeError("No unique lookup for %s from %s: %d matches" %
358  (columns, dataId, len(lookups)))
359  if columns == set(properties):
360  # Have everything we need
361  return lookups
362  for i, prop in enumerate(columns):
363  newId[prop] = lookups[0][i]
364  return Mapping.lookup(self, properties, newId)
365 
366  def standardize(self, mapper, item, dataId):
367  return mapper._standardizeExposure(self, item, dataId, filter=self.setFilter)
368 
369 
371  """DatasetMapping is a Mapping subclass for non-Exposure datasets that can
372  be retrieved by the standard daf_persistence mechanism.
373 
374  The differences are that the Storage type must be specified and no
375  Exposure standardization is performed.
376 
377  The "storage" entry in the Policy is mandatory; the "tables" entry is
378  optional; no "level" entry is allowed. """
379 
380  def __init__(self, datasetType, policy, registry, root, **kwargs):
381  """Constructor for DatasetMapping class.
382  @param[in,out] mapper (lsst.daf.persistence.Mapper) Mapper object
383  @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
384  Mapping Policy
385  @param datasetType (string)
386  @param registry (lsst.daf.butlerUtils.Registry) Registry for metadata lookups
387  @param root (string) Path of root directory"""
388  if isinstance(policy, pexPolicy.Policy):
389  policy = Policy(policy)
390  Mapping.__init__(self, datasetType, policy, registry, root, **kwargs)
391  self.storage = policy["storage"] # Storage type
int iter
a container for holding hierarchical configuration data in memory.
Definition: Policy.h:169