astro-otter 0.0.1__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
@@ -2,6 +2,7 @@
2
2
  This is the primary class for user interaction with the catalog
3
3
  """
4
4
 
5
+ from __future__ import annotations
5
6
  import os
6
7
  import json
7
8
  import glob
@@ -28,14 +29,11 @@ class Otter(object):
28
29
  This is the primary class for users to access the otter backend database
29
30
 
30
31
  Args:
31
- username [str]: Your connection username to the database, default is the user
32
- login which only has read permission.
33
- password [str]: Your password corresponding to your username.
34
- db [str]: The database name to connect to. This is default to 'otter' which is
35
- the only database so far.
36
- collection [str]: The collection to read data from. Right now the only
37
- collection is 'tdes'.
38
- debug [bool]: debug mode, set to true to limit reading from database.
32
+ datadir (str): Path to the data directory with the otter data. If not provided
33
+ will default to a ".otter" directory in the CWD where you call
34
+ this class from.
35
+ debug (bool): If we should just debug and not do anything serious.
36
+
39
37
  """
40
38
 
41
39
  def __init__(self, datadir: str = None, debug: bool = False) -> None:
@@ -87,9 +85,9 @@ class Otter(object):
87
85
  Performs a cone search of the catalog over the given coords and radius.
88
86
 
89
87
  Args:
90
- coords [SkyCoord]: An astropy SkyCoord object with coordinates to match to
91
- radius [float]: The radius of the cone in arcseconds, default is 0.05"
92
- raw [bool]: If False (the default) return an astropy table of the metadata
88
+ coords (SkyCoord): An astropy SkyCoord object with coordinates to match to
89
+ radius (float): The radius of the cone in arcseconds, default is 0.05"
90
+ raw (bool): If False (the default) return an astropy table of the metadata
93
91
  for matching objects. Otherwise, return the raw json dicts
94
92
 
95
93
  Return:
@@ -117,35 +115,40 @@ class Otter(object):
117
115
  unit conversion for you!
118
116
 
119
117
  Args:
120
- flux_units [astropy.unit.Unit]: Either a valid string to convert
118
+ flux_units (astropy.unit.Unit): Either a valid string to convert
121
119
  or an astropy.unit.Unit
122
- date_units [astropy.unit.Unit]: Either a valid string to convert to a date
120
+ date_units (astropy.unit.Unit): Either a valid string to convert to a date
123
121
  or an astropy.unit.Unit
124
- return_type [str]: Either 'astropy' or 'pandas'. If astropy, returns an
122
+ return_type (str): Either 'astropy' or 'pandas'. If astropy, returns an
125
123
  astropy Table. If pandas, returns a pandas DataFrame.
126
124
  Default is 'astropy'.
127
- obs_type [str]: Either 'radio', 'uvoir', or 'xray'. Will only return that
125
+ obs_type (str): Either 'radio', 'uvoir', or 'xray'. Will only return that
128
126
  type of photometry if not None. Default is None and will
129
127
  return any type of photometry.
130
- keep_raw [bool]: If True, keep the raw flux/date/freq/wave associated with
128
+ keep_raw (bool): If True, keep the raw flux/date/freq/wave associated with
131
129
  the dataset. Else, just keep the converted data. Default
132
130
  is False.
133
- **kwargs : Arguments to pass to Otter.query(). Can be:
134
- names [list[str]]: A list of names to get the metadata for
135
- coords [SkyCoord]: An astropy SkyCoord object with coordinates
131
+ **kwargs : Arguments to pass to Otter.query(). Can be::
132
+
133
+ names (list[str]): A list of names to get the metadata for
134
+ coords (SkyCoord): An astropy SkyCoord object with coordinates
136
135
  to match to
137
- radius [float]: The radius in arcseconds for a cone search,
136
+ radius (float): The radius in arcseconds for a cone search,
138
137
  default is 0.05"
