astro-otter 0.1.0__py3-none-any.whl → 0.3.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.

@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: astro-otter
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Author-email: Noah Franz <nfranz@arizona.edu>
5
5
  License: MIT License
6
6
 
@@ -36,14 +36,15 @@ Classifier: Programming Language :: Python :: 3
36
36
  Classifier: Programming Language :: Python :: 3.10
37
37
  Classifier: Programming Language :: Python :: 3.11
38
38
  Classifier: Development Status :: 2 - Pre-Alpha
39
- Requires-Python: <3.12,>=3.10
39
+ Requires-Python: >=3.9
40
40
  Description-Content-Type: text/markdown
41
41
  License-File: LICENSE
42
- Requires-Dist: numpy <2,>=1.20
43
- Requires-Dist: astropy >=6
42
+ Requires-Dist: numpy<2,>=1.20
43
+ Requires-Dist: astropy>=6
44
44
  Requires-Dist: pandas
45
45
  Requires-Dist: synphot
46
46
  Requires-Dist: typing-extensions
47
+ Requires-Dist: pyarango
47
48
  Requires-Dist: matplotlib
48
49
  Requires-Dist: plotly
49
50
  Requires-Dist: astroquery
@@ -53,18 +54,21 @@ Requires-Dist: fundamentals
53
54
  Requires-Dist: astro-datalab
54
55
  Requires-Dist: sparclclient
55
56
  Requires-Dist: astro-ghost
56
- Requires-Dist: pyreadline3 ; platform_system == "Windows"
57
- Provides-Extra: dev
58
- Requires-Dist: ruff ; extra == 'dev'
59
- Requires-Dist: pre-commit ; extra == 'dev'
57
+ Requires-Dist: pydantic
58
+ Requires-Dist: pyreadline3; platform_system == "Windows"
60
59
  Provides-Extra: docs
61
- Requires-Dist: Sphinx >=3.0.0 ; extra == 'docs'
62
- Requires-Dist: myst-parser >=0.13 ; extra == 'docs'
63
- Requires-Dist: nbsphinx >=0.9.1 ; extra == 'docs'
64
- Requires-Dist: sphinx-book-theme >=0.0.33 ; extra == 'docs'
65
- Requires-Dist: sphinx-copybutton ; extra == 'docs'
66
- Requires-Dist: autodoc ; extra == 'docs'
67
- Requires-Dist: ipykernel ; extra == 'docs'
60
+ Requires-Dist: Sphinx>=3.0.0; extra == "docs"
61
+ Requires-Dist: myst-parser>=0.13; extra == "docs"
62
+ Requires-Dist: nbsphinx>=0.9.1; extra == "docs"
63
+ Requires-Dist: sphinx-book-theme>=0.0.33; extra == "docs"
64
+ Requires-Dist: sphinx_copybutton; extra == "docs"
65
+ Requires-Dist: autodoc; extra == "docs"
66
+ Requires-Dist: ipykernel; extra == "docs"
67
+ Requires-Dist: autodoc_pydantic; extra == "docs"
68
+ Provides-Extra: dev
69
+ Requires-Dist: ruff; extra == "dev"
70
+ Requires-Dist: pre-commit; extra == "dev"
71
+ Dynamic: license-file
68
72
 
69
73
  # OTTER API
70
74
  ### **O**pen mul**T**iwavelength **T**ransient **E**vent **R**epository
@@ -104,6 +108,39 @@ To install the OTTER API use
104
108
  python3 -m pip install astro-otter
