pyForceDAQ 2.0.1.dev0__tar.gz → 2.0.1.dev2__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 (42) hide show
  1. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/PKG-INFO +1 -1
  2. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/pyproject.toml +1 -1
  3. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/__init__.py +1 -1
  4. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/__main__.py +2 -2
  5. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/misc.py +25 -14
  6. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/types.py +8 -7
  7. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/daq/__init__.py +5 -4
  8. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/daq/_mock_sensor.py +1 -1
  9. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/daq/_pyATIDAQ.py +1 -3
  10. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/force/data_recorder.py +30 -34
  11. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/force/sensor.py +18 -23
  12. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/force/sensor_process.py +10 -5
  13. pyforcedaq-2.0.1.dev2/src/pyforcedaq/gui/__init__.py +6 -0
  14. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_run.py +40 -38
  15. pyforcedaq-2.0.1.dev2/src/pyforcedaq/gui/_settings.py +101 -0
  16. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/launcher.py +23 -22
  17. pyforcedaq-2.0.1.dev0/src/pyforcedaq/gui/__init__.py +0 -6
  18. pyforcedaq-2.0.1.dev0/src/pyforcedaq/gui/_settings.py +0 -98
  19. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/__init__.py +0 -0
  20. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/lsl.py +0 -0
  21. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/polling_time_profile.py +0 -0
  22. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/process_priority_manager.py +0 -0
  23. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/timer.py +0 -0
  24. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/_lib/udp_connection.py +0 -0
  25. /pyforcedaq-2.0.1.dev0/src/pyforcedaq/daq/_daq_read_analog_nidaqmx.py → /pyforcedaq-2.0.1.dev2/src/pyforcedaq/daq/_use_nidaqmx.py +0 -0
  26. /pyforcedaq-2.0.1.dev0/src/pyforcedaq/daq/_daq_read_Analog_pydaqmx.py → /pyforcedaq-2.0.1.dev2/src/pyforcedaq/daq/_use_pydaqmx.py +0 -0
  27. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/daq/config.py +0 -0
  28. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/extras/__init__.py +0 -0
  29. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/extras/convert.py +0 -0
  30. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/extras/expyriment_daq_control.py +0 -0
  31. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/extras/opensesame_daq_control.py +0 -0
  32. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/extras/read_force_data.py +0 -0
  33. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/extras/remote_control.py +0 -0
  34. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/force/__init__.py +0 -0
  35. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/force/_log.py +0 -0
  36. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_gui_status.py +0 -0
  37. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_layout.py +0 -0
  38. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_level_indicator.py +0 -0
  39. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_pg_surface.py +0 -0
  40. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_plotter.py +0 -0
  41. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/_scaling.py +0 -0
  42. {pyforcedaq-2.0.1.dev0 → pyforcedaq-2.0.1.dev2}/src/pyforcedaq/gui/forceDAQ_logo.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pyForceDAQ
3
- Version: 2.0.1.dev0
3
+ Version: 2.0.1.dev2
4
4
  Summary: A Python package for data acquisition and analysis in force-based experiments.
5
5
  Author: Oliver Lindemann
6
6
  Author-email: Oliver Lindemann <lindemann@essb.eur.nl>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyForceDAQ"
3
- version = "2.0.1-dev0"
3
+ version = "2.0.1-dev2"
4
4
  description = "A Python package for data acquisition and analysis in force-based experiments."
