LSSTApplications  10.0+286,10.0+36,10.0+46,10.0-2-g4f67435,10.1+152,10.1+37,11.0,11.0+1,11.0-1-g47edd16,11.0-1-g60db491,11.0-1-g7418c06,11.0-2-g04d2804,11.0-2-g68503cd,11.0-2-g818369d,11.0-2-gb8b8ce7
LSSTDataManagementBasePackage
cameraMapper.py
Go to the documentation of this file.
1 #!/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008, 2009, 2010 LSST Corporation.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 
24 import os
25 import errno
26 import re
27 import shutil
28 import pyfits # required by _makeDefectsDict until defects are written as AFW tables
29 import lsst.daf.persistence as dafPersist
30 from lsst.daf.butlerUtils import ImageMapping, ExposureMapping, CalibrationMapping, DatasetMapping, Registry
31 import lsst.daf.base as dafBase
32 import lsst.afw.geom as afwGeom
33 import lsst.afw.image as afwImage
34 import lsst.afw.cameraGeom as afwCameraGeom
35 import lsst.pex.logging as pexLog
36 import lsst.pex.policy as pexPolicy
37 
38 """This module defines the CameraMapper base class."""
39 
41 
42  """CameraMapper is a base class for mappers that handle images from a
43  camera and products derived from them. This provides an abstraction layer
44  between the data on disk and the code.
45 
46  Public methods: keys, queryMetadata, getDatasetTypes, map,
47  canStandardize, standardize
48 
49  Mappers for specific data sources (e.g., CFHT Megacam, LSST
50  simulations, etc.) should inherit this class.
51 
52  The CameraMapper manages datasets within a "root" directory. It can also
53  be given an "outputRoot". If so, the input root is linked into the
54  outputRoot directory using a symlink named "_parent"; writes go into the
55  outputRoot while reads can come from either the root or outputRoot. As
56  outputRoots are used as inputs for further processing, the chain of
57  _parent links allows any dataset to be retrieved. Note that writing to a
58  dataset present in the input root will hide the existing dataset but not
59  overwrite it. See #2160 for design discussion.
60 
61  A camera is assumed to consist of one or more rafts, each composed of
62  multiple CCDs. Each CCD is in turn composed of one or more amplifiers
63  (amps). A camera is also assumed to have a camera geometry description
64  (CameraGeom object) as a policy file, a filter description (Filter class
65  static configuration) as another policy file, and an optional defects
66  description directory.
67 
68  Information from the camera geometry and defects are inserted into all
69  Exposure objects returned.
70 
71  The mapper uses one or two registries to retrieve metadata about the
72  images. The first is a registry of all raw exposures. This must contain
73  the time of the observation. One or more tables (or the equivalent)
74  within the registry are used to look up data identifier components that
75  are not specified by the user (e.g. filter) and to return results for
76  metadata queries. The second is an optional registry of all calibration
77  data. This should contain validity start and end entries for each
78  calibration dataset in the same timescale as the observation time.
79 
80  The following method must be provided by the subclass:
81 
82  _extractDetectorName(self, dataId): returns the detector name for a CCD
83  (e.g., "CFHT 21", "R:1,2 S:3,4") as used in the AFW CameraGeom class given
84  a dataset identifier referring to that CCD or a subcomponent of it.
85 
86  Other methods that the subclass may wish to override include:
87 
88  _transformId(self, dataId): transformation of a data identifier
89  from colloquial usage (e.g., "ccdname") to proper/actual usage (e.g., "ccd"),
90  including making suitable for path expansion (e.g. removing commas).
91  The default implementation does nothing. Note that this
92  method should not modify its input parameter.
93 
94  getShortCcdName(self, ccdName): a static method that returns a shortened name
95  suitable for use as a filename. The default version converts spaces to underscores.
96 
97  _getCcdKeyVal(self, dataId): return a CCD key and value
98  by which to look up defects in the defects registry.
99  The default value returns ("ccd", detector name)
100 
101  _mapActualToPath(self, template, actualId): convert a template path to an
102  actual path, using the actual dataset identifier.
103 
104  The mapper's behaviors are largely specified by the policy file.
105  See the MapperDictionary.paf for descriptions of the available items.
106 
107  The 'exposures', 'calibrations', and 'datasets' subpolicies configure
108  mappings (see Mappings class).
109 
110  Functions to map (provide a path to the data given a dataset
111  identifier dictionary) and standardize (convert data into some standard
112  format or type) may be provided in the subclass as "map_{dataset type}"
113  and "std_{dataset type}", respectively.
114 
115  If non-Exposure datasets cannot be retrieved using standard
116  daf_persistence methods alone, a "bypass_{dataset type}" function may be
117  provided in the subclass to return the dataset instead of using the
118  "datasets" subpolicy.
119 
120  Implementations of map_camera and bypass_camera that should typically be
121  sufficient are provided in this base class.
122 
123  @todo
124  * Handle defects the same was as all other calibration products, using the calibration registry
125  * Instead of auto-loading the camera at construction time, load it from the calibration registry
126  * Rewrite defects as AFW tables so we don't need pyfits to unpersist them; then remove all mention
127  of pyfits from this package.
128  """
129  packageName = None
130 
131  def __init__(self, policy, repositoryDir,
132  root=None, registry=None, calibRoot=None, calibRegistry=None,
133  provided=None, outputRoot=None):
134  """Initialize the CameraMapper.
135  @param policy (pexPolicy.Policy) Policy with per-camera defaults
136  already merged
137  @param repositoryDir (string) Policy repository for the subclassing
138  module (obtained with getRepositoryPath() on the
139  per-camera default dictionary)
140  @param root (string) Root directory for data
141  @param registry (string) Path to registry with data's metadata
142  @param calibRoot (string) Root directory for calibrations
143  @param calibRegistry (string) Path to registry with calibrations'
144  metadata
145  @param provided (list of strings) Keys provided by the mapper
146  @param outputRoot (string) Root directory for output data
147  """
148 
149  dafPersist.Mapper.__init__(self)
150 
151  self.log = pexLog.Log(pexLog.getDefaultLog(), "CameraMapper")
152 
153  # Dictionary
154  dictFile = pexPolicy.DefaultPolicyFile("daf_butlerUtils",
155  "MapperDictionary.paf", "policy")
156  dictPolicy = pexPolicy.Policy.createPolicy(dictFile,
157  dictFile.getRepositoryPath())
158  policy.mergeDefaults(dictPolicy)
159 
160  # Levels
161  self.levels = dict()
162  if policy.exists("levels"):
163  levelsPolicy = policy.getPolicy("levels")
164  for key in levelsPolicy.names(True):
165  self.levels[key] = set(levelsPolicy.getStringArray(key))
166  self.defaultLevel = policy.getString("defaultLevel")
167  self.defaultSubLevels = dict()
168  if policy.exists("defaultSubLevels"):
169  defaultSubLevelsPolicy = policy.getPolicy("defaultSubLevels")
170  for key in defaultSubLevelsPolicy.names(True):
171  self.defaultSubLevels[key] = defaultSubLevelsPolicy.getString(key)
172 
173  # Root directories
174  if root is None:
175  root = "."
176  root = dafPersist.LogicalLocation(root).locString()
177 
178  if outputRoot is not None and os.path.abspath(outputRoot) != os.path.abspath(root):
179  # Path manipulations are subject to race condition
180  if not os.path.exists(outputRoot):
181  try:
182  os.makedirs(outputRoot)
183  except OSError, e:
184  if not e.errno == errno.EEXIST:
185  raise
186  if not os.path.exists(outputRoot):
187  raise RuntimeError, "Unable to create output " \
188  "repository '%s'" % (outputRoot,)
189  if os.path.exists(root):
190  # Symlink existing input root to "_parent" in outputRoot.
191  src = os.path.abspath(root)
192  dst = os.path.join(outputRoot, "_parent")
193  if not os.path.exists(dst):
194  try:
195  os.symlink(src, dst)
196  except OSError:
197  pass
198  if os.path.exists(dst):
199  if os.path.realpath(dst) != os.path.realpath(src):
200  raise RuntimeError, "Output repository path " \
201  "'%s' already exists and differs from " \
202  "input repository path '%s'" % (dst, src)
203  else:
204  raise RuntimeError, "Unable to symlink from input " \
205  "repository path '%s' to output repository " \
206  "path '%s'" % (src, dst)
207  # We now use the outputRoot as the main root with access to the
208  # input via "_parent".
209  root = outputRoot
210 
211  if calibRoot is None:
212  if policy.exists('calibRoot'):
213  calibRoot = policy.getString('calibRoot')
214  calibRoot = dafPersist.LogicalLocation(calibRoot).locString()
215  else:
216  calibRoot = root
217 
218  if not os.path.exists(root):
219  self.log.log(pexLog.Log.WARN,
220  "Root directory not found: %s" % (root,))
221  if not os.path.exists(calibRoot):
222  self.log.log(pexLog.Log.WARN,
223  "Calibration root directory not found: %s" % (calibRoot,))
224  self.root = root
225 
226  # Registries
228  "registry", registry, policy, "registryPath", root)
229  if policy.exists('needCalibRegistry') and \
230  policy.getBool('needCalibRegistry'):
231  calibRegistry = self._setupRegistry(
232  "calibRegistry", calibRegistry,
233  policy, "calibRegistryPath", calibRoot)
234  else:
235  calibRegistry = None
236 
237  # Sub-dictionaries (for exposure/calibration/dataset types)
238  imgMappingFile = pexPolicy.DefaultPolicyFile("daf_butlerUtils",
239  "ImageMappingDictionary.paf", "policy")
240  imgMappingPolicy = pexPolicy.Policy.createPolicy(imgMappingFile,
241  imgMappingFile.getRepositoryPath())
242  expMappingFile = pexPolicy.DefaultPolicyFile("daf_butlerUtils",
243  "ExposureMappingDictionary.paf", "policy")
244  expMappingPolicy = pexPolicy.Policy.createPolicy(expMappingFile,
245  expMappingFile.getRepositoryPath())
246  calMappingFile = pexPolicy.DefaultPolicyFile("daf_butlerUtils",
247  "CalibrationMappingDictionary.paf", "policy")
248  calMappingPolicy = pexPolicy.Policy.createPolicy(calMappingFile,
249  calMappingFile.getRepositoryPath())
250  dsMappingFile = pexPolicy.DefaultPolicyFile("daf_butlerUtils",
251  "DatasetMappingDictionary.paf", "policy")
252  dsMappingPolicy = pexPolicy.Policy.createPolicy(dsMappingFile,
253  dsMappingFile.getRepositoryPath())
254 
255  # Dict of valid keys and their value types
256  self.keyDict = dict()
257 
258  # Mappings
259  mappingList = (
260  ("images", imgMappingPolicy, ImageMapping),
261  ("exposures", expMappingPolicy, ExposureMapping),
262  ("calibrations", calMappingPolicy, CalibrationMapping),
263  ("datasets", dsMappingPolicy, DatasetMapping)
264  )
265  self.mappings = dict()
266  for name, defPolicy, cls in mappingList:
267  if policy.exists(name):
268  datasets = policy.getPolicy(name)
269  mappings = dict()
270  setattr(self, name, mappings)
271  for datasetType in datasets.names(True):
272  subPolicy = datasets.getPolicy(datasetType)
273  subPolicy.mergeDefaults(defPolicy)
274  if name == "calibrations":
275  mapping = cls(datasetType, subPolicy,
276  self.registry, calibRegistry, calibRoot, provided=provided)
277  else:
278  mapping = cls(datasetType, subPolicy,
279  self.registry, root, provided=provided)
280  self.keyDict.update(mapping.keys())
281  mappings[datasetType] = mapping
282  self.mappings[datasetType] = mapping
283  if not hasattr(self, "map_" + datasetType):
284  def mapClosure(dataId, write=False,
285  mapper=self, mapping=mapping):
286  return mapping.map(mapper, dataId, write)
287  setattr(self, "map_" + datasetType, mapClosure)
288  if not hasattr(self, "query_" + datasetType):
289  def queryClosure(key, format, dataId, mapping=mapping):
290  return mapping.lookup(format, dataId)
291  setattr(self, "query_" + datasetType, queryClosure)
292  if hasattr(mapping, "standardize") and \
293  not hasattr(self, "std_" + datasetType):
294  def stdClosure(item, dataId,
295  mapper=self, mapping=mapping):
296  return mapping.standardize(mapper, item, dataId)
297  setattr(self, "std_" + datasetType, stdClosure)
298 
299  mapFunc = "map_" + datasetType + "_filename"
300  bypassFunc = "bypass_" + datasetType + "_filename"
301  if not hasattr(self, mapFunc):
302  setattr(self, mapFunc, getattr(self, "map_" + datasetType))
303  if not hasattr(self, bypassFunc):
304  setattr(self, bypassFunc,
305  lambda datasetType, pythonType, location, dataId: location.getLocations())
306 
307  # Set up metadata versions
308  if name == "exposures" or name == "images":
309  expFunc = "map_" + datasetType # Function name to map exposure
310  mdFunc = expFunc + "_md" # Function name to map metadata
311  bypassFunc = "bypass_" + datasetType + "_md" # Function name to bypass daf_persistence
312  if not hasattr(self, mdFunc):
313  setattr(self, mdFunc, getattr(self, expFunc))
314  if not hasattr(self, bypassFunc):
315  setattr(self, bypassFunc,
316  lambda datasetType, pythonType, location, dataId:
317  afwImage.readMetadata(location.getLocations()[0]))
318  if not hasattr(self, "query_" + datasetType + "_md"):
319  setattr(self, "query_" + datasetType + "_md",
320  getattr(self, "query_" + datasetType))
321 
322  subFunc = expFunc + "_sub" # Function name to map subimage
323  if not hasattr(self, subFunc):
324  def mapSubClosure(dataId, write=False, mapper=self, mapping=mapping):
325  subId = dataId.copy()
326  del subId['bbox']
327  loc = mapping.map(mapper, subId, write)
328  bbox = dataId['bbox']
329  llcX = bbox.getMinX()
330  llcY = bbox.getMinY()
331  width = bbox.getWidth()
332  height = bbox.getHeight()
333  loc.additionalData.set('llcX', llcX)
334  loc.additionalData.set('llcY', llcY)
335  loc.additionalData.set('width', width)
336  loc.additionalData.set('height', height)
337  if 'imageOrigin' in dataId:
338  loc.additionalData.set('imageOrigin',
339  dataId['imageOrigin'])
340  return loc
341  setattr(self, subFunc, mapSubClosure)
342  if not hasattr(self, "query_" + datasetType + "_sub"):
343  def querySubClosure(key, format, dataId, mapping=mapping):
344  subId = dataId.copy()
345  del subId['bbox']
346  return mapping.lookup(format, subId)
347  setattr(self, "query_" + datasetType + "_sub", querySubClosure)
348 
349  # Camera geometry
350  self.cameraDataLocation = None # path to camera geometry config file
351  self.camera = self._makeCamera(policy=policy, repositoryDir=repositoryDir)
352 
353  # Defect registry and root
354  self.defectRegistry = None
355  if policy.exists('defects'):
356  self.defectPath = os.path.join(
357  repositoryDir, policy.getString('defects'))
358  defectRegistryLocation = os.path.join(
359  self.defectPath, "defectRegistry.sqlite3")
360  self.defectRegistry = \
361  Registry.create(defectRegistryLocation)
362 
363  # Filter translation table
364  self.filters = None
365 
366  # Skytile policy
367  self.skypolicy = policy.getPolicy("skytiles")
368 
369  # verify that the class variable packageName is set before attempting
370  # to instantiate an instance
371  if self.packageName is None:
372  raise ValueError('class variable packageName must not be None')
373 
374  def _parentSearch(self, path):
375  """Look for the given path in the current root or any of its parents
376  by following "_parent" symlinks; return None if it can't be found. A
377  little tricky because the path may be in an alias of the root (e.g.
378  ".") and because the "_parent" links go between the root and the rest
379  of the path.
380  """
381 
382  # Separate path into a root-equivalent prefix (in dir) and the rest
383  # (left in path)
384  rootDir = self.root
385 
386  # First remove trailing slashes (#2527)
387  while len(rootDir) > 1 and rootDir[-1] == '/':
388  rootDir = rootDir[:-1]
389 
390  if path.startswith(rootDir + "/"):
391  # Common case; we have the same root prefix string
392  path = path[len(rootDir)+1:]
393  dir = rootDir
394  elif rootDir == "/" and path.startswith("/"):
395  path = path[1:]
396  dir = rootDir
397  else:
398  # Search for prefix that is the same as root
399  pathPrefix = os.path.dirname(path)
400  while pathPrefix != "" and pathPrefix != "/":
401  if os.path.realpath(pathPrefix) == os.path.realpath(self.root):
402  break
403  pathPrefix = os.path.dirname(pathPrefix)
404  if os.path.realpath(pathPrefix) != os.path.realpath(self.root):
405  # No prefix matching root, don't search for parents
406  if os.path.exists(path):
407  return path
408  return None
409  if pathPrefix == "/":
410  path = path[1:]
411  elif pathPrefix != "":
412  path = path[len(pathPrefix)+1:]
413  # If pathPrefix == "", then the current directory is the root
414  dir = pathPrefix
415 
416  # Now search for the path in the root or its parents
417  # Strip off any cfitsio bracketed extension if present
418  strippedPath = path
419  firstBracket = path.find("[")
420  if firstBracket != -1:
421  strippedPath = path[:firstBracket]
422  while not os.path.exists(os.path.join(dir, strippedPath)):
423  dir = os.path.join(dir, "_parent")
424  if not os.path.exists(dir):
425  return None
426  return os.path.join(dir, path)
427 
428  def backup(self, datasetType, dataId):
429  """Rename any existing object with the given type and dataId.
430 
431  The CameraMapper implementation saves objects in a sequence of e.g.:
432  foo.fits
433  foo.fits~1
434  foo.fits~2
435  All of the backups will be placed in the output repo, however, and will
436  not be removed if they are found elsewhere in the _parent chain. This
437  means that the same file will be stored twice if the previous version was
438  found in an input repo.
439  """
440  n = 0
441  newLocation = self.map(datasetType, dataId, write=True)
442  newPath = newLocation.getLocations()[0]
443  path = self._parentSearch(newPath)
444  oldPaths = []
445  while path is not None:
446  n += 1
447  oldPaths.append((n, path))
448  path = self._parentSearch("%s~%d" % (newPath, n))
449  for n, oldPath in reversed(oldPaths):
450  newDir, newFile = os.path.split(newPath)
451  if not os.path.exists(newDir):
452  os.makedirs(newDir)
453  shutil.copy(oldPath, "%s~%d" % (newPath, n))
454 
455  def keys(self):
456  """Return supported keys.
457  @return (iterable) List of keys usable in a dataset identifier"""
458  return self.keyDict.iterkeys()
459 
460  def getKeys(self, datasetType, level):
461  """Return supported keys and their value types for a given dataset
462  type at a given level of the key hierarchy.
463 
464  @param datasetType (str) dataset type or None for all keys
465  @param level (str) level or None for all levels
466  @return (iterable) Set of keys usable in a dataset identifier"""
467  if datasetType is None:
468  keyDict = self.keyDict
469  else:
470  keyDict = self.mappings[datasetType].keys()
471  if level is not None and level in self.levels:
472  keyDict = dict(keyDict)
473  for l in self.levels[level]:
474  if l in keyDict:
475  del keyDict[l]
476  return keyDict
477 
478  def getDefaultLevel(self):
479  return self.defaultLevel
480 
481  def getDefaultSubLevel(self, level):
482  if self.defaultSubLevels.has_key(level):
483  return self.defaultSubLevels[level]
484  return None
485 
486  @classmethod
487  def getCameraName(cls):
488  """Return the name of the camera that this CameraMapper is for."""
489  className = str(cls)
490  m = re.search(r'(\w+)Mapper', className)
491  if m is None:
492  m = re.search(r"class '[\w.]*?(\w+)'", className)
493  name = m.group(1)
494  return name[:1].lower() + name[1:] if name else ''
495 
496  @classmethod
497  def getPackageName(cls):
498  """Return the name of the package containing this CameraMapper."""
499  if cls.packageName is None:
500  raise ValueError('class variable packageName must not be None')
501  return cls.packageName
502 
503  def map_camera(self, dataId, write=False):
504  """Map a camera dataset."""
505  if self.camera is None:
506  raise RuntimeError("No camera dataset available.")
507  actualId = self._transformId(dataId)
509  pythonType = "lsst.afw.cameraGeom.CameraConfig",
510  cppType = "Config",
511  storageName = "ConfigStorage",
512  locationList = self.cameraDataLocation or "ignored",
513  dataId = actualId,
514  )
515 
516  def bypass_camera(self, datasetType, pythonType, butlerLocation, dataId):
517  """Return the (preloaded) camera object.
518  """
519  if self.camera is None:
520  raise RuntimeError("No camera dataset available.")
521  return self.camera
522 
523  def map_defects(self, dataId, write=False):
524  """Map defects dataset.
525 
526  @return a very minimal ButlerLocation containing just the locationList field
527  (just enough information that bypass_defects can use it).
528  """
529  defectFitsPath = self._defectLookup(dataId=dataId)
530  if defectFitsPath is None:
531  raise RuntimeError("No defects available for dataId=%s" % (dataId,))
532 
533  return dafPersist.ButlerLocation(None, None, None, defectFitsPath, dataId)
534 
535  def bypass_defects(self, datasetType, pythonType, butlerLocation, dataId):
536  """Return a defect based on the butler location returned by map_defects
537 
538  @param[in] butlerLocation: a ButlerLocation with locationList = path to defects FITS file
539  @param[in] dataId: the usual data ID; "ccd" must be set
540 
541  Note: the name "bypass_XXX" means the butler makes no attempt to convert the ButlerLocation
542  into an object, which is what we want for now, since that conversion is a bit tricky.
543  """
544  detectorName = self._extractDetectorName(dataId)
545  defectsFitsPath = butlerLocation.locationList[0]
546  with pyfits.open(defectsFitsPath) as hduList:
547  for hdu in hduList[1:]:
548  if hdu.header["name"] != detectorName:
549  continue
550 
551  defectList = []
552  for data in hdu.data:
553  bbox = afwGeom.Box2I(
554  afwGeom.Point2I(int(data['x0']), int(data['y0'])),
555  afwGeom.Extent2I(int(data['width']), int(data['height'])),
556  )
557  defectList.append(afwImage.DefectBase(bbox))
558  return defectList
559 
560  raise RuntimeError("No defects for ccd %s in %s" % (detectorName, defectsFitsPath))
561 
562  def std_raw(self, item, dataId):
563  """Standardize a raw dataset by converting it to an Exposure instead of an Image"""
564  item = exposureFromImage(item)
565  return self._standardizeExposure(self.exposures['raw'], item, dataId,
566  trimmed=False)
567 
568  def map_skypolicy(self, dataId):
569  """Map a sky policy."""
570  return dafPersist.ButlerLocation("lsst.pex.policy.Policy", "Policy",
571  "Internal", None, None)
572 
573  def std_skypolicy(self, item, dataId):
574  """Standardize a sky policy by returning the one we use."""
575  return self.skypolicy
576 
577 ###############################################################################
578 #
579 # Utility functions
580 #
581 ###############################################################################
582 
583  def _getCcdKeyVal(self, dataId):
584  """Return CCD key and value used to look a defect in the defect registry
585 
586  The default implementation simply returns ("ccd", full detector name)
587  """
588  return ("ccd", self._extractDetectorName(dataId))
589 
590  def _setupRegistry(self, name, path, policy, policyKey, root):
591  """Set up a registry (usually SQLite3), trying a number of possible
592  paths.
593  @param name (string) Name of registry
594  @param path (string) Path for registry
595  @param policyKey (string) Key in policy for registry path
596  @param root (string) Root directory to look in
597  @return (lsst.daf.butlerUtils.Registry) Registry object"""
598 
599  if path is None and policy.exists(policyKey):
601  policy.getString(policyKey)).locString()
602  if not os.path.exists(path):
603  if not os.path.isabs(path) and root is not None:
604  newPath = self._parentSearch(os.path.join(root, path))
605  if newPath is None:
606  self.log.log(pexLog.Log.WARN,
607  "Unable to locate registry at policy path (also looked in root): %s" % path)
608  path = newPath
609  else:
610  self.log.log(pexLog.Log.WARN,
611  "Unable to locate registry at policy path: %s" % path)
612  path = None
613  if path is None and root is not None:
614  path = os.path.join(root, "%s.sqlite3" % name)
615  newPath = self._parentSearch(path)
616  if newPath is None:
617  self.log.log(pexLog.Log.WARN,
618  "Unable to locate %s registry in root: %s" % (name, path))
619  path = newPath
620  if path is None:
621  path = os.path.join(".", "%s.sqlite3" % name)
622  newPath = self._parentSearch(path)
623  if newPath is None:
624  self.log.log(pexLog.Log.WARN,
625  "Unable to locate %s registry in current dir: %s" % (name, path))
626  path = newPath
627  if path is not None:
628  if not os.path.exists(path):
629  newPath = self._parentSearch(path)
630  if newPath is not None:
631  path = newPath
632  self.log.log(pexLog.Log.INFO,
633  "Loading %s registry from %s" % (name, path))
634  registry = Registry.create(path)
635  if registry is None:
636  raise RuntimeError, "Unable to load %s registry from %s" % (name, path)
637  return registry
638  else:
639  # TODO Try a FsRegistry(root)
640  self.log.log(pexLog.Log.WARN,
641  "No registry loaded; proceeding without one")
642  return None
643 
644  def _transformId(self, dataId):
645  """Generate a standard ID dict from a camera-specific ID dict.
646 
647  Canonical keys include:
648  - amp: amplifier name
649  - ccd: CCD name (in LSST this is a combination of raft and sensor)
650  The default implementation returns a copy of its input.
651 
652  @param dataId[in] (dict) Dataset identifier; this must not be modified
653  @return (dict) Transformed dataset identifier"""
654 
655  return dataId.copy()
656 
657  def _mapActualToPath(self, template, actualId):
658  """Convert a template path to an actual path, using the actual data
659  identifier. This implementation is usually sufficient but can be
660  overridden by the subclass.
661  @param template (string) Template path
662  @param actualId (dict) Dataset identifier
663  @return (string) Pathname"""
664 
665  return template % self._transformId(actualId)
666 
667  @staticmethod
668  def getShortCcdName(ccdName):
669  """Convert a CCD name to a form useful as a filename
670 
671  The default implementation converts spaces to underscores.
672  """
673  return ccdName.replace(" ", "_")
674 
675  def _extractDetectorName(self, dataId):
676  """Extract the detector (CCD) name from the dataset identifier.
677 
678  The name in question is the detector name used by lsst.afw.cameraGeom.
679 
680  @param dataId (dict) Dataset identifier
681  @return (string) Detector name
682  """
683  raise NotImplementedError("No _extractDetectorName() function specified")
684 
685  def _extractAmpId(self, dataId):
686  """Extract the amplifier identifer from a dataset identifier.
687 
688  @warning this is deprecated; DO NOT USE IT
689 
690  amplifier identifier has two parts: the detector name for the CCD
691  containing the amplifier and index of the amplifier in the detector.
692  @param dataId (dict) Dataset identifer
693  @return (tuple) Amplifier identifier"""
694 
695  trDataId = self._transformId(dataId)
696  return (trDataId["ccd"], int(trDataId['amp']))
697 
698  def _setAmpDetector(self, item, dataId, trimmed=True):
699  """Set the detector object in an Exposure for an amplifier.
700  Defects are also added to the Exposure based on the detector object.
701  @param[in,out] item (lsst.afw.image.Exposure)
702  @param dataId (dict) Dataset identifier
703  @param trimmed (bool) Should detector be marked as trimmed? (ignored)"""
704 
705  return self._setCcdDetector(item=item, dataId=dataId, trimmed=trimmed)
706 
707  def _setCcdDetector(self, item, dataId, trimmed=True):
708  """Set the detector object in an Exposure for a CCD.
709  @param[in,out] item (lsst.afw.image.Exposure)
710  @param dataId (dict) Dataset identifier
711  @param trimmed (bool) Should detector be marked as trimmed? (ignored)"""
712 
713  detectorName = self._extractDetectorName(dataId)
714  detector = self.camera[detectorName]
715  item.setDetector(detector)
716 
717  def _setFilter(self, mapping, item, dataId):
718  """Set the filter object in an Exposure. If the Exposure had a FILTER
719  keyword, this was already processed during load. But if it didn't,
720  use the filter from the registry.
721  @param mapping (lsst.daf.butlerUtils.Mapping)
722  @param[in,out] item (lsst.afw.image.Exposure)
723  @param dataId (dict) Dataset identifier"""
724 
725  if not (isinstance(item, afwImage.ExposureU) or isinstance(item, afwImage.ExposureI) or
726  isinstance(item, afwImage.ExposureF) or isinstance(item, afwImage.ExposureD)):
727  return
728 
729  actualId = mapping.need(['filter'], dataId)
730  filterName = actualId['filter']
731  if self.filters is not None and self.filters.has_key(filterName):
732  filterName = self.filters[filterName]
733  item.setFilter(afwImage.Filter(filterName))
734 
735  def _setTimes(self, mapping, item, dataId):
736  """Set the exposure time and exposure midpoint in the calib object in
737  an Exposure. Use the EXPTIME and MJD-OBS keywords (and strip out
738  EXPTIME).
739  @param mapping (lsst.daf.butlerUtils.Mapping)
740  @param[in,out] item (lsst.afw.image.Exposure)
741  @param dataId (dict) Dataset identifier"""
742 
743  md = item.getMetadata()
744  calib = item.getCalib()
745  if md.exists("EXPTIME"):
746  expTime = md.get("EXPTIME")
747  calib.setExptime(expTime)
748  md.remove("EXPTIME")
749  else:
750  expTime = calib.getExptime()
751  if md.exists("MJD-OBS"):
752  obsStart = dafBase.DateTime(md.get("MJD-OBS"),
753  dafBase.DateTime.MJD, dafBase.DateTime.UTC)
754  obsMidpoint = obsStart.nsecs() + long(expTime * 1000000000L / 2)
755  calib.setMidTime(dafBase.DateTime(obsMidpoint))
756 
757 
758  # Default standardization function for exposures
759  def _standardizeExposure(self, mapping, item, dataId, filter=True,
760  trimmed=True):
761  """Default standardization function for images.
762  @param mapping (lsst.daf.butlerUtils.Mapping)
763  @param[in,out] item (lsst.afw.image.Exposure)
764  @param dataId (dict) Dataset identifier
765  @param filter (bool) Set filter?
766  @param trimmed (bool) Should detector be marked as trimmed?
767  @return (lsst.afw.image.Exposure) the standardized Exposure"""
768 
769  if (re.search(r'Exposure', mapping.python) and re.search(r'Image',mapping.persistable)):
770  item = exposureFromImage(item)
771 
772  if mapping.level.lower() == "amp":
773  self._setAmpDetector(item, dataId, trimmed)
774  elif mapping.level.lower() == "ccd":
775  self._setCcdDetector(item, dataId, trimmed)
776 
777  if filter:
778  self._setFilter(mapping, item, dataId)
779  if not isinstance(mapping, CalibrationMapping):
780  self._setTimes(mapping, item, dataId)
781 
782  return item
783 
784  def _defectLookup(self, dataId):
785  """Find the defects for a given CCD.
786  @param dataId (dict) Dataset identifier
787  @return (string) path to the defects file or None if not available"""
788  if self.defectRegistry is None:
789  return None
790  if self.registry is None:
791  raise RuntimeError, "No registry for defect lookup"
792 
793  ccdKey, ccdVal = self._getCcdKeyVal(dataId)
794 
795  rows = self.registry.executeQuery(("taiObs",), ("raw_visit",),
796  [("visit", "?")], None, (dataId['visit'],))
797  if len(rows) == 0:
798  return None
799  assert len(rows) == 1
800  taiObs = rows[0][0]
801 
802  # Lookup the defects for this CCD serial number that are valid at the
803  # exposure midpoint.
804  rows = self.defectRegistry.executeQuery(("path",), ("defect",),
805  [(ccdKey, "?")],
806  ("DATETIME(?)", "DATETIME(validStart)", "DATETIME(validEnd)"),
807  (ccdVal, taiObs))
808  if not rows or len(rows) == 0:
809  return None
810  if len(rows) == 1:
811  return os.path.join(self.defectPath, rows[0][0])
812  else:
813  raise RuntimeError("Querying for defects (%s, %s) returns %d files: %s" %
814  (ccdVal, taiObs, len(rows), ", ".join([_[0] for _ in rows])))
815 
816  def _makeCamera(self, policy, repositoryDir):
817  """Make a camera (instance of lsst.afw.cameraGeom.Camera) describing the camera geometry
818 
819  Also set self.cameraDataLocation, if relevant (else it can be left None).
820 
821  This implementation assumes that policy contains an entry "camera" that points to the
822  subdirectory in this package of camera data; specifically, that subdirectory must contain:
823  - a file named `camera.py` that contains persisted camera config
824  - ampInfo table FITS files, as required by lsst.afw.cameraGeom.makeCameraFromPath
825 
826  @param policy (pexPolicy.Policy) Policy with per-camera defaults
827  already merged
828  @param repositoryDir (string) Policy repository for the subclassing
829  module (obtained with getRepositoryPath() on the
830  per-camera default dictionary)
831  """
832  if not policy.exists('camera'):
833  raise RuntimeError("Cannot find 'camera' in policy; cannot construct a camera")
834 
835  cameraDataSubdir = policy.getString('camera')
836  self.cameraDataLocation = os.path.normpath(
837  os.path.join(repositoryDir, cameraDataSubdir, "camera.py"))
838  cameraConfig = afwCameraGeom.CameraConfig()
839  cameraConfig.load(self.cameraDataLocation)
840  ampInfoPath = os.path.dirname(self.cameraDataLocation)
841  return afwCameraGeom.makeCameraFromPath(
842  cameraConfig = cameraConfig,
843  ampInfoPath = ampInfoPath,
844  shortNameFunc = self.getShortCcdName
845  )
846 
847 
848 def exposureFromImage(image):
849  """Generate an exposure from a DecoratedImage or similar
850  @param[in] image Image of interest
851  @return (lsst.afw.image.Exposure) Exposure containing input image
852  """
853  if isinstance(image, afwImage.DecoratedImageU) or isinstance(image, afwImage.DecoratedImageI) or \
854  isinstance(image, afwImage.DecoratedImageF) or isinstance(image, afwImage.DecoratedImageD):
855  exposure = afwImage.makeExposure(afwImage.makeMaskedImage(image.getImage()))
856  else:
857  exposure = image
858  md = image.getMetadata()
859  exposure.setMetadata(md)
860  wcs = afwImage.makeWcs(md)
861  exposure.setWcs(wcs)
862  wcsMetadata = wcs.getFitsMetadata()
863  for kw in wcsMetadata.paramNames():
864  md.remove(kw)
865 
866  return exposure
Class for handling dates/times, including MJD, UTC, and TAI.
Definition: DateTime.h:58
MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > * makeMaskedImage(typename Image< ImagePixelT >::Ptr image, typename Mask< MaskPixelT >::Ptr mask=typename Mask< MaskPixelT >::Ptr(), typename Image< VariancePixelT >::Ptr variance=typename Image< VariancePixelT >::Ptr())
Definition: MaskedImage.h:1067
Encapsulate information about a bad portion of a detector.
Definition: Defect.h:42
Class for logical location of a persisted Persistable instance.
a representation of a default Policy file that is stored as a file in the installation directory of a...
a place to record messages and descriptions of the state of processing.
Definition: Log.h:154
An integer coordinate rectangle.
Definition: Box.h:53
Wcs::Ptr makeWcs(coord::Coord const &crval, geom::Point2D const &crpix, double CD11, double CD12, double CD21, double CD22)
Create a Wcs object from crval, crpix, CD, using CD elements (useful from python) ...
Definition: makeWcs.cc:141
Holds an integer identifier for an LSST filter.
Definition: Filter.h:107
boost::shared_ptr< daf::base::PropertySet > readMetadata(std::string const &fileName, int hdu=0, bool strip=false)
Return the metadata (header entries) from a FITS file.
Exposure< ImagePixelT, MaskPixelT, VariancePixelT >::Ptr makeExposure(MaskedImage< ImagePixelT, MaskPixelT, VariancePixelT > &mimage, boost::shared_ptr< Wcs const > wcs=boost::shared_ptr< Wcs const >())
Definition: Exposure.h:308