misleep 0.3.2__tar.gz → 0.3.3__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.
Files changed (123) hide show
  1. {misleep-0.3.2/src/misleep.egg-info → misleep-0.3.3}/PKG-INFO +1 -1
  2. {misleep-0.3.2 → misleep-0.3.3}/pyproject.toml +1 -1
  3. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/models/benchmark_models.pkl +1 -1
  4. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/dialogs.py +50 -20
  5. {misleep-0.3.2 → misleep-0.3.3/src/misleep.egg-info}/PKG-INFO +1 -1
  6. {misleep-0.3.2 → misleep-0.3.3}/tests/test_gui.py +59 -0
  7. {misleep-0.3.2 → misleep-0.3.3}/CHANGELOG.md +0 -0
  8. {misleep-0.3.2 → misleep-0.3.3}/LICENSE +0 -0
  9. {misleep-0.3.2 → misleep-0.3.3}/MANIFEST.in +0 -0
  10. {misleep-0.3.2 → misleep-0.3.3}/README.md +0 -0
  11. {misleep-0.3.2 → misleep-0.3.3}/docs/api_reference.md +0 -0
  12. {misleep-0.3.2 → misleep-0.3.3}/docs/config_file.md +0 -0
  13. {misleep-0.3.2 → misleep-0.3.3}/docs/data_formats.md +0 -0
  14. {misleep-0.3.2 → misleep-0.3.3}/docs/developer_guide.md +0 -0
  15. {misleep-0.3.2 → misleep-0.3.3}/docs/getting_started.md +0 -0
  16. {misleep-0.3.2 → misleep-0.3.3}/docs/imgs/entire_logo.png +0 -0
  17. {misleep-0.3.2 → misleep-0.3.3}/docs/imgs/gui/main_light.png +0 -0
  18. {misleep-0.3.2 → misleep-0.3.3}/docs/imgs/logo.png +0 -0
  19. {misleep-0.3.2 → misleep-0.3.3}/docs/index.md +0 -0
  20. {misleep-0.3.2 → misleep-0.3.3}/docs/user_guide.md +0 -0
  21. {misleep-0.3.2 → misleep-0.3.3}/examples/auto_stage_example.py +0 -0
  22. {misleep-0.3.2 → misleep-0.3.3}/examples/basic_usage.py +0 -0
  23. {misleep-0.3.2 → misleep-0.3.3}/examples/extend_io_example.py +0 -0
  24. {misleep-0.3.2 → misleep-0.3.3}/setup.cfg +0 -0
  25. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/__init__.py +0 -0
  26. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/__main__.py +0 -0
  27. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/_compat.py +0 -0
  28. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/__init__.py +0 -0
  29. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/auto_stage.py +0 -0
  30. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/autostage/__init__.py +0 -0
  31. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/autostage/benchmark.py +0 -0
  32. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/autostage/features.py +0 -0
  33. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/autostage/hmm.py +0 -0
  34. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/autostage/model.py +0 -0
  35. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/autostage/postprocess.py +0 -0
  36. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/detection.py +0 -0
  37. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/features.py +0 -0
  38. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/models/__init__.py +0 -0
  39. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/__init__.py +0 -0
  40. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/checkpoints/CausalTransformer_best.pt +0 -0
  41. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/checkpoints/__init__.py +0 -0
  42. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/configs.py +0 -0
  43. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/data.py +0 -0
  44. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/inference.py +0 -0
  45. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/models.py +0 -0
  46. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/analysis/transformer/preprocessing.py +0 -0
  47. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/config/__init__.py +0 -0
  48. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/config/default_config.ini +0 -0
  49. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/data/__init__.py +0 -0
  50. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/data/annotation.py +0 -0
  51. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/data/midata.py +0 -0
  52. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/__init__.py +0 -0
  53. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/app.py +0 -0
  54. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/config_dialog.py +0 -0
  55. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/event_filters.py +0 -0
  56. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/main_window.py +0 -0
  57. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/qt_utils.py +0 -0
  58. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/resources/__init__.py +0 -0
  59. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/resources/entire_logo.png +0 -0
  60. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/resources/logo.png +0 -0
  61. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/resources/misleep.ico +0 -0
  62. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/resources/misleep.qrc +0 -0
  63. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/resources/misleep_rc.py +0 -0
  64. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/spec_window.py +0 -0
  65. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/style.py +0 -0
  66. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/SWA_detect_dialog.ui +0 -0
  67. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/SWA_detect_dialog_ui.py +0 -0
  68. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/__init__.py +0 -0
  69. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/about.ui +0 -0
  70. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/about_ui.py +0 -0
  71. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/auto_stage_causalTransformer_dialog.ui +0 -0
  72. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/auto_stage_causalTransformer_dialog_ui.py +0 -0
  73. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/auto_stage_lightGBM_dialog.ui +0 -0
  74. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/auto_stage_lightGBM_dialog_ui.py +0 -0
  75. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/horizontal_line_dialog.ui +0 -0
  76. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/horizontal_line_dialog_ui.py +0 -0
  77. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/label_dialog.ui +0 -0
  78. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/label_dialog_ui.py +0 -0
  79. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/main_window.ui +0 -0
  80. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/main_window_ui.py +0 -0
  81. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/save_data_dialog.ui +0 -0
  82. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/save_data_dialog_ui.py +0 -0
  83. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/spec_window.ui +0 -0
  84. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/spec_window_ui.py +0 -0
  85. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/spindle_detect_dialog.ui +0 -0
  86. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/spindle_detect_dialog_ui.py +0 -0
  87. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/state_spectral_dialog.ui +0 -0
  88. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/state_spectral_dialog_ui.py +0 -0
  89. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/transfer_result_dialog.ui +0 -0
  90. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/uis/transfer_result_dialog_ui.py +0 -0
  91. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/gui/workers.py +0 -0
  92. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/io/__init__.py +0 -0
  93. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/io/annotation.py +0 -0
  94. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/io/array.py +0 -0
  95. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/io/base.py +0 -0
  96. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/io/edf.py +0 -0
  97. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/io/mat.py +0 -0
  98. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/logger.py +0 -0
  99. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/preprocessing/__init__.py +0 -0
  100. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/preprocessing/artifacts.py +0 -0
  101. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/preprocessing/filtering.py +0 -0
  102. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/preprocessing/segment.py +0 -0
  103. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/preprocessing/spectral.py +0 -0
  104. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/utils/__init__.py +0 -0
  105. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/utils/annotation.py +0 -0
  106. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/utils/entropy.py +0 -0
  107. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/utils/misc.py +0 -0
  108. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/utils/time_utils.py +0 -0
  109. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/viz/__init__.py +0 -0
  110. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/viz/hypnogram.py +0 -0
  111. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/viz/signals.py +0 -0
  112. {misleep-0.3.2 → misleep-0.3.3}/src/misleep/viz/spectral.py +0 -0
  113. {misleep-0.3.2 → misleep-0.3.3}/src/misleep.egg-info/SOURCES.txt +0 -0
  114. {misleep-0.3.2 → misleep-0.3.3}/src/misleep.egg-info/dependency_links.txt +0 -0
  115. {misleep-0.3.2 → misleep-0.3.3}/src/misleep.egg-info/entry_points.txt +0 -0
  116. {misleep-0.3.2 → misleep-0.3.3}/src/misleep.egg-info/requires.txt +0 -0
  117. {misleep-0.3.2 → misleep-0.3.3}/src/misleep.egg-info/top_level.txt +0 -0
  118. {misleep-0.3.2 → misleep-0.3.3}/tests/test_analysis.py +0 -0
  119. {misleep-0.3.2 → misleep-0.3.3}/tests/test_data.py +0 -0
  120. {misleep-0.3.2 → misleep-0.3.3}/tests/test_io.py +0 -0
  121. {misleep-0.3.2 → misleep-0.3.3}/tests/test_preprocessing.py +0 -0
  122. {misleep-0.3.2 → misleep-0.3.3}/tests/test_segment.py +0 -0
  123. {misleep-0.3.2 → misleep-0.3.3}/tests/test_viz_config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: misleep
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: MiSleep: Mice sleep EEG/EMG visualization, scoring, and analysis
5
5
  Author-email: Xueqiang Wang <swang9194@gmail.com>
