Skip to content

Commit 2c85cbe

Browse files
authored
Merge pull request #57 from MELDProject/dev_docker
QC results
2 parents b3257cb + 51dfd34 commit 2c85cbe

File tree

7 files changed

+220
-34
lines changed

7 files changed

+220
-34
lines changed
210 KB
Loading
321 KB
Loading
171 KB
Loading

docs/interpret_results.md

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ After viewing these images, we recommend then viewing the predictions superimpos
77
- Performing quality control
88
- Viewing the .png images of predicted lesions
99

10-
#### Main outputs
10+
## Main outputs
1111

1212
The predictions are saved as NIFTI files in the folder:
1313
/output/predictions_reports/<subject_id>/predictions
@@ -21,37 +21,38 @@ The predictions are saved as NIFTI files in the folder:
2121
The command will create the file predictions_merged_t1.nii.gz which corresponds to the predictions masks merged with T1 in RGB format. It can be viewed on RGB viewer or used to transfert on PACS system.
2222

2323

24-
#### Viewing the predicted clusters
24+
## Viewing the predicted clusters
2525
The MELD pdf report and .png images of the predicted lesions are saved in the folder:
2626
/output/predictions_reports/<subject_id>/reports
2727

2828

2929
The first image is called inflatbrain_<subject_id>.png
3030

31-
![inflated](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/inflatbrain_sub-test001.png)
31+
![inflated](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/inflatbrain_sub-00003.png)
3232

3333
This image tells you the number of predicted clusters and shows on the inflated brain where the clusters are located.
3434

3535
The next images are mri_<subject_id>_<hemi>_c*.png
3636

3737
E.g.
3838

39-
![mri](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/mri_sub-test001_right_c1.png)
39+
![mri](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/mri_sub-00003_right_c1.png)
4040

4141
These images show the cluster on the volumetric T1 image in red and the 20% most salient voxels (i.e. with high confidence) in orange. Each cluster has its own image e.g. mri_<subject_id>_<hemi>_c1.png for cluster 1 and mri_<subject_id>_<hemi>_c2.png for cluster 2.
4242

4343

44-
#### Saliency
44+
## Saliency
4545

4646
The next images are called saliency_<subject_id>_<hemi>_c*.png. Each cluster has a saliency image associated with it. E.g.
4747

48-
![saliency](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/saliency_sub-test001_right_c1.png)
48+
![saliency](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/saliency_sub-00003_right_c1.png)
4949

5050
These detail:
5151
* The hemisphere the cluster is on
52-
* The surface area of the cluster (across the cortical surface)
53-
* The cortical region in which the cluster is located
54-
* The confidence score of the predicted cluster (between 0 and 1)
52+
* The surface area of the cluster (across the cortical surface) in cm2
53+
* The cortical region in which the cluster mass centre is located
54+
* The confidence score of the predicted cluster (in %)
55+
* The integers values used to labelled this cluster and its salient vertices on the NIfTI prediction file.
5556
* The z-scores of the patient’s cortical features averaged within the cluster. In this example, the most abnormal features are the intrinsic curvature (folding measure) and the sulcal depth.
5657
* The saliency of each feature to the network - if a feature is brighter pink, that feature was more important to the network. In this example, the intrinsic curvature is most important to the network’s prediction
5758

@@ -70,21 +71,56 @@ If you only provide a T1 image, the FLAIR features will not be included in the s
7071

7172
The information hereabove mentioned about each cluster are summarised into the csv file info_clusters_<subject_id>.csv
7273

73-
#### Viewing the predictions on the T1 and quality control
74+
## Viewing the predictions on the T1 and quality control
7475

7576
It is important to check that the clusters detected are not due to obvious FreeSurfer reconstruction errors, scan artifacts etc.
7677

77-
Note: The following commands works only with a native installation. Guidelines for docker and singularity users will come soon.
78+
::::{tab-set}
79+
:::{tab-item} Docker
80+
:sync: docker
7881

