py-neuromodulation 0.0.2__py3-none-any.whl → 0.0.3__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 (204) hide show
  1. docs/build/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
  2. docs/build/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +233 -0
  3. docs/build/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
  4. docs/build/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
  5. docs/build/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
  6. docs/build/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
  7. docs/build/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
  8. docs/build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +68 -0
  9. docs/build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +239 -0
  10. docs/build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
  11. docs/build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +97 -0
  12. docs/build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
  13. docs/build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +192 -0
  14. docs/build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
  15. docs/source/_build/html/_downloads/09df217f95985497f45d69e2d4bdc5b1/plot_2_example_add_feature.py +76 -0
  16. docs/source/_build/html/_downloads/0d0d0a76e8f648d5d3cbc47da6351932/plot_real_time_demo.py +97 -0
  17. docs/source/_build/html/_downloads/3b4900a2b2818ff30362215b76f7d5eb/plot_1_example_BIDS.py +240 -0
  18. docs/source/_build/html/_downloads/5d73cadc59a8805c47e3b84063afc157/plot_example_BIDS.py +233 -0
  19. docs/source/_build/html/_downloads/7660317fa5a6bfbd12fcca9961457fc4/plot_example_rmap_computing.py +63 -0
  20. docs/source/_build/html/_downloads/7e92dd2e6cc86b239d14cafad972ae4f/plot_3_example_sharpwave_analysis.py +219 -0
  21. docs/source/_build/html/_downloads/839e5b319379f7fd9e867deb00fd797f/plot_example_gridPointProjection.py +210 -0
  22. docs/source/_build/html/_downloads/ae8be19afe5e559f011fc9b138968ba0/plot_first_demo.py +192 -0
  23. docs/source/_build/html/_downloads/b8b06cacc17969d3725a0b6f1d7741c5/plot_example_sharpwave_analysis.py +219 -0
  24. docs/source/_build/html/_downloads/c2db0bf2b334d541b00662b991682256/plot_6_real_time_demo.py +121 -0
  25. docs/source/_build/html/_downloads/c31a86c0b68cb4167d968091ace8080d/plot_example_add_feature.py +68 -0
  26. docs/source/_build/html/_downloads/ce3914826f782cbd1ea8fd024eaf0ac3/plot_5_example_rmap_computing.py +64 -0
  27. docs/source/_build/html/_downloads/da36848a41e6a3235d91fb7cfb6d59b4/plot_0_first_demo.py +189 -0
  28. docs/source/_build/html/_downloads/eaa4305c75b19a1e2eea941f742a6331/plot_4_example_gridPointProjection.py +210 -0
  29. docs/source/auto_examples/plot_0_first_demo.py +189 -0
  30. docs/source/auto_examples/plot_1_example_BIDS.py +240 -0
  31. docs/source/auto_examples/plot_2_example_add_feature.py +76 -0
  32. docs/source/auto_examples/plot_3_example_sharpwave_analysis.py +219 -0
  33. docs/source/auto_examples/plot_4_example_gridPointProjection.py +210 -0
  34. docs/source/auto_examples/plot_5_example_rmap_computing.py +64 -0
  35. docs/source/auto_examples/plot_6_real_time_demo.py +121 -0
  36. docs/source/conf.py +105 -0
  37. examples/plot_0_first_demo.py +189 -0
  38. examples/plot_1_example_BIDS.py +240 -0
  39. examples/plot_2_example_add_feature.py +76 -0
  40. examples/plot_3_example_sharpwave_analysis.py +219 -0
  41. examples/plot_4_example_gridPointProjection.py +210 -0
  42. examples/plot_5_example_rmap_computing.py +64 -0
  43. examples/plot_6_real_time_demo.py +121 -0
  44. packages/realtime_decoding/build/lib/realtime_decoding/__init__.py +4 -0
  45. packages/realtime_decoding/build/lib/realtime_decoding/decoder.py +104 -0
  46. packages/realtime_decoding/build/lib/realtime_decoding/features.py +163 -0
  47. packages/realtime_decoding/build/lib/realtime_decoding/helpers.py +15 -0
  48. packages/realtime_decoding/build/lib/realtime_decoding/run_decoding.py +345 -0
  49. packages/realtime_decoding/build/lib/realtime_decoding/trainer.py +54 -0
  50. packages/tmsi/build/lib/TMSiFileFormats/__init__.py +37 -0
  51. packages/tmsi/build/lib/TMSiFileFormats/file_formats/__init__.py +36 -0
  52. packages/tmsi/build/lib/TMSiFileFormats/file_formats/lsl_stream_writer.py +200 -0
  53. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_file_writer.py +496 -0
  54. packages/tmsi/build/lib/TMSiFileFormats/file_formats/poly5_to_edf_converter.py +236 -0
  55. packages/tmsi/build/lib/TMSiFileFormats/file_formats/xdf_file_writer.py +977 -0
  56. packages/tmsi/build/lib/TMSiFileFormats/file_readers/__init__.py +35 -0
  57. packages/tmsi/build/lib/TMSiFileFormats/file_readers/edf_reader.py +116 -0
  58. packages/tmsi/build/lib/TMSiFileFormats/file_readers/poly5reader.py +294 -0
  59. packages/tmsi/build/lib/TMSiFileFormats/file_readers/xdf_reader.py +229 -0
  60. packages/tmsi/build/lib/TMSiFileFormats/file_writer.py +102 -0
  61. packages/tmsi/build/lib/TMSiPlotters/__init__.py +2 -0
  62. packages/tmsi/build/lib/TMSiPlotters/gui/__init__.py +39 -0
  63. packages/tmsi/build/lib/TMSiPlotters/gui/_plotter_gui.py +234 -0
  64. packages/tmsi/build/lib/TMSiPlotters/gui/plotting_gui.py +440 -0
  65. packages/tmsi/build/lib/TMSiPlotters/plotters/__init__.py +44 -0
  66. packages/tmsi/build/lib/TMSiPlotters/plotters/hd_emg_plotter.py +446 -0
  67. packages/tmsi/build/lib/TMSiPlotters/plotters/impedance_plotter.py +589 -0
  68. packages/tmsi/build/lib/TMSiPlotters/plotters/signal_plotter.py +1326 -0
  69. packages/tmsi/build/lib/TMSiSDK/__init__.py +54 -0
  70. packages/tmsi/build/lib/TMSiSDK/device.py +588 -0
  71. packages/tmsi/build/lib/TMSiSDK/devices/__init__.py +34 -0
  72. packages/tmsi/build/lib/TMSiSDK/devices/saga/TMSi_Device_API.py +1764 -0
  73. packages/tmsi/build/lib/TMSiSDK/devices/saga/__init__.py +34 -0
  74. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_device.py +1366 -0
  75. packages/tmsi/build/lib/TMSiSDK/devices/saga/saga_types.py +520 -0
  76. packages/tmsi/build/lib/TMSiSDK/devices/saga/xml_saga_config.py +165 -0
  77. packages/tmsi/build/lib/TMSiSDK/error.py +95 -0
  78. packages/tmsi/build/lib/TMSiSDK/sample_data.py +63 -0
  79. packages/tmsi/build/lib/TMSiSDK/sample_data_server.py +99 -0
  80. packages/tmsi/build/lib/TMSiSDK/settings.py +45 -0
  81. packages/tmsi/build/lib/TMSiSDK/tmsi_device.py +111 -0
  82. packages/tmsi/build/lib/__init__.py +4 -0
  83. packages/tmsi/build/lib/apex_sdk/__init__.py +34 -0
  84. packages/tmsi/build/lib/apex_sdk/device/__init__.py +41 -0
  85. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API.py +1009 -0
  86. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_enums.py +239 -0
  87. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_API_structures.py +668 -0
  88. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_device.py +1611 -0
  89. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_dongle.py +38 -0
  90. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_event_reader.py +57 -0
  91. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_channel.py +44 -0
  92. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_config.py +150 -0
  93. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_const.py +36 -0
  94. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_impedance_channel.py +48 -0
  95. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/apex_info.py +108 -0
  96. packages/tmsi/build/lib/apex_sdk/device/devices/apex/apex_structures/dongle_info.py +39 -0
  97. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/download_measurement.py +77 -0
  98. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/eeg_measurement.py +150 -0
  99. packages/tmsi/build/lib/apex_sdk/device/devices/apex/measurements/impedance_measurement.py +129 -0
  100. packages/tmsi/build/lib/apex_sdk/device/threads/conversion_thread.py +59 -0
  101. packages/tmsi/build/lib/apex_sdk/device/threads/sampling_thread.py +57 -0
  102. packages/tmsi/build/lib/apex_sdk/device/tmsi_channel.py +83 -0
  103. packages/tmsi/build/lib/apex_sdk/device/tmsi_device.py +201 -0
  104. packages/tmsi/build/lib/apex_sdk/device/tmsi_device_enums.py +103 -0
  105. packages/tmsi/build/lib/apex_sdk/device/tmsi_dongle.py +43 -0
  106. packages/tmsi/build/lib/apex_sdk/device/tmsi_event_reader.py +50 -0
  107. packages/tmsi/build/lib/apex_sdk/device/tmsi_measurement.py +118 -0
  108. packages/tmsi/build/lib/apex_sdk/sample_data_server/__init__.py +33 -0
  109. packages/tmsi/build/lib/apex_sdk/sample_data_server/event_data.py +44 -0
  110. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data.py +50 -0
  111. packages/tmsi/build/lib/apex_sdk/sample_data_server/sample_data_server.py +136 -0
  112. packages/tmsi/build/lib/apex_sdk/tmsi_errors/error.py +126 -0
  113. packages/tmsi/build/lib/apex_sdk/tmsi_sdk.py +113 -0
  114. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/apex/apex_structure_generator.py +134 -0
  115. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/decorators.py +60 -0
  116. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/logger_filter.py +42 -0
  117. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/singleton.py +42 -0
  118. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/support_functions.py +72 -0
  119. packages/tmsi/build/lib/apex_sdk/tmsi_utilities/tmsi_logger.py +98 -0
  120. py_neuromodulation/{helper.py → _write_example_dataset_helper.py} +1 -1
  121. py_neuromodulation/nm_EpochStream.py +2 -3
  122. py_neuromodulation/nm_IO.py +43 -70
  123. py_neuromodulation/nm_RMAP.py +308 -11
  124. py_neuromodulation/nm_analysis.py +1 -1
  125. py_neuromodulation/nm_artifacts.py +25 -0
  126. py_neuromodulation/nm_bispectra.py +64 -29
  127. py_neuromodulation/nm_bursts.py +44 -30
  128. py_neuromodulation/nm_coherence.py +2 -1
  129. py_neuromodulation/nm_features.py +4 -2
  130. py_neuromodulation/nm_filter.py +63 -32
  131. py_neuromodulation/nm_filter_preprocessing.py +91 -0
  132. py_neuromodulation/nm_fooof.py +47 -29
  133. py_neuromodulation/nm_mne_connectivity.py +1 -1
  134. py_neuromodulation/nm_normalization.py +50 -74
  135. py_neuromodulation/nm_oscillatory.py +151 -31
  136. py_neuromodulation/nm_plots.py +13 -10
  137. py_neuromodulation/nm_rereference.py +10 -8
  138. py_neuromodulation/nm_run_analysis.py +28 -13
  139. py_neuromodulation/nm_sharpwaves.py +103 -136
  140. py_neuromodulation/nm_stats.py +44 -30
  141. py_neuromodulation/nm_stream_abc.py +18 -10
  142. py_neuromodulation/nm_stream_offline.py +181 -40
  143. py_neuromodulation/utils/_logging.py +24 -0
  144. {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/METADATA +182 -142
  145. py_neuromodulation-0.0.3.dist-info/RECORD +188 -0
  146. {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/WHEEL +2 -1
  147. py_neuromodulation-0.0.3.dist-info/top_level.txt +5 -0
  148. tests/__init__.py +0 -0
  149. tests/conftest.py +117 -0
  150. tests/test_all_examples.py +10 -0
  151. tests/test_all_features.py +63 -0
  152. tests/test_bispectra.py +70 -0
  153. tests/test_bursts.py +105 -0
  154. tests/test_feature_sampling_rates.py +143 -0
  155. tests/test_fooof.py +16 -0
  156. tests/test_initalization_offline_stream.py +41 -0
  157. tests/test_multiprocessing.py +58 -0
  158. tests/test_nan_values.py +29 -0
  159. tests/test_nm_filter.py +95 -0
  160. tests/test_nm_resample.py +63 -0
  161. tests/test_normalization_settings.py +146 -0
  162. tests/test_notch_filter.py +31 -0
  163. tests/test_osc_features.py +424 -0
  164. tests/test_preprocessing_filter.py +151 -0
  165. tests/test_rereference.py +171 -0
  166. tests/test_sampling.py +57 -0
  167. tests/test_settings_change_after_init.py +76 -0
  168. tests/test_sharpwave.py +165 -0
  169. tests/test_target_channel_add.py +100 -0
  170. tests/test_timing.py +80 -0
  171. py_neuromodulation/data/README +0 -6
  172. py_neuromodulation/data/dataset_description.json +0 -8
  173. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/MOV_aligned_features_ch_ECOG_RIGHT_0_all.png +0 -0
  174. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/all_feature_plt.pdf +0 -0
  175. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_FEATURES.csv +0 -182
  176. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_LM_ML_RES.p +0 -0
  177. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SETTINGS.json +0 -273
  178. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_SIDECAR.json +0 -6
  179. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_decoding_performance.png +0 -0
  180. py_neuromodulation/data/derivatives/sub-testsub_ses-EphysMedOff_task-gripforce_run-0/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_nm_channels.csv +0 -11
  181. py_neuromodulation/data/participants.json +0 -32
  182. py_neuromodulation/data/participants.tsv +0 -2
  183. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_coordsystem.json +0 -5
  184. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_space-mni_electrodes.tsv +0 -11
  185. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_channels.tsv +0 -11
  186. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.eeg +0 -0
  187. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.json +0 -18
  188. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vhdr +0 -35
  189. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/ieeg/sub-testsub_ses-EphysMedOff_task-gripforce_run-0_ieeg.vmrk +0 -13
  190. py_neuromodulation/data/sub-testsub/ses-EphysMedOff/sub-testsub_ses-EphysMedOff_scans.tsv +0 -2
  191. py_neuromodulation/grid_cortex.tsv +0 -40
  192. py_neuromodulation/grid_subcortex.tsv +0 -1429
  193. py_neuromodulation/nm_settings.json +0 -290
  194. py_neuromodulation/plots/STN_surf.mat +0 -0
  195. py_neuromodulation/plots/Vertices.mat +0 -0
  196. py_neuromodulation/plots/faces.mat +0 -0
  197. py_neuromodulation/plots/grid.mat +0 -0
  198. py_neuromodulation/py_neuromodulation.egg-info/PKG-INFO +0 -104
  199. py_neuromodulation/py_neuromodulation.egg-info/dependency_links.txt +0 -1
  200. py_neuromodulation/py_neuromodulation.egg-info/requires.txt +0 -26
  201. py_neuromodulation/py_neuromodulation.egg-info/top_level.txt +0 -1
  202. py_neuromodulation-0.0.2.dist-info/RECORD +0 -73
  203. /py_neuromodulation/{py_neuromodulation.egg-info/SOURCES.txt → utils/__init__.py} +0 -0
  204. {py_neuromodulation-0.0.2.dist-info → py_neuromodulation-0.0.3.dist-info}/LICENSE +0 -0
@@ -0,0 +1,977 @@
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