139
- minZ [float]: The minimum redshift to search for
140
- maxZ [float]: The maximum redshift to search for
141
- refs [list[str]]: A list of ads bibcodes to match to. Will only
138
+ minZ (float): The minimum redshift to search for
139
+ maxZ (float): The maximum redshift to search for
140
+ refs (list[str]): A list of ads bibcodes to match to. Will only
142
141
  return metadata for transients that have this
143
142
  as a reference.
144
- hasSpec [bool]: if True, only return events that have spectra.
143
+ hasSpec (bool): if True, only return events that have spectra.
145
144
 
146
145
  Return:
147
- The photometry for the requested transients that match the arguments.
148
- Will be an astropy Table sorted by transient default name.
146
+ The photometry for the requested transients that match the arguments.
147
+ Will be an astropy Table sorted by transient default name.
148
+
149
+ Raises:
150
+ FailedQueryError: When the query returns no results
151
+ IOError: if one of your inputs is incorrect
149
152
  """
150
153
  queryres = self.query(hasphot=True, **kwargs)
151
154
 
@@ -153,16 +156,25 @@ class Otter(object):
153
156
  for transient in queryres:
154
157
  # clean the photometry
155
158
  default_name = transient["name/default_name"]
156
- phot = transient.clean_photometry(
157
- flux_unit=flux_unit,
158
- date_unit=date_unit,
159
- wave_unit=wave_unit,
160
- freq_unit=freq_unit,
161
- obs_type=obs_type,
162
- )
163
- phot["name"] = [default_name] * len(phot)
164
159
 
165
- dicts.append(phot)
160
+ try:
161
+ phot = transient.clean_photometry(
162
+ flux_unit=flux_unit,
163
+ date_unit=date_unit,
164
+ wave_unit=wave_unit,
165
+ freq_unit=freq_unit,
166
+ obs_type=obs_type,
167
+ )
168
+
169
+ phot["name"] = [default_name] * len(phot)
170
+
171
+ dicts.append(phot)
172
+
173
+ except FailedQueryError:
174
+ # This is fine, it just means that there is no data associated
175
+ # with this one transient. We'll check and make sure there is data
176
+ # associated with at least one of the transients later!
177
+ pass
166
178
 
167
179
  if len(dicts) == 0:
168
180
  raise FailedQueryError()
@@ -180,11 +192,16 @@ class Otter(object):
180
192
  "converted_date_unit",
181
193
  "converted_wave_unit",
182
194
  "converted_freq_unit",
195
+ "filter_key",
183
196
  "obs_type",
184
197
  "upperlimit",
185
198
  "reference",
199
+ "human_readable_refs",
186
200
  ]
187
201
 
202
+ if "upperlimit" not in fullphot:
203
+ fullphot["upperlimit"] = False
204
+
188
205
  if not keep_raw:
189
206
  if "telescope" in fullphot:
190
207
  fullphot = fullphot[keys_to_keep + ["telescope"]]
@@ -203,7 +220,7 @@ class Otter(object):
203
220
  Loads an otter JSON file
204
221
 
205
222
  Args:
206
- filename [str]: The path to the file to load
223
+ filename (str): The path to the OTTER JSON file to load
207
224
  """
208
225
 
209
226
  # read in files from summary
@@ -234,15 +251,15 @@ class Otter(object):
234
251
  same units.
235
252
 
236
253
  Args:
237
- names [list[str]]: A list of names to get the metadata for
238
- coords [SkyCoord]: An astropy SkyCoord object with coordinates to match to
239
- radius [float]: The radius in arcseconds for a cone search, default is 0.05"
240
- minz [float]: The minimum redshift to search for
241
- maxz [float]: The maximum redshift to search for
242
- refs [list[str]]: A list of ads bibcodes to match to. Will only return
254
+ names (list[str]): A list of names to get the metadata for
255
+ coords (SkyCoord): An astropy SkyCoord object with coordinates to match to
256
+ radius (float): The radius in arcseconds for a cone search, default is 0.05"
257
+ minz (float): The minimum redshift to search for
258
+ maxz (float): The maximum redshift to search for
259
+ refs (list[str]): A list of ads bibcodes to match to. Will only return
243
260
  metadata for transients that have this as a reference.