5
5
  authors = [
6
6
  { name = "Oliver Lindemann", email = "lindemann@essb.eur.nl" },
@@ -7,7 +7,7 @@ launch the GUI force from your Python program:
7
7
  ``
8
8
  from pyforcedaq import gui
9
9
 
10
- gui.run_with_options(remote_control=False,
10
+ gui.run(remote_control=False,
11
11
  ask_filename=True,
12
12
  calibration_file="FT_sensor1.cal")
13
13
  ``
@@ -34,9 +34,9 @@ def cli():
34
34
  from .gui import launcher
35
35
  return launcher.run()
36
36
  else:
37
- gui.run(args.SETTINGS_FILE)
37
+ gui.run_settings(args.SETTINGS_FILE)
38
38
 
39
39
 
40
40
 
41
41
  if __name__ == "__main__": # required because of threading
42
- cli() # gui.run(), gui.run_with_options()
42
+ cli()
@@ -39,20 +39,31 @@ class MinMaxDetector(object):
39
39
  return (self._level_change_time is not None) and \
40
40
  (get_time_ms() - self._level_change_time) < self._duration
41
41
 
42
- def find_calibration_file(calibration_folder, sensor_name,
43
- calibration_suffix=".cal"):
44
-
45
- needle = 'Serial="{0}"'.format(sensor_name)
46
- for x in listdir(path.abspath(calibration_folder)):
47
- filename = path.join(calibration_folder, x)
48
- if path.isfile(filename) and filename.endswith(calibration_suffix):
49
- with open(filename, "r") as fl:
50
- for l in fl:
51
- if l.find(needle)>0:
52
- return filename
53
- raise RuntimeError("Can't find calibration file for sensor '{0}' : {1}.".format(
54
- sensor_name, filename))
55
-
42
+ # def find_calibration_file(calibration_folder: str, sensor_name: str,
43
+ # calibration_suffix=".cal") -> str:
44
+
45
+ # needle = 'Serial="{0}"'.format(sensor_name)
46
+ # calibration_files = []
47
+ # for x in listdir(path.abspath(calibration_folder)):
48
+ # filename = path.join(calibration_folder, x)
49
+ # if path.isfile(filename) and filename.endswith(calibration_suffix):
50
+ # with open(filename, "r") as fl:
51
+ # for l in fl:
52
+ # if l.find(needle)>0:
53
+ # print("Found calibration file for sensor '{0}' : {1}.".format(
54
+ # sensor_name, filename))
55
+ # calibration_files.append(filename)
56
+
57
+ # if len(calibration_files) == 1:
58
+ # return calibration_files[0]
59
+ # elif len(calibration_files) > 1:
60
+ # print("Multiple calibration files found for sensor '{0}'".format(sensor_name))
61
+ # for f in calibration_files:
62
+ # print(" - {0}".format(f))
63
+ # print("Please ensure that only one calibration file exists for each sensor")
64
+ # else:
65
+ # print("No calibration file found for sensor '{0}'.".format(sensor_name))
66
+ # exit()
56
67
 
57
68
  #Sensor History with moving average filtering and distance, velocity
58
69
  class SensorHistory(object):
@@ -1,7 +1,7 @@
1
1
  __author__ = 'Oliver Lindemann'
2
2
 
3
3
  import ctypes as ct
4
- from typing import List
4
+ from typing import Iterator, List, Tuple
5
5
 
6
6
  from .misc import MinMaxDetector as _MinMaxDetector
7
7
 
@@ -13,7 +13,7 @@ TAG_UDPDATA = TAG_COMMENTS + "UDP"
13
13
  CTYPE_FORCES = ct.c_float * 600
14
14
  CTYPE_TRIGGER = ct.c_float * 2
15
15
 
16
- class PollingPriority(object):
16
+ class PollingPriority(object): # TODO needed?
17
17
 
18
18
  NORMAL = 'normal'
19
19
  HIGH = 'high'
@@ -160,12 +160,13 @@ class ForceSensorData(object):
160
160
  self.force = struct.forces
161
161
  self.trigger = struct.trigger
162
162
 
163
- def selected_forces(self, select: List[bool]):
164
- """Return an iterator over selected force values."""
165
- return (force for i, force in enumerate(self.forces) if select[i])
163
+ def selected_forces(self, select: List[bool]) -> List[float]:
164
+ """Return list of selected force values."""
165
+ return [force for i, force in enumerate(self.forces) if select[i]] # FIXME simplify not all force have to save in ForceClass
166
166
 
167
- def selected_trigger(self, select: List[bool]):
168
- return (trigger for i, trigger in enumerate(self.trigger) if select[i])
167
+ def selected_trigger(self, select: List[bool]) -> List[float]:
168
+ """Return list of selected trigger values."""
169
+ return [trigger for i, trigger in enumerate(self.trigger) if select[i]]
169
170
 
170
171
 
171
172
  class UDPData(object):
@@ -1,5 +1,5 @@
1
1
  __author__ = "Oliver Lindemann"
2
- __version__ = "0.4"
2
+ __version__ = "0.5"
3
3
 
4
4
  from .. import USE_MOCK_SENSOR
5
5
  from ._pyATIDAQ import ATI_CDLL
@@ -11,9 +11,10 @@ if USE_MOCK_SENSOR:
11
11
  else:
12
12
  #### change import here if you want to use nidaqmx instead of pydaymx ####
13
13
  try:
14
- from ._daq_read_analog_pydaqmx import DAQReadAnalog
15
- #from ._daq_read_analog_nidaqmx import DAQReadAnalog
16
- except (ImportError, ModuleNotFoundError):
14
+ from ._use_pydaqmx import DAQReadAnalog
15
+ #from ._use_nidaqmx import DAQReadAnalog
16
+ except (ImportError, ModuleNotFoundError, NotImplementedError) as e:
17
+ print("Error importing DAQReadAnalog: {0}".format(e))
17
18
  print("Warning: PyDAQmx or nidaqmx not found. Using mock sensor instead.")
18
19
  from ._mock_sensor import DAQReadAnalog
19
20
 
@@ -75,6 +75,6 @@ class DAQReadAnalog(object):
75
75
  n_new_samples = self._simulation_timer.time - self._sample_cnt
76
76
 
77
77
  self._sample_cnt += 1
78
- x = self._sample_cnt / 2000
78
+ x = self._sample_cnt / 1000
79
79
  y = 10 + np.array((np.sin(x/2), np.cos(x/5), np.sin(x)))*10
80
80
  return np.append(y, np.array((0, 0 , 0, 0, 0))), 1
@@ -12,8 +12,6 @@ __author__ = "Oliver Lindemann"
12
12
  from ctypes import *
13
13
  from sys import platform
14
14
 
15
- from .._lib.misc import find_calibration_file
16
-
17
15
  # ### DATA TYPES ####
18
16
  VOLTAGE_SAMPLE_TYPE = c_float * 7
19
17
  FT_SAMPLE_TYPE = c_float * 6
@@ -292,7 +290,7 @@ if __name__ == "__main__":
292
290
  # -0.065867 0.123803 111.156731 0.039974 0.040417 0.079049
293
291
 
294
292
  #filename = raw_input("Calibration file: ")
295
- filename = find_calibration_file("calibration", "FT30436")
293
+ filename = "calibration/FT30436.cal"
296
294
  atidaq = ATI_CDLL()
297
295
  # get calibration
298
296
  index = c_short(1)
@@ -7,8 +7,7 @@ __author__ = "Oliver Lindemann"
7
7
  import atexit
8
8
  import gzip
9
9
  import logging
10
- import os
11
- import sys
10
+ from pathlib import Path
12
11
  from time import asctime, localtime, strftime
13
12
 
14
13
  from pyforcedaq._lib import timer
@@ -38,7 +37,7 @@ class DataRecorder(object):
38
37
  def __init__(self, force_sensor_settings:SensorSettings | list,
39
38
  poll_udp_connection=False,
40
39
  write_deviceid = False,
41
- polling_priority=None):
40
+ polling_priority:str | None = None):
42
41
 
43
42
 
44
43
  """queue_data will be saved
@@ -82,18 +81,18 @@ class DataRecorder(object):
82
81
  self._proc_manager.add_subprocess(self.udp)
83
82
  self._proc_manager.add_subprocess(self._force_sensor_processes)
84
83
  if polling_priority is not None:
85
- self._proc_manager.set_subprocess_priorities(
86
- level=PollingPriority.get_priority(polling_priority),
87
- disable_gc=False)
84
+ level = PollingPriority.NORMAL
85
+ else:
86
+ level = PollingPriority.get_priority(polling_priority)
87
+ self._proc_manager.set_subprocess_priorities(level=level, disable_gc=False)
88
88
 
89
- logging.info("Main process priority: {}".format(
90
- self._proc_manager.get_main_priority()))
89
+ logging.info("Main process priority: %s", self._proc_manager.get_main_priority())
91
90
  #logging.info("Subprocess priorities: {}".format(self._proc_manager.get_subprocess_priorities()))
92
91
 
93
92
  self._is_recording = False
94
93
  self._file = None
95
94
  self._daq_event = []
96
- self.filename = None
95
+ self.filename: str | None = None
97
96
  atexit.register(self.quit)
98
97
 
99
98
 
@@ -102,7 +101,7 @@ class DataRecorder(object):
102
101
  """Property indicates whether the recording processes are alive"""
103
102
  try:
104
103
  return self._force_sensor_processes[0].is_alive()
105
- except:
104
+ except Exception:
106
105
  return False
107
106
 
108
107
  @property
@@ -119,7 +118,7 @@ class DataRecorder(object):
119
118
  return list(map(lambda x:x.sensor_settings,
120
119
  self._force_sensor_processes))
121
120
 
122
- def quit(self):
121
+ def quit(self) -> list | None:
123
122
  """Stop all recording processes, close data file and quit recording
124
123
 
125
124
  Notes
@@ -145,7 +144,7 @@ class DataRecorder(object):
145
144
 
146
145
  return buffer
147
146
 
148
- def process_and_write_udp_events(self):
147
+ def process_and_write_udp_events(self) -> list:
149
148
  """process udp events and return them"""
150
149
  buffer = []
151
150
  while True:
@@ -159,9 +158,9 @@ class DataRecorder(object):
159
158
  self._save_data(buffer)
160
159
  return buffer
161
160
 
162
- def _save_data(self, data_buffer,
161
+ def _save_data(self, data_buffer: list,
163
162
  recording_screen=None,
164
- float_decimal_places=4):
163
+ float_decimal_places: int = 4) -> None:
165
164
  """ writes data to disk and set counters
166
165
 
167
166
  ignores UDP remote control commands
@@ -199,10 +198,10 @@ class DataRecorder(object):
199
198
  "Writing {0} of {1} blocks".format(c//BLOCKSIZE,
200
199
  buffer_len//BLOCKSIZE)).present()
201
200
 
202
- def _file_write(self, str):
203
- self._file.write(str.encode())
201
+ def _file_write(self, s: str) -> None:
202
+ self._file.write(s.encode())
204
203
 
205
- def save_daq_event(self, code, time=None):
204
+ def save_daq_event(self, code, time: float | None = None) -> None:
206
205
  """Set marker code in file
207
206
 
208
207
  DAQEvent will be timestamps and occur in the data output
@@ -213,7 +212,7 @@ class DataRecorder(object):
213
212
  self._daq_event.append(DAQEvents(time = time, code = code))
214
213
 
215
214
 
216
- def start_recording(self, determine_bias=False):
215
+ def start_recording(self, determine_bias: bool = False) -> None:
217
216
  """Start polling process and record
218
217
 
219
218
  See Also
@@ -233,7 +232,7 @@ class DataRecorder(object):
233
232
  list(map(lambda x:x.start_polling(), self._force_sensor_processes))
234
233
  self._is_recording = True
235
234
 
236
- def pause_recording(self, recording_screen=None):
235
+ def pause_recording(self, recording_screen=None) -> list:
237
236
  """Pauses all polling processes and process data
238
237
 
239
238
  returns
@@ -268,7 +267,7 @@ class DataRecorder(object):
268
267
  self._daq_event = []
269
268
  return data
270
269
 
271
- def determine_biases(self, n_samples):
270
+ def determine_biases(self, n_samples: int) -> None:
272
271
  """Record n data samples (n_samples) to determine bias.
273
272
  Afterwards recording is in pause mode
274
273
 
@@ -289,12 +288,12 @@ class DataRecorder(object):
289
288
  for x in self._force_sensor_processes:
290
289
  x.event_bias_is_available.wait()
291
290
 
292
- def open_data_file(self, filename,
293
- subdirectory="data",
294
- time_stamp_filename=False,
295
- varnames = True,
296
- comment_line="",
297
- zipped=False):
291
+ def open_data_file(self, filename: str,
292
+ subdirectory: str = "data",
293
+ time_stamp_filename: bool = False,
294
+ varnames: bool = True,
295
+ comment_line: str = "",
296
+ zipped: bool = False) -> Path:
298
297
  """Create a data file
299
298
 
300
299
  Only if data file has been opened, data will be saved!
@@ -322,11 +321,8 @@ class DataRecorder(object):
322
321
  full path the actually used file (incl. timestamp)
323
322
 
324
323
  """
325
-
326
- base_dir = os.path.split(sys.argv[0])[0]
327
- data_dir = os.path.join(base_dir, subdirectory)
328
- if not os.path.isdir(data_dir):
329
- os.mkdir(data_dir)
324
+ data_dir = Path.cwd() / subdirectory
325
+ data_dir.mkdir(exist_ok=True)
330
326
  self.close_data_file()
331
327
 
332
328
  if filename is None or len(filename) == 0:
@@ -352,8 +348,8 @@ class DataRecorder(object):
352
348
  else:
353
349
  self.filename = flname + suffix
354
350
 
355
- full_path_file = os.path.join(data_dir, self.filename)
356
- if os.path.isfile(full_path_file):
351
+ full_path_file = data_dir / self.filename
352
+ if full_path_file.is_file():
357
353
  # print "data file already exists, adding counter"
358
354
  cnt += 1
359
355
  else:
@@ -388,7 +384,7 @@ class DataRecorder(object):
388
384
 
389
385
  return full_path_file
390
386
 
391
- def close_data_file(self):
387
+ def close_data_file(self) -> None:
392
388
  """Close the data file
393
389
 
394
390
  Afterwards data will not be saved anymore.
@@ -7,11 +7,11 @@ __author__ = 'Oliver Lindemann'
7
7
 
8
8
  import ctypes as ct
9
9
  from copy import copy
10
+ from pathlib import Path
11
+ from typing import List, Tuple
10
12
 
11
13
  import numpy as np
12
14
 
13
- #from .._lib import lsl
14
- from .._lib.misc import find_calibration_file
15
15
  from .._lib.timer import Timer, app_clock
16
16
  from .._lib.types import ForceSensorData
17
17
  from ..daq import ATI_CDLL, DAQConfiguration, DAQReadAnalog
@@ -22,15 +22,15 @@ class SensorSettings(DAQConfiguration):
22
22
  def __init__(self,
23
23
  device_id:int,
24
24
  sensor_name:str,
25
- calibration_folder:str,
25
+ calibration_file:str | Path,
26
26
  channels="ai0:7",
27
27
  device_name_prefix = "Dev",
28
28
  rate:int=1000,
29
- minVal=-10,
30
- maxVal=10,
31
- reverse_parameter_names=(),
29
+ minVal:float=-10,
30
+ maxVal:float=10,
31
+ reverse_parameter_names: str | Tuple[str] | List[str] | None = None,
32
32
  convert_to_FT:bool=True,
33
- has_lsl_stream:bool=False,
33
+ lsl_stream:bool=False,
34
34
  write_Fx:bool=True,
35
35
  write_Fy:bool=True,
36
36
  write_Fz:bool=True,
@@ -53,7 +53,7 @@ class SensorSettings(DAQConfiguration):
53
53
  self.device_id = device_id
54
54
  self.sensor_name = sensor_name
55
55
  self.convert_to_FT = convert_to_FT
56
- self.has_lsl_stream = has_lsl_stream
56
+ self.lsl_stream = lsl_stream
57
57
  self.write_Fx = write_Fx
58
58
  self.write_Fy = write_Fy
59
59
  self.write_Fz = write_Fz
@@ -62,22 +62,17 @@ class SensorSettings(DAQConfiguration):
62
62
  self.write_Tz = write_Tz
63
63
  self.write_trigger1 = write_trigger1
64
64
  self.write_trigger2 = write_trigger2
65
-
66
- if self.convert_to_FT:
67
- self.calibration_file = find_calibration_file(
68
- calibration_folder=calibration_folder,
69
- sensor_name=sensor_name)
70
- else:
71
- self.calibration_file = None
65
+ self.calibration_file = calibration_file
72
66
 
73
67
  self.reverse_parameters = []
74
- if not isinstance(reverse_parameter_names, (tuple, list)):
75
- reverse_parameter_names = [reverse_parameter_names]
76
- for para in reverse_parameter_names:
77
- try:
78
- self.reverse_parameters.append(ForceSensorData.forces_names.index(para))
79
- except Exception:
80
- pass
68
+ if reverse_parameter_names is not None:
69
+ if isinstance(reverse_parameter_names, str):
70
+ reverse_parameter_names = [reverse_parameter_names]
71
+ for para in reverse_parameter_names:
72
+ try:
73
+ self.reverse_parameters.append(ForceSensorData.forces_names.index(para))
74
+ except Exception:
75
+ pass
81
76
 
82
77
  def array_write_forces(self):
83
78
  return [self.write_Fx, self.write_Fy, self.write_Fz, self.write_Tx, self.write_Ty, self.write_Tz]
@@ -105,7 +100,7 @@ class Sensor(DAQReadAnalog):
105
100
  self.name = settings.sensor_name
106
101
  self.convert_to_FT = settings.convert_to_FT
107
102
  self.timer = Timer(sync_timer=app_clock) # own timer, because this class is used in own process
108
- if self.DAQ_TYPE == "mock":
103
+ if self.DAQ_TYPE == "mock_sensor":
109
104
  self._atidaq = None
110
105
  self.convert_to_FT = False
111
106
  else:
@@ -157,19 +157,21 @@ class SensorProcess(Process):
157
157
  ## create init LSL
158
158
  lsl_data_steam = None
159
159
  lsl_hardware_trigger_stream = None
160
- if self.sensor_settings.has_lsl_stream:
160
+ if self.sensor_settings.lsl_stream:
161
161
  lsl_data_steam = lsl.init(
162
- name=f"Force {self.sensor_settings.device_name}",
162
+ name=f"Force_{self.sensor_settings.device_name}",
163
163
  n_channels=sum(stream_forces),
164
164
  stream_id=f"RF_{self.sensor_settings.device_name}",
165
165
  freq=self.sensor_settings.rate,
166
+ channel_format= lsl.cf_float32,
166
167
  metadata={"sensor_name": self.sensor_settings.sensor_name})
167
168
  n_hardware_trigger = sum(stream_trigger)
168
169
  if n_hardware_trigger > 0:
169
170
  lsl_hardware_trigger_stream = lsl.init(
170
- name=f"Trigger {self.sensor_settings.device_name}",
171
+ name=f"Trigger_{self.sensor_settings.device_name}",
171
172
  n_channels=n_hardware_trigger,
172
173
  stream_id=f"Tr_{self.sensor_settings.device_name}",
174
+ channel_format=lsl.cf_float32,
173
175
  freq=self.sensor_settings.rate)
174
176
 
175
177
 
@@ -189,9 +191,12 @@ class SensorProcess(Process):
189
191
  d = sensor.poll_data()
190
192
  ## LSL
191
193
  if lsl_data_steam is not None:
192
- lsl_data_steam.push_sample(list(d.selected_forces(stream_forces))) # steam only select forces
194
+ lsl_data_steam.push_sample(d.selected_forces(stream_forces)) # steam only select forces
195
+
193
196
  if lsl_hardware_trigger_stream is not None:
194
- lsl_hardware_trigger_stream.push_sample(list(d.selected_trigger(stream_trigger))) # stream only triggers
197
+ tr = d.selected_trigger(stream_trigger)
198
+ if any(tr): # only stream if at least one trigger is active
199
+ lsl_hardware_trigger_stream.push_sample(tr)
195
200
 
196
201
  ptp.update(d.time) # needed? TODO
197
202
  self._last_Fx.value, self._last_Fy.value, self._last_Fz.value, \
@@ -0,0 +1,6 @@
1
+ from ._run import (
2
+ run, # for running with options from Python script
3
+ run_settings,
4
+ )
5
+ from ._settings import settings ## read settings
6
+
@@ -5,7 +5,9 @@ See COPYING file distributed along with the pyForceDAQ copyright and license ter
5
5
  __author__ = "Oliver Lindemann"
6
6
 
7
7
  import logging
8
+ from os import path
8
9
  from pickle import dumps
10
+ from typing import List
9
11
 
10
12
  import numpy as np
11
13
  import pygame
@@ -342,16 +344,16 @@ def _main_loop(exp, recorder, remote_control=False):
342
344
  plotter_thread.join()
343
345
  recorder.pause_recording(s.background)
344
346
 
345
- def run(settings_file: str | None = None):
347
+ def run_settings(settings_file: str | None = None):
346
348
 
347
349
  if settings_file is not None and len(settings_file) > 0:
348
350
  # load different settings file if specified
349
351
  settings.load(settings_file)
350
352
 
351
- return run_with_options(remote_control = settings.recording.remote_control,
353
+ return run(remote_control = settings.recording.remote_control,
352
354
  ask_filename = settings.recording.ask_filename,
353
355
  device_ids = settings.recording.device_ids,
354
- sensor_names = settings.recording.sensor_names,
356
+ calibration_files = settings.recording.calibration_files,
355
357
  calibration_folder = settings.recording.calibration_folder,
356
358
  device_name_prefix = settings.recording.device_name_prefix,
357
359
  write_Fx = settings.recording.write_Fx,
@@ -365,28 +367,28 @@ def run(settings_file: str | None = None):
365
367
  zip_data=settings.recording.zip_data,
366
368
  reverse_scaling = settings.recording.reverse_scaling,
367
369
  convert_to_forces=settings.recording.convert_to_forces,
368
- has_lsl_stream=settings.recording.has_lsl_stream,
370
+ lsl_stream=settings.recording.lsl_stream,
369
371
  polling_priority=settings.recording.priority)
370
372
 
371
- def run_with_options(remote_control,
373
+ def run(remote_control,
372
374
  ask_filename,
373
- device_ids,
374
- sensor_names,
375
- calibration_folder,
376
- device_name_prefix="Dev",
377
- write_Fx = True,
378
- write_Fy = True,
379
- write_Fz = True,
380
- write_Tx = False,
381
- write_Ty = False,
382
- write_Tz = False,
383
- write_trigger1 = True,
384
- write_trigger2 = False,
385
- zip_data=False,
386
- reverse_scaling = None,
387
- convert_to_forces = True,
388
- has_lsl_stream = False,
389
- polling_priority = None):
375
+ device_ids : int | List[int],
376
+ calibration_files : str | List[str],
377
+ calibration_folder: str,
378
+ device_name_prefix: str="Dev",
379
+ write_Fx: bool = True,
380
+ write_Fy: bool = True,
381
+ write_Fz: bool = True,
382
+ write_Tx: bool = False,
383
+ write_Ty: bool = False,
384
+ write_Tz: bool = False,
385
+ write_trigger1 : bool = True,
386
+ write_trigger2: bool = False,
387
+ zip_data: bool = False,
388
+ reverse_scaling: dict | None = None,
389
+ convert_to_forces: bool = True,
390
+ lsl_stream: bool = False,
391
+ polling_priority: str | None = None):
390
392
 
391
393
  """start gui
392
394
  remote_control should be None (ask) or True or False
@@ -401,31 +403,34 @@ def run_with_options(remote_control,
401
403
  """
402
404
  #
403
405
 
404
- logging.info("New Recording with forceDAQ {}".format(forceDAQVersion))
405
- logging.info("Sensors {}".format(sensor_names))
406
- logging.info("Settings " + settings.recording_as_json())
406
+ logging.info("New Recording with forceDAQ %s", forceDAQVersion)
407
+ logging.info("Sensors %s", calibration_files)
408
+ logging.info("Settings %s", settings.recording_as_json())
407
409
 
408
410
 
409
411
  if not isinstance(device_ids, (list, tuple)):
410
412
  device_ids = [device_ids]
411
- if not isinstance(sensor_names, (list, tuple)):
412
- sensor_names = [sensor_names]
413
+ if not isinstance(calibration_files, (list, tuple)):
414
+ calibration_files = [calibration_files]
413
415
 
414
416
  sensors = []
415
- for d_id, sn in zip(device_ids, sensor_names):
416
- try:
417
- reverse_parameter_names = reverse_scaling[str(d_id)]
418
- except:
417
+ for d_id, cal_file in zip(device_ids, calibration_files):
418
+ if reverse_scaling is None:
419
419
  reverse_parameter_names = []
420
+ else:
421
+ try:
422
+ reverse_parameter_names = reverse_scaling[str(d_id)]
423
+ except KeyError:
424
+ reverse_parameter_names = []
420
425
 
421
426
  ss = SensorSettings(device_id = d_id,
422
427
  device_name_prefix=device_name_prefix,
423
- sensor_name = sn,
424
- calibration_folder=calibration_folder,
428
+ sensor_name = cal_file.split(".")[0],
429
+ calibration_file=path.join(calibration_folder, cal_file),
425
430
  reverse_parameter_names=reverse_parameter_names,
426
431
  rate = settings.gui.sampling_rate,
427
432
  convert_to_FT=convert_to_forces,
428
- has_lsl_stream=has_lsl_stream,
433
+ lsl_stream=lsl_stream,
429
434
  write_Fx = write_Fx,
430
435
  write_Fy = write_Fy,
431
436
  write_Fz = write_Fz,
@@ -436,11 +441,8 @@ def run_with_options(remote_control,
436
441
  write_trigger2= write_trigger2)
437
442
  sensors.append(ss)
438
443
 
439
-
440
-
441
444
  # expyriment
442
- control.defaults.initialize_delay = 0
443
- control.defaults.pause_key = None
445
+ control.defaults.initialise_delay = 0
444
446
  control.defaults.window_mode = True
445
447
  control.defaults.window_size = (1000, 700)
446
448
  control.defaults.fast_quit = True
@@ -0,0 +1,101 @@
1
+ import json
2
+ import os
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+
6
+ import tomlkit
7
+ from icecream import ic
8
+
9
+
10
+ @dataclass
11
+ class _GUISettings:
12
+ sampling_rate: int = 1000
13
+ level_detection_parameter: str = "Fz"
14
+ window_font: str = "freemono"
15
+ moving_average_size: int = 5
16
+ screen_refresh_interval_indicator: int = 300
17
+ gui_screen_refresh_interval_plotter: int = 50
18
+ data_min_max: list = field(default_factory=lambda: [-5, 30])
19
+ plotter_pixel_min_max: list = field(default_factory=lambda: [-250, 250])
20
+ indicator_pixel_min_max: list = field(default_factory=lambda: [-150, 150])
21
+ plot_axis: bool = False
22
+ plot_data_indicator_for_single_sensor: list = field(
23
+ default_factory=lambda: [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5)])
24
+ plot_data_plotter_for_single_sensor: list = field(
25
+ default_factory=lambda: [(0, 0), (0, 1), (0, 2)])
26
+ plot_data_indicator_for_two_sensors: list = field(
27
+ default_factory=lambda: [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)])
28
+ plot_data_plotter_for_two_sensors: list = field(
29
+ default_factory=lambda: [(0, 2), (1, 2)])
30
+
31
+ @dataclass
32
+ class _RecordingSetting:
33
+ device_name_prefix: str = "Dev"
34
+ device_ids: List[int] = field(default_factory=lambda: [1])
35
+ ask_filename: bool = False
36
+ remote_control: bool = False
37
+ calibration_folder: str = "calibration"
38
+ calibration_files: List[str] = field(default_factory=lambda: ["FT9334.cal"])
39
+ zip_data: bool = True
40
+ write_Fx: bool = True
41
+ write_Fy: bool = True
42
+ write_Fz: bool = True
43
+ write_Tx: bool = False
44
+ write_Ty: bool = False
45
+ write_Tz: bool = False
46
+ write_trigger1: bool = False
47
+ write_trigger2: bool = False
48
+ lsl_stream: bool = False
49
+ reverse_scaling: dict = field(default_factory=lambda: {"1": ["Fz"], "2": ["Fz"]})
50
+ convert_to_forces: bool = True
51
+ priority: str = "normal"
52
+
53
+ def __post_init__(self):
54
+ if isinstance(self.device_ids, int):
55
+ self.device_ids = [self.device_ids]
56
+ if isinstance(self.calibration_files, str):
57
+ self.calibration_files = [self.calibration_files]
58
+
59
+
60
+ class GUISettings(object):
61
+
62
+ def __init__(self, filename):
63
+ # defaults
64
+ self.gui = _GUISettings()
65
+ self.gui_section = "GUI"
66
+
67
+ self.recording = _RecordingSetting()
68
+ self.recording_section = "Recording"
69
+
70
+ self.filename = filename
71
+ if os.path.isfile(self.filename):
72
+ self.load()
73
+ else:
74
+ self.save() # defaults
75
+
76
+ def _asdict(self):
77
+ return {self.recording_section: self.recording.__dict__,
78
+ self.gui_section: self.gui.__dict__}
79
+
80
+ def set_gui_settings(self, gui_setting_dict):
81
+ self.gui = _GUISettings(**gui_setting_dict)
82
+
83
+ def set_recording_setting(self, recording_setting_dict):
84
+ self.recording = _RecordingSetting(**recording_setting_dict)
85
+
86
+ def load(self, filename=None):
87
+ if filename is not None:
88
+ self.filename = filename
89
+ with open(self.filename, 'r', encoding='utf-8') as fl:
90
+ d = tomlkit.load(fl)
91
+ self.set_gui_settings(d[self.gui_section])
92
+ self.set_recording_setting(d[self.recording_section])
93
+
94
+ def save(self):
95
+ with open(self.filename, 'w', encoding='utf-8') as fl:
96
+ tomlkit.dump(self._asdict(), fl)
97
+
98
+ def recording_as_json(self):
99
+ return json.dumps(self.recording.__dict__)
100
+
101
+ settings = GUISettings(filename="pyForceDAQ.defaults.settings.toml")
@@ -1,13 +1,13 @@
1
1
  import sys
2
2
  from os import path
3
+ from typing import List
3
4
 
4
5
  import FreeSimpleGUI as _sg
5
6
 
6
7
  from .. import USE_MOCK_SENSOR, __version__
7
- from .._lib.misc import find_calibration_file
8
8
  from .._lib.types import PollingPriority
9
9
  from .._lib.udp_connection import UDPConnection
10
- from ._run import run as _gui_run
10
+ from ._run import run_settings as _gui_run
11
11
  from ._settings import settings
12
12
 
13
13
 
@@ -37,23 +37,25 @@ def _s2l(csv_string, is_integer=False, is_float=False): # convert csv string to
37
37
  return rtn
38
38
 
39
39
 
40
- def _check_sensor_calibration_settings(device_ids, sensor_names,
41
- calibration_folder):
40
+ def _check_sensor_calibration_settings(device_ids: List[int],
41
+ calibrations_files : List[str],
42
+ calibration_folder :str):
42
43
  rtn = []
43
44
  for x, d_id in enumerate(device_ids):
44
45
  error = False
45
46
  try:
46
- sensor_name = sensor_names[x]
47
- except:
48
- sensor_name = "??"
49
- error = True
50
- try:
51
- cal = find_calibration_file(calibration_folder=calibration_folder,
52
- sensor_name=sensor_name)
53
- except:
54
- cal = "NO CALIBRATION"
47
+ cal_file = calibrations_files[x]
48
+ except KeyError:
49
+ cal_file = "??"
55
50
  error = True
56
- rtn.append([d_id, sensor_name, cal, error])
51
+
52
+ if not error:
53
+ cal = path.join(calibration_folder, cal_file)
54
+ if not path.isfile(cal):
55
+ cal = "NOT FOUND"
56
+ error = True
57
+
58
+ rtn.append([d_id, cal_file, cal, error])
57
59
 
58
60
  return rtn
59
61
 
@@ -68,7 +70,7 @@ def _windows_run():
68
70
 
69
71
  for d_id, name, cal, error in _check_sensor_calibration_settings(
70
72
  s.device_ids,
71
- s.sensor_names,
73
+ s.calibration_files,
72
74
  s.calibration_folder):
73
75
  if error:
74
76
  col = "red"
@@ -135,8 +137,8 @@ def _window_settings():
135
137
  [[_sg.Text("Folder:", size=(5, 1)), _sg.InputText(
136
138
  s.calibration_folder, size=(23, 1), key="cal_dir"),
137
139
  _sg.FolderBrowse(size=(6, 1))],
138
- _input_text_list("Sensor Names:", s.sensor_names,
139
- key="sensor_names")
140
+ _input_text_list("Cal-files:", s.calibration_files,
141
+ key="calibration_files")
140
142
  ])])