105
109
  ```
106
110
 
111
+ ## Developer Instructions
112
+ 1. Set the `OTTER_ROOT` environment variable
113
+ ```
114
+ export OTTER_ROOT=/path/to/where/to/clone
115
+ ```
116
+ 2. Clone the relevant repos:
117
+ ```
118
+ git clone https://github.com/astro-otter/otter.git $OTTER_ROOT/otter
119
+ git clone https://github.com/astro-otter/otterdb.git $OTTER_ROOT/otterdb
120
+ ```
121
+ 3. Install the NASA ADS Python API by following the instructions at https://ads.readthedocs.io/en/latest/#getting-started
122
+ 4. Install otter, the API for this database. From
123
+ the root directory where you installed these repos:
124
+ ```
125
+ cd $OTTER_ROOT/otter
126
+ python -m pip install -e .
127
+ ```
128
+ 5. Process the data to build the local "database" (although it is really just a directory).
129
+ Then, you can build the "database" by running the
130
+ following commands:
131
+ ```
132
+ cd $OTTER_ROOT/otter/scripts/
133
+ python3 gen_summary_table.py --otterroot $OTTER_ROOT
134
+ ```
135
+ 6. Easily access the data using the Otter code! In python:
136
+ ```
137
+ import os
138
+ from otter import Otter
139
+ otter = Otter(os.path.join(os.environ['OTTER_ROOT'], 'otterdb', '.otter'))
140
+ res = otter.query(names='AT2018hyz')
141
+ print(res)
142
+ ```
143
+
107
144
  ## Installation from Source
108
145
  To install the OTTER API from the source code use
109
146
  ```
