py-neuromodulation 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. py_neuromodulation/ConnectivityDecoding/Automated Anatomical Labeling 3 (Rolls 2020).nii +0 -0
  2. py_neuromodulation/ConnectivityDecoding/_get_grid_hull.m +34 -0
  3. py_neuromodulation/ConnectivityDecoding/_get_grid_whole_brain.py +95 -0
  4. py_neuromodulation/ConnectivityDecoding/_helper_write_connectome.py +107 -0
  5. py_neuromodulation/ConnectivityDecoding/mni_coords_cortical_surface.mat +0 -0
  6. py_neuromodulation/ConnectivityDecoding/mni_coords_whole_brain.mat +0 -0
  7. py_neuromodulation/ConnectivityDecoding/rmap_func_all.nii +0 -0
  8. py_neuromodulation/ConnectivityDecoding/rmap_struc.nii +0 -0
  9. py_neuromodulation/FieldTrip.py +589 -589
  10. py_neuromodulation/__init__.py +74 -13
  11. py_neuromodulation/_write_example_dataset_helper.py +83 -65
  12. py_neuromodulation/data/README +6 -0
  13. py_neuromodulation/data/dataset_description.json +8 -0
  14. py_neuromodulation/data/participants.json +32 -0
  15. py_neuromodulation/data/participants.tsv +2 -0
  16. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +5 -0
  17. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +11 -0
  18. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +11 -0
  19. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
  20. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +18 -0
  21. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +35 -0
  22. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +13 -0
  23. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +2 -0
  24. py_neuromodulation/grid_cortex.tsv +40 -0
  25. py_neuromodulation/grid_subcortex.tsv +1429 -0
  26. py_neuromodulation/liblsl/libpugixml.so.1.12 +0 -0
  27. py_neuromodulation/liblsl/linux/bionic_amd64/liblsl.1.16.2.so +0 -0
  28. py_neuromodulation/liblsl/linux/bookworm_amd64/liblsl.1.16.2.so +0 -0
  29. py_neuromodulation/liblsl/linux/focal_amd46/liblsl.1.16.2.so +0 -0
  30. py_neuromodulation/liblsl/linux/jammy_amd64/liblsl.1.16.2.so +0 -0
  31. py_neuromodulation/liblsl/linux/jammy_x86/liblsl.1.16.2.so +0 -0
  32. py_neuromodulation/liblsl/linux/noble_amd64/liblsl.1.16.2.so +0 -0
  33. py_neuromodulation/liblsl/macos/amd64/liblsl.1.16.2.dylib +0 -0
  34. py_neuromodulation/liblsl/macos/arm64/liblsl.1.16.0.dylib +0 -0
  35. py_neuromodulation/liblsl/windows/amd64/liblsl.1.16.2.dll +0 -0
  36. py_neuromodulation/liblsl/windows/x86/liblsl.1.16.2.dll +0 -0
  37. py_neuromodulation/nm_IO.py +413 -417
  38. py_neuromodulation/nm_RMAP.py +496 -531
  39. py_neuromodulation/nm_analysis.py +993 -1074
  40. py_neuromodulation/nm_artifacts.py +30 -25
  41. py_neuromodulation/nm_bispectra.py +154 -168
  42. py_neuromodulation/nm_bursts.py +292 -198
  43. py_neuromodulation/nm_coherence.py +251 -205
  44. py_neuromodulation/nm_database.py +149 -0
  45. py_neuromodulation/nm_decode.py +918 -992
  46. py_neuromodulation/nm_define_nmchannels.py +300 -302
  47. py_neuromodulation/nm_features.py +144 -116
  48. py_neuromodulation/nm_filter.py +219 -219
  49. py_neuromodulation/nm_filter_preprocessing.py +79 -91
  50. py_neuromodulation/nm_fooof.py +139 -159
  51. py_neuromodulation/nm_generator.py +45 -37
  52. py_neuromodulation/nm_hjorth_raw.py +52 -73
  53. py_neuromodulation/nm_kalmanfilter.py +71 -58
  54. py_neuromodulation/nm_linelength.py +21 -33
  55. py_neuromodulation/nm_logger.py +66 -0
  56. py_neuromodulation/nm_mne_connectivity.py +149 -112
  57. py_neuromodulation/nm_mnelsl_generator.py +90 -0
  58. py_neuromodulation/nm_mnelsl_stream.py +116 -0
  59. py_neuromodulation/nm_nolds.py +96 -93
  60. py_neuromodulation/nm_normalization.py +173 -214
  61. py_neuromodulation/nm_oscillatory.py +423 -448
  62. py_neuromodulation/nm_plots.py +585 -612
  63. py_neuromodulation/nm_preprocessing.py +83 -0
  64. py_neuromodulation/nm_projection.py +370 -394
  65. py_neuromodulation/nm_rereference.py +97 -95
  66. py_neuromodulation/nm_resample.py +59 -50
  67. py_neuromodulation/nm_run_analysis.py +325 -435
  68. py_neuromodulation/nm_settings.py +289 -68
  69. py_neuromodulation/nm_settings.yaml +244 -0
  70. py_neuromodulation/nm_sharpwaves.py +423 -401
  71. py_neuromodulation/nm_stats.py +464 -480
  72. py_neuromodulation/nm_stream.py +398 -0
  73. py_neuromodulation/nm_stream_abc.py +166 -218
  74. py_neuromodulation/nm_types.py +193 -0
  75. py_neuromodulation/plots/STN_surf.mat +0 -0
  76. py_neuromodulation/plots/Vertices.mat +0 -0
  77. py_neuromodulation/plots/faces.mat +0 -0
  78. py_neuromodulation/plots/grid.mat +0 -0
  79. {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.5.dist-info}/METADATA +185 -182
  80. py_neuromodulation-0.0.5.dist-info/RECORD +83 -0
  81. {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.5.dist-info}/WHEEL +1 -2
  82. {py_neuromodulation-0.0.3.dist-info → py_neuromodulation-0.0.5.dist-info/licenses}/LICENSE +21 -21
  83. docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
  84. docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -233
  85. docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
  86. docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
  87. docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
  88. docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
  89. docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
  90. docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -68
  91. docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -239
  92. docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
  93. docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -97
  94. docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
  95. docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -192
  96. docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
  97. docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +0 -76
  98. docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +0 -97
  99. docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +0 -240
  100. docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +0 -233
  101. docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +0 -63
  102. docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +0 -219
  103. docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +0 -210
  104. docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +0 -192
  105. docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +0 -219
  106. docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +0 -121
  107. docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +0 -68
  108. docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +0 -64
  109. docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +0 -189
  110. docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +0 -210
  111. docs/source/auto_examples/plot_0_first_demo.py +0 -189
  112. docs/source/auto_examples/plot_1_example_BIDS.py +0 -240
  113. docs/source/auto_examples/plot_2_example_add_feature.py +0 -76
  114. docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +0 -219
  115. docs/source/auto_examples/plot_4_example_gridPointProjection.py +0 -210
  116. docs/source/auto_examples/plot_5_example_rmap_computing.py +0 -64
  117. docs/source/auto_examples/plot_6_real_time_demo.py +0 -121
  118. docs/source/conf.py +0 -105
  119. examples/plot_0_first_demo.py +0 -189
  120. examples/plot_1_example_BIDS.py +0 -240
  121. examples/plot_2_example_add_feature.py +0 -76
  122. examples/plot_3_example_sharpwave_analysis.py +0 -219
  123. examples/plot_4_example_gridPointProjection.py +0 -210
  124. examples/plot_5_example_rmap_computing.py +0 -64
  125. examples/plot_6_real_time_demo.py +0 -121
  126. packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +0 -4
  127. packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +0 -104
  128. packages/realtime_decoding/build/lib/realtime_decoding/features.py +0 -163
  129. packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +0 -15
  130. packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +0 -345
  131. packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +0 -54
  132. packages/tmsi/build/lib/TMSiFileFormats/__init__.py +0 -37
  133. packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +0 -36
  134. packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +0 -200
  135. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +0 -496
  136. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +0 -236
  137. packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +0 -977
  138. packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +0 -35
  139. packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +0 -116
  140. packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +0 -294
  141. packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +0 -229
  142. packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +0 -102
  143. packages/tmsi/build/lib/TMSiPlotters/__init__.py +0 -2
  144. packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +0 -39
  145. packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +0 -234
  146. packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +0 -440
  147. packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +0 -44
  148. packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +0 -446
  149. packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +0 -589
  150. packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +0 -1326
  151. packages/tmsi/build/lib/TMSiSDK/__init__.py +0 -54
  152. packages/tmsi/build/lib/TMSiSDK/device.py +0 -588
  153. packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +0 -34
  154. packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +0 -1764
  155. packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +0 -34
  156. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +0 -1366
  157. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +0 -520
  158. packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +0 -165
  159. packages/tmsi/build/lib/TMSiSDK/error.py +0 -95
  160. packages/tmsi/build/lib/TMSiSDK/sample_data.py +0 -63
  161. packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +0 -99
  162. packages/tmsi/build/lib/TMSiSDK/settings.py +0 -45
  163. packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +0 -111
  164. packages/tmsi/build/lib/__init__.py +0 -4
  165. packages/tmsi/build/lib/apex_sdk/__init__.py +0 -34
  166. packages/tmsi/build/lib/apex_sdk/device/__init__.py +0 -41
  167. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +0 -1009
  168. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +0 -239
  169. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +0 -668
  170. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +0 -1611
  171. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +0 -38
  172. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +0 -57
  173. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +0 -44
  174. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +0 -150
  175. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +0 -36
  176. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +0 -48
  177. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +0 -108
  178. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +0 -39
  179. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +0 -77
  180. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +0 -150
  181. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +0 -129
  182. packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +0 -59
  183. packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +0 -57
  184. packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +0 -83
  185. packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +0 -201
  186. packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +0 -103
  187. packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +0 -43
  188. packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +0 -50
  189. packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +0 -118
  190. packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +0 -33
  191. packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +0 -44
  192. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +0 -50
  193. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +0 -136
  194. packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +0 -126
  195. packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +0 -113
  196. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +0 -134
  197. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +0 -60
  198. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +0 -42
  199. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +0 -42
  200. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +0 -72
  201. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +0 -98
  202. py_neuromodulation/nm_EpochStream.py +0 -92
  203. py_neuromodulation/nm_across_patient_decoding.py +0 -927
  204. py_neuromodulation/nm_cohortwrapper.py +0 -435
  205. py_neuromodulation/nm_eval_timing.py +0 -239
  206. py_neuromodulation/nm_features_abc.py +0 -39
  207. py_neuromodulation/nm_stream_offline.py +0 -358
  208. py_neuromodulation/utils/_logging.py +0 -24
  209. py_neuromodulation-0.0.3.dist-info/RECORD +0 -188
  210. py_neuromodulation-0.0.3.dist-info/top_level.txt +0 -5
  211. tests/__init__.py +0 -0
  212. tests/conftest.py +0 -117
  213. tests/test_all_examples.py +0 -10
  214. tests/test_all_features.py +0 -63
  215. tests/test_bispectra.py +0 -70
  216. tests/test_bursts.py +0 -105
  217. tests/test_feature_sampling_rates.py +0 -143
  218. tests/test_fooof.py +0 -16
  219. tests/test_initalization_offline_stream.py +0 -41
  220. tests/test_multiprocessing.py +0 -58
  221. tests/test_nan_values.py +0 -29
  222. tests/test_nm_filter.py +0 -95
  223. tests/test_nm_resample.py +0 -63
  224. tests/test_normalization_settings.py +0 -146
  225. tests/test_notch_filter.py +0 -31
  226. tests/test_osc_features.py +0 -424
  227. tests/test_preprocessing_filter.py +0 -151
  228. tests/test_rereference.py +0 -171
  229. tests/test_sampling.py +0 -57
  230. tests/test_settings_change_after_init.py +0 -76
  231. tests/test_sharpwave.py +0 -165
  232. tests/test_target_channel_add.py +0 -100
  233. tests/test_timing.py +0 -80
@@ -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