PyFT8 3.6.5__tar.gz → 3.7.0__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 (43) hide show
  1. {pyft8-3.6.5 → pyft8-3.7.0}/PKG-INFO +1 -1
  2. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/gui.py +66 -40
  3. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/message_broker.py +10 -8
  4. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/pyft8.py +2 -2
  5. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/receiver.py +1 -1
  6. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8.egg-info/PKG-INFO +1 -1
  7. {pyft8-3.6.5 → pyft8-3.7.0}/pyproject.toml +1 -1
  8. pyft8-3.7.0/tests/spare2.py +37 -0
  9. pyft8-3.6.5/tests/spare2.py +0 -28
  10. {pyft8-3.6.5 → pyft8-3.7.0}/LICENSE +0 -0
  11. {pyft8-3.6.5 → pyft8-3.7.0}/MANIFEST.in +0 -0
  12. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/__init__.py +0 -0
  13. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/databases.py +0 -0
  14. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/osd.py +0 -0
  15. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/pskreporter.py +0 -0
  16. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/qso_manager.py +0 -0
  17. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/rigctrl.py +0 -0
  18. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/time_utils.py +0 -0
  19. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8/transmitter.py +0 -0
  20. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8.egg-info/SOURCES.txt +0 -0
  21. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8.egg-info/dependency_links.txt +0 -0
  22. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8.egg-info/entry_points.txt +0 -0
  23. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8.egg-info/requires.txt +0 -0
  24. {pyft8-3.6.5 → pyft8-3.7.0}/PyFT8.egg-info/top_level.txt +0 -0
  25. {pyft8-3.6.5 → pyft8-3.7.0}/README.md +0 -0
  26. {pyft8-3.6.5 → pyft8-3.7.0}/setup.cfg +0 -0
  27. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/CQ AAAA.py +0 -0
  28. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/animation.py +0 -0
  29. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/bits_loopback.py +0 -0
  30. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/bits_loopback_short.py +0 -0
  31. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/osd.py +0 -0
  32. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/symbols_loopback.py +0 -0
  33. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/test_generate_wav.py +0 -0
  34. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/test_loopback_performance.py +0 -0
  35. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/text.py +0 -0
  36. {pyft8-3.6.5 → pyft8-3.7.0}/tests/misc/view_worked_before.py +0 -0
  37. {pyft8-3.6.5 → pyft8-3.7.0}/tests/pipeline/Fine tuning compare IOS_PC.py +0 -0
  38. {pyft8-3.6.5 → pyft8-3.7.0}/tests/pipeline/Targetted tests.py +0 -0
  39. {pyft8-3.6.5 → pyft8-3.7.0}/tests/pipeline/test_01.wav +0 -0
  40. {pyft8-3.6.5 → pyft8-3.7.0}/tests/play_wavs.py +0 -0
  41. {pyft8-3.6.5 → pyft8-3.7.0}/tests/plot_decode_times.py +0 -0
  42. {pyft8-3.6.5 → pyft8-3.7.0}/tests/spare.py +0 -0
  43. {pyft8-3.6.5 → pyft8-3.7.0}/tests/test_batch_and_live.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFT8
3
- Version: 3.6.5
3
+ Version: 3.7.0
4
4
  Summary: FT8 Decoding and Encoding in Python with test/loopback code
5
5
  Author-email: G1OJS <g1ojs@yahoo.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -1,6 +1,8 @@
1
1
  import numpy as np
2
2
  import matplotlib.pyplot as plt
3
3
  from matplotlib import rcParams
4
+ import matplotlib.style as mplstyle
5
+ mplstyle.use('fast')
4
6
  import queue
5
7
  from matplotlib.widgets import Button
6
8
  from PyFT8.time_utils import time_utils
@@ -23,24 +25,31 @@ def add_my_axes(fig, pos):
23
25
  ax.set_facecolor(TEXT_BACKGROUND_COLOR)
24
26
  return ax
25
27
 
