28 from astro_metadata_translator 
import fix_header
 
   30 from . 
import ImageMapping, ExposureMapping, CalibrationMapping, DatasetMapping
 
   39 from .exposureIdInfo 
import ExposureIdInfo
 
   40 from .makeRawVisitInfo 
import MakeRawVisitInfo
 
   41 from .utils 
import createInitialSkyWcs, InitialSkyWcsError
 
   44 __all__ = [
"CameraMapper", 
"exposureFromImage"]
 
   49     """CameraMapper is a base class for mappers that handle images from a 
   50     camera and products derived from them.  This provides an abstraction layer 
   51     between the data on disk and the code. 
   53     Public methods: keys, queryMetadata, getDatasetTypes, map, 
   54     canStandardize, standardize 
   56     Mappers for specific data sources (e.g., CFHT Megacam, LSST 
   57     simulations, etc.) should inherit this class. 
   59     The CameraMapper manages datasets within a "root" directory. Note that 
   60     writing to a dataset present in the input root will hide the existing 
   61     dataset but not overwrite it.  See #2160 for design discussion. 
   63     A camera is assumed to consist of one or more rafts, each composed of 
   64     multiple CCDs.  Each CCD is in turn composed of one or more amplifiers 
   65     (amps).  A camera is also assumed to have a camera geometry description 
   66     (CameraGeom object) as a policy file, a filter description (Filter class 
   67     static configuration) as another policy file. 
   69     Information from the camera geometry and defects are inserted into all 
   70     Exposure objects returned. 
   72     The mapper uses one or two registries to retrieve metadata about the 
   73     images.  The first is a registry of all raw exposures.  This must contain 
   74     the time of the observation.  One or more tables (or the equivalent) 
   75     within the registry are used to look up data identifier components that 
   76     are not specified by the user (e.g. filter) and to return results for 
   77     metadata queries.  The second is an optional registry of all calibration 
   78     data.  This should contain validity start and end entries for each 
   79     calibration dataset in the same timescale as the observation time. 
   81     Subclasses will typically set MakeRawVisitInfoClass and optionally the 
   82     metadata translator class: 
   84     MakeRawVisitInfoClass: a class variable that points to a subclass of 
   85     MakeRawVisitInfo, a functor that creates an 
   86     lsst.afw.image.VisitInfo from the FITS metadata of a raw image. 
   88     translatorClass: The `~astro_metadata_translator.MetadataTranslator` 
   89     class to use for fixing metadata values.  If it is not set an attempt 
   90     will be made to infer the class from ``MakeRawVisitInfoClass``, failing 
   91     that the metadata fixup will try to infer the translator class from the 
   94     Subclasses must provide the following methods: 
   96     _extractDetectorName(self, dataId): returns the detector name for a CCD 
   97     (e.g., "CFHT 21", "R:1,2 S:3,4") as used in the AFW CameraGeom class given 
   98     a dataset identifier referring to that CCD or a subcomponent of it. 
  100     _computeCcdExposureId(self, dataId): see below 
  102     _computeCoaddExposureId(self, dataId, singleFilter): see below 
  104     Subclasses may also need to override the following methods: 
  106     _transformId(self, dataId): transformation of a data identifier 
  107     from colloquial usage (e.g., "ccdname") to proper/actual usage 
  108     (e.g., "ccd"), including making suitable for path expansion (e.g. removing 
  109     commas). The default implementation does nothing.  Note that this 
  110     method should not modify its input parameter. 
  112     getShortCcdName(self, ccdName): a static method that returns a shortened 
  113     name suitable for use as a filename. The default version converts spaces 
  116     _mapActualToPath(self, template, actualId): convert a template path to an 
  117     actual path, using the actual dataset identifier. 
  119     The mapper's behaviors are largely specified by the policy file. 
  120     See the MapperDictionary.paf for descriptions of the available items. 
  122     The 'exposures', 'calibrations', and 'datasets' subpolicies configure 
  123     mappings (see Mappings class). 
  125     Common default mappings for all subclasses can be specified in the 
  126     "policy/{images,exposures,calibrations,datasets}.yaml" files. This 
  127     provides a simple way to add a product to all camera mappers. 
  129     Functions to map (provide a path to the data given a dataset 
  130     identifier dictionary) and standardize (convert data into some standard 
  131     format or type) may be provided in the subclass as "map_{dataset type}" 
  132     and "std_{dataset type}", respectively. 
  134     If non-Exposure datasets cannot be retrieved using standard 
  135     daf_persistence methods alone, a "bypass_{dataset type}" function may be 
  136     provided in the subclass to return the dataset instead of using the 
  137     "datasets" subpolicy. 
  139     Implementations of map_camera and bypass_camera that should typically be 
  140     sufficient are provided in this base class. 
  146        Instead of auto-loading the camera at construction time, load it from 
  147        the calibration registry 
  151     policy : daf_persistence.Policy, 
  152         Policy with per-camera defaults already merged. 
  153     repositoryDir : string 
  154         Policy repository for the subclassing module (obtained with 
  155         getRepositoryPath() on the per-camera default dictionary). 
  156     root : string, optional 
  157         Path to the root directory for data. 
  158     registry : string, optional 
  159         Path to registry with data's metadata. 
  160     calibRoot : string, optional 
  161         Root directory for calibrations. 
  162     calibRegistry : string, optional 
  163         Path to registry with calibrations' metadata. 
  164     provided : list of string, optional 
  165         Keys provided by the mapper. 
  166     parentRegistry : Registry subclass, optional 
  167         Registry from a parent repository that may be used to look up 
  169     repositoryCfg : daf_persistence.RepositoryCfg or None, optional 
  170         The configuration information for the repository this mapper is 
  177     MakeRawVisitInfoClass = MakeRawVisitInfo
 
  180     PupilFactoryClass = afwCameraGeom.PupilFactory
 
  183     translatorClass = 
None 
  186                  root=None, registry=None, calibRoot=None, calibRegistry=None,
 
  187                  provided=None, parentRegistry=None, repositoryCfg=None):
 
  189         dafPersist.Mapper.__init__(self)
 
  191         self.
log = lsstLog.Log.getLogger(
"CameraMapper")
 
  196             self.
root = repositoryCfg.root
 
  200         repoPolicy = repositoryCfg.policy 
if repositoryCfg 
else None 
  201         if repoPolicy 
is not None:
 
  202             policy.update(repoPolicy)
 
  206         if 'levels' in policy:
 
  207             levelsPolicy = policy[
'levels']
 
  208             for key 
in levelsPolicy.names(
True):
 
  212         if 'defaultSubLevels' in policy:
 
  220         self.
rootStorage = dafPersist.Storage.makeFromURI(uri=root)
 
  228         if calibRoot 
is not None:
 
  229             calibRoot = dafPersist.Storage.absolutePath(root, calibRoot)
 
  230             calibStorage = dafPersist.Storage.makeFromURI(uri=calibRoot,
 
  233             calibRoot = policy.get(
'calibRoot', 
None)
 
  235                 calibStorage = dafPersist.Storage.makeFromURI(uri=calibRoot,
 
  237         if calibStorage 
is None:
 
  245                                             posixIfNoSql=(
not parentRegistry))
 
  248         needCalibRegistry = policy.get(
'needCalibRegistry', 
None)
 
  249         if needCalibRegistry:
 
  252                                                          "calibRegistryPath", calibStorage,
 
  256                     "'needCalibRegistry' is true in Policy, but was unable to locate a repo at " 
  257                     f
"calibRoot ivar:{calibRoot} or policy['calibRoot']:{policy.get('calibRoot', None)}")
 
  277             raise ValueError(
'class variable packageName must not be None')
 
  287     def _initMappings(self, policy, rootStorage=None, calibStorage=None, provided=None):
 
  288         """Initialize mappings 
  290         For each of the dataset types that we want to be able to read, there 
  291         are methods that can be created to support them: 
  292         * map_<dataset> : determine the path for dataset 
  293         * std_<dataset> : standardize the retrieved dataset 
  294         * bypass_<dataset> : retrieve the dataset (bypassing the usual 
  296         * query_<dataset> : query the registry 
  298         Besides the dataset types explicitly listed in the policy, we create 
  299         additional, derived datasets for additional conveniences, 
  300         e.g., reading the header of an image, retrieving only the size of a 
  305         policy : `lsst.daf.persistence.Policy` 
  306             Policy with per-camera defaults already merged 
  307         rootStorage : `Storage subclass instance` 
  308             Interface to persisted repository data. 
  309         calibRoot : `Storage subclass instance` 
  310             Interface to persisted calib repository data 
  311         provided : `list` of `str` 
  312             Keys provided by the mapper 
  316             "obs_base", 
"ImageMappingDefaults.yaml", 
"policy"))
 
  318             "obs_base", 
