astro-otter 0.0.1__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.0.1.dist-info/LICENSE +21 -0
- astro_otter-0.0.1.dist-info/METADATA +866 -0
- astro_otter-0.0.1.dist-info/RECORD +15 -0
- astro_otter-0.0.1.dist-info/WHEEL +5 -0
- astro_otter-0.0.1.dist-info/top_level.txt +1 -0
- otter/__init__.py +12 -0
- otter/_version.py +5 -0
- otter/exceptions.py +26 -0
- otter/io/__init__.py +0 -0
- otter/io/otter.py +474 -0
- otter/io/transient.py +883 -0
- otter/plotter/__init__.py +0 -0
- otter/plotter/otter_plotter.py +67 -0
- otter/plotter/plotter.py +95 -0
- otter/util.py +510 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
otter/__init__.py,sha256=llO9sEPh8xa_dH2W0nlp6eaINvOFmxdThUMfnRLvTlg,339
|
|
2
|
+
otter/_version.py,sha256=IF3Dl1lgaRSZdnbDGaVgT-Wp8FNX0rjz37C1iwLyXlU,76
|
|
3
|
+
otter/exceptions.py,sha256=jaNg0fw5WBWlIRLG0aE5xKJvpIg8JAEDqQ7orwkStq4,494
|
|
4
|
+
otter/util.py,sha256=rnzyNWwjrBLsZIbKi33o1Ly-HbTeAMaUT5E8N313RiY,13600
|
|
5
|
+
otter/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
otter/io/otter.py,sha256=S1DaOFg3DtPPyNzrvOg7_QG8fps_6m-w-usINJ6X2gk,17038
|
|
7
|
+
otter/io/transient.py,sha256=vMyaYjlc2SKn9gWII8wrpJRe3NyZViXGMiZ9Pv_voNo,31165
|
|
8
|
+
otter/plotter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
otter/plotter/otter_plotter.py,sha256=WbnzNUR_pE3d2tJcH3K2pxJvl-r6OUUOZ0kWb35FlQE,2083
|
|
10
|
+
otter/plotter/plotter.py,sha256=QeAQXgeJ9O429Khin9dCT2tXvcjxUzc_HLU74LbRw6g,2861
|
|
11
|
+
astro_otter-0.0.1.dist-info/LICENSE,sha256=s9IPE8A3CAMEaZpDhj4eaorpmfLYGB0mIGphq301PUY,1067
|
|
12
|
+
astro_otter-0.0.1.dist-info/METADATA,sha256=KM1FpQrYPArikJMNzmNCYshN6PzJGrYCp4UCTkM3TOw,31847
|
|
13
|
+
astro_otter-0.0.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
14
|
+
astro_otter-0.0.1.dist-info/top_level.txt,sha256=Wth72sCwBRUk3KZGknSKvLQDMFuJk6qiaAavMDOdG5k,6
|
|
15
|
+
astro_otter-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
otter
|
otter/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# get the version
|
|
2
|
+
from ._version import __version__
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# explicitly set the package variable to ensure relative import work
|
|
6
|
+
__package__ = "otter"
|
|
7
|
+
|
|
8
|
+
# import important stuff
|
|
9
|
+
from .io.otter import Otter
|
|
10
|
+
from .io.transient import Transient
|
|
11
|
+
from .plotter.otter_plotter import OtterPlotter
|
|
12
|
+
from .plotter.plotter import plot_light_curve, plot_sed
|
otter/_version.py
ADDED
otter/exceptions.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for otter
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FailedQueryError(ValueError):
|
|
7
|
+
def __str__(self):
|
|
8
|
+
txt = "You're query/search did not return any results! "
|
|
9
|
+
txt += "Try again with different parameters!"
|
|
10
|
+
return txt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IOError(ValueError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OtterLimitationError(Exception):
|
|
18
|
+
def __init__(self, msg):
|
|
19
|
+
self.msg = "Current Limitation Found: " + msg
|
|
20
|
+
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return self.msg
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TransientMergeError(Exception):
|
|
26
|
+
pass
|
otter/io/__init__.py
ADDED
|
File without changes
|
otter/io/otter.py
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This is the primary class for user interaction with the catalog
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import json
|
|
7
|
+
import glob
|
|
8
|
+
from warnings import warn
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from astropy.coordinates import SkyCoord, search_around_sky
|
|
13
|
+
from astropy.table import Table
|
|
14
|
+
from astropy import units as u
|
|
15
|
+
|
|
16
|
+
from .transient import Transient
|
|
17
|
+
from ..exceptions import FailedQueryError, OtterLimitationError
|
|
18
|
+
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
21
|
+
warnings.simplefilter("once", RuntimeWarning)
|
|
22
|
+
warnings.simplefilter("once", UserWarning)
|
|
23
|
+
warnings.simplefilter("once", u.UnitsWarning)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Otter(object):
|
|
27
|
+
"""
|
|
28
|
+
This is the primary class for users to access the otter backend database
|
|
29
|
+
|
|
30
|
+
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.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, datadir: str = None, debug: bool = False) -> None:
|
|
42
|
+
# save inputs
|
|
43
|
+
if datadir is None:
|
|
44
|
+
self.CWD = os.path.dirname(os.path.abspath("__FILE__"))
|
|
45
|
+
self.DATADIR = os.path.join(self.CWD, ".otter")
|
|
46
|
+
else:
|
|
47
|
+
self.CWD = os.path.dirname(datadir)
|
|
48
|
+
self.DATADIR = datadir
|
|
49
|
+
|
|
50
|
+
self.debug = debug
|
|
51
|
+
|
|
52
|
+
# make sure the data directory exists
|
|
53
|
+
if not os.path.exists(self.DATADIR):
|
|
54
|
+
try:
|
|
55
|
+
os.makedirs(self.DATADIR)
|
|
56
|
+
except FileExistsError:
|
|
57
|
+
warn(
|
|
58
|
+
"Directory was created between the if statement and trying "
|
|
59
|
+
+ "to create the directory!"
|
|
60
|
+
)
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def get_meta(self, **kwargs) -> Table:
|
|
64
|
+
"""
|
|
65
|
+
Get the metadata of the objects matching the arguments
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
**kwargs : Arguments to pass to Otter.query()
|
|
69
|
+
Return:
|
|
70
|
+
The metadata for the transients that match the arguments. Will be an astropy
|
|
71
|
+
Table by default, if raw=True will be a dictionary.
|
|
72
|
+
"""
|
|
73
|
+
metakeys = [
|
|
74
|
+
"name",
|
|
75
|
+
"coordinate",
|
|
76
|
+
"date_reference",
|
|
77
|
+
"distance",
|
|
78
|
+
"classification",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
return [t[metakeys] for t in self.query(**kwargs)]
|
|
82
|
+
|
|
83
|
+
def cone_search(
|
|
84
|
+
self, coords: SkyCoord, radius: float = 5, raw: bool = False
|
|
85
|
+
) -> Table:
|
|
86
|
+
"""
|
|
87
|
+
Performs a cone search of the catalog over the given coords and radius.
|
|
88
|
+
|
|
89
|
+
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
|
|
93
|
+
for matching objects. Otherwise, return the raw json dicts
|
|
94
|
+
|
|
95
|
+
Return:
|
|
96
|
+
The metadata for the transients in coords+radius. Will return an astropy
|
|
97
|
+
Table if raw is False, otherwise a dict.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
transients = self.query(coords=coords, radius=radius, raw=raw)
|
|
101
|
+
|
|
102
|
+
return transients
|
|
103
|
+
|
|
104
|
+
def get_phot(
|
|
105
|
+
self,
|
|
106
|
+
flux_unit="mag(AB)",
|
|
107
|
+
date_unit="MJD",
|
|
108
|
+
return_type="astropy",
|
|
109
|
+
obs_type=None,
|
|
110
|
+
keep_raw=False,
|
|
111
|
+
wave_unit="nm",
|
|
112
|
+
freq_unit="GHz",
|
|
113
|
+
**kwargs,
|
|
114
|
+
) -> Table:
|
|
115
|
+
"""
|
|
116
|
+
Get the photometry of the objects matching the arguments. This will do the
|
|
117
|
+
unit conversion for you!
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
flux_units [astropy.unit.Unit]: Either a valid string to convert
|
|
121
|
+
or an astropy.unit.Unit
|
|
122
|
+
date_units [astropy.unit.Unit]: Either a valid string to convert to a date
|
|
123
|
+
or an astropy.unit.Unit
|
|
124
|
+
return_type [str]: Either 'astropy' or 'pandas'. If astropy, returns an
|
|
125
|
+
astropy Table. If pandas, returns a pandas DataFrame.
|
|
126
|
+
Default is 'astropy'.
|
|
127
|
+
obs_type [str]: Either 'radio', 'uvoir', or 'xray'. Will only return that
|
|
128
|
+
type of photometry if not None. Default is None and will
|
|
129
|
+
return any type of photometry.
|
|
130
|
+
keep_raw [bool]: If True, keep the raw flux/date/freq/wave associated with
|
|
131
|
+
the dataset. Else, just keep the converted data. Default
|
|
132
|
+
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
|
|
136
|
+
to match to
|
|
137
|
+
radius [float]: The radius in arcseconds for a cone search,
|
|
138
|
+
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
|
|
142
|
+
return metadata for transients that have this
|
|
143
|
+
as a reference.
|
|
144
|
+
hasSpec [bool]: if True, only return events that have spectra.
|
|
145
|
+
|
|
146
|
+
Return:
|
|
147
|
+
The photometry for the requested transients that match the arguments.
|
|
148
|
+
Will be an astropy Table sorted by transient default name.
|
|
149
|
+
"""
|
|
150
|
+
queryres = self.query(hasphot=True, **kwargs)
|
|
151
|
+
|
|
152
|
+
dicts = []
|
|
153
|
+
for transient in queryres:
|
|
154
|
+
# clean the photometry
|
|
155
|
+
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
|
+
|
|
165
|
+
dicts.append(phot)
|
|
166
|
+
|
|
167
|
+
if len(dicts) == 0:
|
|
168
|
+
raise FailedQueryError()
|
|
169
|
+
fullphot = pd.concat(dicts)
|
|
170
|
+
|
|
171
|
+
# remove some possibly confusing keys
|
|
172
|
+
keys_to_keep = [
|
|
173
|
+
"name",
|
|
174
|
+
"converted_flux",
|
|
175
|
+
"converted_flux_err",
|
|
176
|
+
"converted_date",
|
|
177
|
+
"converted_wave",
|
|
178
|
+
"converted_freq",
|
|
179
|
+
"converted_flux_unit",
|
|
180
|
+
"converted_date_unit",
|
|
181
|
+
"converted_wave_unit",
|
|
182
|
+
"converted_freq_unit",
|
|
183
|
+
"obs_type",
|
|
184
|
+
"upperlimit",
|
|
185
|
+
"reference",
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
if not keep_raw:
|
|
189
|
+
if "telescope" in fullphot:
|
|
190
|
+
fullphot = fullphot[keys_to_keep + ["telescope"]]
|
|
191
|
+
else:
|
|
192
|
+
fullphot = fullphot[keys_to_keep]
|
|
193
|
+
|
|
194
|
+
if return_type == "astropy":
|
|
195
|
+
return Table.from_pandas(fullphot)
|
|
196
|
+
elif return_type == "pandas":
|
|
197
|
+
return fullphot
|
|
198
|
+
else:
|
|
199
|
+
raise IOError("return_type can only be pandas or astropy")
|
|
200
|
+
|
|
201
|
+
def load_file(self, filename: str) -> dict:
|
|
202
|
+
"""
|
|
203
|
+
Loads an otter JSON file
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
filename [str]: The path to the file to load
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
# read in files from summary
|
|
210
|
+
with open(filename, "r") as f:
|
|
211
|
+
to_ret = Transient(json.load(f))
|
|
212
|
+
|
|
213
|
+
return to_ret
|
|
214
|
+
|
|
215
|
+
def query(
|
|
216
|
+
self,
|
|
217
|
+
names: list[str] = None,
|
|
218
|
+
coords: SkyCoord = None,
|
|
219
|
+
radius: float = 5,
|
|
220
|
+
minz: float = None,
|
|
221
|
+
maxz: float = None,
|
|
222
|
+
refs: list[str] = None,
|
|
223
|
+
hasphot: bool = False,
|
|
224
|
+
hasspec: bool = False,
|
|
225
|
+
raw: bool = False,
|
|
226
|
+
) -> dict:
|
|
227
|
+
"""
|
|
228
|
+
Searches the summary.csv table and reads relevant JSON files
|
|
229
|
+
|
|
230
|
+
WARNING! This does not do any conversions for you!
|
|
231
|
+
This is how it differs from the `get_meta` method. Users should prefer to use
|
|
232
|
+
`get_meta`, `getPhot`, and `getSpec` independently because it is a better
|
|
233
|
+
workflow and can return the data in an astropy table with everything in the
|
|
234
|
+
same units.
|
|
235
|
+
|
|
236
|
+
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
|
|
243
|
+
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.
|
|
246
|
+
|
|
247
|
+
Return:
|
|
248
|
+
Get all of the raw (unconverted!) data for objects that match the criteria.
|
|
249
|
+
"""
|
|
250
|
+
if (
|
|
251
|
+
all(arg is None for arg in [names, coords, maxz, minz, refs])
|
|
252
|
+
and not hasphot
|
|
253
|
+
and not hasspec
|
|
254
|
+
):
|
|
255
|
+
# there's nothing to query!
|
|
256
|
+
# read in the metdata from all json files
|
|
257
|
+
# this could be dangerous later on!!
|
|
258
|
+
allfiles = glob.glob(os.path.join(self.DATADIR, "*.json"))
|
|
259
|
+
jsondata = []
|
|
260
|
+
|
|
261
|
+
# read the data from all the json files and convert to Transients
|
|
262
|
+
for jsonfile in allfiles:
|
|
263
|
+
with open(jsonfile, "r") as j:
|
|
264
|
+
t = Transient(json.load(j))
|
|
265
|
+
jsondata.append(t.get_meta())
|
|
266
|
+
|
|
267
|
+
return jsondata
|
|
268
|
+
|
|
269
|
+
# check if the summary table exists, if it doen't create it
|
|
270
|
+
summary_table = os.path.join(self.DATADIR, "summary.csv")
|
|
271
|
+
if not os.path.exists(summary_table):
|
|
272
|
+
self.generate_summary_table(save=True)
|
|
273
|
+
|
|
274
|
+
# then read and query the summary table
|
|
275
|
+
summary = pd.read_csv(summary_table)
|
|
276
|
+
|
|
277
|
+
# coordinate search first
|
|
278
|
+
if coords is not None:
|
|
279
|
+
if not isinstance(coords, SkyCoord):
|
|
280
|
+
raise ValueError("Input coordinate must be an astropy SkyCoord!")
|
|
281
|
+
summary_coords = SkyCoord(
|
|
282
|
+
summary.ra.tolist(), summary.dec.tolist(), unit=(u.deg, u.deg)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
summary_idx, _, _, _ = search_around_sky(
|
|
287
|
+
summary_coords, coords, seplimit=radius * u.arcsec
|
|
288
|
+
)
|
|
289
|
+
except ValueError:
|
|
290
|
+
summary_idx, _, _, _ = search_around_sky(
|
|
291
|
+
summary_coords,
|
|
292
|
+
SkyCoord([coords]),
|
|
293
|
+
seplimit=radius * u.arcsec,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
summary = summary.iloc[summary_idx]
|
|
297
|
+
|
|
298
|
+
# redshift
|
|
299
|
+
if minz is not None:
|
|
300
|
+
summary = summary[summary.z.astype(float) >= minz]
|
|
301
|
+
|
|
302
|
+
if maxz is not None:
|
|
303
|
+
summary = summary[summary.z.astype(float) <= maxz]
|
|
304
|
+
|
|
305
|
+
# check photometry and spectra
|
|
306
|
+
if hasphot:
|
|
307
|
+
summary = summary[summary.hasPhot == True]
|
|
308
|
+
|
|
309
|
+
if hasspec:
|
|
310
|
+
summary = summary[summary.hasSpec == True]
|
|
311
|
+
|
|
312
|
+
# check names
|
|
313
|
+
if names is not None:
|
|
314
|
+
if isinstance(names, str):
|
|
315
|
+
n = {names}
|
|
316
|
+
else:
|
|
317
|
+
n = set(names)
|
|
318
|
+
|
|
319
|
+
checknames = []
|
|
320
|
+
for alias_row in summary.alias:
|
|
321
|
+
rs = set(eval(alias_row))
|
|
322
|
+
intersection = list(n & rs)
|
|
323
|
+
checknames.append(len(intersection) > 0)
|
|
324
|
+
|
|
325
|
+
summary = summary[checknames]
|
|
326
|
+
|
|
327
|
+
# check references
|
|
328
|
+
if refs is not None:
|
|
329
|
+
checkrefs = []
|
|
330
|
+
|
|
331
|
+
if isinstance(refs, str):
|
|
332
|
+
n = {refs}
|
|
333
|
+
else:
|
|
334
|
+
n = set(refs)
|
|
335
|
+
|
|
336
|
+
for ref_row in summary.refs:
|
|
337
|
+
rs = set(eval(ref_row))
|
|
338
|
+
intersection = list(n & rs)
|
|
339
|
+
checkrefs.append(len(intersection) > 0)
|
|
340
|
+
|
|
341
|
+
summary = summary[checkrefs]
|
|
342
|
+
|
|
343
|
+
outdata = [self.load_file(path) for path in summary.json_path]
|
|
344
|
+
|
|
345
|
+
return outdata
|
|
346
|
+
|
|
347
|
+
def save(self, schema: list[dict], testing=False, **kwargs) -> None:
|
|
348
|
+
"""
|
|
349
|
+
Upload all the data in the given list of schemas.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
schema [list[dict]]: A list of json dictionaries
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
if not isinstance(schema, list):
|
|
356
|
+
schema = [schema]
|
|
357
|
+
|
|
358
|
+
for transient in schema:
|
|
359
|
+
print(transient["name/default_name"])
|
|
360
|
+
|
|
361
|
+
# convert the json to a Transient
|
|
362
|
+
if not isinstance(transient, Transient):
|
|
363
|
+
transient = Transient(transient)
|
|
364
|
+
|
|
365
|
+
coord = transient.get_skycoord()
|
|
366
|
+
res = self.cone_search(coords=coord)
|
|
367
|
+
|
|
368
|
+
if len(res) == 0:
|
|
369
|
+
# This is a new object to upload
|
|
370
|
+
print("Adding this as a new object...")
|
|
371
|
+
self._save_document(dict(transient), test_mode=testing)
|
|
372
|
+
|
|
373
|
+
else:
|
|
374
|
+
# We must merge this with existing data
|
|
375
|
+
print("Found this object in the database already, merging the data...")
|
|
376
|
+
if len(res) == 1:
|
|
377
|
+
# we can just add these to merge them!
|
|
378
|
+
combined = res[0] + transient
|
|
379
|
+
self._save_document(combined, test_mode=testing)
|
|
380
|
+
else:
|
|
381
|
+
# for now throw an error
|
|
382
|
+
# this is a limitation we can come back to fix if it is causing
|
|
383
|
+
# problems though!
|
|
384
|
+
raise OtterLimitationError("Some objects in Otter are too close!")
|
|
385
|
+
|
|
386
|
+
# update the summary table appropriately
|
|
387
|
+
self.generate_summary_table(save=True)
|
|
388
|
+
|
|
389
|
+
def _save_document(self, schema, test_mode=False):
|
|
390
|
+
"""
|
|
391
|
+
Save a json file in the correct format to the OTTER data directory
|
|
392
|
+
"""
|
|
393
|
+
# check if this documents key is in the database already
|
|
394
|
+
# and if so remove it!
|
|
395
|
+
jsonpath = os.path.join(self.DATADIR, "*.json")
|
|
396
|
+
aliases = {item["value"].replace(" ", "-") for item in schema["name"]["alias"]}
|
|
397
|
+
filenames = {
|
|
398
|
+
os.path.basename(fname).split(".")[0] for fname in glob.glob(jsonpath)
|
|
399
|
+
}
|
|
400
|
+
todel = list(aliases & filenames)
|
|
401
|
+
|
|
402
|
+
# now save this data
|
|
403
|
+
# create a new file in self.DATADIR with this
|
|
404
|
+
if len(todel) > 0:
|
|
405
|
+
outfilepath = os.path.join(self.DATADIR, todel[0] + ".json")
|
|
406
|
+
if test_mode:
|
|
407
|
+
print("Renaming the following file for backups: ", outfilepath)
|
|
408
|
+
else:
|
|
409
|
+
os.rename(outfilepath, outfilepath + ".backup")
|
|
410
|
+
else:
|
|
411
|
+
if test_mode:
|
|
412
|
+
print("Don't need to mess with the files at all!")
|
|
413
|
+
fname = schema["name"]["default_name"] + ".json"
|
|
414
|
+
fname = fname.replace(" ", "-") # replace spaces in the filename
|
|
415
|
+
outfilepath = os.path.join(self.DATADIR, fname)
|
|
416
|
+
|
|
417
|
+
# format as a json
|
|
418
|
+
if isinstance(schema, Transient):
|
|
419
|
+
schema = dict(schema)
|
|
420
|
+
|
|
421
|
+
out = json.dumps(schema, indent=4)
|
|
422
|
+
# out = '[' + out
|
|
423
|
+
# out += ']'
|
|
424
|
+
|
|
425
|
+
if not test_mode:
|
|
426
|
+
with open(outfilepath, "w") as f:
|
|
427
|
+
f.write(out)
|
|
428
|
+
else:
|
|
429
|
+
print(f"Would write to {outfilepath}")
|
|
430
|
+
# print(out)
|
|
431
|
+
|
|
432
|
+
def generate_summary_table(self, save=False):
|
|
433
|
+
"""
|
|
434
|
+
Generate a summary table for the JSON files in self.DATADIR
|
|
435
|
+
|
|
436
|
+
args:
|
|
437
|
+
save [bool]: if True, save the summary file to "summary.csv"
|
|
438
|
+
in self.DATADIR. Default is False.
|
|
439
|
+
"""
|
|
440
|
+
allfiles = glob.glob(os.path.join(self.DATADIR, "*.json"))
|
|
441
|
+
|
|
442
|
+
# read the data from all the json files and convert to Transients
|
|
443
|
+
rows = []
|
|
444
|
+
for jsonfile in allfiles:
|
|
445
|
+
with open(jsonfile, "r") as j:
|
|
446
|
+
t = Transient(json.load(j))
|
|
447
|
+
skycoord = t.get_skycoord()
|
|
448
|
+
|
|
449
|
+
row = {
|
|
450
|
+
"name": t.default_name,
|
|
451
|
+
"alias": [alias["value"] for alias in t["name"]["alias"]],
|
|
452
|
+
"ra": skycoord.ra,
|
|
453
|
+
"dec": skycoord.dec,
|
|
454
|
+
"refs": [ref["name"] for ref in t["reference_alias"]],
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if "date_reference" in t:
|
|
458
|
+
row["discovery_date"] = t.get_discovery_date()
|
|
459
|
+
|
|
460
|
+
if "distance" in t:
|
|
461
|
+
row["z"] = t.get_redshift()
|
|
462
|
+
|
|
463
|
+
row["hasPhot"] = "photometry" in t
|
|
464
|
+
row["hasSpec"] = "spectra" in t
|
|
465
|
+
|
|
466
|
+
row["json_path"] = os.path.abspath(jsonfile)
|
|
467
|
+
|
|
468
|
+
rows.append(row)
|
|
469
|
+
|
|
470
|
+
alljsons = pd.DataFrame(rows)
|
|
471
|
+
if save:
|
|
472
|
+
alljsons.to_csv(os.path.join(self.DATADIR, "summary.csv"))
|
|
473
|
+
|
|
474
|
+
return alljsons
|