arvi 0.1.10__py3-none-any.whl → 0.1.12__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 arvi might be problematic. Click here for more details.

arvi/__init__.py CHANGED
@@ -8,6 +8,7 @@ def __getattr__(name: str):
8
8
  if name in (
9
9
  '_ipython_canary_method_should_not_exist_',
10
10
  '_repr_mimebundle_',
11
+ '__wrapped__'
11
12
  ):
12
13
  return
13
14
 
@@ -0,0 +1,70 @@
1
+ import os
2
+ import sys
3
+ from matplotlib import pyplot as plt
4
+
5
+ try:
6
+ from astroARIADNE.star import Star
7
+ from astroARIADNE.fitter import Fitter
8
+ except ImportError:
9
+ print('This module requires astroARIADNE. Install with `pip install astroARIADNE`')
10
+ sys.exit(0)
11
+
12
+
13
+ def run_ariadne(self, fit=True, plot=True, priors={},
14
+ models = ('phoenix', 'btsettl', 'btnextgen', 'btcond', 'kurucz', 'ck04'),
15
+ nlive=300, dlogz=1, threads=6, dynamic=False, **kwargs):
16
+ if hasattr(self, 'gaia'):
17
+ s = Star(self.star, self.gaia.ra, self.gaia.dec, g_id=self.gaia.dr3_id,
18
+ search_radius=1)
19
+ else:
20
+ s = Star(self.star, self.simbad.ra, self.simbad.dec, g_id=self.simbad.gaia_id,
21
+ search_radius=1)
22
+
23
+ out_folder = f'{self.star}_ariadne'
24
+
25
+ setup = dict(engine='dynesty', nlive=nlive, dlogz=dlogz,
26
+ bound='multi', sample='auto', threads=threads, dynamic=dynamic)
27
+ setup = list(setup.values())
28
+
29
+ f = Fitter()
30
+ f.star = s
31
+ f.setup = setup
32
+ f.av_law = 'fitzpatrick'
33
+ f.out_folder = out_folder
34
+ f.bma = True
35
+ f.models = models
36
+ f.n_samples = 10_000
37
+
38
+ f.prior_setup = {
39
+ 'teff': priors.get('teff', ('default')),
40
+ 'logg': ('default'),
41
+ 'z': priors.get('feh', ('default')),
42
+ 'dist': ('default'),
43
+ 'rad': ('default'),
44
+ 'Av': ('default')
45
+ }
46
+
47
+ if fit:
48
+ f.initialize()
49
+ f.fit_bma()
50
+
51
+ if plot:
52
+ from pkg_resources import resource_filename
53
+ from astroARIADNE.plotter import SEDPlotter
54
+ modelsdir = resource_filename('astroARIADNE', 'Datafiles/models')
55
+ artist = SEDPlotter(os.path.join(out_folder, 'BMA.pkl'), out_folder, models_dir=modelsdir)
56
+
57
+ artist.plot_SED_no_model()
58
+ try:
59
+ artist.plot_SED()
60
+ except FileNotFoundError as e:
61
+ print('No model found:', e)
62
+ except IndexError as e:
63
+ print('Error!')
64
+ artist.plot_bma_hist()
65
+ artist.plot_bma_HR(10)
66
+ artist.plot_corner()
67
+ plt.close('all')
68
+ return s, f, artist
69
+
70
+ return s, f
arvi/config.py CHANGED
@@ -1 +1,8 @@
1
+ # whether to return self from (some) RV methods
1
2
  return_self = False
3
+
4
+ # whether to check internet connection before querying DACE
5
+ check_internet = False
6
+
7
+ # make all DACE requests without using a .dacerc file
8
+ request_as_public = False
arvi/dace_wrapper.py CHANGED
@@ -5,10 +5,15 @@ 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:
12
+ from .config import request_as_public
13
+ if request_as_public:
14
+ with all_logging_disabled():
15
+ dace = DaceClass(dace_rc_config_path='none')
16
+ return SpectroscopyClass(dace_instance=dace)
12
17
  if 'DACERC' in os.environ:
13
18
  dace = DaceClass(dace_rc_config_path=os.environ['DACERC'])
14
19
  return SpectroscopyClass(dace_instance=dace)
@@ -156,6 +161,13 @@ def check_existing(output_directory, files, type):
156
161
  f.partition('.fits')[0] for f in os.listdir(output_directory)
157
162
  if type in f
158
163
  ]
164
+
165
+ # also check for lowercase type
166
+ existing += [
167
+ f.partition('.fits')[0] for f in os.listdir(output_directory)
168
+ if type.lower() in f
169
+ ]
170
+
159
171
  if os.name == 'nt': # on Windows, be careful with ':' in filename
160
172
  import re
161
173
  existing = [re.sub(r'T(\d+)_(\d+)_(\d+)', r'T\1:\2:\3', f) for f in existing]
@@ -174,9 +186,9 @@ def check_existing(output_directory, files, type):
174
186
  def download(files, type, output_directory):
175
187
  """ Download files from DACE """
176
188
  Spectroscopy = load_spectroscopy()
177
- # with stdout_disabled(), all_logging_disabled():
178
- Spectroscopy.download_files(files, file_type=type,
179
- output_directory=output_directory)
189
+ with stdout_disabled(), all_logging_disabled():
190
+ Spectroscopy.download_files(files, file_type=type.lower(),
191
+ output_directory=output_directory)
180
192
 
181
193
  def extract_fits(output_directory):
182
194
  """ Extract fits files from tar.gz file """
@@ -194,86 +206,99 @@ def extract_fits(output_directory):
194
206
  return files
195
207
 
196
208
 
197
- def do_download_ccf(raw_files, output_directory, clobber=False, verbose=True):
198
- """ Download CCFs from DACE """
209
+ def do_download_filetype(type, raw_files, output_directory, clobber=False,
210
+ verbose=True, chunk_size=20):
211
+ """ Download CCFs / S1Ds / S2Ds from DACE """
199
212
  raw_files = np.atleast_1d(raw_files)
200
213
 
201
214
  create_directory(output_directory)
202
215
 
203
216
  # check existing files to avoid re-downloading
204
217
  if not clobber:
205
- raw_files = check_existing(output_directory, raw_files, 'CCF')
218
+ raw_files = check_existing(output_directory, raw_files, type)
219
+
220
+ n = raw_files.size
206
221
 
207
222
  # any file left to download?
208
- if raw_files.size == 0:
223
+ if n == 0:
209
224
  if verbose:
210
225
  logger.info('no files to download')
211
226
  return
212
227
 
213
- if verbose:
214
- n = raw_files.size
215
- logger.info(f"Downloading {n} CCFs into '{output_directory}'...")
216
-
217
- download(raw_files, 'ccf', output_directory)
228
+ # avoid an empty chunk
229
+ if chunk_size > n:
230
+ chunk_size = n
218
231
 
219
232
  if verbose:
220
- logger.info('Extracting .fits files')
233
+ if chunk_size < n:
234
+ msg = f"Downloading {n} {type}s "
235
+ msg += f"(in chunks of {chunk_size}) "
236
+ msg += f"into '{output_directory}'..."
237
+ logger.info(msg)
238
+ else:
239
+ msg = f"Downloading {n} {type}s into '{output_directory}'..."
240
+ logger.info(msg)
221
241
 
222
- extract_fits(output_directory)
242
+ iterator = [raw_files[i:i + chunk_size] for i in range(0, n, chunk_size)]
243
+ for files in tqdm(iterator, total=len(iterator)):
244
+ download(files, type, output_directory)
245
+ extract_fits(output_directory)
223
246
 
247
+ logger.info('Extracted .fits files')
224
248
 
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)
228
249
 
229
- create_directory(output_directory)
250
+ # def do_download_s1d(raw_files, output_directory, clobber=False, verbose=True):
251
+ # """ Download S1Ds from DACE """
252
+ # raw_files = np.atleast_1d(raw_files)
230
253
 