"ExposureMappingDefaults.yaml", 
"policy"))
 
  320             "obs_base", 
"CalibrationMappingDefaults.yaml", 
"policy"))
 
  325             (
"images", imgMappingPolicy, ImageMapping),
 
  326             (
"exposures", expMappingPolicy, ExposureMapping),
 
  327             (
"calibrations", calMappingPolicy, CalibrationMapping),
 
  328             (
"datasets", dsMappingPolicy, DatasetMapping)
 
  331         for name, defPolicy, cls 
in mappingList:
 
  333                 datasets = policy[name]
 
  336                 defaultsPath = os.path.join(
getPackageDir(
"obs_base"), 
"policy", name + 
".yaml")
 
  337                 if os.path.exists(defaultsPath):
 
  341                 setattr(self, name, mappings)
 
  342                 for datasetType 
in datasets.names(
True):
 
  343                     subPolicy = datasets[datasetType]
 
  344                     subPolicy.merge(defPolicy)
 
  346                     if not hasattr(self, 
"map_" + datasetType) 
and 'composite' in subPolicy:
 
  347                         def compositeClosure(dataId, write=False, mapper=None, mapping=None,
 
  348                                              subPolicy=subPolicy):
 
  349                             components = subPolicy.get(
'composite')
 
  350                             assembler = subPolicy[
'assembler'] 
if 'assembler' in subPolicy 
else None 
  351                             disassembler = subPolicy[
'disassembler'] 
if 'disassembler' in subPolicy 
else None 
  352                             python = subPolicy[
'python']
 
  354                                                                          disassembler=disassembler,
 
  358                             for name, component 