79-
Run with native installation:
82+
Note: Docker does not allow GUI interface, therefore to run the QC you will need to have a stand alone installation of FreeSurfer/FreeView to enable the visualisation.
83+
84+
Open a terminal and `cd` to where you extracted the release zip.
85+
86+
You will need to first activate FreeSurfer
87+
```bash
88+
export FREESURFER_HOME=<freesurfer_installation_directory>
89+
source $FREESURFER_HOME/SetUpFreeSurfer.sh
90+
```
91+
Then run the command:
92+
```bash
93+
python scripts/new_patient_pipeline/new_pt_qc_script_stanalone.py -id <subject_id> -meld_data <path_to_meld_data_folder>
94+
```
95+
:::
96+
97+
:::{tab-item} Native
98+
:sync: native
99+
100+
Open a terminal and `cd` to the meld graph folder.
101+
102+
You will need to first activate FreeSurfer
103+
```bash
104+
export FREESURFER_HOME=<freesurfer_installation_directory>
105+
source $FREESURFER_HOME/SetUpFreeSurfer.sh
106+
```
107+
108+
Then run the command:
80109
```bash
81110
./meldgraph.sh new_pt_qc_script.py -id <subject_id>
82111
```
112+
113+
:::
114+
::::
115+
116+
117+
This will open FreeView and load the T1 and FLAIR (where available) volumes as well as the classifier predictions on the left and right hemispheres. It will also load the FreeSurfer pial and white surfaces. It should look like that:
118+
83119
![qc_surface](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/qc_surface.png)
84120

85-
This will open FreeView and load the T1 and FLAIR (where available) volumes as well as the classifier predictions on the left and right hemispheres. It will also load the FreeSurfer pial and white surfaces. It will look like this:
86121

