Loading [MathJax]/extensions/tex2jax.js
LSST Applications g0fba68d861+849f694866,g1fd858c14a+7a7b9dd5ed,g2c84ff76c0+5cb23283cf,g30358e5240+f0e04ebe90,g35bb328faa+fcb1d3bbc8,g436fd98eb5+bdc6fcdd04,g4af146b050+742274f7cd,g4d2262a081+9d5bd0394b,g4e0f332c67+cb09b8a5b6,g53246c7159+fcb1d3bbc8,g5a012ec0e7+477f9c599b,g60b5630c4e+bdc6fcdd04,g67b6fd64d1+2218407a0c,g78460c75b0+2f9a1b4bcd,g786e29fd12+cf7ec2a62a,g7b71ed6315+fcb1d3bbc8,g87b7deb4dc+777438113c,g8852436030+ebf28f0d95,g89139ef638+2218407a0c,g9125e01d80+fcb1d3bbc8,g989de1cb63+2218407a0c,g9f33ca652e+42fb53f4c8,g9f7030ddb1+11b9b6f027,ga2b97cdc51+bdc6fcdd04,gab72ac2889+bdc6fcdd04,gabe3b4be73+1e0a283bba,gabf8522325+3210f02652,gb1101e3267+9c79701da9,gb58c049af0+f03b321e39,gb89ab40317+2218407a0c,gcf25f946ba+ebf28f0d95,gd6cbbdb0b4+e8f9c9c900,gd9a9a58781+fcb1d3bbc8,gde0f65d7ad+47bbabaf80,gded526ad44+8c3210761e,ge278dab8ac+3ef3db156b,ge410e46f29+2218407a0c,gf67bdafdda+2218407a0c,v29.0.0.rc3
LSST Data Management Base Package
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
functors.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
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 <https://www.gnu.org/licenses/>.
21
22__all__ = ["init_fromDict", "Functor", "CompositeFunctor", "mag_aware_eval",
23 "CustomFunctor", "Column", "Index", "CoordColumn", "RAColumn",
24 "DecColumn", "SinglePrecisionFloatColumn", "HtmIndex20", "fluxName", "fluxErrName", "Mag",
25 "MagErr", "MagDiff", "Color", "DeconvolvedMoments", "SdssTraceSize",
26 "PsfSdssTraceSizeDiff", "HsmTraceSize", "PsfHsmTraceSizeDiff",
27 "HsmFwhm", "E1", "E2", "RadiusFromQuadrupole", "LocalWcs",
28 "ComputePixelScale", "ConvertPixelToArcseconds",
29 "ConvertPixelSqToArcsecondsSq",
30 "ConvertDetectorAngleToPositionAngle",
31 "ReferenceBand", "Photometry",
32 "NanoJansky", "NanoJanskyErr", "LocalPhotometry", "LocalNanojansky",
33 "LocalNanojanskyErr", "LocalDipoleMeanFlux",
34 "LocalDipoleMeanFluxErr", "LocalDipoleDiffFlux",
35 "LocalDipoleDiffFluxErr", "Ebv",
36 ]
37
38import logging
39import os
40import os.path
41import re
42import warnings
43from contextlib import redirect_stdout
44from itertools import product
45
46import astropy.units as u
47import lsst.geom as geom
48import lsst.sphgeom as sphgeom
49import numpy as np
50import pandas as pd
51import yaml
52from astropy.coordinates import SkyCoord
53from lsst.daf.butler import DeferredDatasetHandle
54from lsst.pipe.base import InMemoryDatasetHandle
55from lsst.utils import doImport
56from lsst.utils.introspection import get_full_type_name
57
58
59def init_fromDict(initDict, basePath='lsst.pipe.tasks.functors',
60 typeKey='functor', name=None):
61 """Initialize an object defined in a dictionary.
62
63 The object needs to be importable as f'{basePath}.{initDict[typeKey]}'.
64 The positional and keyword arguments (if any) are contained in "args" and
65 "kwargs" entries in the dictionary, respectively.
66 This is used in `~lsst.pipe.tasks.functors.CompositeFunctor.from_yaml` to
67 initialize a composite functor from a specification in a YAML file.
68
69 Parameters
70 ----------
71 initDict : dictionary
72 Dictionary describing object's initialization.
73 Must contain an entry keyed by ``typeKey`` that is the name of the
74 object, relative to ``basePath``.
75 basePath : str
76 Path relative to module in which ``initDict[typeKey]`` is defined.
77 typeKey : str
78 Key of ``initDict`` that is the name of the object (relative to
79 ``basePath``).
80 """
81 initDict = initDict.copy()
82 # TO DO: DM-21956 We should be able to define functors outside this module
83 pythonType = doImport(f'{basePath}.{initDict.pop(typeKey)}')
84 args = []
85 if 'args' in initDict:
86 args = initDict.pop('args')
87 if isinstance(args, str):
88 args = [args]
89 try:
90 element = pythonType(*args, **initDict)
91 except Exception as e:
92 message = f'Error in constructing functor "{name}" of type {pythonType.__name__} with args: {args}'
93 raise type(e)(message, e.args)
94 return element
95
96
97class Functor(object):
98 """Define and execute a calculation on a DataFrame or Handle holding a
99 DataFrame.
100
101 The `__call__` method accepts either a `~pandas.DataFrame` object or a
102 `~lsst.daf.butler.DeferredDatasetHandle` or
103 `~lsst.pipe.base.InMemoryDatasetHandle`, and returns the
104 result of the calculation as a single column.
105 Each functor defines what columns are needed for the calculation, and only
106 these columns are read from the dataset handle.
107
108 The action of `__call__` consists of two steps: first, loading the
109 necessary columns from disk into memory as a `~pandas.DataFrame` object;
110 and second, performing the computation on this DataFrame and returning the
111 result.
112
113 To define a new `Functor`, a subclass must define a `_func` method,
114 that takes a `~pandas.DataFrame` and returns result in a `~pandas.Series`.
115 In addition, it must define the following attributes:
116
117 * `_columns`: The columns necessary to perform the calculation
118 * `name`: A name appropriate for a figure axis label
119 * `shortname`: A name appropriate for use as a dictionary key
120
121 On initialization, a `Functor` should declare what band (``filt`` kwarg)
122 and dataset (e.g. ``'ref'``, ``'meas'``, ``'forced_src'``) it is intended
123 to be applied to.
124 This enables the `_get_data` method to extract the proper columns from the
125 underlying data.
126 If not specified, the dataset will fall back on the `_defaultDataset`
127 attribute.
128 If band is not specified and ``dataset`` is anything other than ``'ref'``,
129 then an error will be raised when trying to perform the calculation.
130
131 Originally, `Functor` was set up to expect datasets formatted like the
132 ``deepCoadd_obj`` dataset; that is, a DataFrame with a multi-level column
133 index, with the levels of the column index being ``band``, ``dataset``, and
134 ``column``.
135 It has since been generalized to apply to DataFrames without multi-level
136 indices and multi-level indices with just ``dataset`` and ``column``
137 levels.
138 In addition, the `_get_data` method that reads the columns from the
139 underlying data will return a DataFrame with column index levels defined by
140 the `_dfLevels` attribute; by default, this is ``column``.
141
142 The `_dfLevels` attributes should generally not need to be changed, unless
143 `_func` needs columns from multiple filters or datasets to do the
144 calculation.
145 An example of this is the `~lsst.pipe.tasks.functors.Color` functor, for
146 which `_dfLevels = ('band', 'column')`, and `_func` expects the DataFrame
147 it gets to have those levels in the column index.
148
149 Parameters
150 ----------
151 filt : str
152 Band upon which to do the calculation.
153
154 dataset : str
155 Dataset upon which to do the calculation (e.g., 'ref', 'meas',
156 'forced_src').
157 """
158
159 _defaultDataset = 'ref'
160 _dfLevels = ('column',)
161 _defaultNoDup = False
162
163 def __init__(self, filt=None, dataset=None, noDup=None):
164 self.filt = filt
165 self.dataset = dataset if dataset is not None else self._defaultDataset
166 self._noDup = noDup
167 self.log = logging.getLogger(type(self).__name__)
168
169 @property
170 def noDup(self):
171 """Do not explode by band if used on object table."""
172 if self._noDup is not None:
173 return self._noDup
174 else:
175 return self._defaultNoDup
176
177 @property
178 def columns(self):
179 """Columns required to perform calculation."""
180 if not hasattr(self, '_columns'):
181 raise NotImplementedError('Must define columns property or _columns attribute')
182 return self._columns
183
184 def _get_data_columnLevels(self, data, columnIndex=None):
185 """Gets the names of the column index levels.
186
187 This should only be called in the context of a multilevel table.
188
189 Parameters
190 ----------
191 data : various
192 The data to be read, can be a
193 `~lsst.daf.butler.DeferredDatasetHandle` or
194 `~lsst.pipe.base.InMemoryDatasetHandle`.
195 columnIndex (optional): pandas `~pandas.Index` object
196 If not passed, then it is read from the
197 `~lsst.daf.butler.DeferredDatasetHandle`
198 for `~lsst.pipe.base.InMemoryDatasetHandle`.
199 """
200 if columnIndex is None:
201 columnIndex = data.get(component="columns")
202 return columnIndex.names
203
204 def _get_data_columnLevelNames(self, data, columnIndex=None):
205 """Gets the content of each of the column levels for a multilevel
206 table.
207 """
208 if columnIndex is None:
209 columnIndex = data.get(component="columns")
210
211 columnLevels = columnIndex.names
212 columnLevelNames = {
213 level: list(np.unique(np.array([c for c in columnIndex])[:, i]))
214 for i, level in enumerate(columnLevels)
215 }
216 return columnLevelNames
217
218 def _colsFromDict(self, colDict, columnIndex=None):
219 """Converts dictionary column specficiation to a list of columns."""
220 new_colDict = {}
221 columnLevels = self._get_data_columnLevels(None, columnIndex=columnIndex)
222
223 for i, lev in enumerate(columnLevels):
224 if lev in colDict:
225 if isinstance(colDict[lev], str):
226 new_colDict[lev] = [colDict[lev]]
227 else:
228 new_colDict[lev] = colDict[lev]
229 else:
230 new_colDict[lev] = columnIndex.levels[i]
231
232 levelCols = [new_colDict[lev] for lev in columnLevels]
233 cols = list(product(*levelCols))
234 colsAvailable = [col for col in cols if col in columnIndex]
235 return colsAvailable
236
237 def multilevelColumns(self, data, columnIndex=None, returnTuple=False):
238 """Returns columns needed by functor from multilevel dataset.
239
240 To access tables with multilevel column structure, the
241 `~lsst.daf.butler.DeferredDatasetHandle` or
242 `~lsst.pipe.base.InMemoryDatasetHandle` needs to be passed
243 either a list of tuples or a dictionary.
244
245 Parameters
246 ----------
247 data : various
248 The data as either `~lsst.daf.butler.DeferredDatasetHandle`, or
249 `~lsst.pipe.base.InMemoryDatasetHandle`.
250 columnIndex (optional): pandas `~pandas.Index` object
251 Either passed or read in from
252 `~lsst.daf.butler.DeferredDatasetHandle`.
253 `returnTuple` : `bool`
254 If true, then return a list of tuples rather than the column
255 dictionary specification.
256 This is set to `True` by `CompositeFunctor` in order to be able to
257 combine columns from the various component functors.
258
259 """
260 if not isinstance(data, (DeferredDatasetHandle, InMemoryDatasetHandle)):
261 raise RuntimeError(f"Unexpected data type. Got {get_full_type_name(data)}.")
262
263 if columnIndex is None:
264 columnIndex = data.get(component="columns")
265
266 # Confirm that the dataset has the column levels the functor is
267 # expecting it to have.
268 columnLevels = self._get_data_columnLevels(data, columnIndex)
269
270 columnDict = {'column': self.columns,
271 'dataset': self.dataset}
272 if self.filt is None:
273 columnLevelNames = self._get_data_columnLevelNames(data, columnIndex)
274 if "band" in columnLevels:
275 if self.dataset == "ref":
276 columnDict["band"] = columnLevelNames["band"][0]
277 else:
278 raise ValueError(f"'filt' not set for functor {self.name}"
279 f"(dataset {self.dataset}) "
280 "and DataFrame "
281 "contains multiple filters in column index. "
282 "Set 'filt' or set 'dataset' to 'ref'.")
283 else:
284 columnDict['band'] = self.filt
285
286 if returnTuple:
287 return self._colsFromDict(columnDict, columnIndex=columnIndex)
288 else:
289 return columnDict
290
291 def _func(self, df, dropna=True):
292 raise NotImplementedError('Must define calculation on DataFrame')
293
294 def _get_columnIndex(self, data):
295 """Return columnIndex."""
296
297 if isinstance(data, (DeferredDatasetHandle, InMemoryDatasetHandle)):
298 return data.get(component="columns")
299 else:
300 return None
301
302 def _get_data(self, data):
303 """Retrieve DataFrame necessary for calculation.
304
305 The data argument can be a `~pandas.DataFrame`, a
306 `~lsst.daf.butler.DeferredDatasetHandle`, or
307 an `~lsst.pipe.base.InMemoryDatasetHandle`.
308
309 Returns a DataFrame upon which `self._func` can act.
310 """
311 # We wrap a DataFrame in a handle here to take advantage of the
312 # DataFrame delegate DataFrame column wrangling abilities.
313 if isinstance(data, pd.DataFrame):
314 _data = InMemoryDatasetHandle(data, storageClass="DataFrame")
315 elif isinstance(data, (DeferredDatasetHandle, InMemoryDatasetHandle)):
316 _data = data
317 else:
318 raise RuntimeError(f"Unexpected type provided for data. Got {get_full_type_name(data)}.")
319
320 # First thing to do: check to see if the data source has a multilevel
321 # column index or not.
322 columnIndex = self._get_columnIndex(_data)
323 is_multiLevel = isinstance(columnIndex, pd.MultiIndex)
324
325 # Get proper columns specification for this functor.
326 if is_multiLevel:
327 columns = self.multilevelColumns(_data, columnIndex=columnIndex)
328 else:
329 columns = self.columns
330
331 # Load in-memory DataFrame with appropriate columns the gen3 way.
332 df = _data.get(parameters={"columns": columns})
333
334 # Drop unnecessary column levels.
335 if is_multiLevel:
336 df = self._setLevels(df)
337
338 return df
339
340 def _setLevels(self, df):
341 levelsToDrop = [n for n in df.columns.names if n not in self._dfLevels]
342 df.columns = df.columns.droplevel(levelsToDrop)
343 return df
344
345 def _dropna(self, vals):
346 return vals.dropna()
347
348 def __call__(self, data, dropna=False):
349 df = self._get_data(data)
350 try:
351 vals = self._func(df)
352 except Exception as e:
353 self.log.error("Exception in %s call: %s: %s", self.name, type(e).__name__, e)
354 vals = self.fail(df)
355 if dropna:
356 vals = self._dropna(vals)
357
358 return vals
359
360 def difference(self, data1, data2, **kwargs):
361 """Computes difference between functor called on two different
362 DataFrame/Handle objects.
363 """
364 return self(data1, **kwargs) - self(data2, **kwargs)
365
366 def fail(self, df):
367 return pd.Series(np.full(len(df), np.nan), index=df.index)
368
369 @property
370 def name(self):
371 """Full name of functor (suitable for figure labels)."""
372 return NotImplementedError
373
374 @property
375 def shortname(self):
376 """Short name of functor (suitable for column name/dict key)."""
377 return self.name
378
379
381 """Perform multiple calculations at once on a catalog.
382
383 The role of a `CompositeFunctor` is to group together computations from
384 multiple functors.
385 Instead of returning `~pandas.Series` a `CompositeFunctor` returns a
386 `~pandas.DataFrame`, with the column names being the keys of ``funcDict``.
387
388 The `columns` attribute of a `CompositeFunctor` is the union of all columns
389 in all the component functors.
390
391 A `CompositeFunctor` does not use a `_func` method itself; rather, when a
392 `CompositeFunctor` is called, all its columns are loaded at once, and the
393 resulting DataFrame is passed to the `_func` method of each component
394 functor.
395 This has the advantage of only doing I/O (reading from parquet file) once,
396 and works because each individual `_func` method of each component functor
397 does not care if there are *extra* columns in the DataFrame being passed;
398 only that it must contain *at least* the `columns` it expects.
399
400 An important and useful class method is `from_yaml`, which takes as an
401 argument the path to a YAML file specifying a collection of functors.
402
403 Parameters
404 ----------
405 funcs : `dict` or `list`
406 Dictionary or list of functors.
407 If a list, then it will be converted into a dictonary according to the
408 `.shortname` attribute of each functor.
409 """
410 dataset = None
411 name = "CompositeFunctor"
412
413 def __init__(self, funcs, **kwargs):
414
415 if type(funcs) is dict:
416 self.funcDict = funcs
417 else:
418 self.funcDict = {f.shortname: f for f in funcs}
419
420 self._filt = None
421
422 super().__init__(**kwargs)
423
424 @property
425 def filt(self):
426 return self._filt
427
428 @filt.setter
429 def filt(self, filt):
430 if filt is not None:
431 for _, f in self.funcDict.items():
432 f.filt = filt
433 self._filt = filt
434
435 def update(self, new):
436 """Update the functor with new functors."""
437 if isinstance(new, dict):
438 self.funcDict.update(new)
439 elif isinstance(new, CompositeFunctor):
440 self.funcDict.update(new.funcDict)
441 else:
442 raise TypeError('Can only update with dictionary or CompositeFunctor.')
443
444 # Make sure new functors have the same 'filt' set.
445 if self.filt is not None:
446 self.filt = self.filt
447
448 @property
449 def columns(self):
450 return list(set([x for y in [f.columns for f in self.funcDict.values()] for x in y]))
451
452 def multilevelColumns(self, data, **kwargs):
453 # Get the union of columns for all component functors.
454 # Note the need to have `returnTuple=True` here.
455 return list(
456 set(
457 [
458 x
459 for y in [
460 f.multilevelColumns(data, returnTuple=True, **kwargs) for f in self.funcDict.values()
461 ]
462 for x in y
463 ]
464 )
465 )
466
467 def __call__(self, data, **kwargs):
468 """Apply the functor to the data table.
469
470 Parameters
471 ----------
472 data : various
473 The data represented as `~lsst.daf.butler.DeferredDatasetHandle`,
474 `~lsst.pipe.base.InMemoryDatasetHandle`, or `~pandas.DataFrame`.
475 The table or a pointer to a table on disk from which columns can
476 be accessed.
477 """
478 if isinstance(data, pd.DataFrame):
479 _data = InMemoryDatasetHandle(data, storageClass="DataFrame")
480 elif isinstance(data, (DeferredDatasetHandle, InMemoryDatasetHandle)):
481 _data = data
482 else:
483 raise RuntimeError(f"Unexpected type provided for data. Got {get_full_type_name(data)}.")
484
485 columnIndex = self._get_columnIndex(_data)
486
487 if isinstance(columnIndex, pd.MultiIndex):
488 columns = self.multilevelColumns(_data, columnIndex=columnIndex)
489 df = _data.get(parameters={"columns": columns})
490
491 valDict = {}
492 for k, f in self.funcDict.items():
493 try:
494 subdf = f._setLevels(
495 df[f.multilevelColumns(_data, returnTuple=True, columnIndex=columnIndex)]
496 )
497 valDict[k] = f._func(subdf)
498 except Exception as e:
499 self.log.exception(
500 "Exception in %s (funcs: %s) call: %s",
501 self.name,
502 str(list(self.funcDict.keys())),
503 type(e).__name__,
504 )
505 try:
506 valDict[k] = f.fail(subdf)
507 except NameError:
508 raise e
509
510 else:
511 df = _data.get(parameters={"columns": self.columns})
512
513 valDict = {k: f._func(df) for k, f in self.funcDict.items()}
514
515 # Check that output columns are actually columns.
516 for name, colVal in valDict.items():
517 if len(colVal.shape) != 1:
518 raise RuntimeError("Transformed column '%s' is not the shape of a column. "
519 "It is shaped %s and type %s." % (name, colVal.shape, type(colVal)))
520
521 try:
522 valDf = pd.concat(valDict, axis=1)
523 except TypeError:
524 print([(k, type(v)) for k, v in valDict.items()])
525 raise
526
527 if kwargs.get('dropna', False):
528 valDf = valDf.dropna(how='any')
529
530 return valDf
531
532 @classmethod
533 def renameCol(cls, col, renameRules):
534 if renameRules is None:
535 return col
536 for old, new in renameRules:
537 if col.startswith(old):
538 col = col.replace(old, new)
539 return col
540
541 @classmethod
542 def from_file(cls, filename, **kwargs):
543 # Allow environment variables in the filename.
544 filename = os.path.expandvars(filename)
545 with open(filename) as f:
546 translationDefinition = yaml.safe_load(f)
547
548 return cls.from_yaml(translationDefinition, **kwargs)
549
550 @classmethod
551 def from_yaml(cls, translationDefinition, **kwargs):
552 funcs = {}
553 for func, val in translationDefinition['funcs'].items():
554 funcs[func] = init_fromDict(val, name=func)
555
556 if 'flag_rename_rules' in translationDefinition:
557 renameRules = translationDefinition['flag_rename_rules']
558 else:
559 renameRules = None
560
561 if 'calexpFlags' in translationDefinition:
562 for flag in translationDefinition['calexpFlags']:
563 funcs[cls.renameCol(flag, renameRules)] = Column(flag, dataset='calexp')
564
565 if 'refFlags' in translationDefinition:
566 for flag in translationDefinition['refFlags']:
567 funcs[cls.renameCol(flag, renameRules)] = Column(flag, dataset='ref')
568
569 if 'forcedFlags' in translationDefinition:
570 for flag in translationDefinition['forcedFlags']:
571 funcs[cls.renameCol(flag, renameRules)] = Column(flag, dataset='forced_src')
572
573 if 'flags' in translationDefinition:
574 for flag in translationDefinition['flags']:
575 funcs[cls.renameCol(flag, renameRules)] = Column(flag, dataset='meas')
576
577 return cls(funcs, **kwargs)
578
579
580def mag_aware_eval(df, expr, log):
581 """Evaluate an expression on a DataFrame, knowing what the 'mag' function
582 means.
583
584 Builds on `pandas.DataFrame.eval`, which parses and executes math on
585 DataFrames.
586
587 Parameters
588 ----------
589 df : ~pandas.DataFrame
590 DataFrame on which to evaluate expression.
591
592 expr : str
593 Expression.
594 """
595 try:
596 expr_new = re.sub(r'mag\‍((\w+)\‍)', r'-2.5*log(\g<1>)/log(10)', expr)
597 val = df.eval(expr_new)
598 except Exception as e: # Should check what actually gets raised
599 log.error("Exception in mag_aware_eval: %s: %s", type(e).__name__, e)
600 expr_new = re.sub(r'mag\‍((\w+)\‍)', r'-2.5*log(\g<1>_instFlux)/log(10)', expr)
601 val = df.eval(expr_new)
602 return val
603
604
606 """Arbitrary computation on a catalog.
607
608 Column names (and thus the columns to be loaded from catalog) are found by
609 finding all words and trying to ignore all "math-y" words.
610
611 Parameters
612 ----------
613 expr : str
614 Expression to evaluate, to be parsed and executed by
615 `~lsst.pipe.tasks.functors.mag_aware_eval`.
616 """
617 _ignore_words = ('mag', 'sin', 'cos', 'exp', 'log', 'sqrt')
618
619 def __init__(self, expr, **kwargs):
620 self.expr = expr
621 super().__init__(**kwargs)
622
623 @property
624 def name(self):
625 return self.expr
626
627 @property
628 def columns(self):
629 flux_cols = re.findall(r'mag\‍(\s*(\w+)\s*\‍)', self.expr)
630
631 cols = [c for c in re.findall(r'[a-zA-Z_]+', self.expr) if c not in self._ignore_words]
632 not_a_col = []
633 for c in flux_cols:
634 if not re.search('_instFlux$', c):
635 cols.append(f'{c}_instFlux')
636 not_a_col.append(c)
637 else:
638 cols.append(c)
639
640 return list(set([c for c in cols if c not in not_a_col]))
641
642 def _func(self, df):
643 return mag_aware_eval(df, self.expr, self.log)
644
645
647 """Get column with a specified name."""
648
649 def __init__(self, col, **kwargs):
650 self.col = col
651 super().__init__(**kwargs)
652
653 @property
654 def name(self):
655 return self.col
656
657 @property
658 def columns(self):
659 return [self.col]
660
661 def _func(self, df):
662 return df[self.col]
663
664
666 """Return the value of the index for each object."""
667
668 columns = ['coord_ra'] # Just a dummy; something has to be here.
669 _defaultDataset = 'ref'
670 _defaultNoDup = True
671
672 def _func(self, df):
673 return pd.Series(df.index, index=df.index)
674
675
677 """Base class for coordinate column, in degrees."""
678 _radians = True
679
680 def __init__(self, col, **kwargs):
681 super().__init__(col, **kwargs)
682
683 def _func(self, df):
684 # Must not modify original column in case that column is used by
685 # another functor.
686 output = df[self.col] * 180 / np.pi if self._radians else df[self.col]
687 return output
688
689
691 """Right Ascension, in degrees."""
692 name = 'RA'
693 _defaultNoDup = True
694
695 def __init__(self, **kwargs):
696 super().__init__('coord_ra', **kwargs)
697
698 def __call__(self, catalog, **kwargs):
699 return super().__call__(catalog, **kwargs)
700
701
703 """Declination, in degrees."""
704 name = 'Dec'
705 _defaultNoDup = True
706
707 def __init__(self, **kwargs):
708 super().__init__('coord_dec', **kwargs)
709
710 def __call__(self, catalog, **kwargs):
711 return super().__call__(catalog, **kwargs)
712
713
715 """Uncertainty in Right Ascension, in degrees."""
716 name = 'RAErr'
717 _defaultNoDup = True
718
719 def __init__(self, **kwargs):
720 super().__init__('coord_raErr', **kwargs)
721
722
724 """Uncertainty in declination, in degrees."""
725 name = 'DecErr'
726 _defaultNoDup = True
727
728 def __init__(self, **kwargs):
729 super().__init__('coord_decErr', **kwargs)
730
731
733 """Coordinate covariance column, in degrees."""
734 _radians = True
735 name = 'RADecCov'
736 _defaultNoDup = True
737
738 def __init__(self, **kwargs):
739 super().__init__('coord_ra_dec_Cov', **kwargs)
740
741 def _func(self, df):
742 # Must not modify original column in case that column is used by
743 # another functor.
744 output = df[self.col]*(180/np.pi)**2 if self._radians else df[self.col]
745 return output
746
747
749 """A column with a band in a multiband table."""
750 def __init__(self, col, band_to_check, **kwargs):
751 self._band_to_check = band_to_check
752 super().__init__(col=col, **kwargs)
753
754 @property
755 def band_to_check(self):
756 return self._band_to_check
757
758 def _func(self, df):
759 return df[self.col].astype(np.float32)
760
761
763 """Return a column cast to a single-precision float."""
764
765 def _func(self, df):
766 return df[self.col].astype(np.float32)
767
768
770 """Compute the level 20 HtmIndex for the catalog.
771
772 Notes
773 -----
774 This functor was implemented to satisfy requirements of old APDB interface
775 which required the ``pixelId`` column in DiaObject with HTM20 index.
776 The APDB interface had migrated to not need that information, but we keep
777 this class in case it may be useful for something else.
778 """
779 name = "Htm20"
780 htmLevel = 20
781 _radians = True
782
783 def __init__(self, ra, dec, **kwargs):
785 self.ra = ra
786 self.dec = dec
787 self._columns = [self.ra, self.dec]
788 super().__init__(**kwargs)
789
790 def _func(self, df):
791
792 def computePixel(row):
793 if self._radians:
794 sphPoint = geom.SpherePoint(row[self.ra],
795 row[self.dec],
796 geom.radians)
797 else:
798 sphPoint = geom.SpherePoint(row[self.ra],
799 row[self.dec],
800 geom.degrees)
801 return self.pixelator.index(sphPoint.getVector())
802
803 return df.apply(computePixel, axis=1, result_type='reduce').astype('int64')
804
805
806def fluxName(col):
807 """Append _instFlux to the column name if it doesn't have it already."""
808 if not col.endswith('_instFlux'):
809 col += '_instFlux'
810 return col
811
812
813def fluxErrName(col):
814 """Append _instFluxErr to the column name if it doesn't have it already."""
815 if not col.endswith('_instFluxErr'):
816 col += '_instFluxErr'
817 return col
818
819
821 """Compute calibrated magnitude.
822
823 Returns the flux at mag=0.
824 The default ``fluxMag0`` is 63095734448.0194, which is default for HSC.
825 TO DO: This default should be made configurable in DM-21955.
826
827 This calculation hides warnings about invalid values and dividing by zero.
828
829 As with all functors, a ``dataset`` and ``filt`` kwarg should be provided
830 upon initialization.
831 Unlike the default `Functor`, however, the default dataset for a `Mag` is
832 ``'meas'``, rather than ``'ref'``.
833
834 Parameters
835 ----------
836 col : `str`
837 Name of flux column from which to compute magnitude.
838 Can be parseable by the `~lsst.pipe.tasks.functors.fluxName` function;
839 that is, you can pass ``'modelfit_CModel'`` instead of
840 ``'modelfit_CModel_instFlux'``, and it will understand.
841 """
842 _defaultDataset = 'meas'
843
844 def __init__(self, col, **kwargs):
845 self.col = fluxName(col)
846 # TO DO: DM-21955 Replace hard coded photometic calibration values.
847 self.fluxMag0 = 63095734448.0194
848
849 super().__init__(**kwargs)
850
851 @property
852 def columns(self):
853 return [self.col]
854
855 def _func(self, df):
856 with warnings.catch_warnings():
857 warnings.filterwarnings('ignore', r'invalid value encountered')
858 warnings.filterwarnings('ignore', r'divide by zero')
859 return -2.5*np.log10(df[self.col] / self.fluxMag0)
860
861 @property
862 def name(self):
863 return f'mag_{self.col}'
864
865
866class MagErr(Mag):
867 """Compute calibrated magnitude uncertainty.
868
869 Parameters
870 ----------
871 col : `str`
872 Name of the flux column.
873 """
874
875 def __init__(self, *args, **kwargs):
876 super().__init__(*args, **kwargs)
877 # TO DO: DM-21955 Replace hard coded photometic calibration values.
878 self.fluxMag0Err = 0.
879
880 @property
881 def columns(self):
882 return [self.col, self.col + 'Err']
883
884 def _func(self, df):
885 with warnings.catch_warnings():
886 warnings.filterwarnings('ignore', r'invalid value encountered')
887 warnings.filterwarnings('ignore', r'divide by zero')
888 fluxCol, fluxErrCol = self.columns
889 x = df[fluxErrCol] / df[fluxCol]
890 y = self.fluxMag0Err / self.fluxMag0
891 magErr = (2.5 / np.log(10.)) * np.sqrt(x*x + y*y)
892 return magErr
893
894 @property
895 def name(self):
896 return super().name + '_err'
897
898
900 """Functor to calculate magnitude difference."""
901 _defaultDataset = 'meas'
902
903 def __init__(self, col1, col2, **kwargs):
904 self.col1 = fluxName(col1)
905 self.col2 = fluxName(col2)
906 super().__init__(**kwargs)
907
908 @property
909 def columns(self):
910 return [self.col1, self.col2]
911
912 def _func(self, df):
913 with warnings.catch_warnings():
914 warnings.filterwarnings('ignore', r'invalid value encountered')
915 warnings.filterwarnings('ignore', r'divide by zero')
916 return -2.5*np.log10(df[self.col1]/df[self.col2])
917
918 @property
919 def name(self):
920 return f'(mag_{self.col1} - mag_{self.col2})'
921
922 @property
923 def shortname(self):
924 return f'magDiff_{self.col1}_{self.col2}'
925
926
928 """Compute the color between two filters.
929
930 Computes color by initializing two different `Mag` functors based on the
931 ``col`` and filters provided, and then returning the difference.
932
933 This is enabled by the `_func` method expecting a DataFrame with a
934 multilevel column index, with both ``'band'`` and ``'column'``, instead of
935 just ``'column'``, which is the `Functor` default.
936 This is controlled by the `_dfLevels` attribute.
937
938 Also of note, the default dataset for `Color` is ``forced_src'``, whereas
939 for `Mag` it is ``'meas'``.
940
941 Parameters
942 ----------
943 col : str
944 Name of the flux column from which to compute; same as would be passed
945 to `~lsst.pipe.tasks.functors.Mag`.
946
947 filt2, filt1 : str
948 Filters from which to compute magnitude difference.
949 Color computed is ``Mag(filt2) - Mag(filt1)``.
950 """
951 _defaultDataset = 'forced_src'
952 _dfLevels = ('band', 'column')
953 _defaultNoDup = True
954
955 def __init__(self, col, filt2, filt1, **kwargs):
956 self.col = fluxName(col)
957 if filt2 == filt1:
958 raise RuntimeError("Cannot compute Color for %s: %s - %s " % (col, filt2, filt1))
959 self.filt2 = filt2
960 self.filt1 = filt1
961
962 self.mag2 = Mag(col, filt=filt2, **kwargs)
963 self.mag1 = Mag(col, filt=filt1, **kwargs)
964
965 super().__init__(**kwargs)
966
967 @property
968 def filt(self):
969 return None
970
971 @filt.setter
972 def filt(self, filt):
973 pass
974
975 def _func(self, df):
976 mag2 = self.mag2._func(df[self.filt2])
977 mag1 = self.mag1._func(df[self.filt1])
978 return mag2 - mag1
979
980 @property
981 def columns(self):
982 return [self.mag1.col, self.mag2.col]
983
984 def multilevelColumns(self, parq, **kwargs):
985 return [(self.dataset, self.filt1, self.col), (self.dataset, self.filt2, self.col)]
986
987 @property
988 def name(self):
989 return f'{self.filt2} - {self.filt1} ({self.col})'
990
991 @property
992 def shortname(self):
993 return f"{self.col}_{self.filt2.replace('-', '')}m{self.filt1.replace('-', '')}"
994
995
997 """This functor subtracts the trace of the PSF second moments from the
998 trace of the second moments of the source.
999
1000 If the HsmShapeAlgorithm measurement is valid, then these will be used for
1001 the sources.
1002 Otherwise, the SdssShapeAlgorithm measurements will be used.
1003 """
1004 name = 'Deconvolved Moments'
1005 shortname = 'deconvolvedMoments'
1006 _columns = ("ext_shapeHSM_HsmSourceMoments_xx",
1007 "ext_shapeHSM_HsmSourceMoments_yy",
1008 "base_SdssShape_xx", "base_SdssShape_yy",
1009 "ext_shapeHSM_HsmPsfMoments_xx",
1010 "ext_shapeHSM_HsmPsfMoments_yy")
1011
1012 def _func(self, df):
1013 """Calculate deconvolved moments."""
1014 if "ext_shapeHSM_HsmSourceMoments_xx" in df.columns: # _xx added by tdm
1015 hsm = df["ext_shapeHSM_HsmSourceMoments_xx"] + df["ext_shapeHSM_HsmSourceMoments_yy"]
1016 else:
1017 hsm = np.ones(len(df))*np.nan
1018 sdss = df["base_SdssShape_xx"] + df["base_SdssShape_yy"]
1019 if "ext_shapeHSM_HsmPsfMoments_xx" in df.columns:
1020 psf = df["ext_shapeHSM_HsmPsfMoments_xx"] + df["ext_shapeHSM_HsmPsfMoments_yy"]
1021 else:
1022 # LSST does not have shape.sdss.psf.
1023 # We could instead add base_PsfShape to the catalog using
1024 # exposure.getPsf().computeShape(s.getCentroid()).getIxx().
1025 raise RuntimeError('No psf shape parameter found in catalog')
1026
1027 return hsm.where(np.isfinite(hsm), sdss) - psf
1028
1029
1031 """Functor to calculate the SDSS trace radius size for sources.
1032
1033 The SDSS trace radius size is a measure of size equal to the square root of
1034 half of the trace of the second moments tensor measured with the
1035 SdssShapeAlgorithm plugin.
1036 This has units of pixels.
1037 """
1038 name = "SDSS Trace Size"
1039 shortname = 'sdssTrace'
1040 _columns = ("base_SdssShape_xx", "base_SdssShape_yy")
1041
1042 def _func(self, df):
1043 srcSize = np.sqrt(0.5*(df["base_SdssShape_xx"] + df["base_SdssShape_yy"]))
1044 return srcSize
1045
1046
1048 """Functor to calculate the SDSS trace radius size difference (%) between
1049 the object and the PSF model.
1050
1051 See Also
1052 --------
1053 SdssTraceSize
1054 """
1055 name = "PSF - SDSS Trace Size"
1056 shortname = 'psf_sdssTrace'
1057 _columns = ("base_SdssShape_xx", "base_SdssShape_yy",
1058 "base_SdssShape_psf_xx", "base_SdssShape_psf_yy")
1059
1060 def _func(self, df):
1061 srcSize = np.sqrt(0.5*(df["base_SdssShape_xx"] + df["base_SdssShape_yy"]))
1062 psfSize = np.sqrt(0.5*(df["base_SdssShape_psf_xx"] + df["base_SdssShape_psf_yy"]))
1063 sizeDiff = 100*(srcSize - psfSize)/(0.5*(srcSize + psfSize))
1064 return sizeDiff
1065
1066
1068 """Functor to calculate the HSM trace radius size for sources.
1069
1070 The HSM trace radius size is a measure of size equal to the square root of
1071 half of the trace of the second moments tensor measured with the
1072 HsmShapeAlgorithm plugin.
1073 This has units of pixels.
1074 """
1075 name = 'HSM Trace Size'
1076 shortname = 'hsmTrace'
1077 _columns = ("ext_shapeHSM_HsmSourceMoments_xx",
1078 "ext_shapeHSM_HsmSourceMoments_yy")
1079
1080 def _func(self, df):
1081 srcSize = np.sqrt(0.5*(df["ext_shapeHSM_HsmSourceMoments_xx"]
1082 + df["ext_shapeHSM_HsmSourceMoments_yy"]))
1083 return srcSize
1084
1085
1087 """Functor to calculate the HSM trace radius size difference (%) between
1088 the object and the PSF model.
1089
1090 See Also
1091 --------
1092 HsmTraceSize
1093 """
1094 name = 'PSF - HSM Trace Size'
1095 shortname = 'psf_HsmTrace'
1096 _columns = ("ext_shapeHSM_HsmSourceMoments_xx",
1097 "ext_shapeHSM_HsmSourceMoments_yy",
1098 "ext_shapeHSM_HsmPsfMoments_xx",
1099 "ext_shapeHSM_HsmPsfMoments_yy")
1100
1101 def _func(self, df):
1102 srcSize = np.sqrt(0.5*(df["ext_shapeHSM_HsmSourceMoments_xx"]
1103 + df["ext_shapeHSM_HsmSourceMoments_yy"]))
1104 psfSize = np.sqrt(0.5*(df["ext_shapeHSM_HsmPsfMoments_xx"]
1105 + df["ext_shapeHSM_HsmPsfMoments_yy"]))
1106 sizeDiff = 100*(srcSize - psfSize)/(0.5*(srcSize + psfSize))
1107 return sizeDiff
1108
1109
1111 """Functor to calculate the PSF FWHM with second moments measured from the
1112 HsmShapeAlgorithm plugin.
1113
1114 This is in units of arcseconds, and assumes the hsc_rings_v1 skymap pixel
1115 scale of 0.168 arcseconds/pixel.
1116
1117 Notes
1118 -----
1119 This conversion assumes the PSF is Gaussian, which is not always the case.
1120 """
1121 name = 'HSM Psf FWHM'
1122 _columns = ('ext_shapeHSM_HsmPsfMoments_xx', 'ext_shapeHSM_HsmPsfMoments_yy')
1123 # TODO: DM-21403 pixel scale should be computed from the CD matrix or transform matrix
1124 pixelScale = 0.168
1125 SIGMA2FWHM = 2*np.sqrt(2*np.log(2))
1126
1127 def _func(self, df):
1128 return (self.pixelScale*self.SIGMA2FWHM*np.sqrt(
1129 0.5*(df['ext_shapeHSM_HsmPsfMoments_xx']
1130 + df['ext_shapeHSM_HsmPsfMoments_yy']))).astype(np.float32)
1131
1132
1134 r"""Calculate :math:`e_1` ellipticity component for sources, defined as:
1135
1136 .. math::
1137 e_1 &= (I_{xx}-I_{yy})/(I_{xx}+I_{yy})
1138
1139 See Also
1140 --------
1141 E2
1142 """
1143 name = "Distortion Ellipticity (e1)"
1144 shortname = "Distortion"
1145
1146 def __init__(self, colXX, colXY, colYY, **kwargs):
1147 self.colXX = colXX
1148 self.colXY = colXY
1149 self.colYY = colYY
1150 self._columns = [self.colXX, self.colXY, self.colYY]
1151 super().__init__(**kwargs)
1152
1153 @property
1154 def columns(self):
1155 return [self.colXX, self.colXY, self.colYY]
1156
1157 def _func(self, df):
1158 return (df[self.colXX] - df[self.colYY] / (df[self.colXX]
1159 + df[self.colYY])).astype(np.float32)
1160
1161
1163 r"""Calculate :math:`e_2` ellipticity component for sources, defined as:
1164
1165 .. math::
1166 e_2 &= 2I_{xy}/(I_{xx}+I_{yy})
1167
1168 See Also
1169 --------
1170 E1
1171 """
1172 name = "Ellipticity e2"
1173
1174 def __init__(self, colXX, colXY, colYY, **kwargs):
1175 self.colXX = colXX
1176 self.colXY = colXY
1177 self.colYY = colYY
1178 super().__init__(**kwargs)
1179
1180 @property
1181 def columns(self):
1182 return [self.colXX, self.colXY, self.colYY]
1183
1184 def _func(self, df):
1185 return (2*df[self.colXY] / (df[self.colXX] + df[self.colYY])).astype(np.float32)
1186
1187
1189 """Calculate the radius from the quadrupole moments.
1190
1191 This returns the fourth root of the determinant of the second moments
1192 tensor, which has units of pixels.
1193
1194 See Also
1195 --------
1196 SdssTraceSize
1197 HsmTraceSize
1198 """
1199
1200 def __init__(self, colXX, colXY, colYY, **kwargs):
1201 self.colXX = colXX
1202 self.colXY = colXY
1203 self.colYY = colYY
1204 super().__init__(**kwargs)
1205
1206 @property
1207 def columns(self):
1208 return [self.colXX, self.colXY, self.colYY]
1209
1210 def _func(self, df):
1211 return ((df[self.colXX]*df[self.colYY] - df[self.colXY]**2)**0.25).astype(np.float32)
1212
1213
1215 """Computations using the stored localWcs."""
1216 name = "LocalWcsOperations"
1217
1218 def __init__(self,
1219 colCD_1_1,
1220 colCD_1_2,
1221 colCD_2_1,
1222 colCD_2_2,
1223 **kwargs):
1224 self.colCD_1_1 = colCD_1_1
1225 self.colCD_1_2 = colCD_1_2
1226 self.colCD_2_1 = colCD_2_1
1227 self.colCD_2_2 = colCD_2_2
1228 super().__init__(**kwargs)
1229
1230 def computeDeltaRaDec(self, x, y, cd11, cd12, cd21, cd22):
1231 """Compute the dRA, dDec from dx, dy.
1232
1233 Parameters
1234 ----------
1235 x : `~pandas.Series`
1236 X pixel coordinate.
1237 y : `~pandas.Series`
1238 Y pixel coordinate.
1239 cd11 : `~pandas.Series`
1240 [1, 1] element of the local Wcs affine transform.
1241 cd12 : `~pandas.Series`
1242 [1, 2] element of the local Wcs affine transform.
1243 cd21 : `~pandas.Series`
1244 [2, 1] element of the local Wcs affine transform.
1245 cd22 : `~pandas.Series`
1246 [2, 2] element of the local Wcs affine transform.
1247
1248 Returns
1249 -------
1250 raDecTuple : tuple
1251 RA and Dec conversion of x and y given the local Wcs.
1252 Returned units are in radians.
1253
1254 Notes
1255 -----
1256 If x and y are with respect to the CRVAL1, CRVAL2
1257 then this will return the RA, Dec for that WCS.
1258 """
1259 return (x * cd11 + y * cd12, x * cd21 + y * cd22)
1260
1261 def computeSkySeparation(self, ra1, dec1, ra2, dec2):
1262 """Compute the local pixel scale conversion.
1263
1264 Parameters
1265 ----------
1266 ra1 : `~pandas.Series`
1267 Ra of the first coordinate in radians.
1268 dec1 : `~pandas.Series`
1269 Dec of the first coordinate in radians.
1270 ra2 : `~pandas.Series`
1271 Ra of the second coordinate in radians.
1272 dec2 : `~pandas.Series`
1273 Dec of the second coordinate in radians.
1274
1275 Returns
1276 -------
1277 dist : `~pandas.Series`
1278 Distance on the sphere in radians.
1279 """
1280 deltaDec = dec2 - dec1
1281 deltaRa = ra2 - ra1
1282 return 2 * np.arcsin(
1283 np.sqrt(
1284 np.sin(deltaDec / 2) ** 2
1285 + np.cos(dec2) * np.cos(dec1) * np.sin(deltaRa / 2) ** 2))
1286
1287 def getSkySeparationFromPixel(self, x1, y1, x2, y2, cd11, cd12, cd21, cd22):
1288 """Compute the distance on the sphere from x2, y1 to x1, y1.
1289
1290 Parameters
1291 ----------
1292 x1 : `~pandas.Series`
1293 X pixel coordinate.
1294 y1 : `~pandas.Series`
1295 Y pixel coordinate.
1296 x2 : `~pandas.Series`
1297 X pixel coordinate.
1298 y2 : `~pandas.Series`
1299 Y pixel coordinate.
1300 cd11 : `~pandas.Series`
1301 [1, 1] element of the local Wcs affine transform.
1302 cd12 : `~pandas.Series`
1303 [1, 2] element of the local Wcs affine transform.
1304 cd21 : `~pandas.Series`
1305 [2, 1] element of the local Wcs affine transform.
1306 cd22 : `~pandas.Series`
1307 [2, 2] element of the local Wcs affine transform.
1308
1309 Returns
1310 -------
1311 Distance : `~pandas.Series`
1312 Arcseconds per pixel at the location of the local WC.
1313 """
1314 ra1, dec1 = self.computeDeltaRaDec(x1, y1, cd11, cd12, cd21, cd22)
1315 ra2, dec2 = self.computeDeltaRaDec(x2, y2, cd11, cd12, cd21, cd22)
1316 # Great circle distance for small separations.
1317 return self.computeSkySeparation(ra1, dec1, ra2, dec2)
1318
1319 def computePositionAngle(self, ra1, dec1, ra2, dec2):
1320 """Compute position angle (E of N) from (ra1, dec1) to (ra2, dec2).
1321
1322 Parameters
1323 ----------
1324 ra1 : iterable [`float`]
1325 RA of the first coordinate [radian].
1326 dec1 : iterable [`float`]
1327 Dec of the first coordinate [radian].
1328 ra2 : iterable [`float`]
1329 RA of the second coordinate [radian].
1330 dec2 : iterable [`float`]
1331 Dec of the second coordinate [radian].
1332
1333 Returns
1334 -------
1335 Position Angle: `~pandas.Series`
1336 radians E of N
1337
1338 Notes
1339 -----
1340 (ra1, dec1) -> (ra2, dec2) is interpreted as the shorter way around the sphere
1341
1342 For a separation of 0.0001 rad, the position angle is good to 0.0009 rad
1343 all over the sphere.
1344 """
1345 # lsst.geom.SpherePoint has "bearingTo", which returns angle N of E
1346 # We instead want the astronomy convention of "Position Angle", which is angle E of N
1347 position_angle = np.zeros(len(ra1))
1348 for i, (r1, d1, r2, d2) in enumerate(zip(ra1, dec1, ra2, dec2)):
1349 point1 = geom.SpherePoint(r1, d1, geom.radians)
1350 point2 = geom.SpherePoint(r2, d2, geom.radians)
1351 bearing = point1.bearingTo(point2)
1352 pa_ref_angle = geom.Angle(np.pi/2, geom.radians) # in bearing system
1353 pa = pa_ref_angle - bearing
1354 # Wrap around to get Delta_RA from -pi to +pi
1355 pa = pa.wrapCtr()
1356 position_angle[i] = pa.asRadians()
1357
1358 return pd.Series(position_angle)
1359
1360 def getPositionAngleFromDetectorAngle(self, theta, cd11, cd12, cd21, cd22):
1361 """Compute position angle (E of N) from detector angle (+y of +x).
1362
1363 Parameters
1364 ----------
1365 theta : `float`
1366 detector angle [radian]
1367 cd11 : `float`
1368 [1, 1] element of the local Wcs affine transform.
1369 cd12 : `float`
1370 [1, 2] element of the local Wcs affine transform.
1371 cd21 : `float`
1372 [2, 1] element of the local Wcs affine transform.
1373 cd22 : `float`
1374 [2, 2] element of the local Wcs affine transform.
1375
1376 Returns
1377 -------
1378 Position Angle: `~pandas.Series`
1379 Degrees E of N.
1380 """
1381 # Create a unit vector in (x, y) along da
1382 dx = np.cos(theta)
1383 dy = np.sin(theta)
1384 ra1, dec1 = self.computeDeltaRaDec(0, 0, cd11, cd12, cd21, cd22)
1385 ra2, dec2 = self.computeDeltaRaDec(dx, dy, cd11, cd12, cd21, cd22)
1386 # Position angle of vector from (RA1, Dec1) to (RA2, Dec2)
1387 return np.rad2deg(self.computePositionAngle(ra1, dec1, ra2, dec2))
1388
1389
1391 """Compute the local pixel scale from the stored CDMatrix.
1392 """
1393 name = "PixelScale"
1394
1395 @property
1396 def columns(self):
1397 return [self.colCD_1_1,
1400 self.colCD_2_2]
1401
1402 def pixelScaleArcseconds(self, cd11, cd12, cd21, cd22):
1403 """Compute the local pixel to scale conversion in arcseconds.
1404
1405 Parameters
1406 ----------
1407 cd11 : `~pandas.Series`
1408 [1, 1] element of the local Wcs affine transform in radians.
1409 cd11 : `~pandas.Series`
1410 [1, 1] element of the local Wcs affine transform in radians.
1411 cd12 : `~pandas.Series`
1412 [1, 2] element of the local Wcs affine transform in radians.
1413 cd21 : `~pandas.Series`
1414 [2, 1] element of the local Wcs affine transform in radians.
1415 cd22 : `~pandas.Series`
1416 [2, 2] element of the local Wcs affine transform in radians.
1417
1418 Returns
1419 -------
1420 pixScale : `~pandas.Series`
1421 Arcseconds per pixel at the location of the local WC.
1422 """
1423 return 3600 * np.degrees(np.sqrt(np.fabs(cd11 * cd22 - cd12 * cd21)))
1424
1425 def _func(self, df):
1426 return self.pixelScaleArcseconds(df[self.colCD_1_1],
1427 df[self.colCD_1_2],
1428 df[self.colCD_2_1],
1429 df[self.colCD_2_2])
1430
1431
1433 """Convert a value in units of pixels to units of arcseconds."""
1434
1435 def __init__(self,
1436 col,
1437 colCD_1_1,
1438 colCD_1_2,
1439 colCD_2_1,
1440 colCD_2_2,
1441 **kwargs):
1442 self.col = col
1443 super().__init__(colCD_1_1,
1444 colCD_1_2,
1445 colCD_2_1,
1446 colCD_2_2,
1447 **kwargs)
1448
1449 @property
1450 def name(self):
1451 return f"{self.col}_asArcseconds"
1452
1453 @property
1454 def columns(self):
1455 return [self.col,
1459 self.colCD_2_2]
1460
1461 def _func(self, df):
1462 return df[self.col] * self.pixelScaleArcseconds(df[self.colCD_1_1],
1463 df[self.colCD_1_2],
1464 df[self.colCD_2_1],
1465 df[self.colCD_2_2])
1466
1467
1469 """Convert a value in units of pixels squared to units of arcseconds
1470 squared.
1471 """
1472
1473 def __init__(self,
1474 col,
1475 colCD_1_1,
1476 colCD_1_2,
1477 colCD_2_1,
1478 colCD_2_2,
1479 **kwargs):
1480 self.col = col
1481 super().__init__(colCD_1_1,
1482 colCD_1_2,
1483 colCD_2_1,
1484 colCD_2_2,
1485 **kwargs)
1486
1487 @property
1488 def name(self):
1489 return f"{self.col}_asArcsecondsSq"
1490
1491 @property
1492 def columns(self):
1493 return [self.col,
1497 self.colCD_2_2]
1498
1499 def _func(self, df):
1500 pixScale = self.pixelScaleArcseconds(df[self.colCD_1_1],
1501 df[self.colCD_1_2],
1502 df[self.colCD_2_1],
1503 df[self.colCD_2_2])
1504 return df[self.col] * pixScale * pixScale
1505
1506
1508 """Compute a position angle from a detector angle and the stored CDMatrix.
1509
1510 Returns
1511 -------
1512 position angle : degrees
1513 """
1514
1515 name = "PositionAngle"
1516
1518 self,
1519 theta_col,
1520 colCD_1_1,
1521 colCD_1_2,
1522 colCD_2_1,
1523 colCD_2_2,
1524 **kwargs
1525 ):
1526 self.theta_col = theta_col
1527 super().__init__(colCD_1_1, colCD_1_2, colCD_2_1, colCD_2_2, **kwargs)
1528
1529 @property
1530 def columns(self):
1531 return [
1532 self.theta_col,
1536 self.colCD_2_2
1537 ]
1538
1539 def _func(self, df):
1541 df[self.theta_col],
1542 df[self.colCD_1_1],
1543 df[self.colCD_1_2],
1544 df[self.colCD_2_1],
1545 df[self.colCD_2_2]
1546 )
1547
1548
1550 """Return the band used to seed multiband forced photometry.
1551
1552 This functor is to be used on Object tables.
1553 It converts the boolean merge_measurements_{band} columns into a single
1554 string representing the first band for which merge_measurements_{band}
1555 is True.
1556
1557 Assumes the default priority order of i, r, z, y, g, u.
1558 """
1559 name = 'Reference Band'
1560 shortname = 'refBand'
1561
1562 band_order = ("i", "r", "z", "y", "g", "u")
1563
1564 @property
1565 def columns(self):
1566 # Build the actual input column list, not hardcoded ugrizy
1567 bands = [band for band in self.band_order if band in self.bands]
1568 # In the unlikely scenario that users attempt to add non-ugrizy bands
1569 bands += [band for band in self.bands if band not in self.band_order]
1570 return [f"merge_measurement_{band}" for band in bands]
1571
1572 def _func(self, df: pd.DataFrame) -> pd.Series:
1573 def getFilterAliasName(row):
1574 # Get column name with the max value (True > False).
1575 colName = row.idxmax()
1576 return colName.replace('merge_measurement_', '')
1577
1578 # Skip columns that are unavailable, because this functor requests the
1579 # superset of bands that could be included in the object table.
1580 columns = [col for col in self.columns if col in df.columns]
1581 # Makes a Series of dtype object if df is empty.
1582 return df[columns].apply(getFilterAliasName, axis=1,
1583 result_type='reduce').astype('object')
1584
1585 def __init__(self, bands: tuple[str] | list[str] | None = None, **kwargs):
1586 super().__init__(**kwargs)
1587 self.bands = self.band_order if bands is None else tuple(bands)
1588
1589
1591 """Base class for Object table calibrated fluxes and magnitudes."""
1592 # AB to NanoJansky (3631 Jansky).
1593 AB_FLUX_SCALE = (0 * u.ABmag).to_value(u.nJy)
1594 LOG_AB_FLUX_SCALE = 12.56
1595 FIVE_OVER_2LOG10 = 1.085736204758129569
1596 # TO DO: DM-21955 Replace hard coded photometic calibration values.
1597 COADD_ZP = 27
1598
1599 def __init__(self, colFlux, colFluxErr=None, **kwargs):
1600 self.vhypot = np.vectorize(self.hypot)
1601 self.col = colFlux
1602 self.colFluxErr = colFluxErr
1603
1604 self.fluxMag0 = 1./np.power(10, -0.4*self.COADD_ZP)
1605 self.fluxMag0Err = 0.
1606
1607 super().__init__(**kwargs)
1608
1609 @property
1610 def columns(self):
1611 return [self.col]
1612
1613 @property
1614 def name(self):
1615 return f'mag_{self.col}'
1616
1617 @classmethod
1618 def hypot(cls, a, b):
1619 """Compute sqrt(a^2 + b^2) without under/overflow."""
1620 if np.abs(a) < np.abs(b):
1621 a, b = b, a
1622 if a == 0.:
1623 return 0.
1624 q = b/a
1625 return np.abs(a) * np.sqrt(1. + q*q)
1626
1627 def dn2flux(self, dn, fluxMag0):
1628 """Convert instrumental flux to nanojanskys."""
1629 return (self.AB_FLUX_SCALE * dn / fluxMag0).astype(np.float32)
1630
1631 def dn2mag(self, dn, fluxMag0):
1632 """Convert instrumental flux to AB magnitude."""
1633 with warnings.catch_warnings():
1634 warnings.filterwarnings('ignore', r'invalid value encountered')
1635 warnings.filterwarnings('ignore', r'divide by zero')
1636 return (-2.5 * np.log10(dn/fluxMag0)).astype(np.float32)
1637
1638 def dn2fluxErr(self, dn, dnErr, fluxMag0, fluxMag0Err):
1639 """Convert instrumental flux error to nanojanskys."""
1640 retVal = self.vhypot(dn * fluxMag0Err, dnErr * fluxMag0)
1641 retVal *= self.AB_FLUX_SCALE / fluxMag0 / fluxMag0
1642 return retVal.astype(np.float32)
1643
1644 def dn2MagErr(self, dn, dnErr, fluxMag0, fluxMag0Err):
1645 """Convert instrumental flux error to AB magnitude error."""
1646 retVal = self.dn2fluxErr(dn, dnErr, fluxMag0, fluxMag0Err) / self.dn2flux(dn, fluxMag0)
1647 return (self.FIVE_OVER_2LOG10 * retVal).astype(np.float32)
1648
1649
1651 """Convert instrumental flux to nanojanskys."""
1652 def _func(self, df):
1653 return self.dn2flux(df[self.col], self.fluxMag0)
1654
1655
1657 """Convert instrumental flux error to nanojanskys."""
1658 @property
1659 def columns(self):
1660 return [self.col, self.colFluxErr]
1661
1662 def _func(self, df):
1663 retArr = self.dn2fluxErr(df[self.col], df[self.colFluxErr], self.fluxMag0, self.fluxMag0Err)
1664 return pd.Series(retArr, index=df.index)
1665
1666
1668 """Base class for calibrating the specified instrument flux column using
1669 the local photometric calibration.
1670
1671 Parameters
1672 ----------
1673 instFluxCol : `str`
1674 Name of the instrument flux column.
1675 instFluxErrCol : `str`
1676 Name of the assocated error columns for ``instFluxCol``.
1677 photoCalibCol : `str`
1678 Name of local calibration column.
1679 photoCalibErrCol : `str`, optional
1680 Error associated with ``photoCalibCol``. Ignored and deprecated; will
1681 be removed after v29.
1682
1683 See Also
1684 --------
1685 LocalNanojansky
1686 LocalNanojanskyErr
1687 """
1688 logNJanskyToAB = (1 * u.nJy).to_value(u.ABmag)
1689
1690 def __init__(self,
1691 instFluxCol,
1692 instFluxErrCol,
1693 photoCalibCol,
1694 photoCalibErrCol=None,
1695 **kwargs):
1696 self.instFluxCol = instFluxCol
1697 self.instFluxErrCol = instFluxErrCol
1698 self.photoCalibCol = photoCalibCol
1699 # TODO[DM-49400]: remove this check and the argument it corresponds to.
1700 if photoCalibErrCol is not None:
1701 warnings.warn("The photoCalibErrCol argument is deprecated and will be removed after v29.",
1702 category=FutureWarning)
1703 super().__init__(**kwargs)
1704
1705 def instFluxToNanojansky(self, instFlux, localCalib):
1706 """Convert instrument flux to nanojanskys.
1707
1708 Parameters
1709 ----------
1710 instFlux : `~numpy.ndarray` or `~pandas.Series`
1711 Array of instrument flux measurements.
1712 localCalib : `~numpy.ndarray` or `~pandas.Series`
1713 Array of local photometric calibration estimates.
1714
1715 Returns
1716 -------
1717 calibFlux : `~numpy.ndarray` or `~pandas.Series`
1718 Array of calibrated flux measurements.
1719 """
1720 return instFlux * localCalib
1721
1722 def instFluxErrToNanojanskyErr(self, instFlux, instFluxErr, localCalib, localCalibErr=None):
1723 """Convert instrument flux to nanojanskys.
1724
1725 Parameters
1726 ----------
1727 instFlux : `~numpy.ndarray` or `~pandas.Series`
1728 Array of instrument flux measurements. Ignored (accepted for
1729 backwards compatibility and consistency with magnitude-error
1730 calculation methods).
1731 instFluxErr : `~numpy.ndarray` or `~pandas.Series`
1732 Errors on associated ``instFlux`` values.
1733 localCalib : `~numpy.ndarray` or `~pandas.Series`
1734 Array of local photometric calibration estimates.
1735 localCalibErr : `~numpy.ndarray` or `~pandas.Series`, optional
1736 Errors on associated ``localCalib`` values. Ignored and deprecated;
1737 will be removed after v29.
1738
1739 Returns
1740 -------
1741 calibFluxErr : `~numpy.ndarray` or `~pandas.Series`
1742 Errors on calibrated flux measurements.
1743 """
1744 # TODO[DM-49400]: remove this check and the argument it corresponds to.
1745 if localCalibErr is not None:
1746 warnings.warn("The localCalibErr argument is deprecated and will be removed after v29.",
1747 category=FutureWarning)
1748 return instFluxErr * localCalib
1749
1750 def instFluxToMagnitude(self, instFlux, localCalib):
1751 """Convert instrument flux to nanojanskys.
1752
1753 Parameters
1754 ----------
1755 instFlux : `~numpy.ndarray` or `~pandas.Series`
1756 Array of instrument flux measurements.
1757 localCalib : `~numpy.ndarray` or `~pandas.Series`
1758 Array of local photometric calibration estimates.
1759
1760 Returns
1761 -------
1762 calibMag : `~numpy.ndarray` or `~pandas.Series`
1763 Array of calibrated AB magnitudes.
1764 """
1765 return -2.5 * np.log10(self.instFluxToNanojansky(instFlux, localCalib)) + self.logNJanskyToAB
1766
1767 def instFluxErrToMagnitudeErr(self, instFlux, instFluxErr, localCalib, localCalibErr=None):
1768 """Convert instrument flux err to nanojanskys.
1769
1770 Parameters
1771 ----------
1772 instFlux : `~numpy.ndarray` or `~pandas.Series`
1773 Array of instrument flux measurements.
1774 instFluxErr : `~numpy.ndarray` or `~pandas.Series`
1775 Errors on associated ``instFlux`` values.
1776 localCalib : `~numpy.ndarray` or `~pandas.Series`
1777 Array of local photometric calibration estimates.
1778 localCalibErr : `~numpy.ndarray` or `~pandas.Series`, optional
1779 Errors on associated ``localCalib`` values. Ignored and deprecated;
1780 will be removed after v29.
1781
1782 Returns
1783 -------
1784 calibMagErr: `~numpy.ndarray` or `~pandas.Series`
1785 Error on calibrated AB magnitudes.
1786 """
1787 # TODO[DM-49400]: remove this check and the argument it corresponds to.
1788 if localCalibErr is not None:
1789 warnings.warn("The localCalibErr argument is deprecated and will be removed after v29.",
1790 category=FutureWarning)
1791 err = self.instFluxErrToNanojanskyErr(instFlux, instFluxErr, localCalib)
1792 return 2.5 / np.log(10) * err / self.instFluxToNanojansky(instFlux, instFluxErr)
1793
1794
1796 """Compute calibrated fluxes using the local calibration value.
1797
1798 This returns units of nanojanskys.
1799 """
1800
1801 @property
1802 def columns(self):
1803 return [self.instFluxCol, self.photoCalibCol]
1804
1805 @property
1806 def name(self):
1807 return f'flux_{self.instFluxCol}'
1808
1809 def _func(self, df):
1810 return self.instFluxToNanojansky(df[self.instFluxCol],
1811 df[self.photoCalibCol]).astype(np.float32)
1812
1813
1815 """Compute calibrated flux errors using the local calibration value.
1816
1817 This returns units of nanojanskys.
1818 """
1819
1820 @property
1821 def columns(self):
1822 return [self.instFluxCol, self.instFluxErrCol, self.photoCalibCol]
1823
1824 @property
1825 def name(self):
1826 return f'fluxErr_{self.instFluxCol}'
1827
1828 def _func(self, df):
1829 return self.instFluxErrToNanojanskyErr(df[self.instFluxCol], df[self.instFluxErrCol],
1830 df[self.photoCalibCol]).astype(np.float32)
1831
1832
1834 """Compute absolute mean of dipole fluxes.
1835
1836 See Also
1837 --------
1838 LocalNanojansky
1839 LocalNanojanskyErr
1840 LocalDipoleMeanFluxErr
1841 LocalDipoleDiffFlux
1842 LocalDipoleDiffFluxErr
1843 """
1844 def __init__(self,
1845 instFluxPosCol,
1846 instFluxNegCol,
1847 instFluxPosErrCol,
1848 instFluxNegErrCol,
1849 photoCalibCol,
1850 # TODO[DM-49400]: remove this option; it's already deprecated (in super).
1851 photoCalibErrCol=None,
1852 **kwargs):
1853 self.instFluxNegCol = instFluxNegCol
1854 self.instFluxPosCol = instFluxPosCol
1855 self.instFluxNegErrCol = instFluxNegErrCol
1856 self.instFluxPosErrCol = instFluxPosErrCol
1857 self.photoCalibCol = photoCalibCol
1858 super().__init__(instFluxNegCol,
1859 instFluxNegErrCol,
1860 photoCalibCol,
1861 photoCalibErrCol,
1862 **kwargs)
1863
1864 @property
1865 def columns(self):
1866 return [self.instFluxPosCol,
1867 self.instFluxNegCol,
1868 self.photoCalibCol]
1869
1870 @property
1871 def name(self):
1872 return f'dipMeanFlux_{self.instFluxPosCol}_{self.instFluxNegCol}'
1873
1874 def _func(self, df):
1875 return 0.5*(np.fabs(self.instFluxToNanojansky(df[self.instFluxNegCol], df[self.photoCalibCol]))
1876 + np.fabs(self.instFluxToNanojansky(df[self.instFluxPosCol], df[self.photoCalibCol])))
1877
1878
1880 """Compute the error on the absolute mean of dipole fluxes.
1881
1882 See Also
1883 --------
1884 LocalNanojansky
1885 LocalNanojanskyErr
1886 LocalDipoleMeanFlux
1887 LocalDipoleDiffFlux
1888 LocalDipoleDiffFluxErr
1889 """
1890
1891 @property
1899 @property
1900 def name(self):
1901 return f'dipMeanFluxErr_{self.instFluxPosCol}_{self.instFluxNegCol}'
1902
1903 def _func(self, df):
1904 return 0.5*np.hypot(df[self.instFluxNegErrCol], df[self.instFluxPosErrCol]) * df[self.photoCalibCol]
1905
1906
1908 """Compute the absolute difference of dipole fluxes.
1909
1910 Calculated value is (abs(pos) - abs(neg)).
1911
1912 See Also
1913 --------
1914 LocalNanojansky
1915 LocalNanojanskyErr
1916 LocalDipoleMeanFlux
1917 LocalDipoleMeanFluxErr
1918 LocalDipoleDiffFluxErr
1919 """
1920
1921 @property
1922 def columns(self):
1923 return [self.instFluxPosCol,
1925 self.photoCalibCol]
1926
1927 @property
1928 def name(self):
1929 return f'dipDiffFlux_{self.instFluxPosCol}_{self.instFluxNegCol}'
1930
1931 def _func(self, df):
1932 return (np.fabs(self.instFluxToNanojansky(df[self.instFluxPosCol], df[self.photoCalibCol]))
1933 - np.fabs(self.instFluxToNanojansky(df[self.instFluxNegCol], df[self.photoCalibCol])))
1934
1935
1937 """Compute the error on the absolute difference of dipole fluxes.
1938
1939 See Also
1940 --------
1941 LocalNanojansky
1942 LocalNanojanskyErr
1943 LocalDipoleMeanFlux
1944 LocalDipoleMeanFluxErr
1945 LocalDipoleDiffFlux
1946 """
1947
1948 @property
1956 @property
1957 def name(self):
1958 return f'dipDiffFluxErr_{self.instFluxPosCol}_{self.instFluxNegCol}'
1959
1960 def _func(self, df):
1961 return np.hypot(df[self.instFluxPosErrCol], df[self.instFluxNegErrCol]) * df[self.photoCalibCol]
1962
1963
1965 """Compute E(B-V) from dustmaps.sfd."""
1966 _defaultDataset = 'ref'
1967 name = "E(B-V)"
1968 shortname = "ebv"
1969
1970 def __init__(self, **kwargs):
1971 # Import is only needed for Ebv.
1972 # Suppress unnecessary .dustmapsrc log message on import.
1973 with open(os.devnull, "w") as devnull:
1974 with redirect_stdout(devnull):
1975 from dustmaps.sfd import SFDQuery
1976 self._columns = ['coord_ra', 'coord_dec']
1977 self.sfd = SFDQuery()
1978 super().__init__(**kwargs)
1979
1980 def _func(self, df):
1981 coords = SkyCoord(df['coord_ra'].values * u.rad, df['coord_dec'].values * u.rad)
1982 ebv = self.sfd(coords)
1983 return pd.Series(ebv, index=df.index).astype('float32')
A class representing an angle.
Definition Angle.h:128
Point in an unspecified spherical coordinate system.
Definition SpherePoint.h:57
__init__(self, col, filt2, filt1, **kwargs)
Definition functors.py:955
multilevelColumns(self, parq, **kwargs)
Definition functors.py:984
__init__(self, col, **kwargs)
Definition functors.py:649
multilevelColumns(self, data, **kwargs)
Definition functors.py:452
from_file(cls, filename, **kwargs)
Definition functors.py:542
from_yaml(cls, translationDefinition, **kwargs)
Definition functors.py:551
pixelScaleArcseconds(self, cd11, cd12, cd21, cd22)
Definition functors.py:1402
__init__(self, theta_col, colCD_1_1, colCD_1_2, colCD_2_1, colCD_2_2, **kwargs)
Definition functors.py:1525
__init__(self, col, colCD_1_1, colCD_1_2, colCD_2_1, colCD_2_2, **kwargs)
Definition functors.py:1479
__init__(self, col, colCD_1_1, colCD_1_2, colCD_2_1, colCD_2_2, **kwargs)
Definition functors.py:1441
__init__(self, col, **kwargs)
Definition functors.py:680
__call__(self, catalog, **kwargs)
Definition functors.py:710
__init__(self, colXX, colXY, colYY, **kwargs)
Definition functors.py:1146
__init__(self, colXX, colXY, colYY, **kwargs)
Definition functors.py:1174
_func(self, df, dropna=True)
Definition functors.py:291
multilevelColumns(self, data, columnIndex=None, returnTuple=False)
Definition functors.py:237
__call__(self, data, dropna=False)
Definition functors.py:348
_get_data_columnLevels(self, data, columnIndex=None)
Definition functors.py:184
_colsFromDict(self, colDict, columnIndex=None)
Definition functors.py:218
difference(self, data1, data2, **kwargs)
Definition functors.py:360
_get_data_columnLevelNames(self, data, columnIndex=None)
Definition functors.py:204
__init__(self, filt=None, dataset=None, noDup=None)
Definition functors.py:163
__init__(self, ra, dec, **kwargs)
Definition functors.py:783
__init__(self, instFluxPosCol, instFluxNegCol, instFluxPosErrCol, instFluxNegErrCol, photoCalibCol, photoCalibErrCol=None, **kwargs)
Definition functors.py:1852
instFluxToNanojansky(self, instFlux, localCalib)
Definition functors.py:1705
instFluxErrToMagnitudeErr(self, instFlux, instFluxErr, localCalib, localCalibErr=None)
Definition functors.py:1767
instFluxToMagnitude(self, instFlux, localCalib)
Definition functors.py:1750
__init__(self, instFluxCol, instFluxErrCol, photoCalibCol, photoCalibErrCol=None, **kwargs)
Definition functors.py:1695
instFluxErrToNanojanskyErr(self, instFlux, instFluxErr, localCalib, localCalibErr=None)
Definition functors.py:1722
computeSkySeparation(self, ra1, dec1, ra2, dec2)
Definition functors.py:1261
__init__(self, colCD_1_1, colCD_1_2, colCD_2_1, colCD_2_2, **kwargs)
Definition functors.py:1223
computeDeltaRaDec(self, x, y, cd11, cd12, cd21, cd22)
Definition functors.py:1230
getSkySeparationFromPixel(self, x1, y1, x2, y2, cd11, cd12, cd21, cd22)
Definition functors.py:1287
computePositionAngle(self, ra1, dec1, ra2, dec2)
Definition functors.py:1319
getPositionAngleFromDetectorAngle(self, theta, cd11, cd12, cd21, cd22)
Definition functors.py:1360
__init__(self, col1, col2, **kwargs)
Definition functors.py:903
__init__(self, *args, **kwargs)
Definition functors.py:875
__init__(self, col, **kwargs)
Definition functors.py:844
__init__(self, col, band_to_check, **kwargs)
Definition functors.py:750
__init__(self, colFlux, colFluxErr=None, **kwargs)
Definition functors.py:1599
dn2MagErr(self, dn, dnErr, fluxMag0, fluxMag0Err)
Definition functors.py:1644
dn2fluxErr(self, dn, dnErr, fluxMag0, fluxMag0Err)
Definition functors.py:1638
__call__(self, catalog, **kwargs)
Definition functors.py:698
__init__(self, colXX, colXY, colYY, **kwargs)
Definition functors.py:1200
pd.Series _func(self, pd.DataFrame df)
Definition functors.py:1572
__init__(self, tuple[str]|list[str]|None bands=None, **kwargs)
Definition functors.py:1585
HtmPixelization provides HTM indexing of points and regions.
init_fromDict(initDict, basePath='lsst.pipe.tasks.functors', typeKey='functor', name=None)
Definition functors.py:60
mag_aware_eval(df, expr, log)
Definition functors.py:580