in components.items():
 
  359                                 butlerComposite.add(id=name,
 
  360                                                     datasetType=component.get(
'datasetType'),
 
  361                                                     setter=component.get(
'setter', 
None),
 
  362                                                     getter=component.get(
'getter', 
None),
 
  363                                                     subset=component.get(
'subset', 
False),
 
  364                                                     inputOnly=component.get(
'inputOnly', 
False))
 
  365                             return butlerComposite
 
  366                         setattr(self, 
"map_" + datasetType, compositeClosure)
 
  370                     if name == 
"calibrations":
 
  372                                       provided=provided, dataRoot=rootStorage)
 
  374                         mapping = 
cls(datasetType, subPolicy, self.
registry, rootStorage, provided=provided)
 
  377                         raise ValueError(f
"Duplicate mapping policy for dataset type {datasetType}")
 
  378                     self.
keyDict.update(mapping.keys())
 
  379                     mappings[datasetType] = mapping
 
  380                     self.
mappings[datasetType] = mapping
 
  381                     if not hasattr(self, 
"map_" + datasetType):
 
  382                         def mapClosure(dataId, write=False, mapper=weakref.proxy(self), mapping=mapping):
 
  383                             return mapping.map(mapper, dataId, write)
 
  384                         setattr(self, 
"map_" + datasetType, mapClosure)
 
  385                     if not hasattr(self, 
"query_" + datasetType):
 
  386                         def queryClosure(format, dataId, mapping=mapping):
 
  387                             return mapping.lookup(format, dataId)
 
  388                         setattr(self, 
"query_" + datasetType, queryClosure)
 
  389                     if hasattr(mapping, 
"standardize") 
and not hasattr(self, 
"std_" + datasetType):
 
  390                         def stdClosure(item, dataId, mapper=weakref.proxy(self), mapping=mapping):
 
  391                             return mapping.standardize(mapper, item, dataId)
 
  392                         setattr(self, 
"std_" + datasetType, stdClosure)
 
  394                     def setMethods(suffix, mapImpl=None, bypassImpl=None, queryImpl=None):
 
  395                         """Set convenience methods on CameraMapper""" 
  396                         mapName = 
"map_" + datasetType + 
"_" + suffix
 
  397                         bypassName = 
"bypass_" + datasetType + 
"_" + suffix
 
  398                         queryName = 
"query_" + datasetType + 
"_" + suffix
 
  399                         if not hasattr(self, mapName):
 
  400                             setattr(self, mapName, mapImpl 
or getattr(self, 
"map_" + datasetType))
 
  401                         if not hasattr(self, bypassName):
 
  402                             if bypassImpl 
is None and hasattr(self, 
"bypass_" + datasetType):
 
  403                                 bypassImpl = getattr(self, 
"bypass_" + datasetType)
 
  404                             if bypassImpl 
is not None:
 
  405                                 setattr(self, bypassName, bypassImpl)
 
  406                         if not hasattr(self, queryName):
 
  407                             setattr(self, queryName, queryImpl 
or getattr(self, 
"query_" + datasetType))
 
  410                     setMethods(
"filename", bypassImpl=
lambda datasetType, pythonType, location, dataId:
 
  411                                [os.path.join(location.getStorage().root, p) 
for p 
in location.getLocations()])
 
  413                     if subPolicy[
"storage"] == 
"FitsStorage":  
 
  414                         def getMetadata(datasetType, pythonType, location, dataId):
 
  419                         setMethods(
"md", bypassImpl=getMetadata)
 
  422                         addName = 
"add_" + datasetType
 
  423                         if not hasattr(self, addName):
 
  426                         if name == 
"exposures":
 
  427                             def getSkyWcs(datasetType, pythonType, location, dataId):
 
  429                                 return fitsReader.readWcs()
 
  431                             setMethods(
"wcs", bypassImpl=getSkyWcs)
 
  433                             def getRawHeaderWcs(datasetType, pythonType, location, dataId):
 
  434                                 """Create a SkyWcs from the un-modified raw FITS WCS header keys.""" 
  435                                 if datasetType[:3] != 
