SleePyPhases 0.6.2__tar.gz → 0.7.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.6.2 → sleepyphases-0.7.0}/PKG-INFO +1 -1
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/MultiScorer.py +27 -11
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/Plugin.py +2 -0
- sleepyphases-0.7.0/SleePyPhases/SleePyPhases.py +34 -0
- sleepyphases-0.7.0/SleePyPhases/config.yaml +167 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/BuildDataset.py +18 -23
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/Extract.py +7 -7
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/Predict.py +8 -12
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/Setup.py +2 -2
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases.egg-info/PKG-INFO +1 -1
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases.egg-info/SOURCES.txt +3 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/setup.py +1 -1
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/__init__.py +11 -0
- sleepyphases-0.7.0/tests/unit/test_BuildDatasetMetadataSync.py +261 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_DataManipulation.py +1 -1
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/LICENSE +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/README.md +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/DataManipulation.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/EventScorer.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/FeatureExtraction.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/Plot.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/PreManipulation.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/Reporter.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/SignalPreprocessing.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/SleepMetaData.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/__init__.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/DataAnalysis.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/DataReport.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/Eval.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/EvalReport.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/ExtractEvents.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/ExtractFeatures.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/GatherMetadata.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/LogCleanup.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/LogOverview.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/TestRun.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/ThresholdOptimisation.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/Training.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/Validation.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/VisualizeConfig.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases/phases/__init__.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases.egg-info/dependency_links.txt +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases.egg-info/requires.txt +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/SleePyPhases.egg-info/top_level.txt +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/pyproject.toml +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/setup.cfg +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/conftest.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/__init__.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_BuildDataset.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_BuildDatasetFolded.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_BuildDatasetSegments.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_Eval.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_EventScorer.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_Extract.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_PreManipulation.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_RecordFeatureExtraction.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_RecordProcessor.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_Setup.py +0 -0
- {sleepyphases-0.6.2 → sleepyphases-0.7.0}/tests/unit/test_SignalPreprocessing.py +0 -0
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
from pyPhasesML.scorer.Scorer import Scorer
|
|
3
3
|
|
|
4
4
|
class MultiScorer(Scorer):
|
|
5
|
-
def __init__(self, numClasses, metrics, scorerNames=[], ignoreClasses=None, classNames=None, scorerClasses=None) -> None:
|
|
5
|
+
def __init__(self, numClasses, metrics, scorerNames=[], ignoreClasses=None, classNames=None, scorerClasses=None, multilabel=None) -> None:
|
|
6
6
|
|
|
7
7
|
ignoreClasses = ignoreClasses or [-1]
|
|
8
8
|
|
|
@@ -13,11 +13,17 @@ class MultiScorer(Scorer):
|
|
|
13
13
|
self.scorerNames = scorerNames
|
|
14
14
|
self.metricDefinitions = {}
|
|
15
15
|
|
|
16
|
+
# Normalise multilabel to a per-scorer list
|
|
17
|
+
if multilabel is None:
|
|
18
|
+
multilabel = [False] * len(numClasses)
|
|
19
|
+
elif isinstance(multilabel, bool):
|
|
20
|
+
multilabel = [multilabel] * len(numClasses)
|
|
21
|
+
|
|
16
22
|
metrics = metrics if isinstance(metrics[0], list) else [metrics for _ in range(len(numClasses))]
|
|
17
23
|
|
|
18
24
|
for i, (numClasses, scorerName) in enumerate(zip(numClasses, scorerNames)):
|
|
19
25
|
scorerClass = scorerClasses[i] if scorerClasses is not None else Scorer
|
|
20
|
-
s = scorerClass(numClasses)
|
|
26
|
+
s = scorerClass(numClasses, multilabel=multilabel[i])
|
|
21
27
|
s.trace = True
|
|
22
28
|
s.ignoreClasses = ignoreClasses
|
|
23
29
|
s.numClasses = numClasses if numClasses > 1 else 2
|
|
@@ -96,29 +102,39 @@ class MultiScorer(Scorer):
|
|
|
96
102
|
|
|
97
103
|
return scorer.score(truth, prediction, trace=trace)
|
|
98
104
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
def _print2DConfusionMatrix(self, cm):
|
|
106
|
+
cm = np.array(cm)
|
|
107
|
+
num_rows, num_cols = cm.shape
|
|
108
|
+
max_value_length = max(len(str(int(cm.max()))), len(str(int(cm.min()))))
|
|
109
|
+
separator = "-" * ((max_value_length + 3) * num_cols)
|
|
104
110
|
rows = []
|
|
105
111
|
for i in range(num_rows):
|
|
106
|
-
row_data = [f"{int(
|
|
112
|
+
row_data = [f"{int(cm[i, j]):>{max_value_length}}" for j in range(num_cols)]
|
|
107
113
|
rows.append(" | ".join(row_data))
|
|
108
|
-
|
|
109
114
|
print(separator)
|
|
110
115
|
print("\n".join(rows))
|
|
111
116
|
print(separator)
|
|
117
|
+
|
|
118
|
+
def prettyPrintConfusionMatrix(self, confusion_matrix, classNames=None):
|
|
119
|
+
cm = np.array(confusion_matrix)
|
|
120
|
+
if cm.ndim == 3:
|
|
121
|
+
# multilabel: shape (n_classes, 2, 2)
|
|
122
|
+
for i, cm_i in enumerate(cm):
|
|
123
|
+
label = classNames[i] if classNames is not None else f"Class {i}"
|
|
124
|
+
print(f" [{label}]")
|
|
125
|
+
self._print2DConfusionMatrix(cm_i)
|
|
126
|
+
else:
|
|
127
|
+
self._print2DConfusionMatrix(cm)
|
|
112
128
|
|
|
113
129
|
def scoreAllRecords(self):
|
|
114
130
|
results = {}
|
|
115
131
|
self.recordResult = {}
|
|
116
132
|
for name, s in self.scorer.items():
|
|
117
133
|
r = s.scoreAllRecords()
|
|
118
|
-
results.update({f"{m}_{name}": v for m,v in r.items()})
|
|
134
|
+
results.update({f"{m}_{name}": v for m, v in r.items()})
|
|
119
135
|
if "confusion" in r:
|
|
120
136
|
print(f"######{name}########")
|
|
121
|
-
self.prettyPrintConfusionMatrix(r["confusion"] )
|
|
137
|
+
self.prettyPrintConfusionMatrix(r["confusion"], classNames=s.classNames)
|
|
122
138
|
self.recordResult[name] = s.recordResult
|
|
123
139
|
self.results = results
|
|
124
140
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from phases import DefaultProject
|
|
2
|
+
from pyPhases import Project
|
|
3
|
+
from pyPhasesRecordloader import RecordSignal
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SleePyPhases(Project):
|
|
7
|
+
"""
|
|
8
|
+
This is the main class for the SleePyPhases project. It defines the phases that will be executed in the project.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def create(configFiles=None, plugins=None):
|
|
13
|
+
if plugins is None:
|
|
14
|
+
plugins = []
|
|
15
|
+
|
|
16
|
+
defaultPlugins = [
|
|
17
|
+
"SleePyPhases",
|
|
18
|
+
"pyPhasesML",
|
|
19
|
+
"pyPhasesRecordloader",
|
|
20
|
+
]
|
|
21
|
+
project = DefaultProject.create(configFiles, plugins + defaultPlugins, project=SleePyPhases())
|
|
22
|
+
|
|
23
|
+
return project
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def predictRecordSignal(self, recordSignal: RecordSignal):
|
|
27
|
+
from SleePyPhases.phases.Predict import Predict
|
|
28
|
+
predict: Predict = self.getPhase("Predict")
|
|
29
|
+
return predict.predictRecordSignal(recordSignal)
|
|
30
|
+
|
|
31
|
+
def predictFromFile(self, filepath: str):
|
|
32
|
+
from SleePyPhases.phases.Predict import Predict
|
|
33
|
+
predict: Predict = self.getPhase("Predict")
|
|
34
|
+
return predict.predict(filepath)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# isFullConfig: True
|
|
2
|
+
|
|
3
|
+
# phases:
|
|
4
|
+
# - name: Setup
|
|
5
|
+
# description: Prepare the project and compute neccessary config values
|
|
6
|
+
# exports:
|
|
7
|
+
# - dataversionmanager
|
|
8
|
+
# - name: Extract
|
|
9
|
+
# description: Preprocess and save the raw data
|
|
10
|
+
# exports:
|
|
11
|
+
# - removedRecordIds
|
|
12
|
+
# - data-processed
|
|
13
|
+
# - data-features
|
|
14
|
+
# - record-preprocessed
|
|
15
|
+
# - name: BuildDataset
|
|
16
|
+
# description: Combine datasets and manipulate the data live for the model
|
|
17
|
+
# exports:
|
|
18
|
+
# # - dataset-trainval
|
|
19
|
+
# - dataset-training
|
|
20
|
+
# - dataset-validation
|
|
21
|
+
# - dataset-test
|
|
22
|
+
# - dataset-bySplit
|
|
23
|
+
# - data-records
|
|
24
|
+
# - record-data
|
|
25
|
+
# - name: Training
|
|
26
|
+
# description: Train my model
|
|
27
|
+
# exports:
|
|
28
|
+
# - modelState
|
|
29
|
+
# - modelStateConfig
|
|
30
|
+
# - name: Validation
|
|
31
|
+
# description: Run Model on validation data for optimization
|
|
32
|
+
# - name: ThresholdOptimisation
|
|
33
|
+
# description: Threshold optimisation by optimising the threshold metric on validation data
|
|
34
|
+
# exports:
|
|
35
|
+
# - threshold
|
|
36
|
+
# - validationResult
|
|
37
|
+
# - name: Eval
|
|
38
|
+
# description: Evalutate my model against the test data
|
|
39
|
+
# exports:
|
|
40
|
+
# - evalResults
|
|
41
|
+
# - eventResults
|
|
42
|
+
# - name: EvalReport
|
|
43
|
+
# description: generate evaluation report
|
|
44
|
+
# - name: DataAnalysis
|
|
45
|
+
# description: Get class distribution and other statistics
|
|
46
|
+
# exports:
|
|
47
|
+
# - datasetstats
|
|
48
|
+
# - name: DataReport
|
|
49
|
+
# - name: ExtractEvents
|
|
50
|
+
# exports:
|
|
51
|
+
# - events
|
|
52
|
+
# - eventDF
|
|
53
|
+
# - name: ExportMetadataToMeticalDB
|
|
54
|
+
# - name: ExtractFeatures
|
|
55
|
+
# exports:
|
|
56
|
+
# - features
|
|
57
|
+
# - name: Predict
|
|
58
|
+
# description: Execute trained model on a given EDF file for prediction
|
|
59
|
+
# exports:
|
|
60
|
+
# - prediction
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# data:
|
|
64
|
+
# # - name: data-records
|
|
65
|
+
# - name: features
|
|
66
|
+
# dependsOn:
|
|
67
|
+
# - metadata
|
|
68
|
+
# - name: events
|
|
69
|
+
# dependsOn:
|
|
70
|
+
# - allDBRecordIds
|
|
71
|
+
# - name: eventDF
|
|
72
|
+
# dependsOn:
|
|
73
|
+
# - allDBRecordIds
|
|
74
|
+
# - name: data-processed
|
|
75
|
+
# dependsOn:
|
|
76
|
+
# - allDBRecordIds
|
|
77
|
+
# - preprocessing
|
|
78
|
+
# - labelChannels
|
|
79
|
+
# - name: record-preprocessed
|
|
80
|
+
# dependsOn:
|
|
81
|
+
# - recordId
|
|
82
|
+
# - data-processed
|
|
83
|
+
# - name: record-data
|
|
84
|
+
# - name: data-features
|
|
85
|
+
# dependsOn:
|
|
86
|
+
# - data-processed
|
|
87
|
+
# - name: dataversionmanager
|
|
88
|
+
# dependsOn:
|
|
89
|
+
# - data-processed
|
|
90
|
+
# - datafold
|
|
91
|
+
# - dataversion
|
|
92
|
+
# - name: removedRecordIds
|
|
93
|
+
# dependsOn:
|
|
94
|
+
# - data-processed
|
|
95
|
+
# - name: modelState
|
|
96
|
+
# dependsOn:
|
|
97
|
+
# - dataset-bySplit
|
|
98
|
+
# - modelName
|
|
99
|
+
# - model
|
|
100
|
+
# - inputShape
|
|
101
|
+
# - segmentManipulation
|
|
102
|
+
# - trainingParameter
|
|
103
|
+
# - fold
|
|
104
|
+
# - name: modelStateConfig
|
|
105
|
+
# dependsOn:
|
|
106
|
+
# - modelState
|
|
107
|
+
# - name: threshold
|
|
108
|
+
# dependsOn:
|
|
109
|
+
# - modelState
|
|
110
|
+
# - eventEval
|
|
111
|
+
# - thresholdMetric
|
|
112
|
+
# - fixedThreshold
|
|
113
|
+
# - name: validationResult
|
|
114
|
+
# dependsOn:
|
|
115
|
+
# - modelState
|
|
116
|
+
# - segmentManipulationEval
|
|
117
|
+
# - thresholdMetric
|
|
118
|
+
# - optimizeOn
|
|
119
|
+
# - name: evalResults
|
|
120
|
+
# dependsOn:
|
|
121
|
+
# - threshold
|
|
122
|
+
# - evalOn
|
|
123
|
+
# - manipulationAfterPredict
|
|
124
|
+
# - segmentManipulationEval # is applied to the test dataset
|
|
125
|
+
# - name: eventResults
|
|
126
|
+
# dependsOn:
|
|
127
|
+
# - evalResults
|
|
128
|
+
# - eventEval
|
|
129
|
+
# - eval.clinicalMetrics
|
|
130
|
+
# - name: dataset-bySplit
|
|
131
|
+
# dependsOn:
|
|
132
|
+
# # does not require the split, since its encoded in the name and will not be saved
|
|
133
|
+
# - dataversionmanager
|
|
134
|
+
# - name: dataset-training
|
|
135
|
+
# dependsOn:
|
|
136
|
+
# - dataversionmanager
|
|
137
|
+
# - name: dataset-validation
|
|
138
|
+
# dependsOn:
|
|
139
|
+
# - dataversionmanager
|
|
140
|
+
# # - name: dataset-trainval
|
|
141
|
+
# # dependsOn:
|
|
142
|
+
# # - dataset-bySplit
|
|
143
|
+
# # - labelChannels
|
|
144
|
+
# # - fold
|
|
145
|
+
# - name: dataset-test
|
|
146
|
+
# dependsOn:
|
|
147
|
+
# - dataversionmanager
|
|
148
|
+
# - evalOn
|
|
149
|
+
# - name: datasetstats
|
|
150
|
+
# dependsOn:
|
|
151
|
+
# - data-processed
|
|
152
|
+
# - data-features
|
|
153
|
+
# - name: prediction
|
|
154
|
+
# dependsOn:
|
|
155
|
+
# - predict.inputFile
|
|
156
|
+
# - predict.weights
|
|
157
|
+
# - predict.recordLoader
|
|
158
|
+
# - predict.channelMapping
|
|
159
|
+
|
|
160
|
+
# config:
|
|
161
|
+
|
|
162
|
+
predict:
|
|
163
|
+
inputFile: null
|
|
164
|
+
channelMapping: {}
|
|
165
|
+
weights: null
|
|
166
|
+
|
|
167
|
+
inputShape: null
|
|
@@ -20,10 +20,11 @@ class BuildDataset(Phase):
|
|
|
20
20
|
splitName = self.getConfig("datasetSplit")
|
|
21
21
|
|
|
22
22
|
datasetVersions = self.getConfig("datafold.datasets", {"config": {}})
|
|
23
|
-
datasetVersions = datasetVersions if isinstance(datasetVersions, list) else [
|
|
23
|
+
datasetVersions = datasetVersions if isinstance(datasetVersions, list) else [{"config": {}}]
|
|
24
24
|
|
|
25
25
|
seed = self.getConfig("datafold.seed", 2024)
|
|
26
|
-
|
|
26
|
+
shouldShuffle = self.getConfig("datafold.shuffle", False)
|
|
27
|
+
multiSrcMap = MultiSourceMap(seed=seed)
|
|
27
28
|
recordMetadata = []
|
|
28
29
|
for sourceVersion in datasetVersions:
|
|
29
30
|
overwriteConfig = sourceVersion["config"]
|
|
@@ -46,7 +47,7 @@ class BuildDataset(Phase):
|
|
|
46
47
|
|
|
47
48
|
recordIds = dm.getRecordsForSplit(splitName)
|
|
48
49
|
recordIndexes = [allRecordIds.index(r) for r in recordIds]
|
|
49
|
-
recordMetadata =
|
|
50
|
+
recordMetadata = [metadataMap[r] for r in allRecordIds] # unshuffled
|
|
50
51
|
|
|
51
52
|
# handle segment wise data
|
|
52
53
|
recordLengthsInSegments = None
|
|
@@ -67,40 +68,34 @@ class BuildDataset(Phase):
|
|
|
67
68
|
)
|
|
68
69
|
|
|
69
70
|
dataset = DatasetXY(dataExporterSignals, dataExporterFeatures)
|
|
70
|
-
recordSet = RecordMap(dataset, recordIndexes, mappingLengths=recordLengthsInSegments)
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
useConfigName = "segmentManipulationEval" if splitName != "training" else "segmentManipulation"
|
|
73
|
+
manipulationSteps = self.getConfig(useConfigName)
|
|
74
|
+
segmentManipulation = DataManipulation.getInstance(
|
|
75
|
+
manipulationSteps, splitName, self.project.config, recordMetadata=recordMetadata
|
|
76
|
+
)
|
|
73
77
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
# use same seed to shuffle the metadata
|
|
78
|
-
np.random.seed(seed)
|
|
79
|
-
np.random.shuffle(recordMetadata)
|
|
78
|
+
manipulator = Manipulator(dataset, segmentManipulation)
|
|
79
|
+
|
|
80
|
+
recordSet = RecordMap(manipulator, recordIndexes, mappingLengths=recordLengthsInSegments)
|
|
80
81
|
|
|
81
|
-
|
|
82
|
+
multiSrcMap.addRecordMap(recordSet)
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
if shouldShuffle:
|
|
85
|
+
multiSrcMap.shuffle()
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
return multiSrcMap
|
|
87
88
|
|
|
88
|
-
useConfigName = "segmentManipulationEval" if split != "training" else "segmentManipulation"
|
|
89
|
-
manipulationSteps = self.getConfig(useConfigName)
|
|
90
|
-
segmentManipulation = DataManipulation.getInstance(
|
|
91
|
-
manipulationSteps, split, self.project.config, recordMetadata=recordMetaData
|
|
92
|
-
)
|
|
93
|
-
return Manipulator(dataRecords, segmentManipulation)
|
|
94
89
|
|
|
95
90
|
def buildDatasetBySplit(self):
|
|
96
91
|
split = self.getConfig("datasetSplit")
|
|
97
92
|
batchSize = self.getConfig("trainingParameter.batchSize") if split != "test" else self.getConfig("eval.batchSize")
|
|
98
93
|
buildConfig = self.getConfig("BuildDataset", {"useMultiThreading": True})
|
|
99
94
|
|
|
100
|
-
|
|
95
|
+
datasets = self.buildDataRecords()
|
|
101
96
|
|
|
102
97
|
dataloader = DataLoader.build(
|
|
103
|
-
|
|
98
|
+
datasets,
|
|
104
99
|
preload=buildConfig["useMultiThreading"],
|
|
105
100
|
batchSize=batchSize,
|
|
106
101
|
shuffle=self.getConfig("trainingParameter.shuffle", False),
|
|
@@ -93,7 +93,7 @@ class RecordProcessor:
|
|
|
93
93
|
signal = signal.reshape(-1, 1)
|
|
94
94
|
labelChannels.append(signal)
|
|
95
95
|
|
|
96
|
-
return np.concatenate(labelChannels, axis=1)
|
|
96
|
+
return np.concatenate(labelChannels, axis=1) if labelChannels else np.zeros((0, 0))
|
|
97
97
|
|
|
98
98
|
@staticmethod
|
|
99
99
|
def tailorToSleepScoring(recordSignal, events, useSPT=False, fillLastSleepstage=False, addOffset=None, cutToAnnotation=False):
|
|
@@ -169,10 +169,10 @@ class RecordProcessor:
|
|
|
169
169
|
try:
|
|
170
170
|
recordSignal, events = self.recordLoader.loadRecord(recordId)
|
|
171
171
|
|
|
172
|
-
targetSignals = self.preProcessingConfig["targetChannels"]
|
|
173
|
-
targetFrequency = self.preProcessingConfig["targetFrequency"]
|
|
174
|
-
labelFrequency = self.preProcessingConfig["labelFrequency"]
|
|
175
|
-
featureChannels = self.preProcessingConfig["featureChannels"]
|
|
172
|
+
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 []
|
|
175
|
+
featureChannels = self.preProcessingConfig["featureChannels"] if "featureChannels" in self.preProcessingConfig else []
|
|
176
176
|
recordSignal.targetFrequency = targetFrequency
|
|
177
177
|
|
|
178
178
|
# tailor to targetsignals
|
|
@@ -233,7 +233,7 @@ class Extract(Phase):
|
|
|
233
233
|
preProcessingConfig=preprocessingConfig,
|
|
234
234
|
signalProcessing=SignalPreprocessing.getInstance(preprocessingConfig),
|
|
235
235
|
eventManager=PSGEventManager(),
|
|
236
|
-
labelChannels=self.getConfig("labelChannels"),
|
|
236
|
+
labelChannels=self.getConfig("labelChannels", []),
|
|
237
237
|
featureExtraction=featureExtraction,
|
|
238
238
|
preDataManipulation=preDataManipulation,
|
|
239
239
|
project=self.project,
|
|
@@ -243,7 +243,7 @@ class Extract(Phase):
|
|
|
243
243
|
def extractRecord(self, recordId):
|
|
244
244
|
|
|
245
245
|
processRecord = self.getRecordProcessor()
|
|
246
|
-
result = processRecord(recordId)
|
|
246
|
+
result = processRecord(recordId)
|
|
247
247
|
if result is None:
|
|
248
248
|
raise ChannelsNotPresent(processRecord.channelNotPresent)
|
|
249
249
|
return result
|
|
@@ -98,7 +98,7 @@ class Predict(Phase):
|
|
|
98
98
|
# Apply channel mapping if provided
|
|
99
99
|
if channelMapping:
|
|
100
100
|
recordLoader.chanelNameAliasMap = {v: k for k, v in channelMapping.items()}
|
|
101
|
-
|
|
101
|
+
recordLoader.targetSignals = expectedChannels
|
|
102
102
|
try:
|
|
103
103
|
recordSignal = recordLoader.loadSignal(inputFile)
|
|
104
104
|
except ChannelsNotPresent as e:
|
|
@@ -170,26 +170,22 @@ class Predict(Phase):
|
|
|
170
170
|
def predictRecordSignal(self, recordSignal, dummyY=None):
|
|
171
171
|
processedSignal = self.preprocessSignal(recordSignal)
|
|
172
172
|
|
|
173
|
-
# # Prepare input for model
|
|
174
|
-
# if len(processedSignal.shape) == 2:
|
|
175
|
-
# processedSignal = processedSignal.reshape([1] + list(processedSignal.shape))
|
|
176
|
-
|
|
177
|
-
# Apply segment manipulation for evaluation
|
|
178
|
-
manipulationSteps = self.getConfig("segmentManipulationEval", [])
|
|
179
|
-
da = DataManipulation.getInstance(manipulationSteps, "test", self.project.config)
|
|
180
|
-
dummyY = np.array([0]).reshape(1, -1, 1) if dummyY is None else dummyY
|
|
181
|
-
processedSignal, dummyY = da((processedSignal, dummyY)) # Dummy label
|
|
182
|
-
|
|
183
173
|
with self.project:
|
|
184
174
|
overwriteConfig = self.getConfig("predict", {})
|
|
185
175
|
self.project.updateConfig(overwriteConfig)
|
|
186
176
|
|
|
177
|
+
# Apply segment manipulation for evaluation
|
|
178
|
+
manipulationSteps = self.getConfig("segmentManipulationEval", [])
|
|
179
|
+
da = DataManipulation.getInstance(manipulationSteps, "test", self.project.config)
|
|
180
|
+
dummyY = np.array([0]).reshape(1, -1, 1) if dummyY is None else dummyY
|
|
181
|
+
processedSignal, dummyY = da((processedSignal, dummyY)) # Dummy label
|
|
182
|
+
|
|
187
183
|
# Load model with optional custom weights
|
|
188
184
|
weightsPath = self.getConfig("predict.weights", False)
|
|
189
185
|
model = self.getModel(weightsPath)
|
|
190
186
|
|
|
191
187
|
# Run prediction
|
|
192
|
-
prediction = model.predict(processedSignal, returnNumpy=
|
|
188
|
+
prediction = model.predict(processedSignal, returnNumpy=False)
|
|
193
189
|
|
|
194
190
|
# Apply post-prediction manipulation if configured
|
|
195
191
|
manipulationAfterPredict = self.getConfig("manipulationAfterPredict", False)
|
|
@@ -71,7 +71,7 @@ class Setup(Phase):
|
|
|
71
71
|
|
|
72
72
|
# create non manual splits, if not specified
|
|
73
73
|
valSplit = self.getConfig("validationSplit", 0.2)
|
|
74
|
-
testSplit = self.getConfig("testSplit", 0
|
|
74
|
+
testSplit = self.getConfig("testSplit", 0)
|
|
75
75
|
|
|
76
76
|
dm = DataversionManager(groupedRecords, seed=seed)
|
|
77
77
|
|
|
@@ -104,7 +104,7 @@ class Setup(Phase):
|
|
|
104
104
|
dm.addSplitByRemaining("validation", valSplit) # , remainingSplit=trainingSlice
|
|
105
105
|
dm.addSplitByRemaining("training", 1)
|
|
106
106
|
|
|
107
|
-
if "test" not in dm.splits:
|
|
107
|
+
if "test" not in dm.splits and testSplit > 0:
|
|
108
108
|
dm.addSplitByRemaining("test", testSplit)
|
|
109
109
|
|
|
110
110
|
if "validation" not in dm.splits:
|
|
@@ -12,8 +12,10 @@ SleePyPhases/PreManipulation.py
|
|
|
12
12
|
SleePyPhases/RecordFeatureExtraction.py
|
|
13
13
|
SleePyPhases/Reporter.py
|
|
14
14
|
SleePyPhases/SignalPreprocessing.py
|
|
15
|
+
SleePyPhases/SleePyPhases.py
|
|
15
16
|
SleePyPhases/SleepMetaData.py
|
|
16
17
|
SleePyPhases/__init__.py
|
|
18
|
+
SleePyPhases/config.yaml
|
|
17
19
|
SleePyPhases.egg-info/PKG-INFO
|
|
18
20
|
SleePyPhases.egg-info/SOURCES.txt
|
|
19
21
|
SleePyPhases.egg-info/dependency_links.txt
|
|
@@ -46,6 +48,7 @@ tests/conftest.py
|
|
|
46
48
|
tests/unit/__init__.py
|
|
47
49
|
tests/unit/test_BuildDataset.py
|
|
48
50
|
tests/unit/test_BuildDatasetFolded.py
|
|
51
|
+
tests/unit/test_BuildDatasetMetadataSync.py
|
|
49
52
|
tests/unit/test_BuildDatasetSegments.py
|
|
50
53
|
tests/unit/test_DataManipulation.py
|
|
51
54
|
tests/unit/test_Eval.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.7.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,4 +1,15 @@
|
|
|
1
1
|
# Setup project for unittest discovery
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
# Ensure SleePyPhases is imported as a package before loadClass adds
|
|
6
|
+
# the inner directory to sys.path (which would shadow the package with SleePyPhases.py)
|
|
7
|
+
_cwd = os.getcwd()
|
|
8
|
+
_inner = os.path.join(_cwd, "SleePyPhases")
|
|
9
|
+
if _inner in sys.path:
|
|
10
|
+
sys.path.remove(_inner)
|
|
11
|
+
import SleePyPhases # noqa: E402,F401 — force package registration
|
|
12
|
+
|
|
2
13
|
from phases.commands.test import Test as PhasesTest
|
|
3
14
|
from pyPhases.test import TestCase
|
|
4
15
|
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Acceptance tests: verify that recordMetadata stays in sync with the actual
|
|
3
|
+
data items throughout the full pyPhases pipeline – including datafold with
|
|
4
|
+
multiple datasets, shuffle, and deterministic re-loading.
|
|
5
|
+
|
|
6
|
+
Scenario
|
|
7
|
+
--------
|
|
8
|
+
- 3 datasets via ``datafold.datasets``, each contributing 5 recordings
|
|
9
|
+
(recordIds "d0-0"…"d0-4", "d1-0"…"d1-4", "d2-0"…"d2-4").
|
|
10
|
+
- Each recording's X data embeds the *flat record index* so we can tell
|
|
11
|
+
which record a data item came from.
|
|
12
|
+
- A custom DataManipulation subclass (injected via Swappable.setClass)
|
|
13
|
+
adds a ``checkMetadata`` step that, for every item, records what
|
|
14
|
+
``self.recordMetadata[self.currentIndex]`` returns versus the recordId
|
|
15
|
+
derivable from the data payload.
|
|
16
|
+
- We pull data through ``project.getData("dataset-training")`` — the
|
|
17
|
+
same code path the real Training phase uses.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from unittest.mock import MagicMock
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import pandas as pd
|
|
24
|
+
|
|
25
|
+
from pyPhases.test.Mocks import OverwriteConfig
|
|
26
|
+
from pyPhases.test.TestCase import TestCase
|
|
27
|
+
from pyPhasesRecordloader import RecordLoader
|
|
28
|
+
|
|
29
|
+
from SleePyPhases.DataManipulation import DataManipulation
|
|
30
|
+
from SleePyPhases.phases.BuildDataset import BuildDataset
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
# Helpers
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
# Accumulates (index, metadataRecordId, dataValue) triples that the custom
|
|
38
|
+
# manipulation step records while the pipeline iterates.
|
|
39
|
+
_metadata_checks: list = []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CheckingDataManipulation(DataManipulation):
|
|
43
|
+
"""DataManipulation subclass with an extra ``checkMetadata`` step.
|
|
44
|
+
|
|
45
|
+
For every item it reads ``self.recordMetadata[self.currentIndex]`` and
|
|
46
|
+
compares the ``recordId`` to the flat-index value embedded in the X
|
|
47
|
+
data (after ``addBatchDimension`` + ``fixedSize`` centering to pos 5).
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def checkMetadata(self, X, Y):
|
|
51
|
+
meta = self.recordMetadata[self.currentIndex]
|
|
52
|
+
metadataRecordId = meta["recordId"]
|
|
53
|
+
dataValue = int(X[0, 5, 0]) # flat index encoded in the signal
|
|
54
|
+
_metadata_checks.append(
|
|
55
|
+
(self.currentIndex, metadataRecordId, dataValue)
|
|
56
|
+
)
|
|
57
|
+
return X, Y
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# Per-dataset record ids: 3 datasets × 5 records
|
|
61
|
+
ALL_IDS = [[f"d{d}-{r}" for r in range(7)] for d in range(3)]
|
|
62
|
+
FLAT_IDS = [rid for ds in ALL_IDS for rid in ds] # 15 ids in flat order
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
# Test class
|
|
67
|
+
# ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
class TestBuildDatasetMetadataSync(TestCase):
|
|
70
|
+
phase = BuildDataset()
|
|
71
|
+
|
|
72
|
+
def config(self):
|
|
73
|
+
self.project.setConfig("dataversion.split", {})
|
|
74
|
+
return {
|
|
75
|
+
"useLoader": "test",
|
|
76
|
+
"dataversion": {
|
|
77
|
+
"seed": 2023,
|
|
78
|
+
"recordIds": FLAT_IDS,
|
|
79
|
+
"split": {"training": ["0:15"]},
|
|
80
|
+
"groupBy": None,
|
|
81
|
+
},
|
|
82
|
+
"segmentManipulation": [
|
|
83
|
+
{"name": "addBatchDimension"},
|
|
84
|
+
{"name": "fixedSize", "position": "center", "size": 10},
|
|
85
|
+
{"name": "checkMetadata"},
|
|
86
|
+
],
|
|
87
|
+
"segmentManipulationEval": [
|
|
88
|
+
{"name": "addBatchDimension"},
|
|
89
|
+
{"name": "fixedSize", "position": "center", "size": 10},
|
|
90
|
+
{"name": "checkMetadata"},
|
|
91
|
+
],
|
|
92
|
+
"evalOn": None,
|
|
93
|
+
"datafold": {
|
|
94
|
+
"shuffle": False,
|
|
95
|
+
"seed": 42,
|
|
96
|
+
"datasets": [
|
|
97
|
+
{"config": {"dataversion": {"recordIds": ALL_IDS[0], "seed": 1, "split": {"training": ["0:5"], "validation": ["5:7"]}, "groupBy": None}}},
|
|
98
|
+
{"config": {"dataversion": {"recordIds": ALL_IDS[1], "seed": 2, "split": {"training": ["0:5"], "validation": ["5:7"]}, "groupBy": None}}},
|
|
99
|
+
{"config": {"dataversion": {"recordIds": ALL_IDS[2], "seed": 3, "split": {"training": ["0:5"], "validation": ["5:7"]}, "groupBy": None}}},
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
def setUp(self):
|
|
105
|
+
super().setUp()
|
|
106
|
+
DataManipulation.setClass(CheckingDataManipulation)
|
|
107
|
+
|
|
108
|
+
metadata = pd.DataFrame([{"recordId": rid} for rid in FLAT_IDS])
|
|
109
|
+
|
|
110
|
+
RecordLoader.getRecordList = MagicMock(return_value=FLAT_IDS)
|
|
111
|
+
|
|
112
|
+
# Register data for each datafold dataset context — mirrors what
|
|
113
|
+
# buildDataRecords() does with ``with self.project:`` + updateConfig.
|
|
114
|
+
# Each dataset gets its own data arrays (like separate memmaps in prod).
|
|
115
|
+
datasets = self.project.getConfig("datafold.datasets", [{"config": {}}])
|
|
116
|
+
for dsIdx, ds in enumerate(datasets):
|
|
117
|
+
overwriteConfig = ds["config"]
|
|
118
|
+
with self.project:
|
|
119
|
+
if "dataversion" in overwriteConfig:
|
|
120
|
+
self.project.setConfig("dataversion", {})
|
|
121
|
+
self.project.updateConfig(overwriteConfig)
|
|
122
|
+
|
|
123
|
+
dsRecordIds = self.project.getConfig("dataversion.recordIds")
|
|
124
|
+
# Each dataset's memmap only contains its own records.
|
|
125
|
+
# Values are the *global flat index* so we can trace which
|
|
126
|
+
# record a data item originally belonged to.
|
|
127
|
+
dsStart = dsIdx * 7
|
|
128
|
+
dsProcessed = np.arange(dsStart, dsStart + len(dsRecordIds)).reshape(-1, 1, 1)
|
|
129
|
+
dsFeatures = np.arange(dsStart, dsStart + len(dsRecordIds)).reshape(-1, 1, 1)
|
|
130
|
+
|
|
131
|
+
self.project.registerData("allDBRecordIds", {rid: [rid] for rid in dsRecordIds})
|
|
132
|
+
self.project.registerData("metadata", metadata)
|
|
133
|
+
self.project.registerData("data-processed", dsProcessed)
|
|
134
|
+
self.project.registerData("data-features", dsFeatures)
|
|
135
|
+
|
|
136
|
+
def tearDown(self):
|
|
137
|
+
DataManipulation.setClass(None)
|
|
138
|
+
super().tearDown()
|
|
139
|
+
|
|
140
|
+
# -- helpers ----------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
def _drain(self, dataset):
|
|
143
|
+
"""Iterate the full dataset, return list of (x, y)."""
|
|
144
|
+
return [item for item in dataset]
|
|
145
|
+
|
|
146
|
+
def _data_order(self, items):
|
|
147
|
+
"""Return the list of flat-index values read from the X payloads."""
|
|
148
|
+
return [int(x[0, 5, 0]) for x, _ in items]
|
|
149
|
+
|
|
150
|
+
def _assert_metadata_matches_data(self, label=""):
|
|
151
|
+
"""Assert every recorded (metadata, data) pair agrees."""
|
|
152
|
+
mismatches = []
|
|
153
|
+
for idx, metaId, dataVal in _metadata_checks:
|
|
154
|
+
expected_flat = FLAT_IDS.index(metaId)
|
|
155
|
+
if expected_flat != dataVal:
|
|
156
|
+
mismatches.append(
|
|
157
|
+
f" index {idx}: metadata={metaId} (flat {expected_flat}), data={dataVal}"
|
|
158
|
+
)
|
|
159
|
+
if mismatches:
|
|
160
|
+
self.fail(
|
|
161
|
+
f"{label}{len(mismatches)} metadata/data mismatches:\n"
|
|
162
|
+
+ "\n".join(mismatches)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# =====================================================================
|
|
166
|
+
# 1) No shuffle — metadata must match data for every item
|
|
167
|
+
# =====================================================================
|
|
168
|
+
|
|
169
|
+
def testNoShuffle_MetadataMatchesData(self):
|
|
170
|
+
"""Without shuffle the metadata recordId at each index must equal
|
|
171
|
+
the recordId derivable from the data payload."""
|
|
172
|
+
_metadata_checks.clear()
|
|
173
|
+
items = self._drain(self.getData("dataset-training"))
|
|
174
|
+
self.assertEqual(len(items), 15)
|
|
175
|
+
self._assert_metadata_matches_data("no-shuffle: ")
|
|
176
|
+
|
|
177
|
+
def testNoShuffle_MetadataMatchesDataValidation(self):
|
|
178
|
+
"""Without shuffle the metadata recordId at each index must equal
|
|
179
|
+
the recordId derivable from the data payload."""
|
|
180
|
+
_metadata_checks.clear()
|
|
181
|
+
items = self._drain(self.getData("dataset-validation"))
|
|
182
|
+
self.assertEqual(len(items), 6)
|
|
183
|
+
self._assert_metadata_matches_data("no-shuffle: ")
|
|
184
|
+
|
|
185
|
+
# =====================================================================
|
|
186
|
+
# 2) No shuffle — deterministic across two loads
|
|
187
|
+
# =====================================================================
|
|
188
|
+
|
|
189
|
+
def testNoShuffle_DeterministicOrder(self):
|
|
190
|
+
"""Two consecutive loads without shuffle must return data in
|
|
191
|
+
identical order."""
|
|
192
|
+
items1 = self._drain(self.getData("dataset-training"))
|
|
193
|
+
order1 = self._data_order(items1)
|
|
194
|
+
|
|
195
|
+
items2 = self._drain(self.getData("dataset-training"))
|
|
196
|
+
order2 = self._data_order(items2)
|
|
197
|
+
|
|
198
|
+
self.assertEqual(order1, order2)
|
|
199
|
+
|
|
200
|
+
def testNoShuffle_DeterministicOrderValidation(self):
|
|
201
|
+
"""Two consecutive loads without shuffle must return validation data in
|
|
202
|
+
identical order."""
|
|
203
|
+
items1 = self._drain(self.getData("dataset-validation"))
|
|
204
|
+
order1 = self._data_order(items1)
|
|
205
|
+
|
|
206
|
+
items2 = self._drain(self.getData("dataset-validation"))
|
|
207
|
+
order2 = self._data_order(items2)
|
|
208
|
+
|
|
209
|
+
self.assertEqual(order1, order2)
|
|
210
|
+
|
|
211
|
+
# =====================================================================
|
|
212
|
+
# 3) With shuffle — load twice, verify deterministic order via seed
|
|
213
|
+
# =====================================================================
|
|
214
|
+
|
|
215
|
+
@OverwriteConfig({"datafold": {"shuffle": True, "seed": 42}})
|
|
216
|
+
def testShuffle_DeterministicOrder(self):
|
|
217
|
+
"""Loading the dataset twice with the same seed must yield
|
|
218
|
+
identical item order both times."""
|
|
219
|
+
items1 = self._drain(self.getData("dataset-training"))
|
|
220
|
+
order1 = self._data_order(items1)
|
|
221
|
+
|
|
222
|
+
items2 = self._drain(self.getData("dataset-training"))
|
|
223
|
+
order2 = self._data_order(items2)
|
|
224
|
+
|
|
225
|
+
self.assertEqual(order1, order2,
|
|
226
|
+
"Same seed must produce the same item order")
|
|
227
|
+
|
|
228
|
+
@OverwriteConfig({"datafold": {"shuffle": True, "seed": 42}})
|
|
229
|
+
def testShuffle_DeterministicOrderValidation(self):
|
|
230
|
+
"""Loading the validation dataset twice with the same seed must yield
|
|
231
|
+
identical item order both times."""
|
|
232
|
+
items1 = self._drain(self.getData("dataset-validation"))
|
|
233
|
+
order1 = self._data_order(items1)
|
|
234
|
+
|
|
235
|
+
items2 = self._drain(self.getData("dataset-validation"))
|
|
236
|
+
order2 = self._data_order(items2)
|
|
237
|
+
|
|
238
|
+
self.assertEqual(order1, order2,
|
|
239
|
+
"Same seed must produce the same item order")
|
|
240
|
+
|
|
241
|
+
# =====================================================================
|
|
242
|
+
# 4) With shuffle — metadata must still match data for every item
|
|
243
|
+
# =====================================================================
|
|
244
|
+
|
|
245
|
+
@OverwriteConfig({"datafold": {"shuffle": True, "seed": 42}})
|
|
246
|
+
def testShuffle_MetadataMatchesData(self):
|
|
247
|
+
"""After shuffle, recordMetadata[i].recordId must describe the
|
|
248
|
+
record that data item i actually contains."""
|
|
249
|
+
_metadata_checks.clear()
|
|
250
|
+
items = self._drain(self.getData("dataset-training"))
|
|
251
|
+
self.assertEqual(len(items), 15)
|
|
252
|
+
self._assert_metadata_matches_data("shuffled: ")
|
|
253
|
+
|
|
254
|
+
@OverwriteConfig({"datafold": {"shuffle": True, "seed": 42}})
|
|
255
|
+
def testShuffle_MetadataMatchesDataValidation(self):
|
|
256
|
+
"""After shuffle, recordMetadata[i].recordId must describe the
|
|
257
|
+
record that validation data item i actually contains."""
|
|
258
|
+
_metadata_checks.clear()
|
|
259
|
+
items = self._drain(self.getData("dataset-validation"))
|
|
260
|
+
self.assertEqual(len(items), 6)
|
|
261
|
+
self._assert_metadata_matches_data("shuffled-validation: ")
|
|
@@ -207,7 +207,7 @@ class TestDataManipulation(unittest.TestCase):
|
|
|
207
207
|
|
|
208
208
|
def test_deleteIgnored(self):
|
|
209
209
|
testY = np.array([1, 2, 3, -1, 2, 2, 3, -1, 2, -1, 2]).reshape(1, -1, 1)
|
|
210
|
-
testX = np.array([1, 2, 3,
|
|
210
|
+
testX = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]).reshape(1, -1, 1)
|
|
211
211
|
newX, newY = self.getDA().deleteIgnored(testX, testY)
|
|
212
212
|
npt.assert_equal(newY.reshape(-1), [1, 2, 3, 2, 2, 3, 2, 2])
|
|
213
213
|
npt.assert_equal(newX.reshape(-1), [1, 2, 3, 5, 6, 7, 9, 11])
|
|
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
|