misleep 0.2.8__tar.gz → 0.2.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {misleep-0.2.8 → misleep-0.2.9}/PKG-INFO +1 -1
- {misleep-0.2.8 → misleep-0.2.9}/misleep/__init__.py +1 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/config.ini +3 -3
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/dialog.py +63 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/main_window.py +102 -27
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/thread.py +15 -1
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/main_window_ui.py +13 -5
- misleep-0.2.9/misleep/gui/uis/save_data_dialog_ui.py +67 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/io/signal_io.py +94 -1
- {misleep-0.2.8 → misleep-0.2.9}/misleep/utils/signals.py +5 -2
- {misleep-0.2.8 → misleep-0.2.9}/misleep.egg-info/PKG-INFO +1 -1
- {misleep-0.2.8 → misleep-0.2.9}/setup.py +1 -1
- misleep-0.2.9/test/__pycache__/test_annotation_io.cpython-38-pytest-8.3.5.pyc +0 -0
- misleep-0.2.9/test/test_signal_io.py +77 -0
- misleep-0.2.8/misleep/gui/uis/save_data_dialog_ui.py +0 -48
- misleep-0.2.8/test/test_signal_io.py +0 -39
- {misleep-0.2.8 → misleep-0.2.9}/LICENSE +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/README.md +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/__main__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage_model/P30_EEG_F_lightgbm.pkl +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage_model/P30_EEG_P_lightgbm.pkl +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage_model/ado_EEG_F_lightgbm.pkl +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage_model/ado_EEG_P_lightgbm.pkl +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage_model/adult_EEG_F_lightgbm.pkl +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/auto_stage_model/adult_EEG_P_lightgbm.pkl +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/analysis/detection.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/about.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/resources/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/resources/entire_logo.png +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/resources/logo.png +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/resources/misleep.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/resources/misleep.qrc +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/show.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/spec_window.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/SWA_detect_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/about_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/auto_stage_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/horizontal_line_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/label_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/spec_window_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/spindle_detect_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/state_spectral_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/uis/transfer_result_dialog_ui.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/gui/utils.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/io/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/io/annotation_io.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/io/base.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/preprocessing/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/preprocessing/channel.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/preprocessing/signals.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/preprocessing/spectral.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/utils/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/utils/annotation.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/utils/logger_handler.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/utils/self_antropy.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/viz/__init__.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/viz/hypnogram.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/viz/signals.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep/viz/spectral.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep.egg-info/SOURCES.txt +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep.egg-info/dependency_links.txt +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep.egg-info/requires.txt +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/misleep.egg-info/top_level.txt +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/setup.cfg +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/test/test_annotation_io.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/test/test_loadmat73.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/test/test_midata.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/test/test_show.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/test/test_signals_viz.py +0 -0
- {misleep-0.2.8 → misleep-0.2.9}/test/test_spectral_viz.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[gui]
|
|
2
|
-
version = v0.2.
|
|
3
|
-
updatetime = 2025/4/
|
|
4
|
-
marker = ['
|
|
2
|
+
version = v0.2.9
|
|
3
|
+
updatetime = 2025/4/23
|
|
4
|
+
marker = ['half_signal', 'first REM', 'WindEEG', 'W-R ', 'maker']
|
|
5
5
|
startend = ['burst-supression', 'REM', 'Wake', 'Spindle', 'SWA', 'start end label', 'start end label']
|
|
6
6
|
statemap = {"1": "NREM", "2": "REM", "3": "Wake", "4": "INIT", "5": "IS", "6": "MicroArousal"}
|
|
7
7
|
statecolor = {"1": "orange", "2": "skyblue", "3": "red", "4": "white", "5": "green", "6": "pink"}
|
|
@@ -20,6 +20,7 @@ from misleep.gui.uis.horizontal_line_dialog_ui import Ui_horizontal_line_dialog
|
|
|
20
20
|
from misleep.gui.uis.SWA_detect_dialog_ui import Ui_SWADetectDialog
|
|
21
21
|
from misleep.gui.uis.spindle_detect_dialog_ui import Ui_SpindleDetectDialog
|
|
22
22
|
from misleep.gui.uis.auto_stage_dialog_ui import Ui_AutoStageDialog
|
|
23
|
+
from misleep.gui.uis.save_data_dialog_ui import Ui_SaveDataDialog
|
|
23
24
|
from misleep.gui.thread import SaveThread
|
|
24
25
|
from misleep.io.annotation_io import transfer_result
|
|
25
26
|
from misleep.utils.signals import signal_filter
|
|
@@ -981,6 +982,68 @@ class AutoStageDialog(QDialog, Ui_AutoStageDialog):
|
|
|
981
982
|
event.ignore()
|
|
982
983
|
self.closed = True
|
|
983
984
|
self.hide()
|
|
985
|
+
|
|
986
|
+
|
|
987
|
+
class SaveData_dialog(QDialog, Ui_SaveDataDialog):
|
|
988
|
+
def __init__(self, parent=None):
|
|
989
|
+
"""
|
|
990
|
+
Initialize the save data dialog of MiSleep
|
|
991
|
+
"""
|
|
992
|
+
super().__init__(parent)
|
|
993
|
+
|
|
994
|
+
self.setupUi(self)
|
|
995
|
+
|
|
996
|
+
self.CropStartTimeEditor.setDisabled(True)
|
|
997
|
+
self.CropEndTimeEditor.setDisabled(True)
|
|
998
|
+
|
|
999
|
+
self.CropStartTimeEditor.setDisabled(True)
|
|
1000
|
+
self.CropEndTimeEditor.setDisabled(True)
|
|
1001
|
+
self.CropDataStartCheckBox.clicked.connect(self.crop_start_time_editor_changed)
|
|
1002
|
+
self.CropDataEndCheckBox.clicked.connect(self.crop_end_time_editor_changed)
|
|
1003
|
+
|
|
1004
|
+
self.channel_slm = QStringListModel()
|
|
1005
|
+
|
|
1006
|
+
self.OKBtn.clicked.connect(self.okEvent)
|
|
1007
|
+
self.CancelBtn.clicked.connect(self.cancelEvent)
|
|
1008
|
+
self.closed = True
|
|
1009
|
+
|
|
1010
|
+
def crop_start_time_editor_changed(self):
|
|
1011
|
+
if self.CropDataStartCheckBox.isChecked():
|
|
1012
|
+
self.CropStartTimeEditor.setEnabled(True)
|
|
1013
|
+
if not self.CropDataStartCheckBox.isChecked():
|
|
1014
|
+
self.CropStartTimeEditor.setDisabled(True)
|
|
1015
|
+
|
|
1016
|
+
def crop_end_time_editor_changed(self):
|
|
1017
|
+
if self.CropDataEndCheckBox.isChecked():
|
|
1018
|
+
self.CropEndTimeEditor.setEnabled(True)
|
|
1019
|
+
if not self.CropDataEndCheckBox.isChecked():
|
|
1020
|
+
self.CropEndTimeEditor.setDisabled(True)
|
|
1021
|
+
|
|
1022
|
+
def fill_midata_params(self, midata):
|
|
1023
|
+
"""
|
|
1024
|
+
Fill the midata params to the dialog, including time, channels, sf, etc.
|
|
1025
|
+
"""
|
|
1026
|
+
self.channel_slm.setStringList(midata.channels)
|
|
1027
|
+
self.ChannelListView.setModel(self.channel_slm)
|
|
1028
|
+
|
|
1029
|
+
self.CropStartTimeEditor.setDateTime(datetime.datetime.strptime(midata.time, "%Y%m%d-%H:%M:%S"))
|
|
1030
|
+
# End time should add the midata.duration to format the time
|
|
1031
|
+
end_time = datetime.datetime.strptime(midata.time, "%Y%m%d-%H:%M:%S") + datetime.timedelta(seconds=midata.duration)
|
|
1032
|
+
self.CropEndTimeEditor.setDateTime(end_time)
|
|
1033
|
+
|
|
1034
|
+
def okEvent(self):
|
|
1035
|
+
self.closed = False
|
|
1036
|
+
self.hide()
|
|
1037
|
+
|
|
1038
|
+
def cancelEvent(self):
|
|
1039
|
+
"""Triggered by the `cancel` button"""
|
|
1040
|
+
self.closed = True
|
|
1041
|
+
self.hide()
|
|
1042
|
+
|
|
1043
|
+
def closeEvent(self, event):
|
|
1044
|
+
event.ignore()
|
|
1045
|
+
self.closed = True
|
|
1046
|
+
self.hide()
|
|
984
1047
|
|
|
985
1048
|
|
|
986
1049
|
|
|
@@ -28,7 +28,7 @@ from misleep.gui.utils import create_new_mianno, identify_startend_color
|
|
|
28
28
|
from misleep.utils.annotation import lst2group
|
|
29
29
|
from misleep.gui.about import about_dialog
|
|
30
30
|
from misleep.gui.dialog import label_dialog, transferResult_dialog, stateSpectral_dialog, \
|
|
31
|
-
horizontalLine_dialog, SWADetectionDialog, SpindleDetectionDialog, AutoStageDialog
|
|
31
|
+
horizontalLine_dialog, SWADetectionDialog, SpindleDetectionDialog, AutoStageDialog, SaveData_dialog
|
|
32
32
|
from misleep.gui.spec_window import SpecWindow
|
|
33
33
|
from misleep.gui.uis.main_window_ui import Ui_MiSleep
|
|
34
34
|
from misleep.preprocessing.spectral import spectrogram, spectrum, band_power
|
|
@@ -129,6 +129,9 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
129
129
|
# Initial state spectral dialog
|
|
130
130
|
self.state_spectral_dialog = stateSpectral_dialog()
|
|
131
131
|
|
|
132
|
+
# Initial data saving dialog
|
|
133
|
+
self.save_data_dialog = SaveData_dialog()
|
|
134
|
+
|
|
132
135
|
# Initial horizontal line dialog
|
|
133
136
|
self.horizontal_line_dialog = horizontalLine_dialog()
|
|
134
137
|
# The horizontal_line dict contains the line value, line color, line comment
|
|
@@ -166,6 +169,7 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
166
169
|
self.actionSWA_detection.triggered.connect(self.swa_detection)
|
|
167
170
|
self.actionSpindle_Detection.triggered.connect(self.spindle_detection)
|
|
168
171
|
self.actionAuto_Stage.triggered.connect(self.auto_stage)
|
|
172
|
+
self.actionSaveData.triggered.connect(self.save_data)
|
|
169
173
|
|
|
170
174
|
# Spectrogram percentile change
|
|
171
175
|
self.PercentileSpin.setValue(self.spectrogram_percentile)
|
|
@@ -384,12 +388,17 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
384
388
|
|
|
385
389
|
def load_anno(self):
|
|
386
390
|
"""Triggered by actionLoad_Annotation"""
|
|
387
|
-
anno_path, _ = QFileDialog.
|
|
391
|
+
anno_path, _ = QFileDialog.getSaveFileName(
|
|
388
392
|
self, "Select annotation file",
|
|
389
393
|
f"{self.config['gui']['openpath']}",
|
|
390
394
|
"txt Files (*.txt *.TXT)"
|
|
391
395
|
)
|
|
392
396
|
|
|
397
|
+
if not os.path.exists(anno_path):
|
|
398
|
+
# Create the file if it doesn't exist
|
|
399
|
+
with open(anno_path, 'w') as file:
|
|
400
|
+
file.write("")
|
|
401
|
+
|
|
393
402
|
if anno_path == "":
|
|
394
403
|
return
|
|
395
404
|
self.anno_path = anno_path
|
|
@@ -1009,10 +1018,11 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
1009
1018
|
# Clear start end label
|
|
1010
1019
|
self.start_end = []
|
|
1011
1020
|
self.start_end.append(sec)
|
|
1021
|
+
elif sec < self.start_end[0]:
|
|
1022
|
+
# Clear start end label and add the second as start
|
|
1023
|
+
self.start_end = []
|
|
1024
|
+
self.start_end.append(sec)
|
|
1012
1025
|
else:
|
|
1013
|
-
if sec < self.start_end[0]:
|
|
1014
|
-
QMessageBox.about(self, "Error", "End should be larger than Start!")
|
|
1015
|
-
return
|
|
1016
1026
|
self.start_end.append(sec + 1)
|
|
1017
1027
|
|
|
1018
1028
|
self.plot_start_end_line()
|
|
@@ -1365,28 +1375,36 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
1365
1375
|
int(end_*self.midata.sf[channel])
|
|
1366
1376
|
]
|
|
1367
1377
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
relative=True)
|
|
1371
|
-
|
|
1372
|
-
f, t, Sxx = spectrogram(signal=signal_data,
|
|
1378
|
+
try:
|
|
1379
|
+
freq, psd = spectrum(signal=signal_data,
|
|
1373
1380
|
sf=self.midata.sf[channel],
|
|
1374
|
-
|
|
1381
|
+
relative=True)
|
|
1382
|
+
|
|
1383
|
+
f, t, Sxx = spectrogram(signal=signal_data,
|
|
1384
|
+
sf=self.midata.sf[channel],
|
|
1385
|
+
step=1, window=5, norm=True)
|
|
1375
1386
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1387
|
+
bandPower = band_power(psd=psd, freq=freq,
|
|
1388
|
+
bands=[[0.5, 4, 'delta'], [4, 9, 'theta']])
|
|
1389
|
+
|
|
1390
|
+
ratio = bandPower['delta'] / bandPower['theta']
|
|
1391
|
+
|
|
1392
|
+
self.spec_window.show_(spectrum=[psd, freq],
|
|
1393
|
+
spectrogram=[f, t, Sxx],
|
|
1394
|
+
percentile_=self.spectrogram_percentile,
|
|
1395
|
+
ratio=ratio, start_end=[start_, end_])
|
|
1396
|
+
|
|
1397
|
+
self.spec_window.activateWindow()
|
|
1398
|
+
self.spec_window.setWindowState(
|
|
1399
|
+
self.spec_window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
|
|
1400
|
+
self.spec_window.showNormal()
|
|
1401
|
+
except Exception as e:
|
|
1402
|
+
QMessageBox.about(
|
|
1403
|
+
self,
|
|
1404
|
+
"Error",
|
|
1405
|
+
"Filter error.",
|
|
1406
|
+
)
|
|
1407
|
+
return
|
|
1390
1408
|
|
|
1391
1409
|
def set_show_duration(self, type_="Combo"):
|
|
1392
1410
|
"""Set show_duration with ShowRangeCombo or SecondNumSpin"""
|
|
@@ -1646,12 +1664,17 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
1646
1664
|
For when I only want to save a file but not cover the current labels
|
|
1647
1665
|
"""
|
|
1648
1666
|
if self.anno_path == "":
|
|
1649
|
-
anno_path, _ = QFileDialog.
|
|
1667
|
+
anno_path, _ = QFileDialog.getSaveFileName(
|
|
1650
1668
|
self, "Select annotation file",
|
|
1651
1669
|
f"{self.config['gui']['openpath']}",
|
|
1652
1670
|
"txt Files (*.txt *.TXT)"
|
|
1653
1671
|
)
|
|
1654
1672
|
|
|
1673
|
+
if not os.path.exists(anno_path):
|
|
1674
|
+
# Create the file if it doesn't exist
|
|
1675
|
+
with open(anno_path, 'w') as file:
|
|
1676
|
+
file.write("")
|
|
1677
|
+
|
|
1655
1678
|
if anno_path == "":
|
|
1656
1679
|
return
|
|
1657
1680
|
if not just_save:
|
|
@@ -1667,7 +1690,59 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
1667
1690
|
save_thread.quit()
|
|
1668
1691
|
|
|
1669
1692
|
def save_data(self):
|
|
1670
|
-
"""Save data into file"""
|
|
1693
|
+
"""Save data into file"""
|
|
1694
|
+
|
|
1695
|
+
# Pop up dialog to get save data options
|
|
1696
|
+
self.save_data_dialog.fill_midata_params(midata=self.midata)
|
|
1697
|
+
self.save_data_dialog.exec()
|
|
1698
|
+
if self.save_data_dialog.closed:
|
|
1699
|
+
return
|
|
1700
|
+
|
|
1701
|
+
# Get the selected channels to save data
|
|
1702
|
+
selected_channels = [each.row() for each in self.save_data_dialog.ChannelListView.selectedIndexes()]
|
|
1703
|
+
# If did not select any channel, save all channels
|
|
1704
|
+
if selected_channels == []:
|
|
1705
|
+
selected_channels = list(range(self.midata.n_channels))
|
|
1706
|
+
midata_to_save = self.midata.pick_chs([self.midata.channels[each] for each in selected_channels])
|
|
1707
|
+
|
|
1708
|
+
# Crop data with the checkbox and time editor
|
|
1709
|
+
start_sec = 0
|
|
1710
|
+
end_sec = self.midata.duration
|
|
1711
|
+
|
|
1712
|
+
if self.save_data_dialog.CropDataStartCheckBox.isChecked():
|
|
1713
|
+
start_time = self.save_data_dialog.CropStartTimeEditor.dateTime().toPyDateTime()
|
|
1714
|
+
midata_to_save._time = start_time.strftime("%Y%m%d-%H:%M:%S")
|
|
1715
|
+
start_sec = int(datetime.timedelta.total_seconds(start_time - self.ac_time))
|
|
1716
|
+
if start_sec < 0:
|
|
1717
|
+
start_sec = 0
|
|
1718
|
+
|
|
1719
|
+
if self.save_data_dialog.CropDataEndCheckBox.isChecked():
|
|
1720
|
+
end_time = self.save_data_dialog.CropEndTimeEditor.dateTime().toPyDateTime()
|
|
1721
|
+
end_sec = int(datetime.timedelta.total_seconds(end_time - self.ac_time))
|
|
1722
|
+
if end_sec > self.midata.duration:
|
|
1723
|
+
end_sec = self.midata.duration
|
|
1724
|
+
|
|
1725
|
+
if end_sec <= start_sec:
|
|
1726
|
+
start_sec = 0
|
|
1727
|
+
end_sec = self.midata.duration
|
|
1728
|
+
|
|
1729
|
+
midata_to_save = midata_to_save.crop([start_sec, end_sec])
|
|
1730
|
+
|
|
1731
|
+
data_path, _ = QFileDialog.getSaveFileName(
|
|
1732
|
+
self, "Select a file to save data",
|
|
1733
|
+
f"{self.config['gui']['openpath']}_misleep_saved",
|
|
1734
|
+
"*mat Files (*.mat *.MAT);;*edf Files (*.edf *.EDF);"
|
|
1735
|
+
)
|
|
1736
|
+
if data_path == "":
|
|
1737
|
+
return
|
|
1738
|
+
|
|
1739
|
+
save_thread = SaveThread(file=midata_to_save, file_path=data_path)
|
|
1740
|
+
saved = save_thread.save_data()
|
|
1741
|
+
if saved:
|
|
1742
|
+
QMessageBox.about(self, "Info", f"Data Saved to {data_path}")
|
|
1743
|
+
else:
|
|
1744
|
+
QMessageBox.about(self, "Error", "Data save ERROR")
|
|
1745
|
+
save_thread.quit()
|
|
1671
1746
|
|
|
1672
1747
|
def auto_save(self):
|
|
1673
1748
|
"""Auto save every 5 mins"""
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from PyQt5.QtCore import QObject, QThread
|
|
2
2
|
from misleep.gui.utils import second2time
|
|
3
3
|
from misleep.utils.annotation import lst2group
|
|
4
|
-
from misleep.io.signal_io import load_mat
|
|
4
|
+
from misleep.io.signal_io import load_mat, write_edf, write_mat
|
|
5
5
|
import datetime
|
|
6
6
|
|
|
7
7
|
class SaveThread(QThread):
|
|
@@ -69,6 +69,20 @@ class SaveThread(QThread):
|
|
|
69
69
|
with open(self.file_path, 'w') as f:
|
|
70
70
|
f.write('\n'.join(annos))
|
|
71
71
|
return True
|
|
72
|
+
|
|
73
|
+
def save_data(self):
|
|
74
|
+
"""Data save function"""
|
|
75
|
+
midata= self.file
|
|
76
|
+
if midata is None:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
if self.file_path.endswith('.mat'):
|
|
80
|
+
write_mat(midata.signals, midata.channels, midata.sf, midata.time, self.file_path)
|
|
81
|
+
elif self.file_path.endswith('.edf'):
|
|
82
|
+
write_edf(midata.signals, midata.channels, midata.sf, midata.time, self.file_path)
|
|
83
|
+
else:
|
|
84
|
+
raise ValueError("File type not supported!")
|
|
85
|
+
return True
|
|
72
86
|
|
|
73
87
|
class LoadThread(QThread):
|
|
74
88
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
-
# Form implementation generated from reading ui file '
|
|
3
|
+
# Form implementation generated from reading ui file 'misleep/gui/uis/main_window.ui'
|
|
4
4
|
#
|
|
5
5
|
# Created by: PyQt5 UI code generator 5.15.9
|
|
6
6
|
#
|
|
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|
|
14
14
|
class Ui_MiSleep(object):
|
|
15
15
|
def setupUi(self, MiSleep):
|
|
16
16
|
MiSleep.setObjectName("MiSleep")
|
|
17
|
-
MiSleep.resize(
|
|
17
|
+
MiSleep.resize(1451, 967)
|
|
18
18
|
MiSleep.setFocusPolicy(QtCore.Qt.WheelFocus)
|
|
19
19
|
icon = QtGui.QIcon()
|
|
20
20
|
icon.addPixmap(QtGui.QPixmap(":/logo/logo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
@@ -35,7 +35,7 @@ class Ui_MiSleep(object):
|
|
|
35
35
|
self.HypnoArea.setWidgetResizable(True)
|
|
36
36
|
self.HypnoArea.setObjectName("HypnoArea")
|
|
37
37
|
self.scrollAreaWidgetContents_2 = QtWidgets.QWidget()
|
|
38
|
-
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0,
|
|
38
|
+
self.scrollAreaWidgetContents_2.setGeometry(QtCore.QRect(0, 0, 1069, 128))
|
|
39
39
|
self.scrollAreaWidgetContents_2.setObjectName("scrollAreaWidgetContents_2")
|
|
40
40
|
self.HypnoArea.setWidget(self.scrollAreaWidgetContents_2)
|
|
41
41
|
self.gridLayout_3.addWidget(self.HypnoArea, 2, 0, 1, 1)
|
|
@@ -44,7 +44,7 @@ class Ui_MiSleep(object):
|
|
|
44
44
|
self.SignalArea.setWidgetResizable(True)
|
|
45
45
|
self.SignalArea.setObjectName("SignalArea")
|
|
46
46
|
self.scrollAreaWidgetContents = QtWidgets.QWidget()
|
|
47
|
-
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0,
|
|
47
|
+
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 1069, 764))
|
|
48
48
|
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
|
|
49
49
|
self.SignalArea.setWidget(self.scrollAreaWidgetContents)
|
|
50
50
|
self.gridLayout_3.addWidget(self.SignalArea, 0, 0, 1, 1)
|
|
@@ -291,7 +291,7 @@ class Ui_MiSleep(object):
|
|
|
291
291
|
self.TimeDock.setWidget(self.dockWidgetContents_7)
|
|
292
292
|
MiSleep.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.TimeDock)
|
|
293
293
|
self.menuBar = QtWidgets.QMenuBar(MiSleep)
|
|
294
|
-
self.menuBar.setGeometry(QtCore.QRect(0, 0,
|
|
294
|
+
self.menuBar.setGeometry(QtCore.QRect(0, 0, 1451, 22))
|
|
295
295
|
self.menuBar.setObjectName("menuBar")
|
|
296
296
|
self.menuFile = QtWidgets.QMenu(self.menuBar)
|
|
297
297
|
self.menuFile.setObjectName("menuFile")
|
|
@@ -322,8 +322,14 @@ class Ui_MiSleep(object):
|
|
|
322
322
|
self.actionSpindle_Detection.setObjectName("actionSpindle_Detection")
|
|
323
323
|
self.actionAuto_Stage = QtWidgets.QAction(MiSleep)
|
|
324
324
|
self.actionAuto_Stage.setObjectName("actionAuto_Stage")
|
|
325
|
+
self.actionLoad_AccuSleep_Data = QtWidgets.QAction(MiSleep)
|
|
326
|
+
self.actionLoad_AccuSleep_Data.setObjectName("actionLoad_AccuSleep_Data")
|
|
327
|
+
self.actionSaveData = QtWidgets.QAction(MiSleep)
|
|
328
|
+
self.actionSaveData.setObjectName("actionSaveData")
|
|
325
329
|
self.menuFile.addAction(self.actionLoadData)
|
|
326
330
|
self.menuFile.addAction(self.actionLoadAnnotation)
|
|
331
|
+
self.menuFile.addSeparator()
|
|
332
|
+
self.menuFile.addAction(self.actionSaveData)
|
|
327
333
|
self.menuEvent_Detection.addAction(self.actionSWA_detection)
|
|
328
334
|
self.menuEvent_Detection.addAction(self.actionSpindle_Detection)
|
|
329
335
|
self.menuTools.addAction(self.actionAddLine)
|
|
@@ -399,4 +405,6 @@ class Ui_MiSleep(object):
|
|
|
399
405
|
self.actionSWA_detection.setText(_translate("MiSleep", "SWA Detection"))
|
|
400
406
|
self.actionSpindle_Detection.setText(_translate("MiSleep", "Spindle Detection"))
|
|
401
407
|
self.actionAuto_Stage.setText(_translate("MiSleep", "Auto Stage"))
|
|
408
|
+
self.actionLoad_AccuSleep_Data.setText(_translate("MiSleep", "Load AccuSleep Data"))
|
|
409
|
+
self.actionSaveData.setText(_translate("MiSleep", "Save Data"))
|
|
402
410
|
from misleep.gui.resources import misleep
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Form implementation generated from reading ui file 'misleep/gui/uis/save_data_dialog.ui'
|
|
4
|
+
#
|
|
5
|
+
# Created by: PyQt5 UI code generator 5.15.9
|
|
6
|
+
#
|
|
7
|
+
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
|
8
|
+
# run again. Do not edit this file unless you know what you are doing.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Ui_SaveDataDialog(object):
|
|
15
|
+
def setupUi(self, SaveDataDialog):
|
|
16
|
+
SaveDataDialog.setObjectName("SaveDataDialog")
|
|
17
|
+
SaveDataDialog.resize(409, 516)
|
|
18
|
+
self.gridLayout_2 = QtWidgets.QGridLayout(SaveDataDialog)
|
|
19
|
+
self.gridLayout_2.setObjectName("gridLayout_2")
|
|
20
|
+
self.OKBtn = QtWidgets.QPushButton(SaveDataDialog)
|
|
21
|
+
self.OKBtn.setObjectName("OKBtn")
|
|
22
|
+
self.gridLayout_2.addWidget(self.OKBtn, 1, 0, 1, 1)
|
|
23
|
+
self.CancelBtn = QtWidgets.QPushButton(SaveDataDialog)
|
|
24
|
+
self.CancelBtn.setObjectName("CancelBtn")
|
|
25
|
+
self.gridLayout_2.addWidget(self.CancelBtn, 1, 1, 1, 1)
|
|
26
|
+
self.ACTimeEdit = QtWidgets.QGroupBox(SaveDataDialog)
|
|
27
|
+
self.ACTimeEdit.setObjectName("ACTimeEdit")
|
|
28
|
+
self.gridLayout = QtWidgets.QGridLayout(self.ACTimeEdit)
|
|
29
|
+
self.gridLayout.setObjectName("gridLayout")
|
|
30
|
+
self.CropDataStartCheckBox = QtWidgets.QCheckBox(self.ACTimeEdit)
|
|
31
|
+
self.CropDataStartCheckBox.setObjectName("CropDataStartCheckBox")
|
|
32
|
+
self.gridLayout.addWidget(self.CropDataStartCheckBox, 2, 0, 1, 1)
|
|
33
|
+
self.CropStartTimeEditor = QtWidgets.QDateTimeEdit(self.ACTimeEdit)
|
|
34
|
+
self.CropStartTimeEditor.setObjectName("CropStartTimeEditor")
|
|
35
|
+
self.gridLayout.addWidget(self.CropStartTimeEditor, 3, 0, 1, 1)
|
|
36
|
+
self.ChannelListView = QtWidgets.QListView(self.ACTimeEdit)
|
|
37
|
+
self.ChannelListView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
|
|
38
|
+
self.ChannelListView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
|
|
39
|
+
self.ChannelListView.setTabKeyNavigation(True)
|
|
40
|
+
self.ChannelListView.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
|
|
41
|
+
self.ChannelListView.setObjectName("ChannelListView")
|
|
42
|
+
self.gridLayout.addWidget(self.ChannelListView, 1, 0, 1, 2)
|
|
43
|
+
self.label_3 = QtWidgets.QLabel(self.ACTimeEdit)
|
|
44
|
+
self.label_3.setObjectName("label_3")
|
|
45
|
+
self.gridLayout.addWidget(self.label_3, 0, 0, 1, 1)
|
|
46
|
+
self.CropDataEndCheckBox = QtWidgets.QCheckBox(self.ACTimeEdit)
|
|
47
|
+
self.CropDataEndCheckBox.setObjectName("CropDataEndCheckBox")
|
|
48
|
+
self.gridLayout.addWidget(self.CropDataEndCheckBox, 4, 0, 1, 1)
|
|
49
|
+
self.CropEndTimeEditor = QtWidgets.QDateTimeEdit(self.ACTimeEdit)
|
|
50
|
+
self.CropEndTimeEditor.setObjectName("CropEndTimeEditor")
|
|
51
|
+
self.gridLayout.addWidget(self.CropEndTimeEditor, 5, 0, 1, 1)
|
|
52
|
+
self.gridLayout_2.addWidget(self.ACTimeEdit, 0, 0, 1, 2)
|
|
53
|
+
|
|
54
|
+
self.retranslateUi(SaveDataDialog)
|
|
55
|
+
QtCore.QMetaObject.connectSlotsByName(SaveDataDialog)
|
|
56
|
+
|
|
57
|
+
def retranslateUi(self, SaveDataDialog):
|
|
58
|
+
_translate = QtCore.QCoreApplication.translate
|
|
59
|
+
SaveDataDialog.setWindowTitle(_translate("SaveDataDialog", "Dialog"))
|
|
60
|
+
self.OKBtn.setText(_translate("SaveDataDialog", "OK"))
|
|
61
|
+
self.CancelBtn.setText(_translate("SaveDataDialog", "Cancel"))
|
|
62
|
+
self.ACTimeEdit.setTitle(_translate("SaveDataDialog", "Save data options"))
|
|
63
|
+
self.CropDataStartCheckBox.setText(_translate("SaveDataDialog", "Crop data - Set Start Time"))
|
|
64
|
+
self.CropStartTimeEditor.setDisplayFormat(_translate("SaveDataDialog", "yyyy/MM/dd HH:mm:ss"))
|
|
65
|
+
self.label_3.setText(_translate("SaveDataDialog", "Select Channels to Save"))
|
|
66
|
+
self.CropDataEndCheckBox.setText(_translate("SaveDataDialog", "Crop data - Set End Time"))
|
|
67
|
+
self.CropEndTimeEditor.setDisplayFormat(_translate("SaveDataDialog", "yyyy/MM/dd HH:mm:ss"))
|
|
@@ -61,7 +61,7 @@ def load_mat(data_path):
|
|
|
61
61
|
raw_data = raw_data[0][0]
|
|
62
62
|
# Whether saved by python
|
|
63
63
|
if 'save' in names:
|
|
64
|
-
channels = list(
|
|
64
|
+
channels = list(names[:-4])
|
|
65
65
|
sf = [float(each) for each in raw_data['sf'][0]]
|
|
66
66
|
signals = [raw_data[each][0] for each in channels]
|
|
67
67
|
time = raw_data['time'][0]
|
|
@@ -191,3 +191,96 @@ def load_edf(data_path):
|
|
|
191
191
|
channels=[each['label'] for each in signal_headers],
|
|
192
192
|
sf=[each['sample_frequency'] for each in signal_headers],
|
|
193
193
|
time=meta['startdate'].strftime('%Y%m%d-%H:%M:%S'))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def write_edf(signals, channels, sf, time, edf_file=None):
|
|
197
|
+
"""
|
|
198
|
+
Write data to an EDF (European Data Format) file.
|
|
199
|
+
|
|
200
|
+
This function saves the provided signal data, channel names, sampling frequencies,
|
|
201
|
+
and start time into an EDF file at the specified file path.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
signals : list
|
|
206
|
+
A list containing the signal data for each channel. Each element
|
|
207
|
+
corresponds to the signal data for one channel.
|
|
208
|
+
channels : list of str
|
|
209
|
+
A list of channel names corresponding to the signal data.
|
|
210
|
+
sf : list of float
|
|
211
|
+
A list of sampling frequencies for each channel.
|
|
212
|
+
time : str
|
|
213
|
+
The start time of the recording in the format "YYYYMMDD-HH:MM:SS".
|
|
214
|
+
edf_file : str
|
|
215
|
+
The file path where the EDF file will be saved.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
None
|
|
220
|
+
The function writes the data to the specified file and does not return any value.
|
|
221
|
+
|
|
222
|
+
Notes
|
|
223
|
+
-----
|
|
224
|
+
- The function assumes that the length of `signals`, `channels`, and `sf` are the same.
|
|
225
|
+
- The `pyedflib` library is used to handle the EDF file writing process.
|
|
226
|
+
- Ensure that the `edf_file` is a valid path where the file can be created.
|
|
227
|
+
|
|
228
|
+
Example
|
|
229
|
+
-------
|
|
230
|
+
>>> signals = [[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]
|
|
231
|
+
>>> channels = ["EEG1", "EEG2"]
|
|
232
|
+
>>> sf = [256.0, 256.0]
|
|
233
|
+
>>> time = "20250423-12:00:00"
|
|
234
|
+
>>> edf_file = "output.edf"
|
|
235
|
+
>>> write_edf(signals, channels, sf, time, edf_file)
|
|
236
|
+
"""
|
|
237
|
+
import pyedflib
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
if edf_file is None:
|
|
241
|
+
edf_file = f"./{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}_saved.edf"
|
|
242
|
+
|
|
243
|
+
# Create a dictionary for the EDF header
|
|
244
|
+
header = {'technician': '',
|
|
245
|
+
'recording_additional': '',
|
|
246
|
+
'patientname': 'MiSleep',
|
|
247
|
+
'patient_additional': '',
|
|
248
|
+
'patientcode': '',
|
|
249
|
+
'equipment': '',
|
|
250
|
+
'admincode': '',
|
|
251
|
+
'sex': '',
|
|
252
|
+
'startdate': datetime.datetime.strptime(time, "%Y%m%d-%H:%M:%S"),
|
|
253
|
+
'birthdate': '',
|
|
254
|
+
'gender': '',
|
|
255
|
+
'annotations': []
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
signal_headers = [
|
|
259
|
+
{'label': each,
|
|
260
|
+
'dimension': 'uV',
|
|
261
|
+
'sample_rate': sf[idx],
|
|
262
|
+
'sample_frequency': sf[idx],
|
|
263
|
+
'physical_max': 10417.0,
|
|
264
|
+
'physical_min': -10417.0,
|
|
265
|
+
'digital_max': 32767,
|
|
266
|
+
'digital_min': -32768,
|
|
267
|
+
'prefilter': '',
|
|
268
|
+
'transducer': ''
|
|
269
|
+
} for idx, each in enumerate(channels)
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
try:
|
|
273
|
+
|
|
274
|
+
with pyedflib.EdfWriter(edf_file, len(signals)) as edf_writer:
|
|
275
|
+
# Set the header information
|
|
276
|
+
edf_writer.setHeader(header)
|
|
277
|
+
|
|
278
|
+
# Add each signal to the EDF file
|
|
279
|
+
for i, signal in enumerate(signals):
|
|
280
|
+
edf_writer.setSignalHeader(i, signal_headers[i])
|
|
281
|
+
edf_writer.writeSamples(signals)
|
|
282
|
+
|
|
283
|
+
except Exception as e:
|
|
284
|
+
logger.error(f"Write data ERROR: {e}")
|
|
285
|
+
|
|
286
|
+
|
|
@@ -66,8 +66,11 @@ def signal_filter(data, sf=256., btype='lowpass', low=0.5, high=30.):
|
|
|
66
66
|
% btype)
|
|
67
67
|
|
|
68
68
|
# Use irrfilter of scipy.signal to construct a filter
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
try:
|
|
70
|
+
b, a = signal.iirfilter(N=3, Wn=fnorm, btype=btype, analog=False,
|
|
71
|
+
output='ba', ftype='butter', fs=None)
|
|
72
|
+
except ValueError as e:
|
|
73
|
+
raise ValueError(e)
|
|
71
74
|
|
|
72
75
|
filtered_data = signal.filtfilt(b=b, a=a, x=data)
|
|
73
76
|
|
|
@@ -11,7 +11,7 @@ MAINTAINER_EMAIL = "swang@gmail.com"
|
|
|
11
11
|
URL = "https://github.com/BryanWang0702/MiSleep/"
|
|
12
12
|
LICENSE = "BSD (3-clause)"
|
|
13
13
|
DOWNLOAD_URL = "https://github.com/BryanWang0702/MiSleep/"
|
|
14
|
-
VERSION = "0.2.
|
|
14
|
+
VERSION = "0.2.9"
|
|
15
15
|
|
|
16
16
|
INSTALL_REQUIRES = [
|
|
17
17
|
"numpy>=1.18.1",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
@Project: misleep
|
|
4
|
+
@File: test_signal_io.py
|
|
5
|
+
@Author: Xueqiang Wang
|
|
6
|
+
@Date: 2024/2/22
|
|
7
|
+
@Description:
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from misleep.io.signal_io import load_mat, load_edf, write_mat, write_edf
|
|
12
|
+
from time import time
|
|
13
|
+
import os
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestSignalIO():
|
|
17
|
+
def test_load_mat(self):
|
|
18
|
+
"""Test load_mat"""
|
|
19
|
+
mat_path = r'./test/test_data/10mins_example_mat.mat'
|
|
20
|
+
s = time()
|
|
21
|
+
midata = load_mat(data_path=mat_path)
|
|
22
|
+
e = time()
|
|
23
|
+
print(f"Load data from .mat file, cost {e-s} seconds\n"
|
|
24
|
+
f"Signals shape {len(midata.signals)}, \nchannels: {midata.channels}, \nsampling frequency: {midata.sf}, \nacquisition time: {midata.time}")
|
|
25
|
+
|
|
26
|
+
def test_load_edf(self):
|
|
27
|
+
"""Test load_edf"""
|
|
28
|
+
edf_path = r'./test/test_data/10mins_example_edf.edf'
|
|
29
|
+
s = time()
|
|
30
|
+
midata = load_edf(data_path=edf_path)
|
|
31
|
+
e = time()
|
|
32
|
+
print(f"Load data from .edf file, cost {e-s} seconds\n"
|
|
33
|
+
f"Signals shape {len(midata.signals)}, \nchannels: {midata.channels}, \nsampling frequency: {midata.sf}, \nacquisition time: {midata.time}")
|
|
34
|
+
|
|
35
|
+
def test_write_mat(self):
|
|
36
|
+
"""Test write_mat"""
|
|
37
|
+
mat_path = r'./test/test_data/10mins_example_mat.mat'
|
|
38
|
+
s = time()
|
|
39
|
+
midata = load_mat(data_path=mat_path)
|
|
40
|
+
e = time()
|
|
41
|
+
print(f"Load data from .mat file, cost {e-s} seconds\n"
|
|
42
|
+
f"Signals shape {len(midata.signals)}, \nchannels: {midata.channels}, \nsampling frequency: {midata.sf}, \nacquisition time: {midata.time}")
|
|
43
|
+
|
|
44
|
+
write_mat(midata.signals, midata.channels, midata.sf, midata.time,
|
|
45
|
+
r'./test/test_data/10mins_example_mat_saved_by_python.mat')
|
|
46
|
+
print('Saved to .mat successfully')
|
|
47
|
+
|
|
48
|
+
def test_write_edf(self):
|
|
49
|
+
"""Test write_edf"""
|
|
50
|
+
mat_path = r'./test/test_data/10mins_example_mat.mat'
|
|
51
|
+
s = time()
|
|
52
|
+
midata = load_mat(data_path=mat_path)
|
|
53
|
+
e = time()
|
|
54
|
+
print(f"Load data from .mat file, cost {e-s} seconds\n"
|
|
55
|
+
f"Signals shape {len(midata.signals)}, \nchannels: {midata.channels}, \nsampling frequency: {midata.sf}, \nacquisition time: {midata.time}")
|
|
56
|
+
|
|
57
|
+
write_edf(midata.signals, midata.channels, midata.sf, midata.time,
|
|
58
|
+
r'./test/test_data/10mins_example_mat_saved_by_python.edf')
|
|
59
|
+
print('Saved to .edf successfully')
|
|
60
|
+
|
|
61
|
+
def test_load_written_edf(self):
|
|
62
|
+
"""Test load written edf"""
|
|
63
|
+
edf_path = r'./test/test_data/10mins_example_mat_saved_by_python.edf'
|
|
64
|
+
s = time()
|
|
65
|
+
midata = load_edf(data_path=edf_path)
|
|
66
|
+
e = time()
|
|
67
|
+
print(f"Load data from .mat file, cost {e-s} seconds\n"
|
|
68
|
+
f"Signals shape {len(midata.signals)}, \nchannels: {midata.channels}, \nsampling frequency: {midata.sf}, \nacquisition time: {midata.time}")
|
|
69
|
+
|
|
70
|
+
def test_load_written_mat(self):
|
|
71
|
+
"""Test load written mat"""
|
|
72
|
+
mat_path = r'./test/test_data/10mins_example_mat_saved_by_python.mat'
|
|
73
|
+
s = time()
|
|
74
|
+
midata = load_mat(data_path=mat_path)
|
|
75
|
+
e = time()
|
|
76
|
+
print(f"Load data from .mat file, cost {e-s} seconds\n"
|
|
77
|
+
f"Signals shape {len(midata.signals)}, \nchannels: {midata.channels}, \nsampling frequency: {midata.sf}, \nacquisition time: {midata.time}")
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
# Form implementation generated from reading ui file 'misleep/gui/uis/save_data_dialog.ui'
|
|
4
|
-
#
|
|
5
|
-
# Created by: PyQt5 UI code generator 5.15.10
|
|
6
|
-
#
|
|
7
|
-
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
|
8
|
-
# run again. Do not edit this file unless you know what you are doing.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from PyQt5 import QtCore, QtGui, QtWidgets
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Ui_Dialog(object):
|
|
15
|
-
def setupUi(self, Dialog):
|
|
16
|
-
Dialog.setObjectName("Dialog")
|
|
17
|
-
Dialog.resize(432, 154)
|
|
18
|
-
self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
|
|
19
|
-
self.verticalLayout.setObjectName("verticalLayout")
|
|
20
|
-
self.ACTimeEdit = QtWidgets.QGroupBox(Dialog)
|
|
21
|
-
self.ACTimeEdit.setObjectName("ACTimeEdit")
|
|
22
|
-
self.gridLayout = QtWidgets.QGridLayout(self.ACTimeEdit)
|
|
23
|
-
self.gridLayout.setObjectName("gridLayout")
|
|
24
|
-
self.dateTimeEdit = QtWidgets.QDateTimeEdit(self.ACTimeEdit)
|
|
25
|
-
self.dateTimeEdit.setObjectName("dateTimeEdit")
|
|
26
|
-
self.gridLayout.addWidget(self.dateTimeEdit, 1, 0, 1, 1)
|
|
27
|
-
self.ResetACTimeEdit = QtWidgets.QCheckBox(self.ACTimeEdit)
|
|
28
|
-
self.ResetACTimeEdit.setObjectName("ResetACTimeEdit")
|
|
29
|
-
self.gridLayout.addWidget(self.ResetACTimeEdit, 0, 0, 1, 1)
|
|
30
|
-
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
|
31
|
-
self.gridLayout.addItem(spacerItem, 1, 1, 1, 1)
|
|
32
|
-
self.verticalLayout.addWidget(self.ACTimeEdit)
|
|
33
|
-
self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
|
|
34
|
-
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
|
|
35
|
-
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
|
|
36
|
-
self.buttonBox.setObjectName("buttonBox")
|
|
37
|
-
self.verticalLayout.addWidget(self.buttonBox)
|
|
38
|
-
|
|
39
|
-
self.retranslateUi(Dialog)
|
|
40
|
-
self.buttonBox.accepted.connect(Dialog.accept) # type: ignore
|
|
41
|
-
self.buttonBox.rejected.connect(Dialog.reject) # type: ignore
|
|
42
|
-
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
|
43
|
-
|
|
44
|
-
def retranslateUi(self, Dialog):
|
|
45
|
-
_translate = QtCore.QCoreApplication.translate
|
|
46
|
-
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
|
|
47
|
-
self.ACTimeEdit.setTitle(_translate("Dialog", "Save data options"))
|
|
48
|
-
self.ResetACTimeEdit.setText(_translate("Dialog", "Reset acquisition time?"))
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
"""
|
|
3
|
-
@Project: misleep
|
|
4
|
-
@File: test_signal_io.py
|
|
5
|
-
@Author: Xueqiang Wang
|
|
6
|
-
@Date: 2024/2/22
|
|
7
|
-
@Description:
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
from misleep.io.signal_io import load_mat, load_edf, write_mat
|
|
12
|
-
from time import time
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class TestSignalIO():
|
|
16
|
-
def test_load_mat_write_mat(self):
|
|
17
|
-
"""Test load_mat and write_mat"""
|
|
18
|
-
mat_path = r'E:\workplace\EEGProcessing\00_DATA\20231117_test+vid_6pin\data_mini_saved_python.mat'
|
|
19
|
-
s = time()
|
|
20
|
-
midata = load_mat(data_path=mat_path)
|
|
21
|
-
e = time()
|
|
22
|
-
print(f"Load data from .mat file, cost {e-s} seconds\n"
|
|
23
|
-
f"Signals shape {len(midata.signals)}, channels: {midata.channels}, sampling frequency: {midata.sf}")
|
|
24
|
-
|
|
25
|
-
write_mat(midata.signals, midata.channels, midata.sf, midata.time,
|
|
26
|
-
r'E:\workplace\EEGProcessing\00_DATA\20231117_test+vid_6pin\data_mini_saved_python.mat')
|
|
27
|
-
print('Saved to .mat successfully')
|
|
28
|
-
|
|
29
|
-
def test_load_edf_write_mat(self):
|
|
30
|
-
"""Test load_edf and write_mat"""
|
|
31
|
-
edf_path = r'../datasets/learn-nsrr01.edf'
|
|
32
|
-
s = time()
|
|
33
|
-
signals, channels, sf = load_edf(data_path=edf_path)
|
|
34
|
-
e = time()
|
|
35
|
-
print(f"Load data from .edf file, cost {e-s} seconds\n"
|
|
36
|
-
f"Signals shape {len(signals)}, channels: {channels}, sampling frequency: {sf}")
|
|
37
|
-
|
|
38
|
-
write_mat(signals, channels, sf, r'../../../datasets/mat_from_edf.mat')
|
|
39
|
-
print('Saved to .mat successfully')
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|