"raw":
 
  440                             setMethods(
"header_wcs", bypassImpl=getRawHeaderWcs)
 
  442                             def getPhotoCalib(datasetType, pythonType, location, dataId):
 
  444                                 return fitsReader.readPhotoCalib()
 
  446                             setMethods(
"photoCalib", bypassImpl=getPhotoCalib)
 
  448                             def getVisitInfo(datasetType, pythonType, location, dataId):
 
  450                                 return fitsReader.readVisitInfo()
 
  452                             setMethods(
"visitInfo", bypassImpl=getVisitInfo)
 
  454                             def getFilter(datasetType, pythonType, location, dataId):
 
  456                                 return fitsReader.readFilter()
 
  458                             setMethods(
"filter", bypassImpl=getFilter)
 
  460                             setMethods(
"detector",
 
  461                                        mapImpl=
lambda dataId, write=
False:
 
  463                                                pythonType=
"lsst.afw.cameraGeom.CameraConfig",
 
  465                                                storageName=
"Internal",
 
  466                                                locationList=
"ignored",
 
  471                                        bypassImpl=
lambda datasetType, pythonType, location, dataId:
 
  475                             def getBBox(datasetType, pythonType, location, dataId):
 
  476                                 md = 
readMetadata(location.getLocationsWithRoot()[0], hdu=1)
 
  480                             setMethods(
"bbox", bypassImpl=getBBox)
 
  482                         elif name == 
"images":
 
  483                             def getBBox(datasetType, pythonType, location, dataId):
 
  487                             setMethods(
"bbox", bypassImpl=getBBox)
 
  489                     if subPolicy[
"storage"] == 
"FitsCatalogStorage":  
 
  491                         def getMetadata(datasetType, pythonType, location, dataId):
 
  492                             md = 
readMetadata(os.path.join(location.getStorage().root,
 
  493                                               location.getLocations()[0]), hdu=1)
 
  497                         setMethods(
"md", bypassImpl=getMetadata)
 
  500                     if subPolicy[
"storage"] == 
"FitsStorage":
 
  501                         def mapSubClosure(dataId, write=False, mapper=weakref.proxy(self), mapping=mapping):
 
  502                             subId = dataId.copy()
 
  504                             loc = mapping.map(mapper, subId, write)
 
  505                             bbox = dataId[
'bbox']
 
  506                             llcX = bbox.getMinX()
 
  507                             llcY = bbox.getMinY()
 
  508                             width = bbox.getWidth()
 
  509                             height = bbox.getHeight()
 
  510                             loc.additionalData.set(
'llcX', llcX)
 
  511                             loc.additionalData.set(
'llcY', llcY)
 
  512                             loc.additionalData.set(
'width', width)
 
  513                             loc.additionalData.set(
'height', height)
 
  514                             if 'imageOrigin' in dataId:
 
  515                                 loc.additionalData.set(
'imageOrigin',
 
  516                                                        dataId[
'imageOrigin'])
 
  519                         def querySubClosure(key, format, dataId, mapping=mapping):
 
  520                             subId = dataId.copy()
 
  522                             return mapping.lookup(format, subId)
 
  523                         setMethods(
"sub", mapImpl=mapSubClosure, queryImpl=querySubClosure)
 
  525                     if subPolicy[
"storage"] == 
"FitsCatalogStorage":
 
  528                         def getLen(datasetType, pythonType, location, dataId):
 
  529                             md = 
readMetadata(os.path.join(location.getStorage().root,
 
  530                                               location.getLocations()[0]), hdu=1)
 
  534                         setMethods(
"len", bypassImpl=getLen)
 
  537                         if not datasetType.endswith(
"_schema") 
and datasetType + 
"_schema" not in datasets:
 
  538                             setMethods(
"schema", bypassImpl=
lambda datasetType, pythonType, location, dataId:
 
  539                                        afwTable.Schema.readFits(os.path.join(location.getStorage().root,
 
  540                                                                              location.getLocations()[0])))
 
  542     def _computeCcdExposureId(self, dataId):
 
  543         """Compute the 64-bit (long) identifier for a CCD exposure. 
  545         Subclasses must override 
  550             Data identifier with visit, ccd. 
  552         raise NotImplementedError()
 
  554     def _computeCoaddExposureId(self, dataId, singleFilter):
 
  555         """Compute the 64-bit (long) identifier for a coadd. 
  557         Subclasses must override 
  562             Data identifier with tract and patch. 
  563         singleFilter  : `bool` 
  564             True means the desired ID is for a single-filter coadd, in which 
  565             case dataIdmust contain filter. 
  567         raise NotImplementedError()
 
  569     def _search(self, path):
 
  570         """Search for path in the associated repository's storage. 
  575             Path that describes an object in the repository associated with 
  577             Path may contain an HDU indicator, e.g. 'foo.fits[1]'. The 
  578             indicator will be stripped when searching and so will match 
  579             filenames without the HDU indicator, e.g. 'foo.fits'. The path 
  580             returned WILL contain the indicator though, e.g. ['foo.fits[1]']. 
  585             The path for this object in the repository. Will return None if the 
  586             object can't be found. If the input argument path contained an HDU 
  587             indicator, the returned path will also contain the HDU indicator. 
  592         """Rename any existing object with the given type and dataId. 
  594         The CameraMapper implementation saves objects in a sequence of e.g.: 
  600         All of the backups will be placed in the output repo, however, and will 
  601         not be removed if they are found elsewhere in the _parent chain.  This 
  602         means that the same file will be stored twice if the previous version 
  603         was found in an input repo. 
  612         def firstElement(list):
 
  613             """Get the first element in the list, or None if that can't be 
  616             return list[0] 
if list 
is not None and len(list) 
else None 
  619         newLocation = self.
map(datasetType, dataId, write=
True)
 
  620         newPath = newLocation.getLocations()[0]
 
  621         path = dafPersist.PosixStorage.search(self.
root, newPath, searchParents=
True)
 
  622         path = firstElement(path)
 
  624         while path 
is not None:
 
  626             oldPaths.append((n, path))
 
  627             path = dafPersist.PosixStorage.search(self.
root, 
"%s~%d" % (newPath, n), searchParents=
True)
 
  628             path = firstElement(path)
 
  629         for n, oldPath 
in reversed(oldPaths):
 
  630             self.
rootStorage.copyFile(oldPath, 
"%s~%d" % (newPath, n))
 
  633         """Return supported keys. 
  638             List of keys usable in a dataset identifier 
  643         """Return a dict of supported keys and their value types for a given 
  644         dataset type at a given level of the key hierarchy. 
  649             Dataset type or None for all dataset types. 
  650         level :  `str` or None 
  651             Level or None for all levels or '' for the default level for the 
  657             Keys are strings usable in a dataset identifier, values are their 
  665         if datasetType 
is None:
 
  666             keyDict = copy.copy(self.
keyDict)
 
  669         if level 
is not None and level 
in self.
levels:
 
  670             keyDict = copy.copy(keyDict)
 
  671             for lev 
in self.
levels[level]:
 
  686         """Return the name of the camera that this CameraMapper is for.""" 
  688         className = className[className.find(
'.'):-1]
 
  689         m = re.search(
r'(\w+)Mapper', className)
 
  691             m = re.search(
r"class '[\w.]*?(\w+)'", className)
 
  693         return name[:1].lower() + name[1:] 
if name 
else '' 
  697         """Return the name of the package containing this CameraMapper.""" 
  699             raise ValueError(
'class variable packageName must not be None')
 
  704         """Return the base directory of this package""" 
  708         """Map a camera dataset.""" 
  710             raise RuntimeError(
"No camera dataset available.")
 
  713             pythonType=
"lsst.afw.cameraGeom.CameraConfig",
 
  715             storageName=
"ConfigStorage",
 
  723         """Return the (preloaded) camera object. 
  726             raise RuntimeError(
"No camera dataset available.")
 
  731             pythonType=
"lsst.obs.base.ExposureIdInfo",
 
  733             storageName=
"Internal",
 
  734             locationList=
"ignored",
 
  741         """Hook to retrieve an lsst.obs.base.ExposureIdInfo for an exposure""" 
  742         expId = self.bypass_ccdExposureId(datasetType, pythonType, location, dataId)
 
  743         expBits = self.bypass_ccdExposureId_bits(datasetType, pythonType, location, dataId)
 
  747         """Disable standardization for bfKernel 
  749         bfKernel is a calibration product that is numpy array, 
  750         unlike other calibration products that are all images; 
  751         all calibration images are sent through _standardizeExposure 
  752         due to CalibrationMapping, but we don't want that to happen to bfKernel 
  757         """Standardize a raw dataset by converting it to an Exposure instead 
  760                                          trimmed=
False, setVisitInfo=
True)
 
  763         """Map a sky policy.""" 
  765                                          "Internal", 
