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,400 @@
1
+ __author__ = 'Oliver Lindemann'
2
+
3
+ import ctypes as ct
4
+ from typing import List
5
+
6
+ from .misc import MinMaxDetector as _MinMaxDetector
7
+
8
+ # tag in data output
9
+ TAG_COMMENTS = "#"
10
+ TAG_DAQEVENT = TAG_COMMENTS + "T"
11
+ TAG_UDPDATA = TAG_COMMENTS + "UDP"
12
+
13
+ CTYPE_FORCES = ct.c_float * 600
14
+ CTYPE_TRIGGER = ct.c_float * 2
15
+
16
+ class PollingPriority(object):
17
+
18
+ NORMAL = 'normal'
19
+ HIGH = 'high'
20
+ REALTIME = 'real_time'
21
+
22
+ @staticmethod
23
+ def get_priority(priority_str):
24
+ """returns normal or the higher priority if detected """
25
+ if isinstance(priority_str, str):
26
+ if priority_str.find("real") >= 0 and \
27
+ priority_str.find("time") >= 0:
28
+ return PollingPriority.REALTIME
29
+ elif priority_str.startswith("high"):
30
+ return PollingPriority.HIGH
31
+
32
+ return PollingPriority.NORMAL
33
+
34
+
35
+ class CTypesForceSensorData(ct.Structure):
36
+ _fields_ = [("device_id", ct.c_int),
37
+ ("time", ct.c_int),
38
+ ("forces", CTYPE_FORCES),
39
+ ("trigger", CTYPE_TRIGGER)]
40
+
41
+
42
+ class ForceSensorData(object):
43
+ """The Force data structure with the following properties
44
+ * device_id
45
+ * time (time stamp)
46
+ * aquisition delay (time it took to receive the new data)
47
+ * Fx, Fy, & Fz
48
+ * Tx, Ty, & Tz
49
+ * trigger1 & trigger2
50
+
51
+ """
52
+
53
+ forces_names = ["Fx", "Fy", "Fz", "Tx", "Ty", "Tz"]
54
+
55
+ def __init__(self, time=0, acquisition_delay = -1,
56
+ forces= [0] * 6, trigger=(0, 0),
57
+ device_id=0, trigger_threshold=0.9, reverse=()):
58
+ """Create a ForceSensorData object
59
+ Parameters
60
+ ----------
61
+ device_id: int, optional
62
+ the id of the sensor device
63
+ time: int, optional
64
+ the timestamp
65
+ acquisition_delay: int, optional
66
+ time
67
+ forces: array of six floats
68
+ array of the force data defined as [Fx, Fy, Fz, Tx, Ty, Tz]
69
+ trigger: array of two floats
70
+ two trigger values: [trigger1, trigger2]
71
+
72
+ trigger_threshold: float (default = 0.4)
73
+ if abs(trigger1/2) < trigger_threshold the threshold it will considered as noise
74
+ and set to zero
75
+
76
+ """
77
+
78
+ self.time = time
79
+ self.acquisition_delay = acquisition_delay
80
+ self.device_id = device_id
81
+ self.forces = forces
82
+ self.trigger = list(trigger)
83
+ if abs(self.trigger[0]) < trigger_threshold:
84
+ self.trigger[0] = 0
85
+ if abs(self.trigger[1]) < trigger_threshold:
86
+ self.trigger[1] = 0
87
+ for r in reverse:
88
+ forces[r] = -1*forces[r]
89
+
90
+ def __str__(self):
91
+ """converts data to string. """
92
+ txt = "%d,%d,%.4f,%.4f,%.4f,%.4f,%.4f,%.4f" % (self.device_id,
93
+ self.time,
94
+ self.forces[0],
95
+ self.forces[1],
96
+ self.forces[2],
97
+ self.forces[3],
98
+ self.forces[4],
99
+ self.forces[5])
100
+ txt += ",%.4f,%.4f" % (self.trigger[0], self.trigger[1])
101
+ return txt
102
+
103
+ @property
104
+ def Fx(self):
105
+ return self.forces[0]
106
+
107
+ @Fx.setter
108
+ def Fx(self, value):
109
+ self.forces[0] = value
110
+
111
+ @property
112
+ def Fy(self):
113
+ return self.forces[1]
114
+
115
+ @Fy.setter
116
+ def Fy(self, value):
117
+ self.forces[1] = value
118
+
119
+ @property
120
+ def Fz(self):
121
+ return self.forces[2]
122
+
123
+ @Fz.setter
124
+ def Fz(self, value):
125
+ self.forces[2] = value
126
+
127
+ @property
128
+ def Tx(self):
129
+ return self.forces[3]
130
+
131
+ @Tx.setter
132
+ def Tx(self, value):
133
+ self.forces[3] = value
134
+
135
+ @property
136
+ def Ty(self):
137
+ return self.forces[4]
138
+
139
+ @Ty.setter
140
+ def Ty(self, value):
141
+ self.forces[4] = value
142
+
143
+ @property
144
+ def Tz(self):
145
+ return self.forces[5]
146
+
147
+ @Tz.setter
148
+ def Tz(self, value):
149
+ self.forces[5] = value
150
+
151
+ @property
152
+ def ctypes_struct(self):
153
+ return CTypesForceSensorData(self.device_id, self.time,
154
+ CTYPE_FORCES(*self.forces), CTYPE_TRIGGER(*self.trigger))
155
+
156
+ @ctypes_struct.setter
157
+ def ctypes_struct(self, struct):
158
+ self.device_id = struct.device_id
159
+ self.time = struct.time
160
+ self.force = struct.forces
161
+ self.trigger = struct.trigger
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])
166
+
167
+ def selected_trigger(self, select: List[bool]):
168
+ return (trigger for i, trigger in enumerate(self.trigger) if select[i])
169
+
170
+
171
+ class UDPData(object):
172
+ """The UDP data class, used to store UDP DATA with timestamps
173
+
174
+ """
175
+
176
+ def __init__(self, string, time):
177
+ """Create a UDA_DATA object
178
+
179
+ Parameters
180
+ ----------
181
+ time : int
182
+ code : numerical or string
183
+
184
+ """
185
+ self.time = time
186
+ if isinstance(string, str):
187
+ self.byte_string = string.encode()
188
+ else:
189
+ self.byte_string = string
190
+
191
+ @property
192
+ def unicode(self):
193
+ return self.byte_string.decode('utf-8', 'replace')
194
+
195
+ @property
196
+ def is_remote_control_command(self):
197
+ return self.startswith(GUIRemoteControlCommands.COMMAND_STR)
198
+
199
+ def startswith(self, byte_string):
200
+ return self.byte_string[:len(byte_string)] == byte_string
201
+
202
+
203
+ def bytes_startswith(a, b):
204
+ return a[:len(b)] == b
205
+
206
+
207
+ class DAQEvents(object):
208
+ """The DAQEvents data class, used to store trigger
209
+
210
+ See Also
211
+ --------
212
+ DataRecorder.set_daq_event()
213
+
214
+ """
215
+
216
+ def __init__(self, time, code):
217
+ """Create a DAQEvents object
218
+
219
+ Parameters
220
+ ----------
221
+ time : int
222
+ code : numerical or string
223
+
224
+ """
225
+ self.time = time
226
+ self.code = code
227
+
228
+
229
+ class GUIRemoteControlCommands(object):
230
+ """
231
+ SET_THRESHOLDS needs to be followed by a threshold object
232
+ SET_RESPONSE_MINMAX_DETECTION needs to be followed an integer representing duration of sampling
233
+
234
+ feedback:
235
+ CHANGED_LEVEL+int from SET_LEVEL_CHANGE_DETECTION
236
+ RESPONSE_MINMAX+(int, int) from SET_RESPONSE_MINMAX_DETECTION
237
+ VALUE+float from GET_FX, GET_FY, GET_FZ, GET_TX, GET_TY, GET_TZ,
238
+
239
+ see also UDPConnection constants!
240
+ """
241
+
242
+ #DOC REMOTECONTROL
243
+
244
+ COMMAND_STR = b"$"
245
+ # BASIC
246
+ START = COMMAND_STR + b"SRT"
247
+ PAUSE = COMMAND_STR + b"PSE"
248
+ PING = COMMAND_STR + b"PNG"
249
+ QUIT = COMMAND_STR + b"QUT"
250
+ #getter
251
+ GET_FX1 = COMMAND_STR + b"gFX1"
252
+ GET_FX2 = COMMAND_STR + b"gFX2"
253
+ GET_FY1 = COMMAND_STR + b"gFY1"
254
+ GET_FY2 = COMMAND_STR + b"gFY2"
255
+ GET_FZ1 = COMMAND_STR + b"gFZ1"
256
+ GET_FZ2 = COMMAND_STR + b"gFZ2"
257
+ GET_TX1 = COMMAND_STR + b"gTX1"
258
+ GET_TX2 = COMMAND_STR + b"gTX2"
259
+ GET_TY1 = COMMAND_STR + b"gTY1"
260
+ GET_TY2 = COMMAND_STR + b"gTY2"
261
+ GET_TZ1 = COMMAND_STR + b"gTZ1"
262
+ GET_TZ2 = COMMAND_STR + b"gTZ2"
263
+ GET_THRESHOLD_LEVEL =COMMAND_STR + b"gTL"
264
+ GET_THRESHOLD_LEVEL2 = COMMAND_STR + b"gTL2"
265
+ GET_VERSION = COMMAND_STR + b"gVR"
266
+ # setter
267
+ FILENAME = COMMAND_STR + b"sFN"
268
+ SET_THRESHOLDS = COMMAND_STR + b"sTH"
269
+ SET_LEVEL_CHANGE_DETECTION = COMMAND_STR + b"sCD1"
270
+ SET_LEVEL_CHANGE_DETECTION2 = COMMAND_STR + b"sCD2"
271
+ SET_RESPONSE_MINMAX_DETECTION = COMMAND_STR + b"sMD1"
272
+ SET_RESPONSE_MINMAX_DETECTION2 = COMMAND_STR + b"sMD2"
273
+ #feedback
274
+ FEEDBACK = COMMAND_STR + b"xFB"
275
+ VALUE = COMMAND_STR + b"xVL"
276
+ RESPONSE_MINMAX = COMMAND_STR + b"xRM1"
277
+ RESPONSE_MINMAX2 = COMMAND_STR + b"xRM2"
278
+ CHANGED_LEVEL = COMMAND_STR + b"xCL1"
279
+ CHANGED_LEVEL2 = COMMAND_STR + b"xCL2"
280
+
281
+ FEEDBACK_PAUSED = FEEDBACK + b"paused"
282
+ FEEDBACK_STARTED = FEEDBACK + b"started"
283
+
284
+ class Thresholds(object):
285
+
286
+ def __init__(self, thresholds, n_channels=1):
287
+ """Thresholds for a one or multiple channels of data"""
288
+ self._thresholds = list(thresholds)
289
+ self._thresholds.sort()
290
+ self.set_number_of_channels(n_channels=n_channels)
291
+
292
+ def is_detecting(self, channels=0):
293
+ return self._minmax[channels] is not None or self._prev_level[channels] is not None
294
+
295
+ def is_level_change_detecting(self, channels=0):
296
+ return self._prev_level[channels] is not None
297
+
298
+ def is_response_minmax_detecting(self, channels=0):
299
+ return self._minmax[channels] is not None
300
+
301
+ def is_detecting_anything(self):
302
+ """is detecting something in at least one channel"""
303
+ nn = lambda x:x is not None
304
+ return len(list(filter(nn, self._prev_level)))>0 or len(list(filter(nn, self._minmax)))>0
305
+
306
+ def set_number_of_channels(self, n_channels):
307
+ self._prev_level = [None] * n_channels
308
+ self._minmax = [None] * n_channels
309
+
310
+ @property
311
+ def thresholds(self):
312
+ return self._thresholds
313
+
314
+ def get_level(self, value):
315
+ """return [int]
316
+ int: the level of current sensor value depending of thresholds (array)
317
+
318
+ return:
319
+ 0 below smallest threshold
320
+ 1 large first but small second threshold
321
+ ..
322
+ x larger highest threshold (x=n thresholds)
323
+ """
324
+
325
+ level = None
326
+ cnt = 0
327
+ for cnt, x in enumerate(self._thresholds):
328
+ if value < x:
329
+ level = cnt
330
+ break
331
+
332
+ if level is None:
333
+ level = cnt + 1
334
+ return level
335
+
336
+ def set_level_change_detection(self, value, channel=0):
337
+ """sets level change detection
338
+ returns: current level
339
+ """
340
+ self._prev_level[channel] = self.get_level(value)
341
+ self._minmax[channel] = None
342
+ return self._prev_level[channel]
343
+
344
+ def get_level_change(self, value, channel=0):
345
+ """return tuple with level_change (boolean) and current level (int)
346
+ if level change detection is switch on
347
+
348
+ Note: after detected level change detection is switched off!
349
+ """
350
+
351
+ if self._prev_level[channel] is None:
352
+ return None, None
353
+
354
+ current = self.get_level(value)
355
+ changed = (current != self._prev_level[channel])
356
+ if changed:
357
+ self._prev_level[channel] = None
358
+ return changed, current
359
+
360
+ def __str__(self):
361
+ return str(self._thresholds)
362
+
363
+ def set_response_minmax_detection(self, value, duration, channel=0):
364
+ """Start response detection
365
+ Parameters detects minimum and maximum of the response
366
+ after first level change (length =number_of_samples)
367
+
368
+ value: start level
369
+ polled samples need to feed via get_response_minmax()
370
+
371
+ returns: current level
372
+ """
373
+
374
+ lv = self.get_level(value)
375
+ self._minmax[channel] = _MinMaxDetector(start_value=lv,
376
+ duration=duration)
377
+ self._prev_level[channel] = None
378
+ return lv
379
+
380
+
381
+ def get_response_minmax(self, value, channel=0):
382
+ """checks for response minimum and maximum if set_response_minmax_detection is switch on
383
+ With this function you add a sample and check if the response can be classified. If so,
384
+ it returns a tuple with the minimum and maximum response level during the response period
385
+ otherwise
386
+ returns None
387
+
388
+ tuple with level_change (boolean) and current level (int)
389
+
390
+ Note: after response minmax has been determined once response_minmax_detection is switched off!
391
+ """
392
+
393
+ if self._minmax[channel] is None:
394
+ return None
395
+
396
+ rtn = self._minmax[channel].process(self.get_level(value))
397
+ if rtn is not None:
398
+ # minmax just detected
399
+ self._minmax[channel] = None # switch off
400
+ return rtn