Browse Source

first successful windows build

Michael Kistler 1 month ago
parent
commit
e6eb72430e
10 changed files with 578 additions and 266 deletions
  1. 40 1
      README.md
  2. 68 1
      browse-edf.py
  3. BIN
      edf-app/favicon.ico
  4. BIN
      edf-app/favicon.png
  5. 115 0
      edf-app/favicon.svg
  6. 27 0
      edf-app/installer.cfg
  7. 73 118
      edf-app/main.py
  8. 162 68
      merge-edf.py
  9. 20 53
      split-edf.py
  10. 73 25
      split-edf2.py

+ 40 - 1
README.md

@@ -58,4 +58,43 @@ deactivate the edf-tools virtualenv
58 58
 - install.bat: executes the above steps to install and create the virtualenv
59 59
 - activate.bat: activate the edf-tools virutalenv
60 60
 - run.bat: activate, run python script and deactivate
61
-- deactivate.bat: deactivate the edf-tools virtualenv
61
+- deactivate.bat: deactivate the edf-tools virtualenv
62
+
63
+# pyEDFLib
64
+
65
+## Install
66
+
67
+- python, make sure you check the box "include Python in PATH"
68
+- msvc build tools
69
+- virtualenv
70
+- PySide2
71
+- numpy
72
+- pyedflib
73
+- pynsist
74
+
75
+Microsoft C++ Build tools from Microsof: [MSVC 2015 download link](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=2ahUKEwjJ6I3JrZTlAhVD2aQKHZvCAlcQFjAAegQIABAB&url=https%3A%2F%2Fgo.microsoft.com%2Ffwlink%2F%3FLinkId%3D691126&usg=AOvVaw0geDw_h-TSCfzTMvYE2ZOw)
76
+or [from python wiki](https://wiki.python.org/moin/WindowsCompilers)
77
+
78
+
79
+`pip install virtualenv numpy pyedflib PySide2`
80
+
81
+
82
+install git [from here](https://git-scm.com/download/win)
83
+
84
+`mkdir ~/workspace`
85
+`cd ~/workspace`
86
+`git clone https://gitea.kzacom.ch/klm1/edf-tools`
87
+
88
+## Usage
89
+
90
+
91
+
92
+## windows installer
93
+Mac with Homebrew: `brew install makensis`
94
+install pynsist via pip
95
+
96
+run `pynsist installer.cfg`
97
+
98
+
99
+
100
+

+ 68 - 1
browse-edf.py

@@ -1,6 +1,73 @@
1 1
 from pathlib import Path
2
-import mne
3 2
 import matplotlib.pyplot as plt
4 3
 import numpy as np
5 4
 import scipy as sp
5
+from pathlib import Path
6
+import os
7
+#import splitedf2
8
+import pyedflib
9
+
10
+from datetime import datetime, timedelta
11
+
12
+import pyedflib
13
+import mne
14
+
15
+file_type = pyedflib.FILETYPE_EDF
16
+
17
+use_precomputed = True
18
+merge_dir = Path('edf', 'tmp')
19
+fp = Path('edf', '24h_1ch_sample2.EDF')
20
+file_in_2 = Path('edf', '24h_1ch_sample3.EDF')
21
+file_in_3 = Path('edf', 'Pekka_1ch_FastFix_125hz_fw340rc3_23-07-45.EDF')
22
+file_in_4 = Path('edf', 'Pekka_1ch_FastFix_125hz_fw340rc3_23-07-45_part_fixed_0.edf')
23
+file_out = Path('edf', '24h_1ch_sampleconcate.EDF')
24
+
25
+parts = merge_dir.glob('./*.EDF')
26
+
27
+
28
+## read edf file
29
+f_in = pyedflib.EdfReader(os.fspath(fp))
30
+file_duration= f_in.getFileDuration()
31
+start_date = f_in.getStartdatetime()
32
+header = f_in.getHeader()
33
+
34
+sample_frequencies = f_in.getSampleFrequencies()
35
+signal_headers = f_in.getSignalHeaders()
36
+channel_info = f_in.getSignalHeaders()
37
+channels = f_in.signals_in_file
38
+
39
+
40
+## write same file again
41
+f_out = fp.with_name(fp.stem + "copy").with_suffix(fp.suffix)
42
+fo = pyedflib.EdfWriter(os.fspath(f_out), channels, file_type=pyedflib.FILETYPE_EDF)
43
+fo.setHeader(header)
44
+fo.n_channels = channels
45
+fo.channels = channel_info
46
+fo.setStartdatetime(start_date)
47
+fo.duration = 1
48
+fo.file_type = file_type
49
+data_list = list()
50
+for channel in range(0, channels):
51
+    sample_frequency = f_in.getSampleFrequency(channel)
52
+    signal_header = f_in.getSignalHeader(channel)
53
+    fo.setSignalHeader(channel, signal_header)
54
+    print(signal_header)
55
+    #fo.writePhysicalSamples(f_in.readSignal(channel))
56
+    # fo.writeDigitalSamples(f_in.readSignal(channel))
57
+    #data_list.append(f_in.readSignal(channel, start=slice_start, n=duration * sample_frequency))
58
+    data_list.append(f_in.readSignal(channel))
59
+
60
+fo.setSignalHeaders(signal_headers)
61
+fo.writeSamples(data_list)
62
+fo.close()
63
+
64
+## compare values
65
+
66
+f_in_2 = pyedflib.EdfReader(os.fspath(f_out))
67
+out_file_duration= f_in_2.getFileDuration()
68
+out_start_date = f_in_2.getStartdatetime()
69
+
6 70
 
71
+print(file_duration, out_file_duration)
72
+print(start_date, out_start_date)
73
+print(header, f_in_2.getHeader())

BIN
edf-app/favicon.ico


BIN
edf-app/favicon.png


+ 115 - 0
edf-app/favicon.svg

@@ -0,0 +1,115 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<svg
3
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+   xmlns:cc="http://creativecommons.org/ns#"
5
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+   xmlns:svg="http://www.w3.org/2000/svg"
7
+   xmlns="http://www.w3.org/2000/svg"
8
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
+   inkscape:export-ydpi="66.779999"
11
+   inkscape:export-xdpi="66.779999"
12
+   inkscape:export-filename="/Users/mkistler/workspace/edf-tools/favicon.ico.png"
13
+   inkscape:version="1.0beta1 (32d4812, 2019-09-19)"
14
+   sodipodi:docname="favicon.svg"
15
+   viewBox="0 0 92 91.999997"
16
+   height="92"
17
+   width="92"
18
+   xml:space="preserve"
19
+   id="svg2"
20
+   version="1.1">
21
+  <metadata
22
+     id="metadata8">
23
+    <rdf:RDF>
24
+      <cc:Work
25
+         rdf:about="">
26
+        <dc:format>image/svg+xml</dc:format>
27
+        <dc:type
28
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
29
+        <dc:title></dc:title>
30
+      </cc:Work>
31
+    </rdf:RDF>
32
+  </metadata>
33
+  <defs
34
+     id="defs6">
35
+    <clipPath
36
+       id="clipPath18"
37
+       clipPathUnits="userSpaceOnUse">
38
+      <path
39
+         inkscape:connector-curvature="0"
40
+         id="path16"
41
+         d="M 0,841.89 H 595.276 V 0 H 0 Z" />
42
+    </clipPath>
43
+  </defs>
44
+  <sodipodi:namedview
45
+     inkscape:document-rotation="0"
46
+     fit-margin-bottom="0"
47
+     fit-margin-right="0"
48
+     fit-margin-left="0"
49
+     fit-margin-top="0"
50
+     inkscape:current-layer="g10"
51
+     inkscape:window-maximized="1"
52
+     inkscape:window-y="23"
53
+     inkscape:window-x="49"
54
+     inkscape:cy="67.186535"
55
+     inkscape:cx="29.857525"
56
+     inkscape:zoom="7.4940598"
57
+     showgrid="false"
58
+     id="namedview4"
59
+     inkscape:window-height="1395"
60
+     inkscape:window-width="2511"
61
+     inkscape:pageshadow="2"
62
+     inkscape:pageopacity="0"
63
+     guidetolerance="10"
64
+     gridtolerance="10"
65
+     objecttolerance="10"
66
+     borderopacity="1"
67
+     bordercolor="#666666"
68
+     pagecolor="#ffffff" />
69
+  <g
70
+     transform="matrix(1.3333333,0,0,-1.3333333,-230.56369,864.58136)"
71
+     inkscape:label="Symbol Logo Evismo FC"
72
+     inkscape:groupmode="layer"
73
+     id="g10">
74
+    <rect
75
+       style="fill:#495961;stroke-width:0.89968"
76
+       transform="scale(1,-1)"
77
+       y="-648.43604"
78
+       x="172.92278"
79
+       height="69"
80
+       width="69"
81
+       id="rect968" />
82
+    <g
83
+       style="fill:#ffffff"
84
+       transform="matrix(0.22289815,0,0,0.22289815,143.40632,499.18987)"
85
+       id="g135">
86
+      <g
87
+         id="g20"
88
+         transform="translate(341.292,484.0323)"
89
+         style="fill:#ffffff;fill-opacity:1">
90
+        <path
91
+           d="m 0,0 h -54.262 v 53.97 h -53.592 v 54.262 h 53.592 v 53.97 h -26.853 c -44.541,0 -80.649,-36.108 -80.649,-80.65 V -53.143 h 134.805 c 44.48,0 80.54,36.059 80.54,80.539 V 53.97 H 0 Z"
92
+           style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
93
+           id="path22"
94
+           inkscape:connector-curvature="0" />
95
+      </g>
96
+      <path
97
+         d="m 394.873,646.234 h -53.581 v -53.97 h 53.581 z"
98
+         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
99
+         id="path24"
100
+         inkscape:connector-curvature="0" />
101
+    </g>
102
+    <text
103
+       transform="scale(1,-1)"
104
+       id="text139"
105
+       y="-584.11151"
106
+       x="183.27043"
107
+       style="font-style:normal;font-weight:normal;font-size:30px;line-height:1.25;font-family:sans-serif;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.75"
108
+       xml:space="preserve"><tspan
109
+         id="tspan141"
110
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:12px;font-family:Roboto;-inkscape-font-specification:'Roboto Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#ffffff;stroke-width:0.75"
111
+         y="-584.11151"
112
+         x="183.27043"
113
+         sodipodi:role="line">EDFtools</tspan></text>
114
+  </g>
115
+</svg>

+ 27 - 0
edf-app/installer.cfg

@@ -0,0 +1,27 @@
1
+[Application]
2
+name=evismo EDFtools
3
+version=0.1
4
+publisher=michael.kistler@evismo.com
5
+entry_point=main:launch_app
6
+console=true
7
+icon=favicon.ico
8
+
9
+[Shortcut EDF Tools]
10
+target=main:launch_app
11
+console=true
12
+icon=favicon.ico
13
+
14
+[Python]
15
+version=3.6.0
16
+bitness=64
17
+format=bundled
18
+include_msvcrt=true
19
+
20
+[Include]
21
+packages=main
22
+pypi_wheels = PySide2==5.13.1
23
+    pyEDFlib==0.1.14
24
+    edfrd==0.7
25
+    mne==0.19.0
26
+    numpy==1.17.2
27
+;exclude=edf-data/

edf-tools-app.py → edf-app/main.py

@@ -1,48 +1,15 @@
1 1
 
2
-import sys
3 2
 import os
4
-import random
5
-#from PySide2.QtWidgets import (QApplication, QLabel, QPushButton, QVBoxLayout, QWidget)
6
-#from PySide2.QtCore import Slot, Qt
3
+import sys
7 4
 from pathlib import Path
8
-#import splitedf2
9
-import pyedflib
10
-
11 5
 from datetime import datetime, timedelta
6
+import pyedflib
12 7
 
13 8
 
14
-# def openFileNameDialog(self):
15
-#     options = QFileDialog.Options()
16
-#     options |= QFileDialog.DontUseNativeDialog
17
-#     fileName, _ = QFileDialog.getOpenFileName(self,"QFileDialog.getOpenFileName()", "","All Files (*);;Python Files (*.py)", options=options)
18
-#     if fileName:
19
-#         print(fileName)
20
-
21
-
22
-# app = QApplication([])
23
-# file_browser_label = QLabel('Select EDF file:')
24
-# file_browser_dialog_options = QFileDialog.Options()
25
-# file_browser_dialog_options |= QFileDialog.DontUseNativeDialog
26
-# file_browser = QFileDialog()
27
-# days_in_parts_label = QLabel('Split size (days')
28
-# days_in_parts = QLineEdit()
29
-#
30
-# window = QWidget()
31
-# layout = QVBoxLayout()
32
-# # layout.addWidget(QPushButton('Top'))
33
-# # layout.addWidget(QPushButton('Bottom'))
34
-# layout.addWidget(file_browser_label)
35
-# layout.addWidget(file_browser)
36
-# layout.addWidget(days_in_parts_label)
37
-# layout.addWidget(days_in_parts)
38
-# window.setLayout(layout)
39
-# window.show()
40
-# app.exec_()
41
-import sys
42 9
 
43
-from PySide2.QtGui import QValidator, QIntValidator
10
+from PySide2.QtGui import QValidator, QIntValidator, QDoubleValidator
44 11
 from PySide2.QtCore import Slot, Signal, Qt
45
-from PySide2. QtWidgets import (QApplication, QWidget, QComboBox, QDialog,
12
+from PySide2.QtWidgets import (QApplication, QWidget, QComboBox, QDialog,
46 13
 QDialogButtonBox, QFormLayout, QGridLayout, QGroupBox, QHBoxLayout,
47 14
 QLabel, QLineEdit, QMenu, QMenuBar, QPushButton, QSpinBox, QTextEdit, QInputDialog, QFileDialog,
48 15
 QVBoxLayout)
@@ -86,13 +53,17 @@ class App(QWidget):
86 53
         #file_browser_dialog_options |= QFileDialog.DontUseNativeDialog
87 54
         #file_browser = QFileDialog()
88 55
         self.only_Int = QIntValidator()
56
+        self.only_Float = QDoubleValidator()
57
+        self.only_Float.setBottom(1)
58
+        self.only_Float.setTop(3)
59
+        self.only_Float.setDecimals(1)
89 60
        #self.LineEdit.setValidator(self.only_Int)
90 61
         self.days_in_parts_label = QLabel('Split size (days')
91 62
         self.days_in_parts = QLineEdit()
92 63
         self.days_in_parts.setObjectName("daysparts")
93 64
         self.days_in_parts.setClearButtonEnabled(False)
94 65
         self.days_in_parts.setMaximumWidth(30)
95
-        self.days_in_parts.setValidator(self.only_Int)
66
+        self.days_in_parts.setValidator(self.only_Float)
96 67
         self.samples_start_label = QLabel('Start [hh:mm:ss]')
97 68
         self.samples_start = QLineEdit()
98 69
         self.samples_start.setMaximumWidth(80)
@@ -171,12 +142,12 @@ class App(QWidget):
171 142
                 sys.exit()
172 143
 
173 144
         if isinstance(self.days_in_parts.text(), str) and self.days_in_parts.text():
174
-            self.days_in_parts_value = int(self.days_in_parts.text())
175
-            if self.days_in_parts_value >= 1:
145
+            self.days_in_parts_value = float(self.days_in_parts.text())
146
+            if self.days_in_parts_value >= 0:
176 147
                 self.interval_in_seconds_td = timedelta(days=self.days_in_parts_value)
177 148
             else:
178 149
                 self.days_in_parts_value = None
179
-                print('days: input number has to be >= 1')
150
+                print('days: input number has to be >= 0')
180 151
         else:
181 152
             self.days_in_parts_value = None
182 153
             print(self.days_in_parts_value, self.days_in_parts.text())
@@ -245,11 +216,6 @@ class App(QWidget):
245 216
             print('file to split:', os.fspath(self.fp))
246 217
 
247 218
 
248
-
249
-
250
-
251
-
252
-
253 219
         split_edf(self.fp, self.interval_in_seconds_td, start_time_td=self.start_time_td, end_time_td=self.end_time_td)
254 220
 
255 221
 
@@ -306,18 +272,19 @@ def _parts(duration, start, end, period):
306 272
 
307 273
     part = eff_duration // period
308 274
     rest = eff_duration % period
309
-    if rest == 0:
310
-        print("wft, exactly n days long. how godly is this!")
311
-
312
-    if period/rest < 2:
313
-        parts = part + 1
275
+    if not rest == 0:
276
+        if period / rest < 2:
277
+            parts = part + 1
314 278
 
279
+        else:
280
+            parts = part
315 281
     else:
282
+        print("wft, exactly n days long. how godly is this!")
316 283
         parts = part
317 284
 
318 285
     interval = list()
319 286
     # start interval
320
-    interval.append(list([start,start+period-1]))
287
+    interval.append(list([start,start+period]))
321 288
     for p in range(1,parts-1,1):
322 289
         interval.append(list([p*period,(p+1)*period]))
323 290
     # last / end interval
@@ -325,6 +292,30 @@ def _parts(duration, start, end, period):
325 292
     return interval
326 293
 
327 294
 
295
+def _annotations(annotations, slice=None):
296
+    '''
297
+    this function converts a pyedflib annotations list into list of aggregated values that can be easily writen as single annotation to an EDF file
298
+    If slice is set, only the annotations within the range of the slice is extracted
299
+
300
+    :param annotations: (list), edflib annotations of an edf file
301
+    :param slice: (list), len of 2, with a start time and end timestamp in seconds
302
+    :return: (list) annotations_list, set of annotations with timestamp in seconds, value and description text
303
+    '''
304
+    ## annotations
305
+    ## timestamp, value, default -1, name/description
306
+    annotations_list = list()
307
+    for i in range(0, len(annotations[0])):
308
+        if slice:
309
+            if annotations[0][i]-slice[0] >=0 and slice[1]-annotations[0][i] >=0:
310
+                #in range(slice[0],slice[-1]+1):
311
+                annotation = list([annotations[0][i]-slice[0], annotations[1][i], annotations[2][i]])
312
+                annotations_list.append(annotation)
313
+        else:
314
+            annotation = list([annotations[0][i], annotations[1][i], annotations[2][i]])
315
+            annotations_list.append(annotation)
316
+
317
+    return annotations_list
318
+
328 319
 def split_edf(fp,interval_td, start_time_td, end_time_td=None):
329 320
     '''
330 321
     split an EDF file into files of n days
@@ -363,48 +354,38 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
363 354
 
364 355
 
365 356
 
366
-    file_type = pyedflib.FILETYPE_EDF
357
+    file_type = pyedflib.FILETYPE_EDFPLUS
367 358
     start_date = f_in.getStartdatetime()
368
-    new_start_date = None
359
+
369 360
     print(start_date)
370
-    sample_frequencies = f_in.getSampleFrequencies()
371
-    signal_headers = f_in.getSignalHeaders()
361
+    #sample_frequencies = f_in.getSampleFrequencies()
362
+    #signal_headers = f_in.getSignalHeaders()
363
+    #annotations = f_in.readAnnotations()
372 364
     channel_info = f_in.getSignalHeaders()
373
-    annotations = f_in.readAnnotations()
374 365
     channels = f_in.signals_in_file
375
-
376
-    # print(file_duration_stripped_s, "stripped seconds in file")
377
-    # print(file_duration_s, " seconds in file")
378
-    # print(file_duration_s, interval_in_seconds_td.total_seconds())
366
+    annotations = f_in.readAnnotations()
379 367
 
380 368
     if file_duration_stripped_s > interval_in_s:
381
-        #parts = list(_chunks(range(0, file_duration_stripped_s), int(interval_in_seconds_td.total_seconds())))
382 369
         parts = _parts(file_duration_s, start_time_s, end_time_s, interval_in_s)
383 370
         print(parts)
384
-        #for i in range(0, len(parts)):
385 371
 
386 372
         for part in parts:
373
+
387 374
             slice_start = part[0]
388 375
             slice_end = part[-1]
376
+
389 377
             duration = slice_end-slice_start
390 378
             recording_start_date = start_date + timedelta(seconds=slice_start)
391 379
             print(recording_start_date)
392
-            data_list = list()
393
-
394
-            for channel in range(0,1):
395
-                sample_frequency = f_in.getSampleFrequency(channel)
396
-                signal_header = f_in.getSignalHeader(channel)
397
-                print(signal_header)
398
-
399
-                data_list.append(f_in.readSignal(channel, start=slice_start * sample_frequency, n=duration * sample_frequency))
400 380
 
401
-            print("new duration",duration)
402
-            f_out = fp.with_name(fp.stem + "_" + "slice" + "_" + str(slice_start) + "_" + str(slice_end)).with_suffix(fp.suffix)
381
+            anno = _annotations(annotations, slice=list([slice_start, slice_end]))
403 382
 
404
-            # adapt header files with correct numbers (date)
383
+            data_list = list()
405 384
 
406
-            fo = pyedflib.EdfWriter(os.fspath(f_out),channels,file_type=pyedflib.FILETYPE_EDF)
407
-            fo.path = f_out.name #f.file_name
385
+            f_out = fp.with_name(fp.stem + "_" + "slice" + "_" + str(slice_start) + "_" + str(slice_end)).with_suffix(
386
+                fp.suffix)
387
+            fo = pyedflib.EdfWriter(os.fspath(f_out), channels, file_type=file_type)
388
+            fo.path = f_out.name  # f.file_name
408 389
             fo.file_type = file_type
409 390
             fo.patient_name = f_in.getPatientName()
410 391
             fo.patient_code = f_in.getPatientCode()
@@ -416,15 +397,21 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
416 397
             fo.gender = f_in.getGender()
417 398
             fo.recording_start_time = recording_start_date
418 399
             fo.birthdate = f_in.getBirthdate()
419
-            fo.duration = duration
400
+            fo.duration = 1 #1 second 100000 ms
420 401
             fo.number_of_annotations = 1 if file_type in [pyedflib.FILETYPE_EDFPLUS, pyedflib.FILETYPE_BDFPLUS] else 0
421 402
             fo.n_channels = f_in.signals_in_file
422 403
             fo.channels = channel_info
423
-            fo.sample_buffer = []
424
-            #fo.setHeader(f_in.getHeader())
404
+
405
+            for channel in range(0,channels):
406
+                sample_frequency = f_in.getSampleFrequency(channel)
407
+                #signal_header = f_in.getSignalHeader(channel)
408
+                data_list.append(f_in.readSignal(channel, start=slice_start * sample_frequency, n=duration * sample_frequency))
409
+
425 410
             fo.setSignalHeaders(channel_info)
426
-            fo.writeAnnotation(annotations)
427
-            fo.blockWriteSamples(data_list)
411
+            for a in anno:
412
+                fo.writeAnnotation(a[0],a[1],a[2])
413
+            fo.writeSamples(data_list)
414
+
428 415
             print("written file", f_out.name)
429 416
             del data_list
430 417
             del fo
@@ -432,45 +419,13 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
432 419
     else:
433 420
         print('nothing to do, use trim function for only trimming')
434 421
 
422
+
435 423
     print("finished, you can close the app")
436
-if __name__ == '__main__':
424
+
425
+def launch_app():
437 426
     app = QApplication(sys.argv)
438 427
     ex = App()
439 428
     sys.exit(app.exec_())
440 429
 
441
-#
442
-# class MyWidget(QWidget):
443
-#     def __init__(self):
444
-#         QWidget.__init__(self)
445
-#
446
-#         self.hello = ["Hallo Welt", "你好,世界", "Hei maailma",
447
-#             "Hola Mundo", "Привет мир"]
448
-#
449
-#         self.button = QPushButton("Click me!")
450
-#         self.text = QLabel("Hello World")
451
-#         self.text.setAlignment(Qt.AlignCenter)
452
-#
453
-#         self.layout = QVBoxLayout()
454
-#         self.layout.addWidget(self.text)
455
-#         self.layout.addWidget(self.button)
456
-#         self.setLayout(self.layout)
457
-#
458
-#         # Connecting the signal
459
-#         self.button.clicked.connect(self.magic)
460
-#
461
-#     @Slot()
462
-#     def magic(self):
463
-#         self.text.setText(random.choice(self.hello))
464
-#
465
-#
466
-#
467
-#
468
-#
469
-# if __name__ == "__main__":
470
-#     app = QApplication(sys.argv)
471
-#
472
-#     widget = MyWidget()
473
-#     widget.resize(800, 600)
474
-#     widget.show()
475
-#
476
-#     sys.exit(app.exec_())
430
+if __name__ == '__main__':
431
+    launch_app()

+ 162 - 68
merge-edf.py

@@ -1,82 +1,176 @@
1 1
 from pathlib import Path
2
+import os
2 3
 import mne
3 4
 import matplotlib.pyplot as plt
4 5
 import numpy as np
5 6
 import scipy as sp
6 7
 import pprint as pp
8
+from datetime import timedelta
9
+import pyedflib
7 10
 use_precomputed = True
11
+merge_dir = Path('edf', 'tmp')
8 12
 file_in_1 = Path('edf', '24h_1ch_sample2.EDF')
9 13
 file_in_2 = Path('edf', '24h_1ch_sample3.EDF')
10 14
 file_in_3 = Path('edf', 'Pekka_1ch_FastFix_125hz_fw340rc3_23-07-45.EDF')
11 15
 file_in_4 = Path('edf', 'Pekka_1ch_FastFix_125hz_fw340rc3_23-07-45_part_fixed_0.edf')
12 16
 file_out = Path('edf', '24h_1ch_sampleconcate.EDF')
13 17
 
14
-#file_out = Path('edf', '24h_1ch_samplemerged.raw.fif')
15
-preload = not use_precomputed
16
-preload = use_precomputed
17
-
18
-
19
-# #data = mne.io.read_raw_edf(file_out, preload=preload)
20
-# #print(data.info)
21
-# data3 = mne.io.read_raw_edf(file_in_3, preload=preload)
22
-# info3 = data3.info
23
-# print(info3)
24
-# ch3 = data3.ch_names
25
-# print(ch3)
26
-# # raw1 = data3.get_data(return_times=True)
27
-# # print(data3.annotations)
28
-# # print(data3.find_edf_events)
29
-
30
-
31
-raw1 = mne.io.read_raw_edf(file_in_4, preload=preload)
32
-info1 = raw1.info
33
-pp.pprint(info1)
34
-print(info1['sfreq'])
35
-print(info1['ch_names'])
36
-print(info1['chs'][0])
37
-b
38
-info = mne.create_info(ch_names=list([info1['ch_names'][0]]), ch_types=list(['eeg']), sfreq=info1['sfreq'])
39
-ch1 = raw1.ch_names
40
-data1, times1 = raw1.copy().pick_channels(['ECG']).get_data(return_times=True)
41
-print(times1)
42
-print(raw1.annotations)
43
-print(raw1.find_edf_events)
44
-
45
-#mne.viz(raw1)
46
-
47
-
48
-#data_1 = raw_1.copy()
49
-#data_1 = raw1.copy().get_data()
50
-#data_1 = data_1.copy().pick_types(eeg=True)
51
-#data_1 = data_1.copy().pick_channels(['ECG'])
52
-#print(raw_1)
53
-#print(data_1.info)
54
-#print(len(data_1))
55
-#print(len(raw_1.ch_names), '→', len(data_1.ch_names))
56
-
57
-raw2 = mne.io.read_raw_edf(file_in_2, preload=preload)
58
-info2 = raw2.info
59
-ch2 = raw2.ch_names
60
-data2, times2 = raw2.copy().pick_channels(['ECG']).get_data(return_times=True)
61
-print(ch2)
62
-#print(data_2.info)
63
-#print(len(data_2))
64
-#print(type(data_2))
65
-raws_out, events = mne.io.concatenate_raws([raw1,raw2])
66
-
67
-data_out = np.append(data1,data2)
68
-times_out = np.append(times1,times2)
69
-raw = np.array([data_out, times_out])
70
-#mne.viz(raw_out)
71
-raw_out = mne.io.RawArray(raw,info1,copy='data')
72
-mne.viz.plot_raw(raw_out)
73
-raws_out.save(str(file_out), fmt='single', overwrite=True, picks=['ECG'], drop_small_buffer=True)
74
-print(raw_out)
75
-
76
-#data_1.get_data()
77
-#res = data_1.copy()
78
-#data_out = data_1.append(data_2)
79
-#bogo = mne.viz.plot_raw(data_1)
80
-#print(len(data_out))
81
-# print(data_1.get_data())
18
+parts = merge_dir.glob('./*.EDF')
19
+# for part in parts:
20
+#
21
+#     print(part.name)
22
+
23
+
24
+data_list = list()
25
+file_duration_td_list = list()
26
+start_date_list = list()
27
+channels_list = list()
28
+print(len(list(parts)))
29
+for p in sorted(parts):
30
+    fp = p
31
+    print(p.name,"hallo")
32
+    f = pyedflib.EdfReader(os.fspath(fp))
33
+    file_duration= f.getFileDuration()
34
+    file_duration_td = timedelta(seconds=file_duration)
35
+    file_duration_s = int(file_duration_td.total_seconds())
36
+    file_type = pyedflib.FILETYPE_EDF
37
+    start_date = f.getStartdatetime()
38
+    new_start_date = None
39
+    print(start_date)
40
+    sample_frequencies = f.getSampleFrequencies()
41
+    signal_headers = f.getSignalHeaders()
42
+    channel_info = f.getSignalHeaders()
43
+    channels = f.signals_in_file
44
+
45
+parties = merge_dir.glob('./*.EDF')
46
+
47
+for part in sorted(parts):
48
+    f_in = pyedflib.EdfReader(os.fspath(part))
49
+    f_in_channels = f_in.signals_in_file
50
+    print(part.name)
51
+    f_in_duration = f_in.getFileDuration()
52
+    f_in_duration_td = timedelta(seconds=f_in_duration)
53
+    f_in_duration_s = int(f_in_duration_td.total_seconds())
54
+    #recording_start_date = start_date + timedelta(seconds=slice_start)
55
+    #print(recording_start_date)
56
+    file_duration_td_list.append(f_in_duration_td)
57
+    f_in_start_date = f_in.getStartdatetime()
58
+    start_date_list.append(f_in_start_date)
59
+    channels_list.append(f_in_channels)
60
+
61
+
62
+    for channel in range(0,f_in_channels):
63
+        sample_frequency = f_in.getSampleFrequency(channel)
64
+        signal_header = f_in.getSignalHeader(channel)
65
+        print(signal_header)
66
+
67
+        data_list.append(f_in.readSignal(channel))
68
+
69
+#print("new duration", f_in_duration)
70
+f_out = fp.with_name(fp.stem + "_" + "merged").with_suffix(fp.suffix)
71
+
72
+# start = slice_start * sample_frequency, n = duration * sample_frequency)
73
+# adapt header files with correct numbers (date)
74
+
75
+merged_duration = 1
76
+
77
+
78
+fo = pyedflib.EdfWriter(os.fspath(f_out), channels, file_type=pyedflib.FILETYPE_EDF)
79
+fo.path = f_out.name  # f.file_name
80
+fo.file_type = file_type
81
+fo.patient_name = f.getPatientName()
82
+fo.patient_code = f.getPatientCode()
83
+fo.technician = f.getTechnician()
84
+fo.equipment = f.getEquipment()
85
+fo.recording_additional = f.getRecordingAdditional()
86
+fo.patient_additional = f.getPatientAdditional()
87
+fo.admincode = f.getAdmincode()
88
+fo.gender = f.getGender()
89
+fo.recording_start_time = f.getStartdatetime()
90
+fo.birthdate = f.getBirthdate()
91
+fo.duration = merged_duration
92
+fo.number_of_annotations = 1 if file_type in [pyedflib.FILETYPE_EDFPLUS, pyedflib.FILETYPE_BDFPLUS] else 0
93
+fo.n_channels = f.signals_in_file
94
+fo.channels = channel_info
95
+fo.sample_buffer = []
96
+# fo.setHeader(f_in.getHeader())
97
+fo.setSignalHeaders(f.getSignalHeaders())
98
+fo.writeSamples(data_list)
99
+print("written file", f_out.name)
100
+del data_list
101
+del fo
102
+
103
+
104
+
105
+mne = False
106
+if mne:
107
+
108
+    #file_out = Path('edf', '24h_1ch_samplemerged.raw.fif')
109
+    preload = not use_precomputed
110
+    preload = use_precomputed
111
+
112
+
113
+    # #data = mne.io.read_raw_edf(file_out, preload=preload)
114
+    # #print(data.info)
115
+    # data3 = mne.io.read_raw_edf(file_in_3, preload=preload)
116
+    # info3 = data3.info
117
+    # print(info3)
118
+    # ch3 = data3.ch_names
119
+    # print(ch3)
120
+    # # raw1 = data3.get_data(return_times=True)
121
+    # # print(data3.annotations)
122
+    # # print(data3.find_edf_events)
123
+
124
+
125
+    raw1 = mne.io.read_raw_edf(file_in_4, preload=preload)
126
+    info1 = raw1.info
127
+    pp.pprint(info1)
128
+    print(info1['sfreq'])
129
+    print(info1['ch_names'])
130
+    print(info1['chs'][0])
131
+    b
132
+    info = mne.create_info(ch_names=list([info1['ch_names'][0]]), ch_types=list(['eeg']), sfreq=info1['sfreq'])
133
+    ch1 = raw1.ch_names
134
+    data1, times1 = raw1.copy().pick_channels(['ECG']).get_data(return_times=True)
135
+    print(times1)
136
+    print(raw1.annotations)
137
+    print(raw1.find_edf_events)
138
+
139
+    #mne.viz(raw1)
140
+
141
+
142
+    #data_1 = raw_1.copy()
143
+    #data_1 = raw1.copy().get_data()
144
+    #data_1 = data_1.copy().pick_types(eeg=True)
145
+    #data_1 = data_1.copy().pick_channels(['ECG'])
146
+    #print(raw_1)
147
+    #print(data_1.info)
148
+    #print(len(data_1))
149
+    #print(len(raw_1.ch_names), '→', len(data_1.ch_names))
150
+
151
+    raw2 = mne.io.read_raw_edf(file_in_2, preload=preload)
152
+    info2 = raw2.info
153
+    ch2 = raw2.ch_names
154
+    data2, times2 = raw2.copy().pick_channels(['ECG']).get_data(return_times=True)
155
+    print(ch2)
156
+    #print(data_2.info)
157
+    #print(len(data_2))
158
+    #print(type(data_2))
159
+    raws_out, events = mne.io.concatenate_raws([raw1,raw2])
160
+
161
+    data_out = np.append(data1,data2)
162
+    times_out = np.append(times1,times2)
163
+    raw = np.array([data_out, times_out])
164
+    #mne.viz(raw_out)
165
+    raw_out = mne.io.RawArray(raw,info1,copy='data')
166
+    mne.viz.plot_raw(raw_out)
167
+    raws_out.save(str(file_out), fmt='single', overwrite=True, picks=['ECG'], drop_small_buffer=True)
168
+    print(raw_out)
169
+
170
+    #data_1.get_data()
171
+    #res = data_1.copy()
172
+    #data_out = data_1.append(data_2)
173
+    #bogo = mne.viz.plot_raw(data_1)
174
+    #print(len(data_out))
175
+    # print(data_1.get_data())
82 176
 print('finished')

+ 20 - 53
split-edf.py

@@ -1,34 +1,17 @@
1 1
 from pathlib import Path
2
-import mne
3
-import matplotlib.pyplot as plt
2
+#import mne
3
+#import matplotlib.pyplot as plt
4 4
 import numpy as np
5
-import scipy as sp
5
+#import scipy as sp
6 6
 import os
7 7
 import sys
8 8
 import edfrd
9 9
 import getopt
10 10
 import datetime as dt
11 11
 import pprint as pp
12
+import pyedflib
12 13
 
13 14
 
14
-# split_on = False
15
-# recalc_on = True
16
-# merge_on = False
17
-
18
-## Workspace
19
-#ws = Path(Path.home(),'workspace','edf-tools')
20
-
21
-
22
-#
23
-# File management on the share:
24
-# cardioflex/TicketingSystem/OFF_309810
25
-# - OFF_Ticket#
26
-# cardioflex/Platform Temp/patID_ID
27
-#
28
-# data goes into tmp
29
-# then goes into .
30
-# then deletes tmp (cleanup)
31
-
32 15
 def main(argv):
33 16
     '''
34 17
 
@@ -62,6 +45,12 @@ def chunks(l,n):
62 45
         yield l[i:i + n]
63 46
 
64 47
 
48
+def set_record_start_in_header(fp, start_date):
49
+    f = pyedflib.EdfReader(os.fspath(fp))
50
+
51
+
52
+
53
+
65 54
 def split_on(fp, temp_dir_name='tmp'):
66 55
     '''
67 56
     split an EDF file into files of n days
@@ -73,7 +62,7 @@ def split_on(fp, temp_dir_name='tmp'):
73 62
 
74 63
 
75 64
     S_PER_DAY = 86400
76
-    INTERVAL_DAYS = 1
65
+    INTERVAL_DAYS = 0.5
77 66
 
78 67
     ws_dir = fp.parent
79 68
     tmp_dir = Path(ws_dir, temp_dir_name)
@@ -94,10 +83,11 @@ def split_on(fp, temp_dir_name='tmp'):
94 83
 
95 84
         ## find number of parts, iterate over it
96 85
         if nr_data_record > part_size_s:
97
-            parts = list(chunks(range(0,nr_data_record), part_size_s))
86
+            parts = list(chunks(range(0,nr_data_record),int(part_size_s)))
98 87
 
99 88
             for i in range(0,len(parts)):
100 89
                 part_header = header
90
+                print(part_header.startdate_of_recording)
101 91
                 slice_start = parts[i][0]
102 92
                 slice_end = parts[i][-1]
103 93
 
@@ -106,6 +96,8 @@ def split_on(fp, temp_dir_name='tmp'):
106 96
                 print('part #',i,' range: ', slice_start,'-',slice_end)
107 97
 
108 98
                 data_record_part = edfrd.read_data_records(f, header, start=slice_start, end=slice_end)
99
+                print('data record type', type(data_record_part))
100
+
109 101
 
110 102
                 with fp_out.open('wb') as fout:
111 103
                     edfrd.write_header(fout, part_header)
@@ -119,7 +111,7 @@ def split_on(fp, temp_dir_name='tmp'):
119 111
                 with fp_out.open('rb') as fout:
120 112
                     header = edfrd.read_header(fout, calculate_number_of_data_records=True)
121 113
                     data_record = edfrd.read_data_records(fout, header)
122
-
114
+                    print('data record type', type(data_record))
123 115
                     fp_part = Path(ws_dir, fp_out.name)
124 116
                     with fp_part.open('wb') as f0:
125 117
                         print(f'Writing file:{os.fspath(fp_part)}')
@@ -127,6 +119,10 @@ def split_on(fp, temp_dir_name='tmp'):
127 119
                         edfrd.write_data_records(f0, data_record)
128 120
                     f0.close()
129 121
                 fout.close()
122
+
123
+                # with fp_out.open('wb') as fout:
124
+                #     edfrd.write_header(fout,header)
125
+                #     fout.close()
130 126
                 fp_out.unlink()
131 127
 
132 128
         else:
@@ -143,35 +139,6 @@ def split_on(fp, temp_dir_name='tmp'):
143 139
         tmp_dir.rmdir()
144 140
         print('tmp dir removed')
145 141
 
146
-    # tmp_parts = list(tmp_dir.glob(f'./*{fp_suffix}'))
147
-    #
148
-    # if tmp_parts:
149
-    #     for tmp_part in tmp_parts:
150
-    #         fp_part = Path(ws_dir,tmp_part.name)
151
-    #         with tmp_part.open('rb') as f:
152
-    #             header = edfrd.read_header(f, calculate_number_of_data_records=True)
153
-    #             data_record = edfrd.read_data_records(f, header)
154
-    #
155
-    #             with fp_part.open('wb') as f0:
156
-    #                 print(f'Writing file:{os.fspath(fp_part)}')
157
-    #                 edfrd.write_header(f0, header)
158
-    #                 edfrd.write_data_records(f0, data_record)
159
-    #             f0.close()
160
-    #         f.close()
161
-    #
162
-    #     print('cleaning up tmp dir')
163
-    #     for tmp_part in tmp_parts:
164
-    #         tmp_part.unlink()
165
-    #
166
-    #     dir_content = list(tmp_dir.glob('./*'))
167
-    #     if not dir_content:
168
-    #         print('cant remove tmp dir as directory not empty')
169
-    #     else:
170
-    #         tmp_dir.rmdir()
171
-    #     print('tmp dir removed')
172
-    # else:
173
-    #     print('no parts found to convert from tmp folder')
174
-
175 142
     print('finished splitting ', os.fspath(fp))
176 143
 if __name__ == "__main__":
177 144
     main(sys.argv[1:])

+ 73 - 25
split-edf2.py

@@ -60,7 +60,7 @@ optional parameters:\n\
60 60
         elif opt in ("-e", "--end_time"):
61 61
             end_time = str(arg)
62 62
         elif opt in ("-d", "--interval_in_days"):
63
-            interval_in_days = int(arg)
63
+            interval_in_days = float(arg)
64 64
             print("-d", interval_in_days)
65 65
 
66 66
 
@@ -89,6 +89,37 @@ optional parameters:\n\
89 89
 
90 90
     split_edf(fp,interval_in_seconds_td,start_time_td=start_time_td, end_time_td=end_time_td)
91 91
 
92
+def _annotations(annotations, slice=None):
93
+    '''
94
+    this function converts a pyedflib annotations list into list of aggregated values that can be easily writen as single annotation to an EDF file
95
+    If slice is set, only the annotations within the range of the slice is extracted
96
+
97
+    :param annotations: (list), edflib annotations of an edf file
98
+    :param slice: (list), len of 2, with a start time and end timestamp in seconds
99
+    :return: (list) annotations_list, set of annotations with timestamp in seconds, value and description text
100
+    '''
101
+    ## annotations
102
+    ## timestamp, value, default -1, name/description
103
+    annotations_list = list()
104
+    for i in range(0, len(annotations[0])):
105
+        print(annotations[0][i]-slice[0])
106
+        print(slice[1]-annotations[0][i])
107
+        if slice:
108
+            if annotations[0][i]-slice[0] >=0 and slice[1]-annotations[0][i] >=0:
109
+                #in range(slice[0],slice[-1]+1):
110
+                annotation = list([annotations[0][i]-slice[0], annotations[1][i], annotations[2][i]])
111
+                annotations_list.append(annotation)
112
+        else:
113
+            annotation = list([annotations[0][i], annotations[1][i], annotations[2][i]])
114
+            annotations_list.append(annotation)
115
+
116
+    return annotations_list
117
+
118
+    # print('timedelta (s):', )
119
+    # print('value:', annotations[1][i])
120
+    # print('description:', annotations[2][i])
121
+
122
+
92 123
 def _chunks(l,n):
93 124
     for i in range(0, len(l), n):
94 125
         yield l[i:i + n]
@@ -113,18 +144,19 @@ def _parts(duration, start, end, period):
113 144
 
114 145
     part = eff_duration // period
115 146
     rest = eff_duration % period
116
-    if rest == 0:
117
-        print("wft, exactly n days long. how godly is this!")
118
-
119
-    if period/rest < 2:
120
-        parts = part + 1
147
+    if not rest == 0:
148
+        if period/rest < 2:
149
+            parts = part + 1
121 150
 
151
+        else:
152
+            parts = part
122 153
     else:
154
+        print("wft, exactly n days long. how godly is this!")
123 155
         parts = part
124 156
 
125 157
     interval = list()
126 158
     # start interval
127
-    interval.append(list([start,start+period-1]))
159
+    interval.append(list([start,start+period]))
128 160
     for p in range(1,parts-1,1):
129 161
         interval.append(list([p*period,(p+1)*period]))
130 162
     # last / end interval
@@ -158,7 +190,7 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
158 190
         file_duration_stripped_s = file_duration_s-start_time_s
159 191
 
160 192
 
161
-    file_type = pyedflib.FILETYPE_EDF
193
+    file_type = pyedflib.FILETYPE_EDFPLUS
162 194
     start_date = f_in.getStartdatetime()
163 195
     new_start_date = None
164 196
     print(start_date)
@@ -166,6 +198,16 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
166 198
     signal_headers = f_in.getSignalHeaders()
167 199
     channel_info = f_in.getSignalHeaders()
168 200
     channels = f_in.signals_in_file
201
+    annotations = f_in.readAnnotations()
202
+    len(annotations)
203
+    ## annotations
204
+    ## timestamp, value, default -1, name/description
205
+
206
+    # test_an = _annotations(annotations,slice=list([0, 50000]))
207
+    # for ta in test_an:
208
+    #     print(ta)
209
+    #     print(ta[0],ta[1],ta[2])
210
+
169 211
 
170 212
     # print(file_duration_stripped_s, "stripped seconds in file")
171 213
     # print(file_duration_s, " seconds in file")
@@ -183,22 +225,12 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
183 225
             duration = slice_end-slice_start
184 226
             recording_start_date = start_date + timedelta(seconds=slice_start)
185 227
             print(recording_start_date)
228
+            anno = _annotations(annotations, slice=list([slice_start, slice_end]))
186 229
             data_list = list()
187
-
188
-            for channel in range(0,channels):
189
-                sample_frequency = f_in.getSampleFrequency(channel)
190
-                signal_header = f_in.getSignalHeader(channel)
191
-                print(signal_header)
192
-
193
-                data_list.append(f_in.readSignal(channel, start=slice_start * sample_frequency, n=duration * sample_frequency))
194
-
195
-            print("new duration",duration)
196
-            f_out = fp.with_name(fp.stem + "_" + "slice" + "_" + str(slice_start) + "_" + str(slice_end)).with_suffix(fp.suffix)
197
-
198
-            # adapt header files with correct numbers (date)
199
-
200
-            fo = pyedflib.EdfWriter(os.fspath(f_out),channels,file_type=pyedflib.FILETYPE_EDF)
201
-            fo.path = f_out.name #f.file_name
230
+            f_out = fp.with_name(fp.stem + "_" + "slice" + "_" + str(slice_start) + "_" + str(slice_end)).with_suffix(
231
+                fp.suffix)
232
+            fo = pyedflib.EdfWriter(os.fspath(f_out), channels, file_type=file_type)
233
+            fo.path = f_out.name  # f.file_name
202 234
             fo.file_type = file_type
203 235
             fo.patient_name = f_in.getPatientName()
204 236
             fo.patient_code = f_in.getPatientCode()
@@ -210,13 +242,29 @@ def split_edf(fp,interval_td, start_time_td, end_time_td=None):
210 242
             fo.gender = f_in.getGender()
211 243
             fo.recording_start_time = recording_start_date
212 244
             fo.birthdate = f_in.getBirthdate()
213
-            fo.duration = duration
245
+            fo.duration = 1
214 246
             fo.number_of_annotations = 1 if file_type in [pyedflib.FILETYPE_EDFPLUS, pyedflib.FILETYPE_BDFPLUS] else 0
215 247
             fo.n_channels = f_in.signals_in_file
216 248
             fo.channels = channel_info
217
-            fo.sample_buffer = []
249
+
250
+            for channel in range(0,channels):
251
+
252
+                sample_frequency = f_in.getSampleFrequency(channel)
253
+                signal_header = f_in.getSignalHeader(channel)
254
+                #fo.setSignalHeader(channel, signal_header)
255
+                data_list.append(f_in.readSignal(channel, start=slice_start * sample_frequency, n=duration * sample_frequency))
256
+
257
+
258
+            # adapt header files with correct numbers (date)
259
+
260
+            #fo.sample_buffer = []
218 261
             #fo.setHeader(f_in.getHeader())
219 262
             fo.setSignalHeaders(channel_info)
263
+
264
+            for a in anno:
265
+                fo.writeAnnotation(a[0], a[1], a[2])
266
+
267
+            fo.writeSamples(data_list)
220 268
             fo.writeSamples(data_list)
221 269
             print("written file", f_out.name)
222 270
             del data_list