None, 
None, self,
 
  769         """Standardize a sky policy by returning the one we use.""" 
  770         return self.skypolicy
 
  778     def _setupRegistry(self, name, description, path, policy, policyKey, storage, searchParents=True,
 
  780         """Set up a registry (usually SQLite3), trying a number of possible 
  788             Description of registry (for log messages) 
  792             Policy that contains the registry name, used if path is None. 
  794             Key in policy for registry path. 
  795         storage : Storage subclass 
  796             Repository Storage to look in. 
  797         searchParents : bool, optional 
  798             True if the search for a registry should follow any Butler v1 
  800         posixIfNoSql : bool, optional 
  801             If an sqlite registry is not found, will create a posix registry if 
  806         lsst.daf.persistence.Registry 
  809         if path 
is None and policyKey 
in policy:
 
  811             if os.path.isabs(path):
 
  812                 raise RuntimeError(
"Policy should not indicate an absolute path for registry.")
 
  813             if not storage.exists(path):
 
  814                 newPath = storage.instanceSearch(path)
 
  816                 newPath = newPath[0] 
if newPath 
is not None and len(newPath) 
else None 
  818                     self.
log.
warn(
"Unable to locate registry at policy path (also looked in root): %s",
 
  822                 self.
log.
warn(
"Unable to locate registry at policy path: %s", path)
 
  830             if path 
and (path.startswith(root)):
 
  831                 path = path[len(root + 
'/'):]
 
  832         except AttributeError:
 
  838         def search(filename, description):
 
  839             """Search for file in storage 
  844                 Filename to search for 
  846                 Description of file, for error message. 
  850             path : `str` or `None` 
  851                 Path to file, or None 
  853             result = storage.instanceSearch(filename)
 
  856             self.
log.
debug(
"Unable to locate %s: %s", description, filename)
 
  861             path = search(
"%s.pgsql" % name, 
"%s in root" % description)
 
  863             path = search(
"%s.sqlite3" % name, 
"%s in root" % description)
 
  865             path = search(os.path.join(
".", 
"%s.sqlite3" % name), 
"%s in current dir" % description)
 
  868             if not storage.exists(path):
 
  869                 newPath = storage.instanceSearch(path)
 
  870                 newPath = newPath[0] 
if newPath 
is not None and len(newPath) 
else None 
  871                 if newPath 
is not None:
 
  873             localFileObj = storage.getLocalFile(path)
 
  874             self.
log.
info(
"Loading %s registry from %s", description, localFileObj.name)
 
  875             registry = dafPersist.Registry.create(localFileObj.name)
 
  877         elif not registry 
and posixIfNoSql:
 
  879                 self.
log.
info(
"Loading Posix %s registry from %s", description, storage.root)
 
  886     def _transformId(self, dataId):
 
  887         """Generate a standard ID dict from a camera-specific ID dict. 
  889         Canonical keys include: 
  890         - amp: amplifier name 
  891         - ccd: CCD name (in LSST this is a combination of raft and sensor) 
  892         The default implementation returns a copy of its input. 
  897             Dataset identifier; this must not be modified 
  902             Transformed dataset identifier. 
  907     def _mapActualToPath(self, template, actualId):
 
  908         """Convert a template path to an actual path, using the actual data 
  909         identifier.  This implementation is usually sufficient but can be 
  910         overridden by the subclass. 
  927             return template % transformedId
 
  928         except Exception 
as e:
 
  929             raise RuntimeError(
"Failed to format %r with data %r: %s" % (template, transformedId, e))
 
  933         """Convert a CCD name to a form useful as a filename 
  935         The default implementation converts spaces to underscores. 
  937         return ccdName.replace(
" ", 
"_")
 
  939     def _extractDetectorName(self, dataId):
 
  940         """Extract the detector (CCD) name from the dataset identifier. 
  942         The name in question is the detector name used by lsst.afw.cameraGeom. 
  954         raise NotImplementedError(
"No _extractDetectorName() function specified")
 
  956     def _setAmpDetector(self, item, dataId, trimmed=True):
 
  957         """Set the detector object in an Exposure for an amplifier. 
  959         Defects are also added to the Exposure based on the detector object. 
  963         item : `lsst.afw.image.Exposure` 
  964             Exposure to set the detector in. 
  968             Should detector be marked as trimmed? (ignored) 
  973     def _setCcdDetector(self, item, dataId, trimmed=True):
 
  974         """Set the detector object in an Exposure for a CCD. 
  978         item : `lsst.afw.image.Exposure` 
  979             Exposure to set the detector in. 
  983             Should detector be marked as trimmed? (ignored) 
  985         if item.getDetector() 
is not None:
 
  989         detector = self.
camera[detectorName]
 
  990         item.setDetector(detector)
 
  992     def _setFilter(self, mapping, item, dataId):
 
  993         """Set the filter object in an Exposure.  If the Exposure had a FILTER 
  994         keyword, this was already processed during load.  But if it didn't, 
  995         use the filter from the registry. 
  999         mapping : `lsst.obs.base.Mapping` 
 1000             Where to get the filter from. 
 1001         item : `lsst.afw.image.Exposure` 
 1002             Exposure to set the filter in. 
 1007         if not (isinstance(item, afwImage.ExposureU) 
or isinstance(item, afwImage.ExposureI)
 
 1008                 or isinstance(item, afwImage.ExposureF) 
or isinstance(item, afwImage.ExposureD)):
 
 1011         if item.getFilter().getId() != afwImage.Filter.UNKNOWN:
 
 1014         actualId = mapping.need([
'filter'], dataId)
 
 1015         filterName = actualId[
'filter']
 
 1017             filterName = self.
filters[filterName]
 
 1021             self.
log.
warn(
"Filter %s not defined.  Set to UNKNOWN." % (filterName))
 
 1023     def _standardizeExposure(self, mapping, item, dataId, filter=True,
 
 1024                              trimmed=True, setVisitInfo=True):
 
 1025         """Default standardization function for images. 
 1027         This sets the Detector from the camera geometry 
 1028         and optionally set the Filter. In both cases this saves 
 1029         having to persist some data in each exposure (or image). 
 1033         mapping : `lsst.obs.base.Mapping` 
 1034             Where to get the values from. 
 1035         item : image-like object 
 1036             Can be any of lsst.afw.image.Exposure, 
 1037             lsst.afw.image.DecoratedImage, lsst.afw.image.Image 
 1038             or lsst.afw.image.MaskedImage 
 1043             Set filter? Ignored if item is already an exposure 
 1045             Should detector be marked as trimmed? 
 1046         setVisitInfo : `bool` 
 1047             Should Exposure have its VisitInfo filled out from the metadata? 
 1051         `lsst.afw.image.Exposure` 
 1052             The standardized Exposure. 
 1056                                          setVisitInfo=setVisitInfo)
 
 1057         except Exception 