26
- MESSAGE_TYPES = {'generic':{'bg':'blue', 'fg':'white', 'alpha':0.5}, 'CQ':{'bg':'green', 'fg':'white', 'alpha':0.8},
27
- 'from_me': {'bg':'yellow', 'fg':'black', 'alpha':0.95}, 'to_me':{'bg':'red', 'fg':'white', 'alpha':0.9}}
28
+ MESSAGE_TYPES = {'generic':{'bg':'blue', 'fg':'white', 'alpha':0.4}, 'CQ':{'bg':'green', 'fg':'white', 'alpha':0.7},
29
+ 'from_me': {'bg':'yellow', 'fg':'black', 'alpha':0.9}, 'to_me':{'bg':'red', 'fg':'white', 'alpha':0.9}}
28
30
  class Msg_box:
29
- def __init__(self, fig, ax, x, y, w, h, message):
31
+ def __init__(self, fig, ax, w, h):
30
32
  from matplotlib.patches import Rectangle
31
33
  self.fig, self.ax = fig, ax
32
- message_type_params = MESSAGE_TYPES[message['message_type']]
33
- rect = Rectangle((x, y), w, h, edgecolor='lime', lw=2)
34
+ rect = Rectangle((0, 0), w, h, edgecolor='lime', lw=2)
34
35
  self.patch = self.ax.add_patch(rect)
35
36
  self.patch.set_visible(False)
36
- text_inst = self.ax.text(x, y+1, message['display_text'], fontsize='small', fontweight = 'bold' )
37
- text_inst.set_visible(False)
38
- text_inst.set_color(message_type_params['fg'])
39
- self.patch.set_facecolor(message_type_params['bg'])
40
- self.patch.set_alpha(message_type_params['alpha'])
37
+ self.text_inst = self.ax.text(0, 0, '', fontweight = 'bold' )
38
+ self.text_inst.set_visible(False)
39
+ self.text_inst.set_fontfamily('monospace')
40
+ self.artists = [self.patch, self.text_inst]
41
+
42
+ def set_props(self, x, y, message):
43
+ self.xy = (x, y)
41
44
  self.message = message
42
45
  self.cycle = message['their_tx_cycle']
43
- self.artists = [self.patch, text_inst]
46
+ message_type_params = MESSAGE_TYPES[message['message_type']]
47
+ self.text_inst.set_text(message['display_text'])
48
+ self.text_inst.set_color(message_type_params['fg'])
49
+ self.patch.set_facecolor(message_type_params['bg'])
50
+ self.patch.set_alpha(message_type_params['alpha'])
51
+ self.text_inst.set_position((x, y+1))
52
+ self.patch.set_xy((x, y))
44
53
  for a in self.artists:
45
54
  a.set_visible(True)
46
55
 
@@ -166,34 +175,48 @@ class Gui:
166
175
  self.cid = self.fig.canvas.mpl_connect('button_press_event', self._oncanvasclick)
167
176
 
168
177
  def main_loop(self):
169
- last_ptr = 0
170
178
  self.plt.show(block = False)
179
+ target_update_time = 0.15
171
180
  while True:
181
+ self.fig.canvas.flush_events() # for clicks
182
+ tstart_wf_redraw = time_utils.time()
172
183
  self.image.set_data(self.waterfall_data['data'])
173
184
  if self.needs_redraw:
174
185
  self.needs_redraw = False
175
186
  plt.pause(0.01)
176
187
  else:
177
188
  self.ax_wf.draw_artist(self.image)
189
+ self.fig.canvas.flush_events() # for clicks
178
190
  for mb in self.msg_boxes:
179
- mb.draw()
180
- self.fig.canvas.update()
191
+ if mb.patch.get_visible():
192
+ mb.draw()
193
+ self.fig.canvas.flush_events() # for clicks
194
+ self.fig.canvas.blit(self.ax_wf.bbox) #self.fig.canvas.update()
181
195
  self.fig.canvas.flush_events()
196
+ tdelay = target_update_time - (time_utils.time() - tstart_wf_redraw)
197
+ if tdelay > 0.01:
198
+ time_utils.sleep(tdelay)
182
199
 
