Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve SPARE-* plot #86

Merged
merged 1 commit into from
Nov 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/__pycache__/
152 changes: 96 additions & 56 deletions QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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):
Expand All @@ -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)
58 changes: 56 additions & 2 deletions QtBrainChartGUI/plugins/computeSPAREs/computeSPAREs.ui
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string>Compute SPAREs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
Expand All @@ -20,10 +20,23 @@
<string notr="true"/>
</property>
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="page">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="show_SPARE_scores_from_data_Btn">
<property name="enabled">
Expand All @@ -34,13 +47,27 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="load_SPARE_model_Btn">
<property name="text">
<string>Load SPARE Model</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="SPARE_model_info">
<property name="text">
<string>No SPARE-* model loaded</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="compute_SPARE_scores_Btn">
<property name="enabled">
Expand All @@ -51,6 +78,33 @@
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="SPARE_computation_info">
<property name="text">
<string>No computation running</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="factorial_progressBar">
<property name="value">
<number>24</number>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
Expand Down