arvi 0.1.10__tar.gz → 0.1.11__tar.gz

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.
Files changed (49) hide show
  1. {arvi-0.1.10 → arvi-0.1.11}/PKG-INFO +1 -1
  2. arvi-0.1.11/arvi/config.py +2 -0
  3. {arvi-0.1.10 → arvi-0.1.11}/arvi/dace_wrapper.py +60 -49
  4. {arvi-0.1.10 → arvi-0.1.11}/arvi/extra_data.py +11 -2
  5. {arvi-0.1.10 → arvi-0.1.11}/arvi/plots.py +107 -27
  6. {arvi-0.1.10 → arvi-0.1.11}/arvi/simbad_wrapper.py +15 -0
  7. {arvi-0.1.10 → arvi-0.1.11}/arvi/stats.py +16 -2
  8. {arvi-0.1.10 → arvi-0.1.11}/arvi/timeseries.py +74 -29
  9. {arvi-0.1.10 → arvi-0.1.11}/arvi/utils.py +14 -0
  10. {arvi-0.1.10 → arvi-0.1.11}/arvi.egg-info/PKG-INFO +1 -1
  11. {arvi-0.1.10 → arvi-0.1.11}/pyproject.toml +1 -1
  12. arvi-0.1.10/arvi/config.py +0 -1
  13. {arvi-0.1.10 → arvi-0.1.11}/.github/workflows/docs-gh-pages.yml +0 -0
  14. {arvi-0.1.10 → arvi-0.1.11}/.github/workflows/install.yml +0 -0
  15. {arvi-0.1.10 → arvi-0.1.11}/.github/workflows/python-publish.yml +0 -0
  16. {arvi-0.1.10 → arvi-0.1.11}/.gitignore +0 -0
  17. {arvi-0.1.10 → arvi-0.1.11}/LICENSE +0 -0
  18. {arvi-0.1.10 → arvi-0.1.11}/README.md +0 -0
  19. {arvi-0.1.10 → arvi-0.1.11}/arvi/HZ.py +0 -0
  20. {arvi-0.1.10 → arvi-0.1.11}/arvi/__init__.py +0 -0
  21. {arvi-0.1.10 → arvi-0.1.11}/arvi/binning.py +0 -0
  22. {arvi-0.1.10 → arvi-0.1.11}/arvi/data/extra/HD86226_PFS1.rdb +0 -0
  23. {arvi-0.1.10 → arvi-0.1.11}/arvi/data/extra/HD86226_PFS2.rdb +0 -0
  24. {arvi-0.1.10 → arvi-0.1.11}/arvi/data/extra/metadata.json +0 -0
  25. {arvi-0.1.10 → arvi-0.1.11}/arvi/data/info.svg +0 -0
  26. {arvi-0.1.10 → arvi-0.1.11}/arvi/data/obs_affected_ADC_issues.dat +0 -0
  27. {arvi-0.1.10 → arvi-0.1.11}/arvi/data/obs_affected_blue_cryostat_issues.dat +0 -0
  28. {arvi-0.1.10 → arvi-0.1.11}/arvi/instrument_specific.py +0 -0
  29. {arvi-0.1.10 → arvi-0.1.11}/arvi/lbl_wrapper.py +0 -0
  30. {arvi-0.1.10 → arvi-0.1.11}/arvi/nasaexo_wrapper.py +0 -0
  31. {arvi-0.1.10 → arvi-0.1.11}/arvi/programs.py +0 -0
  32. {arvi-0.1.10 → arvi-0.1.11}/arvi/reports.py +0 -0
  33. {arvi-0.1.10 → arvi-0.1.11}/arvi/setup_logger.py +0 -0
  34. {arvi-0.1.10 → arvi-0.1.11}/arvi/translations.py +0 -0
  35. {arvi-0.1.10 → arvi-0.1.11}/arvi.egg-info/SOURCES.txt +0 -0
  36. {arvi-0.1.10 → arvi-0.1.11}/arvi.egg-info/dependency_links.txt +0 -0
  37. {arvi-0.1.10 → arvi-0.1.11}/arvi.egg-info/requires.txt +0 -0
  38. {arvi-0.1.10 → arvi-0.1.11}/arvi.egg-info/top_level.txt +0 -0
  39. {arvi-0.1.10 → arvi-0.1.11}/docs/API.md +0 -0
  40. {arvi-0.1.10 → arvi-0.1.11}/docs/detailed.md +0 -0
  41. {arvi-0.1.10 → arvi-0.1.11}/docs/index.md +0 -0
  42. {arvi-0.1.10 → arvi-0.1.11}/docs/logo/detective.png +0 -0
  43. {arvi-0.1.10 → arvi-0.1.11}/docs/logo/logo.png +0 -0
  44. {arvi-0.1.10 → arvi-0.1.11}/mkdocs.yml +0 -0
  45. {arvi-0.1.10 → arvi-0.1.11}/setup.cfg +0 -0
  46. {arvi-0.1.10 → arvi-0.1.11}/setup.py +0 -0
  47. {arvi-0.1.10 → arvi-0.1.11}/tests/test_binning.py +0 -0
  48. {arvi-0.1.10 → arvi-0.1.11}/tests/test_import_object.py +0 -0
  49. {arvi-0.1.10 → arvi-0.1.11}/tests/test_stats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arvi
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: The Automated RV Inspector
5
5
  Author-email: João Faria <joao.faria@unige.ch>
