SleePyPhases 0.8.1__tar.gz → 0.8.2__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.8.1 → sleepyphases-0.8.2}/PKG-INFO +1 -1
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/Plugin.py +2 -1
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/Eval.py +5 -31
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/EvalReport.py +16 -10
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/Extract.py +6 -2
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/ExtractFeatures.py +100 -12
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/Setup.py +25 -2
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/Training.py +33 -5
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases.egg-info/PKG-INFO +1 -1
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/setup.py +1 -1
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_Extract.py +1 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/LICENSE +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/README.md +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/DataManipulation.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/EventScorer.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/FeatureExtraction.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/MultiScorer.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/Plot.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/PreManipulation.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/Reporter.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/SignalPreprocessing.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/SleePyPhases.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/SleepMetaData.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/__init__.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/config.yaml +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/BuildDataset.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/DataAnalysis.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/DataReport.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/ExtractEvents.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/GatherMetadata.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/LogCleanup.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/LogOverview.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/Predict.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/TestRun.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/ThresholdOptimisation.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/Validation.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/VisualizeConfig.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases/phases/__init__.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases.egg-info/SOURCES.txt +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases.egg-info/dependency_links.txt +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases.egg-info/requires.txt +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/SleePyPhases.egg-info/top_level.txt +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/pyproject.toml +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/setup.cfg +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/__init__.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/conftest.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/__init__.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_BuildDataset.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_BuildDatasetFolded.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_BuildDatasetMetadataSync.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_BuildDatasetSegments.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_DataManipulation.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_Eval.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_EventScorer.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_PreManipulation.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_RecordProcessor.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_Setup.py +0 -0
- {sleepyphases-0.8.1 → sleepyphases-0.8.2}/tests/unit/test_SignalPreprocessing.py +0 -0
|
@@ -33,7 +33,7 @@ class Plugin(PluginAdapter):
|
|
|
33
33
|
"Setup": (Setup, ["dataversionmanager"]),
|
|
34
34
|
"Extract": (Extract, ["removedRecordIds", "data-processed", "data-features", "record-preprocessed"]),
|
|
35
35
|
"BuildDataset": (BuildDataset, ["dataset-training", "dataset-validation", "dataset-test", "dataset-bySplit", "record-data"]),
|
|
36
|
-
"Training": (Training, ["modelState", "modelStateConfig"]),
|
|
36
|
+
"Training": (Training, ["modelState", "modelStateConfig", "trainedModel"]),
|
|
37
37
|
"Validation": (Validation, []),
|
|
38
38
|
"ThresholdOptimisation": (ThresholdOptimisation, ["threshold", "validationResult"]),
|
|
39
39
|
"Eval": (Eval, ["evalResults", "eventResults"]),
|
|
@@ -56,6 +56,7 @@ class Plugin(PluginAdapter):
|
|
|
56
56
|
"dataversionmanager": ["data-processed", "datafold", "dataversion"],
|
|
57
57
|
"removedRecordIds": ["data-processed"],
|
|
58
58
|
"modelState": ["dataset-bySplit", "modelName", "model", "inputShape", "segmentManipulation", "trainingParameter", "fold"],
|
|
59
|
+
"trainedModel": ["dataset-bySplit", "modelName", "model", "inputShape", "segmentManipulation", "trainingParameter", "fold", "pretraining"],
|
|
59
60
|
"modelStateConfig": ["modelState"],
|
|
60
61
|
"threshold": ["modelState", "eventEval", "thresholdMetric", "fixedThreshold"],
|
|
61
62
|
"validationResult": ["modelState", "segmentManipulationEval", "thresholdMetric", "optimizeOn"],
|
|
@@ -16,30 +16,6 @@ class Eval(Phase):
|
|
|
16
16
|
allowTraining: bool = False
|
|
17
17
|
exampleCount: int = 3
|
|
18
18
|
|
|
19
|
-
def getModel(self) -> Model:
|
|
20
|
-
import torch
|
|
21
|
-
|
|
22
|
-
pretrainedPath = self.getConfig("pretraining.pretrainedModel")
|
|
23
|
-
|
|
24
|
-
if not Path(pretrainedPath).exists():
|
|
25
|
-
with self.project:
|
|
26
|
-
pretrainingConfig = self.getConfig("pretraining")
|
|
27
|
-
self.project.config["dataversion"] = {}
|
|
28
|
-
self.project.config.update(pretrainingConfig)
|
|
29
|
-
self.project.trigger("configChanged", None)
|
|
30
|
-
preModelState = self.project.getData("modelState", Model)
|
|
31
|
-
|
|
32
|
-
else:
|
|
33
|
-
deviceName = "cuda" if torch.cuda.is_available() else "cpu"
|
|
34
|
-
preModelState = torch.load(pretrainedPath, map_location=torch.device(deviceName))
|
|
35
|
-
|
|
36
|
-
# set pretrain
|
|
37
|
-
model = ModelManager.getModel(True)
|
|
38
|
-
model.build()
|
|
39
|
-
model.loadState(preModelState)
|
|
40
|
-
|
|
41
|
-
return model
|
|
42
|
-
|
|
43
19
|
def getTestMetadata(self):
|
|
44
20
|
if not bool(self.getConfig("evalOn", {})):
|
|
45
21
|
return self.getData("metadata", list)
|
|
@@ -95,10 +71,9 @@ class Eval(Phase):
|
|
|
95
71
|
def segmentEvaluation(self, name):
|
|
96
72
|
testData = self.getTestData()
|
|
97
73
|
self.setConfig("datasetSplits", list(self.getConfig("dataversion.split", {}).keys()))
|
|
98
|
-
model = self.
|
|
74
|
+
model = self.getData("trainedModel")
|
|
99
75
|
|
|
100
|
-
# setup data and scorer
|
|
101
|
-
|
|
76
|
+
# setup data and scorer
|
|
102
77
|
classNums = self.getConfig("numClasses", [2])
|
|
103
78
|
labelNames = self.getConfig("classification.labelNames")
|
|
104
79
|
ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
|
|
@@ -111,12 +86,11 @@ class Eval(Phase):
|
|
|
111
86
|
threshold = threshold or self.getData("threshold", list)
|
|
112
87
|
scorer.setThresholds(threshold)
|
|
113
88
|
|
|
114
|
-
manipulationAfterPredict = self.getConfig("manipulationAfterPredict",
|
|
89
|
+
manipulationAfterPredict = self.getConfig("manipulationAfterPredict", [])
|
|
115
90
|
da = DataManipulation.getInstance(manipulationAfterPredict, "test", self.project.config, threshold=threshold)
|
|
116
91
|
|
|
117
92
|
for data in tqdm(testData):
|
|
118
93
|
x, truth = data
|
|
119
|
-
|
|
120
94
|
prediction = model.predict(x, returnNumpy=False)
|
|
121
95
|
prediction, truth = da((prediction, truth))
|
|
122
96
|
|
|
@@ -162,7 +136,7 @@ class Eval(Phase):
|
|
|
162
136
|
with self.project:
|
|
163
137
|
self.project.config.update(self.getConfig("eventEval", {}))
|
|
164
138
|
|
|
165
|
-
evalMetrics = self.getConfig("eval.metrics")
|
|
139
|
+
evalMetrics = self.getConfig("eval.metrics", self.getConfig("trainingParameter.validationMetrics"))
|
|
166
140
|
classNums = self.getConfig("numClasses", [2])
|
|
167
141
|
labelNames = self.getConfig("classification.labelNames")
|
|
168
142
|
predictionNames = self.getConfig("classification.predictionSignals")
|
|
@@ -186,7 +160,7 @@ class Eval(Phase):
|
|
|
186
160
|
# metaData["indexArousal"] = metaData["indexArousal"].astype(float)
|
|
187
161
|
numClasses = self.getConfig("numClasses")
|
|
188
162
|
|
|
189
|
-
model = self.
|
|
163
|
+
model = self.getData("trainedModel")
|
|
190
164
|
|
|
191
165
|
resultRows = []
|
|
192
166
|
result = {}
|
|
@@ -177,11 +177,13 @@ class EvalReport(Phase):
|
|
|
177
177
|
|
|
178
178
|
def main(self):
|
|
179
179
|
import pandas as pd
|
|
180
|
+
eventEvalEnabled = bool(self.getConfig("eventEval", True))
|
|
180
181
|
|
|
181
182
|
threshold = self.getConfig("fixedThreshold", False)
|
|
182
183
|
self.threshold = threshold or self.getData("threshold", float)
|
|
183
184
|
|
|
184
|
-
|
|
185
|
+
resultDataName = "eventResults" if eventEvalEnabled else "evalResults"
|
|
186
|
+
modelConfigString = self.project.getDataFromName(resultDataName).getTagString()
|
|
185
187
|
|
|
186
188
|
evalPath = self.getConfig("eval-path", "eval/")
|
|
187
189
|
evalPath = f"{evalPath}/{modelConfigString}/"
|
|
@@ -206,13 +208,16 @@ class EvalReport(Phase):
|
|
|
206
208
|
df.to_csv(f"{evalPath}recordResultsSegment.csv", index=False)
|
|
207
209
|
|
|
208
210
|
self.logSuccess(f"result segments: {segmentResults}")
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
211
|
+
if eventEvalEnabled:
|
|
212
|
+
resultRows, result = self.getData("eventResults", list)
|
|
213
|
+
self.logSuccess(f"result events: {result}")
|
|
214
|
+
df = pd.DataFrame(resultRows)
|
|
215
|
+
else:
|
|
216
|
+
result = {}
|
|
212
217
|
|
|
213
218
|
|
|
214
219
|
metrics = self.getConfig("trainingParameter.validationMetrics")
|
|
215
|
-
evalMetrics = self.getConfig("eval.metrics")
|
|
220
|
+
evalMetrics = self.getConfig("eval.metrics", metrics)
|
|
216
221
|
labelNames = self.getConfig("classification.labelNames")
|
|
217
222
|
|
|
218
223
|
results = {}
|
|
@@ -225,12 +230,13 @@ class EvalReport(Phase):
|
|
|
225
230
|
if metricName in segmentResults:
|
|
226
231
|
results[f"{metricName}-seg"] = segmentResults[metricName]
|
|
227
232
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
if eventEvalEnabled:
|
|
234
|
+
relMetaData = self.getConfig("eval.clinicalMetrics")
|
|
235
|
+
for m in relMetaData:
|
|
236
|
+
df[f"{m}-prediction-diff"] = df[f"{m}-prediction"] - df[f"{m}-truth"]
|
|
237
|
+
df[f"{m}-prediction-error"] = df[f"{m}-prediction-diff"].abs()
|
|
232
238
|
|
|
233
|
-
|
|
239
|
+
df.to_csv(f"{evalPath}recordResultsEvents.csv", index=False)
|
|
234
240
|
|
|
235
241
|
self.logSuccess(f"Results: {results}")
|
|
236
242
|
results = {k: float(v) for k, v in results.items() if not isinstance(v, Iterable)}
|
|
@@ -51,6 +51,7 @@ class RecordProcessor:
|
|
|
51
51
|
if "sleepStage" not in eventSignal:
|
|
52
52
|
raise ChannelsNotPresent("sleepStage")
|
|
53
53
|
eventSignal = eventSignal["sleepStage"]
|
|
54
|
+
labelSignalArray[eventSignal == 0] = -1
|
|
54
55
|
labelSignalArray[eventSignal == PSGEventManager.INDEX_WAKE] = 0
|
|
55
56
|
labelSignalArray[eventSignal == PSGEventManager.INDEX_REM] = 1
|
|
56
57
|
labelSignalArray[eventSignal == PSGEventManager.INDEX_NREM1] = 2
|
|
@@ -170,8 +171,8 @@ class RecordProcessor:
|
|
|
170
171
|
recordSignal, events = self.recordLoader.loadRecord(recordId)
|
|
171
172
|
|
|
172
173
|
targetSignals = self.preProcessingConfig["targetChannels"] if "targetChannels" in self.preProcessingConfig else []
|
|
173
|
-
targetFrequency = self.preProcessingConfig["targetFrequency"] if "targetFrequency" in self.preProcessingConfig else
|
|
174
|
-
labelFrequency = self.preProcessingConfig["labelFrequency"] if "labelFrequency" in self.preProcessingConfig else
|
|
174
|
+
targetFrequency = self.preProcessingConfig["targetFrequency"] if "targetFrequency" in self.preProcessingConfig else 0
|
|
175
|
+
labelFrequency = self.preProcessingConfig["labelFrequency"] if "labelFrequency" in self.preProcessingConfig else targetFrequency
|
|
175
176
|
featureChannels = self.preProcessingConfig["featureChannels"] if "featureChannels" in self.preProcessingConfig else []
|
|
176
177
|
tailorToTargetSignals = self.preProcessingConfig["tailorToTargetSignals"] if "tailorToTargetSignals" in self.preProcessingConfig else False
|
|
177
178
|
recordSignal.targetFrequency = targetFrequency
|
|
@@ -188,11 +189,14 @@ class RecordProcessor:
|
|
|
188
189
|
self.featureExtraction.extractChannelsByConfig(recordSignal, featureChannels)
|
|
189
190
|
|
|
190
191
|
signalLength = round(recordSignal.getSignalLength() * labelFrequency / targetFrequency)
|
|
192
|
+
sleepEvents = [e for e in events if e.name in ["R", "N1", "N2", "N3", "W", "undefined"]]
|
|
193
|
+
lastDefaultEventDuration = sleepEvents[-1].duration if sleepEvents and sleepEvents[-1].duration > 0 else 30 * labelFrequency
|
|
191
194
|
eventSignal = self.eventManager.getEventSignalFromList(
|
|
192
195
|
events,
|
|
193
196
|
signalLength,
|
|
194
197
|
targetFrequency=labelFrequency,
|
|
195
198
|
forceGapBetweenEvents=False,
|
|
199
|
+
lastDefaultEventDuration=lastDefaultEventDuration,
|
|
196
200
|
)
|
|
197
201
|
# signalLength * 30
|
|
198
202
|
eventSignal = self.preparelabelChannels(eventSignal, signalLength)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
from pyPhases import Phase
|
|
3
|
-
from
|
|
3
|
+
from pyPhases.util import BatchProgress
|
|
4
4
|
import pandas as pd
|
|
5
5
|
|
|
6
6
|
from pyPhasesRecordloader import AnnotationNotFound, RecordLoader, ChannelsNotPresent, Event, ParseError
|
|
@@ -8,10 +8,62 @@ from pyPhasesRecordloader import AnnotationNotFound, RecordLoader, ChannelsNotPr
|
|
|
8
8
|
from SleePyPhases.RecordFeatureExtraction import RecordFeatureExtraction
|
|
9
9
|
from SleePyPhases.SleepMetaData import SleepMetaData
|
|
10
10
|
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RecordFeatureProcessor:
|
|
14
|
+
"""Processor class for multithreaded/multiprocess record feature extraction"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, featureConfigs, recordLoader, recordFeatureExtraction):
|
|
17
|
+
self.featureConfigs = featureConfigs
|
|
18
|
+
self.recordLoader = recordLoader
|
|
19
|
+
self.recordFeatureExtraction = recordFeatureExtraction
|
|
20
|
+
|
|
21
|
+
def __call__(self, recordId):
|
|
22
|
+
recordLoader = self.recordLoader
|
|
23
|
+
try:
|
|
24
|
+
recordSignal, eventlist = recordLoader.loadRecord(recordId)
|
|
25
|
+
except AnnotationNotFound as e:
|
|
26
|
+
print(f"\033[31;1;4m\033[0mAnnotation missing in {recordId}: {e.name} ... skipping record")
|
|
27
|
+
return None
|
|
28
|
+
except ChannelsNotPresent as e:
|
|
29
|
+
print(f"\033[31;1;4m\033[0mChannels missing in {recordId}: {e.channels} ... skipping record")
|
|
30
|
+
return None
|
|
31
|
+
except ParseError as e:
|
|
32
|
+
print(f"\033[31;1;4m\033[0mParseError in record {recordId}, record will be skipped: {e}")
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
features = pd.DataFrame(
|
|
36
|
+
index=pd.MultiIndex(levels=[[], []], codes=[[], []], names=["recordId", "channel"])
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
for featureConfig in self.featureConfigs:
|
|
40
|
+
checkedCol = featureConfig["name"] + "_checked"
|
|
41
|
+
channels = featureConfig["channels"]
|
|
42
|
+
for c in channels:
|
|
43
|
+
idx = (recordId, c)
|
|
44
|
+
try:
|
|
45
|
+
featureStep = featureConfig["type"]
|
|
46
|
+
self.recordFeatureExtraction.updateMetadataByStep(
|
|
47
|
+
featureStep, recordSignal, eventlist, metadata=features, channel=c
|
|
48
|
+
)
|
|
49
|
+
except ChannelsNotPresent as e:
|
|
50
|
+
print(f"\033[31;1;4m\033[0mChannels not present in {recordId}: {e.channels}")
|
|
51
|
+
|
|
52
|
+
features.loc[idx, "recordId"] = recordId
|
|
53
|
+
features.loc[idx, "channel"] = c
|
|
54
|
+
features.loc[idx, checkedCol] = True
|
|
55
|
+
|
|
56
|
+
return features
|
|
57
|
+
|
|
58
|
+
|
|
11
59
|
class ExtractFeatures(Phase):
|
|
12
60
|
allowTraining = False
|
|
13
61
|
features = None
|
|
14
62
|
|
|
63
|
+
useMultiThreading = True
|
|
64
|
+
spawnMultiprocessing = False
|
|
65
|
+
threads = None
|
|
66
|
+
|
|
15
67
|
def getChannelMetadata(self, recordId):
|
|
16
68
|
recordLoader = RecordLoader.get()
|
|
17
69
|
recordLoader.getSignalHeaders(recordId)
|
|
@@ -212,16 +264,52 @@ class ExtractFeatures(Phase):
|
|
|
212
264
|
recordIds = metadata["recordId"]
|
|
213
265
|
forceCalculation = self.getConfig("featureExtraction.force", False)
|
|
214
266
|
|
|
215
|
-
reqFeatures = {f["name"] + "_checked": True for f in self.getConfig("featureExtraction.features", [])}
|
|
216
|
-
|
|
217
267
|
recordId = self.getConfig("recordId", False)
|
|
218
|
-
recordIds = [recordId] if recordId else recordIds.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
268
|
+
recordIds = [recordId] if recordId else recordIds.tolist()
|
|
269
|
+
|
|
270
|
+
featureConfigs = self.getConfig("featureExtraction.features", [])
|
|
271
|
+
checkedCols = [f["name"] + "_checked" for f in featureConfigs]
|
|
272
|
+
|
|
273
|
+
self.features = self.getData("features", pd.DataFrame)
|
|
274
|
+
|
|
275
|
+
if not forceCalculation and len(self.features) > 0:
|
|
276
|
+
# a record is complete if all _checked columns are True for it
|
|
277
|
+
completedIds = set()
|
|
278
|
+
if all(col in self.features.columns for col in checkedCols):
|
|
279
|
+
byRecord = self.features.groupby(level="recordId")
|
|
280
|
+
for rid, group in byRecord:
|
|
281
|
+
if group[checkedCols].all(axis=None):
|
|
282
|
+
completedIds.add(rid)
|
|
283
|
+
|
|
284
|
+
pendingIds = [r for r in recordIds if r not in completedIds]
|
|
285
|
+
self.logSuccess(f"Skipping {len(recordIds) - len(pendingIds)} already-calculated records, {len(pendingIds)} to process")
|
|
286
|
+
else:
|
|
287
|
+
pendingIds = recordIds
|
|
288
|
+
|
|
289
|
+
if len(pendingIds) == 0:
|
|
290
|
+
self.logSuccess("All records already have features calculated")
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
recordLoader = RecordLoader.get()
|
|
294
|
+
recordFeatureExtraction = RecordFeatureExtraction.getInstance()
|
|
295
|
+
processor = RecordFeatureProcessor(featureConfigs, recordLoader, recordFeatureExtraction)
|
|
296
|
+
|
|
297
|
+
bp = BatchProgress(pendingIds)
|
|
298
|
+
bp.useMultiThreading = self.useMultiThreading
|
|
299
|
+
if self.threads is not None:
|
|
300
|
+
bp.threads = self.threads
|
|
301
|
+
if self.spawnMultiprocessing:
|
|
302
|
+
bp.startMethod = "spawn"
|
|
303
|
+
|
|
304
|
+
def combine(resultList, dataIndex):
|
|
305
|
+
for result in resultList:
|
|
306
|
+
if result is None:
|
|
307
|
+
continue
|
|
308
|
+
if len(result) > 0:
|
|
309
|
+
self.features = pd.concat([self.features, result])
|
|
310
|
+
else:
|
|
311
|
+
self.logError(f"no features for record at index {dataIndex}")
|
|
312
|
+
self.registerData("features", self.features)
|
|
313
|
+
|
|
314
|
+
bp.start(processor, afterBatch=combine)
|
|
227
315
|
|
|
@@ -28,6 +28,9 @@ class Setup(Phase):
|
|
|
28
28
|
# default fold is 0 if none is set
|
|
29
29
|
self.setConfig("fold", self.getConfig("fold", 0))
|
|
30
30
|
# self.setConfig("datasetSplits", list(self.getConfig("dataversion.split", {}).keys()))
|
|
31
|
+
namedFolds = self.getConfig("dataversion.namedFolds", False)
|
|
32
|
+
if namedFolds:
|
|
33
|
+
self.setConfig("dataversion.folds", len(namedFolds))
|
|
31
34
|
self.setConfig("hasFolds", self.getConfig("dataversion.folds", 0) > 0)
|
|
32
35
|
|
|
33
36
|
s = Scorer(5)
|
|
@@ -75,13 +78,33 @@ class Setup(Phase):
|
|
|
75
78
|
|
|
76
79
|
dm = DataversionManager(groupedRecords, seed=seed)
|
|
77
80
|
|
|
81
|
+
# named fold presets: dataversion.namedFolds resolved by name (foldName) or integer index (fold)
|
|
82
|
+
namedFolds = self.getConfig("dataversion.namedFolds", False)
|
|
83
|
+
if namedFolds:
|
|
84
|
+
foldNames = list(namedFolds.keys())
|
|
85
|
+
foldName = self.getConfig("foldName", False)
|
|
86
|
+
if not foldName:
|
|
87
|
+
foldIndex = self.getConfig("fold", 0)
|
|
88
|
+
if foldIndex >= len(foldNames):
|
|
89
|
+
raise Exception("fold index %i out of range for namedFolds (len %i)" % (foldIndex, len(foldNames)))
|
|
90
|
+
foldName = foldNames[foldIndex]
|
|
91
|
+
elif foldName not in namedFolds:
|
|
92
|
+
raise Exception("foldName '%s' not found in dataversion.namedFolds. Available: %s" % (foldName, foldNames))
|
|
93
|
+
foldConfig = namedFolds[foldName]
|
|
94
|
+
self.log(f"Using named fold '{foldName}'")
|
|
95
|
+
for splitName in ["training", "validation", "test"]:
|
|
96
|
+
if splitName in foldConfig:
|
|
97
|
+
dm.addSplitBySlices(splitName, foldConfig[splitName])
|
|
98
|
+
dm.validatDatasetVersion(raiseException=self.getConfig("validatDataset", True))
|
|
99
|
+
return dm
|
|
100
|
+
|
|
78
101
|
# add manual addes splits
|
|
79
102
|
for splitName in ["training", "validation", "test"]:
|
|
80
103
|
if splitName in splits:
|
|
81
104
|
dm.addSplitBySlices(splitName, splits[splitName])
|
|
82
105
|
|
|
83
106
|
# add training folding splits
|
|
84
|
-
if "trainval" in splits:
|
|
107
|
+
if "trainval" in splits and splits["trainval"] is not None:
|
|
85
108
|
foldcount = self.getConfig("dataversion.folds", 0)
|
|
86
109
|
if foldcount > 0:
|
|
87
110
|
dm.addSplitsByFold("training", "validation", splits["trainval"], self.getConfig("dataversion.folds", 0), self.getConfig("fold", 0))
|
|
@@ -92,7 +115,7 @@ class Setup(Phase):
|
|
|
92
115
|
if "training" in splits or "validation" in splits:
|
|
93
116
|
raise Exception("trainval and training/validation split are mutually exclusive. Please remove one of them from the config. This can be caused by loading multiple configs with different splits.")
|
|
94
117
|
|
|
95
|
-
if "trainvaltest" in splits:
|
|
118
|
+
if "trainvaltest" in splits and splits["trainvaltest"] is not None:
|
|
96
119
|
foldCount = self.getConfig("dataversion.folds")
|
|
97
120
|
currentFold = self.getConfig("fold", 0)
|
|
98
121
|
testSlice, trainingSlice = dm.getSplitsByPercentage(
|
|
@@ -57,9 +57,37 @@ class Training(Phase):
|
|
|
57
57
|
return trainedModel
|
|
58
58
|
|
|
59
59
|
def generateData(self, name):
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
if name == "modelState":
|
|
61
|
+
trainedModel = self.train()
|
|
62
|
+
self.project.registerData("modelState", trainedModel)
|
|
63
|
+
elif name == "modelStateConfig":
|
|
64
|
+
self.project.registerData("modelStateConfig", self.project.getDataFromName("modelState").getDependencyDict())
|
|
65
|
+
elif name == "trainedModel":
|
|
66
|
+
|
|
67
|
+
model = ModelManager.getModel(True)
|
|
68
|
+
pretrainingConfig = self.getConfig("pretraining", {})
|
|
69
|
+
|
|
70
|
+
pretrainedPath = pretrainingConfig.get("pretrainedModel", False)
|
|
71
|
+
preModelState = None
|
|
72
|
+
|
|
73
|
+
if pretrainedPath:
|
|
74
|
+
if not Path(pretrainedPath).exists():
|
|
75
|
+
err = f"model path for evaluation does not exist (pretraining.pretrainedModel): {pretrainedPath}"
|
|
76
|
+
self.logError(err)
|
|
77
|
+
raise Exception(err)
|
|
78
|
+
else:
|
|
79
|
+
preModelState = model.load(pretrainedPath)
|
|
80
|
+
|
|
81
|
+
if preModelState is None:
|
|
82
|
+
with self.project:
|
|
83
|
+
self.project.config["dataversion"] = {}
|
|
84
|
+
self.project.config.update(pretrainingConfig)
|
|
85
|
+
self.project.trigger("configChanged", None)
|
|
86
|
+
preModelState = self.project.getData("modelState", Model)
|
|
87
|
+
|
|
88
|
+
model.build()
|
|
89
|
+
model.loadState(preModelState)
|
|
90
|
+
return model
|
|
63
91
|
|
|
64
92
|
def main(self):
|
|
65
93
|
startfold = self.getConfig("startFold", 0)
|
|
@@ -88,9 +116,9 @@ class Training(Phase):
|
|
|
88
116
|
self.project.unregister("modelStateConfig")
|
|
89
117
|
gridOutputs.append(self.gridOutput)
|
|
90
118
|
# reset model state
|
|
91
|
-
ModelManager.getModel().
|
|
119
|
+
ModelManager.getModel().resetParameters()
|
|
92
120
|
|
|
93
|
-
self.project.gridOutput
|
|
121
|
+
self.project.gridOutput= {
|
|
94
122
|
"epochs": str(np.mean([m["epochs"] for m in gridOutputs])),
|
|
95
123
|
"best": str(np.mean([m["best"] for m in gridOutputs])),
|
|
96
124
|
"folds": len(gridOutputs),
|
|
@@ -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.
|
|
8
|
+
version="v0.8.2"[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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|