87122
You can scroll through and find the predicted clusters.
123+
88124
![qc_surface](https://raw.githubusercontent.com//MELDProject/meld_graph/main/docs/images/qc_cluster.png)
89125

90126
Example of a predicted cluster (orange) on the right hemisphere. It is overlaid on a T1 image, with the right hemisphere pial and white surfaces visualised. Red arrows point to the cluster.

scripts/new_patient_pipeline/new_pt_qc_script.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
import argparse
1010
import subprocess as sub
1111
import glob
12+
from meld_graph.tools_pipeline import get_anat_files
1213
from meld_graph.paths import MELD_DATA_PATH, FS_SUBJECTS_PATH
13-
14+
1415
def return_file(path, file_name):
1516
files = glob.glob(path)
1617
if len(files)>1 :
@@ -44,13 +45,10 @@ def return_file(path, file_name):
4445
if not os.path.isdir(subject_fs_folder):
4546
print(f'Freesurfer outputs does not exist for this subject. Unable to perform qc')
4647
else :
48+
subject_dict = get_anat_files(subject)
4749
#select inputs files T1 and FLAIR
48-
T1_file = return_file(os.path.join(subject_dir, 'T1', '*.nii*'), 'T1')
49-
if T1_file is None:
50-
T1_file = return_file(os.path.join(subject_dir, 'anat', '*T1*.nii*'), 'T1')
51-
FLAIR_file = return_file(os.path.join(subject_dir, 'anat', '*FLAIR*.nii*'), 'FLAIR')
52-
else:
53-
FLAIR_file = return_file(os.path.join(subject_dir, 'FLAIR', '*.nii*'), 'FLAIR')
50+
T1_file = subject_dict['T1_path']
51+
FLAIR_file = subject_dict['FLAIR_path']
5452
#select predictions files
5553
pred_lh_file = return_file(os.path.join(pred_dir, 'predictions', 'lh.prediction.nii*'), 'lh_prediction')
5654
pred_rh_file = return_file(os.path.join(pred_dir, 'predictions', 'rh.prediction.nii*'), 'rh_prediction')
@@ -78,6 +76,5 @@ def return_file(path, file_name):
7876
print('Could not find either T1 volume')
7977
pass
8078

81-
8279

8380

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
## This script open freeview with MRI images, MELD predictions and surfaces for quality check of segmentation
2+
3+
4+
## To run : python new_pt_qc_script.py -id <sub_id>
5+
6+
7+
import os
8+
import sys
9+
import argparse
10+
import subprocess as sub
11+
import bids.layout
12+
import json
13+
import glob
14+
15+
def return_meld_T1_FLAIR(meld_dir, subject_id):
16+
subject_data={}
17+
subject_data['id'] = subject_id
18+
for modality in ['T1', 'FLAIR']:
19+
files = glob.glob(os.path.join(meld_dir, subject_id, modality, "*.nii*"))
20+
if len(files)==1:
21+
subject_data[f"{modality}_path"] = files[0]
22+
elif len(files)>1:
23+
print((f'Find too much volumes for {modality}. Check and remove the additional volumes with same key name', subject_id, 'WARNING'))
24+
return None
25+
else:
26+
subject_data[f"{modality}_path"] = None
27+
return subject_data
28+
29+
def return_bids_T1_FLAIR(bids_dir, subject_id):
30+
subject_data={}
31+
subject_data['id'] = subject_id
32+
if 'sub-' in subject_id:
33+
subject_id = subject_id.split('sub-')[-1]
34+
print(subject_id)
35+
# get bids structure
36+
layout = bids.layout.BIDSLayout(bids_dir)
37+
print(layout)
38+
# find parameters to extract bids file
39+
config_file = os.path.join(bids_dir, 'meld_bids_config.json')
40+
with open(config_file, "r") as json_file:
41+
dict = json.load(json_file)
42+
# Create query
43+
for modality in ['T1', 'FLAIR']:
44+
query = dict[modality]
45+
query['subject'] = subject_id
46+
# Get a list of matching files
47+
files = layout.get(return_type='file', extension=['nii.gz'], **query)
48+
if len(files)==1:
49+
subject_data[f"{modality}_path"] = files[0]
50+
elif len(files)>1:
51+
print(f'Find too much volumes for {modality}. Check and remove the additional volumes with same key name', subject_id, 'WARNING')
52+
return None
53+
else:
54+
subject_data[f"{modality}_path"] = None
55+
return subject_data
56+
57+
def get_anat_files(subject_id, meld_data_path):
58+
'''
59+
return path of T1 and FLAIR if BIDs format or MELD format
60+
'''
61+
input_dir = os.path.join(meld_data_path, "input")
62+
subject_data_meld = return_meld_T1_FLAIR(input_dir, subject_id)
63+
if subject_data_meld is None:
64+
return None
65+
if subject_data_meld['T1_path'] is None:
66+
subject_data_bids = return_bids_T1_FLAIR(input_dir, subject_id)
67+
if subject_data_bids is None:
68+
return None
69+
if subject_data_bids['T1_path'] is None:
70+
print(f'ERROR: Could not find any T1w nifti file. Please ensure your data are in MELD or BIDS format')
71+
return None
72+
else:
73+
subject_data = subject_data_bids
74+
else:
75+
subject_data = subject_data_meld
76+
print(f'INFO: T1 file used : {subject_data[f"T1_path"]} ')
77+
if subject_data['FLAIR_path'] is None:
78+
print(f'INFO: No FLAIR found')
79+
else:
80+
print(f'ERROR: FLAIR file used : {subject_data[f"FLAIR_path"]}')
81+
82+
return subject_data
83+
84+
def return_file(path, file_name):
85+
files = glob.glob(path)
86+
if len(files)>1 :
87+
print(f'ERROR: Find too much volumes for {file_name}. Check and remove the additional volumes with same key name')
88+
return None
89+
elif not files:
90+
print(f'ERROR: Could not find {file_name} volume. Check if name follow the right nomenclature')
91+
return None
92+
else:
93+
return files[0]
94+
95+
if __name__ == '__main__':
96+
97+
#parse commandline arguments
98+
parser = argparse.ArgumentParser(description='perform cortical parcellation using recon-all from freesurfer')
99+
parser.add_argument('-id','--id_subj',
100+
help='Subject ID.',
101+
required=True,)
102+
parser.add_argument('-meld_data','--meld_data',
103+
help='MELD data folder.',
104+
required=True,)
105+
args = parser.parse_args()
106+
subject=str(args.id_subj)
107+
meld_data_path=args.meld_data
108+
109+
# get subject folder and fs folder
110+
subject_dir = os.path.join(meld_data_path,'input', subject)
111+
pred_dir = os.path.join(meld_data_path,'output', 'predictions_reports', subject)
112+
subject_fs_folder = os.path.join(meld_data_path, 'output', 'fs_outputs', subject)
113+
114+
#initialise freesurfer variable environment
115+
ini_freesurfer = format("$FREESURFER_HOME/SetUpFreeSurfer.sh")
116+
117+
# Find inputs T1 and FLAIR if exists
118+
if not os.path.isdir(subject_fs_folder):
119+
print(f'Freesurfer outputs does not exist for this subject. Unable to perform qc')
120+
else :
121+
subject_dict = get_anat_files(subject, meld_data_path)
122+
#select inputs files T1 and FLAIR
123+
T1_file = subject_dict['T1_path']
124+
FLAIR_file = subject_dict['FLAIR_path']
125+
#select predictions files
126+
pred_lh_file = return_file(os.path.join(pred_dir, 'predictions', 'lh.prediction.nii*'), 'lh_prediction')
127+
pred_rh_file = return_file(os.path.join(pred_dir, 'predictions', 'rh.prediction.nii*'), 'rh_prediction')
128+
129+
#setup cortical segmentation command
130+
file_text = os.path.join(meld_data_path, 'temp1.txt')
131+
if T1_file:
132+
#create txt file with freeview commands
133+
with open(file_text, 'w') as f:
134+
f.write(f'-v {T1_file}:colormap=grayscale -layout 2 \n')
135+
if FLAIR_file:
136+
f.write(f'-v {FLAIR_file}:colormap=grayscale \n')
137+
if (pred_lh_file!=None) & (pred_rh_file!=None):
138+
f.write(f'-v {pred_lh_file}:colormap=lut \n')
139+
f.write(f'-v {pred_rh_file}:colormap=lut \n')
140+
f.write(f'-f {subject_fs_folder}/surf/lh.white:edgecolor=yellow {subject_fs_folder}/surf/lh.pial:edgecolor=red {subject_fs_folder}/surf/rh.white:edgecolor=yellow {subject_fs_folder}/surf/rh.pial:edgecolor=red \n')
141+
#launch freeview
142+
freeview = format(f"freeview -cmd {file_text}")
143+
command = ini_freesurfer + ';' + freeview
144+
print(f"INFO : Open freeview")
145+
sub.check_call(command, shell=True)
146+
os.remove(file_text)
147+
148+
else:
149+
print('Could not find either T1 volume')
150+
pass
151+
152+
153+

scripts/new_patient_pipeline/run_script_preprocessing.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,19 @@ def run_data_processing_new_subjects(subject_ids, harmo_code, compute_harmonisat
202202
norm.intra_inter_subject(feature, params_norm = param_norms_file)
203203
norm.asymmetry_subject(feature, params_norm = param_norms_file )
204204

205-
### PLOT FEATURES FOR QC
206-
#-----------------------------------------------------------------
207-
features_to_plot = [ ".inter_z.asym.intra_z" + feature for feature in features_combat]
208-
c_norm = MeldCohort(hdf5_file_root="{site_code}_{group}_featurematrix_combat.hdf5", dataset=tmp.name, data_dir=BASE_PATH)
209-
plot = Preprocess(c_norm,
210-
site_codes=[harmo_code],
211-
write_output_file=None,
212-
data_dir=output_dir)
213-
214-
print(get_m(f'Plot features to QC', None, 'STEP'))
215-
plot.plot_subject_features(features_to_plot)
216-
217-
tmp.close()
205+
### PLOT FEATURES FOR QC
206+
#-----------------------------------------------------------------
207+
features_to_plot = [ ".inter_z.asym.intra_z" + feature for feature in features_combat]
208+
c_norm = MeldCohort(hdf5_file_root="{site_code}_{group}_featurematrix_combat.hdf5", dataset=tmp.name, data_dir=BASE_PATH)
209+
plot = Preprocess(c_norm,
210+
site_codes=[harmo_code],
211+
write_output_file=None,
212+
data_dir=output_dir)
213+
214+
print(get_m(f'Plot features to QC', None, 'STEP'))
215+
plot.plot_subject_features(features_to_plot)
216+
217+
tmp.close()
218218

219219
def run_script_preprocessing(list_ids=None, sub_id=None, harmo_code='noHarmo', output_dir=BASE_PATH, demographic_file=None, harmonisation_only=False, withoutflair=False, verbose=False):
220220
harmo_code = str(harmo_code)

0 commit comments

Comments
 (0)