6
6
  License: MIT
@@ -0,0 +1,2 @@
1
+ return_self = False
2
+ check_internet = False
@@ -5,7 +5,7 @@ import numpy as np
5
5
  from dace_query import DaceClass
6
6
  from dace_query.spectroscopy import SpectroscopyClass, Spectroscopy as default_Spectroscopy
7
7
  from .setup_logger import logger
8
- from .utils import create_directory, all_logging_disabled, stdout_disabled
8
+ from .utils import create_directory, all_logging_disabled, stdout_disabled, tqdm
9
9
 
10
10
 
11
11
  def load_spectroscopy() -> SpectroscopyClass:
@@ -156,6 +156,13 @@ def check_existing(output_directory, files, type):
156
156
  f.partition('.fits')[0] for f in os.listdir(output_directory)
157
157
  if type in f
158
158
  ]
159
+
160
+ # also check for lowercase type
161
+ existing += [
162
+ f.partition('.fits')[0] for f in os.listdir(output_directory)
163
+ if type.lower() in f
164
+ ]
165
+
159
166
  if os.name == 'nt': # on Windows, be careful with ':' in filename
160
167
  import re
161
168
  existing = [re.sub(r'T(\d+)_(\d+)_(\d+)', r'T\1:\2:\3', f) for f in existing]
@@ -175,7 +182,7 @@ def download(files, type, output_directory):
175
182
  """ Download files from DACE """
176
183
  Spectroscopy = load_spectroscopy()
177
184
  # with stdout_disabled(), all_logging_disabled():
