SleePyPhases 0.2.0__tar.gz → 0.3.0__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.3.0}/PKG-INFO +3 -2
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/DataManipulation.py +2 -2
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/MultiScorer.py +8 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/RecordFeatureExtraction.py +78 -6
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/SleepMetaData.py +6 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Eval.py +15 -18
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/EvalReport.py +10 -15
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +62 -72
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Extract.py +19 -7
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExtractFeatures.py +13 -13
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Setup.py +0 -19
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/TestRun.py +4 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ThresholdOptimisation.py +9 -7
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/PKG-INFO +3 -2
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/SOURCES.txt +0 -3
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/setup.py +1 -1
- sleepyphases-0.2.0/SleePyPhases/recordloaders/MedicalDB.py +0 -124
- sleepyphases-0.2.0/SleePyPhases/recordloaders/RecordLoaderTSM.py +0 -34
- sleepyphases-0.2.0/tests/unit/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/LICENSE +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/README.md +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/EventScorer.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/FeatureExtraction copy.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/FeatureExtraction.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/PSGEventManager.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/Plot.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/Plugin.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/PreManipulation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/Reporter.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/SignalPreprocessing.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/BuildDataset.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/DataAnalysis.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/DataReport.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExtractEvents.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/GatherMetadata.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Training.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Validation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/VisualizeConfig.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/dependency_links.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/requires.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/top_level.txt +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/pyproject.toml +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/setup.cfg +0 -0
- {sleepyphases-0.2.0/SleePyPhases/recordloaders → sleepyphases-0.3.0/tests}/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/conftest.py +0 -0
- {sleepyphases-0.2.0/tests → sleepyphases-0.3.0/tests/unit}/__init__.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_BuildDataset.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_BuildDatasetFolded.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_BuildDatasetSegments.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_DataManipulation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_Eval.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_EventScorer.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_Extract.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_PreManipulation.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_RecordProcessor.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_Setup.py +0 -0
- {sleepyphases-0.2.0 → sleepyphases-0.3.0}/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.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -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
|
|
|
@@ -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"]
|
|
@@ -81,13 +81,12 @@ 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")
|
|
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)
|
|
@@ -100,16 +99,10 @@ class Eval(Phase):
|
|
|
100
99
|
|
|
101
100
|
for data in tqdm(testData):
|
|
102
101
|
x, truth = data
|
|
103
|
-
# make sure to have a batchsize if batchSize == 1
|
|
104
|
-
x = x.reshape(batchSize, x.shape[0], x.shape[1])
|
|
105
|
-
truth = truth.reshape(batchSize, truth.shape[0], truth.shape[1])
|
|
106
102
|
|
|
107
|
-
prediction = model.predict(x,
|
|
103
|
+
prediction = model.predict(x, returnNumpy=False)
|
|
108
104
|
prediction, truth = da((prediction, truth))
|
|
109
105
|
|
|
110
|
-
prediction = prediction.reshape(-1, prediction.shape[2])
|
|
111
|
-
truth = truth.reshape(-1, truth.shape[2])
|
|
112
|
-
|
|
113
106
|
scorer.score(truth, prediction, trace=True)
|
|
114
107
|
|
|
115
108
|
result = scorer.scoreAllRecords()
|
|
@@ -150,18 +143,19 @@ class Eval(Phase):
|
|
|
150
143
|
testData, recordsMap = self.getTestDataAndRecordMap()
|
|
151
144
|
|
|
152
145
|
with self.project:
|
|
153
|
-
self.project.config.update(self.getConfig("eventEval"))
|
|
146
|
+
self.project.config.update(self.getConfig("eventEval", {}))
|
|
154
147
|
|
|
155
148
|
evalMetrics = self.getConfig("eval.metrics")
|
|
156
|
-
classNums = self.getConfig("
|
|
149
|
+
classNums = self.getConfig("numClasses", [2])
|
|
157
150
|
labelNames = self.getConfig("classification.labelNames")
|
|
158
151
|
predictionNames = self.getConfig("classification.predictionSignals")
|
|
159
152
|
predictionFrequencies = self.getConfig("classification.predictionFrequencies")
|
|
160
153
|
ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
|
|
154
|
+
scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
|
|
161
155
|
|
|
162
156
|
threshold = self.getConfig("fixedThreshold", False)
|
|
163
157
|
thresholds = threshold or self.getData("threshold", list)
|
|
164
|
-
mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex],
|
|
158
|
+
mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClasses=scorerTypes)
|
|
165
159
|
for threshold, s in zip(thresholds, mScorer.scorer.values()):
|
|
166
160
|
s.majorityVote = self.getConfig("eventEval.tpStrat", "overlap") == "majority"
|
|
167
161
|
s.noTN = self.getConfig("eventEval.tnStrat", "eventcount") == "noTN"
|
|
@@ -172,7 +166,7 @@ class Eval(Phase):
|
|
|
172
166
|
|
|
173
167
|
metaData = pd.DataFrame(self.getTestMetadata())
|
|
174
168
|
metaData.replace("M", None, inplace=True)
|
|
175
|
-
metaData["indexArousal"] = metaData["indexArousal"].astype(float)
|
|
169
|
+
# metaData["indexArousal"] = metaData["indexArousal"].astype(float)
|
|
176
170
|
numClasses = self.getConfig("numClasses")
|
|
177
171
|
|
|
178
172
|
model = self.getModel()
|
|
@@ -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()):
|
|
@@ -212,11 +206,12 @@ class Eval(Phase):
|
|
|
212
206
|
labelTruth = labelTruth.reshape(-1)
|
|
213
207
|
|
|
214
208
|
# score the prediction
|
|
215
|
-
r = scorer.score(labelTruth
|
|
209
|
+
r = scorer.score(labelTruth, labelPrediction)
|
|
210
|
+
# r = scorer.score(labelTruth.reshape(-1), labelPrediction.reshape(-1, numClass))
|
|
216
211
|
resultRow.update({f"{m}_{labelName}": v for m,v in r.items()})
|
|
217
212
|
|
|
218
213
|
# add examples
|
|
219
|
-
resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
|
|
214
|
+
# resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
|
|
220
215
|
|
|
221
216
|
|
|
222
217
|
recordData = metaData.query(f'recordId == "{recordId}"')
|
|
@@ -250,4 +245,6 @@ class Eval(Phase):
|
|
|
250
245
|
self.registerData("eventResults", eventResults)
|
|
251
246
|
|
|
252
247
|
def main(self):
|
|
253
|
-
self.getData("
|
|
248
|
+
segmentResultsRecords, segmentResults = self.getData("evalResults")
|
|
249
|
+
|
|
250
|
+
self.logSuccess(f"result segments: {segmentResults}")
|
|
@@ -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):
|
|
@@ -38,7 +37,7 @@ class EvalReport(Phase):
|
|
|
38
37
|
|
|
39
38
|
def createLogFolder(self, path):
|
|
40
39
|
Path(path).mkdir(parents=True, exist_ok=True)
|
|
41
|
-
Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
|
|
40
|
+
# Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
|
|
42
41
|
|
|
43
42
|
def addAsset(self, name, extension="png", bbox_inches="tight", dpi=300):
|
|
44
43
|
import matplotlib.pyplot as plt
|
|
@@ -178,22 +177,20 @@ class EvalReport(Phase):
|
|
|
178
177
|
|
|
179
178
|
def main(self):
|
|
180
179
|
import pandas as pd
|
|
181
|
-
from SleePyPhases.Plot import Plot
|
|
182
180
|
|
|
183
181
|
threshold = self.getConfig("fixedThreshold", False)
|
|
184
182
|
self.threshold = threshold or self.getData("threshold", float)
|
|
185
183
|
|
|
186
184
|
modelConfigString = self.project.getDataFromName("eventResults").getTagString()
|
|
187
|
-
evalPath = f"eval/{modelConfigString}/"
|
|
188
185
|
|
|
189
|
-
|
|
186
|
+
evalPath = self.getConfig("eval-path", "eval/")
|
|
187
|
+
evalPath = f"{evalPath}/{modelConfigString}/"
|
|
188
|
+
|
|
189
|
+
self.createLogFolder(evalPath)
|
|
190
|
+
|
|
190
191
|
self.evalPath = evalPath
|
|
191
|
-
self.reporter = Reporter("EvalReport")
|
|
192
|
-
self.reporter.createFolder()
|
|
193
|
-
# self.reporter.save()
|
|
194
192
|
|
|
195
193
|
self.copyRawData()
|
|
196
|
-
# self.modelInfo()
|
|
197
194
|
|
|
198
195
|
# self.log("save report to: %s" % self.reporter.getFilePath())
|
|
199
196
|
|
|
@@ -246,8 +243,8 @@ class EvalReport(Phase):
|
|
|
246
243
|
def copyRawData(self):
|
|
247
244
|
modelConfigString = self.project.getDataFromName("modelState").getTagString()
|
|
248
245
|
trainingLogPath = f"logs/{modelConfigString}/"
|
|
249
|
-
shutil.copyfile(trainingLogPath + "log.csv",
|
|
250
|
-
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")
|
|
251
248
|
|
|
252
249
|
def trainingPlot(self, logFile):
|
|
253
250
|
import matplotlib.pyplot as plt
|
|
@@ -257,11 +254,9 @@ class EvalReport(Phase):
|
|
|
257
254
|
df = pd.read_csv(logFile)
|
|
258
255
|
plt.plot(df["epoch"], df["loss"], label="Training Loss")
|
|
259
256
|
plt.plot(df["epoch"], df[self.metrics[0]], label=self.metrics[0])
|
|
260
|
-
self.
|
|
257
|
+
self.addAsset("trainingsProcess")
|
|
261
258
|
|
|
262
259
|
def modelInfo(self):
|
|
263
260
|
model = self.project.getPhase("Eval").getModel()
|
|
264
261
|
sum = model.summary()
|
|
265
|
-
|
|
266
|
-
self.reporter.addVar("parameter", model.parameter, "humannumber")
|
|
267
|
-
self.reporter.addVar("modelSummary", sum)
|
|
262
|
+
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,21 +205,19 @@ 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"]
|
|
212
|
+
forceCalculation = self.getConfig("featureExtraction.features", True)
|
|
208
213
|
|
|
209
214
|
reqFeatures = {f["name"]: True for f in self.getConfig("featureExtraction.features", [])}
|
|
210
215
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
self.logError(f"no features for {recordId}")
|
|
216
|
+
for recordId in tqdm(recordIds.iloc):
|
|
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
|
|
220
|
+
rows = self.getData("features", pd.DataFrame, recordId=recordId, **reqFeatures)
|
|
221
|
+
if len(rows) == 0:
|
|
222
|
+
self.logError(f"no features for {recordId}")
|
|
219
223
|
|
|
220
|
-
|
|
221
|
-
# get a single feature:
|
|
222
|
-
# feat = {"swa": True}
|
|
223
|
-
# x = self.getData("features", pd.DataFrame, recordId="shhs1-200002", channel="EEG", **feat)
|
|
@@ -4,7 +4,6 @@ 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):
|
|
@@ -17,21 +16,6 @@ class Setup(Phase):
|
|
|
17
16
|
|
|
18
17
|
defaultValidationbatchManipulation = self.getConfig("segmentManipulationEval", self.getConfig("segmentManipulation"))
|
|
19
18
|
self.setConfig("segmentManipulationEval", defaultValidationbatchManipulation)
|
|
20
|
-
|
|
21
|
-
# alice loader
|
|
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
19
|
|
|
36
20
|
preprocessingConfig = self.getConfig("preprocessing")
|
|
37
21
|
if "combineChannels" in preprocessingConfig:
|
|
@@ -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)
|
|
@@ -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.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -40,9 +40,6 @@ SleePyPhases/phases/Training.py
|
|
|
40
40
|
SleePyPhases/phases/Validation.py
|
|
41
41
|
SleePyPhases/phases/VisualizeConfig.py
|
|
42
42
|
SleePyPhases/phases/__init__.py
|
|
43
|
-
SleePyPhases/recordloaders/MedicalDB.py
|
|
44
|
-
SleePyPhases/recordloaders/RecordLoaderTSM.py
|
|
45
|
-
SleePyPhases/recordloaders/__init__.py
|
|
46
43
|
tests/__init__.py
|
|
47
44
|
tests/conftest.py
|
|
48
45
|
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.0"[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,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
|