SleePyPhases 0.2.1__tar.gz → 0.3.1__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.
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/PKG-INFO +1 -1
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/MultiScorer.py +8 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/Plugin.py +4 -5
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/RecordFeatureExtraction.py +6 -71
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/Eval.py +4 -10
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/EvalReport.py +2 -3
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/ExtractFeatures.py +6 -9
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/Setup.py +5 -24
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/Training.py +4 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases.egg-info/PKG-INFO +1 -1
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases.egg-info/SOURCES.txt +0 -4
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/setup.py +1 -1
- sleepyphases-0.2.1/SleePyPhases/FeatureExtraction copy.py +0 -116
- sleepyphases-0.2.1/SleePyPhases/recordloaders/MedicalDB.py +0 -124
- sleepyphases-0.2.1/SleePyPhases/recordloaders/RecordLoaderTSM.py +0 -34
- sleepyphases-0.2.1/tests/unit/__init__.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/LICENSE +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/README.md +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/DataManipulation.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/EventScorer.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/FeatureExtraction.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/PSGEventManager.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/Plot.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/PreManipulation.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/Reporter.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/SignalPreprocessing.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/SleepMetaData.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/__init__.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/BuildDataset.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/DataAnalysis.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/DataReport.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/Extract.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/ExtractEvents.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/GatherMetadata.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/TestRun.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/ThresholdOptimisation.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/Validation.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/VisualizeConfig.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases/phases/__init__.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases.egg-info/dependency_links.txt +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases.egg-info/requires.txt +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/SleePyPhases.egg-info/top_level.txt +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/pyproject.toml +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/setup.cfg +0 -0
- {sleepyphases-0.2.1/SleePyPhases/recordloaders → sleepyphases-0.3.1/tests}/__init__.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/conftest.py +0 -0
- {sleepyphases-0.2.1/tests → sleepyphases-0.3.1/tests/unit}/__init__.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_BuildDataset.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_BuildDatasetFolded.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_BuildDatasetSegments.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_DataManipulation.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_Eval.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_EventScorer.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_Extract.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_PreManipulation.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_RecordProcessor.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_Setup.py +0 -0
- {sleepyphases-0.2.1 → sleepyphases-0.3.1}/tests/unit/test_SignalPreprocessing.py +0 -0
|
@@ -42,19 +42,27 @@ class MultiScorer(Scorer):
|
|
|
42
42
|
if isinstance(predictions, list) or isinstance(predictions, tuple):
|
|
43
43
|
p = predictions[i]
|
|
44
44
|
elif predictions.shape[-1] == len(self.multiClassNums):
|
|
45
|
+
if len(predictions.shape) == 3:
|
|
46
|
+
predictions = predictions.reshape(-1, predictions.shape[-1])
|
|
45
47
|
p = predictions[:, i]
|
|
46
48
|
else:
|
|
47
49
|
start = sum(self.multiClassNums[:i])
|
|
48
50
|
end = start + self.multiClassNums[i]
|
|
51
|
+
if len(predictions.shape) == 3:
|
|
52
|
+
predictions = predictions.reshape(-1, predictions.shape[-1])
|
|
49
53
|
p = predictions[:, start:end]
|
|
50
54
|
|
|
51
55
|
if isinstance(truth, list) or isinstance(truth, tuple):
|
|
52
56
|
t = truth[i]
|
|
53
57
|
elif truth.shape[-1] == len(self.multiClassNums):
|
|
58
|
+
if len(truth.shape) == 3:
|
|
59
|
+
truth = truth.reshape(-1, truth.shape[-1])
|
|
54
60
|
t = truth[:, i]
|
|
55
61
|
else:
|
|
56
62
|
start = sum(self.multiClassNums[:i])
|
|
57
63
|
end = start + self.multiClassNums[i]
|
|
64
|
+
if len(truth.shape) == 3:
|
|
65
|
+
truth = truth.reshape(-1, truth.shape[-1])
|
|
58
66
|
t = truth[:, start:end]
|
|
59
67
|
|
|
60
68
|
|
|
@@ -3,6 +3,7 @@ from pyPhasesRecordloader import RecordLoader
|
|
|
3
3
|
|
|
4
4
|
from SleepHarmonizer.recordwriter.RecordWriter import RecordWriter
|
|
5
5
|
|
|
6
|
+
from SleePyPhases.phases.ExtractFeatures import ExtractFeatures
|
|
6
7
|
from SleePyPhases.phases.Extract import Extract
|
|
7
8
|
from SleePyPhases.phases.Setup import Setup
|
|
8
9
|
from SleePyPhases.phases.BuildDataset import BuildDataset
|
|
@@ -12,6 +13,7 @@ from SleePyPhases.phases.Eval import Eval
|
|
|
12
13
|
from SleePyPhases.phases.EvalReport import EvalReport
|
|
13
14
|
from SleePyPhases.phases.ThresholdOptimisation import ThresholdOptimisation
|
|
14
15
|
from SleePyPhases.phases.TestRun import TestRun
|
|
16
|
+
from SleePyPhases.phases.ExtractFeatures import ExtractFeatures
|
|
15
17
|
|
|
16
18
|
|
|
17
19
|
from pyPhases.exporter.PickleExporter import PickleExporter
|
|
@@ -33,8 +35,8 @@ class Plugin(PluginAdapter):
|
|
|
33
35
|
"ThresholdOptimisation": (ThresholdOptimisation, ["threshold", "validationResult"]),
|
|
34
36
|
"Eval": (Eval, ["evalResults", "eventResults"]),
|
|
35
37
|
"EvalReport": (EvalReport, []),
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
+
"ExtractFeatures": (ExtractFeatures, ["features"]),
|
|
39
|
+
"TestRun": (TestRun, []),
|
|
38
40
|
}
|
|
39
41
|
dataMap = {
|
|
40
42
|
"features": ["metadata"],
|
|
@@ -69,8 +71,5 @@ class Plugin(PluginAdapter):
|
|
|
69
71
|
project.registerExporter(ModelExporter({"basePath": dataPath}))
|
|
70
72
|
project.registerExporter(MemmapRecordExporter({"basePath": dataPath}))
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
def initPlugin(self):
|
|
74
|
-
RecordLoader.registerRecordLoader("RecordLoaderTSM", "SleePyPhases.recordloaders")
|
|
75
74
|
|
|
76
75
|
|
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
1
|
from typing import List
|
|
3
2
|
|
|
4
3
|
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
5
|
from pandas import DataFrame
|
|
6
|
+
from pyPhases import Swappable
|
|
6
7
|
from pyPhasesML import FeatureExtraction as pyPhasesFeatureExtraction
|
|
7
|
-
from pyPhasesRecordloader import
|
|
8
|
+
from pyPhasesRecordloader import ChannelsNotPresent, Event, RecordSignal, Signal
|
|
8
9
|
|
|
9
10
|
from SleePyPhases.PSGEventManager import PSGEventManager
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
segmentSignal: Recordsignal of the segment with 18 EEG channels
|
|
14
|
-
"""
|
|
12
|
+
|
|
13
|
+
class RecordFeatureExtraction(pyPhasesFeatureExtraction, Swappable):
|
|
15
14
|
|
|
16
15
|
def step(self, stepname: str, recordSignal: RecordSignal, eventlist: List[Event], channel=None, **options) -> Signal:
|
|
17
16
|
if hasattr(self, stepname):
|
|
@@ -271,68 +270,4 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
|
|
|
271
270
|
# ret[f"cycle-{cycleNumber}-rem-duration"] = remTime
|
|
272
271
|
|
|
273
272
|
return ret
|
|
274
|
-
|
|
275
|
-
def spindles(self, segmentSignal: RecordSignal, eventlist: List[Event], channel: str):
|
|
276
|
-
from .external.sumo.sumo.scripts.a7.butter_filter import butter_bandpass_filter, downsample
|
|
277
|
-
# from .external.sumo.a7.detect_spindles import detect_spindle
|
|
278
|
-
from .external.sumo.sumo.scripts.predict_plain_data import SimpleDataset
|
|
279
|
-
# from .external.sumo.sumo.predict_plain_data import A7
|
|
280
|
-
import pytorch_lightning as pl
|
|
281
|
-
from torch.utils.data import Dataset, DataLoader
|
|
282
|
-
import torch
|
|
283
|
-
from .external.sumo.sumo.model.sumo import SUMO
|
|
284
|
-
from .external.sumo.sumo.config.config import Config
|
|
285
|
-
# from .external.sumo.model.data import spindle_vect_to_indices
|
|
286
|
-
|
|
287
|
-
config = Config('predict', create_dirs=False)
|
|
288
|
-
|
|
289
|
-
def get_model(p):
|
|
290
|
-
# p = Path(p)
|
|
291
|
-
|
|
292
|
-
model_checkpoint = torch.load(p)
|
|
293
|
-
|
|
294
|
-
model = SUMO(config)
|
|
295
|
-
model.load_state_dict(model_checkpoint['state_dict'])
|
|
296
|
-
|
|
297
|
-
return model
|
|
298
|
-
|
|
299
|
-
eegSignal = segmentSignal.getSignalByName(channel)
|
|
300
|
-
resample_rate = 100
|
|
301
|
-
model_path = "SleePyPhases/external/sumo/sumo/output/final.ckpt"
|
|
302
|
-
|
|
303
|
-
if len(eegSignal.signal) == 0:
|
|
304
|
-
return None
|
|
305
|
-
|
|
306
|
-
eegs = []
|
|
307
|
-
eegSignal = downsample(butter_bandpass_filter(eegSignal.signal, 0.3, 30.0, eegSignal.frequency, 10), eegSignal.frequency, resample_rate)
|
|
308
|
-
eegs.append(eegSignal)
|
|
309
|
-
|
|
310
|
-
dataset = SimpleDataset(eegs)
|
|
311
|
-
dataloader = DataLoader(dataset)
|
|
312
|
-
model = get_model(model_path)
|
|
313
|
-
trainer = pl.Trainer(num_sanity_val_steps=0, logger=False)
|
|
314
|
-
predictions = trainer.predict(model, dataloader)
|
|
315
|
-
pred = predictions[0]
|
|
316
|
-
|
|
317
|
-
# spindles_a7 = A7(x, resample_rate)
|
|
318
|
-
spindle_vect = pred[0].numpy()
|
|
319
|
-
|
|
320
|
-
diff = np.diff(np.r_[0, spindle_vect, 0]) # be able to detect spindles at the start and end of vector
|
|
321
|
-
spindles = np.c_[np.argwhere(diff == 1), np.argwhere(diff == -1)] / resample_rate
|
|
322
|
-
# spindles = spindle_vect_to_indices(spindle_vect) / resample_rate
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
# frequencies, psd = signal.welch(eegs[0][s.start:s.end()],
|
|
326
|
-
# fs=100,
|
|
327
|
-
# nperseg=min(256, s.duration),
|
|
328
|
-
# scaling='spectrum')
|
|
329
|
-
# dominant_freq = frequencies[np.argmax(psd)]
|
|
330
|
-
|
|
331
|
-
spindles = [Event("spindle", s[0], s[1] - s[0]) for s in spindles]
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
"spindles-count": len(spindles),
|
|
336
|
-
"spindles-mean-druation": np.array([s.duration for s in spindles]).mean()
|
|
337
|
-
}
|
|
338
|
-
|
|
273
|
+
|
|
@@ -81,7 +81,7 @@ class Eval(Phase):
|
|
|
81
81
|
|
|
82
82
|
# setup data and scorer
|
|
83
83
|
|
|
84
|
-
classNums = self.getConfig("
|
|
84
|
+
classNums = self.getConfig("numClasses", [2])
|
|
85
85
|
labelNames = self.getConfig("classification.labelNames")
|
|
86
86
|
ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
|
|
87
87
|
metrics = self.getConfig("trainingParameter.validationMetrics")
|
|
@@ -99,16 +99,10 @@ class Eval(Phase):
|
|
|
99
99
|
|
|
100
100
|
for data in tqdm(testData):
|
|
101
101
|
x, truth = data
|
|
102
|
-
# make sure to have a batchsize if batchSize == 1
|
|
103
|
-
x = x.reshape(batchSize, x.shape[0], x.shape[1])
|
|
104
|
-
truth = truth.reshape(batchSize, truth.shape[0], truth.shape[1])
|
|
105
102
|
|
|
106
|
-
prediction = model.predict(x,
|
|
103
|
+
prediction = model.predict(x, returnNumpy=False)
|
|
107
104
|
prediction, truth = da((prediction, truth))
|
|
108
105
|
|
|
109
|
-
# prediction = prediction.reshape(-1, prediction.shape[2])
|
|
110
|
-
# truth = truth.reshape(-1, truth.shape[2])
|
|
111
|
-
|
|
112
106
|
scorer.score(truth, prediction, trace=True)
|
|
113
107
|
|
|
114
108
|
result = scorer.scoreAllRecords()
|
|
@@ -152,7 +146,7 @@ class Eval(Phase):
|
|
|
152
146
|
self.project.config.update(self.getConfig("eventEval", {}))
|
|
153
147
|
|
|
154
148
|
evalMetrics = self.getConfig("eval.metrics")
|
|
155
|
-
classNums = self.getConfig("
|
|
149
|
+
classNums = self.getConfig("numClasses", [2])
|
|
156
150
|
labelNames = self.getConfig("classification.labelNames")
|
|
157
151
|
predictionNames = self.getConfig("classification.predictionSignals")
|
|
158
152
|
predictionFrequencies = self.getConfig("classification.predictionFrequencies")
|
|
@@ -193,7 +187,7 @@ class Eval(Phase):
|
|
|
193
187
|
x = x.reshape([1] + list(x.shape))
|
|
194
188
|
truth = truth.reshape([1] + list(truth.shape))
|
|
195
189
|
|
|
196
|
-
prediction = model.predict(x,
|
|
190
|
+
prediction = model.predict(x, returnNumpy=False)
|
|
197
191
|
prediction, truth = da((prediction, truth))
|
|
198
192
|
|
|
199
193
|
# for labelIndex, labelName in enumerate(recordResults.keys()):
|
|
@@ -10,7 +10,6 @@ from pyPhasesRecordloader import RecordSignal, Signal
|
|
|
10
10
|
|
|
11
11
|
from SleePyPhases import Plot
|
|
12
12
|
from SleePyPhases.DataManipulation import DataManipulation
|
|
13
|
-
from SleePyPhases.Reporter import Reporter
|
|
14
13
|
|
|
15
14
|
|
|
16
15
|
class EvalReport(Phase):
|
|
@@ -244,8 +243,8 @@ class EvalReport(Phase):
|
|
|
244
243
|
def copyRawData(self):
|
|
245
244
|
modelConfigString = self.project.getDataFromName("modelState").getTagString()
|
|
246
245
|
trainingLogPath = f"logs/{modelConfigString}/"
|
|
247
|
-
shutil.copyfile(trainingLogPath + "log.csv",
|
|
248
|
-
shutil.copyfile(trainingLogPath + "project.config",
|
|
246
|
+
shutil.copyfile(trainingLogPath + "log.csv", self.evalPath + "training.log")
|
|
247
|
+
shutil.copyfile(trainingLogPath + "project.config", self.evalPath + "project.json")
|
|
249
248
|
|
|
250
249
|
def trainingPlot(self, logFile):
|
|
251
250
|
import matplotlib.pyplot as plt
|
|
@@ -76,7 +76,7 @@ class ExtractFeatures(Phase):
|
|
|
76
76
|
try:
|
|
77
77
|
recordSignal = recordLoader.getHarmonizedSignal(recordId)
|
|
78
78
|
|
|
79
|
-
events: List[Event] = RecordFeatureExtraction(
|
|
79
|
+
events: List[Event] = RecordFeatureExtraction.getInstance().step(name, recordSignal, *options)
|
|
80
80
|
|
|
81
81
|
if channelType is not None:
|
|
82
82
|
channelType = [s.name for s in recordSignal.signals if s.typeStr == channelType]
|
|
@@ -192,7 +192,7 @@ class ExtractFeatures(Phase):
|
|
|
192
192
|
try:
|
|
193
193
|
idx = (recordId, c)
|
|
194
194
|
featureStep = featureConfig["type"]
|
|
195
|
-
RecordFeatureExtraction(
|
|
195
|
+
RecordFeatureExtraction.getInstance().updateMetadataByStep(featureStep, recordSignal, eventlist, metadata=self.features, channel=c)
|
|
196
196
|
except ChannelsNotPresent as e:
|
|
197
197
|
self.logError(f"channels not present: {e.channels}")
|
|
198
198
|
|
|
@@ -209,18 +209,15 @@ class ExtractFeatures(Phase):
|
|
|
209
209
|
self.features = None
|
|
210
210
|
metadata = self.project.getData("metadata", pd.DataFrame)
|
|
211
211
|
recordIds = metadata["recordId"]
|
|
212
|
+
forceCalculation = self.getConfig("featureExtraction.features", True)
|
|
212
213
|
|
|
213
214
|
reqFeatures = {f["name"]: True for f in self.getConfig("featureExtraction.features", [])}
|
|
214
215
|
|
|
215
|
-
# groupedRecords = {"pid_202436073": "acq_237478592"}
|
|
216
216
|
for recordId in tqdm(recordIds.iloc):
|
|
217
|
-
|
|
218
|
-
|
|
217
|
+
if forceCalculation:
|
|
218
|
+
# the parameter forced has no meaning, it is just known to be not in the metadata, so the record will always be recalculated
|
|
219
|
+
reqFeatures["forced"] = True # should never be found
|
|
219
220
|
rows = self.getData("features", pd.DataFrame, recordId=recordId, **reqFeatures)
|
|
220
221
|
if len(rows) == 0:
|
|
221
222
|
self.logError(f"no features for {recordId}")
|
|
222
223
|
|
|
223
|
-
|
|
224
|
-
# get a single feature:
|
|
225
|
-
# feat = {"swa": True}
|
|
226
|
-
# x = self.getData("features", pd.DataFrame, recordId="shhs1-200002", channel="EEG", **feat)
|
|
@@ -4,36 +4,20 @@ from pyPhases import Phase
|
|
|
4
4
|
from pyPhasesML import DataversionManager, Scorer
|
|
5
5
|
from pyPhasesRecordloader import RecordLoader
|
|
6
6
|
|
|
7
|
-
from SleePyPhases.recordloaders.MedicalDB import MedicalDB
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class Setup(Phase):
|
|
11
10
|
def prepareConfig(self):
|
|
12
|
-
numLabels = len(self.getConfig("classification.labelNames"))
|
|
11
|
+
numLabels = len(self.getConfig("classification.labelNames", []))
|
|
13
12
|
self.setConfig("numLabels", numLabels)
|
|
14
13
|
|
|
15
|
-
numClasses = [len(c) for c in self.getConfig("classification.classNames")]
|
|
14
|
+
numClasses = [len(c) for c in self.getConfig("classification.classNames", [])]
|
|
16
15
|
self.setConfig("numClasses", numClasses)
|
|
17
16
|
|
|
18
|
-
defaultValidationbatchManipulation = self.getConfig("segmentManipulationEval", self.getConfig("segmentManipulation"))
|
|
17
|
+
defaultValidationbatchManipulation = self.getConfig("segmentManipulationEval", self.getConfig("segmentManipulation", []))
|
|
19
18
|
self.setConfig("segmentManipulationEval", defaultValidationbatchManipulation)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
RecordLoader.registerRecordLoader("RecordLoaderTest", "SleePyPhases.recordloaders")
|
|
23
|
-
RecordLoader.registerRecordLoader("RecordLoaderTSM", "SleePyPhases.recordloaders")
|
|
24
|
-
tsmPath = self.getConfig("tsm-path", False)
|
|
25
|
-
if tsmPath is not False:
|
|
26
|
-
tsmPath = Path(tsmPath)
|
|
27
|
-
self.project.setConfig("loader.tsm.filePath", tsmPath.as_posix())
|
|
28
|
-
self.project.setConfig("loader.tsm.dataset.downloader.basePath", tsmPath.as_posix())
|
|
29
|
-
|
|
30
|
-
tsmPath = self.getConfig("tsm-domino-path", False)
|
|
31
|
-
if tsmPath is not False:
|
|
32
|
-
tsmPath = Path(tsmPath)
|
|
33
|
-
self.project.setConfig("loader.tsm-domino.filePath", tsmPath.as_posix())
|
|
34
|
-
self.project.setConfig("loader.tsm-domino.dataset.downloader.basePath", tsmPath.as_posix())
|
|
35
|
-
|
|
36
|
-
preprocessingConfig = self.getConfig("preprocessing")
|
|
19
|
+
|
|
20
|
+
preprocessingConfig = self.getConfig("preprocessing", {})
|
|
37
21
|
if "combineChannels" in preprocessingConfig:
|
|
38
22
|
RecordLoader.get().addCombinedChannels(preprocessingConfig["combineChannels"])
|
|
39
23
|
|
|
@@ -62,9 +46,6 @@ class Setup(Phase):
|
|
|
62
46
|
return kappa
|
|
63
47
|
Scorer.registerMetric("reduceAndKappa", reduceKappa, combineReduceKappa)
|
|
64
48
|
|
|
65
|
-
MedicalDB.config = self.getConfig("medicalDB", {})
|
|
66
|
-
|
|
67
|
-
|
|
68
49
|
def getDataVersionManager(self):
|
|
69
50
|
groupedRecords = self.project.getData("allDBRecordIds", list)
|
|
70
51
|
seed = self.getConfig("dataversion.seed", None)
|
|
@@ -77,6 +77,8 @@ class Training(Phase):
|
|
|
77
77
|
|
|
78
78
|
gridOutputs = []
|
|
79
79
|
for fold in range(startfold, endfold):
|
|
80
|
+
# save model state (random initialisation or pretrained)
|
|
81
|
+
startState = ModelManager.getModel().model.state_dict()
|
|
80
82
|
self.setConfig("fold", fold)
|
|
81
83
|
# only train if it doesnt allready exist
|
|
82
84
|
self.getData("modelState", Model)
|
|
@@ -85,6 +87,8 @@ class Training(Phase):
|
|
|
85
87
|
self.project.unregister("modelState")
|
|
86
88
|
self.project.unregister("modelStateConfig")
|
|
87
89
|
gridOutputs.append(self.gridOutput)
|
|
90
|
+
# reset model state
|
|
91
|
+
ModelManager.getModel().model.load_state_dict(startState)
|
|
88
92
|
|
|
89
93
|
self.project.gridOutput = {
|
|
90
94
|
"epochs": str(np.mean([m["epochs"] for m in gridOutputs])),
|
|
@@ -4,7 +4,6 @@ pyproject.toml
|
|
|
4
4
|
setup.py
|
|
5
5
|
SleePyPhases/DataManipulation.py
|
|
6
6
|
SleePyPhases/EventScorer.py
|
|
7
|
-
SleePyPhases/FeatureExtraction copy.py
|
|
8
7
|
SleePyPhases/FeatureExtraction.py
|
|
9
8
|
SleePyPhases/MultiScorer.py
|
|
10
9
|
SleePyPhases/PSGEventManager.py
|
|
@@ -40,9 +39,6 @@ SleePyPhases/phases/Training.py
|
|
|
40
39
|
SleePyPhases/phases/Validation.py
|
|
41
40
|
SleePyPhases/phases/VisualizeConfig.py
|
|
42
41
|
SleePyPhases/phases/__init__.py
|
|
43
|
-
SleePyPhases/recordloaders/MedicalDB.py
|
|
44
|
-
SleePyPhases/recordloaders/RecordLoaderTSM.py
|
|
45
|
-
SleePyPhases/recordloaders/__init__.py
|
|
46
42
|
tests/__init__.py
|
|
47
43
|
tests/conftest.py
|
|
48
44
|
tests/unit/__init__.py
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="SleePyPhases",
|
|
8
|
-
version="v0.
|
|
8
|
+
version="v0.3.1"[1:],
|
|
9
9
|
author="Franz Ehrlich",
|
|
10
10
|
author_email="fehrlichd@gmail.com",
|
|
11
11
|
description="A framwork for creating deep learning pipelines for sleep data",
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from pyPhasesML import FeatureExtraction as pyPhasesFeatureExtraction
|
|
5
|
-
from pyPhasesML import ModelManager
|
|
6
|
-
from pyPhasesRecordloader import RecordSignal, Event
|
|
7
|
-
|
|
8
|
-
from .DataAugmentation import DataAugmentation
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class FeatureExtraction(pyPhasesFeatureExtraction):
|
|
12
|
-
"""augmentation for the physionet challenge 2023
|
|
13
|
-
segmentSignal: Recordsignal of the segment with 18 EEG channels
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
def __init__(self, project) -> None:
|
|
17
|
-
super().__init__()
|
|
18
|
-
self.project = project
|
|
19
|
-
|
|
20
|
-
def time(self, segmentSignal: RecordSignal):
|
|
21
|
-
segmentLength = segmentSignal.getShape()[1]
|
|
22
|
-
# segmentLengthInHOurs = segmentLength / segmentSignal.targetFrequency / 3600
|
|
23
|
-
# hour, minutes = segmentSignal.start.split(":")
|
|
24
|
-
# startTimeInHours = int(hour) + int(minutes) / 60
|
|
25
|
-
# endTimeInHours = startTimeInHours + segmentLengthInHOurs
|
|
26
|
-
hour = segmentSignal.start
|
|
27
|
-
endTimeInHours = hour + 1
|
|
28
|
-
return np.linspace(hour, endTimeInHours, segmentLength)
|
|
29
|
-
|
|
30
|
-
def featureModel(self, segmentSignal: RecordSignal, featureName: str, xChannels: List[str]):
|
|
31
|
-
import torch
|
|
32
|
-
useGPU = torch.cuda.is_available() and False
|
|
33
|
-
with self.project:
|
|
34
|
-
config = self.project.config["featureConfigs", featureName]
|
|
35
|
-
self.project.config.update(config)
|
|
36
|
-
self.project.addConfig(config)
|
|
37
|
-
|
|
38
|
-
self.project.trigger("configChanged", None)
|
|
39
|
-
self.project.setConfig("trainingParameter.batchSize", 1)
|
|
40
|
-
self.project.setConfig("recordWise", True)
|
|
41
|
-
|
|
42
|
-
modelPath = self.project.getConfig("featureModel", torch)
|
|
43
|
-
|
|
44
|
-
# this is needed for mutlithreading
|
|
45
|
-
ModelManager.loadModel(self.project)
|
|
46
|
-
# get feature model
|
|
47
|
-
model = ModelManager.getModel(True)
|
|
48
|
-
model.useGPU = useGPU
|
|
49
|
-
state = model.load(modelPath)
|
|
50
|
-
model.loadState(state)
|
|
51
|
-
featureModel = model.model.eval()
|
|
52
|
-
|
|
53
|
-
featureModel = featureModel.cuda() if useGPU else featureModel.cpu()
|
|
54
|
-
|
|
55
|
-
# we assum that the segment is already preprocessed for the model
|
|
56
|
-
array = segmentSignal.getSignalArray(xChannels, transpose=True)
|
|
57
|
-
|
|
58
|
-
da = DataAugmentation(self.project.config, self.project.getConfig("datasetSplit"), recordAnnotations={})
|
|
59
|
-
array, _ = da.augmentSegment(array, None)
|
|
60
|
-
array = array.transpose(2, 1, 0)
|
|
61
|
-
|
|
62
|
-
features = model.predict(array, get_likelihood=True)
|
|
63
|
-
_, features = da.restoreLength(None, features, length=segmentSignal.getShape()[1])
|
|
64
|
-
features = features[:, :, 1]
|
|
65
|
-
|
|
66
|
-
return features
|
|
67
|
-
|
|
68
|
-
def spindles(self, segmentSignal: RecordSignal): #xChannels: List[str]
|
|
69
|
-
from .external.sumo.a7.butter_filter import butter_bandpass_filter, downsample
|
|
70
|
-
# from .external.sumo.a7.detect_spindles import detect_spindle
|
|
71
|
-
from .external.sumo.predict_plain_data import SimpleDataset
|
|
72
|
-
from .external.sumo.predict_plain_data import A7
|
|
73
|
-
import pytorch_lightning as pl
|
|
74
|
-
from torch.utils.data import Dataset, DataLoader
|
|
75
|
-
import torch
|
|
76
|
-
from .external.sumo.model.sumo import SUMO
|
|
77
|
-
from .external.sumo.model.config import Config
|
|
78
|
-
# from .external.sumo.model.data import spindle_vect_to_indices
|
|
79
|
-
|
|
80
|
-
config = Config('predict', create_dirs=False)
|
|
81
|
-
|
|
82
|
-
def get_model(p):
|
|
83
|
-
# p = Path(p)
|
|
84
|
-
|
|
85
|
-
model_checkpoint = torch.load(p)
|
|
86
|
-
|
|
87
|
-
model = SUMO(config)
|
|
88
|
-
model.load_state_dict(model_checkpoint['state_dict'])
|
|
89
|
-
|
|
90
|
-
return model
|
|
91
|
-
|
|
92
|
-
eegSignals = [s for s in segmentSignal.signals if s.typeStr == "eeg"]
|
|
93
|
-
resample_rate = 100
|
|
94
|
-
model_path = "SleePyPhases/external/sumo/model/final.ckpt"
|
|
95
|
-
|
|
96
|
-
eegs = []
|
|
97
|
-
for eegSignal in eegSignals:
|
|
98
|
-
eegSignal = downsample(butter_bandpass_filter(eegSignal.signal, 0.3, 30.0, eegSignal.frequency, 10), eegSignal.frequency, resample_rate)
|
|
99
|
-
eegs.append(eegSignal)
|
|
100
|
-
|
|
101
|
-
dataset = SimpleDataset(eegs)
|
|
102
|
-
dataloader = DataLoader(dataset)
|
|
103
|
-
model = get_model(model_path)
|
|
104
|
-
trainer = pl.Trainer(num_sanity_val_steps=0, logger=False)
|
|
105
|
-
predictions = trainer.predict(model, dataloader)
|
|
106
|
-
|
|
107
|
-
for x, pred in zip(eegs, predictions):
|
|
108
|
-
# spindles_a7 = A7(x, resample_rate)
|
|
109
|
-
spindle_vect = pred[0].numpy()
|
|
110
|
-
|
|
111
|
-
diff = np.diff(np.r_[0, spindle_vect, 0]) # be able to detect spindles at the start and end of vector
|
|
112
|
-
spindles = np.c_[np.argwhere(diff == 1), np.argwhere(diff == -1)] / resample_rate
|
|
113
|
-
# spindles = spindle_vect_to_indices(spindle_vect) / resample_rate
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return [Event("spindle-slow", 500, 10)]
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
|
|
3
|
-
from pyPhases.util.Logger import classLogger
|
|
4
|
-
from pypika import Table, PostgreSQLQuery as Query
|
|
5
|
-
from pypika.queries import QueryBuilder
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@classLogger
|
|
9
|
-
class MedicalDB:
|
|
10
|
-
connection = None
|
|
11
|
-
client = None
|
|
12
|
-
assocCursor = None
|
|
13
|
-
_instance = None
|
|
14
|
-
|
|
15
|
-
config = {
|
|
16
|
-
"user": "root",
|
|
17
|
-
"password": "example",
|
|
18
|
-
"host": "localhost",
|
|
19
|
-
"port": 5432,
|
|
20
|
-
"database": "data",
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@classmethod
|
|
24
|
-
def get(cls) -> "MedicalDB":
|
|
25
|
-
if cls._instance is None:
|
|
26
|
-
cls._instance = cls.__new__(cls)
|
|
27
|
-
cls._instance.connect()
|
|
28
|
-
return cls._instance
|
|
29
|
-
|
|
30
|
-
def connect(self):
|
|
31
|
-
|
|
32
|
-
import psycopg2
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
c = MedicalDB.config
|
|
36
|
-
conn = psycopg2.connect(
|
|
37
|
-
"host=%s port=%i dbname=%s user=%s password=%s"
|
|
38
|
-
% (
|
|
39
|
-
c["host"],
|
|
40
|
-
c["port"],
|
|
41
|
-
c["database"],
|
|
42
|
-
c["user"],
|
|
43
|
-
c["password"],
|
|
44
|
-
)
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
except psycopg2.Error as e:
|
|
48
|
-
self.logError(f"Error connecting to Postgres Platform: {e}")
|
|
49
|
-
sys.exit(1)
|
|
50
|
-
self.conn = conn
|
|
51
|
-
self.cursor = conn.cursor()
|
|
52
|
-
|
|
53
|
-
def execute(self, sql, **vars):
|
|
54
|
-
if isinstance(sql, QueryBuilder):
|
|
55
|
-
sql = sql.get_sql()
|
|
56
|
-
return self.cursor.execute(sql, **vars)
|
|
57
|
-
|
|
58
|
-
def executeAndFetch(self, sql, **vars):
|
|
59
|
-
self.execute(sql, **vars)
|
|
60
|
-
return self.cursor.fetchall()
|
|
61
|
-
|
|
62
|
-
def commit(self):
|
|
63
|
-
self.conn.commit()
|
|
64
|
-
|
|
65
|
-
def executeAndFetchOne(self, sql, **vars):
|
|
66
|
-
self.execute(sql, **vars)
|
|
67
|
-
return self.cursor.fetchone()
|
|
68
|
-
|
|
69
|
-
def close(self):
|
|
70
|
-
if self.connection is not None:
|
|
71
|
-
self.connection.close()
|
|
72
|
-
|
|
73
|
-
def fillRecordId(self, id):
|
|
74
|
-
return id
|
|
75
|
-
|
|
76
|
-
def getRecordData(self, recordId):
|
|
77
|
-
record = Table("record", schema=self.config["schema"])
|
|
78
|
-
patient = Table("patient", schema=self.config["schema"])
|
|
79
|
-
medCase = Table("medical_case", schema=self.config["schema"])
|
|
80
|
-
q = (
|
|
81
|
-
Query.from_(record)
|
|
82
|
-
.select(patient.id_patient, record.id_medical_case, record.start, patient.date_of_birth, patient.gender)
|
|
83
|
-
.join(medCase).on(record.id_medical_case == medCase.id_medical_case)
|
|
84
|
-
.join(patient).on(patient.id_patient == medCase.id_patient)
|
|
85
|
-
.where(record.id_record == recordId)
|
|
86
|
-
)
|
|
87
|
-
row = self.executeAndFetchOne(q)
|
|
88
|
-
|
|
89
|
-
if row is None:
|
|
90
|
-
return None
|
|
91
|
-
|
|
92
|
-
row = list(row)
|
|
93
|
-
age = None if row[2] or row[3] is None else row[2] - row[3]
|
|
94
|
-
row[3] = age
|
|
95
|
-
|
|
96
|
-
return row
|
|
97
|
-
|
|
98
|
-
def getDiagnoses(self, recordId):
|
|
99
|
-
diag = Table("diagnosis", schema=self.config["schema"])
|
|
100
|
-
record = Table("record", schema=self.config["schema"])
|
|
101
|
-
medCase = Table("medical_case", schema=self.config["schema"])
|
|
102
|
-
q = Query.from_(diag)\
|
|
103
|
-
.select("diagnosis_category_id", "diagnosis")\
|
|
104
|
-
.join(medCase).on(diag.id_medical_case == medCase.id_medical_case)\
|
|
105
|
-
.join(record).on(medCase.id_medical_case == record.id_medical_case)\
|
|
106
|
-
.where(record.id_record == recordId)\
|
|
107
|
-
.get_sql()
|
|
108
|
-
|
|
109
|
-
diagnoses = self.executeAndFetch(q)
|
|
110
|
-
|
|
111
|
-
diagnoseCats = {d[0]: d[1] for d in diagnoses}
|
|
112
|
-
return diagnoseCats
|
|
113
|
-
|
|
114
|
-
def getPSGType(self, recordId):
|
|
115
|
-
|
|
116
|
-
record = Table("record", schema=self.config["schema"])
|
|
117
|
-
q = (
|
|
118
|
-
Query.from_(record)
|
|
119
|
-
.select("psg_config")
|
|
120
|
-
.where(record.id_record == recordId)
|
|
121
|
-
)
|
|
122
|
-
r = self.executeAndFetchOne(q)
|
|
123
|
-
|
|
124
|
-
return r[0] if bool(r) else ""
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
from SleePyPhases.recordloaders.MedicalDB import MedicalDB
|
|
2
|
-
from SleepHarmonizer import RecordLoaderAlice
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class RecordLoaderTSM(RecordLoaderAlice):
|
|
6
|
-
config = {}
|
|
7
|
-
|
|
8
|
-
def getMetaData(self, recordName):
|
|
9
|
-
metadata = super().getMetaData(recordName)
|
|
10
|
-
del metadata["patientName"]
|
|
11
|
-
del metadata["patientCode"]
|
|
12
|
-
del metadata["patientAdd"]
|
|
13
|
-
del metadata["technician"]
|
|
14
|
-
|
|
15
|
-
db = MedicalDB.get()
|
|
16
|
-
row = db.getRecordData(recordName)
|
|
17
|
-
diagnoses = db.getDiagnoses(recordName)
|
|
18
|
-
metadata["psg_type"] = db.getPSGType(recordName)
|
|
19
|
-
|
|
20
|
-
if row is not None:
|
|
21
|
-
patient_id, case_id, recordDate, patientAge, gender = row
|
|
22
|
-
|
|
23
|
-
metadata["patient"] = patient_id
|
|
24
|
-
metadata["case"] = case_id
|
|
25
|
-
metadata["age"] = patientAge
|
|
26
|
-
metadata["gender"] = gender
|
|
27
|
-
metadata["startRecord"] = recordDate
|
|
28
|
-
|
|
29
|
-
if diagnoses is not None:
|
|
30
|
-
metadata.update(diagnoses)
|
|
31
|
-
|
|
32
|
-
db.close()
|
|
33
|
-
|
|
34
|
-
return metadata
|
|
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
|