SleePyPhases 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/PKG-INFO +3 -2
  2. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/DataManipulation.py +2 -2
  3. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/MultiScorer.py +8 -0
  4. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/RecordFeatureExtraction.py +78 -6
  5. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/SleepMetaData.py +6 -0
  6. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Eval.py +15 -18
  7. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/EvalReport.py +10 -15
  8. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExportMetadataToMeticalDB.py +62 -72
  9. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Extract.py +19 -7
  10. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExtractFeatures.py +13 -13
  11. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Setup.py +0 -19
  12. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/TestRun.py +4 -0
  13. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ThresholdOptimisation.py +9 -7
  14. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/PKG-INFO +3 -2
  15. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/SOURCES.txt +0 -3
  16. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/setup.py +1 -1
  17. sleepyphases-0.2.0/SleePyPhases/recordloaders/MedicalDB.py +0 -124
  18. sleepyphases-0.2.0/SleePyPhases/recordloaders/RecordLoaderTSM.py +0 -34
  19. sleepyphases-0.2.0/tests/unit/__init__.py +0 -0
  20. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/LICENSE +0 -0
  21. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/README.md +0 -0
  22. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/EventScorer.py +0 -0
  23. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/FeatureExtraction copy.py +0 -0
  24. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/FeatureExtraction.py +0 -0
  25. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/PSGEventManager.py +0 -0
  26. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/Plot.py +0 -0
  27. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/Plugin.py +0 -0
  28. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/PreManipulation.py +0 -0
  29. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/Reporter.py +0 -0
  30. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/SignalPreprocessing.py +0 -0
  31. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/__init__.py +0 -0
  32. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/BuildDataset.py +0 -0
  33. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/DataAnalysis.py +0 -0
  34. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/DataReport.py +0 -0
  35. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/EvalPlotExamples.py +0 -0
  36. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExtractEvents.py +0 -0
  37. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/ExtractRecordFeatures.py +0 -0
  38. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/GatherMetadata.py +0 -0
  39. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Training.py +0 -0
  40. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/Validation.py +0 -0
  41. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/VisualizeConfig.py +0 -0
  42. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases/phases/__init__.py +0 -0
  43. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/dependency_links.txt +0 -0
  44. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/requires.txt +0 -0
  45. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/SleePyPhases.egg-info/top_level.txt +0 -0
  46. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/pyproject.toml +0 -0
  47. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/setup.cfg +0 -0
  48. {sleepyphases-0.2.0/SleePyPhases/recordloaders → sleepyphases-0.3.0/tests}/__init__.py +0 -0
  49. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/conftest.py +0 -0
  50. {sleepyphases-0.2.0/tests → sleepyphases-0.3.0/tests/unit}/__init__.py +0 -0
  51. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_BuildDataset.py +0 -0
  52. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_BuildDatasetFolded.py +0 -0
  53. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_BuildDatasetSegments.py +0 -0
  54. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_DataManipulation.py +0 -0
  55. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_Eval.py +0 -0
  56. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_EventScorer.py +0 -0
  57. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_Extract.py +0 -0
  58. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_PreManipulation.py +0 -0
  59. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_RecordFeatureExtraction.py +0 -0
  60. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_RecordProcessor.py +0 -0
  61. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/tests/unit/test_Setup.py +0 -0
  62. {sleepyphases-0.2.0 → sleepyphases-0.3.0}/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.3.0
4
4
  Summary: A framwork for creating deep learning pipelines for sleep data
5
5
  Home-page: https://gitlab.com/sleep-is-all-you-need/sleepyphases
6
6
  Author: Franz Ehrlich
@@ -20,6 +20,7 @@ Dynamic: classifier
20
20
  Dynamic: description
21
21
  Dynamic: description-content-type
22
22
  Dynamic: home-page
23
+ Dynamic: license-file
23
24
  Dynamic: requires-dist
24
25
  Dynamic: requires-python
25
26
  Dynamic: summary
@@ -14,8 +14,8 @@ class DataManipulation(pyPhasesDataManipulation, Swappable):
14
14
  def __init__(self, steps, splitName, projectconfig, seed=2, threshold=None, recordMetadata=None, **kwargs) -> None:
15
15
  super().__init__(steps, splitName, **kwargs)
16
16
  self.catMatrix = None
17
- self.numLabels = projectconfig["numLabels"]
18
- self.numClasses = projectconfig["numClasses"]
17
+ self.numLabels = projectconfig["numLabels"] if "numLabels" in projectconfig else 1
18
+ self.numClasses = projectconfig["numClasses"] if "numLabels" in projectconfig else [1]
19
19
  self.rng = default_rng(seed=2)
20
20
  self.threshold = threshold
21
21
  self.recordMetadata = recordMetadata
@@ -42,19 +42,27 @@ class MultiScorer(Scorer):
42
42
  if isinstance(predictions, list) or isinstance(predictions, tuple):
43
43
  p = predictions[i]
44
44
  elif predictions.shape[-1] == len(self.multiClassNums):
45
+ if len(predictions.shape) == 3:
46
+ predictions = predictions.reshape(-1, predictions.shape[-1])
45
47
  p = predictions[:, i]
46
48
  else:
47
49
  start = sum(self.multiClassNums[:i])
48
50
  end = start + self.multiClassNums[i]
51
+ if len(predictions.shape) == 3:
52
+ predictions = predictions.reshape(-1, predictions.shape[-1])
49
53
  p = predictions[:, start:end]
50
54
 
51
55
  if isinstance(truth, list) or isinstance(truth, tuple):
52
56
  t = truth[i]
53
57
  elif truth.shape[-1] == len(self.multiClassNums):
58
+ if len(truth.shape) == 3:
59
+ truth = truth.reshape(-1, truth.shape[-1])
54
60
  t = truth[:, i]
55
61
  else:
56
62
  start = sum(self.multiClassNums[:i])
57
63
  end = start + self.multiClassNums[i]
64
+ if len(truth.shape) == 3:
65
+ truth = truth.reshape(-1, truth.shape[-1])
58
66
  t = truth[:, start:end]
59
67
 
60
68
 
@@ -25,6 +25,10 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
25
25
 
26
26
  def updateMetadataByStep(self, stepname: str, recordSignal: RecordSignal, eventlist: List[Event], metadata: pd.DataFrame, channel=None, **options):
27
27
  features = self.step(stepname, recordSignal, eventlist, channel, **options)
28
+
29
+ if features is None:
30
+ return None
31
+
28
32
  recordId = recordSignal.recordId
29
33
  # update row with index recordId and channel with features dict or create new if not exist
30
34
 
@@ -252,15 +256,83 @@ class RecordFeatureExtraction(pyPhasesFeatureExtraction):
252
256
  ret = {
253
257
  "cycle-count": len(cycles["start"]),
254
258
  }
259
+ frequencyFactor = 30
255
260
 
