arvi 0.2.5__tar.gz → 0.2.7__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.

Potentially problematic release.


This version of arvi might be problematic. Click here for more details.

Files changed (64) hide show
  1. {arvi-0.2.5/arvi.egg-info → arvi-0.2.7}/PKG-INFO +1 -1
  2. {arvi-0.2.5 → arvi-0.2.7}/arvi/dace_wrapper.py +18 -9
  3. {arvi-0.2.5 → arvi-0.2.7}/arvi/gaia_wrapper.py +2 -2
  4. {arvi-0.2.5 → arvi-0.2.7}/arvi/instrument_specific.py +1 -8
  5. {arvi-0.2.5 → arvi-0.2.7}/arvi/simbad_wrapper.py +18 -0
  6. arvi-0.2.7/arvi/sophie_wrapper.py +111 -0
  7. {arvi-0.2.5 → arvi-0.2.7}/arvi/timeseries.py +223 -83
  8. {arvi-0.2.5 → arvi-0.2.7}/arvi/utils.py +5 -2
  9. {arvi-0.2.5 → arvi-0.2.7/arvi.egg-info}/PKG-INFO +1 -1
  10. {arvi-0.2.5 → arvi-0.2.7}/arvi.egg-info/SOURCES.txt +1 -0
  11. {arvi-0.2.5 → arvi-0.2.7}/.github/dependabot.yml +0 -0
  12. {arvi-0.2.5 → arvi-0.2.7}/.github/workflows/docs-gh-pages.yml +0 -0
  13. {arvi-0.2.5 → arvi-0.2.7}/.github/workflows/install.yml +0 -0
  14. {arvi-0.2.5 → arvi-0.2.7}/.github/workflows/python-publish.yml +0 -0
  15. {arvi-0.2.5 → arvi-0.2.7}/.gitignore +0 -0
  16. {arvi-0.2.5 → arvi-0.2.7}/LICENSE +0 -0
  17. {arvi-0.2.5 → arvi-0.2.7}/README.md +0 -0
  18. {arvi-0.2.5 → arvi-0.2.7}/arvi/HZ.py +0 -0
  19. {arvi-0.2.5 → arvi-0.2.7}/arvi/__init__.py +0 -0
  20. {arvi-0.2.5 → arvi-0.2.7}/arvi/ariadne_wrapper.py +0 -0
  21. {arvi-0.2.5 → arvi-0.2.7}/arvi/berv.py +0 -0
  22. {arvi-0.2.5 → arvi-0.2.7}/arvi/binning.py +0 -0
  23. {arvi-0.2.5 → arvi-0.2.7}/arvi/config.py +0 -0
  24. {arvi-0.2.5 → arvi-0.2.7}/arvi/data/extra/HD86226_PFS1.rdb +0 -0
  25. {arvi-0.2.5 → arvi-0.2.7}/arvi/data/extra/HD86226_PFS2.rdb +0 -0
  26. {arvi-0.2.5 → arvi-0.2.7}/arvi/data/extra/metadata.json +0 -0
  27. {arvi-0.2.5 → arvi-0.2.7}/arvi/data/info.svg +0 -0
  28. {arvi-0.2.5 → arvi-0.2.7}/arvi/data/obs_affected_ADC_issues.dat +0 -0
  29. {arvi-0.2.5 → arvi-0.2.7}/arvi/data/obs_affected_blue_cryostat_issues.dat +0 -0
  30. {arvi-0.2.5 → arvi-0.2.7}/arvi/exofop_wrapper.py +0 -0
  31. {arvi-0.2.5 → arvi-0.2.7}/arvi/extra_data.py +0 -0
  32. {arvi-0.2.5 → arvi-0.2.7}/arvi/headers.py +0 -0
  33. {arvi-0.2.5 → arvi-0.2.7}/arvi/kima_wrapper.py +0 -0
  34. {arvi-0.2.5 → arvi-0.2.7}/arvi/lbl_wrapper.py +0 -0
  35. {arvi-0.2.5 → arvi-0.2.7}/arvi/nasaexo_wrapper.py +0 -0
  36. {arvi-0.2.5 → arvi-0.2.7}/arvi/plots.py +0 -0
  37. {arvi-0.2.5 → arvi-0.2.7}/arvi/programs.py +0 -0
  38. {arvi-0.2.5 → arvi-0.2.7}/arvi/reports.py +0 -0
  39. {arvi-0.2.5 → arvi-0.2.7}/arvi/setup_logger.py +0 -0
  40. {arvi-0.2.5 → arvi-0.2.7}/arvi/spectra.py +0 -0
  41. {arvi-0.2.5 → arvi-0.2.7}/arvi/stats.py +0 -0
  42. {arvi-0.2.5 → arvi-0.2.7}/arvi/stellar.py +0 -0
  43. {arvi-0.2.5 → arvi-0.2.7}/arvi/translations.py +0 -0
  44. {arvi-0.2.5 → arvi-0.2.7}/arvi.egg-info/dependency_links.txt +0 -0
  45. {arvi-0.2.5 → arvi-0.2.7}/arvi.egg-info/requires.txt +0 -0
  46. {arvi-0.2.5 → arvi-0.2.7}/arvi.egg-info/top_level.txt +0 -0
  47. {arvi-0.2.5 → arvi-0.2.7}/docs/API.md +0 -0
  48. {arvi-0.2.5 → arvi-0.2.7}/docs/detailed.ipynb +0 -0
  49. {arvi-0.2.5 → arvi-0.2.7}/docs/downloading_data.md +0 -0
  50. {arvi-0.2.5 → arvi-0.2.7}/docs/index.md +0 -0
  51. {arvi-0.2.5 → arvi-0.2.7}/docs/logo/detective.png +0 -0
  52. {arvi-0.2.5 → arvi-0.2.7}/docs/logo/logo.png +0 -0
  53. {arvi-0.2.5 → arvi-0.2.7}/docs/stylesheets/extra.css +0 -0
  54. {arvi-0.2.5 → arvi-0.2.7}/mkdocs.yml +0 -0
  55. {arvi-0.2.5 → arvi-0.2.7}/pyproject.toml +0 -0
  56. {arvi-0.2.5 → arvi-0.2.7}/setup.cfg +0 -0
  57. {arvi-0.2.5 → arvi-0.2.7}/setup.py +0 -0
  58. {arvi-0.2.5 → arvi-0.2.7}/tests/HD10700-Bcor_ESPRESSO18.rdb +0 -0
  59. {arvi-0.2.5 → arvi-0.2.7}/tests/test_binning.py +0 -0
  60. {arvi-0.2.5 → arvi-0.2.7}/tests/test_config.py +0 -0
  61. {arvi-0.2.5 → arvi-0.2.7}/tests/test_create_RV.py +0 -0
  62. {arvi-0.2.5 → arvi-0.2.7}/tests/test_import_object.py +0 -0
  63. {arvi-0.2.5 → arvi-0.2.7}/tests/test_simbad.py +0 -0
  64. {arvi-0.2.5 → arvi-0.2.7}/tests/test_stats.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arvi
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: The Automated RV Inspector
5
5
  Author-email: João Faria <joao.faria@unige.ch>