244
- hasphot [bool]: if True, only returns transients which have photometry.
245
- hasspec [bool]: if True, only return transients that have spectra.
261
+ hasphot (bool): if True, only returns transients which have photometry.
262
+ hasspec (bool): if True, only return transients that have spectra.
246
263
 
247
264
  Return:
248
265
  Get all of the raw (unconverted!) data for objects that match the criteria.
@@ -273,7 +290,6 @@ class Otter(object):
273
290
 
274
291
  # then read and query the summary table
275
292
  summary = pd.read_csv(summary_table)
276
-
277
293
  # coordinate search first
278
294
  if coords is not None:
279
295
  if not isinstance(coords, SkyCoord):
@@ -344,24 +360,29 @@ class Otter(object):
344
360
 
345
361
  return outdata
346
362
 
347
- def save(self, schema: list[dict], testing=False, **kwargs) -> None:
363
+ def save(self, schema: list[dict], testing=False) -> None:
348
364
  """
349
365
  Upload all the data in the given list of schemas.
350
366
 
351
367
  Args:
352
- schema [list[dict]]: A list of json dictionaries
368
+ schema (list[dict]): A list of json dictionaries
369
+ testing (bool): Should we just enter test mode? Default is False
370
+
371
+ Raises:
372
+ OtterLimitationError: If some objects in OTTER are within 5" we can't figure
373
+ out which ones to merge with which ones.
353
374
  """
354
375
 
355
376
  if not isinstance(schema, list):
356
377
  schema = [schema]
357
378
 
358
379
  for transient in schema:
359
- print(transient["name/default_name"])
360
-
361
380
  # convert the json to a Transient
362
381
  if not isinstance(transient, Transient):
363
382
  transient = Transient(transient)
364
383
 
384
+ print(transient["name/default_name"])
385
+
365
386
  coord = transient.get_skycoord()
366
387
  res = self.cone_search(coords=coord)
367
388
 
@@ -405,8 +426,6 @@ class Otter(object):
405
426
  outfilepath = os.path.join(self.DATADIR, todel[0] + ".json")
406
427
  if test_mode:
407
428
  print("Renaming the following file for backups: ", outfilepath)
408
- else:
409
- os.rename(outfilepath, outfilepath + ".backup")
410
429
  else:
411
430
  if test_mode:
412
431
  print("Don't need to mess with the files at all!")
@@ -429,13 +448,16 @@ class Otter(object):
429
448
  print(f"Would write to {outfilepath}")
430
449
  # print(out)
431
450
 
432
- def generate_summary_table(self, save=False):
451
+ def generate_summary_table(self, save=False) -> pd.DataFrame:
433
452
  """
434
453
  Generate a summary table for the JSON files in self.DATADIR
435
454
 
436
455
  args:
437
- save [bool]: if True, save the summary file to "summary.csv"
438
- in self.DATADIR. Default is False.
456
+ save (bool): if True, save the summary file to "summary.csv"
457
+ in self.DATADIR. Default is False and is just returned.
458
+
459
+ returns:
460
+ pandas.DataFrame of the summary meta information of the transients
439
461
  """
440
462
  allfiles = glob.glob(os.path.join(self.DATADIR, "*.json"))
441
463
 
@@ -455,10 +477,14 @@ class Otter(object):
455
477
  }
456
478
 
457
479
  if "date_reference" in t:
458
- 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()
459
483
 
460
484
  if "distance" in t:
461
- 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()
462
488
 
463
489
  row["hasPhot"] = "photometry" in t
464
490
  row["hasSpec"] = "spectra" in t