231
- # check existing files to avoid re-downloading
232
- if not clobber:
233
- raw_files = check_existing(output_directory, raw_files, 'S1D')
254
+ # create_directory(output_directory)
234
255
 
235
- # any file left to download?
236
- if raw_files.size == 0:
237
- if verbose:
238
- logger.info('no files to download')
239
- return
256
+ # # check existing files to avoid re-downloading
257
+ # if not clobber:
258
+ # raw_files = check_existing(output_directory, raw_files, 'S1D')
240
259
 
241
- if verbose:
242
- n = raw_files.size
243
- logger.info(f"Downloading {n} S1Ds into '{output_directory}'...")
260
+ # # any file left to download?
261
+ # if raw_files.size == 0:
262
+ # if verbose:
263
+ # logger.info('no files to download')
264
+ # return
244
265
 
245
- download(raw_files, 's1d', output_directory)
266
+ # if verbose:
267
+ # n = raw_files.size
268
+ # logger.info(f"Downloading {n} S1Ds into '{output_directory}'...")
246
269
 
247
- if verbose:
248
- logger.info('Extracting .fits files')
270
+ # download(raw_files, 's1d', output_directory)
249
271
 
250
- extract_fits(output_directory)
272
+ # if verbose:
273
+ # logger.info('Extracting .fits files')
251
274
 
275
+ # extract_fits(output_directory)
252
276
 
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)
256
277
 
257
- create_directory(output_directory)
278
+ # def do_download_s2d(raw_files, output_directory, clobber=False, verbose=True):
279
+ # """ Download S2Ds from DACE """
280
+ # raw_files = np.atleast_1d(raw_files)
258
281
 
259
- # check existing files to avoid re-downloading
260
- if not clobber:
261
- raw_files = check_existing(output_directory, raw_files, 'S2D')
282
+ # create_directory(output_directory)
262
283
 
263
- # any file left to download?
264
- if raw_files.size == 0:
265
- if verbose:
266
- logger.info('no files to download')
267
- return
284
+ # # check existing files to avoid re-downloading
285
+ # if not clobber:
286
+ # raw_files = check_existing(output_directory, raw_files, 'S2D')
268
287
 
269
- if verbose:
270
- n = raw_files.size
271
- logger.info(f"Downloading {n} S2Ds into '{output_directory}'...")
288
+ # # any file left to download?
289
+ # if raw_files.size == 0:
290
+ # if verbose:
291
+ # logger.info('no files to download')
292
+ # return
272
293
 
273
- download(raw_files, 's2d', output_directory)
294
+ # if verbose:
295
+ # n = raw_files.size
296
+ # logger.info(f"Downloading {n} S2Ds into '{output_directory}'...")
274
297
 
275
- if verbose:
276
- logger.info('Extracting .fits files')
298
+ # download(raw_files, 's2d', output_directory)
299
+
300
+ # if verbose:
301
+ # logger.info('Extracting .fits files')
277
302
 
