Coverage for python/lsst/obs/base/gen2to3/calibRepoConverter.py : 23%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of obs_base.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
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 GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21from __future__ import annotations
23__all__ = ["CalibRepoConverter"]
25import os
26import sqlite3
27from datetime import datetime, timedelta
28from typing import TYPE_CHECKING, Dict, Iterator, Tuple
30from lsst.daf.butler import Butler as Butler3
32from .repoConverter import RepoConverter
33from .repoWalker import RepoWalker
34from .translators import makeCalibrationLabel
36if TYPE_CHECKING: 36 ↛ 37line 36 didn't jump to line 37, because the condition on line 36 was never true
37 from lsst.daf.butler import StorageClass
38 from ..cameraMapper import CameraMapper
39 from ..mapping import Mapping as CameraMapperMapping # disambiguate from collections.abc.Mapping
42class CalibRepoConverter(RepoConverter):
43 """A specialization of `RepoConverter` for calibration repositories.
45 Parameters
46 ----------
47 mapper : `CameraMapper`
48 Gen2 mapper for the data repository. The root associated with the
49 mapper is ignored and need not match the root of the repository.
50 kwds
51 Additional keyword arguments are forwarded to (and required by)
52 `RepoConverter`.
53 """
55 def __init__(self, *, mapper: CameraMapper, **kwds):
56 super().__init__(**kwds)
57 self.mapper = mapper
58 self._datasetTypes = set()
60 def isDatasetTypeSpecial(self, datasetTypeName: str) -> bool:
61 # Docstring inherited from RepoConverter.
62 return datasetTypeName in self.task.config.curatedCalibrations
64 def iterMappings(self) -> Iterator[Tuple[str, CameraMapperMapping]]:
65 # Docstring inherited from RepoConverter.
66 yield from self.mapper.calibrations.items()
68 def makeRepoWalkerTarget(self, datasetTypeName: str, template: str, keys: Dict[str, type],
69 storageClass: StorageClass) -> RepoWalker.Target:
70 # Docstring inherited from RepoConverter.
71 target = RepoWalker.Target(
72 datasetTypeName=datasetTypeName,
73 storageClass=storageClass,
74 template=template,
75 keys=keys,
76 instrument=self.task.instrument.getName(),
77 universe=self.task.registry.dimensions,
78 )
79 self._datasetTypes.add(target.datasetType)
80 return target
82 def insertDimensionData(self):
83 # Docstring inherited from RepoConverter.
84 # This has only been tested on HSC, and it's not clear how general it
85 # is. The catch is that it needs to generate calibration_label strings
86 # consistent with those produced by the Translator system.
87 db = sqlite3.connect(os.path.join(self.root, "calibRegistry.sqlite3"))
88 db.row_factory = sqlite3.Row
89 records = []
90 for datasetType in self._datasetTypes:
91 if "calibration_label" not in datasetType.dimensions:
92 continue
93 fields = ["validStart", "validEnd", "calibDate"]
94 if "detector" in datasetType.dimensions.names:
95 fields.append(self.task.config.ccdKey)
96 else:
97 fields.append(f"NULL AS {self.task.config.ccdKey}")
98 if ("physical_filter" in datasetType.dimensions.names
99 or "abstract_filter" in datasetType.dimensions.names):
100 fields.append("filter")
101 else:
102 fields.append("NULL AS filter")
103 query = f"SELECT DISTINCT {', '.join(fields)} FROM {datasetType.name};"
104 try:
105 results = db.execute(query)
106 except sqlite3.OperationalError as e:
107 if (self.mapper.mappings[datasetType.name].tables is None
108 or len(self.mapper.mappings[datasetType.name].tables) == 0):
109 self.task.log.warn("Could not extract calibration ranges for %s in %s: %r",
110 datasetType.name, self.root, e)
111 continue
112 # Try using one of the alternate table names in the mapper (e.g. cpBias->bias for DECam).
113 name = self.mapper.mappings[datasetType.name].tables[0]
114 query = f"SELECT DISTINCT {', '.join(fields)} FROM {name};"
115 try:
116 results = db.execute(query)
117 except sqlite3.OperationalError as e:
118 self.task.log.warn("Could not extract calibration ranges for %s in %s: %r",
119 datasetType.name, self.root, e)
120 continue
121 for row in results:
122 label = makeCalibrationLabel(datasetType.name, row["calibDate"],
123 ccd=row[self.task.config.ccdKey], filter=row["filter"])
124 records.append({
125 "instrument": self.task.instrument.getName(),
126 "name": label,
127 "datetime_begin": datetime.strptime(row["validStart"], "%Y-%m-%d"),
128 "datetime_end": datetime.strptime(row["validEnd"], "%Y-%m-%d") + timedelta(days=1),
129 })
130 if records:
131 self.task.registry.insertDimensionData("calibration_label", *records)
133 def ingest(self):
134 # Docstring inherited from RepoConverter.
135 if self.task.config.doWriteCuratedCalibrations:
136 try:
137 collections = self.getCollections(None)
138 except LookupError as err:
139 raise ValueError("Cannot ingest curated calibration into a calibration repo with no "
140 "collections of its own; skipping.") from err
141 # TODO: associate the curated calibrations with any other
142 # collections and remove this assert; blocker is DM-23230.
143 assert len(collections) == 1, \
144 "Multiple collections for curated calibrations is not yet supported."
145 butler3 = Butler3(butler=self.task.butler3, run=collections[0])
146 self.task.instrument.writeCuratedCalibrations(butler3)
147 super().ingest()
149 # Class attributes that will be shadowed by public instance attributes;
150 # defined here only for documentation purposes.
152 mapper: CameraMapper
153 """Gen2 mapper associated with this repository.
154 """