178
- Spectroscopy.download_files(files, file_type=type,
185
+ Spectroscopy.download_files(files, file_type=type.lower(),
179
186
  output_directory=output_directory)
180
187
 
181
188
  def extract_fits(output_directory):
@@ -194,15 +201,16 @@ def extract_fits(output_directory):
194
201
  return files
195
202
 
196
203
 
197
- def do_download_ccf(raw_files, output_directory, clobber=False, verbose=True):
198
- """ Download CCFs from DACE """
204
+ def do_download_filetype(type, raw_files, output_directory, clobber=False,
205
+ verbose=True, chunk_size=20):
206
+ """ Download CCFs / S1Ds / S2Ds from DACE """
199
207
  raw_files = np.atleast_1d(raw_files)
200
208
 
201
209
  create_directory(output_directory)
202
210
 
203
211
  # check existing files to avoid re-downloading
204
212
  if not clobber:
205
- raw_files = check_existing(output_directory, raw_files, 'CCF')
213
+ raw_files = check_existing(output_directory, raw_files, type)
206
214
 
207
215
  # any file left to download?
208
216
  if raw_files.size == 0:
@@ -212,68 +220,71 @@ def do_download_ccf(raw_files, output_directory, clobber=False, verbose=True):
212
220
 
213
221
  if verbose:
214
222
  n = raw_files.size
215
- logger.info(f"Downloading {n} CCFs into '{output_directory}'...")
223
+ logger.info(f"Downloading {n} {type}s into '{output_directory}'...")
216
224
 
217
- download(raw_files, 'ccf', output_directory)
225
+ # avoid an empty chunk
226
+ if chunk_size > n:
227
+ chunk_size = n
218
228
 
219
- if verbose:
220
- logger.info('Extracting .fits files')
229
+ for files in tqdm(zip(*(iter(raw_files),) * chunk_size), total=n // chunk_size):
230
+ download(files, type, output_directory)
231
+ extract_fits(output_directory)
221
232
 
222
- extract_fits(output_directory)
233
+ logger.info('Extracted .fits files')
223
234
 
224
235
 
225
- def do_download_s1d(raw_files, output_directory, clobber=False, verbose=True):
226
- """ Download S1Ds from DACE """
227
- raw_files = np.atleast_1d(raw_files)
236
+ # def do_download_s1d(raw_files, output_directory, clobber=False, verbose=True):
237
+ # """ Download S1Ds from DACE """
238
+ # raw_files = np.atleast_1d(raw_files)
228
239
 
229
- create_directory(output_directory)
240
+ # create_directory(output_directory)
230
241
 
231
- # check existing files to avoid re-downloading
232
- if not clobber:
233
- raw_files = check_existing(output_directory, raw_files, 'S1D')
242
+ # # check existing files to avoid re-downloading
243
+ # if not clobber:
244
+ # raw_files = check_existing(output_directory, raw_files, 'S1D')
234
245
 
235
- # any file left to download?
236
- if raw_files.size == 0:
237
- if verbose:
238
- logger.info('no files to download')
239
- return
246
+ # # any file left to download?
247
+ # if raw_files.size == 0:
248
+ # if verbose:
249
+ # logger.info('no files to download')
250
+ # return
240
251
 
241
- if verbose:
242
- n = raw_files.size
243
- logger.info(f"Downloading {n} S1Ds into '{output_directory}'...")
252
+ # if verbose:
253
+ # n = raw_files.size
254
+ # logger.info(f"Downloading {n} S1Ds into '{output_directory}'...")
244
255
 
245
- download(raw_files, 's1d', output_directory)
256
+ # download(raw_files, 's1d', output_directory)
246
257
 
247
- if verbose:
248
- logger.info('Extracting .fits files')
258
+ # if verbose:
259
+ # logger.info('Extracting .fits files')
249
260
 
250
- extract_fits(output_directory)
261
+ # extract_fits(output_directory)
251
262
 
252
263
 
253
- def do_download_s2d(raw_files, output_directory, clobber=False, verbose=True):
254
- """ Download S2Ds from DACE """
255
- raw_files = np.atleast_1d(raw_files)
264
+ # def do_download_s2d(raw_files, output_directory, clobber=False, verbose=True):
265
+ # """ Download S2Ds from DACE """
266
+ # raw_files = np.atleast_1d(raw_files)
256
267
 
257
- create_directory(output_directory)
268
+ # create_directory(output_directory)
258
269
 
259
- # check existing files to avoid re-downloading
260
- if not clobber:
261
- raw_files = check_existing(output_directory, raw_files, 'S2D')
270
+ # # check existing files to avoid re-downloading
271
+ # if not clobber:
272
+ # raw_files = check_existing(output_directory, raw_files, 'S2D')
262
273
 
263
- # any file left to download?
264
- if raw_files.size == 0:
265
- if verbose:
266
- logger.info('no files to download')
267
- return
274
+ # # any file left to download?
275
+ # if raw_files.size == 0:
276
+ # if verbose:
277
+ # logger.info('no files to download')
278
+ # return
268
279
 
269
- if verbose:
270
- n = raw_files.size
271
- logger.info(f"Downloading {n} S2Ds into '{output_directory}'...")
280
+ # if verbose:
281
+ # n = raw_files.size
282
+ # logger.info(f"Downloading {n} S2Ds into '{output_directory}'...")
272
283
 
273
- download(raw_files, 's2d', output_directory)
284
+ # download(raw_files, 's2d', output_directory)
274
285
 
275
- if verbose:
276
- logger.info('Extracting .fits files')
286
+ # if verbose:
287
+ # logger.info('Extracting .fits files')
277
288
 
278
- extracted_files = extract_fits(output_directory)
279
- return extracted_files
289
+ # extracted_files = extract_fits(output_directory)
290
+ # return extracted_files
@@ -19,15 +19,24 @@ def get_extra_data(star, instrument=None, path=None, verbose=True):
19
19
  # print(metadata)
20
20
 
21
21
  files = glob(os.path.join(path, star + '*'))
22
+ files = [f for f in files if os.path.isfile(f)]
23
+ files = [f for f in files if not os.path.basename(f).endswith('.zip')]
24
+
22
25
  if len(files) == 0:
23
26
  raise FileNotFoundError
24
27
 
25
- instruments = [os.path.basename(f).split('.')[0] for f in files]
26
- instruments = [i.split('_', maxsplit=1)[1] for i in instruments]
28
+ def get_instruments(files):
29
+ instruments = [os.path.basename(f).split('.')[0] for f in files]
30
+ instruments = [i.split('_', maxsplit=1)[1] for i in instruments]
31
+ return instruments
32
+
33
+ instruments = get_instruments(files)
27
34
 
28
35
  if instrument is not None:
29
36
  if not any([instrument in i for i in instruments]):
30
37
  raise FileNotFoundError
38
+ files = [f for f in files if instrument in f]
39
+ instruments = get_instruments(files)
31
40
 
32
41
  if verbose:
33
42
  logger.info(f'loading extra data for {star}')
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  from functools import partial, partialmethod
3
+ from itertools import cycle
3
4
 
5
+ import matplotlib.collections
4
6
  import numpy as np
5
7
  import matplotlib
6
8
  import matplotlib.pyplot as plt
@@ -15,7 +17,7 @@ from . import config
15
17
 
16
18
  def plot(self, ax=None, show_masked=False, instrument=None, time_offset=0,
17
19
  remove_50000=False, tooltips=False, label=None, N_in_label=False,
18
- versus_n=False, show_histogram=False, **kwargs):
20
+ versus_n=False, show_histogram=False, bw=False, **kwargs):
19
21
  """ Plot the RVs
20
22
 
21
23
  Args:
@@ -38,6 +40,8 @@ def plot(self, ax=None, show_masked=False, instrument=None, time_offset=0,
38
40
  show_histogram (bool, optional)
39
41
  Whether to show a panel with the RV histograms (per intrument).
40
42
  Defaults to False.
43
+ bw (bool, optional):
44
+ Adapt plot to black and white. Defaults to False.
41
45
 
42
46
  Returns:
43
47
  Figure: the figure
@@ -58,7 +62,7 @@ def plot(self, ax=None, show_masked=False, instrument=None, time_offset=0,
58
62
  ax, axh = ax
59
63
  fig = ax.figure
60
64
 
61
- kwargs.setdefault('marker', 'o')
65
+
62
66
  kwargs.setdefault('ls', '')
63
67
  kwargs.setdefault('capsize', 0)
64
68
  kwargs.setdefault('ms', 4)
@@ -66,10 +70,22 @@ def plot(self, ax=None, show_masked=False, instrument=None, time_offset=0,
66
70
  if remove_50000:
67
71
  time_offset = 50000
68
72
 
69
- instruments = self._check_instrument(instrument)
73
+ strict = kwargs.pop('strict', False)
74
+ instruments = self._check_instrument(instrument, strict=strict)
75
+
76
+ if bw:
77
+ markers = cycle(('o', 'P', 's', '^', '*'))
78
+ else:
79
+ markers = cycle(('o',) * len(instruments))
80
+
81
+ try:
82
+ zorders = cycle(-np.argsort([getattr(self, i).error for i in instruments])[::-1])
83
+ except AttributeError:
84
+ zorders = cycle([1] * len(instruments))
70
85
 
71
86
  cursors = {}
72
- for inst in instruments:
87
+ containers = {}
88
+ for _i, inst in enumerate(instruments):
73
89
  s = self if self._child else getattr(self, inst)
74
90
  if s.mask.sum() == 0:
75
91
  continue
@@ -81,37 +97,42 @@ def plot(self, ax=None, show_masked=False, instrument=None, time_offset=0,
81
97
  p = p.replace('_', '.')
82
98
  _label = f'{i}-{p}'
83
99
  else:
84
- _label = label
100
+ if isinstance(label, list):
101
+ _label = label[_i]
102
+ else:
103
+ _label = label
85
104
 
86
105
  if versus_n:
87
- container = ax.errorbar(np.arange(1, s.mtime.size + 1),
88
- s.mvrad, s.msvrad, label=_label, picker=True, **kwargs)
106
+ container = ax.errorbar(np.arange(1, s.mtime.size + 1), s.mvrad, s.msvrad,
107
+ label=_label, picker=True, marker=next(markers), zorder=next(zorders),
108
+ **kwargs)
89
109
  else:
90
- container = ax.errorbar(s.mtime - time_offset,
91
- s.mvrad, s.msvrad, label=_label, picker=True, **kwargs)
110
+ container = ax.errorbar(s.mtime - time_offset, s.mvrad, s.msvrad,
111
+ label=_label, picker=True, marker=next(markers), zorder=next(zorders),
112
+ **kwargs)
113
+
114
+ containers[inst] = list(container)
92
115
 
93
116
  if show_histogram:
94
117
  kw = dict(histtype='step', bins='doane', orientation='horizontal')
95
118
  hlabel = f'{s.mvrad.std():.2f} {self.units}'
96
119
  axh.hist(s.mvrad, **kw, label=hlabel)
97
120
 
98
- if tooltips:
99
- cursors[inst] = crsr = mplcursors.cursor(container, multiple=False)
100
-
101
- @crsr.connect("add")
102
- def _(sel):
103
- inst = sel.artist.get_label()
104
- _s = getattr(self, inst)
105
- vrad, svrad = _s.vrad[sel.index], _s.svrad[sel.index]
106
- sel.annotation.get_bbox_patch().set(fc="white")
107
- text = f'{inst}\n'
108
- text += f'BJD: {sel.target[0]:9.5f}\n'
109
- text += f'RV: {vrad:.3f} ± {svrad:.3f}'
110
- # if fig.canvas.manager.toolmanager.get_tool('infotool').toggled:
111
- # text += '\n\n'
112
- # text += f'date: {_s.date_night[sel.index]}\n'
113
- # text += f'mask: {_s.ccf_mask[sel.index]}'
114
- sel.annotation.set_text(text)
121
+ # cursors[inst] = crsr = mplcursors.cursor(container, multiple=False)
122
+ # @crsr.connect("add")
123
+ # def _(sel):
124
+ # inst = sel.artist.get_label()
125
+ # _s = getattr(self, inst)
126
+ # vrad, svrad = _s.vrad[sel.index], _s.svrad[sel.index]
127
+ # sel.annotation.get_bbox_patch().set(fc="white")
128
+ # text = f'{inst}\n'
129
+ # text += f'BJD: {sel.target[0]:9.5f}\n'
130
+ # text += f'RV: {vrad:.3f} ± {svrad:.3f}'
131
+ # # if fig.canvas.manager.toolmanager.get_tool('infotool').toggled:
132
+ # # text += '\n\n'
133
+ # # text += f'date: {_s.date_night[sel.index]}\n'
134
+ # # text += f'mask: {_s.ccf_mask[sel.index]}'
135
+ # sel.annotation.set_text(text)
115
136
 
116
137
  if show_masked:
117
138
  if versus_n:
@@ -147,9 +168,43 @@ def plot(self, ax=None, show_masked=False, instrument=None, time_offset=0,
147
168
  fig.canvas.draw()
148
169
  except ValueError:
149
170
  pass
150
-
151
171
  plt.connect('pick_event', on_pick_legend)
152
172
 
173
+ if tooltips:
174
+ annotations = []
175
+ def on_pick_point(event):
176
+ print('annotations:', annotations)
177
+ for text in annotations:
178
+ text.remove()
179
+ annotations.remove(text)
180
+
181
+ artist = event.artist
182
+ if isinstance(artist, (matplotlib.lines.Line2D, matplotlib.collections.LineCollection)):
183
+ print(event.ind, artist)
184
+ if isinstance(artist, matplotlib.lines.Line2D):
185
+ matching_instrument = [k for k, v in containers.items() if artist in v]
186
+ print(matching_instrument)
187
+ if len(matching_instrument) == 0:
188
+ return
189
+ inst = matching_instrument[0]
190
+ _s = getattr(self, inst)
191
+ ind = event.ind[0]
192
+ # print(_s.mtime[ind], _s.mvrad[ind], _s.msvrad[ind])
193
+
194
+ text = f'{inst}\n'
195
+ text += f'{_s.mtime[ind]:9.5f}\n'
196
+ text += f'RV: {_s.mvrad[ind]:.1f} ± {_s.msvrad[ind]:.1f}'
197
+
198
+ annotations.append(
199
+ ax.annotate(text, (_s.mtime[ind], _s.mvrad[ind]), xycoords='data',
200
+ xytext=(5, 10), textcoords='offset points', fontsize=9,
201
+ bbox={'boxstyle': 'round', 'fc': 'w'}, arrowprops=dict(arrowstyle="-"))
202
+ )
203
+ # ax.annotate(f'{inst}', (0.5, 0.5), xycoords=artist, ha='center', va='center')
204
+ fig.canvas.draw()
205
+ # print(event.ind, artist.get_label())
206
+ plt.connect('pick_event', on_pick_point)
207
+
153
208
 
154
209
  if show_histogram:
155
210
  axh.legend()
@@ -340,6 +395,31 @@ def gls(self, ax=None, label=None, fap=True, picker=True, instrument=None, **kwa
340
395
  if label is not None:
341
396
  ax.legend()
342
397
 
398
+ if ax.get_legend() is not None:
399
+ leg = ax.get_legend()
400
+ for text in leg.get_texts():
401
+ text.set_picker(True)
402
+
403
+ def on_pick_legend(event):
404
+ handles, labels = ax.get_legend_handles_labels()
405
+ artist = event.artist
406
+ if isinstance(artist, matplotlib.text.Text):
407
+ # print('handles:', handles)
408
+ # print('labels:', labels)
409
+ # print(artist.get_text())
410
+ try:
411
+ h = handles[labels.index(artist.get_text())]
412
+ alpha_text = {None:0.2, 1.0: 0.2, 0.2:1.0}[artist.get_alpha()]
413
+ alpha_point = {None: 0.0, 1.0: 0.0, 0.2: 1.0}[artist.get_alpha()]
414
+ h.set_alpha(alpha_point)
415
+ artist.set_alpha(alpha_text)
416
+ fig.canvas.draw()
417
+ except ValueError:
418
+ pass
419
+
420
+ if 'pick_event' not in fig.canvas.callbacks.callbacks:
421
+ plt.connect('pick_event', on_pick_legend)
422
+
343
423
 
344
424
  if config.return_self:
345
425
  return self
@@ -1,9 +1,12 @@
1
+ import os
1
2
  from dataclasses import dataclass, field
2
3
  import requests
3
4
 
4
5
  from astropy.coordinates import SkyCoord
5
6
  import pysweetcat
6
7
 
8
+ DATA_PATH = os.path.dirname(__file__)
9
+ DATA_PATH = os.path.join(DATA_PATH, 'data')
7
10
 
8
11
  QUERY = """
9
12
  SELECT basic.OID,
@@ -45,6 +48,8 @@ def run_query(query):
45
48
  response = requests.post(url, data=data, timeout=10)
46
49
  except requests.ReadTimeout as err:
47
50
  raise IndexError(err)
51
+ except requests.ConnectionError as err:
52
+ raise IndexError(err)
48
53
  return response.content.decode()
49
54
 
50
55
  def parse_table(table, cols=None, values=None):
@@ -94,6 +99,16 @@ class simbad:
94
99
  """
95
100
  self.star = star
96
101
 
102
+ if 'kobe' in self.star.lower():
103
+ fname = os.path.join(DATA_PATH, 'KOBE-translate.csv')
104
+ kobe_translate = {}
105
+ if os.path.exists(fname):
106
+ with open(fname) as f:
107
+ for line in f.readlines():
108
+ kobe_id, catname = line.strip().split(',')
109
+ kobe_translate[kobe_id] = catname
110
+ self.star = star = kobe_translate[self.star]
111
+
97
112
  # oid = run_query(query=OID_QUERY.format(star=star))
98
113
  # self.oid = str(oid.split()[-1])
99
114
 
@@ -19,15 +19,19 @@ def wmean(a, e):
19
19
  raise ValueError
20
20
  return np.average(a, weights=1 / e**2)
21
21
 
22
- def rms(a):
22
+ def rms(a, ignore_nans=False):
23
23
  """ Root mean square of array `a`
24
24
 
25
25
  Args:
26
26
  a (array): Array containing data
27
27
  """
28
+ if ignore_nans:
29
+ a = a[~np.isnan(a)]
30
+ if len(a) == 0:
31
+ return np.nan
28
32
  return np.sqrt((a**2).mean())
29
33
 
30
- def wrms(a, e):
34
+ def wrms(a, e, ignore_nans=False):
31
35
  """ Weighted root mean square of array `a`, with uncertanty given by `e`.
32
36
  The weighted rms is calculated using the weighted mean, where the weights
33
37
  are equal to 1/e**2.
@@ -36,6 +40,16 @@ def wrms(a, e):
36
40
  a (array): Array containing data
37
41
  e (array): Uncertainties on `a`
38
42
  """
43
+ if ignore_nans:
44
+ nans = np.logical_or(np.isnan(a), np.isnan(e))
45
+ a = a[~nans]
46
+ e = e[~nans]
47
+ if (e == 0).any():
48
+ raise ZeroDivisionError('uncertainty cannot be zero')
49
+ if (e < 0).any():
50
+ raise ValueError('uncertainty cannot be negative')
51
+ if (a.shape != e.shape):
52
+ raise ValueError('arrays must have the same shape')
39
53
  w = 1 / e**2
40
54
  return np.sqrt(np.sum(w * (a - np.average(a, weights=w))**2) / sum(w))
41
55
 
@@ -11,16 +11,15 @@ import numpy as np
11
11
  from astropy import units
12
12
 
13
13
  from .setup_logger import logger
14
- from .config import return_self
14
+ from .config import return_self, check_internet
15
15
  from .translations import translate
16
- from .dace_wrapper import get_observations, get_arrays
17
- from .dace_wrapper import do_download_ccf, do_download_s1d, do_download_s2d
16
+ from .dace_wrapper import do_download_filetype, get_observations, get_arrays
18
17
  from .simbad_wrapper import simbad
19
18
  from .extra_data import get_extra_data
20
19
  from .stats import wmean, wrms
21
20
  from .binning import bin_ccf_mask, binRV
22
21
  from .HZ import getHZ_period
23
- from .utils import strtobool
22
+ from .utils import strtobool, there_is_internet
24
23
 
25
24
 
26
25
  @dataclass
@@ -71,6 +70,8 @@ class RV:
71
70
  self.__star__ = translate(self.star)
72
71
 
73
72
  if not self._child:
73
+ if check_internet and not there_is_internet():
74
+ raise ConnectionError('There is no internet connection?')
74
75
  # complicated way to query Simbad with self.__star__ or, if that
75
76
  # fails, try after removing a trailing 'A'
76
77
  for target in (self.__star__, self.__star__.replace('A', '')):
@@ -226,6 +227,8 @@ class RV:
226
227
  @property
227
228
  def N_nights(self) -> int:
228
229
  """ Number of individual nights """
230
+ if self.mtime.size == 0:
231
+ return 0
229
232
  return binRV(self.mtime, None, None, binning_bins=True).size - 1
230
233
 
231
234
  @property
@@ -402,7 +405,7 @@ class RV:
402
405
  files = [files]
403
406
 
404
407
  if star is None:
405
- star_ = np.unique([os.path.splitext(f)[0].split('_')[0] for f in files])
408
+ star_ = np.unique([os.path.splitext(os.path.basename(f))[0].split('_')[0] for f in files])
406
409
  if star_.size == 1:
407
410
  logger.info(f'assuming star is {star_[0]}')
408
411
  star = star_[0]
@@ -439,12 +442,12 @@ class RV:
439
442
  names = header.split()
440
443
 
441
444
  if len(names) > 3:
442
- kw = dict(skip_header=2, dtype=None, encoding=None)
443
- try:
444
- data = np.genfromtxt(f, **kw)
445
- except ValueError:
445
+ kw = dict(skip_header=0, comments='--', names=True, dtype=None, encoding=None)
446
+ if '\t' in header:
446
447
  data = np.genfromtxt(f, **kw, delimiter='\t')
447
- data.dtype.names = names
448
+ else:
449
+ data = np.genfromtxt(f, **kw)
450
+ # data.dtype.names = names
448
451
  else:
449
452
  data = np.array([], dtype=np.dtype([]))
450
453
 
@@ -463,8 +466,10 @@ class RV:
463
466
 
464
467
  if 'rhk' in data.dtype.fields:
465
468
  _s.rhk = data['rhk']
466
- if 'srhk' in data.dtype.fields:
467
- _s.rhk_err = data['srhk']
469
+ _s.rhk_err = np.full_like(time, np.nan)
470
+ for possible_name in ['srhk', 'rhk_err']:
471
+ if possible_name in data.dtype.fields:
472
+ _s.rhk_err = data[possible_name]
468
473
  else:
469
474
  _s.rhk = np.zeros_like(time)
470
475
  _s.rhk_err = np.full_like(time, np.nan)
@@ -624,11 +629,12 @@ class RV:
624
629
  setattr(self, q, arr)
625
630
 
626
631
 
627
- def download_ccf(self, instrument=None, limit=None, directory=None):
632
+ def download_ccf(self, instrument=None, index=None, limit=None, directory=None, **kwargs):
628
633
  """ Download CCFs from DACE
629
634
 
630
635
  Args:
631
636
  instrument (str): Specific instrument for which to download data
637
+ index (int): Specific index of point for which to download data (0-based)
632
638
  limit (int): Maximum number of files to download.
633
639
  directory (str): Directory where to store data.
634
640
  """
@@ -638,18 +644,27 @@ class RV:
638
644
  if instrument is None:
639
645
  files = [file for file in self.raw_file if file.endswith('.fits')]
640
646
  else:
641
- instrument = self._check_instrument(instrument)
647
+ strict = kwargs.pop('strict', False)
648
+ instrument = self._check_instrument(instrument, strict=strict)
642
649
  files = []
643
650
  for inst in instrument:
644
651
  files += list(getattr(self, inst).raw_file)
645
652
 
646
- do_download_ccf(files[:limit], directory)
653
+ if index is not None:
654
+ index = np.atleast_1d(index)
655
+ files = list(np.array(files)[index])
656
+
657
+ # remove empty strings
658
+ files = list(filter(None, files))
659
+
660
+ do_download_filetype('CCF', files[:limit], directory, **kwargs)
647
661
 
648
- def download_s1d(self, instrument=None, limit=None, directory=None):
662
+ def download_s1d(self, instrument=None, index=None, limit=None, directory=None, **kwargs):
649
663
  """ Download S1Ds from DACE
650
664
 
651
665
  Args:
652
666
  instrument (str): Specific instrument for which to download data
667
+ index (int): Specific index of point for which to download data (0-based)
653
668
  limit (int): Maximum number of files to download.
654
669
  directory (str): Directory where to store data.
655
670
  """
@@ -659,18 +674,27 @@ class RV:
659
674
  if instrument is None:
660
675
  files = [file for file in self.raw_file if file.endswith('.fits')]
661
676
  else:
662
- instrument = self._check_instrument(instrument)
677
+ strict = kwargs.pop('strict', False)
678
+ instrument = self._check_instrument(instrument, strict=strict)
663
679
  files = []
664
680
  for inst in instrument:
665
681
  files += list(getattr(self, inst).raw_file)
666
682
 
667
- do_download_s1d(files[:limit], directory)
683
+ if index is not None:
684
+ index = np.atleast_1d(index)
685
+ files = list(np.array(files)[index])
668
686
 
669
- def download_s2d(self, instrument=None, limit=None, directory=None):
687
+ # remove empty strings
688
+ files = list(filter(None, files))
689
+
690
+ do_download_filetype('S1D', files[:limit], directory, **kwargs)
691
+
692
+ def download_s2d(self, instrument=None, index=None, limit=None, directory=None, **kwargs):
670
693
  """ Download S2Ds from DACE
671
694
 
672
695
  Args:
673
696
  instrument (str): Specific instrument for which to download data
697
+ index (int): Specific index of point for which to download data (0-based)
674
698
  limit (int): Maximum number of files to download.
675
699
  directory (str): Directory where to store data.
676
700
  """
@@ -680,12 +704,20 @@ class RV:
680
704
  if instrument is None:
681
705
  files = [file for file in self.raw_file if file.endswith('.fits')]
682
706
  else:
683
- instrument = self._check_instrument(instrument)
707
+ strict = kwargs.pop('strict', False)
708
+ instrument = self._check_instrument(instrument, strict=strict)
684
709
  files = []
685
710
  for inst in instrument:
686
711
  files += list(getattr(self, inst).raw_file)
687
712
 
688
- extracted_files = do_download_s2d(files[:limit], directory)
713
+ if index is not None:
714
+ index = np.atleast_1d(index)
715
+ files = list(np.array(files)[index])
716
+
717
+ # remove empty strings
718
+ files = list(filter(None, files))
719
+
720
+ do_download_filetype('S2D', files[:limit], directory, **kwargs)
689
721
 
690
722
 
691
723
  from .plots import plot, plot_fwhm, plot_bis, plot_rhk, plot_quantity
@@ -757,7 +789,9 @@ class RV:
757
789
  return self
758
790
 
759
791
  def remove_point(self, index):
760
- """ Remove individual observations at a given index (or indices)
792
+ """
793
+ Remove individual observations at a given index (or indices).
794
+ NOTE: Like Python, the index is 0-based.
761
795
 
762
796
  Args:
763
797
  index (int, list, ndarray):
@@ -1203,7 +1237,8 @@ class RV:
1203
1237
  self._build_arrays()
1204
1238
 
1205
1239
 
1206
- def save(self, directory=None, instrument=None, full=False, save_nans=True):
1240
+ def save(self, directory=None, instrument=None, full=False,
1241
+ save_masked=False, save_nans=True):
1207
1242
  """ Save the observations in .rdb files.
1208
1243
 
1209
1244
  Args:
@@ -1238,11 +1273,18 @@ class RV:
1238
1273
  continue
1239
1274
 
1240
1275
  if full:
1241
- d = np.c_[
1242
- _s.mtime, _s.mvrad, _s.msvrad,
1243
- _s.fwhm[_s.mask], _s.fwhm_err[_s.mask],
1244
- _s.rhk[_s.mask], _s.rhk_err[_s.mask],
1245
- ]
1276
+ if save_masked:
1277
+ d = np.c_[
1278
+ _s.time, _s.vrad, _s.svrad,
1279
+ _s.fwhm, _s.fwhm_err,
1280
+ _s.rhk, _s.rhk_err,
1281
+ ]
1282
+ else:
1283
+ d = np.c_[
1284
+ _s.mtime, _s.mvrad, _s.msvrad,
1285
+ _s.fwhm[_s.mask], _s.fwhm_err[_s.mask],
1286
+ _s.rhk[_s.mask], _s.rhk_err[_s.mask],
1287
+ ]
1246
1288
  if not save_nans:
1247
1289
  if np.isnan(d).any():
1248
1290
  # remove observations where any of the indicators are # NaN
@@ -1254,7 +1296,10 @@ class RV:
1254
1296
  header = 'bjd\tvrad\tsvrad\tfwhm\tsfwhm\trhk\tsrhk\n'
1255
1297
  header += '---\t----\t-----\t----\t-----\t---\t----'
1256
1298
  else:
1257
- d = np.c_[_s.mtime, _s.mvrad, _s.msvrad]
1299
+ if save_masked:
1300
+ d = np.c_[_s.time, _s.vrad, _s.svrad]
1301
+ else:
1302
+ d = np.c_[_s.mtime, _s.mvrad, _s.msvrad]
1258
1303
  header = 'bjd\tvrad\tsvrad\n---\t----\t-----'
1259
1304
 
1260
1305
  file = f'{star_name}_{inst}.rdb'
@@ -13,6 +13,12 @@ import logging
13
13
  from glob import glob
14
14
  import numpy as np
15
15
 
16
+ try:
17
+ from tqdm import tqdm, trange
18
+ except ImportError:
19
+ tqdm = lambda x, *args, **kwargs: x
20
+ trange = lambda *args, **kwargs: range(*args, **kwargs)
21
+
16
22
 
17
23
  def create_directory(directory):
18
24
  """ Create a directory if it does not exist """
@@ -70,6 +76,14 @@ def strtobool(val):
70
76
  else:
71
77
  raise ValueError("invalid truth value {!r}".format(val))
72
78
 
79
+ def there_is_internet(timeout=1):
80
+ from socket import create_connection
81
+ try:
82
+ create_connection(('8.8.8.8', 53), timeout=timeout)
83
+ return True
84
+ except OSError:
85
+ pass
86
+ return False
73
87
 
74
88
  def find_data_file(file):
75
89
  here = os.path.dirname(os.path.abspath(__file__))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arvi
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: The Automated RV Inspector
5
5
  Author-email: João Faria <joao.faria@unige.ch>
6
6
  License: MIT
@@ -8,7 +8,7 @@ authors = [
8
8
  {name = "João Faria", email = "joao.faria@unige.ch"},
9
9
  ]
10
10
  description = "The Automated RV Inspector"
11
- version = "0.1.10"
11
+ version = "0.1.11"
12
12
  readme = {file = "README.md", content-type = "text/markdown"}
13
13
  requires-python = ">=3.8"
14
14
  keywords = ["RV", "exoplanets"]
@@ -1 +0,0 @@
1
- return_self = False
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes