lsst.obs.base  19.0.0-18-g955d782+4
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 .repoConverter import RepoConverter
31 from .repoWalker import RepoWalker
32 from .translators import makeCalibrationLabel
33 
34 if TYPE_CHECKING:
35  from lsst.daf.butler import StorageClass
36  from ..cameraMapper import CameraMapper
37  from ..mapping import Mapping as CameraMapperMapping # disambiguate from collections.abc.Mapping
38 
39 CURATED_CALIBRATION_DATASET_TYPES = (
40  "defects",
41  "camera",
42  "transmission_sensor",
43  "transmission_filter",
44  "transmission_optics",
45  "transmission_atmosphere",
46  "bfKernel"
47 )
48 
49 
51  """A specialization of `RepoConverter` for calibration repositories.
52 
53  Parameters
54  ----------
55  mapper : `CameraMapper`
56  Gen2 mapper for the data repository. The root associated with the
57  mapper is ignored and need not match the root of the repository.
58  kwds
59  Additional keyword arguments are forwarded to (and required by)
60  `RepoConverter`.
61  """
62 
63  def __init__(self, *, mapper: CameraMapper, **kwds):
64  super().__init__(**kwds)
65  self.mapper = mapper
66  self._datasetTypes = []
67 
68  def isDatasetTypeSpecial(self, datasetTypeName: str) -> bool:
69  # Docstring inherited from RepoConverter.
70  return datasetTypeName in CURATED_CALIBRATION_DATASET_TYPES
71 
72  def iterMappings(self) -> Iterator[Tuple[str, CameraMapperMapping]]:
73  # Docstring inherited from RepoConverter.
74  yield from self.mapper.calibrations.items()
75 
76  def makeRepoWalkerTarget(self, datasetTypeName: str, template: str, keys: Dict[str, type],
77  storageClass: StorageClass) -> RepoWalker.Target:
78  # Docstring inherited from RepoConverter.
79  target = RepoWalker.Target(
80  datasetTypeName=datasetTypeName,
81  storageClass=storageClass,
82  template=template,
83  keys=keys,
84  instrument=self.task.instrument.getName(),
85  universe=self.task.registry.dimensions,
86  )
87  self._datasetTypes.append(target.datasetType)
88  return target
89 
91  # Docstring inherited from RepoConverter.
92  # This has only been tested on HSC, and it's not clear how general it
93  # is. The catch is that it needs to generate calibration_label strings
94  # consistent with those produced by the Translator system.
95  db = sqlite3.connect(os.path.join(self.root, "calibRegistry.sqlite3"))
96  db.row_factory = sqlite3.Row
97  records = []
98  for datasetType in self._datasetTypes:
99  if "calibration_label" not in datasetType.dimensions:
100  continue
101  fields = ["validStart", "validEnd", "calibDate"]
102  if "detector" in datasetType.dimensions.names:
103  fields.append(self.task.config.ccdKey)
104  else:
105  fields.append(f"NULL AS {self.task.config.ccdKey}")
106  if "physical_filter" in datasetType.dimensions.names:
107  fields.append("filter")
108  else:
109  fields.append("NULL AS filter")
110  query = f"SELECT DISTINCT {', '.join(fields)} FROM {datasetType.name};"
111  try:
112  results = db.execute(query)
113  except sqlite3.OperationalError:
114  self.task.log.warn("Could not extract calibration ranges for %s in %s.",
115  datasetType.name, self.root)
116  continue
117  for row in results:
118  label = makeCalibrationLabel(datasetType.name, row["calibDate"],
119  ccd=row[self.task.config.ccdKey], filter=row["filter"])
120  records.append({
121  "instrument": self.task.instrument.getName(),
122  "name": label,
123  "datetime_begin": datetime.strptime(row["validStart"], "%Y-%m-%d"),
124  "datetime_end": datetime.strptime(row["validEnd"], "%Y-%m-%d") + timedelta(days=1),
125  })
126  if records:
127  self.task.registry.insertDimensionData("calibration_label", *records)
128 
129  def ingest(self):
130  # Docstring inherited from RepoConverter.
131  if self.task.config.doWriteCuratedCalibrations:
132  try:
133  butler3, collections = self.getButler(None)
134  except LookupError as err:
135  raise ValueError("Cannot ingest curated calibration into a calibration repo with no "
136  "collections of its own; skipping.") from err
137  # TODO: associate the curated calibrations with any other
138  # collections and remove this assert.
139  assert not collections, "Multiple collections for curated calibrations is not yet supported."
140  self.task.instrument.writeCuratedCalibrations(butler3)
141  super().ingest()
142 
143  # Class attributes that will be shadowed by public instance attributes;
144  # defined here only for documentation purposes.
145 
146  mapper: CameraMapper
147  """Gen2 mapper associated with this repository.
148  """