256
- for cycleNumber, (start, stop) in enumerate(zip(cycles["start"], cycles["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"]
@@ -81,13 +81,12 @@ class Eval(Phase):
81
81
 
82
82
  # setup data and scorer
83
83
 
84
- classNums = self.getConfig("model.multiclassificationSize", [2])
84
+ classNums = self.getConfig("numClasses", [2])
85
85
  labelNames = self.getConfig("classification.labelNames")
86
86
  ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
87
87
  metrics = self.getConfig("trainingParameter.validationMetrics")
88
88
 
89
-
90
- scorer = MultiScorer(classNums, metrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClass=EventScorer)
89
+ scorer = MultiScorer(classNums, metrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex])
91
90
  for s in scorer.scorer.values():
92
91
  s.trace = True
93
92
  threshold = self.getConfig("fixedThreshold", False)
@@ -100,16 +99,10 @@ class Eval(Phase):
100
99
 
101
100
  for data in tqdm(testData):
102
101
  x, truth = data
103
- # make sure to have a batchsize if batchSize == 1
104
- x = x.reshape(batchSize, x.shape[0], x.shape[1])
105
- truth = truth.reshape(batchSize, truth.shape[0], truth.shape[1])
106
102
 
107
- prediction = model.predict(x, get_likelihood=True, returnNumpy=False)
103
+ prediction = model.predict(x, returnNumpy=False)
108
104
  prediction, truth = da((prediction, truth))
109
105
 
110
- prediction = prediction.reshape(-1, prediction.shape[2])
111
- truth = truth.reshape(-1, truth.shape[2])
112
-
113
106
  scorer.score(truth, prediction, trace=True)
114
107
 
115
108
  result = scorer.scoreAllRecords()
@@ -150,18 +143,19 @@ class Eval(Phase):
150
143
  testData, recordsMap = self.getTestDataAndRecordMap()
151
144
 
152
145
  with self.project:
153
- self.project.config.update(self.getConfig("eventEval"))
146
+ self.project.config.update(self.getConfig("eventEval", {}))
154
147
 
155
148
  evalMetrics = self.getConfig("eval.metrics")
156
- classNums = self.getConfig("model.multiclassificationSize", [2])
149
+ classNums = self.getConfig("numClasses", [2])
157
150
  labelNames = self.getConfig("classification.labelNames")
158
151
  predictionNames = self.getConfig("classification.predictionSignals")
159
152
  predictionFrequencies = self.getConfig("classification.predictionFrequencies")
160
153
  ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
154
+ scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
161
155
 
162
156
  threshold = self.getConfig("fixedThreshold", False)
163
157
  thresholds = threshold or self.getData("threshold", list)
164
- mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClass=EventScorer)
158
+ mScorer = MultiScorer(classNums, evalMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], scorerClasses=scorerTypes)
165
159
  for threshold, s in zip(thresholds, mScorer.scorer.values()):
166
160
  s.majorityVote = self.getConfig("eventEval.tpStrat", "overlap") == "majority"
167
161
  s.noTN = self.getConfig("eventEval.tnStrat", "eventcount") == "noTN"
@@ -172,7 +166,7 @@ class Eval(Phase):
172
166
 
173
167
  metaData = pd.DataFrame(self.getTestMetadata())
174
168
  metaData.replace("M", None, inplace=True)
175
- metaData["indexArousal"] = metaData["indexArousal"].astype(float)
169
+ # metaData["indexArousal"] = metaData["indexArousal"].astype(float)
176
170
  numClasses = self.getConfig("numClasses")
177
171
 
178
172
  model = self.getModel()
@@ -193,7 +187,7 @@ class Eval(Phase):
193
187
  x = x.reshape([1] + list(x.shape))
194
188
  truth = truth.reshape([1] + list(truth.shape))
195
189
 
196
- prediction = model.predict(x, get_likelihood=True, returnNumpy=False)
190
+ prediction = model.predict(x, returnNumpy=False)
197
191
  prediction, truth = da((prediction, truth))
198
192
 
199
193
  # for labelIndex, labelName in enumerate(recordResults.keys()):
@@ -212,11 +206,12 @@ class Eval(Phase):
212
206
  labelTruth = labelTruth.reshape(-1)
213
207
 
214
208
  # score the prediction
215
- r = scorer.score(labelTruth.reshape(-1), labelPrediction.reshape(-1, numClass))
209
+ r = scorer.score(labelTruth, labelPrediction)
210
+ # r = scorer.score(labelTruth.reshape(-1), labelPrediction.reshape(-1, numClass))
216
211
  resultRow.update({f"{m}_{labelName}": v for m,v in r.items()})
217
212
 
218
213
  # add examples
219
- resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
214
+ # resultRow[f"examples_{labelName}"] = self.getExamples(labelIndex, labelTruth, labelPrediction)
220
215
 
221
216
 
222
217
  recordData = metaData.query(f'recordId == "{recordId}"')
@@ -250,4 +245,6 @@ class Eval(Phase):
250
245
  self.registerData("eventResults", eventResults)
251
246
 
252
247
  def main(self):
253
- self.getData("eventResults", list)
248
+ segmentResultsRecords, segmentResults = self.getData("evalResults")
249
+
250
+ self.logSuccess(f"result segments: {segmentResults}")
@@ -10,7 +10,6 @@ from pyPhasesRecordloader import RecordSignal, Signal
10
10
 
11
11
  from SleePyPhases import Plot
12
12
  from SleePyPhases.DataManipulation import DataManipulation