141
143
 
142
144
  layout.append([_sg.Frame('Record Forces & Torques',
@@ -190,7 +192,7 @@ def _window_settings():
190
192
  except:
191
193
  event = "Error"
192
194
 
193
- key = "sensor_names"
195
+ key = "calibration_files"
194
196
  try:
195
197
  d[key] = _s2l(values[key])
196
198
  except:
@@ -207,7 +209,7 @@ def _window_settings():
207
209
  main_path = path.split(sys.modules['__main__'].__file__)[0] + path.sep
208
210
  d["calibration_folder"] = values["cal_dir"].replace(main_path, "")
209
211
 
210
- settings.set_recoding_setting(d)
212
+ settings.set_recording_setting(d)
211
213
  settings.save()
212
214
 
213
215
  window.close()
@@ -220,9 +222,8 @@ def run():
220
222
  s = settings.recording
221
223
  settings_error = False
222
224
  n_sensor = len(s.device_ids)
223
- if n_sensor != len(s.sensor_names):
224
- _sg.PopupError("Number of devices IDs and sensor names are "
225
- "not equal.")
225
+ if n_sensor != len(s.calibration_files):
226
+ _sg.PopupError("Number of devices IDs and calibration files are not equal.")
226
227
  settings_error = True
227
228
 
228
229
  if not path.isdir(s.calibration_folder):
@@ -1,6 +0,0 @@
1
- from ._run import (
2
- run,
3
- run_with_options, # for running with options from Python script
4
- )
5
- from ._settings import settings ## read settings
6
-
@@ -1,98 +0,0 @@
1
- import collections
2
- import json
3
- import os
4
-
5
- import tomlkit
6
-
7
- _GUISettings = collections.namedtuple('GUISettings', 'sampling_rate '
8
- 'level_detection_parameter window_font moving_average_size '
9
- 'screen_refresh_interval_indicator gui_screen_refresh_interval_plotter '
10
- 'data_min_max plotter_pixel_min_max indicator_pixel_min_max plot_axis '
11
- 'plot_data_indicator_for_single_sensor plot_data_plotter_for_single_sensor '
12
- 'plot_data_indicator_for_two_sensors plot_data_plotter_for_two_sensors')
13
-
14
- _RecordingSetting = collections.namedtuple('RecordingSetting',
15
- 'device_name_prefix device_ids sensor_names remote_control '
16
- 'ask_filename calibration_folder '
17
- ' zip_data write_Fx write_Fy '
18
- 'write_Fz write_Tx write_Ty write_Tz write_trigger1 '
19
- 'write_trigger2 has_lsl_stream reverse_scaling convert_to_forces priority')
20
-
21
-
22
- class GUISettings(object):
23
-
24
- def __init__(self, filename):
25
-
26
- # defaults
27
- self.gui = _GUISettings(
28
- sampling_rate = 1000,
29
- level_detection_parameter = "Fz",
30
- window_font = "freemono",
31
- moving_average_size = 5,
32
- screen_refresh_interval_indicator = 300,
33
- gui_screen_refresh_interval_plotter = 50,
34
- data_min_max = [-5, 30],
35
- plotter_pixel_min_max = [-250, 250],
36
- indicator_pixel_min_max = [-150, 150],
37
- plot_axis = False,
38
- plot_data_indicator_for_single_sensor = [(0, 0), (0, 1), (0, 2), (0, 3),
39
- (0, 4), (0, 5)], # sensor, parameter
40
- plot_data_plotter_for_single_sensor = [(0, 0), (0, 1), (0, 2)],
41
- # plotter can't plot torques # TODO
42
-
43
- plot_data_indicator_for_two_sensors = [(0, 0), (0, 1), (0, 2), (1, 0),
44
- (1, 1), (1, 2)], # sensor,
45
- # parameter
46
- plot_data_plotter_for_two_sensors = [(0, 2),
47
- (1, 2)],
48
- # plotter can't plot torques
49
- )
50
- self.gui_section = "GUI"
51
-
52
- self.recording = _RecordingSetting(device_name_prefix="Dev",
53
- device_ids = [1],
54
- sensor_names = ["FT9334"],
55
- calibration_folder="calibration",
56
- reverse_scaling = {"1": ["Fz"], "2":["Fz"]}, # key: device_id, parameter. E.g.:if x & z dimension of sensor 1 and z dimension of sensor 2 has to be flipped use {1: ["Fx", "Fz"], 2: ["Fz"]}
57
- remote_control=False, ask_filename= False, write_Fx=True,
58
- write_Fy=True, write_Fz=True, write_Tx=False, write_Ty=False,
59
- write_Tz=False, write_trigger1=True, write_trigger2=False,
60
- has_lsl_stream=False,
61
- zip_data=True, convert_to_forces=True,
62
- priority='normal') # default recording settings
63
- self.recording_section = "Recording"
64
-
65
- self.filename = filename
66
- if os.path.isfile(self.filename):
67
- self.load()
68
- else:
69
- self.save() # defaults
70
-
71
- def _asdict(self):
72
- return {self.recording_section: self.recording._asdict(),
73
- self.gui_section: self.gui._asdict()}
74
-
75
- def set_gui_settings(self, gui_setting_dict):
76
- self.gui = _GUISettings(**gui_setting_dict)
77
-
78
- def set_recoding_setting(self, recording_setting_dict):
79
- self.recording = _RecordingSetting(**recording_setting_dict)
80
-
81
- def load(self, filename=None):
82
- if filename is not None:
83
- self.filename = filename
84
- with open(self.filename, 'r') as fl:
85
- d = tomlkit.load(fl)
86
- self.set_gui_settings(d[self.gui_section])
87
- self.set_recoding_setting(d[self.recording_section])
88
-
89
-
90
- def save(self):
91
- with open(self.filename, 'w') as fl:
92
- tomlkit.dump(self._asdict(), fl)
93
-
94
-
95
- def recording_as_json(self):
96
- return json.dumps(self.recording._asdict())
97
-
98
- settings = GUISettings(filename="pyForceDAQ.defaults.settings.toml")