6
6
  License: MIT
@@ -2,7 +2,7 @@ import os
2
2
  import sys
3
3
  import tarfile
4
4
  import collections
5
- from functools import lru_cache
5
+ from functools import lru_cache, partial
6
6
  from itertools import islice
7
7
  import numpy as np
8
8
 
@@ -170,7 +170,7 @@ def get_observations_from_instrument(star, instrument, user=None, main_id=None,
170
170
  """
171
171
  Spectroscopy = load_spectroscopy(user)
172
172
  found_dace_id = False
173
- with timer('simbad query'):
173
+ with timer('dace_id query'):
174
174
  try:
175
175
  dace_id = get_dace_id(star, verbose=verbose, raise_error=True)
176
176
  found_dace_id = True
@@ -212,17 +212,22 @@ def get_observations_from_instrument(star, instrument, user=None, main_id=None,
212
212
 
213
213
  for inst in np.unique(result['ins_name']):
214
214
  mask1 = result['ins_name'] == inst
215
- r[inst] = {}
215
+ r[str(inst)] = {}
216
216
 
217
- for pipe in np.unique(result['ins_drs_version'][mask1]):
218
- mask2 = mask1 & (result['ins_drs_version'] == pipe)
219
- r[inst][pipe] = {}
217
+ key2 = 'ins_drs_version'
218
+ n_key2 = len(np.unique(result[key2][mask1]))
219
+ if len(np.unique(result['pub_bibcode'][mask1])) >= n_key2:
220
+ key2 = 'pub_bibcode'
221
+
222
+ for pipe in np.unique(result[key2][mask1]):
223
+ mask2 = mask1 & (result[key2] == pipe)
224
+ r[str(inst)][str(pipe)] = {}
220
225
 
221
226
  for ins_mode in np.unique(result['ins_mode'][mask2]):
222
227
  mask3 = mask2 & (result['ins_mode'] == ins_mode)
223
228
  _nan = np.full(mask3.sum(), np.nan)
224
229
 
225
- r[inst][pipe][ins_mode] = {
230
+ r[str(inst)][str(pipe)][str(ins_mode)] = {
226
231
  'texp': result['texp'][mask3],
227
232
  'bispan': result['spectro_ccf_bispan'][mask3],
228
233
  'bispan_err': result['spectro_ccf_bispan_err'][mask3],
@@ -372,9 +377,11 @@ def get_observations(star, instrument=None, user=None, main_id=None, verbose=Tru
372
377
  # # For all other strings, sort alphabetically
373
378
  # return (2, s)
374
379
 
375
- def custom_key(val):
380
+ def custom_key(val, strip_EGGS=False):
381
+ if strip_EGGS:
382
+ val = val.replace('-EGGS', '').replace(' EGGS', '')
376
383
  key = 0
377
- key -= 2 if val == '3.5' else 0
384
+ key -= 1 if '3.5' in val else 0
378
385
  key -= 1 if 'EGGS' in val else 0
379
386
  key -= 1 if ('UHR' in val or 'MR' in val) else 0
380
387
  key -= 1 if 'LBL' in val else 0
@@ -385,6 +392,8 @@ def get_observations(star, instrument=None, user=None, main_id=None, verbose=Tru
385
392
  # new_result[inst] = dict(
386
393
  # sorted(result[inst].items(), key=custom_sort_key, reverse=True)
387
394
  # )
395
+ if all(['EGGS' in k for k in result[inst].keys()]):
396
+ custom_key = partial(custom_key, strip_EGGS=True)
388
397
  # WARNING: not the same as reverse=True (not sure why)
389
398
  sorted_keys = sorted(result[inst].keys(), key=custom_key)[::-1]
390
399
  new_result[inst] = {}
@@ -3,8 +3,6 @@ from io import StringIO
3
3
  from csv import DictReader
4
4
  import requests
5
5
 
6
- from astropy.coordinates import SkyCoord
7
-
8
6
  DATA_PATH = os.path.dirname(__file__)
9
7
  DATA_PATH = os.path.join(DATA_PATH, 'data')
10
8
 
@@ -78,6 +76,8 @@ class gaia:
78
76
  Args:
79
77
  star (str): The name of the star to query simbad
80
78
  """
79
+ from astropy.coordinates import SkyCoord
80
+
81
81
  self.star = star
82
82
 
83
83
  if simbad is None:
@@ -107,14 +107,7 @@ def divide_HARPS(self):
107
107
 
108
108
 
109
109
  def check(self, instrument):
110
- logger = setup_logger()
111
- instruments = self._check_instrument(instrument)
112
- if instruments is None:
113
- if self.verbose:
114
- logger.error(f"HARPS_fiber_commissioning: no data from {instrument}")
115
- return None
116
- return instruments
117
-
110
+ return self._check_instrument(instrument)
118
111
 
119
112
  # HARPS commissioning
120
113
  def HARPS_commissioning(self, mask=True, plot=True):
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import requests
3
3
  from dataclasses import dataclass
4
+ from functools import partial
4
5
 
5
6
  import numpy as np
6
7
 
@@ -63,6 +64,23 @@ JOIN ident ON oidref = oid
63
64
  WHERE id = '{star}';
64
65
  """
65
66
 
67
+ HD_GJ_HIP_QUERY = """
68
+ SELECT id2.id
69
+ FROM ident AS id1 JOIN ident AS id2 USING(oidref)
70
+ WHERE id1.id = '{star}' AND id2.id LIKE '{name}%';
71
+ """
72
+
73
+ def find_identifier(identifier, star):
74
+ response = run_query(HD_GJ_HIP_QUERY.format(name=identifier, star=star))
75
+ if identifier in response:
76
+ return response.split('"')[1]
77
+ raise ValueError(f'no {identifier} identifier found for "{star}"')
78
+
79
+ find_HD = partial(find_identifier, 'HD')
80
+ find_GJ = partial(find_identifier, 'GJ')
81
+ find_HIP = partial(find_identifier, 'HIP')
82
+
83
+
66
84
  @dataclass
67
85
  class Measurements:
68
86
  teff: list
@@ -0,0 +1,111 @@
1
+ from multiprocessing.pool import ThreadPool
2
+ import re
3
+ import requests
4
+ from io import StringIO
5
+
6
+ import numpy as np
7
+
8
+ from .setup_logger import setup_logger
9
+
10
+ URL_CCF = "http://atlas.obs-hp.fr/sophie/sophie.cgi?n=sophiecc&ob=date&a=t&o={target}"
11
+ URL_HEADER = "http://atlas.obs-hp.fr/sophie/sophie.cgi?n=sophiecc&c=i&z=fd&a=t&o=sophie:[ccf,{seq},{mask},0]"
12
+
13
+ def extract_keyword(keyword, text, raise_error=True):
14
+ for line in text.splitlines():
15
+ if keyword in line:
16
+ value = re.findall(fr'{keyword}\s+([\'\w\d.]+)', line)[0]
17
+ value = value.replace("'", "")
18
+ try:
19
+ return float(value)
20
+ except ValueError:
21
+ return value
22
+ if raise_error:
23
+ raise KeyError(f'Keyword {keyword} not found')
24
+
25
+ def query_sophie_archive(star: str, verbose=True):
26
+ from .timeseries import RV
27
+ logger = setup_logger()
28
+
29
+ resp = requests.get(URL_CCF.format(target=star))
30
+ if 'leda did not return a position for the name' in resp.text:
31
+ raise ValueError(f'no SOPHIE observations for {star}')
32
+
33
+ data = np.genfromtxt(StringIO(resp.text), dtype=None, usecols=(0, 4),
34
+ names=("seq", "mask"))
35
+
36
+ if verbose:
37
+ logger.info(f'found {len(data)} SOPHIE observations for {star}')
38
+
39
+ urls = [URL_HEADER.format(seq=seq, mask=mask) for seq, mask in data]
40
+ with ThreadPool(8) as pool:
41
+ responses = pool.map(requests.get, urls)
42
+
43
+ bjd, vrad, svrad = [], [], []
44
+ fwhm, contrast = [], []
45
+ ccf_mask = []
46
+ _quantities = []
47
+ errors = []
48
+
49
+ for i, resp in enumerate(responses):
50
+ if resp.text == '':
51
+ errors.append(i)
52
+ continue
53
+
54
+ try:
55
+ t, v = map(lambda k: extract_keyword(k, resp.text),
56
+ ("OHP DRS BJD", "OHP DRS CCF RV"))
57
+ except KeyError:
58
+ errors.append(i)
59
+ continue
60
+ else:
61
+ bjd.append(t)
62
+ vrad.append(v)
63
+
64
+ try:
65
+ svrad.append(extract_keyword("OHP DRS CCF ERR", resp.text))
66
+ except KeyError:
67
+ try:
68
+ svrad.append(1e-3 * extract_keyword("OHP DRS DVRMS", resp.text))
69
+ except KeyError:
70
+ bjd.pop(-1)
71
+ vrad.pop(-1)
72
+ errors.append(i)
73
+ continue
74
+
75
+ fwhm.append(extract_keyword('OHP DRS CCF FWHM', resp.text))
76
+ _quantities.append('fwhm')
77
+
78
+ contrast.append(extract_keyword('OHP DRS CCF CONTRAST', resp.text))
79
+ _quantities.append('contrast')
80
+
81
+ ccf_mask.append(extract_keyword('OHP DRS CCF MASK', resp.text))
82
+ _quantities.append('ccf_mask')
83
+
84
+ if len(errors) > 0:
85
+ logger.warning(f'Could not retrieve {len(errors)} observation'
86
+ f'{"s" if len(errors) > 1 else ""}')
87
+
88
+ bjd = np.array(bjd) - 2400000.5
89
+
90
+ s = RV.from_arrays(star, bjd, vrad, svrad, 'SOPHIE',
91
+ fwhm=fwhm, fwhm_err=2*np.array(svrad),
92
+ contrast=contrast,
93
+ ccf_mask=ccf_mask)
94
+ s.units = 'km/s'
95
+
96
+ # strings
97
+ for q in ['date_night', 'prog_id', 'raw_file', 'pub_reference']:
98
+ setattr(s, q, np.full(bjd.size, ''))
99
+ _quantities.append(q)
100
+
101
+ s._quantities = np.array(_quantities)
102
+
103
+ setattr(s, 'SOPHIE', s)
104
+ s._child = False
105
+ s.verbose = False
106
+ s._build_arrays()
107
+ s.change_units('m/s')
108
+ s.verbose = verbose
109
+
110
+ return s
111
+
@@ -40,43 +40,74 @@ class RV(ISSUES, REPORTS):
40
40
  """
41
41
  A class holding RV observations
42
42
 
43
+ Args:
44
+ star (str):
45
+ Name of the star
46
+ instrument (str, list):
47
+ Name of the instrument or list of instruments
48
+ verbose (bool):
49
+ Print logging messages
50
+ do_maxerror:
51
+ Mask points based on a maximum RV uncertainty
52
+ do_secular_acceleration:
53
+ Apply secular acceleration correction. This only applies
54
+ to certain instruments.
55
+ do_sigma_clip (bool):
56
+ Apply sigma clipping on the RVs
57
+ do_adjust_means (bool):
58
+ Subtract individual weighted mean RV from each instrument
59
+ only_latest_pipeline (bool):
60
+ Select only the latest pipeline from each instrument
61
+ load_extra_data (bool):
62
+ check_drs_qc (bool):
63
+ Mask points based on DRS quality control flags
64
+ user (str):
65
+ User name for DACE queries (should be a section in `~/.dacerc` file)
66
+
43
67
  Examples:
44
68
  >>> s = RV('Proxima')
69
+ >>> s = RV('HD10180', instrument='HARPS')
45
70
 
46
- Attributes:
47
- star (str):
48
- The name of the star
49
- N (int):
50
- Total number of observations
51
- instruments (list):
52
- List of instruments for which there are RVs. Each instrument is also
53
- stored as an attribute (e.g. `self.CORALIE98` or `self.HARPS`)
54
- simbad (simbad):
55
- Information on the target from Simbad
56
71
  """
72
+ # Attributes:
73
+ # star (str):
74
+ # The name of the star
75
+ # N (int):
76
+ # Total number of observations
77
+ # NN (dict):
78
+ # Number of observations per instrument
79
+ # instruments (list):
80
+ # List of instruments for which there are RVs. Each instrument is also
81
+ # stored as an attribute (e.g. `self.CORALIE98` or `self.HARPS`)
82
+ # simbad (simbad):
83
+ # Information on the target from Simbad
84
+ # gaia (gaia):
85
+ # Information on the target from Gaia DR3
57
86
  star: str
58
87
  instrument: Union[str, list] = field(init=True, repr=False, default=None)
59
88
  verbose: bool = field(init=True, repr=False, default=True)
60
- do_maxerror: Union[bool, float] = field(init=True, repr=False, default=False)
89
+ do_maxerror: float = field(init=True, repr=False, default=None)
61
90
  do_secular_acceleration: bool = field(init=True, repr=False, default=True)
62
91
  do_sigma_clip: bool = field(init=True, repr=False, default=False)
63
92
  do_adjust_means: bool = field(init=True, repr=False, default=True)
64
93
  only_latest_pipeline: bool = field(init=True, repr=False, default=True)
65
94
  load_extra_data: Union[bool, str] = field(init=True, repr=False, default=False)
66
95
  check_drs_qc: bool = field(init=True, repr=False, default=True)
67
- user: bool = field(init=True, repr=False, default=None)
96
+ check_sophie_archive: bool = field(init=True, repr=False, default=False)
97
+ user: Union[str, None] = field(init=True, repr=False, default=None)
68
98
  #
69
99
  units = 'm/s'
70
100
  _child: bool = field(init=True, repr=False, default=False)
71
- _did_secular_acceleration: bool = field(init=False, repr=False, default=False)
72
- _did_sigma_clip: bool = field(init=False, repr=False, default=False)
73
- _did_adjust_means: bool = field(init=False, repr=False, default=False)
74
- _did_simbad_query: bool = field(init=False, repr=False, default=False)
75
- _did_gaia_query: bool = field(init=False, repr=False, default=False)
76
- _did_toi_query: bool = field(init=False, repr=False, default=False)
77
- _raise_on_error: bool = field(init=True, repr=False, default=True)
78
- __masked_numbers: bool = field(init=False, repr=False, default=False)
79
- #
101
+ #
102
+ _did_secular_acceleration : bool = field(init=False, repr=False, default=False)
103
+ _did_sigma_clip : bool = field(init=False, repr=False, default=False)
104
+ _did_adjust_means : bool = field(init=False, repr=False, default=False)
105
+ _did_simbad_query : bool = field(init=False, repr=False, default=False)
106
+ _did_gaia_query : bool = field(init=False, repr=False, default=False)
107
+ _did_toi_query : bool = field(init=False, repr=False, default=False)
108
+ _raise_on_error : bool = field(init=True, repr=False, default=True)
109
+ __masked_numbers : bool = field(init=False, repr=False, default=False)
110
+ #
80
111
  _simbad = None
81
112
  _gaia = None
82
113
  _toi = None
@@ -104,7 +135,7 @@ class RV(ISSUES, REPORTS):
104
135
 
105
136
  if self._child:
106
137
  return None
107
-
138
+
108
139
  if self._did_simbad_query:
109
140
  return None
110
141
 
@@ -215,7 +246,7 @@ class RV(ISSUES, REPORTS):
215
246
  else:
216
247
  mid = None
217
248
 
218
- with timer():
249
+ with timer('dace query'):
219
250
  self.dace_result = get_observations(self.__star__, self.instrument,
220
251
  user=self.user, main_id=mid, verbose=self.verbose)
221
252
  except ValueError as e:
@@ -288,6 +319,21 @@ class RV(ISSUES, REPORTS):
288
319
  # all other quantities
289
320
  self._build_arrays()
290
321
 
322
+ # self.actin = get_actin_data(self, verbose=self.verbose)
323
+
324
+
325
+ # check for SOPHIE observations
326
+ cond = not self._child
327
+ cond = cond and self.instrument is None
328
+ cond = cond and self.check_sophie_archive
329
+ if cond:
330
+ try:
331
+ from arvi.sophie_wrapper import query_sophie_archive
332
+ self.__add__(query_sophie_archive(self.star, verbose=self.verbose),
333
+ inplace=True)
334
+ except Exception as e:
335
+ print(e)
336
+
291
337
  # do clip_maxerror, secular_acceleration, sigmaclip, adjust_means
292
338
  if not self._child:
293
339
  if self.do_maxerror:
@@ -301,7 +347,7 @@ class RV(ISSUES, REPORTS):
301
347
 
302
348
  if self.do_adjust_means:
303
349
  self.adjust_means()
304
-
350
+
305
351
  _star_no_space = self.star.replace(' ', '')
306
352
  _directory = sanitize_path(_star_no_space)
307
353
  self._download_directory = f'{_directory}_downloads'
@@ -526,7 +572,7 @@ class RV(ISSUES, REPORTS):
526
572
  return s
527
573
 
528
574
  @classmethod
529
- def from_arrays(cls, star, time, vrad, svrad, inst, *args, **kwargs):
575
+ def from_arrays(cls, star, time, vrad, svrad, inst, **kwargs):
530
576
  s = cls(star, _child=True)
531
577
  time, vrad, svrad = map(np.atleast_1d, (time, vrad, svrad))
532
578
 
@@ -541,11 +587,15 @@ class RV(ISSUES, REPORTS):
541
587
  s.time = time
542
588
  s.vrad = vrad
543
589
  s.svrad = svrad
544
- # mask
545
- s.mask = kwargs.get('mask', np.full_like(s.time, True, dtype=bool))
590
+
591
+ s.mask = kwargs.pop('mask', np.full_like(s.time, True, dtype=bool))
592
+ s.units = kwargs.pop('units', 'm/s')
593
+
594
+ for k, v in kwargs.items():
595
+ setattr(s, k, np.atleast_1d(v))
546
596
 
547
597
  s.instruments = [inst]
548
- s._quantities = np.array([])
598
+ s._quantities = np.array(list(kwargs.keys()))
549
599
 
550
600
  return s
551
601
 
@@ -566,7 +616,7 @@ class RV(ISSUES, REPORTS):
566
616
  dt = datetime.fromtimestamp(float(timestamp))
567
617
  if verbose:
568
618
  logger.info(f'reading snapshot of {star} from {dt}')
569
-
619
+
570
620
  s = pickle.load(open(file, 'rb'))
571
621
  s._snapshot = file
572
622
  return s
@@ -578,7 +628,7 @@ class RV(ISSUES, REPORTS):
578
628
 
579
629
  Args:
580
630
  files (str, list):
581
- File name or list of file names
631
+ File name, file object, or list of file names
582
632
  star (str, optional):
583
633
  Name of the star. If None, try to infer it from file name
584
634
  instrument (str, list, optional):
@@ -589,37 +639,68 @@ class RV(ISSUES, REPORTS):
589
639
  Number of lines to skip in the header. Defaults to 2.
590
640
 
591
641
  Examples:
592
- s = RV.from_rdb('star_HARPS.rdb')
642
+ >>> s = RV.from_rdb('star_HARPS.rdb')
593
643
  """
594
644
  from glob import glob
595
645
  from os.path import splitext, basename
596
646
 
597
647
  verbose = kwargs.pop('verbose', True)
598
648
 
649
+ file_object = False
650
+
599
651
  if isinstance(files, str):
600
652
  if '*' in files:
601
653
  files = glob(files)
602
654
  else:
603
655
  files = [files]
656
+ elif isinstance(files, list):
657
+ pass
658
+ else:
659
+ file_object = hasattr(files, 'read')
660
+ files = [files]
604
661
 
605
- if len(files) == 0:
606
- if verbose:
607
- logger.error('no files found')
608
- return
662
+ # if len(files) == 0:
663
+ # if verbose:
664
+ # logger.error('no files found')
665
+ # return
666
+ def get_star_name(file):
667
+ return splitext(basename(file))[0].split('_')[0].replace('-', '_')
668
+
669
+ def get_instrument(file):
670
+ return splitext(basename(file))[0].split('_')[1]
609
671
 
610
- if star is None:
611
- star_ = np.unique([splitext(basename(f))[0].split('_')[0] for f in files])
612
- if star_.size == 1:
613
- star = star_[0].replace('-', '_')
672
+ if file_object:
673
+ if star is None:
674
+ try:
675
+ star = get_star_name(files[0].name)
676
+ except Exception:
677
+ star ='unknown'
614
678
  if verbose:
615
679
  logger.info(f'assuming star is {star}')
616
680
 
617
- if instrument is None:
618
- instruments = np.array([splitext(basename(f))[0].split('_')[1] for f in files])
619
- if verbose:
620
- logger.info(f'assuming instruments: {instruments}')
681
+ if instrument is None:
682
+ try:
683
+ instrument = get_instrument(files[0].name)
684
+ except Exception:
685
+ instrument = 'unknown'
686
+ if verbose:
687
+ logger.info(f'assuming instrument is {instrument}')
688
+
689
+ instruments = np.array([instrument])
621
690
  else:
622
- instruments = np.atleast_1d(instrument)
691
+ if star is None:
692
+ star = np.unique([get_star_name(f) for f in files])[0]
693
+ if verbose:
694
+ logger.info(f'assuming star is {star}')
695
+ else:
696
+ star = 'unknown'
697
+
698
+ if instrument is None:
699
+ instruments = np.array([splitext(basename(f))[0].split('_')[1] for f in files])
700
+ if verbose:
701
+ logger.info(f'assuming instruments: {instruments}')
702
+ else:
703
+ instruments = np.atleast_1d(instrument)
623
704
 
624
705
  if instruments.size == 1 and len(files) > 1:
625
706
  instruments = np.repeat(instruments, len(files))
@@ -652,12 +733,16 @@ class RV(ISSUES, REPORTS):
652
733
  _quantities = []
653
734
 
654
735
  #! hack
655
- with open(f) as ff:
656
- header = ff.readline().strip()
657
- if '\t' in header:
658
- names = header.split('\t')
659
- else:
660
- names = header.split()
736
+ if file_object:
737
+ header = f.readline().strip()
738
+ else:
739
+ with open(f) as ff:
740
+ header = ff.readline().strip()
741
+
742
+ if '\t' in header:
743
+ names = header.split('\t')
744
+ else:
745
+ names = header.split()
661
746
 
662
747
  if len(names) > 3:
663
748
  # if f.endswith('.rdb'):
@@ -670,7 +755,7 @@ class RV(ISSUES, REPORTS):
670
755
  data = np.genfromtxt(f, **kw, delimiter='\t')
671
756
  else:
672
757
  data = np.genfromtxt(f, **kw)
673
-
758
+
674
759
  # if data.ndim in (0, 1):
675
760
  # data = data.reshape(-1, 1)
676
761
 
@@ -814,7 +899,22 @@ class RV(ISSUES, REPORTS):
814
899
 
815
900
  @classmethod
816
901
  def from_ccf(cls, files, star=None, instrument=None, **kwargs):
817
- """ Create an RV object from a CCF file or a list of CCF files """
902
+ """ Create an RV object from a CCF file or a list of CCF files
903
+
904
+ !!! Note
905
+ This function relies on the `iCCF` package
906
+
907
+ Args:
908
+ files (str or list):
909
+ CCF file or list of CCF files
910
+ star (str):
911
+ Star name. If not provided, it will be inferred from the header
912
+ of the CCF file
913
+ instrument (str):
914
+ Instrument name. If not provided, it will be inferred from the
915
+ header of the CCF file
916
+
917
+ """
818
918
  try:
819
919
  import iCCF
820
920
  except ImportError:
@@ -839,7 +939,7 @@ class RV(ISSUES, REPORTS):
839
939
  objects = np.unique([i.HDU[0].header['OBJECT'].replace(' ', '') for i in CCFs])
840
940
 
841
941
  if len(objects) != 1:
842
- logger.warning(f'found {objects.size} different stars in the CCF files, '
942
+ logger.warning(f'found {objects.size} different stars in the CCF files ({objects}), '
843
943
  'choosing the first one')
844
944
  star = objects[0]
845
945
 
@@ -865,9 +965,22 @@ class RV(ISSUES, REPORTS):
865
965
  _quantities.append('contrast')
866
966
  _quantities.append('contrast_err')
867
967
 
968
+ _s.bispan = np.array([i.BIS*1e3 for i in CCFs])
969
+ _s.bispan_err = np.array([i.BISerror*1e3 for i in CCFs])
970
+ _quantities.append('bispan')
971
+ _quantities.append('bispan_err')
972
+
973
+ _s.rhk = np.full_like(time, np.nan)
974
+ _s.rhk_err = np.full_like(time, np.nan)
975
+ _quantities.append('rhk')
976
+ _quantities.append('rhk_err')
977
+
868
978
  _s.texp = np.array([i.HDU[0].header['EXPTIME'] for i in CCFs])
869
979
  _quantities.append('texp')
870
980
 
981
+ _s.berv = np.array([i.HDU[0].header['HIERARCH ESO QC BERV'] for i in CCFs])
982
+ _quantities.append('berv')
983
+
871
984
  _s.date_night = np.array([
872
985
  i.HDU[0].header['DATE-OBS'].split('T')[0] for i in CCFs
873
986
  ])
@@ -938,17 +1051,17 @@ class RV(ISSUES, REPORTS):
938
1051
  if fits_file not in tar.getnames():
939
1052
  logger.error(f'KOBE file not found for {star}')
940
1053
  return
941
-
1054
+
942
1055
  hdul = fits.open(tar.extractfile(fits_file))
943
1056
 
944
1057
  else:
945
1058
  resp = requests.get(f'https://kobe.caha.es/internal/fitsfiles/{fits_file}',
946
1059
  auth=HTTPBasicAuth('kobeteam', config.kobe_password))
947
-
1060
+
948
1061
  if resp.status_code != 200:
949
1062
  # something went wrong, try to extract the file by downloading the
950
1063
  # full tar.gz archive
951
-
1064
+
952
1065
  logger.warning(f'could not find "{fits_file}" on server, trying to download full archive')
953
1066
  resp = requests.get('https://kobe.caha.es/internal/fitsfiles.tar.gz',
954
1067
  auth=HTTPBasicAuth('kobeteam', config.kobe_password))
@@ -966,7 +1079,7 @@ class RV(ISSUES, REPORTS):
966
1079
  if fits_file not in tar.getnames():
967
1080
  logger.error(f'KOBE file not found for {star}')
968
1081
  return
969
-
1082
+
970
1083
  hdul = fits.open(tar.extractfile(fits_file))
971
1084
 
972
1085
  else:
@@ -1365,7 +1478,9 @@ class RV(ISSUES, REPORTS):
1365
1478
  def remove_point(self, index):
1366
1479
  """
1367
1480
  Remove individual observations at a given index (or indices).
1368
- NOTE: Like Python, the index is 0-based.
1481
+
1482
+ !!! Note
1483
+ Like Python, the index is 0-based.
1369
1484
 
1370
1485
  Args:
1371
1486
  index (int, list, ndarray):
@@ -1376,7 +1491,7 @@ class RV(ISSUES, REPORTS):
1376
1491
  instrument_index = self.obs[index]
1377
1492
  np.array(self.instruments)[instrument_index - 1]
1378
1493
  except IndexError:
1379
- logger.errors(f'index {index} is out of bounds for N={self.N}')
1494
+ logger.error(f'index {index} is out of bounds for N={self.N}')
1380
1495
  return
1381
1496
 
1382
1497
  if self.verbose:
@@ -1397,18 +1512,21 @@ class RV(ISSUES, REPORTS):
1397
1512
  def restore_point(self, index):
1398
1513
  """
1399
1514
  Restore previously deleted individual observations at a given index (or
1400
- indices). NOTE: Like Python, the index is 0-based.
1515
+ indices).
1516
+
1517
+ !!! Note
1518
+ Like Python, the index is 0-based
1401
1519
 
1402
1520
  Args:
1403
1521
  index (int, list, ndarray):
1404
- Single index, list, or array of indices to restore.
1522
+ Single index, list, or array of indices to restore
1405
1523
  """
1406
1524
  index = np.atleast_1d(index)
1407
1525
  try:
1408
1526
  instrument_index = self.obs[index]
1409
1527
  np.array(self.instruments)[instrument_index - 1]
1410
1528
  except IndexError:
1411
- logger.errors(f'index {index} is out of bounds for N={self.N}')
1529
+ logger.error(f'index {index} is out of bounds for N={self.N}')
1412
1530
  return
1413
1531
 
1414
1532
  if self.verbose:
@@ -1427,6 +1545,14 @@ class RV(ISSUES, REPORTS):
1427
1545
  self.mask = self.mask & self.public
1428
1546
  self._propagate_mask_changes()
1429
1547
 
1548
+ def remove_public(self):
1549
+ """ Remove public observations """
1550
+ if self.verbose:
1551
+ n = self.public.sum()
1552
+ logger.info(f'masking public observations ({n})')
1553
+ self.mask = self.mask & (~self.public)
1554
+ self._propagate_mask_changes()
1555
+
1430
1556
  def remove_single_observations(self):
1431
1557
  """ Remove instruments for which there is a single observation """
1432
1558
  singles = [i for i in self.instruments if getattr(self, i).mtime.size == 1]
@@ -1452,26 +1578,26 @@ class RV(ISSUES, REPORTS):
1452
1578
  if self.verbose:
1453
1579
  logger.warning(f'no observations for prog_id "{prog_id}"')
1454
1580
 
1455
- def remove_after_bjd(self, bjd):
1581
+ def remove_after_bjd(self, bjd: float):
1456
1582
  """ Remove observations after a given BJD """
1457
1583
  if (self.time > bjd).any():
1458
1584
  ind = np.where(self.time > bjd)[0]
1459
1585
  self.remove_point(ind)
1460
1586
 
1461
- def remove_before_bjd(self, bjd):
1587
+ def remove_before_bjd(self, bjd: float):
1462
1588
  """ Remove observations before a given BJD """
1463
1589
  if (self.time < bjd).any():
1464
1590
  ind = np.where(self.time < bjd)[0]
1465
1591
  self.remove_point(ind)
1466
-
1467
- def remove_between_bjds(self, bjd1, bjd2):
1592
+
1593
+ def remove_between_bjds(self, bjd1: float, bjd2: float):
1468
1594
  """ Remove observations between two BJDs """
1469
1595
  to_remove = (self.time > bjd1) & (self.time < bjd2)
1470
1596
  if to_remove.any():
1471
1597
  ind = np.where(to_remove)[0]
1472
1598
  self.remove_point(ind)
1473
1599
 
1474
- def choose_n_points(self, n, seed=None, instrument=None):
1600
+ def choose_n_points(self, n: int, seed=None, instrument=None):
1475
1601
  """ Randomly choose `n` observations and mask out the remaining ones
1476
1602
 
1477
1603
  Args:
@@ -1509,15 +1635,20 @@ class RV(ISSUES, REPORTS):
1509
1635
  getattr(self, inst).mask[m - n_before] = False
1510
1636
 
1511
1637
  def secular_acceleration(self, epoch=None, just_compute=False, force_simbad=False):
1512
- """
1513
- Remove secular acceleration from RVs
1638
+ """
1639
+ Remove secular acceleration from RVs. This uses the proper motions from
1640
+ Gaia (in `self.gaia`) if available, otherwise from Simbad (in
1641
+ `self.simbad`), unless `force_simbad=True`.
1642
+
1514
1643
 
1515
1644
  Args:
1516
1645
  epoch (float, optional):
1517
1646
  The reference epoch (DACE uses 55500, 31/10/2010)
1518
- instruments (bool or collection of str):
1519
- Only remove secular acceleration for some instruments, or for all
1520
- if `instruments=True`
1647
+ just_compute (bool, optional):
1648
+ Just compute the secular acceleration and return, without
1649
+ changing the RVs
1650
+ force_simbad (bool, optional):
1651
+ Use Simbad proper motions even if Gaia is available
1521
1652
  """
1522
1653
  if self._did_secular_acceleration and not just_compute: # don't do it twice
1523
1654
  return
@@ -1626,7 +1757,7 @@ class RV(ISSUES, REPORTS):
1626
1757
 
1627
1758
  if config.return_self:
1628
1759
  return self
1629
-
1760
+
1630
1761
  def _undo_secular_acceleration(self):
1631
1762
  if self._did_secular_acceleration:
1632
1763
  _old_verbose = self.verbose
@@ -1654,7 +1785,15 @@ class RV(ISSUES, REPORTS):
1654
1785
  self._did_secular_acceleration = False
1655
1786
 
1656
1787
  def sigmaclip(self, sigma=5, instrument=None, strict=True):
1657
- """ Sigma-clip RVs (per instrument!) """
1788
+ """
1789
+ Sigma-clip RVs (per instrument!), by MAD away from the median.
1790
+
1791
+ Args:
1792
+ sigma (float):
1793
+ Number of MADs to clip
1794
+ instrument (str, list):
1795
+ Instrument(s) to sigma-clip
1796
+ """
1658
1797
  #from scipy.stats import sigmaclip as dosigmaclip
1659
1798
  from .stats import sigmaclip_median as dosigmaclip
1660
1799
 
@@ -1691,7 +1830,7 @@ class RV(ISSUES, REPORTS):
1691
1830
 
1692
1831
  self._propagate_mask_changes()
1693
1832
 
1694
- if self._did_adjust_means:
1833
+ if len(changed_instruments) > 0 and self._did_adjust_means:
1695
1834
  self._did_adjust_means = False
1696
1835
  self.adjust_means(instrument=changed_instruments)
1697
1836
 
@@ -1724,7 +1863,8 @@ class RV(ISSUES, REPORTS):
1724
1863
  """
1725
1864
  Nightly bin the observations.
1726
1865
 
1727
- WARNING: This creates and returns a new object and does not modify self.
1866
+ !!! Warning
1867
+ This creates and returns a new object and does not modify self.
1728
1868
  """
1729
1869
 
1730
1870
  # create copy of self to be returned
@@ -1828,7 +1968,7 @@ class RV(ISSUES, REPORTS):
1828
1968
  return np.nanmean(z, axis=0)
1829
1969
 
1830
1970
  def subtract_mean(self):
1831
- """ Subtract (single) mean RV from all instruments """
1971
+ """ Subtract (a single) non-weighted mean RV from all instruments """
1832
1972
  self._meanRV = meanRV = self.mvrad.mean()
1833
1973
  for inst in self.instruments:
1834
1974
  s = getattr(self, inst)
@@ -1864,7 +2004,7 @@ class RV(ISSUES, REPORTS):
1864
2004
  # row = []
1865
2005
  # if print_as_table:
1866
2006
  # logger.info('subtracted weighted average from each instrument:')
1867
-
2007
+
1868
2008
  others = ('fwhm', 'bispan', )
1869
2009
 
1870
2010
  instruments = self._check_instrument(instrument, strict=kwargs.get('strict', False))
@@ -2143,10 +2283,10 @@ class RV(ISSUES, REPORTS):
2143
2283
  """ Sort instruments by first or last observation date.
2144
2284
 
2145
2285
  Args:
2146
- by_first_observation (bool, optional, default=True):
2147
- Sort by first observation date.
2148
- by_last_observation (bool, optional, default=False):
2149
- Sort by last observation date.
2286
+ by_first_observation (bool, optional):
2287
+ Sort by first observation date
2288
+ by_last_observation (bool, optional):
2289
+ Sort by last observation date
2150
2290
  """
2151
2291
  if by_last_observation:
2152
2292
  by_first_observation = False
@@ -2222,7 +2362,7 @@ class RV(ISSUES, REPORTS):
2222
2362
  # if self.verbose:
2223
2363
  # logger.warning(f'masking {nan_mask.sum()} observations with NaN in indicators')
2224
2364
 
2225
- header = '\t'.join(['rjd', 'vrad', 'svrad',
2365
+ header = '\t'.join(['rjd', 'vrad', 'svrad',
2226
2366
  'fwhm', 'sig_fwhm',
2227
2367
  'bispan', 'sig_bispan',
2228
2368
  'contrast', 'sig_contrast',
@@ -79,14 +79,17 @@ def timer(name=None):
79
79
  if name is None:
80
80
  logger.debug('starting timer')
81
81
  else:
82
- logger.debug(f'starting timer: {name}')
82
+ logger.debug(f'{name}: starting timer')
83
83
 
84
84
  start = time.time()
85
85
  try:
86
86
  yield
87
87
  finally:
88
88
  end = time.time()
89
- logger.debug(f'elapsed time: {end - start:.2f} seconds')
89
+ if name is None:
90
+ logger.debug(f'elapsed time {end - start:.2f} seconds')
91
+ else:
92
+ logger.debug(f'{name}: elapsed time {end - start:.2f} seconds')
90
93
 
91
94
 
92
95
  def sanitize_path(path):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arvi
3
- Version: 0.2.5
3
+ Version: 0.2.7
4
4
  Summary: The Automated RV Inspector
5
5
  Author-email: João Faria <joao.faria@unige.ch>
6
6
  License: MIT
@@ -28,6 +28,7 @@ arvi/programs.py
28
28
  arvi/reports.py
29
29
  arvi/setup_logger.py
30
30
  arvi/simbad_wrapper.py
31
+ arvi/sophie_wrapper.py
31
32
  arvi/spectra.py
32
33
  arvi/stats.py
33
34
  arvi/stellar.py
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
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