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.
Files changed (42) hide show
  1. pyforcedaq/__init__.py +43 -0
  2. pyforcedaq/__main__.py +42 -0
  3. pyforcedaq/_lib/__init__.py +1 -0
  4. pyforcedaq/_lib/lsl.py +56 -0
  5. pyforcedaq/_lib/misc.py +126 -0
  6. pyforcedaq/_lib/polling_time_profile.py +52 -0
  7. pyforcedaq/_lib/process_priority_manager.py +148 -0
  8. pyforcedaq/_lib/timer.py +45 -0
  9. pyforcedaq/_lib/types.py +400 -0
  10. pyforcedaq/_lib/udp_connection.py +326 -0
  11. pyforcedaq/daq/__init__.py +19 -0
  12. pyforcedaq/daq/_daq_read_Analog_pydaqmx.py +114 -0
  13. pyforcedaq/daq/_daq_read_analog_nidaqmx.py +84 -0
  14. pyforcedaq/daq/_mock_sensor.py +80 -0
  15. pyforcedaq/daq/_pyATIDAQ.py +306 -0
  16. pyforcedaq/daq/config.py +13 -0
  17. pyforcedaq/extras/__init__.py +0 -0
  18. pyforcedaq/extras/convert.py +275 -0
  19. pyforcedaq/extras/expyriment_daq_control.py +246 -0
  20. pyforcedaq/extras/opensesame_daq_control.py +280 -0
  21. pyforcedaq/extras/read_force_data.py +89 -0
  22. pyforcedaq/extras/remote_control.py +93 -0
  23. pyforcedaq/force/__init__.py +13 -0
  24. pyforcedaq/force/_log.py +18 -0
  25. pyforcedaq/force/data_recorder.py +400 -0
  26. pyforcedaq/force/sensor.py +200 -0
  27. pyforcedaq/force/sensor_process.py +251 -0
  28. pyforcedaq/gui/__init__.py +6 -0
  29. pyforcedaq/gui/_gui_status.py +306 -0
  30. pyforcedaq/gui/_layout.py +104 -0
  31. pyforcedaq/gui/_level_indicator.py +59 -0
  32. pyforcedaq/gui/_pg_surface.py +100 -0
  33. pyforcedaq/gui/_plotter.py +234 -0
  34. pyforcedaq/gui/_run.py +522 -0
  35. pyforcedaq/gui/_scaling.py +71 -0
  36. pyforcedaq/gui/_settings.py +98 -0
  37. pyforcedaq/gui/forceDAQ_logo.png +0 -0
  38. pyforcedaq/gui/launcher.py +249 -0
  39. pyforcedaq-2.0.0.dist-info/METADATA +15 -0
  40. pyforcedaq-2.0.0.dist-info/RECORD +42 -0
  41. pyforcedaq-2.0.0.dist-info/WHEEL +4 -0
  42. 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,6 @@
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
+
@@ -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
+