py-neuromodulation 0.0.3__py3-none-any.whl → 0.0.4__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.
- py_neuromodulation/ConnectivityDecoding/Automated Anatomical Labeling 3 (Rolls 2020).nii +0 -0
- py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -0
- py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +106 -0
- py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +119 -0
- py_neuromodulation/ConnectivityDecoding/mni_coords_cortical_surface.mat +0 -0
- py_neuromodulation/ConnectivityDecoding/mni_coords_whole_brain.mat +0 -0
- py_neuromodulation/ConnectivityDecoding/rmap_func_all.nii +0 -0
- py_neuromodulation/ConnectivityDecoding/rmap_struc.nii +0 -0
- py_neuromodulation/data/README +6 -0
- py_neuromodulation/data/dataset_description.json +8 -0
- py_neuromodulation/data/participants.json +32 -0
- py_neuromodulation/data/participants.tsv +2 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -0
- py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -0
- py_neuromodulation/grid_cortex.tsv +40 -0
- py_neuromodulation/grid_subcortex.tsv +1429 -0
- py_neuromodulation/nm_settings.json +338 -0
- py_neuromodulation/nm_stream_offline.py +7 -6
- py_neuromodulation/plots/STN_surf.mat +0 -0
- py_neuromodulation/plots/Vertices.mat +0 -0
- py_neuromodulation/plots/faces.mat +0 -0
- py_neuromodulation/plots/grid.mat +0 -0
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info}/METADATA +182 -182
- py_neuromodulation-0.0.4.dist-info/RECORD +72 -0
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info}/WHEEL +1 -2
- docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
- docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -233
- docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
- docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
- docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
- docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -239
- docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
- docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
- docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -76
- docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +0 -97
- docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -240
- docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +0 -233
- docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +0 -63
- docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
- docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +0 -210
- docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +0 -192
- docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +0 -219
- docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -121
- docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +0 -68
- docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
- docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -189
- docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
- docs/source/auto_examples/plot_0_first_demo.py +0 -189
- docs/source/auto_examples/plot_1_example_BIDS.py +0 -240
- docs/source/auto_examples/plot_2_example_add_feature.py +0 -76
- docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +0 -219
- docs/source/auto_examples/plot_4_example_gridPointProjection.py +0 -210
- docs/source/auto_examples/plot_5_example_rmap_computing.py +0 -64
- docs/source/auto_examples/plot_6_real_time_demo.py +0 -121
- docs/source/conf.py +0 -105
- examples/plot_0_first_demo.py +0 -189
- examples/plot_1_example_BIDS.py +0 -240
- examples/plot_2_example_add_feature.py +0 -76
- examples/plot_3_example_sharpwave_analysis.py +0 -219
- examples/plot_4_example_gridPointProjection.py +0 -210
- examples/plot_5_example_rmap_computing.py +0 -64
- examples/plot_6_real_time_demo.py +0 -121
- packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +0 -4
- packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +0 -104
- packages/realtime_decoding/build/lib/realtime_decoding/features.py +0 -163
- packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +0 -15
- packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +0 -345
- packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +0 -54
- packages/tmsi/build/lib/TMSiFileFormats/__init__.py +0 -37
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +0 -36
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +0 -200
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +0 -496
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +0 -236
- packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +0 -977
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +0 -35
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +0 -116
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +0 -294
- packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +0 -229
- packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +0 -102
- packages/tmsi/build/lib/TMSiPlotters/__init__.py +0 -2
- packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +0 -39
- packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +0 -234
- packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +0 -440
- packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +0 -44
- packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +0 -446
- packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +0 -589
- packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +0 -1326
- packages/tmsi/build/lib/TMSiSDK/__init__.py +0 -54
- packages/tmsi/build/lib/TMSiSDK/device.py +0 -588
- packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +0 -34
- packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +0 -1764
- packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +0 -34
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +0 -1366
- packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +0 -520
- packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +0 -165
- packages/tmsi/build/lib/TMSiSDK/error.py +0 -95
- packages/tmsi/build/lib/TMSiSDK/sample_data.py +0 -63
- packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +0 -99
- packages/tmsi/build/lib/TMSiSDK/settings.py +0 -45
- packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +0 -111
- packages/tmsi/build/lib/__init__.py +0 -4
- packages/tmsi/build/lib/apex_sdk/__init__.py +0 -34
- packages/tmsi/build/lib/apex_sdk/device/__init__.py +0 -41
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +0 -1009
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +0 -239
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +0 -668
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +0 -1611
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +0 -38
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +0 -57
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +0 -44
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +0 -150
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +0 -36
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +0 -48
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +0 -108
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +0 -39
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +0 -77
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +0 -150
- packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +0 -129
- packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +0 -59
- packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +0 -57
- packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +0 -83
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +0 -201
- packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +0 -103
- packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +0 -43
- packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +0 -50
- packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +0 -118
- packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +0 -33
- packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +0 -44
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +0 -50
- packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +0 -136
- packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +0 -126
- packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +0 -113
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +0 -134
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +0 -60
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +0 -42
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +0 -42
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +0 -72
- packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +0 -98
- py_neuromodulation-0.0.3.dist-info/RECORD +0 -188
- py_neuromodulation-0.0.3.dist-info/top_level.txt +0 -5
- tests/__init__.py +0 -0
- tests/conftest.py +0 -117
- tests/test_all_examples.py +0 -10
- tests/test_all_features.py +0 -63
- tests/test_bispectra.py +0 -70
- tests/test_bursts.py +0 -105
- tests/test_feature_sampling_rates.py +0 -143
- tests/test_fooof.py +0 -16
- tests/test_initalization_offline_stream.py +0 -41
- tests/test_multiprocessing.py +0 -58
- tests/test_nan_values.py +0 -29
- tests/test_nm_filter.py +0 -95
- tests/test_nm_resample.py +0 -63
- tests/test_normalization_settings.py +0 -146
- tests/test_notch_filter.py +0 -31
- tests/test_osc_features.py +0 -424
- tests/test_preprocessing_filter.py +0 -151
- tests/test_rereference.py +0 -171
- tests/test_sampling.py +0 -57
- tests/test_settings_change_after_init.py +0 -76
- tests/test_sharpwave.py +0 -165
- tests/test_target_channel_add.py +0 -100
- tests/test_timing.py +0 -80
- {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.4.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,977 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
(c) 2022 Twente Medical Systems International B.V., Oldenzaal The Netherlands
|
|
3
|
-
|
|
4
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
you may not use this file except in compliance with the License.
|
|
6
|
-
You may obtain a copy of the License at
|
|
7
|
-
|
|
8
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
|
|
10
|
-
Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
See the License for the specific language governing permissions and
|
|
14
|
-
limitations under the License.
|
|
15
|
-
|
|
16
|
-
####### # # ##### #
|
|
17
|
-
# ## ## #
|
|
18
|
-
# # # # # # #
|
|
19
|
-
# # # # ##### #
|
|
20
|
-
# # # # #
|
|
21
|
-
# # # # #
|
|
22
|
-
# # # ##### #
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @file ${xdf_file_writer.py}
|
|
26
|
-
* @brief XDF File Writer Interface
|
|
27
|
-
*
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
from datetime import datetime, timedelta
|
|
34
|
-
from enum import IntEnum
|
|
35
|
-
|
|
36
|
-
import threading
|
|
37
|
-
import queue
|
|
38
|
-
import struct
|
|
39
|
-
import time
|
|
40
|
-
import xml.etree.ElementTree as ET
|
|
41
|
-
import numpy as np
|
|
42
|
-
import pandas as pd
|
|
43
|
-
import os
|
|
44
|
-
|
|
45
|
-
from TMSiSDK.device import ChannelType
|
|
46
|
-
from TMSiSDK.error import TMSiError, TMSiErrorCode
|
|
47
|
-
from TMSiSDK import sample_data_server
|
|
48
|
-
|
|
49
|
-
from apex_sdk.device.tmsi_device import TMSiDevice
|
|
50
|
-
from apex_sdk.sample_data_server.sample_data_server import (
|
|
51
|
-
SampleDataServer as ApexSampleDataServer,
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
from os.path import join, dirname, realpath
|
|
56
|
-
|
|
57
|
-
Writer_dir = dirname(realpath(__file__)) # directory of this file
|
|
58
|
-
measurements_dir = join(
|
|
59
|
-
Writer_dir, "../../measurements"
|
|
60
|
-
) # directory with all measurements
|
|
61
|
-
modules_dir = join(Writer_dir, "../../") # directory with all modules
|
|
62
|
-
|
|
63
|
-
_QUEUE_SIZE_SAMPLE_SETS = 1000
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
class ChunkTag(IntEnum):
|
|
67
|
-
"""<ChunkTag> The chunk tag defines the type of the chunk."""
|
|
68
|
-
|
|
69
|
-
file_header = 1
|
|
70
|
-
stream_header = 2
|
|
71
|
-
samples = 3
|
|
72
|
-
clock_offset = 4
|
|
73
|
-
boundary = 5
|
|
74
|
-
stream_footer = 6
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def xml_etree_to_string(elem):
|
|
78
|
-
"""Returns a XML string for the XML Element.
|
|
79
|
-
|
|
80
|
-
Args:
|
|
81
|
-
elem : 'ET.Element'
|
|
82
|
-
"""
|
|
83
|
-
rough_string = ET.tostring(elem, "utf-8")
|
|
84
|
-
return rough_string
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
class XdfWriter:
|
|
88
|
-
def __init__(self, filename, add_ch_locs):
|
|
89
|
-
self.q_sample_sets = queue.Queue(_QUEUE_SIZE_SAMPLE_SETS)
|
|
90
|
-
self.device = None
|
|
91
|
-
|
|
92
|
-
self.filename = filename
|
|
93
|
-
self._fp = None
|
|
94
|
-
self._date = None
|
|
95
|
-
self.add_ch_locs = add_ch_locs
|
|
96
|
-
|
|
97
|
-
def open(self, device):
|
|
98
|
-
"""Opens and initializes a xdf file-writer session.
|
|
99
|
-
|
|
100
|
-
1. Opens the xdf-file
|
|
101
|
-
2. Writes the magic code 'XDF:'
|
|
102
|
-
3. Writes the FileHeader-chunk
|
|
103
|
-
4. Determines the number of sample sets within one Samples-chunk
|
|
104
|
-
5. Registers at the sample-data-server and start the sampling-thread
|
|
105
|
-
"""
|
|
106
|
-
print("XdfWriter-open")
|
|
107
|
-
if isinstance(device, TMSiDevice):
|
|
108
|
-
self.__open_TMSiDevice(device)
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
self.device = device
|
|
112
|
-
|
|
113
|
-
self._sample_rate = device.config.sample_rate
|
|
114
|
-
self._num_channels = len(device.channels)
|
|
115
|
-
|
|
116
|
-
now = datetime.now()
|
|
117
|
-
self._date = now
|
|
118
|
-
filetime = now.strftime("%Y%m%d_%H%M%S")
|
|
119
|
-
fileparts = self.filename.split(".")
|
|
120
|
-
if fileparts[-1] == "xdf" or fileparts[-1] == "Xdf":
|
|
121
|
-
self.filename = ".".join(fileparts[:-1]) + "-" + filetime + ".xdf"
|
|
122
|
-
else:
|
|
123
|
-
self.filename = self.filename + "-" + filetime + ".xdf"
|
|
124
|
-
|
|
125
|
-
# Check for recent impedance values
|
|
126
|
-
imp_df = None
|
|
127
|
-
for file in os.listdir(measurements_dir):
|
|
128
|
-
if (".txt" in file) and ("Impedances_" in file):
|
|
129
|
-
Impedance_time = datetime.strptime(
|
|
130
|
-
file[-19:-4], "%Y%m%d_%H%M%S"
|
|
131
|
-
)
|
|
132
|
-
if (now - Impedance_time) < timedelta(minutes=2):
|
|
133
|
-
imp_file = file
|
|
134
|
-
# read impedance data
|
|
135
|
-
imp_df = pd.read_csv(
|
|
136
|
-
join(measurements_dir, file),
|
|
137
|
-
delimiter="\t",
|
|
138
|
-
header=None,
|
|
139
|
-
)
|
|
140
|
-
imp_df.columns = ["ch_name", "impedance", "unit"]
|
|
141
|
-
if imp_df is not None:
|
|
142
|
-
print("Included impedance values from file:", imp_file)
|
|
143
|
-
|
|
144
|
-
try:
|
|
145
|
-
# 1. Open the xdf-file
|
|
146
|
-
self._fp = open(self.filename, "wb")
|
|
147
|
-
|
|
148
|
-
# 2. Write the magic code 'XDF:'
|
|
149
|
-
self._fp.write(str.encode("XDF:"))
|
|
150
|
-
self._write_file_header_chunk()
|
|
151
|
-
|
|
152
|
-
# 3. Write the file-header chunk
|
|
153
|
-
self.device.config
|
|
154
|
-
|
|
155
|
-
self._write_stream_header_chunk(
|
|
156
|
-
self.device.channels, self._sample_rate, imp_df
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
# 4. Determine the number of sample-sets within one Samples-chunk:
|
|
160
|
-
# This is the number of sample-sets received within 150 milli-seconds or when the
|
|
161
|
-
# sample-data-block-size exceeds 64kb it will become the number of sample-sets that fit within 64kb
|
|
162
|
-
self._num_sample_sets_per_sample_data_block = int(
|
|
163
|
-
self._sample_rate * 0.15
|
|
164
|
-
)
|
|
165
|
-
size_one_sample_set = len(self.device.channels) * 4
|
|
166
|
-
if (
|
|
167
|
-
self._num_sample_sets_per_sample_data_block
|
|
168
|
-
* size_one_sample_set
|
|
169
|
-
) > 64000:
|
|
170
|
-
self._num_sample_sets_per_sample_data_block = int(
|
|
171
|
-
64000 / size_one_sample_set
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
fmt = "f" * self._num_channels
|
|
175
|
-
self.pack_struct = struct.Struct(fmt)
|
|
176
|
-
|
|
177
|
-
# 5. Register at the sample-data-server and start the sampling-thread
|
|
178
|
-
sample_data_server.registerConsumer(
|
|
179
|
-
self.device.id, self.q_sample_sets
|
|
180
|
-
)
|
|
181
|
-
self._sampling_thread = ConsumerThread(
|
|
182
|
-
self, name="Xdf-writer : dev-id-" + str(self.device.id)
|
|
183
|
-
)
|
|
184
|
-
self._sampling_thread.start()
|
|
185
|
-
except:
|
|
186
|
-
raise TMSiError(TMSiErrorCode.file_writer_error)
|
|
187
|
-
|
|
188
|
-
def __open_TMSiDevice(self, device):
|
|
189
|
-
"""Opens and initializes a xdf file-writer session.
|
|
190
|
-
|
|
191
|
-
1. Opens the xdf-file
|
|
192
|
-
2. Writes the magic code 'XDF:'
|
|
193
|
-
3. Writes the FileHeader-chunk
|
|
194
|
-
4. Determines the number of sample sets within one Samples-chunk
|
|
195
|
-
5. Registers at the sample-data-server and start the sampling-thread
|
|
196
|
-
"""
|
|
197
|
-
|
|
198
|
-
self.device = device
|
|
199
|
-
|
|
200
|
-
self._sample_rate = self.device.get_device_sampling_frequency()
|
|
201
|
-
self._num_channels = self.device.get_num_channels()
|
|
202
|
-
|
|
203
|
-
now = datetime.now()
|
|
204
|
-
self._date = now
|
|
205
|
-
filetime = now.strftime("%Y%m%d_%H%M%S")
|
|
206
|
-
fileparts = self.filename.split(".")
|
|
207
|
-
if fileparts[-1] == "xdf" or fileparts[-1] == "Xdf":
|
|
208
|
-
self.filename = ".".join(fileparts[:-1]) + "-" + filetime + ".xdf"
|
|
209
|
-
else:
|
|
210
|
-
self.filename = self.filename + "-" + filetime + ".xdf"
|
|
211
|
-
|
|
212
|
-
# Check for recent impedance values
|
|
213
|
-
imp_df = None
|
|
214
|
-
for file in os.listdir(measurements_dir):
|
|
215
|
-
if (".txt" in file) and ("Impedances_" in file):
|
|
216
|
-
Impedance_time = datetime.strptime(
|
|
217
|
-
file[-19:-4], "%Y%m%d_%H%M%S"
|
|
218
|
-
)
|
|
219
|
-
if (now - Impedance_time) < timedelta(minutes=2):
|
|
220
|
-
imp_file = file
|
|
221
|
-
# read impedance data
|
|
222
|
-
imp_df = pd.read_csv(
|
|
223
|
-
join(measurements_dir, file),
|
|
224
|
-
delimiter="\t",
|
|
225
|
-
header=None,
|
|
226
|
-
)
|
|
227
|
-
imp_df.columns = ["ch_name", "impedance", "unit"]
|
|
228
|
-
if imp_df is not None:
|
|
229
|
-
print("Included impedance values from file:", imp_file)
|
|
230
|
-
|
|
231
|
-
try:
|
|
232
|
-
# 1. Open the xdf-file
|
|
233
|
-
self._fp = open(self.filename, "wb")
|
|
234
|
-
|
|
235
|
-
# 2. Write the magic code 'XDF:'
|
|
236
|
-
self._fp.write(str.encode("XDF:"))
|
|
237
|
-
self._write_file_header_chunk()
|
|
238
|
-
|
|
239
|
-
# 3. Write the file-header chunk
|
|
240
|
-
# self.device.config
|
|
241
|
-
|
|
242
|
-
channels = self.device.get_device_active_channels()
|
|
243
|
-
self._write_stream_header_chunk(channels, self._sample_rate, imp_df)
|
|
244
|
-
|
|
245
|
-
# 4. Determine the number of sample-sets within one Samples-chunk:
|
|
246
|
-
# This is the number of sample-sets received within 150 milli-seconds or when the
|
|
247
|
-
# sample-data-block-size exceeds 64kb it will become the number of sample-sets that fit within 64kb
|
|
248
|
-
self._num_sample_sets_per_sample_data_block = int(
|
|
249
|
-
self._sample_rate * 0.15
|
|
250
|
-
)
|
|
251
|
-
size_one_sample_set = self._num_channels * 4
|
|
252
|
-
if (
|
|
253
|
-
self._num_sample_sets_per_sample_data_block
|
|
254
|
-
* size_one_sample_set
|
|
255
|
-
) > 64000:
|
|
256
|
-
self._num_sample_sets_per_sample_data_block = int(
|
|
257
|
-
64000 / size_one_sample_set
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
fmt = "f" * self._num_channels
|
|
261
|
-
self.pack_struct = struct.Struct(fmt)
|
|
262
|
-
|
|
263
|
-
# 5. Register at the sample-data-server and start the sampling-thread
|
|
264
|
-
ApexSampleDataServer().register_consumer(
|
|
265
|
-
self.device.get_id(), self.q_sample_sets
|
|
266
|
-
)
|
|
267
|
-
self._sampling_thread = ConsumerThread(
|
|
268
|
-
self, name="Xdf-writer : dev-id-" + str(self.device.get_id())
|
|
269
|
-
)
|
|
270
|
-
self._sampling_thread.start()
|
|
271
|
-
except:
|
|
272
|
-
raise TMSiError(TMSiErrorCode.file_writer_error)
|
|
273
|
-
|
|
274
|
-
def save_offline(self, stream_info, streams):
|
|
275
|
-
"""Opens and initializes a xdf file-writer session for an offline processing.
|
|
276
|
-
|
|
277
|
-
1. Opens the xdf-file
|
|
278
|
-
2. Writes the magic code 'XDF:'
|
|
279
|
-
3. Writes the FileHeader-chunk
|
|
280
|
-
4. Writes the Stream header chunk
|
|
281
|
-
5. Saves all data
|
|
282
|
-
"""
|
|
283
|
-
try:
|
|
284
|
-
# 1. Open the xdf-file
|
|
285
|
-
self._fp = open(self.filename, "wb")
|
|
286
|
-
# 2. Write the magic code 'XDF:'
|
|
287
|
-
self._fp.write(str.encode("XDF:"))
|
|
288
|
-
# 3. Write the file-header chunk
|
|
289
|
-
self._write_file_header_chunk()
|
|
290
|
-
# 4. Write the stream-header chunk
|
|
291
|
-
self._write_stream_header_chunk_offline(stream_info)
|
|
292
|
-
# 5 write data
|
|
293
|
-
self._write_data_streams_into_file(streams)
|
|
294
|
-
# 6 write footer
|
|
295
|
-
self._write_stream_footer_chunk(
|
|
296
|
-
0, 10, self._num_written_sample_sets, self._sample_rate
|
|
297
|
-
)
|
|
298
|
-
# 7 close
|
|
299
|
-
self._fp.close()
|
|
300
|
-
|
|
301
|
-
except:
|
|
302
|
-
raise TMSiError(TMSiErrorCode.file_writer_error)
|
|
303
|
-
|
|
304
|
-
def _write_data_streams_into_file(self, streams):
|
|
305
|
-
"""Method that writes the streams for offline saving of data streams"""
|
|
306
|
-
n_ch = len(streams)
|
|
307
|
-
fmt = "f" * n_ch
|
|
308
|
-
self.pack_struct = struct.Struct(fmt)
|
|
309
|
-
_num_sample_sets_per_sample_data_block = int(6400000 / 4 / n_ch)
|
|
310
|
-
self._num_written_sample_sets = 0
|
|
311
|
-
_sample_set_block_index = 0
|
|
312
|
-
_boundary_chunk_counter = 0
|
|
313
|
-
_boundary_chunk_counter_threshold = 10 * self._sample_rate
|
|
314
|
-
n_samp = int(len(streams[0]) * len(streams) / n_ch)
|
|
315
|
-
n_iter = np.int(
|
|
316
|
-
np.floor(n_samp / _num_sample_sets_per_sample_data_block)
|
|
317
|
-
)
|
|
318
|
-
try:
|
|
319
|
-
for i in range(n_iter):
|
|
320
|
-
time_range = [
|
|
321
|
-
j
|
|
322
|
-
for j in range(
|
|
323
|
-
i * _num_sample_sets_per_sample_data_block,
|
|
324
|
-
(i + 1) * _num_sample_sets_per_sample_data_block,
|
|
325
|
-
)
|
|
326
|
-
]
|
|
327
|
-
self._sample_sets_in_block = [
|
|
328
|
-
streams[n_channel][n_sample]
|
|
329
|
-
for n_sample in time_range
|
|
330
|
-
for n_channel in range(len(streams))
|
|
331
|
-
]
|
|
332
|
-
print(
|
|
333
|
-
"\rwriting progress: {:.2f}%\r".format(100 * i / n_iter),
|
|
334
|
-
end="\r",
|
|
335
|
-
)
|
|
336
|
-
XdfWriter._write_sample_chunk(
|
|
337
|
-
self._fp,
|
|
338
|
-
self._sample_sets_in_block,
|
|
339
|
-
_num_sample_sets_per_sample_data_block,
|
|
340
|
-
n_chan=n_ch,
|
|
341
|
-
pack_struct=self.pack_struct,
|
|
342
|
-
)
|
|
343
|
-
|
|
344
|
-
self._num_written_sample_sets += (
|
|
345
|
-
_num_sample_sets_per_sample_data_block
|
|
346
|
-
)
|
|
347
|
-
_sample_set_block_index += 1
|
|
348
|
-
|
|
349
|
-
# Write approximately every 10 seconds a Boundary-chunk
|
|
350
|
-
_boundary_chunk_counter += (
|
|
351
|
-
_num_sample_sets_per_sample_data_block
|
|
352
|
-
)
|
|
353
|
-
if _boundary_chunk_counter >= _boundary_chunk_counter_threshold:
|
|
354
|
-
XdfWriter._write_boundary_chunk(self._fp)
|
|
355
|
-
_boundary_chunk_counter = 0
|
|
356
|
-
|
|
357
|
-
# Store remaining samples for next repetion
|
|
358
|
-
i = np.int(
|
|
359
|
-
np.floor(n_samp / _num_sample_sets_per_sample_data_block)
|
|
360
|
-
)
|
|
361
|
-
time_range = [
|
|
362
|
-
j
|
|
363
|
-
for j in range(
|
|
364
|
-
i * _num_sample_sets_per_sample_data_block, n_samp
|
|
365
|
-
)
|
|
366
|
-
]
|
|
367
|
-
self._sample_sets_in_block = [
|
|
368
|
-
streams[n_channel][n_sample]
|
|
369
|
-
for n_sample in time_range
|
|
370
|
-
for n_channel in range(len(streams))
|
|
371
|
-
]
|
|
372
|
-
XdfWriter._write_sample_chunk(
|
|
373
|
-
self._fp,
|
|
374
|
-
self._sample_sets_in_block,
|
|
375
|
-
n_samp - i * _num_sample_sets_per_sample_data_block,
|
|
376
|
-
n_chan=n_ch,
|
|
377
|
-
pack_struct=self.pack_struct,
|
|
378
|
-
)
|
|
379
|
-
print("\rwriting progress: 100.00%")
|
|
380
|
-
|
|
381
|
-
except:
|
|
382
|
-
raise TMSiError(TMSiErrorCode.file_writer_error)
|
|
383
|
-
|
|
384
|
-
def close(self):
|
|
385
|
-
"""Closes a xdf file-writer session.
|
|
386
|
-
|
|
387
|
-
1. Stops the sampling-thread
|
|
388
|
-
2. Writes the StreamFooter-chunk (by the sampling-thread)
|
|
389
|
-
3. Closes the xdf-file (by the sampling-thread)
|
|
390
|
-
"""
|
|
391
|
-
print("XdfWriter-close")
|
|
392
|
-
self._sampling_thread.stop_sampling()
|
|
393
|
-
|
|
394
|
-
if isinstance(self.device, TMSiDevice):
|
|
395
|
-
ApexSampleDataServer().unregister_consumer(
|
|
396
|
-
self.device.get_id(), self.q_sample_sets
|
|
397
|
-
)
|
|
398
|
-
else:
|
|
399
|
-
sample_data_server.unregisterConsumer(
|
|
400
|
-
self.device.id, self.q_sample_sets
|
|
401
|
-
)
|
|
402
|
-
|
|
403
|
-
@staticmethod
|
|
404
|
-
def _write_chunk(f, length_size, chunk_tag, chunk_data):
|
|
405
|
-
"""Writes a complete chunk to the xdf-file. Writes the chunk-meta-data and chunk-data.
|
|
406
|
-
|
|
407
|
-
Args:
|
|
408
|
-
f : 'file-object' of the xdf-file
|
|
409
|
-
length_size : 'int' variable-length indicator of the chunk-length (1, 4 or 8)
|
|
410
|
-
chunk_tag : 'ChunkTag' defines the type of the chunk
|
|
411
|
-
chunk_data : byte-array with the chunk-data to write
|
|
412
|
-
"""
|
|
413
|
-
# 1. Write the chunk meta-data :
|
|
414
|
-
# - chunk-length,
|
|
415
|
-
# - arbitary bytes
|
|
416
|
-
# - stream-id (when needed)
|
|
417
|
-
# - chunk-tag
|
|
418
|
-
if length_size == 1:
|
|
419
|
-
length_size_type = b"\1"
|
|
420
|
-
else:
|
|
421
|
-
if length_size == 4:
|
|
422
|
-
length_size_type = b"\4"
|
|
423
|
-
else:
|
|
424
|
-
length_size_type = b"\8"
|
|
425
|
-
|
|
426
|
-
num_arbitrary_bytes = 2
|
|
427
|
-
data_size = len(chunk_data) + num_arbitrary_bytes
|
|
428
|
-
if chunk_tag != ChunkTag.file_header:
|
|
429
|
-
data_size += 4
|
|
430
|
-
|
|
431
|
-
f.write(length_size_type)
|
|
432
|
-
f.write(data_size.to_bytes(length_size, "little"))
|
|
433
|
-
f.write(chunk_tag.to_bytes(num_arbitrary_bytes, "little"))
|
|
434
|
-
if chunk_tag != ChunkTag.file_header:
|
|
435
|
-
_stream_id = 1
|
|
436
|
-
f.write(_stream_id.to_bytes(4, "little"))
|
|
437
|
-
|
|
438
|
-
# 2. Write the chunk-data
|
|
439
|
-
f.write(chunk_data)
|
|
440
|
-
|
|
441
|
-
def _write_file_header_chunk(self):
|
|
442
|
-
"""Writes the standard FileHeader-chunk."""
|
|
443
|
-
data = ET.Element("info")
|
|
444
|
-
item = ET.SubElement(data, "version")
|
|
445
|
-
item.text = "1"
|
|
446
|
-
XdfWriter._write_chunk(
|
|
447
|
-
self._fp, 1, ChunkTag.file_header, xml_etree_to_string(data)
|
|
448
|
-
)
|
|
449
|
-
|
|
450
|
-
def _write_stream_header_chunk_offline(self, stream_info):
|
|
451
|
-
de_info = ET.Element("info")
|
|
452
|
-
item_name = ET.SubElement(de_info, "name")
|
|
453
|
-
item_name.text = stream_info["name"][0]
|
|
454
|
-
item_type = ET.SubElement(de_info, "type")
|
|
455
|
-
item_type.text = stream_info["type"][0]
|
|
456
|
-
item_channel_count = ET.SubElement(de_info, "channel_count")
|
|
457
|
-
item_channel_count.text = stream_info["channel_count"][0]
|
|
458
|
-
item_nominal_srate = ET.SubElement(de_info, "nominal_srate")
|
|
459
|
-
item_nominal_srate.text = stream_info["nominal_srate"][0]
|
|
460
|
-
self._sample_rate = int(
|
|
461
|
-
float(stream_info["nominal_srate"][0])
|
|
462
|
-
) # needed for later
|
|
463
|
-
item_channel_format = ET.SubElement(de_info, "channel_format")
|
|
464
|
-
item_channel_format.text = stream_info["channel_format"][0]
|
|
465
|
-
# Assemble the EEG meta-data
|
|
466
|
-
de_desc = ET.SubElement(de_info, "desc")
|
|
467
|
-
de_channels = ET.SubElement(de_desc, "channels")
|
|
468
|
-
for channel in stream_info["desc"][0]["channels"][0]["channel"]:
|
|
469
|
-
item_channel = ET.SubElement(de_channels, "channel")
|
|
470
|
-
# channel label
|
|
471
|
-
item_label = ET.SubElement(item_channel, "label")
|
|
472
|
-
item_label.text = channel["label"][0]
|
|
473
|
-
# channel index
|
|
474
|
-
item_index = ET.SubElement(item_channel, "index")
|
|
475
|
-
item_index.text = channel["index"][0]
|
|
476
|
-
# channel content-type (EEG, EMG, EOG, ...)
|
|
477
|
-
item_type = ET.SubElement(item_channel, "type")
|
|
478
|
-
item_type.text = channel["type"][0]
|
|
479
|
-
# measurement unit (strongly preferred unit: microvolts)
|
|
480
|
-
item_unit = ET.SubElement(item_channel, "unit")
|
|
481
|
-
item_unit.text = "-"
|
|
482
|
-
if channel["impedance"]:
|
|
483
|
-
item_impedance = ET.SubElement(item_channel, "impedance")
|
|
484
|
-
if len(channel["impedance"]) > 0:
|
|
485
|
-
item_impedance.text = channel["impedance"][0]
|
|
486
|
-
|
|
487
|
-
item_manufacturer = ET.SubElement(de_desc, "manufacturer")
|
|
488
|
-
item_manufacturer.text = stream_info["desc"][0]["manufacturer"][0]
|
|
489
|
-
# Write the StreamHeader-cunk
|
|
490
|
-
XdfWriter._write_chunk(
|
|
491
|
-
self._fp, 4, ChunkTag.stream_header, xml_etree_to_string(de_info)
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
def _write_stream_header_chunk(self, channels, sample_rate, imp_df=None):
|
|
495
|
-
"""Writes the StreamHeader-chunk :
|
|
496
|
-
- <info>-element with:
|
|
497
|
-
- device-name,
|
|
498
|
-
- overall content-type,
|
|
499
|
-
- channel-count,
|
|
500
|
-
- sample-rate and
|
|
501
|
-
- sample-data-format
|
|
502
|
-
- EEG meta-data, the channels-element ('<desc><channels>'):
|
|
503
|
-
- channel-name (<label>)
|
|
504
|
-
- channel-type (<type>)
|
|
505
|
-
- channel-unit-name (<unit>)
|
|
506
|
-
- channel-location (<location>)
|
|
507
|
-
- channel-impedance (<impedance>)
|
|
508
|
-
-reference meta-data
|
|
509
|
-
-acquistion meta-data
|
|
510
|
-
|
|
511
|
-
Args:
|
|
512
|
-
channels : 'list DeviceChannel' channel list
|
|
513
|
-
sample_rate : int' The rate of the current configuration, with which
|
|
514
|
-
sample-sets are sent during a measurement.
|
|
515
|
-
imp_df: 'DataFrame' Previously recorded impedances
|
|
516
|
-
"""
|
|
517
|
-
|
|
518
|
-
if isinstance(self.device, TMSiDevice):
|
|
519
|
-
self.__write_stream_header_chunk(channels, sample_rate, imp_df)
|
|
520
|
-
return
|
|
521
|
-
|
|
522
|
-
# Assemble <info>-root-element:
|
|
523
|
-
num_channels = self._num_channels
|
|
524
|
-
de_info = ET.Element("info")
|
|
525
|
-
item_name = ET.SubElement(de_info, "name")
|
|
526
|
-
item_name.text = "SAGA"
|
|
527
|
-
item_type = ET.SubElement(de_info, "type")
|
|
528
|
-
item_type.text = "EEG"
|
|
529
|
-
item_channel_count = ET.SubElement(de_info, "channel_count")
|
|
530
|
-
item_channel_count.text = str(num_channels)
|
|
531
|
-
item_nominal_srate = ET.SubElement(de_info, "nominal_srate")
|
|
532
|
-
item_nominal_srate.text = str(sample_rate)
|
|
533
|
-
item_channel_format = ET.SubElement(de_info, "channel_format")
|
|
534
|
-
item_channel_format.text = "float32"
|
|
535
|
-
|
|
536
|
-
# Assemble the EEG meta-data
|
|
537
|
-
de_desc = ET.SubElement(de_info, "desc")
|
|
538
|
-
de_channels = ET.SubElement(de_desc, "channels")
|
|
539
|
-
|
|
540
|
-
# read channel locations
|
|
541
|
-
chLocs = pd.read_csv(
|
|
542
|
-
join(modules_dir, "TMSiSDK/_resources/EEGchannelsTMSi3D.txt"),
|
|
543
|
-
sep="\t",
|
|
544
|
-
header=None,
|
|
545
|
-
)
|
|
546
|
-
chLocs.columns = ["default_name", "eeg_name", "X", "Y", "Z"]
|
|
547
|
-
|
|
548
|
-
# Meta-data per channel
|
|
549
|
-
i = 0 # active channel counter
|
|
550
|
-
for j in range(len(self.device._config._channels)):
|
|
551
|
-
if (
|
|
552
|
-
self.device._config._channels[j].def_name
|
|
553
|
-
== self.device._channels[i].def_name
|
|
554
|
-
):
|
|
555
|
-
# description of one channel, repeated (one for each channel in the time series)
|
|
556
|
-
item_channel = ET.SubElement(de_channels, "channel")
|
|
557
|
-
# channel label
|
|
558
|
-
item_label = ET.SubElement(item_channel, "label")
|
|
559
|
-
item_label.text = channels[i].name
|
|
560
|
-
# channel content-type (EEG, EMG, EOG, ...)
|
|
561
|
-
item_type = ET.SubElement(item_channel, "type")
|
|
562
|
-
|
|
563
|
-
if channels[i].type.value == ChannelType.UNI.value:
|
|
564
|
-
if not j == 0:
|
|
565
|
-
item_type.text = "EEG"
|
|
566
|
-
# channel location
|
|
567
|
-
if self.add_ch_locs:
|
|
568
|
-
item_location = ET.SubElement(
|
|
569
|
-
item_channel, "location"
|
|
570
|
-
)
|
|
571
|
-
item_x = ET.SubElement(item_location, "X")
|
|
572
|
-
item_y = ET.SubElement(item_location, "Y")
|
|
573
|
-
item_z = ET.SubElement(item_location, "Z")
|
|
574
|
-
item_x.text = str(95 * chLocs["X"].values[j - 1])
|
|
575
|
-
item_y.text = str(95 * chLocs["Y"].values[j - 1])
|
|
576
|
-
item_z.text = str(95 * chLocs["Z"].values[j - 1])
|
|
577
|
-
else:
|
|
578
|
-
item_type.text = "CREF"
|
|
579
|
-
elif channels[i].type.value == ChannelType.BIP.value:
|
|
580
|
-
item_type.text = "BIP"
|
|
581
|
-
elif channels[i].type.value == ChannelType.AUX.value:
|
|
582
|
-
item_type.text = "AUX"
|
|
583
|
-
elif channels[i].type.value == ChannelType.sensor.value:
|
|
584
|
-
item_type.text = "sensor"
|
|
585
|
-
elif channels[i].type.value == ChannelType.status.value:
|
|
586
|
-
item_type.text = "status"
|
|
587
|
-
elif channels[i].type.value == ChannelType.counter.value:
|
|
588
|
-
item_type.text = "counter"
|
|
589
|
-
else:
|
|
590
|
-
item_type.text = "-"
|
|
591
|
-
|
|
592
|
-
if imp_df is not None:
|
|
593
|
-
# channel impedence
|
|
594
|
-
item_impedance = ET.SubElement(item_channel, "impedance")
|
|
595
|
-
if channels[i].type.value == ChannelType.UNI.value:
|
|
596
|
-
item_impedance.text = str(imp_df["impedance"].values[j])
|
|
597
|
-
else:
|
|
598
|
-
item_impedance.text = "N.A."
|
|
599
|
-
# measurement unit (strongly preferred unit: microvolts)
|
|
600
|
-
item_unit = ET.SubElement(item_channel, "unit")
|
|
601
|
-
item_unit.text = channels[i].unit_name
|
|
602
|
-
i += 1
|
|
603
|
-
|
|
604
|
-
# Acquisition meta-data
|
|
605
|
-
de_acquisition = ET.SubElement(de_desc, "acquisition")
|
|
606
|
-
item_manufacturer = ET.SubElement(de_acquisition, "manufacturer")
|
|
607
|
-
item_manufacturer.text = "TMSi"
|
|
608
|
-
item_model = ET.SubElement(de_acquisition, "model")
|
|
609
|
-
item_model.text = "SAGA"
|
|
610
|
-
item_precision = ET.SubElement(de_acquisition, "precision")
|
|
611
|
-
item_precision.text = "32"
|
|
612
|
-
|
|
613
|
-
# Reference meta-data
|
|
614
|
-
de_reference = ET.SubElement(de_desc, "reference")
|
|
615
|
-
item_label = ET.SubElement(de_reference, "label")
|
|
616
|
-
item_subtracted = ET.SubElement(de_reference, "subtracted")
|
|
617
|
-
item_subtracted.text = "Yes"
|
|
618
|
-
item_common_average = ET.SubElement(de_reference, "common_average")
|
|
619
|
-
if self.device.config._reference_method:
|
|
620
|
-
item_label.text = "average"
|
|
621
|
-
item_common_average.text = "Yes"
|
|
622
|
-
else:
|
|
623
|
-
item_label.text = "CREF"
|
|
624
|
-
item_common_average.text = "No"
|
|
625
|
-
|
|
626
|
-
# Write the StreamHeader-cunk
|
|
627
|
-
XdfWriter._write_chunk(
|
|
628
|
-
self._fp, 4, ChunkTag.stream_header, xml_etree_to_string(de_info)
|
|
629
|
-
)
|
|
630
|
-
|
|
631
|
-
def __write_stream_header_chunk(self, channels, sample_rate, imp_df=None):
|
|
632
|
-
"""Writes the StreamHeader-chunk :
|
|
633
|
-
- <info>-element with:
|
|
634
|
-
- device-name,
|
|
635
|
-
- overall content-type,
|
|
636
|
-
- channel-count,
|
|
637
|
-
- sample-rate and
|
|
638
|
-
- sample-data-format
|
|
639
|
-
- EEG meta-data, the channels-element ('<desc><channels>'):
|
|
640
|
-
- channel-name (<label>)
|
|
641
|
-
- channel-type (<type>)
|
|
642
|
-
- channel-unit-name (<unit>)
|
|
643
|
-
- channel-location (<location>)
|
|
644
|
-
- channel-impedance (<impedance>)
|
|
645
|
-
-reference meta-data
|
|
646
|
-
-acquistion meta-data
|
|
647
|
-
|
|
648
|
-
Args:
|
|
649
|
-
channels : 'list DeviceChannel' channel list
|
|
650
|
-
sample_rate : int' The rate of the current configuration, with which
|
|
651
|
-
sample-sets are sent during a measurement.
|
|
652
|
-
imp_df: 'DataFrame' Previously recorded impedances
|
|
653
|
-
"""
|
|
654
|
-
# Assemble <info>-root-element:
|
|
655
|
-
num_channels = self._num_channels
|
|
656
|
-
de_info = ET.Element("info")
|
|
657
|
-
item_name = ET.SubElement(de_info, "name")
|
|
658
|
-
item_name.text = self.device.get_device_type()
|
|
659
|
-
item_type = ET.SubElement(de_info, "type")
|
|
660
|
-
item_type.text = "EEG"
|
|
661
|
-
item_channel_count = ET.SubElement(de_info, "channel_count")
|
|
662
|
-
item_channel_count.text = str(num_channels)
|
|
663
|
-
item_nominal_srate = ET.SubElement(de_info, "nominal_srate")
|
|
664
|
-
item_nominal_srate.text = str(sample_rate)
|
|
665
|
-
item_channel_format = ET.SubElement(de_info, "channel_format")
|
|
666
|
-
item_channel_format.text = "float32"
|
|
667
|
-
|
|
668
|
-
# Assemble the EEG meta-data
|
|
669
|
-
de_desc = ET.SubElement(de_info, "desc")
|
|
670
|
-
de_channels = ET.SubElement(de_desc, "channels")
|
|
671
|
-
|
|
672
|
-
# read channel locations
|
|
673
|
-
chLocs = pd.read_csv(
|
|
674
|
-
join(modules_dir, "TMSiSDK/_resources/EEGchannelsTMSi3D.txt"),
|
|
675
|
-
sep="\t",
|
|
676
|
-
header=None,
|
|
677
|
-
)
|
|
678
|
-
chLocs.columns = ["default_name", "eeg_name", "X", "Y", "Z"]
|
|
679
|
-
|
|
680
|
-
# Meta-data per channel
|
|
681
|
-
for j in range(len(channels)):
|
|
682
|
-
# description of one channel, repeated (one for each channel in the time series)
|
|
683
|
-
item_channel = ET.SubElement(de_channels, "channel")
|
|
684
|
-
# channel label
|
|
685
|
-
item_label = ET.SubElement(item_channel, "label")
|
|
686
|
-
item_label.text = channels[j].get_channel_name()
|
|
687
|
-
# channel content-type (EEG, EMG, EOG, ...)
|
|
688
|
-
item_type = ET.SubElement(item_channel, "type")
|
|
689
|
-
|
|
690
|
-
if channels[j].get_channel_type().value == ChannelType.UNI.value:
|
|
691
|
-
item_type.text = "EEG"
|
|
692
|
-
# channel location
|
|
693
|
-
if self.add_ch_locs:
|
|
694
|
-
item_location = ET.SubElement(item_channel, "location")
|
|
695
|
-
item_x = ET.SubElement(item_location, "X")
|
|
696
|
-
item_y = ET.SubElement(item_location, "Y")
|
|
697
|
-
item_z = ET.SubElement(item_location, "Z")
|
|
698
|
-
item_x.text = str(95 * chLocs["X"].values[j])
|
|
699
|
-
item_y.text = str(95 * chLocs["Y"].values[j])
|
|
700
|
-
item_z.text = str(95 * chLocs["Z"].values[j])
|
|
701
|
-
|
|
702
|
-
elif channels[j].get_channel_type().value == ChannelType.BIP.value:
|
|
703
|
-
item_type.text = "BIP"
|
|
704
|
-
elif channels[j].get_channel_type().value == ChannelType.AUX.value:
|
|
705
|
-
item_type.text = "AUX"
|
|
706
|
-
elif (
|
|
707
|
-
channels[j].get_channel_type().value == ChannelType.sensor.value
|
|
708
|
-
):
|
|
709
|
-
item_type.text = "sensor"
|
|
710
|
-
elif (
|
|
711
|
-
channels[j].get_channel_type().value == ChannelType.status.value
|
|
712
|
-
):
|
|
713
|
-
item_type.text = "status"
|
|
714
|
-
elif (
|
|
715
|
-
channels[j].get_channel_type().value
|
|
716
|
-
== ChannelType.counter.value
|
|
717
|
-
):
|
|
718
|
-
item_type.text = "counter"
|
|
719
|
-
else:
|
|
720
|
-
item_type.text = "-"
|
|
721
|
-
|
|
722
|
-
# if imp_df is not None:
|
|
723
|
-
# #channel impedance
|
|
724
|
-
# item_impedance = ET.SubElement(item_channel, 'impedance')
|
|
725
|
-
# if (channels[i].type.value == ChannelType.UNI.value):
|
|
726
|
-
# item_impedance.text=str(imp_df['impedance'].values[j])
|
|
727
|
-
# else:
|
|
728
|
-
# item_impedance.text='N.A.'
|
|
729
|
-
# measurement unit (strongly preferred unit: microvolts)
|
|
730
|
-
item_unit = ET.SubElement(item_channel, "unit")
|
|
731
|
-
item_unit.text = channels[j].get_channel_unit_name()
|
|
732
|
-
|
|
733
|
-
# Acquisition meta-data
|
|
734
|
-
de_acquisition = ET.SubElement(de_desc, "acquisition")
|
|
735
|
-
item_manufacturer = ET.SubElement(de_acquisition, "manufacturer")
|
|
736
|
-
item_manufacturer.text = "TMSi"
|
|
737
|
-
item_model = ET.SubElement(de_acquisition, "model")
|
|
738
|
-
item_model.text = self.device.get_device_type()
|
|
739
|
-
item_precision = ET.SubElement(de_acquisition, "precision")
|
|
740
|
-
item_precision.text = "32"
|
|
741
|
-
|
|
742
|
-
# Reference meta-data
|
|
743
|
-
de_reference = ET.SubElement(de_desc, "reference")
|
|
744
|
-
item_label = ET.SubElement(de_reference, "label")
|
|
745
|
-
item_subtracted = ET.SubElement(de_reference, "subtracted")
|
|
746
|
-
item_subtracted.text = "Yes"
|
|
747
|
-
item_common_average = ET.SubElement(de_reference, "common_average")
|
|
748
|
-
item_label.text = "average"
|
|
749
|
-
item_common_average.text = "Yes"
|
|
750
|
-
|
|
751
|
-
# Write the StreamHeader-cunk
|
|
752
|
-
XdfWriter._write_chunk(
|
|
753
|
-
self._fp, 4, ChunkTag.stream_header, xml_etree_to_string(de_info)
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
def _write_stream_footer_chunk(
|
|
757
|
-
self, first_timestamp, last_timestamp, sample_count, sample_rate
|
|
758
|
-
):
|
|
759
|
-
"""Writes the StreamFooter-chunk :
|
|
760
|
-
- first timestamp in seconds
|
|
761
|
-
- last timestamp in seconds
|
|
762
|
-
- sample-set count
|
|
763
|
-
- sample rate
|
|
764
|
-
|
|
765
|
-
Args:
|
|
766
|
-
first_timestamp : timestart measurement start
|
|
767
|
-
last_timestamp : timestart measurement end
|
|
768
|
-
sample_count : Total number of sample-sets written to the xdf-file
|
|
769
|
-
sample_rate : int' The rate of the current configuration, with which
|
|
770
|
-
sample-sets are sent during a measurement.
|
|
771
|
-
|
|
772
|
-
"""
|
|
773
|
-
data = ET.Element("info")
|
|
774
|
-
item_name = ET.SubElement(data, "first_timestamp")
|
|
775
|
-
item_name.text = str(first_timestamp)
|
|
776
|
-
item_type = ET.SubElement(data, "last_timestamp")
|
|
777
|
-
item_type.text = str(last_timestamp)
|
|
778
|
-
item_channel_count = ET.SubElement(data, "sample_count")
|
|
779
|
-
item_channel_count.text = str(sample_count)
|
|
780
|
-
item_nominal_srate = ET.SubElement(data, "measured_srate")
|
|
781
|
-
item_nominal_srate.text = str(sample_rate)
|
|
782
|
-
|
|
783
|
-
XdfWriter._write_chunk(
|
|
784
|
-
self._fp, 4, ChunkTag.stream_footer, xml_etree_to_string(data)
|
|
785
|
-
)
|
|
786
|
-
|
|
787
|
-
@staticmethod
|
|
788
|
-
def _write_sample_chunk(
|
|
789
|
-
f, sample_sets, num_sample_sets, n_chan, pack_struct
|
|
790
|
-
):
|
|
791
|
-
"""Writes the Samples-chunk :
|
|
792
|
-
|
|
793
|
-
Args:
|
|
794
|
-
f : 'file-object' of the xdf-file
|
|
795
|
-
sample_sets : list of <SampleSet>
|
|
796
|
-
num_sample_sets : Number of sample-sets to write
|
|
797
|
-
|
|
798
|
-
"""
|
|
799
|
-
sample_chunk = bytearray()
|
|
800
|
-
num_sample_bytes = int(4)
|
|
801
|
-
ts = int(0)
|
|
802
|
-
|
|
803
|
-
sample_chunk += num_sample_bytes.to_bytes(1, "little")
|
|
804
|
-
sample_chunk += num_sample_sets.to_bytes(4, "little")
|
|
805
|
-
|
|
806
|
-
for i in range(num_sample_sets):
|
|
807
|
-
sample_chunk += ts.to_bytes(1, "little")
|
|
808
|
-
sample_chunk += pack_struct.pack(
|
|
809
|
-
*sample_sets[i * n_chan : (i + 1) * n_chan]
|
|
810
|
-
)
|
|
811
|
-
|
|
812
|
-
XdfWriter._write_chunk(f, 4, ChunkTag.samples, sample_chunk)
|
|
813
|
-
|
|
814
|
-
@staticmethod
|
|
815
|
-
def _write_boundary_chunk(f):
|
|
816
|
-
"""Writes the Boundary-chunk :
|
|
817
|
-
|
|
818
|
-
Args:
|
|
819
|
-
f : 'file-object' of the xdf-file
|
|
820
|
-
"""
|
|
821
|
-
uuid = [
|
|
822
|
-
0x43,
|
|
823
|
-
0xA5,
|
|
824
|
-
0x46,
|
|
825
|
-
0xDC,
|
|
826
|
-
0xCB,
|
|
827
|
-
0xF5,
|
|
828
|
-
0x41,
|
|
829
|
-
0x0F,
|
|
830
|
-
0xB3,
|
|
831
|
-
0x0E,
|
|
832
|
-
0xD5,
|
|
833
|
-
0x46,
|
|
834
|
-
0x73,
|
|
835
|
-
0x83,
|
|
836
|
-
0xCB,
|
|
837
|
-
0xE4,
|
|
838
|
-
]
|
|
839
|
-
boundary_chunk = bytearray(uuid)
|
|
840
|
-
|
|
841
|
-
XdfWriter._write_chunk(f, 4, ChunkTag.boundary, boundary_chunk)
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
class ConsumerThread(threading.Thread):
|
|
845
|
-
def __init__(self, file_writer, name):
|
|
846
|
-
super(ConsumerThread, self).__init__()
|
|
847
|
-
self.name = name
|
|
848
|
-
self._fw = file_writer
|
|
849
|
-
self.q_sample_sets = file_writer.q_sample_sets
|
|
850
|
-
self.sampling = True
|
|
851
|
-
self._sample_set_block_index = 0
|
|
852
|
-
self._start_time = time.time()
|
|
853
|
-
self._num_sample_sets_per_sample_data_block = (
|
|
854
|
-
file_writer._num_sample_sets_per_sample_data_block
|
|
855
|
-
)
|
|
856
|
-
self._sample_sets_in_block = []
|
|
857
|
-
self._num_written_sample_sets = 0
|
|
858
|
-
self._boundary_chunk_counter_threshold = self._fw._sample_rate * 10
|
|
859
|
-
self._boundary_chunk_counter = 0
|
|
860
|
-
self.pack_struct = file_writer.pack_struct
|
|
861
|
-
self._remaining_samples = np.array([])
|
|
862
|
-
|
|
863
|
-
def run(self):
|
|
864
|
-
|
|
865
|
-
while (self.sampling) or (not self.q_sample_sets.empty()):
|
|
866
|
-
while not self.q_sample_sets.empty():
|
|
867
|
-
# Request sample data
|
|
868
|
-
sd = self.q_sample_sets.get()
|
|
869
|
-
self.q_sample_sets.task_done()
|
|
870
|
-
|
|
871
|
-
# Combine with previous data
|
|
872
|
-
if self._remaining_samples.size:
|
|
873
|
-
samples = np.concatenate(
|
|
874
|
-
(self._remaining_samples, sd.samples)
|
|
875
|
-
)
|
|
876
|
-
else:
|
|
877
|
-
samples = np.array(sd.samples)
|
|
878
|
-
|
|
879
|
-
n_samp = int(len(samples) / sd.num_samples_per_sample_set)
|
|
880
|
-
|
|
881
|
-
try:
|
|
882
|
-
# Collect the sample-sets:
|
|
883
|
-
# When collected enough to fill a sample-data-block, write it to a Samples-chunk
|
|
884
|
-
for i in range(
|
|
885
|
-
np.int(
|
|
886
|
-
np.floor(
|
|
887
|
-
n_samp
|
|
888
|
-
/ self._num_sample_sets_per_sample_data_block
|
|
889
|
-
)
|
|
890
|
-
)
|
|
891
|
-
):
|
|
892
|
-
self._sample_sets_in_block = samples[
|
|
893
|
-
i
|
|
894
|
-
* self._num_sample_sets_per_sample_data_block
|
|
895
|
-
* sd.num_samples_per_sample_set : (i + 1)
|
|
896
|
-
* self._num_sample_sets_per_sample_data_block
|
|
897
|
-
* sd.num_samples_per_sample_set
|
|
898
|
-
]
|
|
899
|
-
XdfWriter._write_sample_chunk(
|
|
900
|
-
self._fw._fp,
|
|
901
|
-
self._sample_sets_in_block,
|
|
902
|
-
self._num_sample_sets_per_sample_data_block,
|
|
903
|
-
n_chan=sd.num_samples_per_sample_set,
|
|
904
|
-
pack_struct=self.pack_struct,
|
|
905
|
-
)
|
|
906
|
-
self._num_written_sample_sets += (
|
|
907
|
-
self._num_sample_sets_per_sample_data_block
|
|
908
|
-
)
|
|
909
|
-
self._sample_set_block_index += 1
|
|
910
|
-
|
|
911
|
-
# Write approximately every 10 seconds a Boundary-chunk
|
|
912
|
-
self._boundary_chunk_counter += (
|
|
913
|
-
self._num_sample_sets_per_sample_data_block
|
|
914
|
-
)
|
|
915
|
-
if (
|
|
916
|
-
self._boundary_chunk_counter
|
|
917
|
-
>= self._boundary_chunk_counter_threshold
|
|
918
|
-
):
|
|
919
|
-
XdfWriter._write_boundary_chunk(self._fw._fp)
|
|
920
|
-
self._boundary_chunk_counter = 0
|
|
921
|
-
|
|
922
|
-
# Store remaining samples for next repetion
|
|
923
|
-
i = np.int(
|
|
924
|
-
np.floor(
|
|
925
|
-
n_samp / self._num_sample_sets_per_sample_data_block
|
|
926
|
-
)
|
|
927
|
-
)
|
|
928
|
-
ind = np.arange(
|
|
929
|
-
i
|
|
930
|
-
* self._num_sample_sets_per_sample_data_block
|
|
931
|
-
* sd.num_samples_per_sample_set,
|
|
932
|
-
n_samp * sd.num_samples_per_sample_set,
|
|
933
|
-
)
|
|
934
|
-
if ind.any:
|
|
935
|
-
self._remaining_samples = samples[ind]
|
|
936
|
-
else:
|
|
937
|
-
self._remaining_samples = np.array([])
|
|
938
|
-
|
|
939
|
-
except:
|
|
940
|
-
raise TMSiError(TMSiErrorCode.file_writer_error)
|
|
941
|
-
|
|
942
|
-
time.sleep(0.01)
|
|
943
|
-
|
|
944
|
-
# Handle remaining samples before closing file
|
|
945
|
-
if self._remaining_samples.any():
|
|
946
|
-
self._sample_sets_in_block = np.zeros(
|
|
947
|
-
self._num_sample_sets_per_sample_data_block
|
|
948
|
-
* sd.num_samples_per_sample_set
|
|
949
|
-
)
|
|
950
|
-
self._sample_sets_in_block[
|
|
951
|
-
: len(self._remaining_samples)
|
|
952
|
-
] = self._remaining_samples
|
|
953
|
-
|
|
954
|
-
XdfWriter._write_sample_chunk(
|
|
955
|
-
self._fw._fp,
|
|
956
|
-
self._sample_sets_in_block,
|
|
957
|
-
self._num_sample_sets_per_sample_data_block,
|
|
958
|
-
n_chan=sd.num_samples_per_sample_set,
|
|
959
|
-
pack_struct=self.pack_struct,
|
|
960
|
-
)
|
|
961
|
-
|
|
962
|
-
# When done : write the StreamFoot-cunk and close the file
|
|
963
|
-
elapsed_time = time.time() - self._start_time
|
|
964
|
-
self._fw._write_stream_footer_chunk(
|
|
965
|
-
0,
|
|
966
|
-
int(elapsed_time),
|
|
967
|
-
self._num_written_sample_sets,
|
|
968
|
-
self._fw._sample_rate,
|
|
969
|
-
)
|
|
970
|
-
|
|
971
|
-
print(self.name, " ready, closing file")
|
|
972
|
-
self._fw._fp.close()
|
|
973
|
-
return
|
|
974
|
-
|
|
975
|
-
def stop_sampling(self):
|
|
976
|
-
print(self.name, " stop sampling")
|
|
977
|
-
self.sampling = False
|