Hide keyboard shortcuts

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 

22 

23__all__ = ["CalibRepoConverter"] 

24 

25import os 

26import sqlite3 

27import astropy.time 

28from typing import TYPE_CHECKING, Dict, Iterator, Tuple, Optional 

29 

30from .repoConverter import RepoConverter 

31from .repoWalker import RepoWalker 

32from .translators import makeCalibrationLabel 

33 

34if TYPE_CHECKING: 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true

35 from lsst.daf.butler import StorageClass, FormatterParameter 

36 from .repoWalker.scanner import PathElementHandler 

37 from ..cameraMapper import CameraMapper 

38 from ..mapping import Mapping as CameraMapperMapping # disambiguate from collections.abc.Mapping 

39 

40 

41class CalibRepoConverter(RepoConverter): 

42 """A specialization of `RepoConverter` for calibration repositories. 

43 

44 Parameters 

45 ---------- 

46 mapper : `CameraMapper` 

47 Gen2 mapper for the data repository. The root associated with the 

48 mapper is ignored and need not match the root of the repository. 

49 kwds 

50 Additional keyword arguments are forwarded to (and required by) 

51 `RepoConverter`. 

52 """ 

53 

54 def __init__(self, *, mapper: CameraMapper, **kwds): 

55 super().__init__(**kwds) 

56 self.mapper = mapper 

57 self._datasetTypes = set() 

58 

59 def isDatasetTypeSpecial(self, datasetTypeName: str) -> bool: 

60 # Docstring inherited from RepoConverter. 

61 return datasetTypeName in self.instrument.getCuratedCalibrationNames() 

62 

63 def iterMappings(self) -> Iterator[Tuple[str, CameraMapperMapping]]: 

64 # Docstring inherited from RepoConverter. 

65 yield from self.mapper.calibrations.items() 

66 

67 def makeRepoWalkerTarget(self, datasetTypeName: str, template: str, keys: Dict[str, type], 

68 storageClass: StorageClass, formatter: FormatterParameter = None, 

69 targetHandler: Optional[PathElementHandler] = None, 

70 ) -> RepoWalker.Target: 

71 # Docstring inherited from RepoConverter. 

72 target = RepoWalker.Target( 

73 datasetTypeName=datasetTypeName, 

74 storageClass=storageClass, 

75 template=template, 

76 keys=keys, 

77 instrument=self.task.instrument.getName(), 

78 universe=self.task.registry.dimensions, 

79 formatter=formatter, 

80 targetHandler=targetHandler, 

81 translatorFactory=self.task.translatorFactory, 

82 ) 

83 self._datasetTypes.add(target.datasetType) 

84 return target 

85 

86 def insertDimensionData(self): 

87 # Docstring inherited from RepoConverter. 

88 # This has only been tested on HSC, and it's not clear how general it 

89 # is. The catch is that it needs to generate calibration_label strings 

90 # consistent with those produced by the Translator system. 

91 

92 calibFile = os.path.join(self.root, "calibRegistry.sqlite3") 

93 

94 # If the registry file does not exist this indicates a problem. 

95 # We check explicitly because sqlite will try to create the 

96 # missing file if it can. 

97 if not os.path.exists(calibFile): 

98 raise RuntimeError("Attempting to convert calibrations but no registry database" 

99 f" found in {self.root}") 

100 db = sqlite3.connect(calibFile) 

101 db.row_factory = sqlite3.Row 

102 records = [] 

103 for datasetType in self._datasetTypes: 

104 if "calibration_label" not in datasetType.dimensions: 

105 continue 

106 fields = ["validStart", "validEnd", "calibDate"] 

107 if "detector" in datasetType.dimensions.names: 

108 fields.append(self.task.config.ccdKey) 

109 else: 

110 fields.append(f"NULL AS {self.task.config.ccdKey}") 

111 if ("physical_filter" in datasetType.dimensions.names 

112 or "band" in datasetType.dimensions.names): 

113 fields.append("filter") 

114 else: 

115 fields.append("NULL AS filter") 

116 query = f"SELECT DISTINCT {', '.join(fields)} FROM {datasetType.name};" 

117 try: 

118 results = db.execute(query) 

119 except sqlite3.OperationalError as e: 

120 if (self.mapper.mappings[datasetType.name].tables is None 

121 or len(self.mapper.mappings[datasetType.name].tables) == 0): 

122 self.task.log.warn("Could not extract calibration ranges for %s in %s: %r", 

123 datasetType.name, self.root, e) 

124 continue 

125 # Try using one of the alternate table names in the mapper (e.g. cpBias->bias for DECam). 

126 name = self.mapper.mappings[datasetType.name].tables[0] 

127 query = f"SELECT DISTINCT {', '.join(fields)} FROM {name};" 

128 try: 

129 results = db.execute(query) 

130 except sqlite3.OperationalError as e: 

131 self.task.log.warn("Could not extract calibration ranges for %s in %s: %r", 

132 datasetType.name, self.root, e) 

133 continue 

134 for row in results: 

135 label = makeCalibrationLabel(datasetType.name, row["calibDate"], 

136 ccd=row[self.task.config.ccdKey], filter=row["filter"]) 

137 # For validity times we use TAI as some gen2 repos have validity 

138 # dates very far in the past or future. 

139 day = astropy.time.TimeDelta(1, format="jd", scale="tai") 

140 records.append({ 

141 "instrument": self.task.instrument.getName(), 

142 "name": label, 

143 "datetime_begin": astropy.time.Time(row["validStart"], format="iso", scale="tai"), 

144 "datetime_end": astropy.time.Time(row["validEnd"], format="iso", scale="tai") + day 

145 }) 

146 if records: 

147 self.task.registry.insertDimensionData("calibration_label", *records) 

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 """