13
- from SleePyPhases.Reporter import Reporter
14
13
 
15
14
 
16
15
  class EvalReport(Phase):
@@ -38,7 +37,7 @@ class EvalReport(Phase):
38
37
 
39
38
  def createLogFolder(self, path):
40
39
  Path(path).mkdir(parents=True, exist_ok=True)
41
- Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
40
+ # Path(path).joinpath("/examples").mkdir(parents=True, exist_ok=True)
42
41
 
43
42
  def addAsset(self, name, extension="png", bbox_inches="tight", dpi=300):
44
43
  import matplotlib.pyplot as plt
@@ -178,22 +177,20 @@ class EvalReport(Phase):
178
177
 
179
178
  def main(self):
180
179
  import pandas as pd
181
- from SleePyPhases.Plot import Plot
182
180
 
183
181
  threshold = self.getConfig("fixedThreshold", False)
184
182
  self.threshold = threshold or self.getData("threshold", float)
185
183
 
186
184
  modelConfigString = self.project.getDataFromName("eventResults").getTagString()
187
- evalPath = f"eval/{modelConfigString}/"
188
185
 
189
- Reporter.basePath = evalPath
186
+ evalPath = self.getConfig("eval-path", "eval/")
187
+ evalPath = f"{evalPath}/{modelConfigString}/"
188
+
189
+ self.createLogFolder(evalPath)
190
+
190
191
  self.evalPath = evalPath
191
- self.reporter = Reporter("EvalReport")
192
- self.reporter.createFolder()
193
- # self.reporter.save()
194
192
 
195
193
  self.copyRawData()
196
- # self.modelInfo()
197
194
 
198
195
  # self.log("save report to: %s" % self.reporter.getFilePath())
199
196
 
@@ -246,8 +243,8 @@ class EvalReport(Phase):
246
243
  def copyRawData(self):
247
244
  modelConfigString = self.project.getDataFromName("modelState").getTagString()
248
245
  trainingLogPath = f"logs/{modelConfigString}/"
249
- shutil.copyfile(trainingLogPath + "log.csv", Reporter.basePath + "training.log")
250
- shutil.copyfile(trainingLogPath + "project.config", Reporter.basePath + "project.json")
246
+ shutil.copyfile(trainingLogPath + "log.csv", self.evalPath + "training.log")
247
+ shutil.copyfile(trainingLogPath + "project.config", self.evalPath + "project.json")
251
248
 
252
249
  def trainingPlot(self, logFile):
253
250
  import matplotlib.pyplot as plt
@@ -257,11 +254,9 @@ class EvalReport(Phase):
257
254
  df = pd.read_csv(logFile)
258
255
  plt.plot(df["epoch"], df["loss"], label="Training Loss")
259
256
  plt.plot(df["epoch"], df[self.metrics[0]], label=self.metrics[0])
260
- self.reporter.addAsset("trainingsProcess")
257
+ self.addAsset("trainingsProcess")
261
258
 
262
259
  def modelInfo(self):
263
260
  model = self.project.getPhase("Eval").getModel()
264
261
  sum = model.summary()
265
- self.reporter.addVar("modelName", self.getConfig("modelName"))
266
- self.reporter.addVar("parameter", model.parameter, "humannumber")
267
- self.reporter.addVar("modelSummary", sum)
262
+ print(sum)
@@ -18,32 +18,10 @@ class ExportMetadataToMeticalDB(Phase):
18
18
  from SleePyPhases.recordloaders.MedicalDB import MedicalDB
19
19
 
20
20
  metadata = self.getData("metadata", pd.DataFrame)
21
- 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,21 +205,19 @@ class ExtractFeatures(Phase):
203
205
  return self.features.loc[idx]
204
206
 
205
207
  def main(self):
208
+ import pandas as pd
206
209
  self.features = None
207
- groupedRecords = self.project.getData("allDBRecordIds", list)
210
+ metadata = self.project.getData("metadata", pd.DataFrame)
211
+ recordIds = metadata["recordId"]
212
+ forceCalculation = self.getConfig("featureExtraction.features", True)
208
213
 
209
214
  reqFeatures = {f["name"]: True for f in self.getConfig("featureExtraction.features", [])}
