dexter-controller 0.2.2__tar.gz → 0.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.
Files changed (26) hide show
  1. dexter_controller-0.3.0/PKG-INFO +266 -0
  2. dexter_controller-0.3.0/README.md +241 -0
  3. dexter_controller-0.3.0/pyproject.toml +37 -0
  4. dexter_controller-0.3.0/pyproject.toml.orig +35 -0
  5. dexter_controller-0.3.0/src/dexter_controller/__init__.py +44 -0
  6. dexter_controller-0.3.0/src/dexter_controller/ble_loadcell_device.py +400 -0
  7. dexter_controller-0.3.0/src/dexter_controller/calibration.py +99 -0
  8. dexter_controller-0.3.0/src/dexter_controller/data_recorder.py +209 -0
  9. {dexter_controller-0.2.2 → dexter_controller-0.3.0}/src/dexter_controller/dexter_hand_controller.py +290 -253
  10. dexter_controller-0.3.0/src/dexter_controller/finger.py +73 -0
  11. dexter_controller-0.3.0/src/dexter_controller/finger_orientation.py +38 -0
  12. dexter_controller-0.3.0/src/dexter_controller/force_converter.py +84 -0
  13. dexter_controller-0.3.0/src/dexter_controller/force_processor.py +60 -0
  14. {dexter_controller-0.2.2 → dexter_controller-0.3.0}/src/dexter_controller/loadcell_device.py +69 -54
  15. dexter_controller-0.3.0/src/dexter_controller/recording.py +42 -0
  16. dexter_controller-0.3.0/src/dexter_controller/recording_format.py +16 -0
  17. dexter_controller-0.3.0/src/dexter_controller/recording_metadata.py +137 -0
  18. dexter_controller-0.3.0/src/dexter_controller/recording_reader.py +270 -0
  19. dexter_controller-0.3.0/src/dexter_controller/tare.py +87 -0
  20. dexter_controller-0.3.0/src/dexter_controller/writers.py +258 -0
  21. dexter_controller-0.2.2/PKG-INFO +0 -57
  22. dexter_controller-0.2.2/README.md +0 -44
  23. dexter_controller-0.2.2/pyproject.toml +0 -21
  24. dexter_controller-0.2.2/src/dexter_controller/__init__.py +0 -12
  25. dexter_controller-0.2.2/src/dexter_controller/ble_loadcell_device.py +0 -152
  26. dexter_controller-0.2.2/src/dexter_controller/finger.py +0 -16