as e:
 
 1058             self.
log.
error(
"Could not turn item=%r into an exposure: %s" % (repr(item), e))
 
 1061         if mapping.level.lower() == 
"amp":
 
 1063         elif mapping.level.lower() == 
"ccd":
 
 1069         if mapping.level.lower() != 
"amp" and exposure.getWcs() 
is None and \
 
 1070            (exposure.getInfo().getVisitInfo() 
is not None or exposure.getMetadata().toDict()):
 
 1078     def _createSkyWcsFromMetadata(self, exposure):
 
 1079         """Create a SkyWcs from the FITS header metadata in an Exposure. 
 1083         exposure : `lsst.afw.image.Exposure` 
 1084             The exposure to get metadata from, and attach the SkyWcs to. 
 1086         metadata = exposure.getMetadata()
 
 1090             exposure.setWcs(wcs)
 
 1093             self.
log.
debug(
"wcs set to None; missing information found in metadata to create a valid wcs:" 
 1096         exposure.setMetadata(metadata)
 
 1098     def _createInitialSkyWcs(self, exposure):
 
 1099         """Create a SkyWcs from the boresight and camera geometry. 
 1101         If the boresight or camera geometry do not support this method of 
 1102         WCS creation, this falls back on the header metadata-based version 
 1103         (typically a purely linear FITS crval/crpix/cdmatrix WCS). 
 1107         exposure : `lsst.afw.image.Exposure` 
 1108             The exposure to get data from, and attach the SkyWcs to. 
 1113         if exposure.getInfo().getVisitInfo() 
is None:
 
 1114             msg = 
"No VisitInfo; cannot access boresight information. Defaulting to metadata-based SkyWcs." 
 1118             newSkyWcs = 