278
- extracted_files = extract_fits(output_directory)
279
- return extracted_files
303
+ # extracted_files = extract_fits(output_directory)
304
+ # return extracted_files
arvi/extra_data.py CHANGED
@@ -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}')
arvi/gaia_wrapper.py ADDED
@@ -0,0 +1,94 @@
1
+ import os
2
+ from io import StringIO
3
+ from csv import DictReader
4
+ from dataclasses import dataclass, field
5
+ import requests
6
+
7
+ from astropy.coordinates import SkyCoord
8
+ import pysweetcat
9
+
10
+ DATA_PATH = os.path.dirname(__file__)
11
+ DATA_PATH = os.path.join(DATA_PATH, 'data')
12
+
13
+ QUERY = """
14
+ SELECT TOP 20 gaia_source.designation,gaia_source.source_id,gaia_source.ra,gaia_source.dec,gaia_source.parallax,gaia_source.pmra,gaia_source.pmdec,gaia_source.ruwe,gaia_source.phot_g_mean_mag,gaia_source.bp_rp,gaia_source.radial_velocity,gaia_source.phot_variable_flag,gaia_source.non_single_star,gaia_source.has_xp_continuous,gaia_source.has_xp_sampled,gaia_source.has_rvs,gaia_source.has_epoch_photometry,gaia_source.has_epoch_rv,gaia_source.has_mcmc_gspphot,gaia_source.has_mcmc_msc,gaia_source.teff_gspphot,gaia_source.logg_gspphot,gaia_source.mh_gspphot,gaia_source.distance_gspphot,gaia_source.azero_gspphot,gaia_source.ag_gspphot,gaia_source.ebpminrp_gspphot
15
+ FROM gaiadr3.gaia_source
16
+ WHERE
17
+ CONTAINS(
18
+ POINT('ICRS',gaiadr3.gaia_source.ra,gaiadr3.gaia_source.dec),
19
+ CIRCLE(
20
+ 'ICRS',
21
+ COORD1(EPOCH_PROP_POS({ra},{dec},{plx},{pmra},{pmdec},{rv},2000,2016.0)),
22
+ COORD2(EPOCH_PROP_POS({ra},{dec},{plx},{pmra},{pmdec},{rv},2000,2016.0)),
23
+ 0.001388888888888889)
24
+ )=1
25
+ """
26
+
27
+ def run_query(query):
28
+ url = 'https://gea.esac.esa.int/tap-server/tap/sync'
29
+ data = dict(query=query, request='doQuery', lang='ADQL', format='csv')
30
+ try:
31
+ response = requests.post(url, data=data, timeout=10)
32
+ except requests.ReadTimeout as err:
33
+ raise IndexError(err)
34
+ except requests.ConnectionError as err:
35
+ raise IndexError(err)
36
+ return response.content.decode()
37
+
38
+ def parse_csv(csv):
39
+ reader = DictReader(StringIO(csv))
40
+ return list(reader)
41
+
42
+
43
+ class gaia:
44
+ """
45
+ A very simple wrapper around a TAP query to gaia for a given target. This
46
+ class simply runs a few TAP queries and stores the result as attributes.
47
+
48
+ Attributes:
49
+ ra (float): right ascension
50
+ dec (float): declination
51
+ coords (SkyCoord): coordinates as a SkyCoord object
52
+ dr3_id (int): Gaia DR3 identifier
53
+ plx (float): parallax
54
+ radial_velocity (float): radial velocity
55
+ """
56
+ def __init__(self, star:str, simbad=None):
57
+ """
58
+ Args:
59
+ star (str): The name of the star to query simbad
60
+ """
61
+ self.star = star
62
+
63
+ if simbad is None:
64
+ from .simbad_wrapper import simbad as Simbad
65
+ simbad = Simbad(star)
66
+
67
+ ra = simbad.ra
68
+ dec = simbad.dec
69
+ plx = simbad.plx
70
+ pmra = simbad.pmra
71
+ pmdec = simbad.pmdec
72
+ rv = simbad.rvz_radvel
73
+ args = dict(ra=ra, dec=dec, plx=plx, pmra=pmra, pmdec=pmdec, rv=rv)
74
+
75
+ try:
76
+ table1 = run_query(query=QUERY.format(**args))
77
+ results = parse_csv(table1)[0]
78
+ except IndexError:
79
+ raise ValueError(f'Gaia query for {star} failed')
80
+
81
+ self.dr3_id = int(results['source_id'])
82
+
83
+ self.ra = float(results['ra'])
84
+ self.dec = float(results['dec'])
85
+ self.pmra = float(results['pmra'])
86
+ self.pmdec = float(results['pmdec'])
87
+ self.coords = SkyCoord(self.ra, self.dec, unit='deg')
88
+ self.plx = float(results['parallax'])
89
+ self.radial_velocity = float(results['radial_velocity'])
90
+
91
+ return
92
+
93
+ def __repr__(self):
94
+ return f'{self.star} (DR3 id={self.dr3_id})'
arvi/plots.py CHANGED
@@ -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