explorepy 4.1.0__tar.gz → 4.3.0__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.
- {explorepy-4.1.0 → explorepy-4.3.0}/.bumpversion.cfg +1 -1
- {explorepy-4.1.0 → explorepy-4.3.0}/CHANGELOG.rst +16 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/PKG-INFO +9 -23
- {explorepy-4.1.0 → explorepy-4.3.0}/README.rst +8 -22
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/conf.py +1 -1
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/installation.rst +4 -17
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/requirements.txt +1 -1
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/usage.rst +88 -49
- {explorepy-4.1.0 → explorepy-4.3.0}/pyproject.toml +1 -1
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/BLEClient.py +12 -6
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/BTClient.py +0 -9
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/__init__.py +1 -1
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/cli.py +12 -62
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/command.py +0 -80
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/explore.py +162 -111
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/packet.py +42 -37
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/parser.py +44 -30
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/serial_client.py +3 -5
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/stream_processor.py +8 -37
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/tools.py +45 -456
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy.egg-info/PKG-INFO +9 -23
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy.egg-info/SOURCES.txt +1 -0
- explorepy-4.3.0/tests/integration_test_ble.py +115 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/.cookiecutterrc +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/.coveragerc +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/.editorconfig +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/AUTHORS.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/CONTRIBUTING.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/LICENSE +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/MANIFEST.in +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/authors.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/changelog.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/contributing.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/explore_legacy_devices.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/index.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/logo.jpg +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/readme.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/reference/explorepy.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/reference/index.rst +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/docs/spelling_wordlist.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/setup.cfg +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/_exceptions.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/bt_mock_client.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/bt_mock_server.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/debug.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/filters.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/log_config.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy/settings_manager.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy.egg-info/dependency_links.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy.egg-info/entry_points.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy.egg-info/requires.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/src/explorepy.egg-info/top_level.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/README.md +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/__init__.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/conftest.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/calibration_info +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/calibration_info_usbc +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/cmd_rcv +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/cmd_stat +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/device_info +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/device_info_ble +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/device_info_v2 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/device_info_v2_2 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/disconnect +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg16_ble +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg32 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg94 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg98 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg98_ble +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg98_usbc +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/eeg98_usbc_2 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/env +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/orn +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/orn_2 +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/orn_matrix.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/push_marker +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/in/trigger_in +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/axis_and_angle.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/calibration_info_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/calibration_info_usbc_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/cmd_rcv_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/cmd_stat_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/device_info_ble_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/device_info_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/device_info_v2_2_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/device_info_v2_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/disconnect_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg16_ble_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg32_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg94_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg98_ble_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg98_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg98_out_fake.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg98_usbc_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/eeg98_usbc_out_2.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/env_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/orn_2_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/orn_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/push_marker_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/res/out/trigger_in_out.txt +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tests/test_packet.py +0 -0
- {explorepy-4.1.0 → explorepy-4.3.0}/tox.ini +0 -0
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
4.3.0 (10.9.2025)
|
|
6
|
+
------------------
|
|
7
|
+
* Live impedance acquisition and recording
|
|
8
|
+
* Improve parser efficiency
|
|
9
|
+
* Bugfix for Linux release
|
|
10
|
+
* Refactor codebase
|
|
11
|
+
|
|
12
|
+
4.2.0 (15.7.2025)
|
|
13
|
+
------------------
|
|
14
|
+
* Add ExplorePy and Arduino interfacing example
|
|
15
|
+
* Add fieldtrip integration example
|
|
16
|
+
* Add live exg and impedance acquisition example
|
|
17
|
+
* Update MNE integration
|
|
18
|
+
* Remove deprecated functions
|
|
19
|
+
* Update documentation
|
|
20
|
+
|
|
5
21
|
4.1.0 (31.3.2025)
|
|
6
22
|
------------------
|
|
7
23
|
* Improve bluetooth connection UX
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: explorepy
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.0
|
|
4
4
|
Author-email: MentaLab Hub <support@mentab.org>
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/Mentalab-hub/explorepy
|
|
@@ -43,7 +43,7 @@ Requires-Dist: flake8==4.0.1; extra == "test"
|
|
|
43
43
|
Requires-Dist: isort==5.10.1; extra == "test"
|
|
44
44
|
Dynamic: license-file
|
|
45
45
|
|
|
46
|
-
.. image:: logo.
|
|
46
|
+
.. image:: https://raw.githubusercontent.com/Mentalab-hub/explorepy/master/docs/logo.jpg
|
|
47
47
|
:scale: 100 %
|
|
48
48
|
:align: left
|
|
49
49
|
|
|
@@ -62,9 +62,9 @@ Dynamic: license-file
|
|
|
62
62
|
:target: https://pypi.org/project/explorepy
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.
|
|
65
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.3.0.svg
|
|
66
66
|
:alt: Commits since latest release
|
|
67
|
-
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.
|
|
67
|
+
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.3.0...master
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
.. |wheel| image:: https://img.shields.io/pypi/wheel/explorepy.svg
|
|
@@ -82,6 +82,7 @@ Dynamic: license-file
|
|
|
82
82
|
|
|
83
83
|
.. end-badges
|
|
84
84
|
|
|
85
|
+
|
|
85
86
|
=========================
|
|
86
87
|
``explorepy`` overview
|
|
87
88
|
=========================
|
|
@@ -89,18 +90,11 @@ Dynamic: license-file
|
|
|
89
90
|
``explorepy`` is an open-source Python API designed to collect and process ExG data using Mentalab's Explore device. Amongst other things, ``explorepy`` provides the following features:
|
|
90
91
|
|
|
91
92
|
* Real-time streaming of ExG, orientation and environmental data.
|
|
92
|
-
* Real-time visualization of ExG, orientation and environmental data.
|
|
93
93
|
* Data recording in CSV and BDF+ formats.
|
|
94
94
|
* Integration with LabStreaming Layer.
|
|
95
95
|
* Electrode impedance measurements.
|
|
96
96
|
* Explore device configuration.
|
|
97
97
|
|
|
98
|
-
Quick installation
|
|
99
|
-
==================
|
|
100
|
-
For Windows users, the best way to install ``explorepy`` is to download the latest ``explorepy`` version from the `release page <https://github.com/Mentalab-hub/explorepy/releases>`_. Please note that dependencies will install automatically from the release page.
|
|
101
|
-
|
|
102
|
-
For other operating systems, or to build the package manually on Windows, please refer to the information below.
|
|
103
|
-
|
|
104
98
|
|
|
105
99
|
Requirements
|
|
106
100
|
------------
|
|
@@ -121,7 +115,7 @@ To install ``explorepy`` from PyPI run:
|
|
|
121
115
|
To install the latest development version (git must be installed before running this command):
|
|
122
116
|
::
|
|
123
117
|
|
|
124
|
-
pip install git+https://github.com/Mentalab-hub/explorepy
|
|
118
|
+
pip install git+https://github.com/Mentalab-hub/explorepy.git@develop
|
|
125
119
|
|
|
126
120
|
|
|
127
121
|
Get started
|
|
@@ -147,17 +141,9 @@ In Python you can connect to the Explore device and print data using:
|
|
|
147
141
|
|
|
148
142
|
import explorepy
|
|
149
143
|
explorer = explorepy.Explore()
|
|
150
|
-
explorer.connect(device_name="Explore_XXXX") #
|
|
144
|
+
explorer.connect(device_name="Explore_XXXX") # Use correct device ID here
|
|
151
145
|
explorer.acquire()
|
|
152
146
|
|
|
153
|
-
You can also visualize the data in real-time.
|
|
154
|
-
|
|
155
|
-
::
|
|
156
|
-
|
|
157
|
-
import explorepy
|
|
158
|
-
explorer = explorepy.Explore()
|
|
159
|
-
explorer.connect(device_name="Explore_XXXX") # Put your device Bluetooth name
|
|
160
|
-
|
|
161
147
|
Documentation
|
|
162
148
|
=============
|
|
163
149
|
|
|
@@ -185,7 +171,7 @@ Authors
|
|
|
185
171
|
- `Sonja Stefani`_
|
|
186
172
|
- `Alex Platt`_
|
|
187
173
|
- `Andreas Gutsche`_
|
|
188
|
-
- `
|
|
174
|
+
- `Masoome Fazelian`_
|
|
189
175
|
- `Philipp Jakovleski`_
|
|
190
176
|
- `Florian Sesser`_
|
|
191
177
|
- `Sebastian Herberger`_
|
|
@@ -197,7 +183,7 @@ Authors
|
|
|
197
183
|
.. _Sonja Stefani: https://github.com/SonjaSt
|
|
198
184
|
.. _Alex Platt: https://github.com/Nujanauss
|
|
199
185
|
.. _Andreas Gutsche: https://github.com/andyman410
|
|
200
|
-
..
|
|
186
|
+
.. _Masoome Fazelian: https://github.com/fazelian
|
|
201
187
|
.. _Philipp Jakovleski: https://github.com/philippjak
|
|
202
188
|
.. _Florian Sesser : https://github.com/hacklschorsch
|
|
203
189
|
.. _Sebastian Herberger: https://github.com/SHerberger
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.. image:: logo.
|
|
1
|
+
.. image:: https://raw.githubusercontent.com/Mentalab-hub/explorepy/master/docs/logo.jpg
|
|
2
2
|
:scale: 100 %
|
|
3
3
|
:align: left
|
|
4
4
|
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
:target: https://pypi.org/project/explorepy
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.
|
|
20
|
+
.. |commits-since| image:: https://img.shields.io/github/commits-since/Mentalab-hub/explorepy/v4.3.0.svg
|
|
21
21
|
:alt: Commits since latest release
|
|
22
|
-
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.
|
|
22
|
+
:target: https://github.com/Mentalab-hub/explorepy/compare/v4.3.0...master
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
.. |wheel| image:: https://img.shields.io/pypi/wheel/explorepy.svg
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
.. end-badges
|
|
39
39
|
|
|
40
|
+
|
|
40
41
|
=========================
|
|
41
42
|
``explorepy`` overview
|
|
42
43
|
=========================
|
|
@@ -44,18 +45,11 @@
|
|
|
44
45
|
``explorepy`` is an open-source Python API designed to collect and process ExG data using Mentalab's Explore device. Amongst other things, ``explorepy`` provides the following features:
|
|
45
46
|
|
|
46
47
|
* Real-time streaming of ExG, orientation and environmental data.
|
|
47
|
-
* Real-time visualization of ExG, orientation and environmental data.
|
|
48
48
|
* Data recording in CSV and BDF+ formats.
|
|
49
49
|
* Integration with LabStreaming Layer.
|
|
50
50
|
* Electrode impedance measurements.
|
|
51
51
|
* Explore device configuration.
|
|
52
52
|
|
|
53
|
-
Quick installation
|
|
54
|
-
==================
|
|
55
|
-
For Windows users, the best way to install ``explorepy`` is to download the latest ``explorepy`` version from the `release page <https://github.com/Mentalab-hub/explorepy/releases>`_. Please note that dependencies will install automatically from the release page.
|
|
56
|
-
|
|
57
|
-
For other operating systems, or to build the package manually on Windows, please refer to the information below.
|
|
58
|
-
|
|
59
53
|
|
|
60
54
|
Requirements
|
|
61
55
|
------------
|
|
@@ -76,7 +70,7 @@ To install ``explorepy`` from PyPI run:
|
|
|
76
70
|
To install the latest development version (git must be installed before running this command):
|
|
77
71
|
::
|
|
78
72
|
|
|
79
|
-
pip install git+https://github.com/Mentalab-hub/explorepy
|
|
73
|
+
pip install git+https://github.com/Mentalab-hub/explorepy.git@develop
|
|
80
74
|
|
|
81
75
|
|
|
82
76
|
Get started
|
|
@@ -102,17 +96,9 @@ In Python you can connect to the Explore device and print data using:
|
|
|
102
96
|
|
|
103
97
|
import explorepy
|
|
104
98
|
explorer = explorepy.Explore()
|
|
105
|
-
explorer.connect(device_name="Explore_XXXX") #
|
|
99
|
+
explorer.connect(device_name="Explore_XXXX") # Use correct device ID here
|
|
106
100
|
explorer.acquire()
|
|
107
101
|
|
|
108
|
-
You can also visualize the data in real-time.
|
|
109
|
-
|
|
110
|
-
::
|
|
111
|
-
|
|
112
|
-
import explorepy
|
|
113
|
-
explorer = explorepy.Explore()
|
|
114
|
-
explorer.connect(device_name="Explore_XXXX") # Put your device Bluetooth name
|
|
115
|
-
|
|
116
102
|
Documentation
|
|
117
103
|
=============
|
|
118
104
|
|
|
@@ -140,7 +126,7 @@ Authors
|
|
|
140
126
|
- `Sonja Stefani`_
|
|
141
127
|
- `Alex Platt`_
|
|
142
128
|
- `Andreas Gutsche`_
|
|
143
|
-
- `
|
|
129
|
+
- `Masoome Fazelian`_
|
|
144
130
|
- `Philipp Jakovleski`_
|
|
145
131
|
- `Florian Sesser`_
|
|
146
132
|
- `Sebastian Herberger`_
|
|
@@ -152,7 +138,7 @@ Authors
|
|
|
152
138
|
.. _Sonja Stefani: https://github.com/SonjaSt
|
|
153
139
|
.. _Alex Platt: https://github.com/Nujanauss
|
|
154
140
|
.. _Andreas Gutsche: https://github.com/andyman410
|
|
155
|
-
..
|
|
141
|
+
.. _Masoome Fazelian: https://github.com/fazelian
|
|
156
142
|
.. _Philipp Jakovleski: https://github.com/philippjak
|
|
157
143
|
.. _Florian Sesser : https://github.com/hacklschorsch
|
|
158
144
|
.. _Sebastian Herberger: https://github.com/SHerberger
|
|
@@ -19,25 +19,12 @@ Recommended Requirements
|
|
|
19
19
|
How to install
|
|
20
20
|
--------------
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
"""""""""""""""""""""""""""""""""""""""""""""""
|
|
24
|
-
|
|
25
|
-
Windows and Mac
|
|
26
|
-
^^^^^^^^^^^^^^^
|
|
27
|
-
|
|
28
|
-
*This option is best for users who only intend to use functionalities offered by* ``explorepy`` *via a graphical user interface*
|
|
29
|
-
|
|
30
|
-
For example, if you want to quickly visualize and record data and don't need the command line interface or to use it in your own Python script, use this option.
|
|
31
|
-
|
|
32
|
-
If you intend to call ``explorepy`` from the command line or a Python script (e.g. from an experiment script), install ``explorepy`` via Anaconda/pip instead.
|
|
33
|
-
|
|
34
|
-
For Windows and Mac, the standalone desktop software ExploreDesktop can be installed using the installer files uploaded to the
|
|
35
|
-
`release page <https://github.com/Mentalab-hub/explore-desktop-release/releases/latest/>`_. Please note that the dependencies will be installed automatically and bundled locally with the installed software.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Option 2: Installing from Python Package Index (PyPI) and pip (advanced)
|
|
22
|
+
Installing from Python Package Index (PyPI)
|
|
39
23
|
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
|
40
24
|
|
|
25
|
+
.. note::
|
|
26
|
+
For Windows, Mac and Linux the standalone desktop software ExploreDesktop can be installed using the installer files uploaded to the
|
|
27
|
+
`release page <https://github.com/Mentalab-hub/explore-desktop-release/releases/latest/>`_.
|
|
41
28
|
|
|
42
29
|
.. note::
|
|
43
30
|
|
|
@@ -27,14 +27,6 @@ For example to get help about the visualize command, run: ``explorepy push2lsl -
|
|
|
27
27
|
Available Commands
|
|
28
28
|
""""""""""""""""""
|
|
29
29
|
|
|
30
|
-
find-device
|
|
31
|
-
%%%%
|
|
32
|
-
Scans for nearby Mentalab Explore devices. Prints out the Name and MAC address of found devices.
|
|
33
|
-
|
|
34
|
-
Options:
|
|
35
|
-
-h, --help Show this message and exit.
|
|
36
|
-
|
|
37
|
-
|
|
38
30
|
acquire
|
|
39
31
|
%%%%
|
|
40
32
|
Connects to a device with selected name or address. Only one input is necessary.
|
|
@@ -63,6 +55,7 @@ Connects to a device and records ExG and orientation data into two separate file
|
|
|
63
55
|
-d, --duration <integer> Recording duration in seconds
|
|
64
56
|
--edf Write in EDF file
|
|
65
57
|
--csv Write in csv file (default type)
|
|
58
|
+
--imp-mode Enable impedance mode with live monitoring
|
|
66
59
|
-h, --help Show this message and exit.
|
|
67
60
|
|
|
68
61
|
|
|
@@ -135,18 +128,6 @@ Example (overwrite):
|
|
|
135
128
|
::
|
|
136
129
|
explorepy bin2edf -f input_file.BIN -ow
|
|
137
130
|
|
|
138
|
-
calibrate-orn
|
|
139
|
-
%%%%
|
|
140
|
-
|
|
141
|
-
Calibrates the orientation module of a device. This module stores calibration parameters in ``explorepy``'s configuration file. Once calibrated, ``explorepy`` computes the device's orientation (degree and rotation axis).
|
|
142
|
-
::
|
|
143
|
-
|
|
144
|
-
Options:
|
|
145
|
-
-a, --address TEXT Explore device's MAC address
|
|
146
|
-
-n, --name TEXT Name of the device
|
|
147
|
-
-ow, --overwrite Overwrite existing file
|
|
148
|
-
-h, --help Show this message and exit.
|
|
149
|
-
|
|
150
131
|
|
|
151
132
|
format-memory
|
|
152
133
|
%%%%
|
|
@@ -181,18 +162,6 @@ Example:
|
|
|
181
162
|
::
|
|
182
163
|
explorepy set-sampling-rate -n Explore_XXXX -sr 500
|
|
183
164
|
|
|
184
|
-
disable-module
|
|
185
|
-
%%%%
|
|
186
|
-
|
|
187
|
-
Disables a device module (orientation, environment and ExG).
|
|
188
|
-
::
|
|
189
|
-
|
|
190
|
-
Options:
|
|
191
|
-
-a, --address TEXT Explore device's MAC address
|
|
192
|
-
-n, --name TEXT Name of the device
|
|
193
|
-
-m, --module TEXT Module name to be disabled, options: ORN, ENV, EXG
|
|
194
|
-
[required]
|
|
195
|
-
|
|
196
165
|
|
|
197
166
|
soft-reset
|
|
198
167
|
%%%%
|
|
@@ -254,6 +223,10 @@ You can record data in realtime to EDF (BDF+) or CSV files using:
|
|
|
254
223
|
::
|
|
255
224
|
explore.record_data(file_name='test', duration=120, file_type='csv')
|
|
256
225
|
|
|
226
|
+
To also record impedance data, use:
|
|
227
|
+
::
|
|
228
|
+
explore.record_data(file_name='test', duration=120, file_type='csv', imp_mode=True)
|
|
229
|
+
|
|
257
230
|
This will record data in three separate files: "``test_ExG.csv``", "``test_ORN.csv``" and "``test_marker.csv``", which contain ExG data, orientation data (accelerometer, gyroscope, magnetometer) and event markers respectively. Add command arguments to overwrite files and set the duration of the recording (in seconds).
|
|
258
231
|
::
|
|
259
232
|
explore.record_data(file_name='test', do_overwrite=True, file_type='csv', duration=120)
|
|
@@ -408,34 +381,100 @@ Impedance data acquisition in real-time
|
|
|
408
381
|
"""""""""""""""""""""""""""""""""""""""
|
|
409
382
|
::
|
|
410
383
|
|
|
411
|
-
|
|
412
|
-
|
|
384
|
+
import argparse
|
|
385
|
+
import csv
|
|
413
386
|
import time
|
|
387
|
+
|
|
388
|
+
import numpy as np
|
|
389
|
+
import pandas as pd
|
|
390
|
+
import matplotlib.pyplot as plt
|
|
391
|
+
from scipy import signal
|
|
392
|
+
|
|
414
393
|
import explorepy
|
|
415
394
|
from explorepy.stream_processor import TOPICS
|
|
416
395
|
|
|
417
396
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
397
|
+
# ----------------------------- Argument Parsing ----------------------------- #
|
|
398
|
+
parser = argparse.ArgumentParser(description="Acquire and filter impedance-mode ExG data from an Explore device.")
|
|
399
|
+
parser.add_argument("--device-name", required=True, help="Name of the Explore device (e.g., Explore_AAXX)")
|
|
400
|
+
args = parser.parse_args()
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
# ----------------------------- Configuration ----------------------------- #
|
|
404
|
+
FS = 250 # Sampling rate in Hz
|
|
405
|
+
CHANNEL_LABELS = [f"ch{i}" for i in range(1, 33)]
|
|
406
|
+
OUTPUT_FILENAME = "exg_data_imp_mode.csv"
|
|
407
|
+
RECORD_SECONDS = 40
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# ----------------------------- CSV Setup ----------------------------- #
|
|
411
|
+
csv_file = open(OUTPUT_FILENAME, 'w', newline='\n')
|
|
412
|
+
csv_writer = csv.writer(csv_file, delimiter=",")
|
|
413
|
+
csv_writer.writerow(['Timestamp'] + CHANNEL_LABELS[:8]) # Log only first 8 channels
|
|
414
|
+
|
|
422
415
|
|
|
416
|
+
# ----------------------------- Packet Handlers ----------------------------- #
|
|
417
|
+
def handle_exg_packet(packet):
|
|
418
|
+
"""Callback to handle incoming ExG data packets."""
|
|
419
|
+
timestamps, signals = packet.get_data(FS)
|
|
420
|
+
data = np.concatenate((np.array(timestamps)[:, np.newaxis].T, np.array(signals)), axis=0)
|
|
421
|
+
np.savetxt(csv_file, np.round(data.T, 4), fmt='%4f', delimiter=',')
|
|
423
422
|
|
|
424
|
-
exp_device = explorepy.Explore()
|
|
425
423
|
|
|
426
|
-
|
|
427
|
-
|
|
424
|
+
def handle_impedance_packet(packet):
|
|
425
|
+
"""Callback to handle incoming impedance packets."""
|
|
426
|
+
impedance_values = packet.get_impedances()
|
|
427
|
+
print("Impedance:", impedance_values)
|
|
428
428
|
|
|
429
|
-
exp_device.stream_processor.subscribe(callback=handle_imp, topic=TOPICS.imp)
|
|
430
429
|
|
|
431
|
-
#
|
|
432
|
-
|
|
430
|
+
# ----------------------------- Device Initialization ----------------------------- #
|
|
431
|
+
device = explorepy.Explore()
|
|
432
|
+
device.connect(args.device_name)
|
|
433
|
+
device.stream_processor.subscribe(callback=handle_impedance_packet, topic=TOPICS.imp)
|
|
434
|
+
device.stream_processor.subscribe(callback=handle_exg_packet, topic=TOPICS.raw_ExG)
|
|
435
|
+
device.stream_processor.imp_initialize(notch_freq=50)
|
|
433
436
|
|
|
434
|
-
|
|
435
|
-
|
|
437
|
+
|
|
438
|
+
# ----------------------------- Data Acquisition Loop ----------------------------- #
|
|
439
|
+
for _ in range(RECORD_SECONDS):
|
|
436
440
|
time.sleep(1)
|
|
437
|
-
count += 1
|
|
438
441
|
|
|
439
|
-
|
|
440
|
-
|
|
442
|
+
|
|
443
|
+
# ----------------------------- Cleanup ----------------------------- #
|
|
444
|
+
device.stream_processor.disable_imp()
|
|
445
|
+
device.stream_processor.unsubscribe(callback=handle_impedance_packet, topic=TOPICS.imp)
|
|
446
|
+
device.stream_processor.unsubscribe(callback=handle_exg_packet, topic=TOPICS.raw_ExG)
|
|
447
|
+
csv_file.close()
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ----------------------------- Signal Processing ----------------------------- #
|
|
451
|
+
def apply_bandpass_filter(signal_data, low_freq, high_freq, fs, order=3):
|
|
452
|
+
"""Apply a Butterworth bandpass filter to the signal."""
|
|
453
|
+
b, a = signal.butter(order, [low_freq / fs, high_freq / fs], btype='bandpass')
|
|
454
|
+
return signal.filtfilt(b, a, signal_data)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def apply_notch_filter(signal_data, fs, freq=50, quality_factor=30.0):
|
|
458
|
+
"""Apply a notch filter at the specified frequency."""
|
|
459
|
+
b, a = signal.iirnotch(freq, quality_factor, fs)
|
|
460
|
+
return signal.filtfilt(b, a, signal_data)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
# ----------------------------- Load and Filter Data ----------------------------- #
|
|
464
|
+
df = pd.read_csv(OUTPUT_FILENAME, delimiter=',', dtype=np.float64)
|
|
465
|
+
raw_ch1 = df['ch1']
|
|
466
|
+
filtered = apply_notch_filter(raw_ch1, FS, freq=62.5)
|
|
467
|
+
filtered = apply_notch_filter(filtered, FS, freq=50)
|
|
468
|
+
filtered = apply_bandpass_filter(filtered, low_freq=0.5, high_freq=30, fs=FS)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
# ----------------------------- Plot Filtered Signal ----------------------------- #
|
|
472
|
+
plt.figure(figsize=(10, 4))
|
|
473
|
+
plt.plot(df['Timestamp'], filtered, label='Filtered ch1')
|
|
474
|
+
plt.xlabel("Time (s)")
|
|
475
|
+
plt.ylabel("Amplitude (µV)")
|
|
476
|
+
plt.title("Filtered EEG Signal - Channel 1")
|
|
477
|
+
plt.grid(True)
|
|
478
|
+
plt.tight_layout()
|
|
479
|
+
plt.show()
|
|
441
480
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import atexit
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
6
7
|
from queue import (
|
|
@@ -189,16 +190,20 @@ class BLEClient(BTClient):
|
|
|
189
190
|
def disconnect(self):
|
|
190
191
|
"""Disconnect from the device"""
|
|
191
192
|
self.is_connected = False
|
|
192
|
-
|
|
193
|
+
if os.name != 'nt':
|
|
194
|
+
if self.client and self.client.is_connected:
|
|
195
|
+
try:
|
|
196
|
+
asyncio.run(self.client.stop_notify(self.eeg_tx_char_uuid))
|
|
197
|
+
except RuntimeError:
|
|
198
|
+
# nothing to do here, this works even though there is an exception
|
|
199
|
+
pass
|
|
200
|
+
if self.notify_task:
|
|
201
|
+
self.notify_task.cancel()
|
|
193
202
|
self.read_event.set()
|
|
194
203
|
time.sleep(1)
|
|
195
204
|
self.stop_read_loop()
|
|
196
205
|
self.ble_device = None
|
|
197
206
|
self.buffer = Queue()
|
|
198
|
-
logger.info('ExplorePy disconnecting from device')
|
|
199
|
-
|
|
200
|
-
def _find_mac_address(self):
|
|
201
|
-
raise NotImplementedError
|
|
202
207
|
|
|
203
208
|
def read(self, n_bytes):
|
|
204
209
|
"""Read n_bytes from the socket
|
|
@@ -221,7 +226,8 @@ class BLEClient(BTClient):
|
|
|
221
226
|
raise ConnectionAbortedError('Error reading data from BLE stream, too many bytes requested')
|
|
222
227
|
return ret
|
|
223
228
|
except Empty:
|
|
224
|
-
|
|
229
|
+
if self.is_connected:
|
|
230
|
+
raise ConnectionAbortedError
|
|
225
231
|
except Exception as error:
|
|
226
232
|
logger.error('Unknown error reading data from BLE stream, error is {}'.format(error))
|
|
227
233
|
raise ConnectionAbortedError(str(error))
|
|
@@ -12,7 +12,6 @@ class BTClient(abc.ABC):
|
|
|
12
12
|
self.mac_address = mac_address
|
|
13
13
|
self.device_name = device_name
|
|
14
14
|
self.bt_serial_port_manager = None
|
|
15
|
-
self.device_manager = None
|
|
16
15
|
|
|
17
16
|
@abc.abstractmethod
|
|
18
17
|
def connect(self):
|
|
@@ -34,10 +33,6 @@ class BTClient(abc.ABC):
|
|
|
34
33
|
def disconnect(self):
|
|
35
34
|
"""Disconnect from the device"""
|
|
36
35
|
|
|
37
|
-
@abc.abstractmethod
|
|
38
|
-
def _find_mac_address(self):
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
36
|
@abc.abstractmethod
|
|
42
37
|
def read(self, n_bytes):
|
|
43
38
|
"""Read n_bytes from the socket
|
|
@@ -56,7 +51,3 @@ class BTClient(abc.ABC):
|
|
|
56
51
|
Args:
|
|
57
52
|
data (bytearray): Data to be sent
|
|
58
53
|
"""
|
|
59
|
-
|
|
60
|
-
@staticmethod
|
|
61
|
-
def _check_mac_address(device_name, mac_address):
|
|
62
|
-
return (device_name[-4:-2] == mac_address[-5:-3]) and (device_name[-2:] == mac_address[-2:])
|
|
@@ -12,8 +12,6 @@ import explorepy
|
|
|
12
12
|
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
|
-
default_bt_backend = explorepy.get_bt_interface()
|
|
16
|
-
|
|
17
15
|
|
|
18
16
|
@click.group(context_settings=CONTEXT_SETTINGS, invoke_without_command=True)
|
|
19
17
|
@click.option("--version", "-V", help="Print explorepy version", is_flag=True)
|
|
@@ -56,13 +54,6 @@ def verify_inputs(func):
|
|
|
56
54
|
return update_wrapper(wrapper, func)
|
|
57
55
|
|
|
58
56
|
|
|
59
|
-
@cli.command()
|
|
60
|
-
def find_device():
|
|
61
|
-
"""List available Explore devices"""
|
|
62
|
-
explorepy.set_bt_interface(default_bt_backend)
|
|
63
|
-
explorepy.tools.run_ble_scanner()
|
|
64
|
-
|
|
65
|
-
|
|
66
57
|
@cli.command()
|
|
67
58
|
@click.option("--address", "-a", type=str, help="Explore device's MAC address")
|
|
68
59
|
@click.option("--name", "-n", type=str, help="Name of the device")
|
|
@@ -84,13 +75,22 @@ def acquire(name, address, duration):
|
|
|
84
75
|
@click.option("-d", "--duration", type=int, help="Recording duration in seconds", metavar="<integer>")
|
|
85
76
|
@click.option("--edf", 'file_type', flag_value='edf', help="Write in EDF file")
|
|
86
77
|
@click.option("--csv", 'file_type', flag_value='csv', help="Write in csv file (default type)", default=True)
|
|
78
|
+
@click.option("--imp-mode", is_flag=True, help="Enable impedance mode with live monitoring")
|
|
79
|
+
@click.option("-nf", "--notch-freq", help="Notch frequency for impedance mode initialization", type=float, default=50.0)
|
|
87
80
|
@verify_inputs
|
|
88
|
-
def record_data(address, name, filename, overwrite, duration, file_type):
|
|
81
|
+
def record_data(address, name, filename, overwrite, duration, file_type, imp_mode, notch_freq):
|
|
89
82
|
"""Record data from Explore to a file"""
|
|
90
83
|
explore = explorepy.explore.Explore()
|
|
91
84
|
explore.connect(mac_address=address, device_name=name)
|
|
92
|
-
explore.record_data(
|
|
93
|
-
|
|
85
|
+
explore.record_data(
|
|
86
|
+
file_name=filename,
|
|
87
|
+
file_type=file_type,
|
|
88
|
+
do_overwrite=overwrite,
|
|
89
|
+
duration=duration,
|
|
90
|
+
imp_mode=imp_mode,
|
|
91
|
+
notch_freq=notch_freq,
|
|
92
|
+
block=True
|
|
93
|
+
)
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
@cli.command()
|
|
@@ -160,53 +160,3 @@ def soft_reset(address, name):
|
|
|
160
160
|
explore = explorepy.explore.Explore()
|
|
161
161
|
explore.connect(mac_address=address, device_name=name)
|
|
162
162
|
explore.reset_soft()
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@cli.command()
|
|
166
|
-
@click.option("--address", "-a", type=str, help="Explore device's MAC address")
|
|
167
|
-
@click.option("--name", "-n", type=str, help="Name of the device")
|
|
168
|
-
@click.option("-m", "--channel-mask", type=str, required=True,
|
|
169
|
-
help="Channel mask, it should be a binary string containing 1 and 0, "
|
|
170
|
-
"representing the mask (LSB is channel 1).")
|
|
171
|
-
@verify_inputs
|
|
172
|
-
def set_channels(address, name, channel_mask):
|
|
173
|
-
"""Mask the channels of the Explore device"""
|
|
174
|
-
explore = explorepy.explore.Explore()
|
|
175
|
-
explore.connect(mac_address=address, device_name=name)
|
|
176
|
-
explore.set_channels(channel_mask)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
@cli.command()
|
|
180
|
-
@click.option("--address", "-a", type=str, help="Explore device's MAC address")
|
|
181
|
-
@click.option("--name", "-n", type=str, help="Name of the device")
|
|
182
|
-
@click.option("-m", "--module", required=True, type=str, help="Module name to be disabled, options: ORN, ENV, EXG")
|
|
183
|
-
@verify_inputs
|
|
184
|
-
def disable_module(address, name, module):
|
|
185
|
-
"""Disable a module of Explore device"""
|
|
186
|
-
explore = explorepy.explore.Explore()
|
|
187
|
-
explore.connect(mac_address=address, device_name=name)
|
|
188
|
-
explore.disable_module(module)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
@cli.command()
|
|
192
|
-
@click.option("--address", "-a", type=str, help="Explore device's MAC address")
|
|
193
|
-
@click.option("--name", "-n", type=str, help="Name of the device")
|
|
194
|
-
@click.option("-m", "--module", required=True, type=str, help="Module name to be enabled, options: ORN, ENV, EXG")
|
|
195
|
-
@verify_inputs
|
|
196
|
-
def enable_module(address, name, module):
|
|
197
|
-
"""Enable a module of Explore device"""
|
|
198
|
-
explore = explorepy.explore.Explore()
|
|
199
|
-
explore.connect(mac_address=address, device_name=name)
|
|
200
|
-
explore.enable_module(module)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
@cli.command()
|
|
204
|
-
@click.option("--address", "-a", type=str, help="Explore device's MAC address")
|
|
205
|
-
@click.option("--name", "-n", type=str, help="Name of the device")
|
|
206
|
-
@click.option("-ow", "--overwrite", is_flag=True, help="Overwrite existing file")
|
|
207
|
-
@verify_inputs
|
|
208
|
-
def calibrate_orn(address, name, overwrite):
|
|
209
|
-
"""Calibrate the orientation module"""
|
|
210
|
-
explore = explorepy.explore.Explore()
|
|
211
|
-
explore.connect(mac_address=address, device_name=name)
|
|
212
|
-
explore.calibrate_orn(do_overwrite=overwrite)
|