SleePyPhases 0.2.0__tar.gz → 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/PKG-INFO +3 -2
  2. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/DataManipulation.py +2 -2
  3. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/RecordFeatureExtraction.py +78 -6
  4. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/SleepMetaData.py +6 -0
  5. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Eval.py +13 -10
  6. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/EvalReport.py +8 -12
  7. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +62 -72
  8. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Extract.py +19 -7
  9. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExtractFeatures.py +11 -8
  10. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/TestRun.py +4 -0
  11. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ThresholdOptimisation.py +9 -7
  12. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/PKG-INFO +3 -2
  13. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/setup.py +1 -1
  14. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/LICENSE +0 -0
  15. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/README.md +0 -0
  16. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/EventScorer.py +0 -0
  17. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/FeatureExtraction copy.py +0 -0
  18. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/FeatureExtraction.py +0 -0
  19. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/MultiScorer.py +0 -0
  20. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/PSGEventManager.py +0 -0
  21. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/Plot.py +0 -0
  22. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/Plugin.py +0 -0
  23. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/PreManipulation.py +0 -0
  24. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/Reporter.py +0 -0
  25. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/SignalPreprocessing.py +0 -0
  26. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/__init__.py +0 -0
  27. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/BuildDataset.py +0 -0
  28. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/DataAnalysis.py +0 -0
  29. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/DataReport.py +0 -0
  30. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
  31. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExtractEvents.py +0 -0
  32. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
  33. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/GatherMetadata.py +0 -0
  34. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Setup.py +0 -0
  35. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Training.py +0 -0
  36. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/Validation.py +0 -0
  37. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/VisualizeConfig.py +0 -0
  38. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/phases/__init__.py +0 -0
  39. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/recordloaders/MedicalDB.py +0 -0
  40. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/recordloaders/RecordLoaderTSM.py +0 -0
  41. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases/recordloaders/__init__.py +0 -0
  42. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/SOURCES.txt +0 -0
  43. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/dependency_links.txt +0 -0
  44. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/requires.txt +0 -0
  45. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/SleePyPhases.egg-info/top_level.txt +0 -0
  46. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/pyproject.toml +0 -0
  47. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/setup.cfg +0 -0
  48. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/__init__.py +0 -0
  49. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/conftest.py +0 -0
  50. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/__init__.py +0 -0
  51. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_BuildDataset.py +0 -0
  52. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_BuildDatasetFolded.py +0 -0
  53. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_BuildDatasetSegments.py +0 -0
  54. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_DataManipulation.py +0 -0
  55. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_Eval.py +0 -0
  56. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_EventScorer.py +0 -0
  57. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_Extract.py +0 -0
  58. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_PreManipulation.py +0 -0
  59. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_RecordFeatureExtraction.py +0 -0
  60. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_RecordProcessor.py +0 -0
  61. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_Setup.py +0 -0
  62. {sleepyphases-0.2.0 → sleepyphases-0.2.1}/tests/unit/test_SignalPreprocessing.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: SleePyPhases
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A framwork for creating deep learning pipelines for sleep data
5
5
  Home-page: https://gitlab.com/sleep-is-all-you-need/sleepyphases
6
6
  Author: Franz Ehrlich
@@ -20,6 +20,7 @@ Dynamic: classifier
20
20
  Dynamic: description
21
21
  Dynamic: description-content-type
22
22
  Dynamic: home-page
23
+ Dynamic: license-file
23
24
  Dynamic: requires-dist
24
25
  Dynamic: requires-python
25
26
  Dynamic: summary
@@ -14,8 +14,8 @@ class DataManipulation(pyPhasesDataManipulation, Swappable):
14
14
  def __init__(self, steps, splitName, projectconfig, seed=2, threshold=None, recordMetadata=None, **kwargs) -> None:
15
15
  super().__init__(steps, splitName, **kwargs)
16
16
  self.catMatrix = None
17
- self.numLabels = projectconfig["numLabels"]
18
- self.numClasses = projectconfig["numClasses"]
17
+ self.numLabels = projectconfig["numLabels"] if "numLabels" in projectconfig else 1
18
+ self.numClasses = projectconfig["numClasses"] if "numLabels" in projectconfig else [1]
19
19
  self.rng = default_rng(seed=2)
