23 from builtins
import zip
24 from builtins
import object
25 from collections
import OrderedDict
32 """This module defines the Mapping base class."""
37 """Mapping is a base class for all mappings. Mappings are used by
38 the Mapper to map (determine a path to some data given some
39 identifiers) and standardize (convert data into some standard
40 format or type) data, and to query the associated registry to see
41 what data is available.
43 Subclasses must specify self.storage or else override self.map().
45 Public methods: lookup, have, need, getKeys, map
47 Mappings are specified mainly by policy. A Mapping policy should
50 template (string): a Python string providing the filename for that
51 particular dataset type based on some data identifiers. In the
52 case of redundancy in the path (e.g., file uniquely specified by
53 the exposure number, but filter in the path), the
54 redundant/dependent identifiers can be looked up in the registry.
56 python (string): the Python type for the retrieved data (e.g.
57 lsst.afw.image.ExposureF)
59 persistable (string): the Persistable registration for the on-disk data
62 storage (string, optional): Storage type for this dataset type (e.g.
65 level (string, optional): the level in the camera hierarchy at which the
66 data is stored (Amp, Ccd or skyTile), if relevant
68 tables (string, optional): a whitespace-delimited list of tables in the
69 registry that can be NATURAL JOIN-ed to look up additional
72 def __init__(self, datasetType, policy, registry, root, provided=None):
73 """Constructor for Mapping class.
74 @param datasetType (string)
75 @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
77 @param registry (lsst.obs.base.Registry) Registry for metadata lookups
78 @param root (string) Path of root directory
79 @param provided (list of strings) Keys provided by the mapper
83 raise RuntimeError(
"No policy provided for mapping")
96 re.findall(
r'\%\((\w+)\).*?([diouxXeEfFgGcrs])', self.
template)
98 if provided
is not None:
105 if 'level' in policy:
107 if 'tables' in policy:
113 self.
obsTimeName = policy[
'obsTimeName']
if 'obsTimeName' in policy
else None
116 """Return the dict of keys and value types required for this mapping."""
119 def map(self, mapper, dataId, write=False):
120 """Standard implementation of map function.
121 @param mapper (lsst.daf.persistence.Mapper)
122 @param dataId (dict) Dataset identifier
123 @return (lsst.daf.persistence.ButlerLocation)"""
124 actualId = self.
need(
iter(self.keyDict.keys()), dataId)
125 usedDataId = {key: actualId[key]
for key
in self.keyDict.keys()}
126 path = mapper._mapActualToPath(self.
template, actualId)
127 if not os.path.isabs(path):
128 path = os.path.join(self.
root, path)
135 for ext
in (
None,
'.gz',
'.fz'):
136 if ext
and path.endswith(ext):
138 extPath = path + ext
if ext
else path
139 newPath = mapper._parentSearch(extPath)
143 assert path,
"Fully-qualified filename is empty."
146 if hasattr(mapper, addFunc):
147 addFunc = getattr(mapper, addFunc)
148 additionalData = addFunc(actualId)
149 assert isinstance(additionalData, dict),
"Bad type for returned data"
151 additionalData = actualId.copy()
154 usedDataId=usedDataId, datasetType=self.
datasetType)
157 """Look up properties for in a metadata registry given a partial
159 @param properties (list of strings)
160 @param dataId (dict) Dataset identifier
161 @return (list of tuples) values of properties"""
164 raise RuntimeError(
"No registry for lookup")
166 skyMapKeys = (
"tract",
"patch")
178 substitutions = OrderedDict()
180 properties = list(properties)
184 substitutions[p] = dataId[p]
188 "Cannot look up skymap key '%s'; it must be explicitly included in the data ID" % p
191 substitutions[p] = index
199 if p
not in (
'filter',
'expTime',
'taiObs'):
202 if fastPath
and 'visit' in dataId
and "raw" in self.
tables:
203 lookupDataId = {
'visit': dataId[
'visit']}
204 result = self.registry.lookup(properties,
'raw_visit', lookupDataId, template=self.
template)
206 if dataId
is not None:
207 for k, v
in dataId.items():
214 where.append((k,
'?'))
216 lookupDataId = {k[0]: v
for k, v
in zip(where, values)}
221 result = self.registry.lookup(properties, self.
tables, lookupDataId, template=self.
template)
225 result = [tuple(v
if k
in removed
else item[v]
for k, v
in substitutions.items())
229 def have(self, properties, dataId):
230 """Returns whether the provided data identifier has all
231 the properties in the provided list.
232 @param properties (list of strings) Properties required
233 @parm dataId (dict) Dataset identifier
234 @return (bool) True if all properties are present"""
235 for prop
in properties:
236 if prop
not in dataId:
240 def need(self, properties, dataId):
241 """Ensures all properties in the provided list are present in
242 the data identifier, looking them up as needed. This is only
243 possible for the case where the data identifies a single
245 @param properties (list of strings) Properties required
246 @param dataId (dict) Partial dataset identifier
247 @return (dict) copy of dataset identifier with enhanced values
249 newId = dataId.copy()
251 for prop
in properties:
252 if prop
not in newId:
253 newProps.append(prop)
254 if len(newProps) == 0:
257 lookups = self.
lookup(newProps, newId)
258 if len(lookups) != 1:
259 raise RuntimeError(
"No unique lookup for %s from %s: %d matches" %
260 (newProps, newId, len(lookups)))
261 for i, prop
in enumerate(newProps):
262 newId[prop] = lookups[0][i]
267 """Convert a format character into a Python type."""
275 raise RuntimeError(
"Unexpected format specifier %s"
276 " for field %s in template for dataset %s" %
277 (ch, k, datasetType))
281 """ImageMapping is a Mapping subclass for non-camera images."""
283 def __init__(self, datasetType, policy, registry, root, **kwargs):
284 """Constructor for Mapping class.
285 @param datasetType (string)
286 @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
288 @param registry (lsst.obs.base.Registry) Registry for metadata lookups
289 @param root (string) Path of root directory"""
292 Mapping.__init__(self, datasetType, policy, registry, root, **kwargs)
293 self.
columns = policy.asArray(
'columns')
if 'columns' in policy
else None
297 """ExposureMapping is a Mapping subclass for normal exposures."""
299 def __init__(self, datasetType, policy, registry, root, **kwargs):
300 """Constructor for Mapping class.
301 @param datasetType (string)
302 @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
304 @param registry (lsst.obs.base.Registry) Registry for metadata lookups
305 @param root (string) Path of root directory"""
308 Mapping.__init__(self, datasetType, policy, registry, root, **kwargs)
309 self.
columns = policy.asArray(
'columns')
if 'columns' in policy
else None
312 return mapper._standardizeExposure(self, item, dataId)
316 """CalibrationMapping is a Mapping subclass for calibration-type products.
318 The difference is that data properties in the query or template
319 can be looked up using a reference Mapping in addition to this one.
321 CalibrationMapping Policies can contain the following:
323 reference (string, optional): a list of tables for finding missing dataset
324 identifier components (including the observation time, if a validity range
325 is required) in the exposure registry; note that the "tables" entry refers
326 to the calibration registry
328 refCols (string, optional): a list of dataset properties required from the
329 reference tables for lookups in the calibration registry
331 validRange (bool): true if the calibration dataset has a validity range
332 specified by a column in the tables of the reference dataset in the
333 exposure registry) and two columns in the tables of this calibration
334 dataset in the calibration registry)
336 obsTimeName (string, optional): the name of the column in the reference
337 dataset tables containing the observation time (default "taiObs")
339 validStartName (string, optional): the name of the column in the
340 calibration dataset tables containing the start of the validity range
341 (default "validStart")
343 validEndName (string, optional): the name of the column in the
344 calibration dataset tables containing the end of the validity range
345 (default "validEnd") """
347 def __init__(self, datasetType, policy, registry, calibRegistry, calibRoot, **kwargs):
348 """Constructor for Mapping class.
349 @param datasetType (string)
350 @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
352 @param registry (lsst.obs.base.Registry) Registry for metadata lookups
353 @param calibRegistry (lsst.obs.base.Registry) Registry for calibration metadata lookups
354 @param calibRoot (string) Path of calibration root directory"""
357 Mapping.__init__(self, datasetType, policy, calibRegistry, calibRoot, **kwargs)
358 self.
reference = policy.asArray(
"reference")
if "reference" in policy
else None
359 self.
refCols = policy.asArray(
"refCols")
if "refCols" in policy
else None
361 if "validRange" in policy
and policy[
"validRange"]:
362 self.
range = (
"?", policy[
"validStartName"], policy[
"validEndName"])
363 if "columns" in policy:
365 if "filter" in policy:
368 if "metadataKey" in policy:
372 """Look up properties for in a metadata registry given a partial
374 @param properties (list of strings)
375 @param dataId (dict) Dataset identifier
376 @return (list of tuples) values of properties"""
381 newId = dataId.copy()
385 for k, v
in dataId.items():
394 for k
in dataId.keys():
397 columns = set(properties)
401 return Mapping.lookup(self, properties, newId)
403 lookupDataId = dict(zip(where, values))
404 lookups = self.refRegistry.lookup(columns, self.
reference, lookupDataId)
405 if len(lookups) != 1:
406 raise RuntimeError(
"No unique lookup for %s from %s: %d matches" %
407 (columns, dataId, len(lookups)))
408 if columns == set(properties):
411 for i, prop
in enumerate(columns):
412 newId[prop] = lookups[0][i]
413 return Mapping.lookup(self, properties, newId)
416 return mapper._standardizeExposure(self, item, dataId, filter=self.
setFilter)
420 """DatasetMapping is a Mapping subclass for non-Exposure datasets that can
421 be retrieved by the standard daf_persistence mechanism.
423 The differences are that the Storage type must be specified and no
424 Exposure standardization is performed.
426 The "storage" entry in the Policy is mandatory; the "tables" entry is
427 optional; no "level" entry is allowed. """
429 def __init__(self, datasetType, policy, registry, root, **kwargs):
430 """Constructor for DatasetMapping class.
431 @param[in,out] mapper (lsst.daf.persistence.Mapper) Mapper object
432 @param policy (daf_persistence.Policy, or pexPolicy.Policy (only for backward compatibility))
434 @param datasetType (string)
435 @param registry (lsst.obs.base.Registry) Registry for metadata lookups
436 @param root (string) Path of root directory"""
439 Mapping.__init__(self, datasetType, policy, registry, root, **kwargs)
a container for holding hierarchical configuration data in memory.