183
- def before_search(self, curr_cycle):
184
- self._hide_msg_boxes(curr_cycle)
185
-
186
- def after_search(self,curr_cycle):
187
- self._refresh_hearing()
188
- self._refresh_band_buttons()
200
+ def before_search(self, curr_cycle): # would be better with a local trigger
201
+ #self._hide_msg_boxes(curr_cycle) # hide boxes overlapping new decodes
202
+ self._hide_msg_boxes(curr_cycle = None) # hide all boxes
189
203
  self._refresh_home_panel()
190
- self._clear_msg_boxes(curr_cycle)
204
+ self._refresh_band_buttons()
205
+ self._refresh_hearing()
191
206
 
192
207
  def display_message(self, message):
208
+ mb = self._get_message_box()
193
209
  x = int(message['t0'] / self.waterfall_data['dt'] + message['their_tx_cycle'] * self.waterfall_data['pixels_per_cycle'])
194
210
  y = int(message['fHz'] / self.waterfall_data['df'])
195
- mb = Msg_box(self.fig, self.ax_wf, x, y, self.waterfall_data['sig_w'], self.waterfall_data['sig_h'], message)
211
+ mb.set_props(x, y, message)
196
212
  self.msg_boxes.append(mb)
213
+
214
+ def update_message(self, display_text, update_dict):
215
+ for mb in self.msg_boxes:
216
+ if mb.patch.get_visible():
217
+ if mb.message['display_text'] == display_text:
218
+ mb.message.update(update_dict)
219
+ mb.set_props(mb.xy[0], mb.xy[1], mb.message)
197
220
 
198
221
  def get_band_info(self):
199
222
  return self.band_info
@@ -215,18 +238,34 @@ class Gui:
215
238
  color = row_text['color'])
216
239
  self.needs_redraw = True
217
240
 
241
+ def _get_message_box(self):
242
+ mb = None
243
+ for mb1 in self.msg_boxes:
244
+ if not mb1.patch.get_visible():
245
+ mb = mb1
246
+ break
247
+ if not mb:
248
+ mb = Msg_box(self.fig, self.ax_wf, self.waterfall_data['sig_w'], self.waterfall_data['sig_h'])
249
+ return mb
250
+
218
251
  def _set_band(self, band):
219
252
  if band in self.configured_bands:
220
253
  self.band_info = {'current_band':band, 'fMHz':self.configured_bands[band], 'time_set':time_utils.time()}
221
254
  self.console_print(f"[PyFT8] Set band: {self.band_info['current_band']} {self.band_info['fMHz']}")
255
+ for mb in self.msg_boxes:
256
+ mb.hide()
257
+ mb.remove()
258
+ self.msg_boxes = []
259
+ self._refresh_home_panel()
260
+ self._refresh_band_buttons()
261
+ self._refresh_hearing()
262
+ self.needs_redraw = True
222
263
 
223
264
  def _oncanvasclick(self, clickargs):
224
- #print(f"Click at {time_utils.cycle_time():6.2f}")
225
-
226
265
  if clickargs.inaxes is self.ax_wf:
227
266
  for mb in self.msg_boxes:
228
267
  if mb.contains(clickargs.x, clickargs.y):
229
- self.console_print(f"[GUI] Clicked on message '{mb.message['msg_text']}'")
268
+ self.console_print(f"[GUI] Clicked on message '{mb.message['msg_text']}")
230
269
  self.qso_manager.on_click({'action':"MESSAGE_CLICK", 'message':mb.message})
231
270
  return
232
271
  for bb in self.button_boxes:
@@ -235,28 +274,15 @@ class Gui:
235
274
  self.needs_redraw = True
236
275
  self._set_band(bb.id)
237
276
  self.qso_manager.on_click({'action':"SET_BAND", 'fMHz':self.configured_bands[bb.id]})
238
- self.after_search(curr_cycle = None)
239
277
  else:
240
278
  action = ["CQ", "RPT_LAST", "TX_OFF"][['CQ', 'Repeat last', 'Tx off'].index(bb.id)]
241
279
  self.qso_manager.on_click({'action':action})