createInitialSkyWcs(exposure.getInfo().getVisitInfo(), exposure.getDetector())
 
 1119             exposure.setWcs(newSkyWcs)
 
 1120         except InitialSkyWcsError 
as e:
 
 1121             msg = 
"Cannot create SkyWcs using VisitInfo and Detector, using metadata-based SkyWcs: %s" 
 1123             self.
log.
debug(
"Exception was: %s", traceback.TracebackException.from_exception(e))
 
 1124             if e.__context__ 
is not None:
 
 1125                 self.
log.
debug(
"Root-cause Exception was: %s",
 
 1126                                traceback.TracebackException.from_exception(e.__context__))
 
 1128     def _makeCamera(self, policy, repositoryDir):
 
 1129         """Make a camera (instance of lsst.afw.cameraGeom.Camera) describing 
 1132         Also set self.cameraDataLocation, if relevant (else it can be left 
 1135         This implementation assumes that policy contains an entry "camera" 
 1136         that points to the subdirectory in this package of camera data; 
 1137         specifically, that subdirectory must contain: 
 1138         - a file named `camera.py` that contains persisted camera config 
 1139         - ampInfo table FITS files, as required by 
 1140           lsst.afw.cameraGeom.makeCameraFromPath 
 1144         policy : `lsst.daf.persistence.Policy` 
 1145              Policy with per-camera defaults already merged 
 1146              (PexPolicy only for backward compatibility). 
 1147         repositoryDir : `str` 
 1148             Policy repository for the subclassing module (obtained with 
 1149             getRepositoryPath() on the per-camera default dictionary). 
 1151         if 'camera' not in policy:
 
 1152             raise RuntimeError(
"Cannot find 'camera' in policy; cannot construct a camera")
 
 1153         cameraDataSubdir = policy[
'camera']
 
 1155             os.path.join(repositoryDir, cameraDataSubdir, 
"camera.py"))
 
 1156         cameraConfig = afwCameraGeom.CameraConfig()
 
 1159         return afwCameraGeom.makeCameraFromPath(
 
 1160             cameraConfig=cameraConfig,
 
 1161             ampInfoPath=ampInfoPath,
 
 1167         """Get the registry used by this mapper. 
 1172             The registry used by this mapper for this mapper's repository. 
 1177         """Stuff image compression settings into a daf.base.PropertySet 
 1179         This goes into the ButlerLocation's "additionalData", which gets 
 1180         passed into the boost::persistence framework. 
 1185             Type of dataset for which to get the image compression settings. 
 1191         additionalData : `lsst.daf.base.PropertySet` 
 1192             Image compression settings. 
 1194         mapping = self.
mappings[datasetType]
 
 1195         recipeName = mapping.recipe
 
 1196         storageType = mapping.storage
 
 1200             raise RuntimeError(
"Unrecognized write recipe for datasetType %s (storage type %s): %s" %
 
 1201                                (datasetType, storageType, recipeName))
 
 1202         recipe = self.
_writeRecipes[storageType][recipeName].deepCopy()
 
 1203         seed = hash(tuple(dataId.items())) % 2**31
 
 1204         for plane 
in (
"image", 
"mask", 
"variance"):
 
 1205             if recipe.exists(plane + 
".scaling.seed") 
and recipe.getScalar(plane + 
".scaling.seed") == 0:
 
 1206                 recipe.set(plane + 
".scaling.seed", seed)
 
 1209     def _initWriteRecipes(self):
 
 1210         """Read the recipes for writing files 
 1212         These recipes are currently used for configuring FITS compression, 
 1213         but they could have wider uses for configuring different flavors 
 1214         of the storage types. A recipe is referred to by a symbolic name, 
 1215         which has associated settings. These settings are stored as a 
 1216         `PropertySet` so they can easily be passed down to the 
 1217         boost::persistence framework as the "additionalData" parameter. 
 1219         The list of recipes is written in YAML. A default recipe and 
 1220         some other convenient recipes are in obs_base/policy/writeRecipes.yaml 
 1221         and these may be overridden or supplemented by the individual obs_* 
 1222         packages' own policy/writeRecipes.yaml files. 
 1224         Recipes are grouped by the storage type. Currently, only the 
 1225         ``FitsStorage`` storage type uses recipes, which uses it to 
 1226         configure FITS image compression. 
 1228         Each ``FitsStorage`` recipe for FITS compression should define 
 1229         "image", "mask" and "variance" entries, each of which may contain 
 1230         "compression" and "scaling" entries. Defaults will be provided for 
 1231         any missing elements under "compression" and "scaling". 
 1233         The allowed entries under "compression" are: 
 1235         * algorithm (string): compression algorithm to use 
 1236         * rows (int): number of rows per tile (0 = entire dimension) 
 1237         * columns (int): number of columns per tile (0 = entire dimension) 
 1238         * quantizeLevel (float): cfitsio quantization level 
 1240         The allowed entries under "scaling" are: 
 1242         * algorithm (string): scaling algorithm to use 
 1243         * bitpix (int): bits per pixel (0,8,16,32,64,-32,-64) 
 1244         * fuzz (bool): fuzz the values when quantising floating-point values? 
 1245         * seed (long): seed for random number generator when fuzzing 
 1246         * maskPlanes (list of string): mask planes to ignore when doing 
 1248         * quantizeLevel: divisor of the standard deviation for STDEV_* scaling 
 1249         * quantizePad: number of stdev to allow on the low side (for 
 1250           STDEV_POSITIVE/NEGATIVE) 
 1251         * bscale: manually specified BSCALE (for MANUAL scaling) 
 1252         * bzero: manually specified BSCALE (for MANUAL scaling) 
 1254         A very simple example YAML recipe: 
 1260                     algorithm: GZIP_SHUFFLE 
 1264         recipesFile = os.path.join(
getPackageDir(
"obs_base"), 
"policy", 
"writeRecipes.yaml")
 
 1266         supplementsFile = os.path.join(self.
getPackageDir(), 
"policy", 
"writeRecipes.yaml")
 
 1267         validationMenu = {
'FitsStorage': validateRecipeFitsStorage, }
 
 1268         if os.path.exists(supplementsFile) 
