lsst.obs.base  19.0.0-24-g940be9f+1
calibRepoConverter.py
Go to the documentation of this file.
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/>.
21 from __future__ import annotations
22 
23 __all__ = ["CalibRepoConverter"]
24 
25 import os
26 import sqlite3
27 from datetime import datetime, timedelta
28 from typing import TYPE_CHECKING, Dict, Iterator, Tuple
29 
30 from lsst.daf.butler import Butler as Butler3
31 
32 from .repoConverter import RepoConverter
33 from .repoWalker import RepoWalker
34 from .translators import makeCalibrationLabel
35 
36 if TYPE_CHECKING:
37  from lsst.daf.butler import StorageClass
38  from ..cameraMapper import CameraMapper
39  from ..mapping import Mapping as CameraMapperMapping # disambiguate from collections.abc.Mapping
40 
41 
43  """A specialization of `RepoConverter` for calibration repositories.
44 
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  """
54 
55  def __init__(self, *, mapper: CameraMapper, **kwds):
56  super().__init__(**kwds)
57  self.mapper = mapper
58  self._datasetTypes = set()
59 
60  def isDatasetTypeSpecial(self, datasetTypeName: str) -> bool:
61  # Docstring inherited from RepoConverter.
62  return datasetTypeName in self.task.config.curatedCalibrations
63 
64  def iterMappings(self) -> Iterator[Tuple[str, CameraMapperMapping]]:
65  # Docstring inherited from RepoConverter.
66  yield from self.mapper.calibrations.items()
67 
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
81 
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)
132 
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()
148 
149  # Class attributes that will be shadowed by public instance attributes;
150  # defined here only for documentation purposes.
151 
152  mapper: CameraMapper
153  """Gen2 mapper associated with this repository.
154  """