@@ -0,0 +1,266 @@
1
+ Metadata-Version: 2.3
2
+ Name: dexter-controller
3
+ Version: 0.3.0
4
+ Summary: Dexter device Controller
5
+ Author: Hardware and Software Platform, Champalimaud Foundation
6
+ Author-email: Hardware and Software Platform, Champalimaud Foundation <software@research.fchampalimaud.org>
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Dist: bleak==2.1.1
13
+ Requires-Dist: harp-loadcells>=0.1.0a3
14
+ Requires-Dist: pydoc-markdown>=4.8.2 ; extra == 'docs'
15
+ Requires-Dist: pyarrow>=14.0.0 ; extra == 'parquet'
16
+ Requires-Dist: dexter-controller[reader] ; extra == 'parquet'
17
+ Requires-Dist: numpy>=1.25.0 ; extra == 'reader'
18
+ Requires-Python: >=3.10
19
+ Project-URL: Repository, https://github.com/fchampalimaud/dexter-controller/
20
+ Project-URL: Bug Tracker, https://github.com/fchampalimaud/dexter-controller/issues
21
+ Provides-Extra: docs
22
+ Provides-Extra: parquet
23
+ Provides-Extra: reader
24
+ Description-Content-Type: text/markdown
25
+
26
+ # dexter-controller
27
+
28
+ [![PyPI](https://img.shields.io/pypi/v/dexter-controller)](https://pypi.org/project/dexter-controller/)
29
+ [![Python](https://img.shields.io/pypi/pyversions/dexter-controller)](https://pypi.org/project/dexter-controller/)
30
+
31
+ Python library for the **Dexter** hand device. This library handles device communication, force computation, calibration, and data recording.
32
+
33
+ ## Installation
34
+
35
+ The recommended way to install the library is via `uv`. In your project add the dependency with:
36
+
37
+ ```bash
38
+ uv add dexter-controller
39
+ # or use pip
40
+ pip install dexter-controller
41
+ ```
42
+
43
+ Optional extras for reading and writing recordings:
44
+
45
+ ```bash
46
+ uv add dexter-controller[reader] # numpy, for loading recordings
47
+ uv add dexter-controller[parquet] # pyarrow + numpy, for Parquet format support
48
+ # or use pip
49
+ pip install dexter-controller[reader] # numpy, for loading recordings
50
+ pip install dexter-controller[parquet] # pyarrow + numpy, for Parquet format support
51
+ ```
52
+
53
+ ## Quick start
54
+
55
+ At this moment, the recommended way to connect is over Bluetooth Low Energy. The controller auto-discovers and connects to the first Dexter device in range, but you can specify a particular device by name:
56
+
57
+ ```python
58
+ import time
59
+ from dexter_controller import DexterHandController, Finger
60
+
61
+ controller = DexterHandController(use_ble=True,
62
+ # if you want to connect to a specific device by name
63
+ #ble_device_name="Dexter-001"
64
+ )
65
+
66
+ device_name = controller.get_device_name()
67
+ battery = controller.get_battery_level()
68
+ rate = controller.get_sampling_rate()
69
+ print(f"Device: {device_name} | Battery: {battery}% | Rate: {rate.value} Hz")
70
+
71
+ try:
72
+ while True:
73
+ for finger in Finger:
74
+ data = controller.finger_data[finger]
75
+ print(f"{finger.name}: {data.raw_data}", end=" ")
76
+ print(end="\r", flush=True)
77
+ time.sleep(0.02)
78
+ except KeyboardInterrupt:
79
+ pass
80
+ finally:
81
+ controller.close()
82
+ ```
83
+
84
+ ## Force computation
85
+
86
+ Convert raw load-cell readings into calibrated 2D force vectors (Fx, Fy):
87
+
88
+ ```python
89
+ from dexter_controller import (
90
+ DEFAULT_CALIBRATION_3CH,
91
+ DexterHandController,
92
+ Finger,
93
+ ForceProcessor,
94
+ get_default_orientation,
95
+ )
96
+
97
+ controller = DexterHandController(use_ble=True)
98
+
99
+ processors = {}
100
+ for finger in Finger:
101
+ orientation = get_default_orientation(finger)
102
+ processors[finger] = ForceProcessor(DEFAULT_CALIBRATION_3CH, orientation)
103
+
104
+ # In your read loop:
105
+ data = controller.finger_data[Finger.INDEX]
106
+ fx, fy = processors[Finger.INDEX].process_3(data)
107
+ ```
108
+
109
+ Each `ForceProcessor` includes a `TareState` that lets you zero-out the baseline:
110
+
111
+ ```python
112
+ processors[Finger.INDEX].tare.start_tare(sample_count=20)
113
+ ```
114
+
115
+ ## Recording data
116
+
117
+ Record sessions to disk in CSV, SQLite (**default**), or Parquet format:
118
+
119
+ ```python
120
+ from dexter_controller import (
121
+ DEFAULT_CALIBRATION_3CH,
122
+ DataRecorder,
123
+ DexterHandController,
124
+ Finger,
125
+ ForceProcessor,
126
+ RecordingMetadata,
127
+ get_default_orientation,
128
+ )
129
+ from dexter_controller.recording_format import RecordingFormat
130
+
131
+ controller = DexterHandController(use_ble=True)
132
+
133
+ processors = {}
134
+ for finger in Finger:
135
+ processors[finger] = ForceProcessor(
136
+ DEFAULT_CALIBRATION_3CH, get_default_orientation(finger)
137
+ )
138
+
139
+ recorder = DataRecorder(
140
+ "./recordings",
141
+ file_format=RecordingFormat.SQLITE,
142
+ metadata=RecordingMetadata(
143
+ device_name=controller.get_device_name(),
144
+ sampling_rate_hz=controller.get_sampling_rate().value,
145
+ ),
146
+ )
147
+
148
+ def on_sample(_data):
149
+ fingers = [controller.finger_data[f] for f in Finger]
150
+ forces = [processors[f].process_3(controller.finger_data[f]) for f in Finger]
151
+ baselines = [(processors[f].tare.baseline_x, processors[f].tare.baseline_y) for f in Finger]
152
+ recorder.write_sample(fingers, forces, baselines)
153
+
154
+ controller.register_finger_callback(Finger.PINKY, on_sample)
155
+
156
+ recorder.start_recording()
157
+ # ... run until done ...
158
+ recorder.stop_recording()
159
+ controller.close()
160
+ ```
161
+
162
+ Each recording file includes session metadata.
163
+
164
+ Session metadata has the following fields:
165
+
166
+ - `session_id`: Unique identifier for the recording session.
167
+ - `recording_started_at`: Timestamp when the recording started.
168
+ - `recording_stopped_at`: Timestamp when the recording stopped.
169
+ - `total_samples`: Total number of samples recorded.
170
+ - `library_version`: Version of the library used for recording.
171
+ - `timestamps`: Array of timestamps for each sample.
172
+ - `device_name`: Name of the device used for recording.
173
+ - `sampling_rate_hz`: Sampling rate in Hz.
174
+ - `extras`: Dictionary for experiment-specific metadata.
175
+
176
+ You can attach experiment-specific metadata via `RecordingMetadata.extras`:
177
+
178
+ ```python
179
+ metadata = RecordingMetadata(
180
+ device_name="Dexter-001",
181
+ sampling_rate_hz=1000,
182
+ extras={"subject_id": "S01", "trial": "3", "condition": "baseline"},
183
+ )
184
+ ```
185
+
186
+ ## Reading recordings
187
+
188
+ Load recorded sessions back for analysis:
189
+
190
+ ```python
191
+ from dexter_controller import RecordingReader
192
+
193
+ recording = RecordingReader.read("recordings/session.db")
194
+ print(f"Samples: {recording.num_rows}")
195
+ print(f"Session: {recording.metadata.session_id}")
196
+ print(f"Forces shape: {recording.forces.shape}") # (N, 10) - Fx,Fy per finger
197
+ print(f"Raw channels: {recording.raw_channels.shape}") # (N, 15) - 3 channels per finger
198
+ ```
199
+
200
+ For either the Sqlite and Parquet formats, you can use Pandas or Polars to load the data for analysis.
201
+
202
+ ```python
203
+ import pandas as pd
204
+
205
+ # for Sqlite format, use:
206
+ conn = RecordingReader.open("recordings/session.db")
207
+ df = pd.read_sql("SELECT * FROM samples", conn)
208
+ print(df.head())
209
+
210
+ # or for Parquet format, use:
211
+ df = pd.read_parquet("recordings/session.parquet")
212
+ print(df.head())
213
+ ```
214
+
215
+ You can also convert those formats to CSV for easier analysis or interoperability:
216
+
217
+ ```python
218
+ RecordingReader.to_csv("recordings/session.db") # produces session.csv
219
+ ```
220
+
221
+ ## BLE device configuration
222
+
223
+ When connected via BLE, you can read and write device settings:
224
+
225
+ ```python
226
+ from dexter_controller import SamplingRate
227
+
228
+ controller.get_device_name() # read name
229
+ controller.set_device_name("MyDexter") # rename device
230
+ controller.get_sampling_rate() # current rate
231
+ controller.set_sampling_rate(SamplingRate.HZ_1000) # change rate
232
+ controller.get_battery_level() # battery percentage
233
+ ```
234
+
235
+ > [!WARNING]
236
+ > The Visualizer application requires that the device's name must include "Dexter" within the name (e.g., "MyDexter").
237
+
238
+ Available sampling rates: 100, 200, 400, 800, 1000, 2000 Hz.
239
+
240
+ ## Examples
241
+
242
+ See the [example/](example/) directory for complete, runnable scripts:
243
+
244
+ | File | Description |
245
+ | ------------------------------------------------------------ | --------------------------------------------------------------------------------- |
246
+ | [example_ble.py](example/example_ble.py) | BLE connection with raw data streaming and event rate monitoring |
247
+ | [example_ble_force.py](example/example_ble_force.py) | BLE connection with real-time force computation using `ForceProcessor` |
248
+ | [example_ble_recording.py](example/example_ble_recording.py) | Full recording session: BLE connect, force processing, and data recording to file |
249
+ | [example_serial.py](example/example_serial.py) | Serial (wired) connection with multi-port finger mapping |
250
+
251
+ ## Serial connection (old prototype only, to be deprecated)
252
+
253
+ For wired setups using Harp load-cell boards, provide a port-to-finger mapping:
254
+
255
+ ```python
256
+ from dexter_controller import DexterHandController, Finger
257
+
258
+ mapping = {
259
+ "COM3": [Finger.THUMB, Finger.INDEX], # /dev/ttyUSB0 on Linux
260
+ "COM4": [Finger.MIDDLE, Finger.RING],
261
+ "COM5": [Finger.PINKY],
262
+ }
263
+ controller = DexterHandController(mapping)
264
+ ```
265
+
266
+ Each serial device supports up to two fingers (8 channels, 4 per finger).
@@ -0,0 +1,241 @@
1
+ # dexter-controller
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/dexter-controller)](https://pypi.org/project/dexter-controller/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/dexter-controller)](https://pypi.org/project/dexter-controller/)
5
+
6
+ Python library for the **Dexter** hand device. This library handles device communication, force computation, calibration, and data recording.
7
+
8
+ ## Installation
9
+
10
+ The recommended way to install the library is via `uv`. In your project add the dependency with:
11
+
12
+ ```bash
13
+ uv add dexter-controller
14
+ # or use pip
15
+ pip install dexter-controller
16
+ ```
17
+
18
+ Optional extras for reading and writing recordings:
19
+
20
+ ```bash
21
+ uv add dexter-controller[reader] # numpy, for loading recordings
22
+ uv add dexter-controller[parquet] # pyarrow + numpy, for Parquet format support
23
+ # or use pip
24
+ pip install dexter-controller[reader] # numpy, for loading recordings
25
+ pip install dexter-controller[parquet] # pyarrow + numpy, for Parquet format support
26
+ ```
27
+
28
+ ## Quick start
29
+
30
+ At this moment, the recommended way to connect is over Bluetooth Low Energy. The controller auto-discovers and connects to the first Dexter device in range, but you can specify a particular device by name:
31
+
32
+ ```python
33
+ import time
34
+ from dexter_controller import DexterHandController, Finger
35
+
36
+ controller = DexterHandController(use_ble=True,
37
+ # if you want to connect to a specific device by name
38
+ #ble_device_name="Dexter-001"
39
+ )
40
+
41
+ device_name = controller.get_device_name()
42
+ battery = controller.get_battery_level()
43
+ rate = controller.get_sampling_rate()
44
+ print(f"Device: {device_name} | Battery: {battery}% | Rate: {rate.value} Hz")
45
+
46
+ try:
47
+ while True:
48
+ for finger in Finger:
49
+ data = controller.finger_data[finger]
50
+ print(f"{finger.name}: {data.raw_data}", end=" ")
51
+ print(end="\r", flush=True)
52
+ time.sleep(0.02)
53
+ except KeyboardInterrupt:
54
+ pass
55
+ finally:
56
+ controller.close()
57
+ ```
58
+
59
+ ## Force computation
60
+
61
+ Convert raw load-cell readings into calibrated 2D force vectors (Fx, Fy):
62
+
63
+ ```python
64
+ from dexter_controller import (
65
+ DEFAULT_CALIBRATION_3CH,
66
+ DexterHandController,
67
+ Finger,
68
+ ForceProcessor,
69
+ get_default_orientation,
70
+ )
71
+
72
+ controller = DexterHandController(use_ble=True)
73
+
74
+ processors = {}
75
+ for finger in Finger:
76
+ orientation = get_default_orientation(finger)
77
+ processors[finger] = ForceProcessor(DEFAULT_CALIBRATION_3CH, orientation)
78
+
79
+ # In your read loop:
80
+ data = controller.finger_data[Finger.INDEX]
81
+ fx, fy = processors[Finger.INDEX].process_3(data)
82
+ ```
83
+
84
+ Each `ForceProcessor` includes a `TareState` that lets you zero-out the baseline:
85
+
86
+ ```python
87
+ processors[Finger.INDEX].tare.start_tare(sample_count=20)
88
+ ```
89
+
90
+ ## Recording data
91
+
92
+ Record sessions to disk in CSV, SQLite (**default**), or Parquet format:
93
+
94
+ ```python
95
+ from dexter_controller import (
96
+ DEFAULT_CALIBRATION_3CH,
97
+ DataRecorder,
98
+ DexterHandController,
99
+ Finger,
100
+ ForceProcessor,
101
+ RecordingMetadata,
102
+ get_default_orientation,
103
+ )
104
+ from dexter_controller.recording_format import RecordingFormat
105
+
106
+ controller = DexterHandController(use_ble=True)
107
+
108
+ processors = {}
109
+ for finger in Finger:
110
+ processors[finger] = ForceProcessor(
111
+ DEFAULT_CALIBRATION_3CH, get_default_orientation(finger)
112
+ )
113
+
114
+ recorder = DataRecorder(
115
+ "./recordings",
116
+ file_format=RecordingFormat.SQLITE,
117
+ metadata=RecordingMetadata(
118
+ device_name=controller.get_device_name(),
119
+ sampling_rate_hz=controller.get_sampling_rate().value,
120
+ ),
121
+ )
122
+
123
+ def on_sample(_data):
124
+ fingers = [controller.finger_data[f] for f in Finger]
125
+ forces = [processors[f].process_3(controller.finger_data[f]) for f in Finger]
126
+ baselines = [(processors[f].tare.baseline_x, processors[f].tare.baseline_y) for f in Finger]
127
+ recorder.write_sample(fingers, forces, baselines)
128
+
129
+ controller.register_finger_callback(Finger.PINKY, on_sample)
130
+
131
+ recorder.start_recording()
132
+ # ... run until done ...
133
+ recorder.stop_recording()
134
+ controller.close()
135
+ ```
136
+
137
+ Each recording file includes session metadata.
138
+
139
+ Session metadata has the following fields:
140
+
141
+ - `session_id`: Unique identifier for the recording session.
142
+ - `recording_started_at`: Timestamp when the recording started.
143
+ - `recording_stopped_at`: Timestamp when the recording stopped.
144
+ - `total_samples`: Total number of samples recorded.
145
+ - `library_version`: Version of the library used for recording.
146
+ - `timestamps`: Array of timestamps for each sample.
147
+ - `device_name`: Name of the device used for recording.
148
+ - `sampling_rate_hz`: Sampling rate in Hz.
149
+ - `extras`: Dictionary for experiment-specific metadata.
150
+
151
+ You can attach experiment-specific metadata via `RecordingMetadata.extras`:
152
+
153
+ ```python
154
+ metadata = RecordingMetadata(
155
+ device_name="Dexter-001",
156
+ sampling_rate_hz=1000,
157
+ extras={"subject_id": "S01", "trial": "3", "condition": "baseline"},
158
+ )
159
+ ```
160
+
161
+ ## Reading recordings
162
+
163
+ Load recorded sessions back for analysis:
164
+
165
+ ```python
166
+ from dexter_controller import RecordingReader
167
+
168
+ recording = RecordingReader.read("recordings/session.db")
169
+ print(f"Samples: {recording.num_rows}")
170
+ print(f"Session: {recording.metadata.session_id}")
171
+ print(f"Forces shape: {recording.forces.shape}") # (N, 10) - Fx,Fy per finger
172
+ print(f"Raw channels: {recording.raw_channels.shape}") # (N, 15) - 3 channels per finger
173
+ ```
174
+
175
+ For either the Sqlite and Parquet formats, you can use Pandas or Polars to load the data for analysis.
176
+
177
+ ```python
178
+ import pandas as pd
179
+
180
+ # for Sqlite format, use:
181
+ conn = RecordingReader.open("recordings/session.db")
182
+ df = pd.read_sql("SELECT * FROM samples", conn)
183
+ print(df.head())
184
+
185
+ # or for Parquet format, use:
186
+ df = pd.read_parquet("recordings/session.parquet")
187
+ print(df.head())
188
+ ```
189
+
190
+ You can also convert those formats to CSV for easier analysis or interoperability:
191
+
192
+ ```python
193
+ RecordingReader.to_csv("recordings/session.db") # produces session.csv
194
+ ```
195
+
196
+ ## BLE device configuration
197
+
198
+ When connected via BLE, you can read and write device settings:
199
+
200
+ ```python
201
+ from dexter_controller import SamplingRate
202
+
203
+ controller.get_device_name() # read name
204
+ controller.set_device_name("MyDexter") # rename device
205
+ controller.get_sampling_rate() # current rate
206
+ controller.set_sampling_rate(SamplingRate.HZ_1000) # change rate
207
+ controller.get_battery_level() # battery percentage
208
+ ```
209
+
210
+ > [!WARNING]
211
+ > The Visualizer application requires that the device's name must include "Dexter" within the name (e.g., "MyDexter").
212
+
213
+ Available sampling rates: 100, 200, 400, 800, 1000, 2000 Hz.
214
+
215
+ ## Examples
216
+
217
+ See the [example/](example/) directory for complete, runnable scripts:
218
+
219
+ | File | Description |
220
+ | ------------------------------------------------------------ | --------------------------------------------------------------------------------- |
221
+ | [example_ble.py](example/example_ble.py) | BLE connection with raw data streaming and event rate monitoring |
222
+ | [example_ble_force.py](example/example_ble_force.py) | BLE connection with real-time force computation using `ForceProcessor` |
223
+ | [example_ble_recording.py](example/example_ble_recording.py) | Full recording session: BLE connect, force processing, and data recording to file |
224
+ | [example_serial.py](example/example_serial.py) | Serial (wired) connection with multi-port finger mapping |
225
+
226
+ ## Serial connection (old prototype only, to be deprecated)
227
+
228
+ For wired setups using Harp load-cell boards, provide a port-to-finger mapping:
229
+
230
+ ```python
231
+ from dexter_controller import DexterHandController, Finger
232
+
233
+ mapping = {
234
+ "COM3": [Finger.THUMB, Finger.INDEX], # /dev/ttyUSB0 on Linux
235
+ "COM4": [Finger.MIDDLE, Finger.RING],
236
+ "COM5": [Finger.PINKY],
237
+ }
238
+ controller = DexterHandController(mapping)
239
+ ```
240
+
241
+ Each serial device supports up to two fingers (8 channels, 4 per finger).
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "dexter-controller"
3
+ version = "0.3.0"
4
+ description = "Dexter device Controller"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ classifiers = [
8
+ "Programming Language :: Python :: 3",
9
+ "Programming Language :: Python :: 3.10",
10
+ "Programming Language :: Python :: 3.11",
11
+ "Programming Language :: Python :: 3.12",
12
+ "Programming Language :: Python :: 3.13",
13
+ ]
14
+ dependencies = [
15
+ "bleak==2.1.1",
16
+ "harp-loadcells>=0.1.0a3",
17
+ ]
18
+
19
+ [[project.authors]]
20
+ name = "Hardware and Software Platform, Champalimaud Foundation"
21
+ email = "software@research.fchampalimaud.org"
22
+
23
+ [project.optional-dependencies]
24
+ reader = ["numpy>=1.25.0"]
25
+ parquet = [
26
+ "pyarrow>=14.0.0",
27
+ "dexter-controller[reader]",
28
+ ]
29
+ docs = ["pydoc-markdown>=4.8.2"]
30
+
31
+ [project.urls]
32
+ Repository = "https://github.com/fchampalimaud/dexter-controller/"
33
+ "Bug Tracker" = "https://github.com/fchampalimaud/dexter-controller/issues"
34
+
35
+ [build-system]
36
+ requires = ["uv_build>=0.9.5,<0.13.0"]
37
+ build-backend = "uv_build"
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "dexter-controller"
3
+ version = "0.3.0"
4
+ description = "Dexter device Controller"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Hardware and Software Platform, Champalimaud Foundation", email = "software@research.fchampalimaud.org" }
8
+ ]
9
+ requires-python = ">=3.10"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3.10",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ ]
17
+ dependencies = [
18
+ "bleak==2.1.1",
19
+ "harp-loadcells>=0.1.0a3",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ reader = ["numpy>=1.25.0"]
24
+ parquet = ["pyarrow>=14.0.0", "dexter-controller[reader]"]
25
+ docs = [
26
+ "pydoc-markdown>=4.8.2",
27
+ ]
28
+
29
+ [project.urls]
30
+ Repository = "https://github.com/fchampalimaud/dexter-controller/"
31
+ "Bug Tracker" = "https://github.com/fchampalimaud/dexter-controller/issues"
32
+
33
+ [build-system]
34
+ requires = ["uv_build>=0.9.5,<0.13.0"]
35
+ build-backend = "uv_build"
@@ -0,0 +1,44 @@
1
+ from .ble_loadcell_device import BLELoadCellDevice, DataMode, SamplingRate
2
+ from .calibration import (
3
+ DEFAULT_CALIBRATION,
4
+ DEFAULT_CALIBRATION_3CH,
5
+ CalibrationProfile,
6
+ LoadCellCalibration,
7
+ create_3channel,
8
+ )
9
+ from .data_recorder import DataRecorder
10
+ from .dexter_hand_controller import DexterHandController
11
+ from .finger import Finger, FingerData
12
+ from .finger_orientation import FingerOrientation
13
+ from .finger_orientation import get_default as get_default_orientation
14
+ from .force_processor import ForceProcessor
15
+ from .loadcell_device import LoadCellDevice
16
+ from .recording import Recording
17
+ from .recording_format import RecordingFormat
18
+ from .recording_metadata import RecordingMetadata
19
+ from .recording_reader import RecordingReader
20
+ from .tare import TareState
21
+
22
+ __all__ = [
23
+ "DEFAULT_CALIBRATION",
24
+ "DEFAULT_CALIBRATION_3CH",
25
+ "BLELoadCellDevice",
26
+ "CalibrationProfile",
27
+ "DataMode",
28
+ "DataRecorder",
29
+ "DexterHandController",
30
+ "Finger",
31
+ "FingerData",
32
+ "FingerOrientation",
33
+ "ForceProcessor",
34
+ "LoadCellCalibration",
35
+ "LoadCellDevice",
36
+ "Recording",
37
+ "RecordingFormat",
38
+ "RecordingMetadata",
39
+ "RecordingReader",
40
+ "SamplingRate",
41
+ "TareState",
42
+ "create_3channel",
43
+ "get_default_orientation",
44
+ ]