20
20
  self.threshold = threshold
21
21
  self.recordMetadata = recordMetadata
@@ -25,6 +25,10 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
25
25
 
26
26
  def updateMetadataByStep(self, stepname: str, recordSignal: RecordSignal, eventlist: List[Event], metadata: pd.DataFrame, channel=None, **options):
27
27
  features = self.step(stepname, recordSignal, eventlist, channel, **options)
28
+
29
+ if features is None:
30
+ return None
31
+
28
32
  recordId = recordSignal.recordId
29
33
  # update row with index recordId and channel with features dict or create new if not exist
30
34
 
@@ -252,15 +256,83 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
252
256
  ret = {
253
257
  "cycle-count": len(cycles["start"]),
254
258
  }
259
+ frequencyFactor = 30
255
260
 
256
- for cycleNumber, (start, stop) in enumerate(zip(cycles["start"], cycles["end"])):
257
- if lastStop > 0:
258
- remTime = sum([e.duration for e in remEvents if e.start >= lastStop and e.start < start])
259
- ret[f"cycle-{cycleNumber}-rem-duration"] = remTime
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
- "cycle-count": len(cycles["start"]),
335
+ "spindles-count": len(spindles),
336
+ "spindles-mean-druation": np.array([s.duration for s in spindles]).mean()
265
337
  }
266
-
338
+
@@ -76,6 +76,7 @@ class SleepMetaData:
76
76
  self.fromSleepSignalBin(signal, frequency=frequency)
77
77
  case 'Sleep4':
78
78
  self.fromSleepSignalBin(signal, frequency=frequency)
79
+ self.fromSleepSignalFour(signal, frequency=frequency)
79
80
 
80
81
  def fromSleepSignalBin(self, signal, frequency = 1):
81
82
  # all time values in seconds
@@ -100,6 +101,11 @@ class SleepMetaData:
100
101
  self.registerMetadata("waso", trt-sLatency-tst, unit="s")
101
102
  self.registerMetadata("sEfficiency", tst / trt * 100, unit="%")
102
103
 
104
+ def fromSleepSignalFour(self, signal, frequency = 1):
105
+ whereREM = np.where(signal == 1)
106
+ rLatency = whereREM[0] - self.getMetaData("sLatency")
107
+ self.registerMetadata("rLatency", rLatency)
108
+
103
109
 
104
110
  def fromApneaSignal(self, signal, classification=None):
105
111
  # classification = classification or ["None", "obstructive", "mixed", "central", "hypopnea"]
@@ -86,8 +86,7 @@ class Eval(Phase):
86
86
  ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
87
87
  metrics = self.getConfig("trainingParameter.validationMetrics")
88
88
 
89
-
90
- scorer = MultiScorer(classNums, metrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClass=EventScorer)
89
+ scorer = MultiScorer(classNums, metrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex])
91
90
  for s in scorer.scorer.values():
92
91
  s.trace = True
93
92
  threshold = self.getConfig("fixedThreshold", False)
@@ -107,8 +106,8 @@ class Eval(Phase):
107
106
  prediction = model.predict(x, get_likelihood=True, returnNumpy=False)
108
107
  prediction, truth = da((prediction, truth))
109
108
 
110
- prediction = prediction.reshape(-1, prediction.shape[2])
111
- truth = truth.reshape(-1, truth.shape[2])
109
+ # prediction = prediction.reshape(-1, prediction.shape[2])
110
+ # truth = truth.reshape(-1, truth.shape[2])
112
111
 
113
112
  scorer.score(truth, prediction, trace=True)
114
113
 
@@ -150,7 +149,7 @@ class Eval(Phase):
150
149
  testData, recordsMap = self.getTestDataAndRecordMap()
151
150
 
152
151
  with self.project:
153
- self.project.config.update(self.getConfig("eventEval"))
152
+ self.project.config.update(self.getConfig("eventEval", {}))
154
153
 
155
154
  evalMetrics = self.getConfig("eval.metrics")
156
155
  classNums = self.getConfig("model.multiclassificationSize", [2])
