diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..b42097e86
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+**/__pycache__/
\ No newline at end of file
diff --git a/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.py b/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.py
index a9f3ca5c5..8bd4d9dd4 100644
--- a/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.py
+++ b/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.py
@@ -3,7 +3,7 @@
from yapsy.IPlugin import IPlugin
from PyQt5 import QtGui, QtCore, QtWidgets, uic
import joblib
-import sys, os
+import sys, os, time
import seaborn as sns
import numpy as np
@@ -23,6 +23,7 @@ def __init__(self):
self.plotCanvas.axes = self.plotCanvas.fig.add_subplot(111)
self.SPAREs = None
self.ui.stackedWidget.setCurrentIndex(0)
+ self.ui.factorial_progressBar.setValue(0)
def getUI(self):
@@ -43,17 +44,20 @@ def SetupConnections(self):
# are present in data frame
if ('SPARE_BA' in self.datamodel.GetColumnHeaderNames() and
'SPARE_AD' in self.datamodel.GetColumnHeaderNames()):
+ self.ui.show_SPARE_scores_from_data_Btn.setStyleSheet("background-color: rgb(230,230,255)")
self.ui.show_SPARE_scores_from_data_Btn.setEnabled(True)
+ self.ui.show_SPARE_scoresfrom_data_Btn.setToolTip('The data frame has variables `SPARE_AD` and `SPARE_BA` so these can be plotted.')
else:
self.ui.show_SPARE_scores_from_data_Btn.setEnabled(False)
- # Allow loading of SPARE-* model only when harmonized residuals are
- # present
- if 'RES_ICV_Sex_MUSE_Volume_47' in self.datamodel.GetColumnHeaderNames():
- self.ui.load_SPARE_model_Btn.setEnabled(True)
- else:
- self.ui.load_SPARE_model_Btn.setEnabled(False)
+ # Allow loading of SPARE-* model always, even when residuals are not
+ # calculated yet
+ self.ui.load_SPARE_model_Btn.setEnabled(True)
+
+ def updateProgress(self, txt, vl):
+ self.ui.SPARE_computation_info.setText(txt)
+ self.ui.factorial_progressBar.setValue(vl)
def OnLoadSPAREModel(self):
@@ -64,25 +68,56 @@ def OnLoadSPAREModel(self):
if fileName != "":
self.model['BrainAge'], self.model['AD'] = joblib.load(fileName)
self.ui.compute_SPARE_scores_Btn.setEnabled(True)
+ self.ui.SPARE_model_info.setText('File: %s' % (fileName))
self.ui.stackedWidget.setCurrentIndex(0)
-
- def OnComputeSPAREs(self):
if 'RES_ICV_Sex_MUSE_Volume_47' in self.datamodel.GetColumnHeaderNames():
- self.SPAREs = pd.DataFrame.from_dict({
- 'SPARE_BA': predictBrainAge(self.datamodel.data, self.model['BrainAge']),
- 'SPARE_AD': predictAD(self.datamodel.data, self.model['AD'])})
- self.plotSPAREs()
- self.ui.stackedWidget.setCurrentIndex(1)
+ self.ui.compute_SPARE_scores_Btn.setStyleSheet("background-color: rgb(230,255,230)")
+ self.ui.compute_SPARE_scores_Btn.setEnabled(True)
+ self.ui.compute_SPARE_scores_Btn.setToolTip('Model loaded and `RES_ICV_Sex_MUSE_Volmue_*` available so the MUSE volumes can be harmonized.')
else:
+ self.ui.compute_SPARE_scores_Btn.setStyleSheet("background-color: rgb(255,230,230)")
+ self.ui.compute_SPARE_scores_Btn.setEnabled(False)
+ self.ui.compute_SPARE_scores_Btn.setToolTip('Model loaded but `RES_ICV_Sex_MUSE_Volmue_*` not available so the MUSE volumes can not be harmonized.')
+
+
print('No field `RES_ICV_Sex_MUSE_Volume_47` found. ' +
- 'Make sure to compute harmonized residuals first.')
+ 'Make sure to compute and add harmonized residuals first.')
+
+
+ def OnComputationDone(self, y_hat):
+ self.SPAREs = y_hat
+ self.plotSPAREs()
+ self.ui.stackedWidget.setCurrentIndex(1)
+
+
+
+ def OnComputeSPAREs(self):
+ # Setup tasks for long running jobs
+ # Using this example: https://realpython.com/python-pyqt-qthread/
+ self.thread = QtCore.QThread()
+ self.worker = BrainAgeWorker(self.datamodel.data, self.model)
+ self.worker.moveToThread(self.thread)
+ self.thread.started.connect(self.worker.run)
+ self.worker.done.connect(self.thread.quit)
+ self.worker.done.connect(self.worker.deleteLater)
+ self.thread.finished.connect(self.thread.deleteLater)
+ self.worker.progress.connect(self.updateProgress)
+ self.worker.done.connect(lambda y_hat: self.OnComputationDone(y_hat))
+ self.ui.factorial_progressBar.setRange(0, len(self.model['BrainAge']['scaler'])-1)
+ self.thread.start()
+ self.ui.compute_SPARE_scores_Btn.setEnabled(False)
def plotSPAREs(self):
+ # Plot data
sns.scatterplot(x='SPARE_AD', y='SPARE_BA', data=self.SPAREs,
- ax=self.plotCanvas.axes)
+ ax=self.plotCanvas.axes, linewidth=0,
+ facecolor=(0.5, 0.5, 0.5, 0.5), size=1, legend=None)
+ sns.despine(ax=self.plotCanvas.axes, trim=True)
+ self.plotCanvas.axes.set(ylabel='SPARE-BA', xlabel='SPARE-AD')
+ self.plotCanvas.axes.get_figure().set_tight_layout(True)
def OnAddToDataFrame(self):
@@ -103,58 +138,63 @@ def OnDataChanged(self):
if ('SPARE_BA' in self.datamodel.GetColumnHeaderNames() and
'SPARE_AD' in self.datamodel.GetColumnHeaderNames()):
self.ui.show_SPARE_scores_from_data_Btn.setEnabled(True)
+ self.ui.show_SPARE_scores_from_data_Btn.setStyleSheet("background-color: rgb(230,230,255)")
else:
self.ui.show_SPARE_scores_from_data_Btn.setEnabled(False)
- # Allow loading of SPARE-* model only when harmonized residuals are
- # present
- if 'RES_ICV_Sex_MUSE_Volume_47' in self.datamodel.GetColumnHeaderNames():
- self.ui.load_SPARE_model_Btn.setEnabled(True)
- else:
- self.ui.load_SPARE_model_Btn.setEnabled(False)
+class BrainAgeWorker(QtCore.QObject):
+
+ done = QtCore.pyqtSignal(pd.DataFrame)
+ progress = QtCore.pyqtSignal(str, int)
+
+ #constructor
+ def __init__(self, data, model):
+ super(BrainAgeWorker, self).__init__()
+ self.data = data
+ self.model = model
+
+ def run(self):
+ y_hat = pd.DataFrame.from_dict({'SPARE_BA': np.full((self.data.shape[0],),np.nan),
+ 'SPARE_AD': np.full((self.data.shape[0],),np.nan)})
-def predictBrainAge(data,model):
-
- idx = ~data[model['predictors'][0]].isnull()
+ # SPARE-BA
+ idx = ~self.data[self.model['BrainAge']['predictors'][0]].isnull()
- y_hat_test = np.zeros((np.sum(idx),))
- n_ensembles = np.zeros((np.sum(idx),))
+ y_hat_test = np.zeros((np.sum(idx),))
+ n_ensembles = np.zeros((np.sum(idx),))
- for i,_ in enumerate(model['scaler']):
- # Predict validation (fold) and test
- print('Fold %d' % (i))
- test = np.logical_not(data[idx]['participant_id'].isin(np.concatenate(model['train']))) | data[idx]['participant_id'].isin(model['validation'][i])
- X = data[idx].loc[test, model['predictors']].values
- X = model['scaler'][i].transform(X)
- y_hat_test[test] += (model['svm'][i].predict(X) - model['bias_ints'][i]) / model['bias_slopes'][i]
- n_ensembles[test] += 1.
+ for i,_ in enumerate(self.model['BrainAge']['scaler']):
+ # Predict validation (fold) and test
+ self.progress.emit('Computing SPARE-BA | Task 1 of 2', i)
+ test = np.logical_not(self.data[idx]['participant_id'].isin(np.concatenate(self.model['BrainAge']['train']))) | self.data[idx]['participant_id'].isin(self.model['BrainAge']['validation'][i])
+ X = self.data[idx].loc[test, self.model['BrainAge']['predictors']].values
+ X = self.model['BrainAge']['scaler'][i].transform(X)
+ y_hat_test[test] += (self.model['BrainAge']['svm'][i].predict(X) - self.model['BrainAge']['bias_ints'][i]) / self.model['BrainAge']['bias_slopes'][i]
+ n_ensembles[test] += 1.
- y_hat_test /= n_ensembles
- y_hat = np.full((data.shape[0],),np.nan)
- y_hat[idx] = y_hat_test
+ y_hat_test /= n_ensembles
+ y_hat.loc[idx, 'SPARE_BA'] = y_hat_test
- return y_hat
+ idx = ~self.data[self.model['BrainAge']['predictors'][0]].isnull()
+ y_hat_test = np.zeros((np.sum(idx),))
+ n_ensembles = np.zeros((np.sum(idx),))
-def predictAD(data,model):
-
- idx = ~data[model['predictors'][0]].isnull()
+ for i,_ in enumerate(self.model['AD']['scaler']):
+ # Predict validation (fold) and test
+ self.progress.emit('Computing SPARE-AD | Task 2 of 2', i)
- y_hat_test = np.zeros((np.sum(idx),))
- n_ensembles = np.zeros((np.sum(idx),))
+ test = np.logical_not(self.data[idx]['participant_id'].isin(np.concatenate(self.model['AD']['train']))) | self.data[idx]['participant_id'].isin(self.model['AD']['validation'][i])
+ X = self.data[idx].loc[test, self.model['AD']['predictors']].values
+ X = self.model['AD']['scaler'][i].transform(X)
+ y_hat_test[test] += self.model['AD']['svm'][i].decision_function(X)
+ n_ensembles[test] += 1.
- for i,_ in enumerate(model['scaler']):
- # Predict validation (fold) and test
- print('Fold %d' % (i))
- test = np.logical_not(data[idx]['participant_id'].isin(np.concatenate(model['train']))) | data[idx]['participant_id'].isin(model['validation'][i])
- X = data[idx].loc[test, model['predictors']].values
- X = model['scaler'][i].transform(X)
- y_hat_test[test] += model['svm'][i].decision_function(X)
- n_ensembles[test] += 1.
+ y_hat_test /= n_ensembles
+ y_hat.loc[idx, 'SPARE_AD'] = y_hat_test
- y_hat_test /= n_ensembles
- y_hat = np.full((data.shape[0],),np.nan)
- y_hat[idx] = y_hat_test
+ self.progress.emit('All done.', i)
- return y_hat
+ # Emit the result
+ self.done.emit(y_hat)
diff --git a/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.ui b/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.ui
index d74a721c6..de939006a 100644
--- a/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.ui
+++ b/QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.ui
@@ -11,7 +11,7 @@
- Form
+ Compute SPAREs
-
@@ -20,10 +20,23 @@
- 1
+ 0
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
-
@@ -34,6 +47,13 @@
+ -
+
+
+ Qt::Horizontal
+
+
+
-
@@ -41,6 +61,13 @@
+ -
+
+
+ No SPARE-* model loaded
+
+
+
-
@@ -51,6 +78,33 @@
+ -
+
+
+ No computation running
+
+
+
+ -
+
+
+ 24
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+