210
215
 
211
- # 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
+ if forceCalculation:
218
+ # the parameter forced has no meaning, it is just known to be not in the metadata, so the record will always be recalculated
219
+ reqFeatures["forced"] = True # should never be found
220
+ rows = self.getData("features", pd.DataFrame, recordId=recordId, **reqFeatures)
221
+ if len(rows) == 0:
222
+ self.logError(f"no features for {recordId}")
219
223
 
220
-
221
- # get a single feature:
222
- # feat = {"swa": True}
223
- # x = self.getData("features", pd.DataFrame, recordId="shhs1-200002", channel="EEG", **feat)
@@ -4,7 +4,6 @@ from pyPhases import Phase
4
4
  from pyPhasesML import DataversionManager, Scorer
5
5
  from pyPhasesRecordloader import RecordLoader
6
6
 
7
- from SleePyPhases.recordloaders.MedicalDB import MedicalDB
8
7
 
9
8
 
10
9
  class Setup(Phase):
@@ -17,21 +16,6 @@ class Setup(Phase):
17
16
 
18
17
  defaultValidationbatchManipulation = self.getConfig("segmentManipulationEval", self.getConfig("segmentManipulation"))
19
18
  self.setConfig("segmentManipulationEval", defaultValidationbatchManipulation)
20
-
21
- # alice loader
22
- RecordLoader.registerRecordLoader("RecordLoaderTest", "SleePyPhases.recordloaders")
23
- RecordLoader.registerRecordLoader("RecordLoaderTSM", "SleePyPhases.recordloaders")
24
- tsmPath = self.getConfig("tsm-path", False)
25
- if tsmPath is not False:
26
- tsmPath = Path(tsmPath)
27
- self.project.setConfig("loader.tsm.filePath", tsmPath.as_posix())
28
- self.project.setConfig("loader.tsm.dataset.downloader.basePath", tsmPath.as_posix())
29
-
30
- tsmPath = self.getConfig("tsm-domino-path", False)
31
- if tsmPath is not False:
32
- tsmPath = Path(tsmPath)
33
- self.project.setConfig("loader.tsm-domino.filePath", tsmPath.as_posix())
34
- self.project.setConfig("loader.tsm-domino.dataset.downloader.basePath", tsmPath.as_posix())
35
19
 
36
20
  preprocessingConfig = self.getConfig("preprocessing")
37
21
  if "combineChannels" in preprocessingConfig:
@@ -62,9 +46,6 @@ class Setup(Phase):
62
46
  return kappa
63
47
  Scorer.registerMetric("reduceAndKappa", reduceKappa, combineReduceKappa)
64
48
 
65
- MedicalDB.config = self.getConfig("medicalDB", {})
66
-
67
-
68
49
  def getDataVersionManager(self):
69
50
  groupedRecords = self.project.getData("allDBRecordIds", list)
70
51
  seed = self.getConfig("dataversion.seed", None)
@@ -48,6 +48,8 @@ class TestRun(Phase):
48
48
  self.log(f"Creating a new Data Folder ({testRunData.as_posix()}) for the testrun")
49
49
  if not testRunData.exists():
50
50
  testRunData.mkdir()
51
+ self.setConfig("data-path", testRunData)
52
+ self.project.prepareAllPhases()
51
53
  # else:
52
54
  # # remove all files from the testrun folder
53
55
  # for f in testRunData.iterdir():
@@ -121,6 +123,8 @@ class TestRun(Phase):
121
123
  self.log(f"Test Model: {self.getConfig('modelName')}")
122
124
 
123
125
  model = ModelManager.getModel()
126
+ model.config.logPath = testRunData / "logs"
127
+
124
128
  self.log(f"Current Model: {type(model)}")
125
129
  self.log(model.summary())
126
130
  train = self.project.generateData("dataset-training")
@@ -4,6 +4,7 @@ from pyPhasesML import Model, ModelManager
4
4
  from tqdm import tqdm
5
5
 
6
6
  from SleePyPhases.DataManipulation import DataManipulation
7
+ from pyPhasesML import Scorer
7
8
  from SleePyPhases.EventScorer import EventScorer