and supplementsFile != recipesFile:
 
 1271             for entry 
in validationMenu:
 
 1272                 intersection = 
set(recipes[entry].names()).intersection(
set(supplements.names()))
 
 1274                     raise RuntimeError(
"Recipes provided in %s section %s may not override those in %s: %s" %
 
 1275                                        (supplementsFile, entry, recipesFile, intersection))
 
 1276             recipes.update(supplements)
 
 1279         for storageType 
in recipes.names(
True):
 
 1280             if "default" not in recipes[storageType]:
 
 1281                 raise RuntimeError(
"No 'default' recipe defined for storage type %s in %s" %
 
 1282                                    (storageType, recipesFile))
 
 1283             self.
_writeRecipes[storageType] = validationMenu[storageType](recipes[storageType])
 
 1287     """Generate an Exposure from an image-like object 
 1289     If the image is a DecoratedImage then also set its WCS and metadata 
 1290     (Image and MaskedImage are missing the necessary metadata 
 1291     and Exposure already has those set) 
 1295     image : Image-like object 
 1296         Can be one of lsst.afw.image.DecoratedImage, Image, MaskedImage or 
 1301     `lsst.afw.image.Exposure` 
 1302         Exposure containing input image. 
 1304     translatorClass = 
None 
 1305     if mapper 
is not None:
 
 1306         translatorClass = mapper.translatorClass
 
 1313         metadata = image.getMetadata()
 
 1314         fix_header(metadata, translator_class=translatorClass)
 
 1315         exposure.setMetadata(metadata)
 
 1318         metadata = exposure.getMetadata()
 
 1319         fix_header(metadata, translator_class=translatorClass)
 
 1324     if setVisitInfo 
and exposure.getInfo().getVisitInfo() 
is None:
 
 1325         if metadata 
is not None:
 
 1328                     logger = lsstLog.Log.getLogger(
"CameraMapper")
 
 1329                 logger.warn(
"I can only set the VisitInfo if you provide a mapper")
 
 1331                 exposureId = mapper._computeCcdExposureId(dataId)
 
 1332                 visitInfo = mapper.makeRawVisitInfo(md=metadata, exposureId=exposureId)
 
 1334                 exposure.getInfo().setVisitInfo(visitInfo)
 
 1340     """Validate recipes for FitsStorage 
 1342     The recipes are supplemented with default values where appropriate. 
 1344     TODO: replace this custom validation code with Cerberus (DM-11846) 
 1348     recipes : `lsst.daf.persistence.Policy` 
 1349         FitsStorage recipes to validate. 
 1353     validated : `lsst.daf.base.PropertySet` 
 1354         Validated FitsStorage recipe. 
 1359         If validation fails. 
 1363     compressionSchema = {
 
 1364         "algorithm": 
"NONE",
 
 1367         "quantizeLevel": 0.0,
 
 1370         "algorithm": 
"NONE",
 
 1372         "maskPlanes": [
"NO_DATA"],
 
 1374         "quantizeLevel": 4.0,
 
 1381     def checkUnrecognized(entry, allowed, description):
 
 1382         """Check to see if the entry contains unrecognised keywords""" 
 1383         unrecognized = 
set(entry.keys()) - 
set(allowed)
 
 1386                 "Unrecognized entries when parsing image compression recipe %s: %s" %
 
 1387                 (description, unrecognized))
 
 1390     for name 
in recipes.names(
True):
 
 1391         checkUnrecognized(recipes[name], [
"image", 
"mask", 
"variance"], name)
 
 1393         validated[name] = rr
 
 1394         for plane 
in (
"image", 
"mask", 
"variance"):
 
 1395             checkUnrecognized(recipes[name][plane], [
"compression", 
"scaling"],
 
 1396                               name + 
"->" + plane)
 
 1398             for settings, schema 
in ((
"compression", compressionSchema),
 
 1399                                      (
"scaling", scalingSchema)):
 
 1400                 prefix = plane + 
"." + settings
 
 1401                 if settings 
not in recipes[name][plane]:
 
 1403                         rr.set(prefix + 
"." + key, schema[key])
 
 1405                 entry = recipes[name][plane][settings]
 
 1406                 checkUnrecognized(entry, schema.keys(), name + 
"->" + plane + 
"->" + settings)
 
 1408                     value = 
type(schema[key])(entry[key]) 
if key 
in entry 
else schema[key]
 
 1409                     rr.set(prefix + 
"." + key, value)