242
280
 
243
- def _hide_msg_boxes(self, curr_cycle):
244
- to_hide = [mb for mb in self.msg_boxes if mb.cycle == curr_cycle]
281
+ def _hide_msg_boxes(self, curr_cycle = None):
282
+ to_hide = [mb for mb in self.msg_boxes if (mb.cycle == curr_cycle) or (curr_cycle is None)]
245
283
  for mb in to_hide:
246
284
  mb.hide()
247
285
  self.needs_redraw = True
248
-
249
- def _clear_msg_boxes(self, curr_cycle = None):
250
- to_remove = [mb for mb in self.msg_boxes if mb.cycle == curr_cycle or curr_cycle is None]
251
- self.msg_boxes = [mb for mb in self.msg_boxes if mb.cycle != curr_cycle]
252
- for mb in to_remove:
253
- mb.remove()
254
- self.needs_redraw = True
255
-
256
- def _text_row(self, ax, x, y, text = '', color = 'white', **args):
257
- art = ax.text(x, y, text, color = color)
258
- art.set_fontfamily('monospace')
259
- return art
260
286
 
261
287
  def _refresh_home_panel(self):
262
288
  current_band = self.band_info['current_band']
@@ -26,20 +26,15 @@ class Broker():
26
26
  mtype = ['generic', 'from_me', 'to_me', 'CQ'][mtype_val]
27
27
  message_dict.update( {'message_type':mtype, 'display_text':f"{hail} {their_call} {grid_rpt}",
28
28
  'priority':(mtype == 'to_me' or mtype == 'CQ')} )
29
- if self.history:
30
- current_band = self.gui.get_band_info()['current_band']
31
- hearing_me, wb_text, geo_text = self.history.get_message_extra_info(their_call, current_band)
32
- display_text = f"{message_dict['hail']} {message_dict['their_call']} {message_dict['grid_rpt']} {hearing_me} {wb_text} {geo_text}"
33
- message_dict.update( {'hearing_me':hearing_me, 'wb_text':wb_text, 'geo_text':geo_text, 'display_text':display_text } )
34
-
35
29
  if message_dict['priority']:
36
30
  if self.gui:
37
31
  self.gui.display_message(message_dict)
32
+
38
33
  if self.on_decode:
39
34
  self.on_decode(message_dict)
40
35
  m = message_dict
41
36
  screen_format = f"{cyclestart_string} {their_snr} {m['dt']:4.1f} {m['fHz']:4.0f} ~ {hail} {their_call} {grid_rpt}"
42
- print(f"{screen_format:50s} decoded@ {m['decode_completed']:5.1f}s, dec = {m['decode_status']}")
37
+ print(f"{screen_format:50s} decoded@ {m['decode_completed'] %15:5.1f}s, dec = {m['decode_status']}")
43
38
  self.message_queue_non_time_critical.put(message_dict)
44
39
 
45
40
  def _process_message_ntc(self):
@@ -52,7 +47,14 @@ class Broker():
52
47
  if self.gui:
53
48
  band_info = self.gui.get_band_info()
54
49
  if not m['priority']:
55
- self.gui.display_message(m)
50
+ self.gui.display_message(m)
51
+ else:
52
+ if self.history:
53
+ current_band = self.gui.get_band_info()['current_band']
54
+ hearing_me, wb_text, geo_text = self.history.get_message_extra_info(m['their_call'], current_band)
55
+ new_display_text = f"{m['display_text']} {hearing_me} {wb_text} {geo_text}"
56
+ self.gui.update_message( m['display_text'], {'hearing_me':hearing_me, 'wb_text':wb_text,
57
+ 'geo_text':geo_text, 'display_text':new_display_text } )
56
58
  if band_info:
57
59
  if m['their_call'] != 'not':
58
60
  if self.history:
@@ -11,7 +11,7 @@ from PyFT8.databases import ADIF
11
11
  from PyFT8.qso_manager import QSO_manager
12
12
  from PyFT8.message_broker import Broker
13
13
 