8
9
  from SleePyPhases.MultiScorer import MultiScorer
9
10
 
@@ -35,12 +36,8 @@ class ThresholdOptimisation(Phase):
35
36
  x, t = validationData[i]
36
37
  p = model.predict(x, get_likelihood=True, returnNumpy=False)
37
38
  p, t = da((p, t))
38
- prediction.append(p)
39
- truth.append(t)
40
-
41
- # we assume output is reduces to bachsize 1 during augmentation (1, X, channels)
42
- prediction = np.concatenate(prediction, axis=1)
43
- truth = np.concatenate(truth, axis=1)
39
+ [prediction.append(x) for x in p]
40
+ [truth.append(x) for x in t]
44
41
 
45
42
  return prediction, truth
46
43
 
@@ -54,8 +51,10 @@ class ThresholdOptimisation(Phase):
54
51
  classNums = self.getConfig("model.multiclassificationSize", [2])
55
52
  labelNames = self.getConfig("classification.labelNames")
56
53
  ignoreIndex = self.getConfig("classification.ignoreIndex", -1)
54
+ optimizeThresholdFor = self.getConfig("optimizeThresholdFor", None)
55
+ scorerTypes = [(EventScorer if sc == "event" else Scorer) for sc in self.getConfig("classification.scorerTypes")]
57
56
 
