SleePyPhases 0.2.0__tar.gz → 0.2.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.0 → sleepyphases-0.2.1}/PKG-INFO +3 -2
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/DataManipulation.py +2 -2
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/RecordFeatureExtraction.py +78 -6
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/SleepMetaData.py +6 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Eval.py +13 -10
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/EvalReport.py +8 -12
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +62 -72
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Extract.py +19 -7
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExtractFeatures.py +11 -8
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/TestRun.py +4 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ThresholdOptimisation.py +9 -7
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/PKG-INFO +3 -2
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/setup.py +1 -1
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/LICENSE +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/README.md +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/EventScorer.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/FeatureExtraction copy.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/FeatureExtraction.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/MultiScorer.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/PSGEventManager.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/Plot.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/Plugin.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/PreManipulation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/Reporter.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/SignalPreprocessing.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/BuildDataset.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/DataAnalysis.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/DataReport.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExtractEvents.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/GatherMetadata.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Setup.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Training.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Validation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/VisualizeConfig.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/recordloaders/MedicalDB.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/recordloaders/RecordLoaderTSM.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/recordloaders/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/SOURCES.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/dependency_links.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/requires.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/top_level.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/pyproject.toml +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/setup.cfg +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/conftest.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_BuildDataset.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_BuildDatasetFolded.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_BuildDatasetSegments.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_DataManipulation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_Eval.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_EventScorer.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_Extract.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_PreManipulation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_RecordProcessor.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_Setup.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_SignalPreprocessing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: SleePyPhases
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A framwork for creating deep learning pipelines for sleep data
|
|
5
5
|
Home-page: https://gitlab.com/sleep-is-all-you-need/sleepyphases
|
|
6
6
|
Author: Franz Ehrlich
|
|
@@ -20,6 +20,7 @@ Dynamic: classifier
|
|
|
20
20
|
Dynamic: description
|
|
21
21
|
Dynamic: description-content-type
|
|
22
22
|
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
23
24
|
Dynamic: requires-dist
|
|
24
25
|
Dynamic: requires-python
|
|
25
26
|
Dynamic: summary
|
|
@@ -14,8 +14,8 @@ class DataManipulation(pyPhasesDataManipulation, Swappable):
|
|
|
14
14
|
def __init__(self, steps, splitName, projectconfig, seed=2, threshold=None, recordMetadata=None, **kwargs) -> None:
|
|
15
15
|
super().__init__(steps, splitName, **kwargs)
|
|
16
16
|
self.catMatrix = None
|
|
17
|
-
self.numLabels = projectconfig["numLabels"]
|
|
18
|
-
self.numClasses = projectconfig["numClasses"]
|
|
17
|
+
self.numLabels = projectconfig["numLabels"] if "numLabels" in projectconfig else 1
|
|
18
|
+
self.numClasses = projectconfig["numClasses"] if "numLabels" in projectconfig else [1]
|
|
19
19
|
self.rng = default_rng(seed=2)
|
|
20
20
|
self.threshold = threshold
|
|
21
21
|
self.recordMetadata = recordMetadata
|
|
@@ -25,6 +25,10 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
|
|
|
25
25
|
|
|
26
26
|
def updateMetadataByStep(self, stepname: str, recordSignal: RecordSignal, eventlist: List[Event], metadata: pd.DataFrame, channel=None, **options):
|
|
27
27
|
features = self.step(stepname, recordSignal, eventlist, channel, **options)
|
|
28
|
+
|
|
29
|
+
if features is None:
|
|
30
|
+
return None
|
|
31
|
+
|
|
28
32
|
recordId = recordSignal.recordId
|
|
29
33
|
# update row with index recordId and channel with features dict or create new if not exist
|
|
30
34
|
|
|
@@ -252,15 +256,83 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
|
|
|
252
256
|
ret = {
|
|
253
257
|
"cycle-count": len(cycles["start"]),
|
|
254
258
|
}
|
|
259
|
+
frequencyFactor = 30
|
|
255
260
|
|
|
256
|
-
for cycleNumber, (start, stop) in enumerate(zip(cycles["start"], cycles["
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
for cycleNumber, (start, stop) in enumerate(zip(cycles["start"], cycles["stop"])):
|
|
262
|
+
start *= frequencyFactor
|
|
263
|
+
stop *= frequencyFactor
|
|
264
|
+
# if lastStop > 0:
|
|
265
|
+
remTime = sum([e.duration for e in remEvents if e.start >= lastStop and e.start < start])
|
|
266
|
+
ret[f"cycle-{cycleNumber}-rem-duration"] = remTime
|
|
260
267
|
|
|
261
268
|
lastStop = stop
|
|
262
269
|
|
|
270
|
+
# remTime = sum([e.duration for e in remEvents if e.start >= lastStop and e.start < start])
|
|
271
|
+
# ret[f"cycle-{cycleNumber}-rem-duration"] = remTime
|
|
272
|
+
|
|
273
|
+
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
|
+
|
|
263
334
|
return {
|
|
264
|
-
"
|
|
335
|
+
"spindles-count": len(spindles),
|
|
336
|
+
"spindles-mean-druation": np.array([s.duration for s in spindles]).mean()
|
|
265
337
|
}
|
|
266
|
-
|
|
338
|
+
|
|
@@ -76,6 +76,7 @@ class SleepMetaData:
|
|
|
76
76
|
self.fromSleepSignalBin(signal, frequency=frequency)
|
|
77
77
|
case 'Sleep4':
|
|
78
78
|
self.fromSleepSignalBin(signal, frequency=frequency)
|
|
79
|
+
self.fromSleepSignalFour(signal, frequency=frequency)
|
|
79
80
|
|
|
80
81
|
def fromSleepSignalBin(self, signal, frequency = 1):
|
|
81
82
|
# all time values in seconds
|
|
@@ -100,6 +101,11 @@ class SleepMetaData:
|
|
|
100
101
|
self.registerMetadata("waso", trt-sLatency-tst, unit="s")
|
|
101
102
|
self.registerMetadata("sEfficiency", tst / trt * 100, unit="%")
|
|
102
103
|
|
|
104
|
+
def fromSleepSignalFour(self, signal, frequency = 1):
|
|
105
|
+
whereREM = np.where(signal == 1)
|
|
106
|
+
rLatency = whereREM[0] - self.getMetaData("sLatency")
|
|
107
|
+
self.registerMetadata("rLatency", rLatency)
|
|
108
|
+
|
|
103
109
|
|
|
104
110
|
def fromApneaSignal(self, signal, classification=None):
|
|
105
111
|
# classification = classification or ["None", "obstructive", "mixed", "central", "hypopnea"]
|
|
@@ -86,8 +86,7 @@ class Eval(Phase):
|
|
|
86
86
|
ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
|
|
87
87
|
metrics = self.getConfig("trainingParameter.validationMetrics")
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
scorer = MultiScorer(classNums, metrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClass=EventScorer)
|
|
89
|
+
scorer = MultiScorer(classNums, metrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex])
|
|
91
90
|
for s in scorer.scorer.values():
|
|
92
91
|
s.trace = True
|
|
93
92
|
threshold = self.getConfig("fixedThreshold", False)
|
|
@@ -107,8 +106,8 @@ class Eval(Phase):
|
|
|
107
106
|
prediction = model.predict(x, get_likelihood=True, returnNumpy=False)
|
|
108
107
|
prediction, truth = da((prediction, truth))
|
|
109
108
|
|
|
110
|
-
prediction = prediction.reshape(-1, prediction.shape[2])
|
|
111
|
-
truth = truth.reshape(-1, truth.shape[2])
|
|
109
|
+
# prediction = prediction.reshape(-1, prediction.shape[2])
|
|
110
|
+
# truth = truth.reshape(-1, truth.shape[2])
|
|
112
111
|
|
|
113
112
|
scorer.score(truth, prediction, trace=True)
|
|
114
113
|
|
|
@@ -150,7 +149,7 @@ class Eval(Phase):
|
|
|
150
149
|
testData, recordsMap = self.getTestDataAndRecordMap()
|
|
151
150
|
|
|
152
151
|
with self.project:
|
|
153
|
-
self.project.config.update(self.getConfig("eventEval"))
|
|
152
|
+
self.project.config.update(self.getConfig("eventEval", {}))
|
|
154
153
|
|
|
155
154
|
evalMetrics = self.getConfig("eval.metrics")
|
|
156
155
|
classNums = self.getConfig("model.multiclassificationSize", [2])
|
|
@@ -158,10 +157,11 @@ class Eval(Phase):
|
|
|
158
157
|
predictionNames = self.getConfig("classification.predictionSignals")
|
|
159
158
|
predictionFrequencies = self.getConfig("classification.predictionFrequencies")
|
|
160
159
|
ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
|
|
160
|
+
scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
|
|
161
161
|
|
|
162
162
|
threshold = self.getConfig("fixedThreshold", False)
|
|
163
163
|
thresholds = threshold or self.getData("threshold", list)
|
|
164
|
-
mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex],
|
|
164
|
+
mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClasses=scorerTypes)
|
|
165
165
|
for threshold, s in zip(thresholds, mScorer.scorer.values()):
|
|
166
166
|
s.majorityVote = self.getConfig("eventEval.tpStrat", "overlap") == "majority"
|
|
167
167
|
s.noTN = self.getConfig("eventEval.tnStrat", "eventcount") == "noTN"
|
|
@@ -172,7 +172,7 @@ class Eval(Phase):
|
|
|
172
172
|
|
|
173
173
|
metaData = pd.DataFrame(self.getTestMetadata())
|
|
174
174
|
metaData.replace("M", None, inplace=True)
|
|
175
|
-
metaData["indexArousal"] = metaData["indexArousal"].astype(float)
|
|
175
|
+
# metaData["indexArousal"] = metaData["indexArousal"].astype(float)
|
|
176
176
|
numClasses = self.getConfig("numClasses")
|
|
177
177
|
|
|
178
178
|
model = self.getModel()
|
|
@@ -212,11 +212,12 @@ class Eval(Phase):
|
|
|
212
212
|
labelTruth = labelTruth.reshape(-1)
|
|
213
213
|
|
|
214
214
|
# score the prediction
|
|
215
|
-
r = scorer.score(labelTruth
|
|
215
|
+
r = scorer.score(labelTruth, labelPrediction)
|
|
216
|
+
# r = scorer.score(labelTruth.reshape(-1), labelPrediction.reshape(-1, numClass))
|
|
216
217
|
resultRow.update({f"{m}_{labelName}": v for m,v in r.items()})
|
|
217
218
|
|
|
218
219
|
# add examples
|
|
219
|
-
resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
|
|
220
|
+
# resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
|
|
220
221
|
|
|
221
222
|
|
|
222
223
|
recordData = metaData.query(f'recordId == "{recordId}"')
|
|
@@ -250,4 +251,6 @@ class Eval(Phase):
|
|
|
250
251
|
self.registerData("eventResults", eventResults)
|
|
251
252
|
|
|
252
253
|
def main(self):
|
|
253
|
-
self.getData("
|
|
254
|
+
segmentResultsRecords, segmentResults = self.getData("evalResults")
|
|
255
|
+
|
|
256
|
+
self.logSuccess(f"result segments: {segmentResults}")
|
|
@@ -38,7 +38,7 @@ class EvalReport(Phase):
|
|
|
38
38
|
|
|
39
39
|
def createLogFolder(self, path):
|
|
40
40
|
Path(path).mkdir(parents=True, exist_ok=True)
|
|
41
|
-
Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
|
|
41
|
+
# Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
|
|
42
42
|
|
|
43
43
|
def addAsset(self, name, extension="png", bbox_inches="tight", dpi=300):
|
|
44
44
|
import matplotlib.pyplot as plt
|
|
@@ -178,22 +178,20 @@ class EvalReport(Phase):
|
|
|
178
178
|
|
|
179
179
|
def main(self):
|
|
180
180
|
import pandas as pd
|
|
181
|
-
from SleePyPhases.Plot import Plot
|
|
182
181
|
|
|
183
182
|
threshold = self.getConfig("fixedThreshold", False)
|
|
184
183
|
self.threshold = threshold or self.getData("threshold", float)
|
|
185
184
|
|
|
186
185
|
modelConfigString = self.project.getDataFromName("eventResults").getTagString()
|
|
187
|
-
evalPath = f"eval/{modelConfigString}/"
|
|
188
186
|
|
|
189
|
-
|
|
187
|
+
evalPath = self.getConfig("eval-path", "eval/")
|
|
188
|
+
evalPath = f"{evalPath}/{modelConfigString}/"
|
|
189
|
+
|
|
190
|
+
self.createLogFolder(evalPath)
|
|
191
|
+
|
|
190
192
|
self.evalPath = evalPath
|
|
191
|
-
self.reporter = Reporter("EvalReport")
|
|
192
|
-
self.reporter.createFolder()
|
|
193
|
-
# self.reporter.save()
|
|
194
193
|
|
|
195
194
|
self.copyRawData()
|
|
196
|
-
# self.modelInfo()
|
|
197
195
|
|
|
198
196
|
# self.log("save report to: %s" % self.reporter.getFilePath())
|
|
199
197
|
|
|
@@ -257,11 +255,9 @@ class EvalReport(Phase):
|
|
|
257
255
|
df = pd.read_csv(logFile)
|
|
258
256
|
plt.plot(df["epoch"], df["loss"], label="Training Loss")
|
|
259
257
|
plt.plot(df["epoch"], df[self.metrics[0]], label=self.metrics[0])
|
|
260
|
-
self.
|
|
258
|
+
self.addAsset("trainingsProcess")
|
|
261
259
|
|
|
262
260
|
def modelInfo(self):
|
|
263
261
|
model = self.project.getPhase("Eval").getModel()
|
|
264
262
|
sum = model.summary()
|
|
265
|
-
|
|
266
|
-
self.reporter.addVar("parameter", model.parameter, "humannumber")
|
|
267
|
-
self.reporter.addVar("modelSummary", sum)
|
|
263
|
+
print(sum)
|
|
@@ -18,32 +18,10 @@ class ExportMetadataToMeticalDB(Phase):
|
|
|
18
18
|
from SleePyPhases.recordloaders.MedicalDB import MedicalDB
|
|
19
19
|
|
|
20
20
|
metadata = self.getData("metadata", pd.DataFrame)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# for dataRow in metadata.iloc:
|
|
24
|
-
|
|
25
|
-
# idDict = {
|
|
26
|
-
# "acquisition_id": dataRow["recordId"],
|
|
27
|
-
# "patient_psn": dataRow["patient"] if "patient" in dataRow else None,
|
|
28
|
-
# "bmi": dataRow["bmi"] if "bmi" in dataRow else None,
|
|
29
|
-
# "domino": self.getConfig("useLoader") == "tsm-domino"
|
|
30
|
-
# }
|
|
31
|
-
# for col in cols:
|
|
32
|
-
# if col in dataRow:
|
|
33
|
-
# idDict[col] = dataRow[col]
|
|
34
|
-
# recordMetadataRows.append(idDict)
|
|
21
|
+
relevantCols = [v for v in AliceTextReportLoader.relevantRows.values() if v != ""]
|
|
22
|
+
channelMetaData = self.getData("metadata-channels", pd.DataFrame)
|
|
35
23
|
|
|
36
|
-
|
|
37
|
-
# channelMetadata = self.getData("metadata-channels", pd.DataFrame)
|
|
38
|
-
# features = self.getData("features", pd.DataFrame)
|
|
39
|
-
|
|
40
|
-
# for dataRow in list(channelMetadata.iloc) + list(features.iloc):
|
|
41
|
-
# pass
|
|
42
|
-
|
|
43
|
-
# if self.exportTo == "csv":
|
|
44
|
-
# df = pd.DataFrame.from_records(recordMetadataRows, index="acquisition_id")
|
|
45
|
-
# df.to_csv("recordMetadata.csv")
|
|
46
|
-
# else:
|
|
24
|
+
featureData = self.getData("features", pd.DataFrame)
|
|
47
25
|
MedicalDB.config = self.getConfig("medicalDB")
|
|
48
26
|
medDB = MedicalDB.get()
|
|
49
27
|
medDB.connect()
|
|
@@ -58,18 +36,8 @@ class ExportMetadataToMeticalDB(Phase):
|
|
|
58
36
|
somnoemetricTable = schema.somnometric_type
|
|
59
37
|
|
|
60
38
|
for rowIndex, row in tqdm(metadata.iterrows(), total=len(metadata)):
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
channelData = self.getData("metadata-channels", pd.DataFrame, recordId=recordId)
|
|
64
|
-
features = {}
|
|
65
|
-
try:
|
|
66
|
-
features = self.getData("features", pd.DataFrame, recordId=recordId)
|
|
67
|
-
except ChannelsNotPresent:
|
|
68
|
-
pass
|
|
69
|
-
except AnnotationNotFound:
|
|
70
|
-
pass
|
|
71
|
-
except ParseError:
|
|
72
|
-
pass
|
|
39
|
+
channelData = channelMetaData.query(f"recordId == '{rowIndex}'")
|
|
40
|
+
features = featureData.query(f"recordId == '{rowIndex}'")
|
|
73
41
|
|
|
74
42
|
missingCaseCounts = {}
|
|
75
43
|
def getMissingCid(pid):
|
|
@@ -143,7 +111,7 @@ class ExportMetadataToMeticalDB(Phase):
|
|
|
143
111
|
if isinstance(val, bool):
|
|
144
112
|
return "float", 1 if val else 0
|
|
145
113
|
|
|
146
|
-
dtype = "float" if val.dtype == float else "string"
|
|
114
|
+
dtype = "float" if isinstance(val, float) or val.dtype == float else "string"
|
|
147
115
|
|
|
148
116
|
return dtype, val
|
|
149
117
|
|
|
@@ -166,29 +134,56 @@ class ExportMetadataToMeticalDB(Phase):
|
|
|
166
134
|
# where source == psg
|
|
167
135
|
medDB.execute(str(q))
|
|
168
136
|
|
|
137
|
+
for metricName in relevantCols:
|
|
138
|
+
if metricName in metadata.columns:
|
|
139
|
+
addMetricType(metricName, metadata[metricName])
|
|
140
|
+
|
|
141
|
+
# add all record metadata
|
|
142
|
+
for column, val in row.items():
|
|
143
|
+
if column in relevantCols and not pd.isna(val) and val is not None:
|
|
144
|
+
typestr, val = getTypeAndValue(row[column])
|
|
145
|
+
q = PostgreSQLQuery.into(recordMetaTable)\
|
|
146
|
+
.columns(["id_record", "id_somnometric_type", f"value_{typestr}"])\
|
|
147
|
+
.insert([recordId, column, val])
|
|
148
|
+
medDB.execute(str(q))
|
|
169
149
|
|
|
170
150
|
# add all the metadata types (somnometrics types)
|
|
171
151
|
# channelMetrics = [col for col in channelData.columns if col not in ["label", "signalName", "recordId"]]
|
|
172
152
|
# for metricName in channelMetrics:
|
|
173
153
|
# addMetricType(metricName, channelData[metricName].dtype)
|
|
174
154
|
|
|
155
|
+
# delete all chanel data for the reocord
|
|
156
|
+
q = PostgreSQLQuery.from_(channelMetaTable)\
|
|
157
|
+
.using(channelTable)\
|
|
158
|
+
.where(channelMetaTable.id_channel == channelTable.id_channel)\
|
|
159
|
+
.where(channelTable.id_record == recordId)\
|
|
160
|
+
.delete()
|
|
161
|
+
medDB.execute(str(q))
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
q = PostgreSQLQuery.from_(channelTable)\
|
|
165
|
+
.delete()\
|
|
166
|
+
.where(channelTable.id_record == recordId)
|
|
167
|
+
|
|
168
|
+
medDB.execute(str(q))
|
|
169
|
+
|
|
175
170
|
if len(features) > 0:
|
|
176
171
|
featureMetrics = [col for col in features.columns if col not in ["channel", "recordId"]]
|
|
177
172
|
for metricName in featureMetrics:
|
|
178
173
|
addMetricType(metricName, features[metricName])
|
|
179
174
|
|
|
175
|
+
|
|
180
176
|
for channel in channelData.iloc:
|
|
181
177
|
if "signalName" not in channel:
|
|
182
178
|
continue
|
|
183
179
|
chanelDict = {
|
|
184
180
|
"name": channel["signalName"],
|
|
185
|
-
"frequency": channel["sample_rate"], # sample_frequency
|
|
181
|
+
"frequency": channel["sample_rate"] if "sample_rate" in channel else channel["sample_frequency"], # sample_frequency
|
|
186
182
|
"prefilter": channel["prefilter"],
|
|
187
183
|
"physical_max": channel["physical_max"],
|
|
188
184
|
"physical_min": channel["physical_min"],
|
|
189
185
|
"digital_max": channel["digital_max"],
|
|
190
186
|
"digital_min": channel["digital_min"],
|
|
191
|
-
"prefilter": channel["prefilter"],
|
|
192
187
|
"transducer": channel["transducer"],
|
|
193
188
|
"type": channel["type"],
|
|
194
189
|
"id_record": recordId
|
|
@@ -197,7 +192,7 @@ class ExportMetadataToMeticalDB(Phase):
|
|
|
197
192
|
q = PostgreSQLQuery.into(channelTable)\
|
|
198
193
|
.columns(*list(chanelDict.keys()))\
|
|
199
194
|
.insert(*list(chanelDict.values()))
|
|
200
|
-
channelId = medDB.
|
|
195
|
+
channelId = medDB.executeAndFetchOne(f"{q} RETURNING id_channel")[0]
|
|
201
196
|
|
|
202
197
|
|
|
203
198
|
# add channel specific features
|
|
@@ -232,37 +227,32 @@ class ExportMetadataToMeticalDB(Phase):
|
|
|
232
227
|
# medDB.execute(str(q))
|
|
233
228
|
|
|
234
229
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
.insert(*list(chanelDict.values()))
|
|
262
|
-
medDB.execute(str(q))
|
|
263
|
-
|
|
264
|
-
if rowIndex % 25 == 0:
|
|
265
|
-
medDB.commit()
|
|
230
|
+
# if "channels" not in row:
|
|
231
|
+
# continue
|
|
232
|
+
|
|
233
|
+
# for channel in row["channels"]:
|
|
234
|
+
# channelName = channel["signalName"]
|
|
235
|
+
|
|
236
|
+
# chanelDict = {
|
|
237
|
+
# "name": channelName,
|
|
238
|
+
# "frequency": channel["sample_rate"], # sample_frequency
|
|
239
|
+
# "prefilter": channel["prefilter"],
|
|
240
|
+
# "physical_max": channel["physical_max"],
|
|
241
|
+
# "physical_min": channel["physical_min"],
|
|
242
|
+
# "digital_max": channel["digital_max"],
|
|
243
|
+
# "digital_min": channel["digital_min"],
|
|
244
|
+
# "prefilter": channel["prefilter"],
|
|
245
|
+
# "transducer": channel["transducer"],
|
|
246
|
+
# "type": channel["type"],
|
|
247
|
+
# "id_record": recordId
|
|
248
|
+
# }
|
|
249
|
+
# q = PostgreSQLQuery.into(channelTable)\
|
|
250
|
+
# .columns(*list(chanelDict.keys()))\
|
|
251
|
+
# .insert(*list(chanelDict.values()))
|
|
252
|
+
# medDB.execute(str(q))
|
|
253
|
+
|
|
254
|
+
# if rowIndex % 25 == 0:
|
|
255
|
+
medDB.commit()
|
|
266
256
|
# table = schema.acq_metadata
|
|
267
257
|
|
|
268
258
|
# checkQuery = PostgreSQLQuery.from_(table).select(1).where(table.acquisition_id == metaData['acquisition_id'])
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from pathlib import Path
|
|
1
2
|
import numpy as np
|
|
2
3
|
from pyPhases import Phase
|
|
3
4
|
from pyPhases.util import BatchProgress
|
|
@@ -228,25 +229,33 @@ class Extract(Phase):
|
|
|
228
229
|
exporterSignals.options["dtype"] = self.getConfig("preprocessing.dtype", "float32")
|
|
229
230
|
exporterFeatures.options["dtype"] = self.getConfig("preprocessing.dtype", "float32")
|
|
230
231
|
|
|
232
|
+
removedRecordIds = []
|
|
233
|
+
removed_records_path = exporterSignals.getPath(f"{dataIdSignals}-removed-records-tmp.npy")
|
|
234
|
+
|
|
231
235
|
if force is False:
|
|
232
236
|
if exporterSignals.exists(dataIdSignals) and exporterFeatures.exists(dataIdFeatures):
|
|
233
|
-
self.logSuccess("Datafiles
|
|
234
|
-
return
|
|
237
|
+
self.logSuccess("Datafiles already exist")
|
|
238
|
+
return []
|
|
235
239
|
|
|
236
240
|
if exporterSignals.existsTmp(dataIdSignals) and exporterFeatures.existsTmp(dataIdFeatures):
|
|
237
241
|
exporterSignals.loadTmp(dataIdSignals)
|
|
238
242
|
exporterFeatures.loadTmp(dataIdFeatures)
|
|
243
|
+
|
|
244
|
+
if Path(removed_records_path).exists():
|
|
245
|
+
removedRecordIds = np.load(removed_records_path, allow_pickle=True).tolist()
|
|
246
|
+
|
|
239
247
|
recordsLoaded = len(exporterFeatures.currentArrayShapes[dataIdSignals][0])
|
|
240
|
-
|
|
241
|
-
|
|
248
|
+
total_processed = recordsLoaded + len(removedRecordIds)
|
|
249
|
+
|
|
250
|
+
# Skip both successfully processed and removed records
|
|
251
|
+
records = records[total_processed:]
|
|
252
|
+
self.logSuccess(f"Resuming extraction: {recordsLoaded} records already loaded, {len(removedRecordIds)} records skipped, {len(records)} to go")
|
|
242
253
|
|
|
243
254
|
bp = BatchProgress(records)
|
|
244
255
|
bp.useMultiThreading = self.useMultiThreading
|
|
245
256
|
if self.threads is not None:
|
|
246
257
|
bp.threads = self.threads
|
|
247
258
|
|
|
248
|
-
removedRecordIds = []
|
|
249
|
-
|
|
250
259
|
def combine(recordList, dataIndex):
|
|
251
260
|
recordList = recordList.copy()
|
|
252
261
|
# append the the data and the shape to a tmp file
|
|
@@ -254,7 +263,10 @@ class Extract(Phase):
|
|
|
254
263
|
# check if all records where loaded, else extend the removedRecord list
|
|
255
264
|
if None in recordList:
|
|
256
265
|
rm = [i + dataIndex for i, r in enumerate(recordList) if r is None]
|
|
257
|
-
[
|
|
266
|
+
new_removed = [records[r] for r in rm]
|
|
267
|
+
removedRecordIds.extend(new_removed)
|
|
268
|
+
np.save(removed_records_path, removedRecordIds)
|
|
269
|
+
|
|
258
270
|
recordList = [r for r in recordList if r is not None]
|
|
259
271
|
self.logWarning("Skipped %i records: channels/annotation not present" % len(rm))
|
|
260
272
|
|
|
@@ -174,6 +174,8 @@ class ExtractFeatures(Phase):
|
|
|
174
174
|
recordSignal, eventlist = recordLoader.loadRecord(recordId)
|
|
175
175
|
except AnnotationNotFound as e:
|
|
176
176
|
self.logError(f"Annotation missing in {recordId}: {e.name} ... skipping record")
|
|
177
|
+
except ChannelsNotPresent as e:
|
|
178
|
+
self.logError(f"Channels missing in {recordId}: {e.channels} ... skipping record")
|
|
177
179
|
except ParseError as e:
|
|
178
180
|
self.logError(f"ParseError in record {recordId}, record will be skipped: {e}")
|
|
179
181
|
|
|
@@ -203,19 +205,20 @@ class ExtractFeatures(Phase):
|
|
|
203
205
|
return self.features.loc[idx]
|
|
204
206
|
|
|
205
207
|
def main(self):
|
|
208
|
+
import pandas as pd
|
|
206
209
|
self.features = None
|
|
207
|
-
|
|
210
|
+
metadata = self.project.getData("metadata", pd.DataFrame)
|
|
211
|
+
recordIds = metadata["recordId"]
|
|
208
212
|
|
|
209
213
|
reqFeatures = {f["name"]: True for f in self.getConfig("featureExtraction.features", [])}
|
|
210
214
|
|
|
211
215
|
# groupedRecords = {"pid_202436073": "acq_237478592"}
|
|
212
|
-
for
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
self.logError(f"no features for {recordId}")
|
|
216
|
+
for recordId in tqdm(recordIds.iloc):
|
|
217
|
+
# tryToGenerate has no meaning, it is just known to be not in the metadata, so the record will always be recalculated
|
|
218
|
+
# self.getData("features", pd.DataFrame, recordId=recordId, tryToGenerate=True, **reqFeatures)
|
|
219
|
+
rows = self.getData("features", pd.DataFrame, recordId=recordId, **reqFeatures)
|
|
220
|
+
if len(rows) == 0:
|
|
221
|
+
self.logError(f"no features for {recordId}")
|
|
219
222
|
|
|
220
223
|
|
|
221
224
|
# get a single feature:
|
|
@@ -48,6 +48,8 @@ class TestRun(Phase):
|
|
|
48
48
|
self.log(f"Creating a new Data Folder ({testRunData.as_posix()}) for the testrun")
|
|
49
49
|
if not testRunData.exists():
|
|
50
50
|
testRunData.mkdir()
|
|
51
|
+
self.setConfig("data-path", testRunData)
|
|
52
|
+
self.project.prepareAllPhases()
|
|
51
53
|
# else:
|
|
52
54
|
# # remove all files from the testrun folder
|
|
53
55
|
# for f in testRunData.iterdir():
|
|
@@ -121,6 +123,8 @@ class TestRun(Phase):
|
|
|
121
123
|
self.log(f"Test Model: {self.getConfig('modelName')}")
|
|
122
124
|
|
|
123
125
|
model = ModelManager.getModel()
|
|
126
|
+
model.config.logPath = testRunData / "logs"
|
|
127
|
+
|
|
124
128
|
self.log(f"Current Model: {type(model)}")
|
|
125
129
|
self.log(model.summary())
|
|
126
130
|
train = self.project.generateData("dataset-training")
|
|
@@ -4,6 +4,7 @@ from pyPhasesML import Model, ModelManager
|
|
|
4
4
|
from tqdm import tqdm
|
|
5
5
|
|
|
6
6
|
from SleePyPhases.DataManipulation import DataManipulation
|
|
7
|
+
from pyPhasesML import Scorer
|
|
7
8
|
from SleePyPhases.EventScorer import EventScorer
|
|
8
9
|
from SleePyPhases.MultiScorer import MultiScorer
|
|
9
10
|
|
|
@@ -35,12 +36,8 @@ class ThresholdOptimisation(Phase):
|
|
|
35
36
|
x, t = validationData[i]
|
|
36
37
|
p = model.predict(x, get_likelihood=True, returnNumpy=False)
|
|
37
38
|
p, t = da((p, t))
|
|
38
|
-
prediction.append(p
|
|
39
|
-
truth.append(t
|
|
40
|
-
|
|
41
|
-
# we assume output is reduces to bachsize 1 during augmentation (1, X, channels)
|
|
42
|
-
prediction = np.concatenate(prediction, axis=1)
|
|
43
|
-
truth = np.concatenate(truth, axis=1)
|
|
39
|
+
[prediction.append(x) for x in p]
|
|
40
|
+
[truth.append(x) for x in t]
|
|
44
41
|
|
|
45
42
|
return prediction, truth
|
|
46
43
|
|
|
@@ -54,8 +51,10 @@ class ThresholdOptimisation(Phase):
|
|
|
54
51
|
classNums = self.getConfig("model.multiclassificationSize", [2])
|
|
55
52
|
labelNames = self.getConfig("classification.labelNames")
|
|
56
53
|
ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
|
|
54
|
+
optimizeThresholdFor = self.getConfig("optimizeThresholdFor", None)
|
|
55
|
+
scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
|
|
57
56
|
|
|
58
|
-
scorer = MultiScorer(classNums, thresholdMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex],
|
|
57
|
+
scorer = MultiScorer(classNums, thresholdMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClasses=scorerTypes)
|
|
59
58
|
for s in scorer.scorer.values():
|
|
60
59
|
s.majorityVote = self.getConfig("eventEval.tpStrat", "overlap") == "majority"
|
|
61
60
|
s.noTN = self.getConfig("eventEval.tnStrat", "eventcount") == "noTN"
|
|
@@ -88,6 +87,9 @@ class ThresholdOptimisation(Phase):
|
|
|
88
87
|
|
|
89
88
|
thresholds = []
|
|
90
89
|
for scorerIndex, (scorerName, thresholdMetric) in enumerate(zip(labelNames, thresholdMetrics)):
|
|
90
|
+
if optimizeThresholdFor is not None and scorerName not in optimizeThresholdFor:
|
|
91
|
+
thresholds.append(None)
|
|
92
|
+
continue
|
|
91
93
|
self.log(f"Optimize threshold for {scorerName} {thresholdMetric}")
|
|
92
94
|
t = scipy.optimize.fmin(score, args=(prediction, truth, scorerIndex, scorerName, thresholdMetric), x0=0.3, xtol=0.01, ftol=0.01)
|
|
93
95
|
self.logSuccess(f"Optimized threshold for {scorerName} {thresholdMetric}: {t}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: SleePyPhases
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A framwork for creating deep learning pipelines for sleep data
|
|
5
5
|
Home-page: https://gitlab.com/sleep-is-all-you-need/sleepyphases
|
|
6
6
|
Author: Franz Ehrlich
|
|
@@ -20,6 +20,7 @@ Dynamic: classifier
|
|
|
20
20
|
Dynamic: description
|
|
21
21
|
Dynamic: description-content-type
|
|
22
22
|
Dynamic: home-page
|
|
23
|
+
Dynamic: license-file
|
|
23
24
|
Dynamic: requires-dist
|
|
24
25
|
Dynamic: requires-python
|
|
25
26
|
Dynamic: summary
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="SleePyPhases",
|
|
8
|
-
version="v0.2.
|
|
8
|
+
version="v0.2.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",
|
|
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
|