pyForceDAQ 2.0.0__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.
- pyforcedaq/__init__.py +43 -0
- pyforcedaq/__main__.py +42 -0
- pyforcedaq/_lib/__init__.py +1 -0
- pyforcedaq/_lib/lsl.py +56 -0
- pyforcedaq/_lib/misc.py +126 -0
- pyforcedaq/_lib/polling_time_profile.py +52 -0
- pyforcedaq/_lib/process_priority_manager.py +148 -0
- pyforcedaq/_lib/timer.py +45 -0
- pyforcedaq/_lib/types.py +400 -0
- pyforcedaq/_lib/udp_connection.py +326 -0
- pyforcedaq/daq/__init__.py +19 -0
- pyforcedaq/daq/_daq_read_Analog_pydaqmx.py +114 -0
- pyforcedaq/daq/_daq_read_analog_nidaqmx.py +84 -0
- pyforcedaq/daq/_mock_sensor.py +80 -0
- pyforcedaq/daq/_pyATIDAQ.py +306 -0
- pyforcedaq/daq/config.py +13 -0
- pyforcedaq/extras/__init__.py +0 -0
- pyforcedaq/extras/convert.py +275 -0
- pyforcedaq/extras/expyriment_daq_control.py +246 -0
- pyforcedaq/extras/opensesame_daq_control.py +280 -0
- pyforcedaq/extras/read_force_data.py +89 -0
- pyforcedaq/extras/remote_control.py +93 -0
- pyforcedaq/force/__init__.py +13 -0
- pyforcedaq/force/_log.py +18 -0
- pyforcedaq/force/data_recorder.py +400 -0
- pyforcedaq/force/sensor.py +200 -0
- pyforcedaq/force/sensor_process.py +251 -0
- pyforcedaq/gui/__init__.py +6 -0
- pyforcedaq/gui/_gui_status.py +306 -0
- pyforcedaq/gui/_layout.py +104 -0
- pyforcedaq/gui/_level_indicator.py +59 -0
- pyforcedaq/gui/_pg_surface.py +100 -0
- pyforcedaq/gui/_plotter.py +234 -0
- pyforcedaq/gui/_run.py +522 -0
- pyforcedaq/gui/_scaling.py +71 -0
- pyforcedaq/gui/_settings.py +98 -0
- pyforcedaq/gui/forceDAQ_logo.png +0 -0
- pyforcedaq/gui/launcher.py +249 -0
- pyforcedaq-2.0.0.dist-info/METADATA +15 -0
- pyforcedaq-2.0.0.dist-info/RECORD +42 -0
- pyforcedaq-2.0.0.dist-info/WHEEL +4 -0
- pyforcedaq-2.0.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
__author__ = 'Oliver Lindemann'
|
|
2
|
+
|
|
3
|
+
import atexit
|
|
4
|
+
import ctypes as ct
|
|
5
|
+
import logging
|
|
6
|
+
from multiprocessing import Event, Pipe, Process, sharedctypes
|
|
7
|
+
|
|
8
|
+
from .._lib import lsl, timer
|
|
9
|
+
from .._lib.polling_time_profile import PollingTimeProfile
|
|
10
|
+
from .._lib.process_priority_manager import get_priority
|
|
11
|
+
from .._lib.types import DAQEvents
|
|
12
|
+
from .sensor import Sensor, SensorSettings
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SensorProcess(Process):
|
|
16
|
+
|
|
17
|
+
def __init__(self, settings, pipe_buffered_data_after_pause=True,
|
|
18
|
+
chunk_size=10000):
|
|
19
|
+
"""ForceSensorProcess
|
|
20
|
+
|
|
21
|
+
return_buffered_data_after_pause: does not write shared data queue continuously and
|
|
22
|
+
writes it the buffer data to queue only after pause (or stop)
|
|
23
|
+
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# DOC explain usage
|
|
27
|
+
|
|
28
|
+
# type checks
|
|
29
|
+
if not isinstance(settings, SensorSettings):
|
|
30
|
+
raise RuntimeError(
|
|
31
|
+
"settings has to be force_sensor.Settings object")
|
|
32
|
+
|
|
33
|
+
super(SensorProcess, self).__init__()
|
|
34
|
+
self.sensor_settings = settings
|
|
35
|
+
self._pipe_buffer_after_pause = pipe_buffered_data_after_pause
|
|
36
|
+
self._chunk_size = chunk_size
|
|
37
|
+
|
|
38
|
+
self._pipe_i, self._pipe_o = Pipe()
|
|
39
|
+
self._event_is_polling = Event()
|
|
40
|
+
self._event_sending_data = Event()
|
|
41
|
+
self._event_new_data = Event()
|
|
42
|
+
self.event_bias_is_available = Event()
|
|
43
|
+
self.event_trigger = Event() # software trigger
|
|
44
|
+
|
|
45
|
+
self._last_Fx = sharedctypes.RawValue(ct.c_float)
|
|
46
|
+
self._last_Fy = sharedctypes.RawValue(ct.c_float)
|
|
47
|
+
self._last_Fz = sharedctypes.RawValue(ct.c_float)
|
|
48
|
+
self._last_Tx = sharedctypes.RawValue(ct.c_float)
|
|
49
|
+
self._last_Ty = sharedctypes.RawValue(ct.c_float)
|
|
50
|
+
self._last_Tz = sharedctypes.RawValue(ct.c_float)
|
|
51
|
+
self._buffer_size = sharedctypes.RawValue(ct.c_uint64)
|
|
52
|
+
self._sample_cnt = sharedctypes.Value(ct.c_uint64)
|
|
53
|
+
self._event_quit_request = Event()
|
|
54
|
+
self._determine_bias_flag = Event()
|
|
55
|
+
|
|
56
|
+
self._bias_n_samples = 200
|
|
57
|
+
atexit.register(self.join)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def Fx(self):
|
|
61
|
+
return self._last_Fx.value
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def Fy(self):
|
|
65
|
+
return self._last_Fy.value
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def Fz(self):
|
|
69
|
+
return self._last_Fz.value
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def Tx(self):
|
|
73
|
+
return self._last_Tx.value
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def Ty(self):
|
|
77
|
+
return self._last_Ty.value
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def Tz(self):
|
|
81
|
+
return self._last_Tz.value
|
|
82
|
+
|
|
83
|
+
def get_force(self, parameter_id):
|
|
84
|
+
if parameter_id == 0: return self._last_Fx.value
|
|
85
|
+
elif parameter_id == 1: return self._last_Fy.value
|
|
86
|
+
elif parameter_id == 2: return self._last_Fz.value
|
|
87
|
+
elif parameter_id == 3: return self._last_Tx.value
|
|
88
|
+
elif parameter_id == 4: return self._last_Ty.value
|
|
89
|
+
elif parameter_id == 5: return self._last_Tz.value
|
|
90
|
+
else: return None
|
|
91
|
+
|
|
92
|
+
def get_Fxyz(self):
|
|
93
|
+
return (self._last_Fx.value, self._last_Fy.value, self._last_Fz.value)
|
|
94
|
+
|
|
95
|
+
def Txyz(self):
|
|
96
|
+
return (self._last_Tx.value, self._last_Ty.value, self._last_Tz.value)
|
|
97
|
+
|
|
98
|
+
def get_sample_cnt(self):
|
|
99
|
+
return int(self._sample_cnt.value)
|
|
100
|
+
|
|
101
|
+
def get_buffer_size(self):
|
|
102
|
+
return int(self._buffer_size.value)
|
|
103
|
+
|
|
104
|
+
def determine_bias(self, n_samples=100):
|
|
105
|
+
"""recording is paused after bias determination
|
|
106
|
+
|
|
107
|
+
Bias determination is only possible while pause.
|
|
108
|
+
This process might take a while. Please use "wait_bias_available" to
|
|
109
|
+
ensure that the process is finished and the sensor is again read for
|
|
110
|
+
recording.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
if not self._event_is_polling.is_set():
|
|
114
|
+
self._bias_n_samples = n_samples
|
|
115
|
+
self.event_bias_is_available.clear()
|
|
116
|
+
self._determine_bias_flag.set()
|
|
117
|
+
|
|
118
|
+
def start_polling(self):
|
|
119
|
+
self._event_is_polling.set()
|
|
120
|
+
|
|
121
|
+
def pause_polling(self):
|
|
122
|
+
self._event_is_polling.clear()
|
|
123
|
+
|
|
124
|
+
def get_buffer(self, timeout=1.0):
|
|
125
|
+
"""return recorded buffer"""
|
|
126
|
+
rtn = []
|
|
127
|
+
if self._event_sending_data.is_set() or self._buffer_size.value > 0:
|
|
128
|
+
self._event_sending_data.wait()
|
|
129
|
+
while self._buffer_size.value > 0: # wait until buffer is empty
|
|
130
|
+
rtn.extend(self._pipe_i.recv())
|
|
131
|
+
self._event_sending_data.clear() # stop sending
|
|
132
|
+
return rtn
|
|
133
|
+
|
|
134
|
+
def join(self, timeout=None):
|
|
135
|
+
|
|
136
|
+
if self._event_is_polling.is_set():
|
|
137
|
+
self.pause_polling()
|
|
138
|
+
timer.wait(100)
|
|
139
|
+
self.get_buffer() # empty buffer, required to quit process run loop
|
|
140
|
+
|
|
141
|
+
self._event_quit_request.set()
|
|
142
|
+
super(SensorProcess, self).join(timeout)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def run(self):
|
|
146
|
+
buffer = []
|
|
147
|
+
self._buffer_size.value = 0
|
|
148
|
+
sensor = Sensor(self.sensor_settings)
|
|
149
|
+
stream_forces = self.sensor_settings.array_write_forces()
|
|
150
|
+
stream_trigger = self.sensor_settings.array_write_trigger()
|
|
151
|
+
|
|
152
|
+
self._event_is_polling.clear()
|
|
153
|
+
self._event_sending_data.clear()
|
|
154
|
+
is_polling = False
|
|
155
|
+
ptp = PollingTimeProfile() #TODO just for testing?
|
|
156
|
+
|
|
157
|
+
## create init LSL
|
|
158
|
+
lsl_data_steam = None
|
|
159
|
+
lsl_hardware_trigger_stream = None
|
|
160
|
+
if self.sensor_settings.has_lsl_stream:
|
|
161
|
+
lsl_data_steam = lsl.init(
|
|
162
|
+
name=f"Force {self.sensor_settings.device_name}",
|
|
163
|
+
n_channels=sum(stream_forces),
|
|
164
|
+
stream_id=f"RF_{self.sensor_settings.device_name}",
|
|
165
|
+
freq=self.sensor_settings.rate,
|
|
166
|
+
metadata={"sensor_name": self.sensor_settings.sensor_name})
|
|
167
|
+
n_hardware_trigger = sum(stream_trigger)
|
|
168
|
+
if n_hardware_trigger > 0:
|
|
169
|
+
lsl_hardware_trigger_stream = lsl.init(
|
|
170
|
+
name=f"Trigger {self.sensor_settings.device_name}",
|
|
171
|
+
n_channels=n_hardware_trigger,
|
|
172
|
+
stream_id=f"Tr_{self.sensor_settings.device_name}",
|
|
173
|
+
freq=self.sensor_settings.rate)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
while not self._event_quit_request.is_set():
|
|
177
|
+
if self._event_is_polling.is_set():
|
|
178
|
+
# is polling
|
|
179
|
+
if not is_polling:
|
|
180
|
+
# start NI device and acquire one first sample to
|
|
181
|
+
# ensure good timing
|
|
182
|
+
sensor.start_data_acquisition()
|
|
183
|
+
buffer.append(DAQEvents(time=sensor.timer.time,
|
|
184
|
+
code="started:"+repr(sensor.device_id)))
|
|
185
|
+
logging.info("Sensor start, name %s, pid %s, priority %s",
|
|
186
|
+
sensor.name,self.pid, get_priority(self.pid))
|
|
187
|
+
is_polling = True
|
|
188
|
+
|
|
189
|
+
d = sensor.poll_data()
|
|
190
|
+
## LSL
|
|
191
|
+
if lsl_data_steam is not None:
|
|
192
|
+
lsl_data_steam.push_sample(list(d.selected_forces(stream_forces))) # steam only select forces
|
|
193
|
+
if lsl_hardware_trigger_stream is not None:
|
|
194
|
+
lsl_hardware_trigger_stream.push_sample(list(d.selected_trigger(stream_trigger))) # stream only triggers
|
|
195
|
+
|
|
196
|
+
ptp.update(d.time) # needed? TODO
|
|
197
|
+
self._last_Fx.value, self._last_Fy.value, self._last_Fz.value, \
|
|
198
|
+
self._last_Tx.value, self._last_Ty.value, \
|
|
199
|
+
self._last_Tz.value = d.forces
|
|
200
|
+
self._sample_cnt.value += 1
|
|
201
|
+
|
|
202
|
+
if self.event_trigger.is_set():
|
|
203
|
+
self.event_trigger.clear()
|
|
204
|
+
d.trigger[0] = 1
|
|
205
|
+
|
|
206
|
+
buffer.append(d)
|
|
207
|
+
self._buffer_size.value = len(buffer)
|
|
208
|
+
|
|
209
|
+
else:
|
|
210
|
+
# pause: not polling
|
|
211
|
+
if is_polling:
|
|
212
|
+
sensor.stop_data_acquisition()
|
|
213
|
+
buffer.append(DAQEvents(time=sensor.timer.time,
|
|
214
|
+
code="pause:"+repr(sensor.device_id)))
|
|
215
|
+
self._buffer_size.value = len(buffer)
|
|
216
|
+
logging.info(
|
|
217
|
+
"Sensor stop, name %s, pid %s, priority %s",
|
|
218
|
+
sensor.name,
|
|
219
|
+
self.pid,
|
|
220
|
+
get_priority(self.pid),
|
|
221
|
+
)
|
|
222
|
+
is_polling = False
|
|
223
|
+
ptp.stop()
|
|
224
|
+
|
|
225
|
+
if self._pipe_buffer_after_pause and self._buffer_size.value>0:
|
|
226
|
+
# sending data to force
|
|
227
|
+
self._event_sending_data.set()
|
|
228
|
+
chks = self._chunk_size
|
|
229
|
+
while len(buffer)>0:
|
|
230
|
+
if chks > len(buffer):
|
|
231
|
+
chks = len(buffer)
|
|
232
|
+
self._pipe_o.send(buffer[0:chks])
|
|
233
|
+
buffer[0:chks] = [] # clear buffer
|
|
234
|
+
self._buffer_size.value = len(buffer)
|
|
235
|
+
|
|
236
|
+
while self._event_sending_data.is_set():
|
|
237
|
+
timer.wait(2)
|
|
238
|
+
|
|
239
|
+
if self._determine_bias_flag.is_set():
|
|
240
|
+
sensor.determine_bias(n_samples=self._bias_n_samples)
|
|
241
|
+
self._determine_bias_flag.clear()
|
|
242
|
+
self.event_bias_is_available.set()
|
|
243
|
+
|
|
244
|
+
self._event_is_polling.wait(timeout=0.1)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# stop process
|
|
248
|
+
sensor.stop_data_acquisition()
|
|
249
|
+
self._buffer_size.value = 0
|
|
250
|
+
|
|
251
|
+
logging.info("Sensor quit, %s, %s", sensor.name, ptp.get_profile_str())
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
__author__ = "Oliver Lindemann"
|
|
2
|
+
|
|
3
|
+
from pickle import dumps, loads
|
|
4
|
+
|
|
5
|
+
from expyriment import io, misc
|
|
6
|
+
|
|
7
|
+
from .. import __version__ as forceDAQVersion
|
|
8
|
+
from .._lib.misc import SensorHistory
|
|
9
|
+
from .._lib.types import ForceSensorData, Thresholds
|
|
10
|
+
from .._lib.types import GUIRemoteControlCommands as RcCmd
|
|
11
|
+
from ..force.sensor_process import SensorProcess
|
|
12
|
+
from ._layout import RecordingScreen, logo_text_line
|
|
13
|
+
from ._scaling import Scaling
|
|
14
|
+
from ._settings import settings
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _text2number_array(txt):
|
|
18
|
+
"""helper function"""
|
|
19
|
+
rtn = []
|
|
20
|
+
try:
|
|
21
|
+
for x in txt.split(","):
|
|
22
|
+
rtn.append(float(x))
|
|
23
|
+
return rtn
|
|
24
|
+
except:
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
class GUIStatus(object):
|
|
28
|
+
|
|
29
|
+
def __init__(self,
|
|
30
|
+
screen_refresh_interval_indicator,
|
|
31
|
+
screen_refresh_interval_plotter,
|
|
32
|
+
recorder,
|
|
33
|
+
remote_control,
|
|
34
|
+
level_detection_parameter,
|
|
35
|
+
data_min_max,
|
|
36
|
+
plotter_pixel_min_max,
|
|
37
|
+
indicator_pixel_min_max,
|
|
38
|
+
screen_size,
|
|
39
|
+
plot_axis):
|
|
40
|
+
|
|
41
|
+
self.screen_refresh_interval_indicator = screen_refresh_interval_indicator
|
|
42
|
+
self.screen_refresh_interval_plotter = screen_refresh_interval_plotter
|
|
43
|
+
self.plot_axis = plot_axis
|
|
44
|
+
self.recorder = recorder
|
|
45
|
+
self.remote_control = remote_control
|
|
46
|
+
self.level_detection_parameter = level_detection_parameter
|
|
47
|
+
|
|
48
|
+
self.background = RecordingScreen(window_size = screen_size,
|
|
49
|
+
filename=recorder.filename,
|
|
50
|
+
remote_control=remote_control)
|
|
51
|
+
self.scaling_plotter = Scaling(min=data_min_max[0], max= data_min_max[1],
|
|
52
|
+
pixel_min=plotter_pixel_min_max[0],
|
|
53
|
+
pixel_max=plotter_pixel_min_max[1])
|
|
54
|
+
self.scaling_indicator = Scaling(min=data_min_max[0], max= data_min_max[1],
|
|
55
|
+
pixel_min = indicator_pixel_min_max[0],
|
|
56
|
+
pixel_max = indicator_pixel_min_max[1])
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
self.sensor_processes = recorder.force_sensor_processes
|
|
60
|
+
self.n_sensors = len(self.sensor_processes)
|
|
61
|
+
self.history = []
|
|
62
|
+
for _ in range(self.n_sensors):
|
|
63
|
+
self.history.append( SensorHistory(history_size = settings.gui.moving_average_size,
|
|
64
|
+
number_of_parameter= 3) )
|
|
65
|
+
|
|
66
|
+
self._start_recording_time = 0
|
|
67
|
+
self.pause_recording = True
|
|
68
|
+
self.quit_recording = False
|
|
69
|
+
self.clear_screen = True
|
|
70
|
+
self.thresholds = None
|
|
71
|
+
self.set_marker = False
|
|
72
|
+
self.last_udp_data = None
|
|
73
|
+
self._last_processed_smpl = [0] * self.n_sensors
|
|
74
|
+
self._last_recording_status = None
|
|
75
|
+
self._last_thresholds = None
|
|
76
|
+
self._clock = misc.Clock()
|
|
77
|
+
|
|
78
|
+
self.sensor_info_str = ""
|
|
79
|
+
for tmp in self.recorder.sensor_settings_list:
|
|
80
|
+
self.sensor_info_str = self.sensor_info_str + \
|
|
81
|
+
"{0}: {1}\n".format(tmp.device_name, tmp.sensor_name)
|
|
82
|
+
self.sensor_info_str = self.sensor_info_str.strip()
|
|
83
|
+
self.plot_indicator = True
|
|
84
|
+
self.plot_filtered = False
|
|
85
|
+
if self.n_sensors == 1:
|
|
86
|
+
self.plot_data_indicator = settings.gui.plot_data_indicator_for_single_sensor
|
|
87
|
+
self.plot_data_plotter = settings.gui.plot_data_plotter_for_single_sensor
|
|
88
|
+
else:
|
|
89
|
+
self.plot_data_indicator = settings.gui.plot_data_indicator_for_two_sensors
|
|
90
|
+
self.plot_data_plotter = settings.gui.plot_data_plotter_for_two_sensors
|
|
91
|
+
# plot data parameter names
|
|
92
|
+
self.plot_data_indicator_names = []
|
|
93
|
+
for x in self.plot_data_indicator:
|
|
94
|
+
self.plot_data_indicator_names.append(self.recorder.sensor_settings_list[x[0]].device_name +\
|
|
95
|
+
"_" + ForceSensorData.forces_names[ x[1]])
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
self.plot_data_plotter_names = []
|
|
99
|
+
for x in self.plot_data_plotter:
|
|
100
|
+
self.plot_data_plotter_names.append(str(x[0]) + "_" + ForceSensorData.forces_names[ x[1]])
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def set_start_recording_time(self):
|
|
104
|
+
self._start_recording_time = self._clock.time
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def recording_duration_in_sec(self):
|
|
108
|
+
return (self._clock.time - self._start_recording_time) / 1000
|
|
109
|
+
|
|
110
|
+
def check_refresh_required(self):
|
|
111
|
+
"""also resets clock"""
|
|
112
|
+
if self.plot_indicator:
|
|
113
|
+
intervall = self.screen_refresh_interval_indicator
|
|
114
|
+
else:
|
|
115
|
+
intervall = self.screen_refresh_interval_plotter
|
|
116
|
+
|
|
117
|
+
if not self.pause_recording and self._clock.stopwatch_time >= intervall:
|
|
118
|
+
self._clock.reset_stopwatch()
|
|
119
|
+
return True
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
def check_recording_status_change(self):
|
|
123
|
+
"""returns only onces true if not changed between calls"""
|
|
124
|
+
if self.pause_recording != self._last_recording_status:
|
|
125
|
+
self._last_recording_status = self.pause_recording
|
|
126
|
+
return True
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def check_new_samples(self):
|
|
130
|
+
"""returns list of sensors with new samples"""
|
|
131
|
+
rtn = []
|
|
132
|
+
for i, cnt in enumerate(map(SensorProcess.get_sample_cnt, self.sensor_processes)):
|
|
133
|
+
if self._last_processed_smpl[i] < cnt:
|
|
134
|
+
# new sample
|
|
135
|
+
self._last_processed_smpl[i] = cnt
|
|
136
|
+
rtn.append(i)
|
|
137
|
+
return rtn
|
|
138
|
+
|
|
139
|
+
def check_thresholds_changed(self):
|
|
140
|
+
"""returns only true if not changed between calls"""
|
|
141
|
+
if self.thresholds != self._last_thresholds:
|
|
142
|
+
# new sample
|
|
143
|
+
self._last_thresholds = self.thresholds
|
|
144
|
+
return True
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
def process_key(self, key):
|
|
148
|
+
if key == misc.constants.K_q or key == misc.constants.K_ESCAPE:
|
|
149
|
+
self.quit_recording = True
|
|
150
|
+
elif key == misc.constants.K_v:
|
|
151
|
+
self.plot_indicator = not self.plot_indicator
|
|
152
|
+
self.background.stimulus().present()
|
|
153
|
+
elif key == misc.constants.K_p:
|
|
154
|
+
# pause
|
|
155
|
+
self.pause_recording = not self.pause_recording
|
|
156
|
+
elif key == misc.constants.K_b and self.pause_recording:
|
|
157
|
+
self.background.stimulus("Recording baseline").present()
|
|
158
|
+
self.recorder.determine_biases(n_samples=500)
|
|
159
|
+
self.background.stimulus("Paused").present()
|
|
160
|
+
|
|
161
|
+
elif key == misc.constants.K_KP_MINUS:
|
|
162
|
+
self.scaling_plotter.increase_data_range()
|
|
163
|
+
self.scaling_indicator.increase_data_range()
|
|
164
|
+
self.background.stimulus().present()
|
|
165
|
+
self.clear_screen = True
|
|
166
|
+
elif key == misc.constants.K_KP_PLUS:
|
|
167
|
+
self.scaling_plotter.decrease_data_range()
|
|
168
|
+
self.scaling_indicator.decrease_data_range()
|
|
169
|
+
self.background.stimulus().present()
|
|
170
|
+
self.clear_screen = True
|
|
171
|
+
elif key == misc.constants.K_UP:
|
|
172
|
+
self.scaling_plotter.data_range_up()
|
|
173
|
+
self.scaling_indicator.data_range_up()
|
|
174
|
+
self.background.stimulus().present()
|
|
175
|
+
self.clear_screen = True
|
|
176
|
+
elif key == misc.constants.K_DOWN:
|
|
177
|
+
self.scaling_plotter.data_range_down()
|
|
178
|
+
self.scaling_indicator.data_range_down()
|
|
179
|
+
self.background.stimulus().present()
|
|
180
|
+
self.clear_screen = True
|
|
181
|
+
elif key == misc.constants.K_f:
|
|
182
|
+
self.plot_filtered = not self.plot_filtered
|
|
183
|
+
|
|
184
|
+
elif key == misc.constants.K_t:
|
|
185
|
+
tmp = _text2number_array(
|
|
186
|
+
io.TextInput("Enter thresholds",
|
|
187
|
+
background_stimulus=logo_text_line("")).get())
|
|
188
|
+
self.background.stimulus().present()
|
|
189
|
+
if tmp is not None:
|
|
190
|
+
self.thresholds = Thresholds(tmp, n_channels=self.n_sensors)
|
|
191
|
+
else:
|
|
192
|
+
self.thresholds = None
|
|
193
|
+
|
|
194
|
+
def process_udp_event(self, udp_event):
|
|
195
|
+
"""remote control
|
|
196
|
+
|
|
197
|
+
See commands in pyforceDAQ.types.GUIRemoteControlCommands
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
if self.remote_control and udp_event.is_remote_control_command:
|
|
201
|
+
if udp_event.byte_string == RcCmd.START:
|
|
202
|
+
self.pause_recording = False
|
|
203
|
+
elif udp_event.byte_string == RcCmd.PAUSE:
|
|
204
|
+
self.pause_recording = True
|
|
205
|
+
elif udp_event.byte_string == RcCmd.QUIT:
|
|
206
|
+
self.quit_recording = True
|
|
207
|
+
|
|
208
|
+
elif udp_event.startswith(RcCmd.SET_THRESHOLDS): # thresholds
|
|
209
|
+
try:
|
|
210
|
+
self.thresholds = loads(
|
|
211
|
+
udp_event.byte_string[len(RcCmd.SET_THRESHOLDS):])
|
|
212
|
+
if not isinstance(self.thresholds, Thresholds): # ensure not strange types
|
|
213
|
+
self.thresholds = None
|
|
214
|
+
else:
|
|
215
|
+
self.thresholds.set_number_of_channels(self.n_sensors)
|
|
216
|
+
except:
|
|
217
|
+
self.thresholds = None
|
|
218
|
+
|
|
219
|
+
elif udp_event.startswith(RcCmd.GET_THRESHOLD_LEVEL) or \
|
|
220
|
+
udp_event.startswith(RcCmd.GET_THRESHOLD_LEVEL2):
|
|
221
|
+
if self.thresholds is not None:
|
|
222
|
+
s = int(udp_event.startswith(RcCmd.GET_THRESHOLD_LEVEL2))
|
|
223
|
+
tmp = self.thresholds.get_level(self.level_detection_parameter_average(s))
|
|
224
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE + dumps(tmp))
|
|
225
|
+
else:
|
|
226
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE + dumps(None))
|
|
227
|
+
elif udp_event.startswith(RcCmd.SET_LEVEL_CHANGE_DETECTION) or \
|
|
228
|
+
udp_event.startswith(RcCmd.SET_LEVEL_CHANGE_DETECTION2):
|
|
229
|
+
if self.thresholds is not None:
|
|
230
|
+
s = int(udp_event.startswith(RcCmd.SET_LEVEL_CHANGE_DETECTION2))
|
|
231
|
+
self.thresholds.set_level_change_detection(self.level_detection_parameter_average(s),
|
|
232
|
+
channel=s)
|
|
233
|
+
|
|
234
|
+
elif udp_event.startswith(RcCmd.SET_RESPONSE_MINMAX_DETECTION) or \
|
|
235
|
+
udp_event.startswith(RcCmd.SET_RESPONSE_MINMAX_DETECTION2):
|
|
236
|
+
try:
|
|
237
|
+
duration = int(loads(
|
|
238
|
+
udp_event.byte_string[len(RcCmd.SET_RESPONSE_MINMAX_DETECTION):]))
|
|
239
|
+
except:
|
|
240
|
+
duration = None
|
|
241
|
+
|
|
242
|
+
s = int(udp_event.startswith(RcCmd.SET_LEVEL_CHANGE_DETECTION2))
|
|
243
|
+
if self.thresholds is not None and duration is not None:
|
|
244
|
+
self.thresholds.set_response_minmax_detection(
|
|
245
|
+
value = self.level_detection_parameter_average(s), duration = duration,
|
|
246
|
+
channel=s)
|
|
247
|
+
|
|
248
|
+
elif udp_event.byte_string == RcCmd.GET_VERSION:
|
|
249
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
250
|
+
dumps(forceDAQVersion))
|
|
251
|
+
elif udp_event.byte_string == RcCmd.PING:
|
|
252
|
+
self.recorder.udp.send_queue.put(RcCmd.PING)
|
|
253
|
+
elif udp_event.byte_string == RcCmd.GET_FX1:
|
|
254
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
255
|
+
dumps(self.sensor_processes[0].Fx))
|
|
256
|
+
elif udp_event.byte_string == RcCmd.GET_FY1:
|
|
257
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
258
|
+
dumps(self.sensor_processes[0].Fy))
|
|
259
|
+
elif udp_event.byte_string == RcCmd.GET_FZ1:
|
|
260
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
261
|
+
dumps(self.sensor_processes[0].Fz))
|
|
262
|
+
elif udp_event.byte_string == RcCmd.GET_TX1:
|
|
263
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
264
|
+
dumps(self.sensor_processes[0].Fx))
|
|
265
|
+
elif udp_event.byte_string == RcCmd.GET_TY1:
|
|
266
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
267
|
+
dumps(self.sensor_processes[0].Fy))
|
|
268
|
+
elif udp_event.byte_string == RcCmd.GET_TZ1:
|
|
269
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
270
|
+
dumps(self.sensor_processes[0].Fz))
|
|
271
|
+
elif self.n_sensors > 1:
|
|
272
|
+
if udp_event.byte_string == RcCmd.GET_FX2:
|
|
273
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
274
|
+
dumps(self.sensor_processes[1].Fx))
|
|
275
|
+
elif udp_event.byte_string == RcCmd.GET_FY2:
|
|
276
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
277
|
+
dumps(self.sensor_processes[1].Fy))
|
|
278
|
+
elif udp_event.byte_string == RcCmd.GET_FZ2:
|
|
279
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
280
|
+
dumps(self.sensor_processes[1].Fz))
|
|
281
|
+
elif udp_event.byte_string == RcCmd.GET_TX2:
|
|
282
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
283
|
+
dumps(self.sensor_processes[1].Fx))
|
|
284
|
+
elif udp_event.byte_string == RcCmd.GET_TY2:
|
|
285
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
286
|
+
dumps(self.sensor_processes[1].Fy))
|
|
287
|
+
elif udp_event.byte_string == RcCmd.GET_TZ2:
|
|
288
|
+
self.recorder.udp.send_queue.put(RcCmd.VALUE +
|
|
289
|
+
dumps(self.sensor_processes[1].Fz))
|
|
290
|
+
else:
|
|
291
|
+
# not remote control command
|
|
292
|
+
self.set_marker = True
|
|
293
|
+
self.last_udp_data = udp_event.byte_string
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def update_history(self, sensor):
|
|
297
|
+
self.history[sensor].update(self.sensor_processes[sensor].get_Fxyz())
|
|
298
|
+
|
|
299
|
+
def level_detection_parameter_average(self, sensor):
|
|
300
|
+
"""just a short cut"""
|
|
301
|
+
if sensor < self.n_sensors:
|
|
302
|
+
return self.history[sensor].moving_average[self.level_detection_parameter]
|
|
303
|
+
else:
|
|
304
|
+
return None
|
|
305
|
+
|
|
306
|
+
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
__author__ = 'Oliver Lindemann'
|
|
2
|
+
|
|
3
|
+
# helper functions
|
|
4
|
+
import os
|
|
5
|
+
from time import strftime
|
|
6
|
+
import pygame
|
|
7
|
+
|
|
8
|
+
from expyriment import stimuli
|
|
9
|
+
from expyriment.misc import constants
|
|
10
|
+
|
|
11
|
+
from .. import __version__ as forceDAQVersion
|
|
12
|
+
|
|
13
|
+
colours = [constants.C_RED,
|
|
14
|
+
constants.C_GREEN,
|
|
15
|
+
constants.C_YELLOW,
|
|
16
|
+
constants.C_BLUE,
|
|
17
|
+
constants.C_EXPYRIMENT_ORANGE,
|
|
18
|
+
constants.C_EXPYRIMENT_PURPLE]
|
|
19
|
+
|
|
20
|
+
def get_pygame_rect(stimulus, screen_size):
|
|
21
|
+
"""little helper function that returns the pygame rect from stimuli"""
|
|
22
|
+
half_screen_size = (screen_size[0] / 2, screen_size[1] / 2)
|
|
23
|
+
pos = stimulus.absolute_position
|
|
24
|
+
stim_size = stimulus.surface_size
|
|
25
|
+
rect_pos = (pos[0] + half_screen_size[0] - stim_size[0] / 2,
|
|
26
|
+
- pos[1] + half_screen_size[1] - stim_size[1] / 2)
|
|
27
|
+
return pygame.Rect(rect_pos, stim_size)
|
|
28
|
+
|
|
29
|
+
def logo_text_line(text):
|
|
30
|
+
blank = stimuli.Canvas(size=(600, 400))
|
|
31
|
+
logo = stimuli.Picture(filename=os.path.join(os.path.dirname(__file__),
|
|
32
|
+
"forceDAQ_logo.png"), position = (0, 150))
|
|
33
|
+
logo.scale(0.6)
|
|
34
|
+
stimuli.TextLine(text="Version " + forceDAQVersion, position=(0,80),
|
|
35
|
+
text_size = 14,
|
|
36
|
+
text_colour=constants.C_EXPYRIMENT_ORANGE).plot(blank)
|
|
37
|
+
logo.plot(blank)
|
|
38
|
+
stimuli.TextLine(text=text).plot(blank)
|
|
39
|
+
return blank
|
|
40
|
+
|
|
41
|
+
class RecordingScreen(object):
|
|
42
|
+
def __init__(self, window_size, filename, remote_control):
|
|
43
|
+
"""Expyriment has to be intialized"""
|
|
44
|
+
margin = 30
|
|
45
|
+
self.left = -1*window_size[0]/2 + margin
|
|
46
|
+
self.right = window_size[0]/2 - margin
|
|
47
|
+
self.top = window_size[1]/2 - margin
|
|
48
|
+
self.bottom = -1*window_size[1]/2 + margin
|
|
49
|
+
|
|
50
|
+
self.elements = []
|
|
51
|
+
self.add_text_line_left("Force Recorder " + str(forceDAQVersion),
|
|
52
|
+
[self.left, self.top], text_size=15)
|
|
53
|
+
self.add_text_line_left("(p) pause/unpause", [self.left, self.bottom])
|
|
54
|
+
self.add_text_line_left("(v) switch view", [self.left + 160, self.bottom +20])
|
|
55
|
+
self.add_text_line_left("(f) switch filtered", [self.left + 160, self.bottom])
|
|
56
|
+
self.add_text_line_left("(+/-): axes scaling", [self.left + 340, self.bottom + 20])
|
|
57
|
+
self.add_text_line_left("(up/down): axes shift", [self.left + 340, self.bottom])
|
|
58
|
+
self.add_text_line_left("(t): change thresholds", [self.left + 560, self.bottom])
|
|
59
|
+
self.add_text_line_right("(q) quit recording", [self.right, self.bottom ])
|
|
60
|
+
self.add_text_line_centered("file: " + filename, [0, self.top], text_size=15)
|
|
61
|
+
if remote_control:
|
|
62
|
+
self.add_text_line_centered("REMOTE CONTROL", [0, self.top-20], text_size=15)
|
|
63
|
+
self.add_text_line_right("date: {0}".format(strftime("%d/%m/%Y")),
|
|
64
|
+
[self.right, self.top], text_size=15)
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def _text_line(text, position, text_size=12, text_colour=(255, 150, 50)):
|
|
68
|
+
"""helper function"""
|
|
69
|
+
return stimuli.TextLine(text, position=position,
|
|
70
|
+
text_size=text_size,
|
|
71
|
+
text_colour=text_colour)
|
|
72
|
+
|
|
73
|
+
def add_text_line_centered(self, text, position, text_size=12,
|
|
74
|
+
text_colour=(255, 150, 50)):
|
|
75
|
+
self.elements.append(RecordingScreen._text_line(text, position,
|
|
76
|
+
text_size,
|
|
77
|
+
text_colour))
|
|
78
|
+
|
|
79
|
+
def add_text_line_right(self, text, position, text_size=12,
|
|
80
|
+
text_colour=(255, 150, 50)):
|
|
81
|
+
"""text_line right aligned"""
|
|
82
|
+
txt = RecordingScreen._text_line(text, position, text_size,
|
|
83
|
+
text_colour)
|
|
84
|
+
txt.move((-1 * (txt.surface_size[0] / 2), 0))
|
|
85
|
+
self.elements.append(txt)
|
|
86
|
+
|
|
87
|
+
def add_text_line_left(self, text, position, text_size=12,
|
|
88
|
+
text_colour=(255, 150, 50)):
|
|
89
|
+
"""text line left aligned"""
|
|
90
|
+
txt = RecordingScreen._text_line(text, position, text_size,
|
|
91
|
+
text_colour)
|
|
92
|
+
txt.move((txt.surface_size[0] / 2, 0))
|
|
93
|
+
self.elements.append(txt)
|
|
94
|
+
|
|
95
|
+
def stimulus(self, infotext=""):
|
|
96
|
+
"""Return the stimulus including infotext (obligatory)"""
|
|
97
|
+
canvas = stimuli.BlankScreen()
|
|
98
|
+
for elem in self.elements:
|
|
99
|
+
elem.plot(canvas)
|
|
100
|
+
if len(infotext) > 0:
|
|
101
|
+
RecordingScreen._text_line(text=infotext, position=[0, 0],
|
|
102
|
+
text_size=36).plot(canvas)
|
|
103
|
+
return canvas
|
|
104
|
+
|