@@ -158,10 +157,11 @@ class Eval(Phase):
158
157
  predictionNames = self.getConfig("classification.predictionSignals")
159
158
  predictionFrequencies = self.getConfig("classification.predictionFrequencies")
160
159
  ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
160
+ scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
161
161
 
162
162
  threshold = self.getConfig("fixedThreshold", False)
163
163
  thresholds = threshold or self.getData("threshold", list)
164
- mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClass=EventScorer)
164
+ mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClasses=scorerTypes)
165
165
  for threshold, s in zip(thresholds, mScorer.scorer.values()):
166
166
  s.majorityVote = self.getConfig("eventEval.tpStrat", "overlap") == "majority"
167
167
  s.noTN = self.getConfig("eventEval.tnStrat", "eventcount") == "noTN"
@@ -172,7 +172,7 @@ class Eval(Phase):
172
172
 
173
173
  metaData = pd.DataFrame(self.getTestMetadata())
174
174
  metaData.replace("M", None, inplace=True)
175
- metaData["indexArousal"] = metaData["indexArousal"].astype(float)
175
+ # metaData["indexArousal"] = metaData["indexArousal"].astype(float)
176
176
  numClasses = self.getConfig("numClasses")
177
177
 
178
178
  model = self.getModel()
@@ -212,11 +212,12 @@ class Eval(Phase):
212
212
  labelTruth = labelTruth.reshape(-1)
213
213
 
214
214
  # score the prediction
215
- r = scorer.score(labelTruth.reshape(-1), labelPrediction.reshape(-1, numClass))
215
+ r = scorer.score(labelTruth, labelPrediction)
216
+ # r = scorer.score(labelTruth.reshape(-1), labelPrediction.reshape(-1, numClass))
216
217
  resultRow.update({f"{m}_{labelName}": v for m,v in r.items()})
217
218
 
218
219
  # add examples
219
- resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
220
+ # resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
220
221
 
221
222
 
222
223
  recordData = metaData.query(f'recordId == "{recordId}"')
@@ -250,4 +251,6 @@ class Eval(Phase):
250
251
  self.registerData("eventResults", eventResults)
251
252
 
252
253
  def main(self):
253
- self.getData("eventResults", list)
254
+ segmentResultsRecords, segmentResults = self.getData("evalResults")
255
+
256
+ self.logSuccess(f"result segments: {segmentResults}")
@@ -38,7 +38,7 @@ class EvalReport(Phase):
38
38
 
39
39
  def createLogFolder(self, path):
40
40
  Path(path).mkdir(parents=True, exist_ok=True)
41
- Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
41
+ # Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
42
42
 
43
43
  def addAsset(self, name, extension="png", bbox_inches="tight", dpi=300):
44
44
  import matplotlib.pyplot as plt
@@ -178,22 +178,20 @@ class EvalReport(Phase):
178
178
 
179
179
  def main(self):
180
180
  import pandas as pd
181
- from SleePyPhases.Plot import Plot
182
181
 
183
182
  threshold = self.getConfig("fixedThreshold", False)
184
183
  self.threshold = threshold or self.getData("threshold", float)
185
184
 
186
185
  modelConfigString = self.project.getDataFromName("eventResults").getTagString()
187
- evalPath = f"eval/{modelConfigString}/"
188
186
 
189
- Reporter.basePath = evalPath
187
+ evalPath = self.getConfig("eval-path", "eval/")
188
+ evalPath = f"{evalPath}/{modelConfigString}/"
189
+
190
+ self.createLogFolder(evalPath)
191
+
190
192
  self.evalPath = evalPath
191
- self.reporter = Reporter("EvalReport")
192
- self.reporter.createFolder()
193
- # self.reporter.save()
194
193
 
195
194
  self.copyRawData()
196
- # self.modelInfo()
197
195
 
198
196
  # self.log("save report to: %s" % self.reporter.getFilePath())
199
197
 
@@ -257,11 +255,9 @@ class EvalReport(Phase):
257
255
  df = pd.read_csv(logFile)
258
256
  plt.plot(df["epoch"], df["loss"], label="Training Loss")