58
- scorer = MultiScorer(classNums, thresholdMetrics, scorerNames=labelNames, ignoreClasses=[ignoreIndex], 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.3.0
4
4
  Summary: A framwork for creating deep learning pipelines for sleep data
5
5
  Home-page: https://gitlab.com/sleep-is-all-you-need/sleepyphases
6
6
  Author: Franz Ehrlich
@@ -20,6 +20,7 @@ Dynamic: classifier
20
20
  Dynamic: description
21
21
  Dynamic: description-content-type
22
22
  Dynamic: home-page
23
+ Dynamic: license-file
23
24
  Dynamic: requires-dist
24
25
  Dynamic: requires-python
25
26
  Dynamic: summary
@@ -40,9 +40,6 @@ SleePyPhases/phases/Training.py
40
40
  SleePyPhases/phases/Validation.py
41
41
  SleePyPhases/phases/VisualizeConfig.py
42
42
  SleePyPhases/phases/__init__.py
43
- SleePyPhases/recordloaders/MedicalDB.py
44
- SleePyPhases/recordloaders/RecordLoaderTSM.py
45
- SleePyPhases/recordloaders/__init__.py
46
43
  tests/__init__.py
47
44
  tests/conftest.py
48
45
  tests/unit/__init__.py
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="SleePyPhases",
8
- version="v0.2.0"[1:],
8
+ version="v0.3.0"[1:],
9
9
  author="Franz Ehrlich",
10
10
  author_email="fehrlichd@gmail.com",
11
11
  description="A framwork for creating deep learning pipelines for sleep data",
@@ -1,124 +0,0 @@
1
- import sys
2
-
3
- from pyPhases.util.Logger import classLogger
4
- from pypika import Table, PostgreSQLQuery as Query
5
- from pypika.queries import QueryBuilder
6
-
7
-
8
- @classLogger
9
- class MedicalDB:
10
- connection = None
11
- client = None
12
- assocCursor = None
13
- _instance = None
14
-
15
- config = {
16
- "user": "root",
17
- "password": "example",
18
- "host": "localhost",
19
- "port": 5432,
20
- "database": "data",
21
- }
22
-
23
- @classmethod
24
- def get(cls) -> "MedicalDB":
25
- if cls._instance is None:
26
- cls._instance = cls.__new__(cls)
27
- cls._instance.connect()
28
- return cls._instance
29
-
30
- def connect(self):
31
-
32
- import psycopg2
33
-
34
- try:
35
- c = MedicalDB.config
36
- conn = psycopg2.connect(
37
- "host=%s port=%i dbname=%s user=%s password=%s"
38
- % (
39
- c["host"],
40
- c["port"],
41
- c["database"],
42
- c["user"],
43
- c["password"],
44
- )
45
- )
46
-
47
- except psycopg2.Error as e:
48
- self.logError(f"Error connecting to Postgres Platform: {e}")
49
- sys.exit(1)
50
- self.conn = conn
51
- self.cursor = conn.cursor()
52
-
53
- def execute(self, sql, **vars):
54
- if isinstance(sql, QueryBuilder):
55
- sql = sql.get_sql()
56
- return self.cursor.execute(sql, **vars)
57
-
58
- def executeAndFetch(self, sql, **vars):
59
- self.execute(sql, **vars)
60
- return self.cursor.fetchall()
61
-
62
- def commit(self):
63
- self.conn.commit()
64
-
65
- def executeAndFetchOne(self, sql, **vars):
66
- self.execute(sql, **vars)
67
- return self.cursor.fetchone()
68
-
69
- def close(self):
70
- if self.connection is not None:
71
- self.connection.close()
72
-
73
- def fillRecordId(self, id):
74
- return id
75
-
76
- def getRecordData(self, recordId):
77
- record = Table("record", schema=self.config["schema"])
78
- patient = Table("patient", schema=self.config["schema"])
79
- medCase = Table("medical_case", schema=self.config["schema"])
80
- q = (
81
- Query.from_(record)
82
- .select(patient.id_patient, record.id_medical_case, record.start, patient.date_of_birth, patient.gender)
83
- .join(medCase).on(record.id_medical_case == medCase.id_medical_case)
84
- .join(patient).on(patient.id_patient == medCase.id_patient)
85
- .where(record.id_record == recordId)
86
- )
87
- row = self.executeAndFetchOne(q)
88
-
89
- if row is None:
90
- return None
91
-
92
- row = list(row)
93
- age = None if row[2] or row[3] is None else row[2] - row[3]
94
- row[3] = age
95
-
96
- return row
97
-
98
- def getDiagnoses(self, recordId):
99
- diag = Table("diagnosis", schema=self.config["schema"])
100
- record = Table("record", schema=self.config["schema"])
101
- medCase = Table("medical_case", schema=self.config["schema"])
102
- q = Query.from_(diag)\
103
- .select("diagnosis_category_id", "diagnosis")\
104
- .join(medCase).on(diag.id_medical_case == medCase.id_medical_case)\
105
- .join(record).on(medCase.id_medical_case == record.id_medical_case)\
106
- .where(record.id_record == recordId)\
107
- .get_sql()
108
-
109
- diagnoses = self.executeAndFetch(q)
110
-
111
- diagnoseCats = {d[0]: d[1] for d in diagnoses}
112
- return diagnoseCats
113
-
114
- def getPSGType(self, recordId):
115
-
116
- record = Table("record", schema=self.config["schema"])
117
- q = (
118
- Query.from_(record)
119
- .select("psg_config")
120
- .where(record.id_record == recordId)
121
- )
122
- r = self.executeAndFetchOne(q)
123
-
124
- return r[0] if bool(r) else ""
@@ -1,34 +0,0 @@
1
- from SleePyPhases.recordloaders.MedicalDB import MedicalDB
2
- from SleepHarmonizer import RecordLoaderAlice
3
-
4
-
5
- class RecordLoaderTSM(RecordLoaderAlice):
6
- config = {}
7
-
8
- def getMetaData(self, recordName):
9
- metadata = super().getMetaData(recordName)
10
- del metadata["patientName"]
11
- del metadata["patientCode"]
12
- del metadata["patientAdd"]
13
- del metadata["technician"]
14
-
15
- db = MedicalDB.get()
16
- row = db.getRecordData(recordName)
17
- diagnoses = db.getDiagnoses(recordName)
18
- metadata["psg_type"] = db.getPSGType(recordName)
19
-
20
- if row is not None:
21
- patient_id, case_id, recordDate, patientAge, gender = row
22
-
23
- metadata["patient"] = patient_id
24
- metadata["case"] = case_id
25
- metadata["age"] = patientAge
26
- metadata["gender"] = gender
27
- metadata["startRecord"] = recordDate
28
-
29
- if diagnoses is not None:
30
- metadata.update(diagnoses)
31
-
32
- db.close()
33
-
34
- return metadata
File without changes
File without changes
File without changes
File without changes