LSSTApplications  18.0.0+106,18.0.0+50,19.0.0,19.0.0+1,19.0.0+10,19.0.0+11,19.0.0+13,19.0.0+17,19.0.0+2,19.0.0-1-g20d9b18+6,19.0.0-1-g425ff20,19.0.0-1-g5549ca4,19.0.0-1-g580fafe+6,19.0.0-1-g6fe20d0+1,19.0.0-1-g7011481+9,19.0.0-1-g8c57eb9+6,19.0.0-1-gb5175dc+11,19.0.0-1-gdc0e4a7+9,19.0.0-1-ge272bc4+6,19.0.0-1-ge3aa853,19.0.0-10-g448f008b,19.0.0-12-g6990b2c,19.0.0-2-g0d9f9cd+11,19.0.0-2-g3d9e4fb2+11,19.0.0-2-g5037de4,19.0.0-2-gb96a1c4+3,19.0.0-2-gd955cfd+15,19.0.0-3-g2d13df8,19.0.0-3-g6f3c7dc,19.0.0-4-g725f80e+11,19.0.0-4-ga671dab3b+1,19.0.0-4-gad373c5+3,19.0.0-5-ga2acb9c+2,19.0.0-5-gfe96e6c+2,w.2020.01
LSSTDataManagementBasePackage
translators.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 
22 __all__ = ("Translator", "KeyHandler", "CopyKeyHandler", "ConstantKeyHandler",
23  "makeCalibrationLabel")
24 
25 import itertools
26 from typing import Optional, Any, Dict, Tuple, FrozenSet, Iterable, List
27 from abc import ABCMeta, abstractmethod
28 
29 from lsst.skymap import BaseSkyMap
30 
31 
32 def makeCalibrationLabel(datasetTypeName: str, calibDate: str, ccd: Optional[int] = None,
33  filter: Optional[str] = None) -> str:
34  """Make a Gen3 calibration_label string corresponding to a Gen2 data ID.
35 
36  Parameters
37  ----------
38  datasetTypeName : `str`
39  Name of the dataset type this calibration label identifies.
40  calibDate : `str`
41  Date string used in the Gen2 template.
42  ccd : `int`, optional
43  Detector ID used in the Gen2 template.
44  filter : `str`, optional
45  Filter used in the Gen2 template.
46 
47  Returns
48  -------
49  label : `str`
50  Calibration label string.
51  """
52  # TODO: this function is probably HSC-specific, but I don't know how other
53  # obs calib registries behave so I don't know (yet) how to generalize it.
54  elements = [datasetTypeName, calibDate]
55  if ccd is not None:
56  elements.append(f"{ccd:03d}")
57  if filter is not None:
58  elements.append(filter)
59  return "gen2/{}".format("_".join(elements))
60 
61 
62 class KeyHandler(metaclass=ABCMeta):
63  """Base class for Translator helpers that each handle just one Gen3 Data
64  ID key.
65 
66  Parameters
67  ----------
68  dimension : `str`
69  Name of the Gen3 dimension (data ID key) populated by
70  this handler (e.g. "visit" or "abstract_filter").
71  """
72  def __init__(self, dimension: str):
73 
74  self.dimension = dimension
75  __slots__ = ("dimension",)
76 
77  def translate(self, gen2id: dict, gen3id: dict,
78  skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
79  datasetTypeName: str):
80  """Update a Gen3 data ID dict with a single key-value pair from a Gen2
81  data ID.
82 
83  This method is implemented by the base class and is not expected to
84  be re-implemented by subclasses.
85 
86  Parameters
87  ----------
88  gen2id: `dict`
89  Gen2 data ID from which to draw key-value pairs from.
90  gen3id: `dict`
91  Gen3 data ID to update in-place.
92  skyMap: `BaseSkyMap`, optional
93  SkyMap that defines the tracts and patches used in the Gen2 data
94  ID, if any.
95  skyMapName: `str`
96  Name of the Gen3 skymap dimension that defines the tracts and
97  patches used in the Gen3 data ID.
98  datasetTypeName: `str`
99  Name of the dataset type.
100  """
101  gen3id[self.dimension] = self.extract(gen2id, skyMap=skyMap, skyMapName=skyMapName,
102  datasetTypeName=datasetTypeName)
103 
104  @abstractmethod
105  def extract(self, gen2id: dict, skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
106  datasetTypeName: str) -> Any:
107  """Extract a Gen3 data ID value from a Gen2 data ID.
108 
109  Parameters
110  ----------
111  gen2id: `dict`
112  Gen2 data ID from which to draw key-value pairs from.
113  skyMap: `BaseSkyMap`, optional
114  SkyMap that defines the tracts and patches used in the Gen2 data
115  ID, if any.
116  skyMapName: `str`
117  Name of the Gen3 skymap dimension that defines the tracts and
118  patches used in the Gen3 data ID.
119  datasetTypeName: `str`
120  Name of the dataset type.
121  """
122  raise NotImplementedError()
123 
124 
126  """A KeyHandler that adds a constant key-value pair to the Gen3 data ID.
127 
128  Parameters
129  ----------
130  dimension : `str`
131  Name of the Gen3 dimension (data ID key) populated by
132  this handler (e.g. "visit" or "abstract_filter").
133  value : `object`
134  Data ID value.
135  """
136  def __init__(self, dimension: str, value: Any):
137  super().__init__(dimension)
138  self.value = value
139 
140  __slots__ = ("value",)
141 
142  def extract(self, gen2id: dict, skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
143  datasetTypeName: str) -> Any:
144  # Docstring inherited from KeyHandler.extract.
145  return self.value
146 
147 
149  """A KeyHandler that simply copies a value from a Gen3 data ID.
150 
151  Parameters
152  ----------
153  dimension : `str`
154  Name of the Gen3 dimension produced by this handler.
155  dtype : `type`, optional
156  If not `None`, the type that values for this key must be an
157  instance of.
158  """
159  def __init__(self, dimension: str, gen2key: Optional[str] = None,
160  dtype: Optional[type] = None):
161  super().__init__(dimension)
162  self.gen2key = gen2key if gen2key is not None else dimension
163  self.dtype = dtype
164 
165  __slots__ = ("gen2key", "dtype")
166 
167  def extract(self, gen2id: dict, skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
168  datasetTypeName: str) -> Any:
169  # Docstring inherited from KeyHandler.extract.
170  r = gen2id[self.gen2key]
171  if self.dtype is not None:
172  try:
173  r = self.dtype(r)
174  except ValueError as err:
175  raise TypeError(
176  f"'{r}' is not a valid value for {self.dimension}; "
177  f"expected {self.dtype.__name__}, got {type(r).__name__}."
178  ) from err
179  return r
180 
181 
183  """A KeyHandler for skymap patches.
184  """
185  def __init__(self):
186  super().__init__("patch")
187 
188  __slots__ = ()
189 
190  def extract(self, gen2id: dict, skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
191  datasetTypeName: str) -> Any:
192  # Docstring inherited from KeyHandler.extract.
193  tract = gen2id["tract"]
194  tractInfo = skyMap[tract]
195  x, y = gen2id["patch"].split(",")
196  patchInfo = tractInfo[int(x), int(y)]
197  return tractInfo.getSequentialPatchIndex(patchInfo)
198 
199 
201  """A KeyHandler for skymaps."""
202  def __init__(self):
203  super().__init__("skymap")
204 
205  __slots__ = ()
206 
207  def extract(self, gen2id: dict, skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
208  datasetTypeName: str) -> Any:
209  # Docstring inherited from KeyHandler.extract.
210  return skyMapName
211 
212 
214  """A KeyHandler for master calibration datasets.
215  """
216 
217  def __init__(self):
218  super().__init__("calibration_label")
219 
220  __slots__ = ()
221 
222  def extract(self, gen2id: dict, skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
223  datasetTypeName: str) -> Any:
224  # Docstring inherited from KeyHandler.extract.
225  return makeCalibrationLabel(datasetTypeName, gen2id["calibDate"],
226  ccd=gen2id.get("ccd"), filter=gen2id.get("filter"))
227 
228 
230  """Callable object that translates Gen2 Data IDs to Gen3 Data IDs for a
231  particular DatasetType.
232 
233  Translators should usually be constructed via the `makeMatching` method.
234 
235  Parameters
236  ----------
237  handlers : `list`
238  A list of KeyHandlers this Translator should use.
239  skyMap : `BaseSkyMap`, optional
240  SkyMap instance used to define any tract or patch Dimensions.
241  skyMapName : `str`
242  Gen3 SkyMap Dimension name to be associated with any tract or patch
243  Dimensions.
244  datasetTypeName : `str`
245  Name of the dataset type whose data IDs this translator handles.
246  """
247  def __init__(self, handlers: List[KeyHandler], skyMap: Optional[BaseSkyMap], skyMapName: Optional[str],
248  datasetTypeName: str):
249  self.handlers = handlers
250  self.skyMap = skyMap
251  self.skyMapName = skyMapName
252  self.datasetTypeName = datasetTypeName
253 
254  __slots__ = ("handlers", "skyMap", "skyMapName", "datasetTypeName")
255 
256  # Rules used to match Handlers when constring a Translator.
257  # outer key is instrument name, or None for any
258  # inner key is DatasetType name, or None for any
259  # values are 3-tuples of (frozenset(gen2keys), handler, consume)
260  _rules: Dict[
261  Optional[str],
262  Dict[
263  Optional[str],
264  Tuple[FrozenSet[str], KeyHandler, bool]
265  ]
266  ] = {
267  None: {
268  None: []
269  }
270  }
271 
272  @classmethod
273  def addRule(cls, handler: KeyHandler, instrument: Optional[str] = None,
274  datasetTypeName: Optional[str] = None, gen2keys: Iterable[str] = (),
275  consume: bool = True):
276  """Add a KeyHandler and an associated matching rule.
277 
278  Parameters
279  ----------
280  handler : `KeyHandler`
281  A KeyHandler instance to add to a Translator when this rule
282  matches.
283  instrument : `str`
284  Gen3 instrument name the Gen2 repository must be associated with
285  for this rule to match, or None to match any instrument.
286  datasetTypeName : `str`
287  Name of the DatasetType this rule matches, or None to match any
288  DatasetType.
289  gen2Keys : sequence
290  Sequence of Gen2 data ID keys that must all be present for this
291  rule to match.
292  consume : `bool` or `tuple`
293  If True (default), remove all entries in gen2keys from the set of
294  keys being matched to in order to prevent less-specific handlers
295  from matching them.
296  May also be a `tuple` listing only the keys to consume.
297  """
298  # Ensure consume is always a frozenset, so we can process it uniformly
299  # from here on.
300  if consume is True:
301  consume = frozenset(gen2keys)
302  elif consume:
303  consume = frozenset(consume)
304  else:
305  consume = frozenset()
306  # find the rules for this instrument, or if we haven't seen it before,
307  # add a nested dictionary that matches any DatasetType name and then
308  # append this rule.
309  rulesForInstrument = cls._rules.setdefault(instrument, {None: []})
310  rulesForInstrumentAndDatasetType = rulesForInstrument.setdefault(datasetTypeName, [])
311  rulesForInstrumentAndDatasetType.append((frozenset(gen2keys), handler, consume))
312 
313  @classmethod
314  def makeMatching(cls, datasetTypeName: str, gen2keys: Dict[str, type], instrument: Optional[str] = None,
315  skyMap: Optional[BaseSkyMap] = None, skyMapName: Optional[str] = None):
316  """Construct a Translator appropriate for instances of the given
317  dataset.
318 
319  Parameters
320  ----------
321  datasetTypeName : `str`
322  Name of the dataset type.
323  gen2keys: `dict`
324  Keys of a Gen2 data ID for this dataset.
325  instrument: `str`, optional
326  Name of the Gen3 instrument dimension for translated data IDs.
327  skyMap: `~lsst.skymap.BaseSkyMap`, optional
328  The skymap instance that defines any tract/patch data IDs.
329  `~lsst.skymap.BaseSkyMap` instances.
330  skyMapName : `str`, optional
331  Gen3 SkyMap Dimension name to be associated with any tract or patch
332  Dimensions.
333 
334  Returns
335  -------
336  translator : `Translator`
337  A translator whose translate() method can be used to transform Gen2
338  data IDs to Gen3 dataIds.
339  """
340  if instrument is not None:
341  rulesForInstrument = cls._rules.get(instrument, {None: []})
342  else:
343  rulesForInstrument = {None: []}
344  rulesForAnyInstrument = cls._rules[None]
345  candidateRules = itertools.chain(
346  rulesForInstrument.get(datasetTypeName, []), # this instrument, this DatasetType
347  rulesForInstrument[None], # this instrument, any DatasetType
348  rulesForAnyInstrument.get(datasetTypeName, []), # any instrument, this DatasetType
349  rulesForAnyInstrument[None], # any instrument, any DatasetType
350  )
351  matchedHandlers = []
352  targetKeys = set(gen2keys)
353  for ruleKeys, ruleHandlers, consume in candidateRules:
354  if ruleKeys.issubset(targetKeys):
355  matchedHandlers.append(ruleHandlers)
356  targetKeys -= consume
357  return Translator(matchedHandlers, skyMap=skyMap, skyMapName=skyMapName,
358  datasetTypeName=datasetTypeName)
359 
360  def __call__(self, gen2id):
361  """Return a Gen3 data ID that corresponds to the given Gen2 data ID.
362  """
363  gen3id = {}
364  for handler in self.handlers:
365  handler.translate(gen2id, gen3id, skyMap=self.skyMap, skyMapName=self.skyMapName,
366  datasetTypeName=self.datasetTypeName)
367  return gen3id
368 
369  @property
370  def dimensionNames(self):
371  """The names of the dimensions populated by this Translator
372  (`frozenset`).
373  """
374  return frozenset(h.dimension for h in self.handlers)
375 
376 
377 # Add "skymap" to Gen3 ID if Gen2 ID has a "tract" key.
378 Translator.addRule(SkyMapKeyHandler(), gen2keys=("tract",), consume=False)
379 
380 # Add "skymap" to Gen3 ID if DatasetType is one of a few specific ones
381 for coaddName in ("deep", "goodSeeing", "psfMatched", "dcr"):
382  Translator.addRule(SkyMapKeyHandler(), datasetTypeName=f"{coaddName}Coadd_skyMap")
383 
384 # Translate Gen2 str patch IDs to Gen3 sequential integers.
385 Translator.addRule(PatchKeyHandler(), gen2keys=("patch",))
386 
387 # Copy Gen2 "tract" to Gen3 "tract".
388 Translator.addRule(CopyKeyHandler("tract", dtype=int), gen2keys=("tract",))
389 
390 # Add valid_first, valid_last to instrument-level transmission/ datasets;
391 # these are considered calibration products in Gen3.
392 for datasetTypeName in ("transmission_sensor", "transmission_optics", "transmission_filter"):
393  Translator.addRule(ConstantKeyHandler("calibration_label", "unbounded"),
394  datasetTypeName=datasetTypeName)
395 
396 # Translate Gen2 pixel_id to Gen3 skypix.
397 # TODO: For now, we just assume that the refcat indexer uses htm7, since that's
398 # what the ps1 refcat in testdata_ci_hsc uses.
399 Translator.addRule(CopyKeyHandler("htm7", gen2key="pixel_id", dtype=int), gen2keys=("pixel_id",))
400 
401 # Translate Gen2 calibDate and datasetType to Gen3 calibration_label.
402 Translator.addRule(CalibKeyHandler(), gen2keys=("calibDate",))
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
daf::base::PropertySet * set
Definition: fits.cc:902