259
257
  plt.plot(df["epoch"], df[self.metrics[0]], label=self.metrics[0])
260
- self.reporter.addAsset("trainingsProcess")
258
+ self.addAsset("trainingsProcess")
261
259
 
262
260
  def modelInfo(self):
263
261
  model = self.project.getPhase("Eval").getModel()
264
262
  sum = model.summary()
265
- self.reporter.addVar("modelName", self.getConfig("modelName"))
266
- self.reporter.addVar("parameter", model.parameter, "humannumber")
267
- self.reporter.addVar("modelSummary", sum)
263
+ print(sum)
@@ -18,32 +18,10 @@ class ExportMetadataToMeticalDB(Phase):
18
18
  from SleePyPhases.recordloaders.MedicalDB import MedicalDB
19
19
 
20
20
  metadata = self.getData("metadata", pd.DataFrame)
21
- relevantData = [v for v in AliceTextReportLoader.relevantRows.values() if v != ""]
22
- # recordMetadataRows = []
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
- # df = metaData.items()
62
- recordId = row["recordId"]
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.execute(str(q))
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
- q = PostgreSQLQuery.from_(channelTable)\
236
- .delete()\
237
- .where(channelTable.id_record == recordId)
238
- medDB.execute(str(q))
239
-
240
- if "channels" not in row:
241
- continue
242
-
243
- for channel in row["channels"]:
244
- channelName = channel["signalName"]
245
-
246
- chanelDict = {
247
- "name": channelName,
248
- "frequency": channel["sample_rate"], # sample_frequency
249
- "prefilter": channel["prefilter"],
250
- "physical_max": channel["physical_max"],
251
- "physical_min": channel["physical_min"],
252
- "digital_max": channel["digital_max"],
253
- "digital_min": channel["digital_min"],
254
- "prefilter": channel["prefilter"],
255
- "transducer": channel["transducer"],
256
- "type": channel["type"],
257
- "id_record": recordId
258
- }
259
- q = PostgreSQLQuery.into(channelTable)\
260
- .columns(*list(chanelDict.keys()))\
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 allready exist")
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
- records = records[recordsLoaded:]
241
- self.logSuccess("Resuming extraction: %i records allready loaded / %i to go" % (recordsLoaded, len(records)))
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
- [removedRecordIds.append(records[r]) for r in rm]
266
+ new_removed = [records[r] for r in rm]
267
+ removedRecordIds.extend(new_removed)
268
+ np.save(removed_records_path, removedRecordIds)
269
+
258
270
  recordList = [r for r in recordList if r is not None]
259
271
  self.logWarning("Skipped %i records: channels/annotation not present" % len(rm))
260
272
 
@@ -174,6 +174,8 @@ class ExtractFeatures(Phase):
174
174
  recordSignal, eventlist = recordLoader.loadRecord(recordId)
175
175
  except AnnotationNotFound as e:
176
176
  self.logError(f"Annotation missing in {recordId}: {e.name} ... skipping record")
177
+ except ChannelsNotPresent as e:
178
+ self.logError(f"Channels missing in {recordId}: {e.channels} ... skipping record")
177
179
  except ParseError as e:
178
180
  self.logError(f"ParseError in record {recordId}, record will be skipped: {e}")
179
181
 
@@ -203,19 +205,20 @@ class ExtractFeatures(Phase):
203
205
  return self.features.loc[idx]
204
206
 
205
207
  def main(self):
208
+ import pandas as pd
206
209
  self.features = None
207
- groupedRecords = self.project.getData("allDBRecordIds", list)
210
+ metadata = self.project.getData("metadata", pd.DataFrame)
211
+ recordIds = metadata["recordId"]
208
212
 
209
213
  reqFeatures = {f["name"]: True for f in self.getConfig("featureExtraction.features", [])}
210
214
 
211
215
  # groupedRecords = {"pid_202436073": "acq_237478592"}
