lsst.obs.base  19.0.0-43-gbcf6a3c
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 import astropy.time
28 from typing import TYPE_CHECKING, Dict, Iterator, Tuple, Optional
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, FormatterParameter
38  from .repoWalker.scanner import PathElementHandler
39  from ..cameraMapper import CameraMapper
40  from ..mapping import Mapping as CameraMapperMapping # disambiguate from collections.abc.Mapping
41 
42 
44  """A specialization of `RepoConverter` for calibration repositories.
45 
46  Parameters
47  ----------
48  mapper : `CameraMapper`
49  Gen2 mapper for the data repository. The root associated with the
50  mapper is ignored and need not match the root of the repository.
51  kwds
52  Additional keyword arguments are forwarded to (and required by)
53  `RepoConverter`.
54  """
55 
56  def __init__(self, *, mapper: CameraMapper, **kwds):
57  super().__init__(**kwds)
58  self.mapper = mapper
59  self._datasetTypes = set()
60 
61  def isDatasetTypeSpecial(self, datasetTypeName: str) -> bool:
62  # Docstring inherited from RepoConverter.
63  return datasetTypeName in self.task.config.curatedCalibrations
64 
65  def iterMappings(self) -> Iterator[Tuple[str, CameraMapperMapping]]:
66  # Docstring inherited from RepoConverter.
67  yield from self.mapper.calibrations.items()
68 
69  def makeRepoWalkerTarget(self, datasetTypeName: str, template: str, keys: Dict[str, type],
70  storageClass: StorageClass, formatter: FormatterParameter = None,
71  targetHandler: Optional[PathElementHandler] = None,
72  ) -> RepoWalker.Target:
73  # Docstring inherited from RepoConverter.
74  target = RepoWalker.Target(
75  datasetTypeName=datasetTypeName,
76  storageClass=storageClass,
77  template=template,
78  keys=keys,
79  instrument=self.task.instrument.getName(),
80  universe=self.task.registry.dimensions,
81  formatter=formatter,
82  targetHandler=targetHandler,
83  )
84  self._datasetTypes.add(target.datasetType)
85  return target
86 
88  # Docstring inherited from RepoConverter.
89  # This has only been tested on HSC, and it's not clear how general it
90  # is. The catch is that it needs to generate calibration_label strings
91  # consistent with those produced by the Translator system.
92  db = sqlite3.connect(os.path.join(self.root, "calibRegistry.sqlite3"))
93  db.row_factory = sqlite3.Row
94  records = []
95  for datasetType in self._datasetTypes:
96  if "calibration_label" not in datasetType.dimensions:
97  continue
98  fields = ["validStart", "validEnd", "calibDate"]
99  if "detector" in datasetType.dimensions.names:
100  fields.append(self.task.config.ccdKey)
101  else:
102  fields.append(f"NULL AS {self.task.config.ccdKey}")
103  if ("physical_filter" in datasetType.dimensions.names
104  or "abstract_filter" in datasetType.dimensions.names):
105  fields.append("filter")
106  else:
107  fields.append("NULL AS filter")
108  query = f"SELECT DISTINCT {', '.join(fields)} FROM {datasetType.name};"
109  try:
110  results = db.execute(query)
111  except sqlite3.OperationalError as e:
112  if (self.mapper.mappings[datasetType.name].tables is None
113  or len(self.mapper.mappings[datasetType.name].tables) == 0):
114  self.task.log.warn("Could not extract calibration ranges for %s in %s: %r",
115  datasetType.name, self.root, e)
116  continue
117  # Try using one of the alternate table names in the mapper (e.g. cpBias->bias for DECam).
118  name = self.mapper.mappings[datasetType.name].tables[0]
119  query = f"SELECT DISTINCT {', '.join(fields)} FROM {name};"
120  try:
121  results = db.execute(query)
122  except sqlite3.OperationalError as e:
123  self.task.log.warn("Could not extract calibration ranges for %s in %s: %r",
124  datasetType.name, self.root, e)
125  continue
126  for row in results:
127  label = makeCalibrationLabel(datasetType.name, row["calibDate"],
128  ccd=row[self.task.config.ccdKey], filter=row["filter"])
129  # For validity times we use TAI as some gen2 repos have validity
130  # dates very far in the past or future.
131  day = astropy.time.TimeDelta(1, format="jd", scale="tai")
132  records.append({
133  "instrument": self.task.instrument.getName(),
134  "name": label,
135  "datetime_begin": astropy.time.Time(row["validStart"], format="iso", scale="tai"),
136  "datetime_end": astropy.time.Time(row["validEnd"], format="iso", scale="tai") + day
137  })
138  if records:
139  self.task.registry.insertDimensionData("calibration_label", *records)
140 
141  def ingest(self):
142  # Docstring inherited from RepoConverter.
143  if self.task.config.doWriteCuratedCalibrations:
144  butler3 = Butler3(butler=self.task.butler3, run=self._run)
145  self.task.instrument.writeCuratedCalibrations(butler3)
146  super().ingest()
147 
148  # Class attributes that will be shadowed by public instance attributes;
149  # defined here only for documentation purposes.
150 
151  mapper: CameraMapper
152  """Gen2 mapper associated with this repository.
153  """