astro-otter 0.0.2__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of astro-otter might be problematic. Click here for more details.
- astro_otter-0.1.0.dist-info/METADATA +127 -0
- astro_otter-0.1.0.dist-info/RECORD +17 -0
- {astro_otter-0.0.2.dist-info → astro_otter-0.1.0.dist-info}/WHEEL +1 -1
- otter/__init__.py +2 -0
- otter/_version.py +1 -1
- otter/exceptions.py +29 -0
- otter/io/data_finder.py +744 -0
- otter/io/host.py +106 -0
- otter/io/otter.py +11 -3
- otter/io/transient.py +179 -135
- otter/util.py +42 -0
- astro_otter-0.0.2.dist-info/METADATA +0 -875
- astro_otter-0.0.2.dist-info/RECORD +0 -15
- {astro_otter-0.0.2.dist-info → astro_otter-0.1.0.dist-info}/LICENSE +0 -0
- {astro_otter-0.0.2.dist-info → astro_otter-0.1.0.dist-info}/top_level.txt +0 -0
otter/io/host.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Host object that stores information on the Transient Host and provides utility methods
|
|
3
|
+
for pulling in data corresponding to that host
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
import numpy as np
|
|
8
|
+
from astropy.coordinates import SkyCoord
|
|
9
|
+
from astropy import units as u
|
|
10
|
+
|
|
11
|
+
from .data_finder import DataFinder
|
|
12
|
+
from ..exceptions import OtterLimitationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Host(DataFinder):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
host_ra: str | float,
|
|
19
|
+
host_dec: str | float,
|
|
20
|
+
host_ra_units: str | u.Unit,
|
|
21
|
+
host_dec_units: str | u.Unit,
|
|
22
|
+
host_name: str = None,
|
|
23
|
+
host_redshift: float = None,
|
|
24
|
+
reference: list[str] = None,
|
|
25
|
+
transient_name: str = None,
|
|
26
|
+
**kwargs,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""
|
|
29
|
+
Object to store host information and query public data sources of host galaxies
|
|
30
|
+
|
|
31
|
+
Subclass of the data scraper class to allow for these queries to happen
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
host_ra (str|float) : The RA of the host to be passed to an astropy SkyCoord
|
|
35
|
+
host_dec (str|float) : The declination of the host to be passed to an
|
|
36
|
+
astropy SkyCoord
|
|
37
|
+
host_ra_units (str|astropy.units.Unit) : units of the RA, to be passed to
|
|
38
|
+
the unit keyword of SkyCoord
|
|
39
|
+
host_dec_units (str|astropy.units.Unit) : units of the declination, to be
|
|
40
|
+
passed to the unit keyword of
|
|
41
|
+
SkyCoord
|
|
42
|
+
host_name (str) : The name of the host galaxy
|
|
43
|
+
host_redshift (float) : The redshift of the host galaxy
|
|
44
|
+
reference (list[str]) : a list of bibcodes that found this to be the host
|
|
45
|
+
transient_name (str) : the name of the transient associated with this host
|
|
46
|
+
kwargs : Just here so we can pass **Transient['host'] into this constructor
|
|
47
|
+
and any extraneous properties will be ignored.
|
|
48
|
+
"""
|
|
49
|
+
self.coord = SkyCoord(host_ra, host_dec, unit=(host_ra_units, host_dec_units))
|
|
50
|
+
self.name = host_name
|
|
51
|
+
self.z = host_redshift
|
|
52
|
+
self.redshift = host_redshift # just here for ease of use
|
|
53
|
+
self.bibcodes = reference
|
|
54
|
+
self.transient_name = transient_name
|
|
55
|
+
|
|
56
|
+
def pcc(self, transient_coord: SkyCoord, mag: float = None):
|
|
57
|
+
"""
|
|
58
|
+
Compute the Probability of Chance Coincindence as described in
|
|
59
|
+
Bloom et al. (2002) "Offset Distribution of Gamma-Ray Bursts.
|
|
60
|
+
|
|
61
|
+
This computes the probability that this galaxy is by chance nearby to the
|
|
62
|
+
transient on the sky. Or, in simpler terms this essentially computes the
|
|
63
|
+
probability that we are wrong about this being the transient host. So, a
|
|
64
|
+
smaller probability is better!
|
|
65
|
+
|
|
66
|
+
Note: This probability was initially defined for GRB afterglows, which tend to
|
|
67
|
+
be redder transients (supernova too). So, be cautious when using this algorithm
|
|
68
|
+
for TDEs!
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
transient_coord (astropy.coordinates.SkyCoord) : The coordinates of the
|
|
72
|
+
transient object.
|
|
73
|
+
mag (float) : An r-band magnitude to compute from. Default is None which
|
|
74
|
+
will prompt us to check SDSS for one within 10".
|
|
75
|
+
Returns:
|
|
76
|
+
A float probability in the range [0,1]
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
# first get the separation r, in arcseconds
|
|
80
|
+
r = self.coord.separation(transient_coord).arcsec
|
|
81
|
+
|
|
82
|
+
# next get the host r magnitude
|
|
83
|
+
if mag is None:
|
|
84
|
+
res = self.query_vizier(radius=10 * u.arcsec)
|
|
85
|
+
|
|
86
|
+
if len(res) == 0:
|
|
87
|
+
raise OtterLimitationError(
|
|
88
|
+
"No magnitude found in SDSS! Please provide a magnitude via the \
|
|
89
|
+
`mag` keyword to make this calculation!"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
sdss = [k for k in res.keys() if "sdss" in k]
|
|
93
|
+
use = max(sdss, key=lambda k: int(k.split("sdss")[-1]))
|
|
94
|
+
print(f"Using the r magnitude from the {use} table")
|
|
95
|
+
mag = res[use]["rmag"][0]
|
|
96
|
+
|
|
97
|
+
# then compute the probability
|
|
98
|
+
sigma_prefactor = 1 / (3600**2 * 0.334 * np.log(10))
|
|
99
|
+
sigma_pow = 0.334 * (mag - 22.963) + 4.320
|
|
100
|
+
sigma = sigma_prefactor * 10**sigma_pow
|
|
101
|
+
|
|
102
|
+
eta = np.pi * r**2 * sigma
|
|
103
|
+
|
|
104
|
+
prob = 1 - np.exp(-eta)
|
|
105
|
+
|
|
106
|
+
return prob
|
otter/io/otter.py
CHANGED
|
@@ -192,11 +192,16 @@ class Otter(object):
|
|
|
192
192
|
"converted_date_unit",
|
|
193
193
|
"converted_wave_unit",
|
|
194
194
|
"converted_freq_unit",
|
|
195
|
+
"filter_key",
|
|
195
196
|
"obs_type",
|
|
196
197
|
"upperlimit",
|
|
197
198
|
"reference",
|
|
199
|
+
"human_readable_refs",
|
|
198
200
|
]
|
|
199
201
|
|
|
202
|
+
if "upperlimit" not in fullphot:
|
|
203
|
+
fullphot["upperlimit"] = False
|
|
204
|
+
|
|
200
205
|
if not keep_raw:
|
|
201
206
|
if "telescope" in fullphot:
|
|
202
207
|
fullphot = fullphot[keys_to_keep + ["telescope"]]
|
|
@@ -285,7 +290,6 @@ class Otter(object):
|
|
|
285
290
|
|
|
286
291
|
# then read and query the summary table
|
|
287
292
|
summary = pd.read_csv(summary_table)
|
|
288
|
-
|
|
289
293
|
# coordinate search first
|
|
290
294
|
if coords is not None:
|
|
291
295
|
if not isinstance(coords, SkyCoord):
|
|
@@ -473,10 +477,14 @@ class Otter(object):
|
|
|
473
477
|
}
|
|
474
478
|
|
|
475
479
|
if "date_reference" in t:
|
|
476
|
-
|
|
480
|
+
date_types = {d["date_type"] for d in t["date_reference"]}
|
|
481
|
+
if "discovery" in date_types:
|
|
482
|
+
row["discovery_date"] = t.get_discovery_date()
|
|
477
483
|
|
|
478
484
|
if "distance" in t:
|
|
479
|
-
|
|
485
|
+
dist_types = {d["distance_type"] for d in t["distance"]}
|
|
486
|
+
if "redshift" in dist_types:
|
|
487
|
+
row["z"] = t.get_redshift()
|
|
480
488
|
|
|
481
489
|
row["hasPhot"] = "photometry" in t
|
|
482
490
|
row["hasSpec"] = "spectra" in t
|
otter/io/transient.py
CHANGED
|
@@ -9,6 +9,7 @@ from copy import deepcopy
|
|
|
9
9
|
import re
|
|
10
10
|
from collections.abc import MutableMapping
|
|
11
11
|
from typing_extensions import Self
|
|
12
|
+
import logging
|
|
12
13
|
|
|
13
14
|
import numpy as np
|
|
14
15
|
import pandas as pd
|
|
@@ -17,9 +18,6 @@ import astropy.units as u
|
|
|
17
18
|
from astropy.time import Time
|
|
18
19
|
from astropy.coordinates import SkyCoord
|
|
19
20
|
|
|
20
|
-
from synphot.units import VEGAMAG, convert_flux
|
|
21
|
-
from synphot.spectrum import SourceSpectrum
|
|
22
|
-
|
|
23
21
|
from ..exceptions import (
|
|
24
22
|
FailedQueryError,
|
|
25
23
|
IOError,
|
|
@@ -27,10 +25,12 @@ from ..exceptions import (
|
|
|
27
25
|
TransientMergeError,
|
|
28
26
|
)
|
|
29
27
|
from ..util import XRAY_AREAS
|
|
28
|
+
from .host import Host
|
|
30
29
|
|
|
31
30
|
warnings.simplefilter("once", RuntimeWarning)
|
|
32
31
|
warnings.simplefilter("once", UserWarning)
|
|
33
32
|
np.seterr(divide="ignore")
|
|
33
|
+
logger = logging.getLogger(__name__)
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class Transient(MutableMapping):
|
|
@@ -196,6 +196,19 @@ class Transient(MutableMapping):
|
|
|
196
196
|
+ " You can set strict_merge=False to override the check"
|
|
197
197
|
)
|
|
198
198
|
|
|
199
|
+
# create set of the allowed keywords
|
|
200
|
+
allowed_keywords = {
|
|
201
|
+
"name",
|
|
202
|
+
"date_reference",
|
|
203
|
+
"coordinate",
|
|
204
|
+
"distance",
|
|
205
|
+
"filter_alias",
|
|
206
|
+
"schema_version",
|
|
207
|
+
"photometry",
|
|
208
|
+
"classification",
|
|
209
|
+
"host",
|
|
210
|
+
}
|
|
211
|
+
|
|
199
212
|
# create a blank dictionary since we don't want to overwrite this object
|
|
200
213
|
out = {}
|
|
201
214
|
|
|
@@ -230,24 +243,8 @@ class Transient(MutableMapping):
|
|
|
230
243
|
continue
|
|
231
244
|
|
|
232
245
|
# There are some special keys that we are expecting
|
|
233
|
-
if key
|
|
234
|
-
|
|
235
|
-
elif key == "coordinate":
|
|
236
|
-
self._merge_coords(other, out)
|
|
237
|
-
elif key == "date_reference":
|
|
238
|
-
self._merge_date(other, out)
|
|
239
|
-
elif key == "distance":
|
|
240
|
-
self._merge_distance(other, out)
|
|
241
|
-
elif key == "filter_alias":
|
|
242
|
-
self._merge_filter_alias(other, out)
|
|
243
|
-
elif key == "schema_version":
|
|
244
|
-
self._merge_schema_version(other, out)
|
|
245
|
-
elif key == "photometry":
|
|
246
|
-
self._merge_photometry(other, out)
|
|
247
|
-
elif key == "spectra":
|
|
248
|
-
self._merge_spectra(other, out)
|
|
249
|
-
elif key == "classification":
|
|
250
|
-
self._merge_class(other, out)
|
|
246
|
+
if key in allowed_keywords:
|
|
247
|
+
Transient._merge_arbitrary(key, self, other, out)
|
|
251
248
|
else:
|
|
252
249
|
# this is an unexpected key!
|
|
253
250
|
if strict_merge:
|
|
@@ -341,7 +338,7 @@ class Transient(MutableMapping):
|
|
|
341
338
|
else:
|
|
342
339
|
f = "mjd"
|
|
343
340
|
|
|
344
|
-
return Time(date["value"], format=f)
|
|
341
|
+
return Time(str(date["value"]).strip(), format=f)
|
|
345
342
|
|
|
346
343
|
def get_redshift(self) -> float:
|
|
347
344
|
"""
|
|
@@ -357,7 +354,73 @@ class Transient(MutableMapping):
|
|
|
357
354
|
else:
|
|
358
355
|
return default["value"]
|
|
359
356
|
|
|
360
|
-
def
|
|
357
|
+
def get_classification(self) -> tuple(str, float, list):
|
|
358
|
+
"""
|
|
359
|
+
Get the default classification of this Transient.
|
|
360
|
+
This normally corresponds to the highest confidence classification that we have
|
|
361
|
+
stored for the transient.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
The default object class as a string, the confidence level in that class,
|
|
365
|
+
and a list of the bibcodes corresponding to that classification. Or, None
|
|
366
|
+
if there is no classification.
|
|
367
|
+
"""
|
|
368
|
+
default = self._get_default("classification")
|
|
369
|
+
if default is None:
|
|
370
|
+
return default
|
|
371
|
+
return default.object_class, default.confidence, default.reference
|
|
372
|
+
|
|
373
|
+
def get_host(self, max_hosts=3, **kwargs) -> list[Host]:
|
|
374
|
+
"""
|
|
375
|
+
Gets the default host information of this Transient. This returns an otter.Host
|
|
376
|
+
object. If no host is known in OTTER, it uses astro-ghost to find the best
|
|
377
|
+
match.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
max_hosts [int] : The maximum number of hosts to return
|
|
381
|
+
**kwargs : keyword arguments to be passed to getGHOST
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
A list of otter.Host objects. This is useful becuase the Host objects have
|
|
385
|
+
useful methods for querying public catalogs for data of the host.
|
|
386
|
+
"""
|
|
387
|
+
# first try to get the host information from our local database
|
|
388
|
+
if "host" in self:
|
|
389
|
+
host = [
|
|
390
|
+
Host(transient_name=self.default_name, **dict(h)) for h in self["host"]
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
# then try astro-ghost
|
|
394
|
+
else:
|
|
395
|
+
logger.warn(
|
|
396
|
+
"No host known, trying to find it with astro-ghost. \
|
|
397
|
+
See https://uiucsnastro-ghost.readthedocs.io/en/latest/index.html"
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# this import has to be here otherwise the code breaks
|
|
401
|
+
from astro_ghost.ghostHelperFunctions import getTransientHosts, getGHOST
|
|
402
|
+
|
|
403
|
+
getGHOST(real=False, verbose=1)
|
|
404
|
+
res = getTransientHosts(
|
|
405
|
+
[self.default_name], [self.get_skycoord()], verbose=False
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
host = [
|
|
409
|
+
Host(
|
|
410
|
+
host_ra=row["raStack"],
|
|
411
|
+
host_dec=row["decStack"],
|
|
412
|
+
host_ra_units="deg",
|
|
413
|
+
host_dec_units="deg",
|
|
414
|
+
host_name=row["objName"],
|
|
415
|
+
transient_name=self.default_name,
|
|
416
|
+
reference=["astro-ghost"],
|
|
417
|
+
)
|
|
418
|
+
for i, row in res.iterrows()
|
|
419
|
+
]
|
|
420
|
+
|
|
421
|
+
return host
|
|
422
|
+
|
|
423
|
+
def _get_default(self, key, filt=None):
|
|
361
424
|
"""
|
|
362
425
|
Get the default of key
|
|
363
426
|
|
|
@@ -370,7 +433,8 @@ class Transient(MutableMapping):
|
|
|
370
433
|
raise KeyError(f"This transient does not have {key} associated with it!")
|
|
371
434
|
|
|
372
435
|
df = pd.DataFrame(self[key])
|
|
373
|
-
|
|
436
|
+
if filt is not None:
|
|
437
|
+
df = df[eval(filt)] # apply the filters
|
|
374
438
|
|
|
375
439
|
if "default" in df:
|
|
376
440
|
# first try to get the default
|
|
@@ -441,6 +505,10 @@ class Transient(MutableMapping):
|
|
|
441
505
|
Returns:
|
|
442
506
|
A pandas DataFrame of the cleaned up photometry in the requested units
|
|
443
507
|
"""
|
|
508
|
+
# these imports need to be here for some reason
|
|
509
|
+
# otherwise the code breaks
|
|
510
|
+
from synphot.units import VEGAMAG, convert_flux
|
|
511
|
+
from synphot.spectrum import SourceSpectrum
|
|
444
512
|
|
|
445
513
|
# check inputs
|
|
446
514
|
if by not in {"value", "raw"}:
|
|
@@ -523,8 +591,9 @@ class Transient(MutableMapping):
|
|
|
523
591
|
)
|
|
524
592
|
|
|
525
593
|
unit = unit[0]
|
|
594
|
+
isvegamag = "vega" in unit.lower()
|
|
526
595
|
try:
|
|
527
|
-
if
|
|
596
|
+
if isvegamag:
|
|
528
597
|
astropy_units = VEGAMAG
|
|
529
598
|
else:
|
|
530
599
|
astropy_units = u.Unit(unit)
|
|
@@ -555,25 +624,24 @@ class Transient(MutableMapping):
|
|
|
555
624
|
astropy_units
|
|
556
625
|
) # assume error and values have the same unit
|
|
557
626
|
|
|
558
|
-
# get the effective wavelength
|
|
627
|
+
# get and save the effective wavelength
|
|
559
628
|
if "freq_eff" in data and not np.isnan(data["freq_eff"].iloc[0]):
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
"Can not convert different units to the same unit!"
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
freq_eff = np.array(data["freq_eff"]) * u.Unit(freq_units.iloc[0])
|
|
567
|
-
wave_eff = freq_eff.to(u.AA, equivalencies=u.spectral())
|
|
629
|
+
zz = zip(data["freq_eff"], data["freq_units"])
|
|
630
|
+
freq_eff = u.Quantity([vv * u.Unit(uu) for vv, uu in zz], freq_unit)
|
|
631
|
+
wave_eff = freq_eff.to(wave_unit, equivalencies=u.spectral())
|
|
568
632
|
|
|
569
633
|
elif "wave_eff" in data and not np.isnan(data["wave_eff"].iloc[0]):
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
634
|
+
zz = zip(data["wave_eff"], data["wave_units"])
|
|
635
|
+
wave_eff = u.Quantity([vv * u.Unit(uu) for vv, uu in zz], wave_unit)
|
|
636
|
+
freq_eff = wave_eff.to(freq_unit, equivalencies=u.spectral())
|
|
637
|
+
|
|
638
|
+
else:
|
|
639
|
+
raise ValueError("No known frequency or wavelength, please fix!")
|
|
575
640
|
|
|
576
|
-
|
|
641
|
+
data["converted_wave"] = wave_eff.value
|
|
642
|
+
data["converted_wave_unit"] = wave_unit
|
|
643
|
+
data["converted_freq"] = freq_eff.value
|
|
644
|
+
data["converted_freq_unit"] = freq_unit
|
|
577
645
|
|
|
578
646
|
# convert using synphot
|
|
579
647
|
# stuff has to be done slightly differently for xray than for the others
|
|
@@ -593,39 +661,48 @@ class Transient(MutableMapping):
|
|
|
593
661
|
|
|
594
662
|
# we also need to make this wave_min and wave_max
|
|
595
663
|
# instead of just the effective wavelength like for radio and uvoir
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
664
|
+
zz = zip(data["wave_min"], data["wave_max"], data["wave_units"])
|
|
665
|
+
wave_eff = u.Quantity(
|
|
666
|
+
[np.array([m, M]) * u.Unit(uu) for m, M, uu in zz],
|
|
667
|
+
u.Unit(wave_unit),
|
|
668
|
+
)
|
|
599
669
|
|
|
600
670
|
else:
|
|
601
671
|
area = None
|
|
602
672
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
673
|
+
if obstype == "xray" or isvegamag:
|
|
674
|
+
# we unfortunately have to loop over the points here because
|
|
675
|
+
# syncphot does not work with a 2D array of min max wavelengths
|
|
676
|
+
# for converting counts to other flux units. It also can't convert
|
|
677
|
+
# vega mags with a wavelength array because it interprets that as the
|
|
678
|
+
# wavelengths corresponding to the SourceSpectrum.from_vega()
|
|
679
|
+
|
|
680
|
+
flux, flux_err = [], []
|
|
681
|
+
for wave, xray_point, xray_point_err in zip(wave_eff, q, q_err):
|
|
682
|
+
f_val = convert_flux(
|
|
683
|
+
wave,
|
|
684
|
+
xray_point,
|
|
685
|
+
u.Unit(flux_unit),
|
|
686
|
+
vegaspec=SourceSpectrum.from_vega(),
|
|
687
|
+
area=area,
|
|
688
|
+
)
|
|
689
|
+
f_err = convert_flux(
|
|
690
|
+
wave,
|
|
691
|
+
xray_point_err,
|
|
692
|
+
u.Unit(flux_unit),
|
|
693
|
+
vegaspec=SourceSpectrum.from_vega(),
|
|
694
|
+
area=area,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
# then we take the average of the minimum and maximum values
|
|
698
|
+
# computed by syncphot
|
|
699
|
+
flux.append(np.mean(f_val).value)
|
|
700
|
+
flux_err.append(np.mean(f_err).value)
|
|
624
701
|
|
|
625
|
-
|
|
626
|
-
#
|
|
627
|
-
flux
|
|
628
|
-
flux_err
|
|
702
|
+
else:
|
|
703
|
+
# this will be faster and cover most cases
|
|
704
|
+
flux = convert_flux(wave_eff, q, u.Unit(flux_unit))
|
|
705
|
+
flux_err = convert_flux(wave_eff, q_err, u.Unit(flux_unit))
|
|
629
706
|
|
|
630
707
|
flux = np.array(flux) * u.Unit(flux_unit)
|
|
631
708
|
flux_err = np.array(flux_err) * u.Unit(flux_unit)
|
|
@@ -639,7 +716,7 @@ class Transient(MutableMapping):
|
|
|
639
716
|
outdata = pd.concat(outdata)
|
|
640
717
|
|
|
641
718
|
# copy over the flux units
|
|
642
|
-
outdata["converted_flux_unit"] =
|
|
719
|
+
outdata["converted_flux_unit"] = flux_unit
|
|
643
720
|
|
|
644
721
|
# make sure all the datetimes are in the same format here too!!
|
|
645
722
|
times = [
|
|
@@ -647,27 +724,7 @@ class Transient(MutableMapping):
|
|
|
647
724
|
for d, f in zip(outdata.date, outdata.date_format.str.lower())
|
|
648
725
|
]
|
|
649
726
|
outdata["converted_date"] = times
|
|
650
|
-
outdata["converted_date_unit"] =
|
|
651
|
-
|
|
652
|
-
# same with frequencies and wavelengths
|
|
653
|
-
freqs = []
|
|
654
|
-
waves = []
|
|
655
|
-
|
|
656
|
-
for _, row in df.iterrows():
|
|
657
|
-
if "freq_eff" in row and not np.isnan(row["freq_eff"]):
|
|
658
|
-
val = row["freq_eff"] * u.Unit(row["freq_units"])
|
|
659
|
-
elif "wave_eff" in df and not np.isnan(row["wave_eff"]):
|
|
660
|
-
val = row["wave_eff"] * u.Unit(row["wave_units"])
|
|
661
|
-
else:
|
|
662
|
-
raise ValueError("No known frequency or wavelength, please fix!")
|
|
663
|
-
|
|
664
|
-
freqs.append(val.to(freq_unit, equivalencies=u.spectral()).value)
|
|
665
|
-
waves.append(val.to(wave_unit, equivalencies=u.spectral()).value)
|
|
666
|
-
|
|
667
|
-
outdata["converted_freq"] = freqs
|
|
668
|
-
outdata["converted_wave"] = waves
|
|
669
|
-
outdata["converted_wave_unit"] = [wave_unit] * len(outdata)
|
|
670
|
-
outdata["converted_freq_unit"] = [freq_unit] * len(outdata)
|
|
727
|
+
outdata["converted_date_unit"] = date_unit
|
|
671
728
|
|
|
672
729
|
return outdata
|
|
673
730
|
|
|
@@ -756,16 +813,6 @@ class Transient(MutableMapping):
|
|
|
756
813
|
bothlines = [{"value": k, "reference": t1map[k] + t2map[k]} for k in inboth]
|
|
757
814
|
out[key]["alias"] = line2 + line1 + bothlines
|
|
758
815
|
|
|
759
|
-
def _merge_coords(t1, t2, out): # noqa: N805
|
|
760
|
-
"""
|
|
761
|
-
Merge the coordinates subdictionaries for t1 and t2 and put it in out
|
|
762
|
-
|
|
763
|
-
Use pandas to drop any duplicates
|
|
764
|
-
"""
|
|
765
|
-
key = "coordinate"
|
|
766
|
-
|
|
767
|
-
Transient._merge_arbitrary(key, t1, t2, out)
|
|
768
|
-
|
|
769
816
|
def _merge_filter_alias(t1, t2, out): # noqa: N805
|
|
770
817
|
"""
|
|
771
818
|
Combine the filter alias lists across the transient objects
|
|
@@ -797,8 +844,15 @@ class Transient(MutableMapping):
|
|
|
797
844
|
key = "photometry"
|
|
798
845
|
|
|
799
846
|
out[key] = deepcopy(t1[key])
|
|
800
|
-
refs = np.array([d["reference"] for d in out[key]])
|
|
847
|
+
refs = [] # np.array([d["reference"] for d in out[key]])
|
|
801
848
|
# merge_dups = lambda val: np.sum(val) if np.any(val.isna()) else val.iloc[0]
|
|
849
|
+
for val in out[key]:
|
|
850
|
+
if isinstance(val, list):
|
|
851
|
+
refs += val
|
|
852
|
+
elif isinstance(val, np.ndarray):
|
|
853
|
+
refs += list(val)
|
|
854
|
+
else:
|
|
855
|
+
refs.append(val)
|
|
802
856
|
|
|
803
857
|
for val in t2[key]:
|
|
804
858
|
# first check if t2's reference is in out
|
|
@@ -823,12 +877,6 @@ class Transient(MutableMapping):
|
|
|
823
877
|
|
|
824
878
|
out[key][i1] = newdict # replace the dictionary at i1 with the new dict
|
|
825
879
|
|
|
826
|
-
def _merge_spectra(t1, t2, out): # noqa: N805
|
|
827
|
-
"""
|
|
828
|
-
Combine spectra sources
|
|
829
|
-
"""
|
|
830
|
-
pass
|
|
831
|
-
|
|
832
880
|
def _merge_class(t1, t2, out): # noqa: N805
|
|
833
881
|
"""
|
|
834
882
|
Combine the classification attribute
|
|
@@ -864,22 +912,6 @@ class Transient(MutableMapping):
|
|
|
864
912
|
else:
|
|
865
913
|
item["default"] = False
|
|
866
914
|
|
|
867
|
-
def _merge_date(t1, t2, out): # noqa: N805
|
|
868
|
-
"""
|
|
869
|
-
Combine epoch data across two transients and write it to "out"
|
|
870
|
-
"""
|
|
871
|
-
key = "date_reference"
|
|
872
|
-
|
|
873
|
-
Transient._merge_arbitrary(key, t1, t2, out)
|
|
874
|
-
|
|
875
|
-
def _merge_distance(t1, t2, out): # noqa: N805
|
|
876
|
-
"""
|
|
877
|
-
Combine distance information for these two transients
|
|
878
|
-
"""
|
|
879
|
-
key = "distance"
|
|
880
|
-
|
|
881
|
-
Transient._merge_arbitrary(key, t1, t2, out)
|
|
882
|
-
|
|
883
915
|
@staticmethod
|
|
884
916
|
def _merge_arbitrary(key, t1, t2, out):
|
|
885
917
|
"""
|
|
@@ -889,24 +921,36 @@ class Transient(MutableMapping):
|
|
|
889
921
|
a NxM pandas dataframe!
|
|
890
922
|
"""
|
|
891
923
|
|
|
892
|
-
|
|
893
|
-
|
|
924
|
+
if key == "name":
|
|
925
|
+
t1._merge_names(t2, out)
|
|
926
|
+
elif key == "filter_alias":
|
|
927
|
+
t1._merge_filter_alias(t2, out)
|
|
928
|
+
elif key == "schema_version":
|
|
929
|
+
t1._merge_schema_version(t2, out)
|
|
930
|
+
elif key == "photometry":
|
|
931
|
+
t1._merge_photometry(t2, out)
|
|
932
|
+
elif key == "classification":
|
|
933
|
+
t1._merge_class(t2, out)
|
|
934
|
+
else:
|
|
935
|
+
# this is where we can standardize some of the merging
|
|
936
|
+
df1 = pd.DataFrame(t1[key])
|
|
937
|
+
df2 = pd.DataFrame(t2[key])
|
|
894
938
|
|
|
895
|
-
|
|
939
|
+
merged_with_dups = pd.concat([df1, df2]).reset_index(drop=True)
|
|
896
940
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
941
|
+
# have to get the indexes to drop using a string rep of the df
|
|
942
|
+
# this is cause we have lists in some cells
|
|
943
|
+
to_drop = merged_with_dups.astype(str).drop_duplicates().index
|
|
900
944
|
|
|
901
|
-
|
|
945
|
+
merged = merged_with_dups.iloc[to_drop].reset_index(drop=True)
|
|
902
946
|
|
|
903
|
-
|
|
947
|
+
outdict = merged.to_dict(orient="records")
|
|
904
948
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
949
|
+
outdict_cleaned = Transient._remove_nans(
|
|
950
|
+
outdict
|
|
951
|
+
) # clear out the nans from pandas conversion
|
|
908
952
|
|
|
909
|
-
|
|
953
|
+
out[key] = outdict_cleaned
|
|
910
954
|
|
|
911
955
|
@staticmethod
|
|
912
956
|
def _remove_nans(d):
|
otter/util.py
CHANGED
|
@@ -567,3 +567,45 @@ subschema = {
|
|
|
567
567
|
"""
|
|
568
568
|
A useful variable to describe all of the subschemas that are available and can be used
|
|
569
569
|
"""
|
|
570
|
+
|
|
571
|
+
VIZIER_LARGE_CATALOGS = [
|
|
572
|
+
"2MASS-PSC",
|
|
573
|
+
"2MASX",
|
|
574
|
+
"AC2000.2",
|
|
575
|
+
"AKARI",
|
|
576
|
+
"ALLWISE",
|
|
577
|
+
"ASCC-2.5",
|
|
578
|
+
"B/DENIS",
|
|
579
|
+
"CMC14",
|
|
580
|
+
"Gaia-DR1",
|
|
581
|
+
"GALEX",
|
|
582
|
+
"GLIMPSE",
|
|
583
|
+
"GSC-ACT",
|
|
584
|
+
"GSC1.2",
|
|
585
|
+
"GSC2.2",
|
|
586
|
+
"GSC2.3",
|
|
587
|
+
"HIP",
|
|
588
|
+
"HIP2",
|
|
589
|
+
"IRAS",
|
|
590
|
+
"NOMAD1",
|
|
591
|
+
"NVSS",
|
|
592
|
+
"PanSTARRS-DR1",
|
|
593
|
+
"PGC",
|
|
594
|
+
"Planck-DR1",
|
|
595
|
+
"PPMX",
|
|
596
|
+
"PPMXL",
|
|
597
|
+
"SDSS-DR12",
|
|
598
|
+
"SDSS-DR7",
|
|
599
|
+
"SDSS-DR9",
|
|
600
|
+
"Tycho-2",
|
|
601
|
+
"UCAC2",
|
|
602
|
+
"UCAC3",
|
|
603
|
+
"UCAC4",
|
|
604
|
+
"UKIDSS",
|
|
605
|
+
"USNO-A2",
|
|
606
|
+
"USNO-B1",
|
|
607
|
+
"WISE",
|
|
608
|
+
]
|
|
609
|
+
"""
|
|
610
|
+
ViZier catalog names that we query for host information in the Host class
|
|
611
|
+
"""
|