212
- for patient, recordIds in tqdm(groupedRecords.items()):
213
- for recordId in recordIds:
214
- # tryToGenerate has no meaning, it is just known to be not in the metadata, so the record will always be recalculated
215
- # self.getData("features", pd.DataFrame, recordId=recordId, tryToGenerate=True, **reqFeatures)
216
- rows = self.getData("features", pd.DataFrame, recordId=recordId, **reqFeatures)
217
- if len(rows) == 0:
218
- self.logError(f"no features for {recordId}")
216
+ for recordId in tqdm(recordIds.iloc):
217
+ # tryToGenerate has no meaning, it is just known to be not in the metadata, so the record will always be recalculated
218
+ # self.getData("features", pd.DataFrame, recordId=recordId, tryToGenerate=True, **reqFeatures)
219
+ rows = self.getData("features", pd.DataFrame, recordId=recordId, **reqFeatures)
220
+ if len(rows) == 0:
221
+ self.logError(f"no features for {recordId}")
219
222
 
220
223
 
221
224
  # get a single feature:
@@ -48,6 +48,8 @@ class TestRun(Phase):
48
48
  self.log(f"Creating a new Data Folder ({testRunData.as_posix()}) for the testrun")
49
49
  if not testRunData.exists():
50
50
  testRunData.mkdir()
51
+ self.setConfig("data-path", testRunData)
52
+ self.project.prepareAllPhases()
51
53
  # else:
52
54
  # # remove all files from the testrun folder
53
55
  # for f in testRunData.iterdir():
@@ -121,6 +123,8 @@ class TestRun(Phase):
121
123
  self.log(f"Test Model: {self.getConfig('modelName')}")
122
124
 
123
125
  model = ModelManager.getModel()
126
+ model.config.logPath = testRunData / "logs"
127
+
124
128
  self.log(f"Current Model: {type(model)}")
125
129
  self.log(model.summary())
126
130
  train = self.project.generateData("dataset-training")
@@ -4,6 +4,7 @@ from pyPhasesML import Model, ModelManager
4
4
  from tqdm import tqdm
5
5
 
6
6
  from SleePyPhases.DataManipulation import DataManipulation
7
+ from pyPhasesML import Scorer
7
8
  from SleePyPhases.EventScorer import EventScorer
8
9
  from SleePyPhases.MultiScorer import MultiScorer
9
10
 
@@ -35,12 +36,8 @@ class ThresholdOptimisation(Phase):
35
36
  x, t = validationData[i]
36
37
  p = model.predict(x, get_likelihood=True, returnNumpy=False)
37
38
  p, t = da((p, t))
38
- prediction.append(p)
39
- truth.append(t)
40
-
41
- # we assume output is reduces to bachsize 1 during augmentation (1, X, channels)
42
- prediction = np.concatenate(prediction, axis=1)
43
- truth = np.concatenate(truth, axis=1)
39
+ [prediction.append(x) for x in p]
40
+ [truth.append(x) for x in t]
44
41
 
45
42
  return prediction, truth
46
43
 
@@ -54,8 +51,10 @@ class ThresholdOptimisation(Phase):
54
51
  classNums = self.getConfig("model.multiclassificationSize", [2])
55
52
  labelNames = self.getConfig("classification.labelNames")
56
53
  ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
54
+ optimizeThresholdFor = self.getConfig("optimizeThresholdFor", None)
55
+ scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
57
56
 
58
- scorer = MultiScorer(classNums, thresholdMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClass=EventScorer)
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.2
1
+ Metadata-Version: 2.4
2
2
  Name: SleePyPhases
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: A framwork for creating deep learning pipelines for sleep data
5
5
  Home-page: https://gitlab.com/sleep-is-all-you-need/sleepyphases
6
6
  Author: Franz Ehrlich
@@ -20,6 +20,7 @@ Dynamic: classifier
20
20
  Dynamic: description
21
21
  Dynamic: description-content-type
22
22
  Dynamic: home-page
23
+ Dynamic: license-file
23
24
  Dynamic: requires-dist
24
25
  Dynamic: requires-python
25
26
  Dynamic: summary
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="SleePyPhases",
8
- version="v0.2.0"[1:],
8
+ version="v0.2.1"[1:],
9
9
  author="Franz Ehrlich",
10
10
  author_email="fehrlichd@gmail.com",
11
11
  description="A framwork for creating deep learning pipelines for sleep data",
File without changes
File without changes
File without changes