14
- VER = '3.6.5'
14
+ VER = '3.7.0'
15
15
  PSKR_REFRESH_MINS = 20
16
16
  message_broker = None
17
17
 
@@ -136,7 +136,7 @@ def cli():
136
136
  qso_manager = QSO_manager(message_broker, rig_control, console_print)
137
137
  message_broker.gui.register_qso_manager(qso_manager)
138
138
  message_broker.rx.register_before_search(message_broker.gui.before_search)
139
- message_broker.rx.register_after_search(message_broker.gui.after_search)
139
+ #message_broker.rx.register_after_search(message_broker.gui.after_search)
140
140
  message_broker.history.incorporate_log_data(qso_manager.adif_logging.cache)
141
141
  message_broker.history.start_collect_new()
142
142
 
@@ -592,7 +592,7 @@ class Receiver():
592
592
  'msg_text':f"{hail} {their_call} {grid_rpt}",
593
593
  'their_snr':f"{c.snr:+03d}", 'their_tx_cycle':c.origin['odd_even'],
594
594
  't0':c.origin['t0'], 'dt':c.origin['t0'] - 0.5, 'fHz':c.origin['f0'],
595
- 'decode_status':c.decode_status, 'decode_completed':time_utils.cycle_time()}
595
+ 'decode_status':c.decode_status, 'decode_completed':time_utils.time()}
596
596
  self.process_message(message_dict)
597
597
 
598
598
  if len(to_decode):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFT8
3
- Version: 3.6.5
3
+ Version: 3.7.0
4
4
  Summary: FT8 Decoding and Encoding in Python with test/loopback code
5
5
  Author-email: G1OJS <g1ojs@yahoo.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PyFT8"
3
- version = "3.6.5"
3
+ version = "3.7.0"
4
4
  license = "GPL-3.0-or-later"
5
5
 
6
6
  authors = [
@@ -0,0 +1,37 @@
1
+
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import matplotlib as mpl
5
+ import time
6
+ import threading
7
+ SIZE = 1000
8
+
9
+ def thread1():
10
+ dat = np.random.rand(SIZE, SIZE)
11
+ while True:
12
+ time.sleep(0.1)
13
+ dat = ( dat * dat )%1
14
+
15
+
16
+ def oncanvasclick(args):
17
+ t = time.time()
18
+ print(t %60)
19
+
20
+ fig, ax = plt.subplots( figsize=(8,8) )
21
+ ax.set_xlim(0,SIZE)
22
+ ax.set_ylim(0,SIZE)
23
+ threading.Thread(target = thread1, daemon = True).start()
24
+ plt.show(block = False)
25
+
26
+ dat = np.random.rand(SIZE, SIZE)
27
+ img = ax.imshow(dat)
28
+ plt.pause(0.1)
29
+ cid = fig.canvas.mpl_connect('button_press_event', oncanvasclick)
30
+ while True:
31
+ time.sleep(0.1)
32
+ img.set_data(np.random.rand(SIZE, SIZE))
33
+ plt.pause(0.2)
34
+
35
+
36
+
37
+
@@ -1,28 +0,0 @@
1
-
2
- import numpy as np
3
- import matplotlib.pyplot as plt
4
- import time
5
- SIZE = 500
6
-
7
- fig, ax = plt.subplots( figsize=(8,8) )
8
- ax.set_xlim(0,SIZE)
9
- ax.set_ylim(0,SIZE)
10
- plt.show(block = False)
11
-
12
- arts = []
13
-
14
- from matplotlib.patches import Rectangle
15
- for i in range(10):
16
- x, y = SIZE*np.random.rand(1)[0], SIZE*np.random.rand(1)[0]
17
- rect = Rectangle((x, y), width=100, height=6, alpha=0.6, edgecolor='lime', lw=2)
18
- art = ax.add_patch(rect)
19
- arts.append(art)
20
- ax.draw_artist(art)
21
- fig.canvas.update()
22
- fig.canvas.flush_events()
23
-
24
- time.sleep(1)
25
- for a in arts:
26
- a.remove()
27
- plt.pause(0.001)
28
-
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