6
6
  Maintainer-email: Xueqiang Wang <swang9194@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "misleep"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "MiSleep: Mice sleep EEG/EMG visualization, scoring, and analysis"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
- name: misleep-0.3.2/src/misleep/analysis/models/benchmark_models.pkl
2
+ name: misleep-0.3.3/src/misleep/analysis/models/benchmark_models.pkl
3
3
  size: 50435195 bytes
4
4
  sha256: 8390ffa5ef8d45701a09244ec75dea46d2e4bff6a4c8242b0de84da8ea9cd31c
@@ -7,6 +7,7 @@ the two auto-staging dialogs and the save-data dialog, plus the About box.
7
7
  """
8
8
 
9
9
  import datetime
10
+ import math
10
11
  import os
11
12
  from copy import deepcopy
12
13
 
@@ -50,6 +51,33 @@ from misleep.logger import logger
50
51
  from misleep.utils.annotation import lst2group
51
52
 
52
53
 
54
+ def _split_state_data_by_hour(channel_data, sleep_states, states, sf):
55
+ """Return per-state signal segments in fixed, time-aligned hour slots.
56
+
57
+ Every state receives one entry per elapsed hour. An entry is ``None``
58
+ when that state has no usable data in the hour, so later hours never
59
+ shift left to fill an empty ZT column.
60
+ """
61
+ hour_count = int(math.ceil(len(sleep_states) / 3600))
62
+ result = {state: [None] * hour_count for state in states}
63
+
64
+ for hour_idx in range(hour_count):
65
+ hour_start = hour_idx * 3600
66
+ hour_stop = min(hour_start + 3600, len(sleep_states))
67
+ groups = lst2group(enumerate(sleep_states[hour_start:hour_stop]))
68
+ for state in states:
69
+ parts = [
70
+ channel_data[
71
+ int((hour_start + group_start) * sf):
72
+ int((hour_start + group_stop) * sf)]
73
+ for group_start, group_stop, value in groups
74
+ if value == state
75
+ ]
76
+ if parts:
77
+ result[state][hour_idx] = np.concatenate(parts)
78
+ return result
79
+
80
+
53
81
  class AboutDialog(QDialog, Ui_AboutDialog):
54
82
  """The About box of MiSleep."""
55
83
 
@@ -400,11 +428,13 @@ class StateSpectralDialog(QDialog, Ui_StateSpectralDialog):
400
428
  end_sec = mianno.anno_length
401
429
 
402
430
  midata = midata.crop([start_sec, end_sec])
403
- sleep_state = mianno.sleep_state[start_sec:end_sec + 1]
431
+ # ``end_sec`` is exclusive, matching MiData.crop(). Using end+1 here
432
+ # created a spurious sample at exact hour boundaries.
433
+ sleep_state_values = mianno.sleep_state[start_sec:end_sec]
404
434
 
405
435
  channel_idx = self.ChannelSelector.currentIndex()
406
436
  channel_data = midata.signals[channel_idx]
407
- sleep_state = lst2group([[idx, each] for idx, each in enumerate(sleep_state)])
437
+ sleep_state = lst2group(enumerate(sleep_state_values))
408
438
  sf = midata.sf[channel_idx]
409
439
 
410
440
  # Band-pass filter if checked
@@ -422,7 +452,7 @@ class StateSpectralDialog(QDialog, Ui_StateSpectralDialog):
422
452
 
423
453
  gaussian_sigma = self.GaussianSpinBox.value() if self.GaussianCheckBox.isChecked() else None
424
454
 
425
- state_codes = sorted(set(mianno.sleep_state[start_sec:end_sec + 1]))
455
+ state_codes = sorted(set(sleep_state_values))
426
456
  state_data = {
427
457
  state: np.concatenate([
428
458
  channel_data[int(each[0] * sf): int(each[1] * sf)]
@@ -451,21 +481,21 @@ class StateSpectralDialog(QDialog, Ui_StateSpectralDialog):
451
481
  # Optional per-hour spectral segmentation
452
482
  hour_spec = {state: [] for state in spectra}
453
483
  if self.HourSegmentCheckBox.isChecked():
454
- for sec in range(0, end_sec - start_sec, 3600):
455
- hour_states = mianno.sleep_state[start_sec + sec:start_sec + sec + 3600]
456
- hour_states = lst2group([[idx + sec, each]
457
- for idx, each in enumerate(hour_states)])
458
- for state in spectra:
459
- data_parts = [channel_data[int(each[0] * sf): int(each[1] * sf)]
460
- for each in hour_states if each[2] == state]
461
- data = np.concatenate(data_parts) if data_parts else np.array([])
462
- if self.RejectArtifactCheckBox.isChecked() and len(data):
463
- data = reject_artifact(data, sf=sf, threshold=threshold)
464
- if len(data) > sf * 10:
465
- hour_spec[state].append(cal_draw_spectrum(
484
+ hourly_data = _split_state_data_by_hour(
485
+ channel_data, sleep_state_values, spectra, sf)
486
+ for state, state_hours in hourly_data.items():
487
+ for data in state_hours:
488
+ if data is not None and self.RejectArtifactCheckBox.isChecked():
489
+ data = reject_artifact(
490
+ data, sf=sf, threshold=threshold)
491
+ if data is not None and len(data) > sf * 10:
492
+ power = cal_draw_spectrum(
466
493
  data=data, sf=sf, nperseg=nperseg,
467
494
  freq_band=freq_band, relative=relative, nfft=nfft,
468
- gaussian_sigma=gaussian_sigma)[0][1])
495
+ gaussian_sigma=gaussian_sigma)[0][1]
496
+ hour_spec[state].append(power)
497
+ else:
498
+ hour_spec[state].append(None)
469
499
 
470
500
  fd = QFileDialog.getExistingDirectory(self, "Select a folder to save states' data",
471
501
  f"{config['gui']['openpath']}")
@@ -485,10 +515,10 @@ class StateSpectralDialog(QDialog, Ui_StateSpectralDialog):
485
515
  figure.savefig(fd + "/" + safe_name + "_spectrum.pdf")
486
516
  _df = pd.DataFrame(
487
517
  data=spec.T, columns=["frequency", "power"])
488
- if hour_spec[state]:
489
- _df[[str(each) for each in range(
490
- 1, len(hour_spec[state]) + 1)]] = \
491
- pd.DataFrame(hour_spec[state]).T
518
+ for hour_idx, hour_power in enumerate(hour_spec[state]):
519
+ column = f"ZT{hour_idx}"
520
+ _df[column] = np.nan if hour_power is None \
521
+ else pd.Series(hour_power)
492
522
  _df.to_excel(
493
523
  excel_writer=writer, sheet_name=safe_name[:31],
494
524
  index=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: misleep
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: MiSleep: Mice sleep EEG/EMG visualization, scoring, and analysis
5
5
  Author-email: Xueqiang Wang <swang9194@gmail.com>
6
6
  Maintainer-email: Xueqiang Wang <swang9194@gmail.com>
@@ -766,6 +766,65 @@ def test_data_export_always_restores_main_window(tmp_path, monkeypatch):
766
766
  app.processEvents()
767
767
 
768
768
 
769
+ @pytest.mark.skipif(not _pyside6_available(), reason="PySide6 not installed")
770
+ def test_state_spectral_hour_columns_preserve_empty_zt_slots(
771
+ fresh_config, tmp_path, monkeypatch):
772
+ """Hourly state spectra stay aligned to ZT0..ZTn despite empty hours."""
773
+ import numpy as np
774
+ import pandas as pd
775
+ from PySide6.QtWidgets import QApplication, QFileDialog
776
+
777
+ app = QApplication.instance() or QApplication([])
778
+ from misleep.data import MiAnnotation, MiData
779
+ from misleep.gui.dialogs import StateSpectralDialog
780
+
781
+ class FakeFigure:
782
+ def savefig(self, *args, **kwargs):
783
+ pass
784
+
785
+ def clear(self):
786
+ pass
787
+
788
+ def fake_spectrum(data, **kwargs):
789
+ return np.array([[1.0, 2.0], [len(data), float(np.mean(data))]]), \
790
+ FakeFigure()
791
+
792
+ monkeypatch.setattr(
793
+ "misleep.gui.dialogs.cal_draw_spectrum", fake_spectrum)
794
+ monkeypatch.setattr(
795
+ QFileDialog, "getExistingDirectory",
796
+ staticmethod(lambda *args, **kwargs: str(tmp_path)))
797
+
798
+ # State 1 is absent from ZT1; state 2 is absent from ZT0 and ZT2.
799
+ states = [1] * 3600 + [2] * 3600 + [1] * 3600
800
+ midata = MiData(
801
+ signals=[np.arange(len(states), dtype=float)],
802
+ channels=["EEG"], sf=[1.0], time="20240409-18:00:00")
803
+ mianno = MiAnnotation(states, state_map={1: "NREM", 2: "REM"})
804
+ fresh_config["gui"]["openpath"] = "recording.mat"
805
+
806
+ dialog = StateSpectralDialog(config=fresh_config)
807
+ dialog.dialog_show(midata.channels)
808
+ dialog.BPFilterCheckBox.setChecked(False)
809
+ dialog.HourSegmentCheckBox.setChecked(True)
810
+ assert dialog.spectral_analysis(midata, mianno, fresh_config) is True
811
+
812
+ workbook = tmp_path / "recording_power_results.xlsx"
813
+ nrem = pd.read_excel(workbook, sheet_name="NREM")
814
+ rem = pd.read_excel(workbook, sheet_name="REM")
815
+ expected = ["frequency", "power", "ZT0", "ZT1", "ZT2"]
816
+ assert list(nrem.columns) == expected
817
+ assert list(rem.columns) == expected
818
+ assert nrem["ZT1"].isna().all()
819
+ assert rem["ZT0"].isna().all()
820
+ assert rem["ZT2"].isna().all()
821
+ assert nrem["ZT0"].notna().all() and nrem["ZT2"].notna().all()
822
+ assert rem["ZT1"].notna().all()
823
+
824
+ dialog.close()
825
+ app.processEvents()
826
+
827
+
769
828
  @pytest.mark.skipif(not _pyside6_available(), reason="PySide6 not installed")
770
829
  def test_gui_with_data(tmp_path):
771
830
  from PySide6.QtWidgets import QApplication
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