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,59 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from expyriment.stimuli import Canvas, Rectangle, TextLine
|
|
3
|
+
from expyriment.misc import constants
|
|
4
|
+
|
|
5
|
+
def level_indicator(value, text, scaling, width=20,
|
|
6
|
+
text_size=14, text_gap=20, position=(0,0), thresholds = None,
|
|
7
|
+
colour=constants.C_EXPYRIMENT_ORANGE):
|
|
8
|
+
"""make an level indicator in for of an Expyriment stimulus
|
|
9
|
+
|
|
10
|
+
text_gap: gap between indicator and text
|
|
11
|
+
scaling: Scaling object
|
|
12
|
+
|
|
13
|
+
Returns
|
|
14
|
+
--------
|
|
15
|
+
expyriment.Canvas
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
value = scaling.trim(value)
|
|
20
|
+
|
|
21
|
+
# indicator
|
|
22
|
+
height = scaling.pixel_max - scaling.pixel_min
|
|
23
|
+
indicator = Canvas(size=(width + 2, height + 2),
|
|
24
|
+
colour=(30, 30, 30))
|
|
25
|
+
|
|
26
|
+
zero = scaling.data2pixel(0)
|
|
27
|
+
px_bar_height = scaling.data2pixel(value) - zero
|
|
28
|
+
bar = Rectangle(size=(width, abs(px_bar_height)),
|
|
29
|
+
position=(0, zero + int((px_bar_height + 1) / 2)),
|
|
30
|
+
colour=colour)
|
|
31
|
+
bar.plot(indicator)
|
|
32
|
+
|
|
33
|
+
# levels & horizontal lines
|
|
34
|
+
try:
|
|
35
|
+
px_horizontal_lines = scaling.data2pixel(values=np.array(thresholds.thresholds))
|
|
36
|
+
except:
|
|
37
|
+
px_horizontal_lines = None
|
|
38
|
+
if px_horizontal_lines is not None:
|
|
39
|
+
for px in px_horizontal_lines:
|
|
40
|
+
level = Rectangle(size=(width+6, 2),
|
|
41
|
+
position=(0, px),
|
|
42
|
+
colour=constants.C_WHITE)
|
|
43
|
+
level.plot(indicator)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# text labels
|
|
49
|
+
txt = TextLine(text=text, text_size=text_size,
|
|
50
|
+
position=(0, -1 * (int(height / 2.0) + text_gap)),
|
|
51
|
+
text_colour=constants.C_YELLOW)
|
|
52
|
+
|
|
53
|
+
# make return canvas
|
|
54
|
+
w = max(txt.surface_size[0], indicator.size[0])
|
|
55
|
+
h = height + 2 * (txt.surface_size[1]) + text_gap
|
|
56
|
+
rtn = Canvas(size=(w, h), colour=(0, 0, 0), position=position)
|
|
57
|
+
indicator.plot(rtn)
|
|
58
|
+
txt.plot(rtn)
|
|
59
|
+
return rtn
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import pygame
|
|
2
|
+
from expyriment.stimuli import Canvas
|
|
3
|
+
|
|
4
|
+
class PGSurface(Canvas):
|
|
5
|
+
"""PyGame Surface: Expyriment Stimulus for direct Pygame operations and
|
|
6
|
+
PixelArrays
|
|
7
|
+
|
|
8
|
+
In contrast to other Expyriment stimuli the class does not generate temporary
|
|
9
|
+
surfaces.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, size, position=None, colour=None):
|
|
13
|
+
Canvas.__init__(self, size, position, colour)
|
|
14
|
+
self._px_array = None
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def surface(self):
|
|
18
|
+
"""DOC"""
|
|
19
|
+
if not self.has_surface:
|
|
20
|
+
ok = self._set_surface(self._get_surface()) # create surface
|
|
21
|
+
if not ok:
|
|
22
|
+
raise RuntimeError("Cannot call surface on compressed stimuli!")
|
|
23
|
+
return self._surface
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def pixel_array(self):
|
|
27
|
+
"""DOC"""
|
|
28
|
+
if self._px_array is None:
|
|
29
|
+
self._px_array = pygame.PixelArray(self.surface)
|
|
30
|
+
return self._px_array
|
|
31
|
+
|
|
32
|
+
@pixel_array.setter
|
|
33
|
+
def pixel_array(self, value):
|
|
34
|
+
if self._px_array is None:
|
|
35
|
+
self._px_array = pygame.PixelArray(self.surface)
|
|
36
|
+
self._px_array = value
|
|
37
|
+
|
|
38
|
+
def unlock_pixel_array(self):
|
|
39
|
+
"""DOC"""
|
|
40
|
+
self._px_array = None
|
|
41
|
+
|
|
42
|
+
def preload(self, inhibit_ogl_compress=False):
|
|
43
|
+
self.unlock_pixel_array()
|
|
44
|
+
return Canvas.preload(self, inhibit_ogl_compress)
|
|
45
|
+
|
|
46
|
+
def compress(self):
|
|
47
|
+
self.unlock_pixel_array()
|
|
48
|
+
return Canvas.compress(self)
|
|
49
|
+
|
|
50
|
+
def decompress(self):
|
|
51
|
+
self.unlock_pixel_array()
|
|
52
|
+
return Canvas.decompress(self)
|
|
53
|
+
|
|
54
|
+
def plot(self, stimulus):
|
|
55
|
+
self.unlock_pixel_array()
|
|
56
|
+
return Canvas.plot(self, stimulus)
|
|
57
|
+
|
|
58
|
+
def clear_surface(self):
|
|
59
|
+
self.unlock_pixel_array()
|
|
60
|
+
return Canvas.clear_surface(self)
|
|
61
|
+
|
|
62
|
+
def copy(self):
|
|
63
|
+
self.unlock_pixel_array()
|
|
64
|
+
return Canvas.copy(self)
|
|
65
|
+
|
|
66
|
+
def unload(self, keep_surface=False):
|
|
67
|
+
if not keep_surface:
|
|
68
|
+
self.unlock_pixel_array()
|
|
69
|
+
return Canvas.unload(self, keep_surface)
|
|
70
|
+
|
|
71
|
+
def rotate(self, degree, filter=True): # Exypriment >=.9
|
|
72
|
+
self.unlock_pixel_array()
|
|
73
|
+
return Canvas.rotate(self, degree, filter)
|
|
74
|
+
|
|
75
|
+
def scale(self, factors):
|
|
76
|
+
self.unlock_pixel_array()
|
|
77
|
+
return Canvas.scale(self, factors)
|
|
78
|
+
|
|
79
|
+
# expyriment 0.8.0
|
|
80
|
+
# def scale_to_fullscreen(self, keep_aspect_ratio=True):
|
|
81
|
+
# self.unlock_pixel_array()
|
|
82
|
+
# return Canvas.scale_to_fullscreen(self, keep_aspect_ratio)
|
|
83
|
+
|
|
84
|
+
def flip(self, booleans):
|
|
85
|
+
self.unlock_pixel_array()
|
|
86
|
+
return Canvas.flip(self, booleans)
|
|
87
|
+
|
|
88
|
+
def blur(self, level):
|
|
89
|
+
self.unlock_pixel_array()
|
|
90
|
+
return Canvas.blur(self, level)
|
|
91
|
+
|
|
92
|
+
def scramble(self, grain_size):
|
|
93
|
+
self.unlock_pixel_array()
|
|
94
|
+
return Canvas.scramble(self, grain_size)
|
|
95
|
+
|
|
96
|
+
def add_noise(self, grain_size, percentage, colour):
|
|
97
|
+
self.unlock_pixel_array()
|
|
98
|
+
return Canvas.add_noise(self, grain_size, percentage, colour)
|
|
99
|
+
|
|
100
|
+
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
__version__ = "0.3"
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pygame
|
|
6
|
+
try:
|
|
7
|
+
# expyriment >= 0.10
|
|
8
|
+
from expyriment.misc import Colour as ExpyColor
|
|
9
|
+
except:
|
|
10
|
+
# old expyriment
|
|
11
|
+
ExpyColor = tuple
|
|
12
|
+
|
|
13
|
+
from ._pg_surface import PGSurface
|
|
14
|
+
|
|
15
|
+
lock_expyriment = threading.Lock()
|
|
16
|
+
Numpy_array_type = type(np.array([]))
|
|
17
|
+
|
|
18
|
+
class Plotter(PGSurface):
|
|
19
|
+
"""Pygame Plotter"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, n_data_rows, data_row_colours,
|
|
22
|
+
width=600, y_range=(-100, 100),
|
|
23
|
+
background_colour=(180, 180, 180),
|
|
24
|
+
marker_colour=(200, 200, 200),
|
|
25
|
+
position=None,
|
|
26
|
+
axis_colour=None):
|
|
27
|
+
self._n_data_rows = n_data_rows
|
|
28
|
+
self._data_row_colours = check_colour_array(values=data_row_colours,
|
|
29
|
+
required_n_colours=n_data_rows)
|
|
30
|
+
self._y_range = y_range
|
|
31
|
+
height = self._y_range[1] - self._y_range[0]
|
|
32
|
+
|
|
33
|
+
self._background_colour = tuple(background_colour)
|
|
34
|
+
self._marker_colour = tuple(marker_colour)
|
|
35
|
+
self._horizontal_lines = None
|
|
36
|
+
|
|
37
|
+
if axis_colour is None:
|
|
38
|
+
self.axis_colour = background_colour
|
|
39
|
+
else:
|
|
40
|
+
self.axis_colour = axis_colour
|
|
41
|
+
self._previous = [None] * n_data_rows
|
|
42
|
+
PGSurface.__init__(self, size=(width, height),
|
|
43
|
+
position=position)
|
|
44
|
+
self.clear_area()
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def width(self):
|
|
48
|
+
return self.size[0]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def height(self):
|
|
52
|
+
return self.size[1]
|
|
53
|
+
|
|
54
|
+
def clear_area(self):
|
|
55
|
+
self.pixel_array[:, :] = self._background_colour
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def set_horizontal_line(self, y_values):
|
|
59
|
+
"""y_values: array"""
|
|
60
|
+
try:
|
|
61
|
+
self._horizontal_lines = np.array(y_values, dtype=int)
|
|
62
|
+
except:
|
|
63
|
+
self._horizontal_lines = None
|
|
64
|
+
|
|
65
|
+
def write_values(self, position, values, set_marker=False,
|
|
66
|
+
set_point_marker=False):
|
|
67
|
+
"""
|
|
68
|
+
additional points: np.array
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
if set_marker:
|
|
72
|
+
self.pixel_array[position, :] = self._marker_colour
|
|
73
|
+
else:
|
|
74
|
+
self.pixel_array[position, :] = self._background_colour
|
|
75
|
+
|
|
76
|
+
if set_point_marker:
|
|
77
|
+
self.pixel_array[position, 0:2] = self._marker_colour
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
if self._horizontal_lines is not None:
|
|
81
|
+
for c in (self._y_range[1] - self._horizontal_lines):
|
|
82
|
+
self.pixel_array[:, c:c+1] = self._marker_colour
|
|
83
|
+
|
|
84
|
+
for c, plot_value in enumerate(self._y_range[1] - \
|
|
85
|
+
np.array(values, dtype=int)):
|
|
86
|
+
|
|
87
|
+
if self._previous[c] is not None and \
|
|
88
|
+
plot_value >= 0 and \
|
|
89
|
+
self._previous[c] >= 0 and \
|
|
90
|
+
plot_value <= self.height and \
|
|
91
|
+
self._previous[c] <= self.height:
|
|
92
|
+
if self._previous[c] > plot_value:
|
|
93
|
+
self.pixel_array[position,
|
|
94
|
+
plot_value:self._previous[c] + 1] = \
|
|
95
|
+
self._data_row_colours[c]
|
|
96
|
+
else:
|
|
97
|
+
self.pixel_array[position,
|
|
98
|
+
self._previous[c]:plot_value + 1] = \
|
|
99
|
+
self._data_row_colours[c]
|
|
100
|
+
self._previous[c] = plot_value
|
|
101
|
+
|
|
102
|
+
def add_values(self, values, set_marker=False):
|
|
103
|
+
""" high level function of write values with type check and shifting to left
|
|
104
|
+
not used by plotter thread
|
|
105
|
+
"""
|
|
106
|
+
if type(values) is not Numpy_array_type and \
|
|
107
|
+
not isinstance(values, tuple) and \
|
|
108
|
+
not isinstance(values, list):
|
|
109
|
+
values = [values]
|
|
110
|
+
if len(values) != self._n_data_rows:
|
|
111
|
+
raise RuntimeError('Number of data values does not match the ' +
|
|
112
|
+
'defined number of data rows!')
|
|
113
|
+
|
|
114
|
+
# move plot one pixel to the left
|
|
115
|
+
self.pixel_array[:-1, :] = self.pixel_array[1:, :]
|
|
116
|
+
self.write_values(position=-1, values=values, set_marker=set_marker)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class PlotterThread(threading.Thread):
|
|
120
|
+
def __init__(self, n_data_rows, data_row_colours,
|
|
121
|
+
width=600, y_range=(-100, 100),
|
|
122
|
+
background_colour=(80, 80, 80),
|
|
123
|
+
marker_colour=(200, 200, 200),
|
|
124
|
+
position=None,
|
|
125
|
+
axis_colour=None):
|
|
126
|
+
super(PlotterThread, self).__init__()
|
|
127
|
+
self._plotter = Plotter(n_data_rows=n_data_rows,
|
|
128
|
+
data_row_colours=data_row_colours,
|
|
129
|
+
width=width, y_range=y_range,
|
|
130
|
+
background_colour=background_colour,
|
|
131
|
+
marker_colour=marker_colour,
|
|
132
|
+
position=position,
|
|
133
|
+
axis_colour=axis_colour)
|
|
134
|
+
self._new_values = []
|
|
135
|
+
self._lock_new_values = threading.Lock()
|
|
136
|
+
self._running = threading.Event()
|
|
137
|
+
self._stop_request = threading.Event()
|
|
138
|
+
self._clear_area_event = threading.Event()
|
|
139
|
+
self.unpause()
|
|
140
|
+
|
|
141
|
+
def get_plotter_rect(self, screen_size):
|
|
142
|
+
half_screen_size = (screen_size[0] / 2, screen_size[1] / 2)
|
|
143
|
+
pos = self._plotter.absolute_position
|
|
144
|
+
stim_size = self._plotter.surface_size
|
|
145
|
+
rect_pos = (pos[0] + half_screen_size[0] - stim_size[0] / 2,
|
|
146
|
+
- pos[1] + half_screen_size[1] - stim_size[1] / 2)
|
|
147
|
+
return pygame.Rect(rect_pos, stim_size)
|
|
148
|
+
|
|
149
|
+
def clear_area(self):
|
|
150
|
+
self._clear_area_event.set()
|
|
151
|
+
|
|
152
|
+
def pause(self):
|
|
153
|
+
self._running.clear()
|
|
154
|
+
|
|
155
|
+
def unpause(self):
|
|
156
|
+
self._running.set()
|
|
157
|
+
|
|
158
|
+
def join(self, timeout=None):
|
|
159
|
+
self._stop_request.set()
|
|
160
|
+
super(PlotterThread, self).join(timeout)
|
|
161
|
+
|
|
162
|
+
def run(self):
|
|
163
|
+
"""the plotter thread is constantly updating the the
|
|
164
|
+
pixel_area"""
|
|
165
|
+
|
|
166
|
+
while not self._stop_request.is_set():
|
|
167
|
+
|
|
168
|
+
if not self._running.is_set():
|
|
169
|
+
self._running.wait(timeout=1)
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
if self._clear_area_event.is_set():
|
|
173
|
+
self._plotter.clear_area()
|
|
174
|
+
self._clear_area_event.clear()
|
|
175
|
+
|
|
176
|
+
# get data
|
|
177
|
+
if self._lock_new_values.acquire(False):
|
|
178
|
+
values = self._new_values
|
|
179
|
+
self._new_values = []
|
|
180
|
+
self._lock_new_values.release() # release to receive new values
|
|
181
|
+
else:
|
|
182
|
+
values = []
|
|
183
|
+
|
|
184
|
+
n = len(values)
|
|
185
|
+
if n > 0:
|
|
186
|
+
if n > self._plotter.width:
|
|
187
|
+
values = values[-1 * self._plotter.width:] # only the last
|
|
188
|
+
n = len(values)
|
|
189
|
+
self._plotter.pixel_array[:-1 * n, :] = \
|
|
190
|
+
self._plotter.pixel_array[n:, :]
|
|
191
|
+
for x in range(-1 * n, 0):
|
|
192
|
+
self._plotter.write_values(position=x,
|
|
193
|
+
values=values[x][0],
|
|
194
|
+
set_marker=values[x][1],
|
|
195
|
+
set_point_marker=values[x][2])
|
|
196
|
+
# Expyriment present
|
|
197
|
+
lock_expyriment.acquire()
|
|
198
|
+
self._plotter.present(update=False, clear=False)
|
|
199
|
+
lock_expyriment.release()
|
|
200
|
+
|
|
201
|
+
def set_horizontal_lines(self, y_values):
|
|
202
|
+
"""adds new values to the plotter
|
|
203
|
+
y_values has to be an array
|
|
204
|
+
"""
|
|
205
|
+
self._lock_new_values.acquire()
|
|
206
|
+
self._plotter.set_horizontal_line(y_values=y_values)
|
|
207
|
+
self._lock_new_values.release()
|
|
208
|
+
|
|
209
|
+
def add_values(self, values, set_marker=False, set_point_marker=False):
|
|
210
|
+
"""adds new values to the plotter"""
|
|
211
|
+
self._lock_new_values.acquire()
|
|
212
|
+
self._new_values.append((values, set_marker, set_point_marker))
|
|
213
|
+
self._lock_new_values.release()
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
## helper
|
|
217
|
+
def check_colour_array(values, required_n_colours):
|
|
218
|
+
""" check if it is a list of colours list of colours,
|
|
219
|
+
otherwise raise an error
|
|
220
|
+
"""
|
|
221
|
+
try:
|
|
222
|
+
if not isinstance(values[0], (list, tuple, ExpyColor) ):
|
|
223
|
+
# it is a list, but the elements are not colours, let's assume
|
|
224
|
+
# it is one dimensional array
|
|
225
|
+
values = [values]
|
|
226
|
+
except:
|
|
227
|
+
values = [()] # values is not list pixel_array
|
|
228
|
+
|
|
229
|
+
values = list(map(lambda x:tuple(x), values)) # force list of tuples
|
|
230
|
+
|
|
231
|
+
if len(values) != required_n_colours:
|
|
232
|
+
raise RuntimeError('Number of data row colour does not match the ' +
|
|
233
|
+
'defined number of data rows!')
|
|
234
|
+
return values
|