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.

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
- row["discovery_date"] = t.get_discovery_date()
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
- row["z"] = t.get_redshift()
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 == "name":
234
- self._merge_names(other, out)
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 _get_default(self, key, filt=""):
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
- df = df[eval(filt)] # apply the filters
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 "vega" in unit.lower():
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
- freq_units = data["freq_units"]
561
- if len(np.unique(freq_units)) > 1:
562
- raise OtterLimitationError(
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
- wave_units = data["wave_units"]
571
- if len(np.unique(wave_units)) > 1:
572
- raise OtterLimitationError(
573
- "Can not convert different units to the same unit!"
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
- wave_eff = np.array(data["wave_eff"]) * u.Unit(wave_units.iloc[0])
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
- wave_eff = np.array(
597
- list(zip(data["wave_min"], data["wave_max"]))
598
- ) * u.Unit(wave_units.iloc[0])
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
- # we unfortunately have to loop over the points here because
604
- # syncphot does not work with a 2D array of min max wavelengths
605
- # for converting counts to other flux units. It also can't convert
606
- # vega mags with a wavelength array because it then interprets that as the
607
- # wavelengths corresponding to the SourceSpectrum.from_vega()
608
- flux, flux_err = [], []
609
- for wave, xray_point, xray_point_err in zip(wave_eff, q, q_err):
610
- f_val = convert_flux(
611
- wave,
612
- xray_point,
613
- u.Unit(flux_unit),
614
- vegaspec=SourceSpectrum.from_vega(),
615
- area=area,
616
- )
617
- f_err = convert_flux(
618
- wave,
619
- xray_point_err,
620
- u.Unit(flux_unit),
621
- vegaspec=SourceSpectrum.from_vega(),
622
- area=area,
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
- # then we take the average of the minimum and maximum values
626
- # computed by syncphot
627
- flux.append(np.mean(f_val).value)
628
- flux_err.append(np.mean(f_err).value)
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"] = [flux_unit] * len(outdata)
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"] = [date_unit] * len(outdata)
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
- df1 = pd.DataFrame(t1[key])
893
- df2 = pd.DataFrame(t2[key])
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
- merged_with_dups = pd.concat([df1, df2]).reset_index(drop=True)
939
+ merged_with_dups = pd.concat([df1, df2]).reset_index(drop=True)
896
940
 
897
- # have to get the indexes to drop using a string rep of the df
898
- # this is cause we have lists in some cells
899
- to_drop = merged_with_dups.astype(str).drop_duplicates().index
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
- merged = merged_with_dups.iloc[to_drop].reset_index(drop=True)
945
+ merged = merged_with_dups.iloc[to_drop].reset_index(drop=True)
902
946
 
903
- outdict = merged.to_dict(orient="records")
947
+ outdict = merged.to_dict(orient="records")
904
948
 
905
- outdict_cleaned = Transient._remove_nans(
906
- outdict
907
- ) # clear out the nans from pandas conversion
949
+ outdict_cleaned = Transient._remove_nans(
950
+ outdict
951
+ ) # clear out the nans from pandas conversion
908
952
 
909
- out[key] = outdict_cleaned
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
+ """