@@ -0,0 +1,18 @@
1
+ astro_otter-0.3.0.dist-info/licenses/LICENSE,sha256=s9IPE8A3CAMEaZpDhj4eaorpmfLYGB0mIGphq301PUY,1067
2
+ otter/__init__.py,sha256=pvX-TN7nLVmvKpkDi89Zxe-jMfHNiVMD3zsd_bPEK9Y,535
3
+ otter/_version.py,sha256=XDCAY_avI84JEWPKHKBF9kXkCGBGi2CjNGEItozNsk4,76
4
+ otter/exceptions.py,sha256=3lQF4AXVTfs9VRsVePQoIrXnramsPZbUL5crvf1s9Ng,1702
5
+ otter/schema.py,sha256=eOxlrtp9TTbiENy38ueE2HcYlI-M56g0Ohg7zQeTxjk,10631
6
+ otter/util.py,sha256=xKsNkkxGajML1rZZnR9d5rJV1_z1KeAlIdAs8t0814M,22440
7
+ otter/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ otter/io/data_finder.py,sha256=v3jZCOhvysHUQG1FOwHdeJ-psTT-MsdO_GZalBpMBGc,38218
9
+ otter/io/host.py,sha256=xv_SznZuvMoMVsZLqlcmlOyaqKCMZqlTQ_gkN4VBSTw,7139
10
+ otter/io/otter.py,sha256=HiYQ5hgAvPYSHmU6taF6NumpElYzFgjjA0oIgLEo9_4,47471
11
+ otter/io/transient.py,sha256=b_gOQxjk8z3VylZ9ZrAv-Dhqp_d2iXPICL65Hq3kYGg,41846
12
+ otter/plotter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ otter/plotter/otter_plotter.py,sha256=OQhuLgnMSzgtAjJF8SYBuQOyYcu7Pr0uia5P0G_7z5Q,2144
14
+ otter/plotter/plotter.py,sha256=z09NwQVJS2tuwH3sv95DZv8xogjvf-7Gvj6iWCEx-gQ,9635
15
+ astro_otter-0.3.0.dist-info/METADATA,sha256=7qokfMceKHN1Ai0FNKqY95LYiil9jf3BzAX_7_A5KVI,7127
16
+ astro_otter-0.3.0.dist-info/WHEEL,sha256=ooBFpIzZCPdw3uqIQsOo4qqbA4ZRPxHnOH7peeONza0,91
17
+ astro_otter-0.3.0.dist-info/top_level.txt,sha256=Wth72sCwBRUk3KZGknSKvLQDMFuJk6qiaAavMDOdG5k,6
18
+ astro_otter-0.3.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (80.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
otter/__init__.py CHANGED
@@ -13,4 +13,7 @@ from .io.transient import Transient
13
13
  from .io.host import Host
14
14
  from .io.data_finder import DataFinder
15
15
  from .plotter.otter_plotter import OtterPlotter
16
- from .plotter.plotter import plot_light_curve, plot_sed
16
+ from .plotter.plotter import plot_light_curve, plot_sed, quick_view, query_quick_view
17
+ from . import util
18
+ from . import schema
19
+ from . import exceptions
otter/_version.py CHANGED
@@ -2,4 +2,4 @@
2
2
  Just define the package version in one place
3
3
  """
4
4
 
5
- __version__ = "0.1.0"
5
+ __version__ = "0.3.0"
otter/io/data_finder.py CHANGED
@@ -11,15 +11,17 @@ import re
11
11
  import time
12
12
  import math
13
13
  from urllib.request import urlopen
14
+ import requests
14
15
 
15
16
  from astropy import units as u
16
17
  from astropy.coordinates import SkyCoord
17
18
  from astropy.time import Time
18
19
  from astropy.table import Table
19
20
  from astropy.io.votable import parse_single_table
21
+ from astropy.io import ascii
20
22
 
23
+ import numpy as np
21
24
  import pandas as pd
22
- import requests
23
25
  import logging
24
26
 
25
27
  from fundamentals.stats import rolling_window_sigma_clip
@@ -382,23 +384,84 @@ class DataFinder(object):
382
384
  )
383
385
  return light_curve.data
384
386
 
385
- def query_wise(self, radius: float = 5, **kwargs) -> Table:
387
+ def query_wise(
388
+ self,
389
+ radius: float = 5,
390
+ datadir: str = "ipac/",
391
+ overwrite: bool = False,
392
+ verbose=False,
393
+ **kwargs,
394
+ ) -> pd.DataFrame:
386
395
  """
387
396
  Query NEOWISE for their multiepoch photometry
388
397
 
398
+ The method used to query wise here was taken from this github repo:
399
+ https://github.com/HC-Hwang/wise_light_curves/tree/master
400
+ and you should cite this other paper that the authors of this code developed
401
+ it for: https://ui.adsabs.harvard.edu/abs/2020MNRAS.493.2271H/abstract
402
+
403
+ This will download the ipac data files to the "datadir" argument. by default,
404
+ these will go into os.getcwd()/ipac
405
+
389
406
  Args:
390
407
  radius (float) : The cone search radius in arcseconds
408
+ overwrite (bool) : Overwrite the existing datasets downloaded from wise
391
409
  **kwargs : Other optional arguments for the astroquery query_region
392
410
  Returns:
393
411
  An astropy Table of the multiepoch wise data for this host
394
412
  """
395
- from astroquery.ipac.irsa import Irsa
413
+ # from https://www.cambridge.org/core/journals/
414
+ # publications-of-the-astronomical-society-of-australia/article/
415
+ # recalibrating-the-widefield-infrared-survey-explorer-wise-w4-filter/
416
+ # B238BFFE19A533A2D2638FE88CCC2E89
417
+ band_vals = {"w1": 3.4, "w2": 4.6, "w3": 12, "w4": 22} # in um
396
418
 
397
- wise_catalogs = "neowiser_p1bs_psd"
398
- res = DataFinder._wrap_astroquery(
399
- Irsa, self.coord, radius="5 arcsec", catalog=wise_catalogs, **kwargs
419
+ ra, dec = self.coord.ra.value, self.coord.dec.value
420
+
421
+ fbasename = f"wise_{self.name}"
422
+ allwise_name = f"{fbasename}_allwise.ipac"
423
+ neowise_name = f"{fbasename}_neowise.ipac"
424
+
425
+ if not os.path.exists(datadir):
426
+ os.makedirs(datadir)
427
+
428
+ self._download_single_data(
429
+ name=fbasename,
430
+ ra=ra,
431
+ dec=dec,
432
+ root_path=datadir,
433
+ radius=radius,
434
+ overwrite=overwrite,
400
435
  )
401
- return res
436
+
437
+ allwise = ascii.read(f"ipac/{allwise_name}", format="ipac")
438
+ neowise = ascii.read(f"ipac/{neowise_name}", format="ipac")
439
+
440
+ allwise, neowise = self._only_good_data(allwise, neowise, verbose=verbose)
441
+ if verbose and (allwise is None or neowise is None):
442
+ print(f"Limited good infrared data for {self.name}, skipping!")
443
+
444
+ mjd, mag, mag_err, filts = self._make_full_lightcurve_multibands(
445
+ allwise, neowise, bands=["w1", "w2", "w3", "w4"]
446
+ )
447
+
448
+ df = pd.DataFrame(
449
+ dict(
450
+ name=[self.name] * len(mjd),
451
+ date_mjd=mjd,
452
+ filter=filts,
453
+ filter_eff=[band_vals[f] for f in filts],
454
+ filter_eff_unit=["um"] * len(mjd),
455
+ flux=mag,
456
+ flux_err=mag_err,
457
+ flux_unit=["mag(AB)"] * len(mjd),
458
+ upperlimit=[False] * len(mjd),
459
+ )
460
+ )
461
+
462
+ # clean up the wise data by filtering out negative flux
463
+ wise = df[df.flux > 0].reset_index(drop=True)
464
+ return wise
402
465
 
403
466
  def query_alma(self, radius: float = 5, **kwargs) -> Table:
404
467
  """
@@ -415,6 +478,11 @@ class DataFinder(object):
415
478
  An astropy Table of the multiepoch wise data for this host
416
479
  """
417
480
 
481
+ logger.warn(
482
+ "This method may not work if you are using a conda environment!\
483
+ This is a known issue in setuptools that is not resolved!"
484
+ )
485
+
418
486
  from astroquery.alma import Alma
419
487
 
420
488
  res = DataFinder._wrap_astroquery(
@@ -473,9 +541,7 @@ class DataFinder(object):
473
541
  )
474
542
  return res
475
543
 
476
- def query_heasarc(
477
- self, radius: u.Quantity = 5 * u.arcsec, heasarc_table: str = "xray", **kwargs
478
- ) -> Table:
544
+ def query_heasarc(self, radius: u.Quantity = 5 * u.arcsec, **kwargs) -> Table:
479
545
  """
480
546
  Query Heasarc by the argument "heasarc_key" for the ra/dec associated with this
481
547
  DataLoader object.
@@ -494,9 +560,7 @@ class DataFinder(object):
494
560
  """
495
561
  from astroquery.heasarc import Heasarc
496
562
 
497
- res = DataFinder._wrap_astroquery(
498
- Heasarc, self.coord, mission=heasarc_table, radius=radius, **kwargs
499
- )
563
+ res = DataFinder._wrap_astroquery(Heasarc, self.coord, radius=radius, **kwargs)
500
564
 
501
565
  return res
502
566
 
@@ -742,3 +806,232 @@ class DataFinder(object):
742
806
  print("completed the ``stack_photometry`` method")
743
807
 
744
808
  return alldata
809
+
810
+ """
811
+ The following code was taken and modified for the purposes of this package from
812
+ https://github.com/HC-Hwang/wise_light_curves/blob/master/wise_light_curves.py
813
+
814
+ Original Authors:
815
+ - Matthew Hill
816
+ - Hsiang-Chih Hwang
817
+
818
+ Update Author:
819
+ - Noah Franz
820
+ """
821
+
822
+ @staticmethod
823
+ def _get_by_position(ra, dec, radius=2.5):
824
+ allwise_cat = "allwise_p3as_mep"
825
+ neowise_cat = "neowiser_p1bs_psd"
826
+ query_url = "http://irsa.ipac.caltech.edu/cgi-bin/Gator/nph-query"
827
+ payload = {
828
+ "catalog": allwise_cat,
829
+ "spatial": "cone",
830
+ "objstr": " ".join([str(ra), str(dec)]),
831
+ "radius": str(radius),
832
+ "radunits": "arcsec",
833
+ "outfmt": "1",
834
+ }
835
+ r = requests.get(query_url, params=payload)
836
+ allwise = ascii.read(r.text)
837
+ payload = {
838
+ "catalog": neowise_cat,
839
+ "spatial": "cone",
840
+ "objstr": " ".join([str(ra), str(dec)]),
841
+ "radius": str(radius),
842
+ "radunits": "arcsec",
843
+ "outfmt": "1",
844
+ "selcols": "ra,dec,sigra,sigdec,sigradec,glon,glat,elon,elat,w1mpro,w1sigmpro,w1snr,w1rchi2,w2mpro,w2sigmpro,w2snr,w2rchi2,rchi2,nb,na,w1sat,w2sat,satnum,cc_flags,det_bit,ph_qual,sso_flg,qual_frame,qi_fact,saa_sep,moon_masked,w1frtr,w2frtr,mjd,allwise_cntr,r_allwise,pa_allwise,n_allwise,w1mpro_allwise,w1sigmpro_allwise,w2mpro_allwise,w2sigmpro_allwise,w3mpro_allwise,w3sigmpro_allwise,w4mpro_allwise,w4sigmpro_allwise", # noqa: E501
845
+ }
846
+ r = requests.get(query_url, params=payload)
847
+
848
+ neowise = ascii.read(r.text, guess=False, format="ipac")
849
+
850
+ return allwise, neowise
851
+
852
+ @staticmethod
853
+ def _download_single_data(
854
+ name, ra, dec, root_path="ipac/", radius=2.5, overwrite=False
855
+ ):
856
+ # ra, dec: in degree
857
+ # name, ra, dec = row['Name'], row['RAJ2000'], row['DEJ2000']
858
+ # name = 'J' + ra + dec
859
+ if root_path[-1] != "/":
860
+ root_path += "/"
861
+ if (
862
+ not overwrite
863
+ and os.path.isfile(root_path + name + "_allwise.ipac")
864
+ and os.path.isfile(root_path + name + "_neowise.ipac")
865
+ ):
866
+ pass
867
+ else:
868
+ allwise, neowise = DataFinder._get_by_position(ra, dec, radius=radius)
869
+ allwise.write(
870
+ root_path + name + "_allwise.ipac", format="ascii.ipac", overwrite=True
871
+ )
872
+ neowise.write(
873
+ root_path + name + "_neowise.ipac", format="ascii.ipac", overwrite=True
874
+ )
875
+
876
+ @staticmethod
877
+ def _get_data_arrays(table, t, mag, magerr):
878
+ """Get the time series from a potentially masked astropy table"""
879
+ if table.masked:
880
+ full_mask = table[t].mask | table[mag].mask | table[magerr].mask
881
+ t = table[t].data
882
+ mag = table[mag].data
883
+ magerr = table[magerr].data
884
+
885
+ t.mask = full_mask
886
+ mag.mask = full_mask
887
+ magerr.mask = full_mask
888
+
889
+ return t.compressed(), mag.compressed(), magerr.compressed()
890
+
891
+ else:
892
+ return table[t].data, table[mag].data, table[magerr].data
893
+
894
+ @staticmethod
895
+ def _make_full_lightcurve(allwise, neowise, band):
896
+ """band = 'w1', 'w2', 'w3', or 'w4'"""
897
+ """Get a combined AllWISE and NEOWISE lightcurve from their Astropy tables"""
898
+
899
+ if band not in ["w1", "w2", "w3", "w4"]:
900
+ raise ValueError("band can only be w1, w2, w3, or w4")
901
+
902
+ use_neowise = band in {"w1", "w2"}
903
+ use_allwise = allwise is not None
904
+
905
+ if use_neowise and use_allwise:
906
+ t, m, e = DataFinder._get_data_arrays(
907
+ allwise, "mjd", band + "mpro_ep", band + "sigmpro_ep"
908
+ )
909
+ t_n, m_n, e_n = DataFinder._get_data_arrays(
910
+ neowise, "mjd", band + "mpro", band + "sigmpro"
911
+ )
912
+ t, m, e = (
913
+ np.concatenate((t, t_n)),
914
+ np.concatenate((m, m_n)),
915
+ np.concatenate((e, e_n)),
916
+ )
917
+
918
+ elif use_neowise and not use_allwise:
919
+ t, m, e = DataFinder._get_data_arrays(
920
+ neowise, "mjd", band + "mpro", band + "sigmpro"
921
+ )
922
+
923
+ elif not use_neowise and use_allwise:
924
+ t, m, e = DataFinder._get_data_arrays(
925
+ allwise, "mjd", band + "mpro_ep", band + "sigmpro_ep"
926
+ )
927
+
928
+ else:
929
+ raise Exception("No good allwise or neowise data!")
930
+
931
+ t_index = t.argsort()
932
+ t, m, e = map(lambda e: e[t_index], [t, m, e])
933
+
934
+ return t, m, e
935
+
936
+ @staticmethod
937
+ def _make_full_lightcurve_multibands(allwise, neowise, bands=["w1", "w2"]):
938
+ t, m, e = DataFinder._make_full_lightcurve(allwise, neowise, bands[0])
939
+ filts = [bands[0] for i in range(len(t))]
940
+ for band in bands[1:]:
941
+ try:
942
+ t_tmp, m_tmp, e_tmp = DataFinder._make_full_lightcurve(
943
+ allwise, neowise, band
944
+ )
945
+ except Exception:
946
+ continue
947
+ t = np.concatenate((t, t_tmp))
948
+ m = np.concatenate((m, m_tmp))
949
+ e = np.concatenate((e, e_tmp))
950
+ filts += [band for i in range(len(t_tmp))]
951
+ return t, m, e, np.array(filts)
952
+
953
+ @staticmethod
954
+ def _cntr_to_source_id(cntr):
955
+ cntr = str(cntr)
956
+
957
+ # fill leanding 0s
958
+ if len(cntr) < 19:
959
+ num_leading_zeros = 19 - len(cntr)
960
+ cntr = "0" * num_leading_zeros + cntr
961
+
962
+ pm = "p"
963
+ if cntr[4] == "0":
964
+ pm = "m"
965
+
966
+ t = chr(96 + int(cntr[8:10]))
967
+
968
+ return "%s%s%s_%cc%s-%s" % (
969
+ cntr[0:4],
970
+ pm,
971
+ cntr[5:8],
972
+ t,
973
+ cntr[11:13],
974
+ cntr[13:19],
975
+ )
976
+
977
+ @staticmethod
978
+ def _only_good_data(allwise, neowise, verbose=False):
979
+ """
980
+ Select good-quality data. The criteria include:
981
+ - matching the all-wise ID
982
+
983
+ To be done:
984
+ - deal with multiple cntr
985
+
986
+ This filtering is described here:
987
+ https://wise2.ipac.caltech.edu/docs/release/neowise/expsup/sec2_3.html
988
+ """
989
+
990
+ neowise_prefilter_n = len(neowise)
991
+ neowise = neowise[
992
+ (neowise["qual_frame"] > 0.0)
993
+ * (neowise["qi_fact"] > 0.9)
994
+ * (neowise["saa_sep"] > 0)
995
+ * (neowise["moon_masked"] == "00")
996
+ ]
997
+ neowise_postfilter_n = len(neowise)
998
+ if verbose:
999
+ print(
1000
+ f"Filtered out {neowise_prefilter_n-neowise_postfilter_n} neowise \
1001
+ points, leaving {neowise_postfilter_n}"
1002
+ )
1003
+
1004
+ cntr_list = []
1005
+ for data in neowise:
1006
+ if data["allwise_cntr"] not in cntr_list and data["allwise_cntr"] > 10.0:
1007
+ cntr_list.append(data["allwise_cntr"])
1008
+
1009
+ if len(cntr_list) >= 2:
1010
+ print("multiple cntr:")
1011
+ print(cntr_list)
1012
+ return None, neowise
1013
+
1014
+ if len(cntr_list) == 0:
1015
+ # import pdb; pdb.set_trace()
1016
+ # raise Exception('No center!')
1017
+ return None, neowise
1018
+
1019
+ cntr = cntr_list[0]
1020
+
1021
+ source_id = DataFinder._cntr_to_source_id(cntr)
1022
+
1023
+ allwise_prefilter_n = len(allwise)
1024
+ allwise = allwise[
1025
+ (allwise["source_id_mf"] == source_id)
1026
+ * (allwise["saa_sep"] > 0.0)
1027
+ * (allwise["moon_masked"] == "0000")
1028
+ * (allwise["qi_fact"] > 0.9)
1029
+ ]
1030
+ allwise_postfilter_n = len(neowise)
1031
+ if verbose:
1032
+ print(
1033
+ f"Filtered out {allwise_prefilter_n-allwise_postfilter_n} allwise \
1034
+ points, leaving {allwise_postfilter_n}"
1035
+ )
1036
+
1037
+ return allwise, neowise
otter/io/host.py CHANGED
@@ -4,6 +4,10 @@ for pulling in data corresponding to that host
4
4
  """
5
5
 
6
6
  from __future__ import annotations
7
+
8
+ from urllib.request import urlopen
9
+ import json
10
+
7
11
  import numpy as np
8
12
  from astropy.coordinates import SkyCoord
9
13
  from astropy import units as u
@@ -11,6 +15,10 @@ from astropy import units as u
11
15
  from .data_finder import DataFinder
12
16
  from ..exceptions import OtterLimitationError
13
17
 
18
+ import logging
19
+
20
+ logger = logging.getLogger(__name__)
21
+
14
22
 
15
23
  class Host(DataFinder):
16
24
  def __init__(
@@ -21,6 +29,7 @@ class Host(DataFinder):
21
29
  host_dec_units: str | u.Unit,
22
30
  host_name: str = None,
23
31
  host_redshift: float = None,
32
+ redshift_type: str = None,
24
33
  reference: list[str] = None,
25
34
  transient_name: str = None,
26
35
  **kwargs,
@@ -41,6 +50,8 @@ class Host(DataFinder):
41
50
  SkyCoord
42
51
  host_name (str) : The name of the host galaxy
43
52
  host_redshift (float) : The redshift of the host galaxy
53
+ redshift_type (str) : Either "phot" or "spec", tells you if the redshift
54
+ is a phot-z or spec-z.
44
55
  reference (list[str]) : a list of bibcodes that found this to be the host
45
56
  transient_name (str) : the name of the transient associated with this host
46
57
  kwargs : Just here so we can pass **Transient['host'] into this constructor
@@ -52,6 +63,7 @@ class Host(DataFinder):
52
63
  self.redshift = host_redshift # just here for ease of use
53
64
  self.bibcodes = reference
54
65
  self.transient_name = transient_name
66
+ self.redshift_type = redshift_type
55
67
 
56
68
  def pcc(self, transient_coord: SkyCoord, mag: float = None):
57
69
  """
@@ -104,3 +116,71 @@ class Host(DataFinder):
104
116
  prob = 1 - np.exp(-eta)
105
117
 
106
118
  return prob
119
+
120
+ ###################################################################################
121
+ ######### METHODS FOR FINDING HOSTS ###########################
122
+ ###################################################################################
123
+ @staticmethod
124
+ def query_blast(tns_name: str) -> dict:
125
+ """
126
+ Query the BLAST host galaxy service
127
+
128
+ Args:
129
+ tns_name (str) : The TNS target name to grab the data from BLAST for
130
+
131
+ Returns:
132
+ best match BLAST ID, host ra, host dec, host redshift. Redshift will be
133
+ spectroscopic if available, otherwise photometric.
134
+ """
135
+
136
+ # clean up the input name a little
137
+ if tns_name[:2] == "AT":
138
+ tns_name = tns_name.replace("AT", "")
139
+
140
+ failed_query_res = None
141
+
142
+ # do the query
143
+ blast_base_url = "https://blast.scimma.org"
144
+ blast_query_url = f"{blast_base_url}/api/transient/?name={tns_name}&format=json"
145
+ with urlopen(blast_query_url) as response:
146
+ if response.status != 200:
147
+ logger.warn(f"BLAST query failed with response code {response.status}!")
148
+ return failed_query_res
149
+ else:
150
+ blast_data = json.loads(response.read())
151
+ if len(blast_data) == 0:
152
+ logger.warn("BLAST query returned no results!")
153
+ return failed_query_res
154
+
155
+ blast_data = blast_data[0]
156
+ if "host" not in blast_data:
157
+ logger.warn("BLAST query found the object but it has no host associated!")
158
+ return failed_query_res
159
+
160
+ blast_host = blast_data["host"]
161
+
162
+ if blast_host["redshift"] is not None:
163
+ # prefer spec-z over phot-z
164
+ z = blast_host["redshift"]
165
+ z_type = "spec"
166
+ else:
167
+ # well I guess we need to use phot-z
168
+ z = blast_host["photometric_redshift"]
169
+ z_type = "phot"
170
+
171
+ refs = [
172
+ "2021ApJ...908..170G", # GHOST citation
173
+ "2024arXiv241017322J", # BLAST citation
174
+ ]
175
+
176
+ return Host(
177
+ host_ra=blast_host["ra_deg"],
178
+ host_dec=blast_host["dec_deg"],
179
+ host_ra_units="deg",
180
+ host_dec_units="deg",
181
+ host_name=blast_host["id"],
182
+ host_redshift=z,
183
+ redshift_type=z_type,
184
+ reference=refs,
185
+ transient_name=tns_name,
186
+ )