Skip to content

Commit 70d39cd

Browse files
Add results postprocessing
1 parent d17be1d commit 70d39cd

File tree

6 files changed

+202
-3
lines changed

6 files changed

+202
-3
lines changed

postprocess.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import glob
2+
import os
3+
from argparse import ArgumentParser
4+
5+
import nibabel as nib
6+
7+
from postprocess.postprocess_service import PostprocessService
8+
9+
10+
def postproc():
11+
postproc_srv = PostprocessService()
12+
13+
parser = ArgumentParser(description='Post processing of results')
14+
parser.add_argument('--results', required=True, type=str, help='path to results')
15+
args = parser.parse_args()
16+
17+
basedir = args.results
18+
mean_path = os.path.join(basedir, 'mean_result.nii')
19+
ds_path = os.path.join(basedir, 'dataset.csv')
20+
21+
ids = []
22+
results = []
23+
paths = glob.glob(os.path.join(basedir, '*/'), recursive=True)
24+
for path in paths:
25+
ids.append(os.path.basename(os.path.dirname(path)))
26+
results.append(os.path.join(path, '_subject_id_01', 'result.nii'))
27+
28+
size = len(results)
29+
if size < 1:
30+
print(f'No results found in [{basedir}]')
31+
return
32+
else:
33+
print(f'[{size}] results found in [{basedir}]')
34+
35+
print(f"Generating mean result image from [{size}] results...")
36+
mean_nifti_image = postproc_srv.get_mean_image(results, 10)
37+
nib.save(mean_nifti_image, mean_path)
38+
print(f"Mean result image written to [{mean_path}]")
39+
40+
print(f"Building dataset from [{size}] results...")
41+
dataframe = postproc_srv.get_dataframe(basedir, ids)
42+
dataframe.to_csv(ds_path, index=False, sep=';')
43+
print(f"Dataset CSV written to [{mean_path}]")
44+
45+
46+
if __name__ == '__main__':
47+
postproc()

postprocess.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env bash
2+
3+
#OAR -l walltime=2
4+
#OAR -O ./log/postprocess_log_%jobid%.stdout
5+
#OAR -E ./log/postprocess_log_%jobid%.stderr
6+
#OAR -q production
7+
8+
TAG="fmri-confs-runner"
9+
10+
BASE="/home/ymerel/empenn_group_storage/private/ymerel"
11+
RESULTS="$BASE/results/auditory"
12+
13+
g5k-setup-docker -t
14+
docker pull ghcr.io/inria-empenn/fmri-confs-runner:latest
15+
docker run -u root -v "$RESULTS:/results" $TAG python postprocess.py --results /results
16+

postprocess/__init__.py

Whitespace-only changes.

postprocess/correlation_service.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import numpy as np
2+
import pandas as pd
3+
from nibabel import load, Nifti1Image
4+
from nibabel.processing import resample_from_to
5+
from numpy import corrcoef, reshape
6+
from pandas import DataFrame
7+
from scipy.stats import spearmanr
8+
from typing import List
9+
10+
11+
class CorrelationService:
12+
def compute_correlations(self, image, images: List[str]) -> DataFrame:
13+
print(f"Computing correlations from [{image}] to [{len(images)}] images... ", end='')
14+
dfs = []
15+
for img in images:
16+
corr = self.get_correlation_coefficient(image, img, 'spearman')
17+
dfs.append(pd.DataFrame([[image, img, corr]], columns=['source', 'target', 'correlation']))
18+
merged = pd.concat(dfs, ignore_index=True)
19+
print("OK")
20+
return merged.sort_values(by='correlation', ascending=False)
21+
22+
def get_correlation_coefficient(self,
23+
file_1: str, file_2: str, method: str = 'pearson') -> float:
24+
""" Return the correlation coefficient of two images.
25+
26+
Arguments :
27+
- file_1, str - path to the first image
28+
- file_2, str - path to the second image ; file_2 will be resampled on file_1
29+
- method, str - either 'pearson', or 'spearman': the correlation method to use
30+
- reslice_on_file_2, bool - set to :
31+
- True if you wish to reslice file_1 on file_2
32+
- False otherwise
33+
34+
Returns :
35+
- _, float - the correlation coefficient of the two input images,
36+
using the passed method
37+
"""
38+
39+
# Load images
40+
image_1 = load(file_1)
41+
image_2 = load(file_2)
42+
43+
# Set masking using NaN's
44+
image_1 = self.mask_using_zeros(image_1)
45+
image_2 = self.mask_using_zeros(image_2)
46+
47+
# Resample using nearest neighbours
48+
image_2 = resample_from_to(image_2, image_1, order=0)
49+
50+
# Make 1D vectors from the images data
51+
data_1 = reshape(image_1.get_fdata(), -1)
52+
data_2 = reshape(image_2.get_fdata(), -1)
53+
54+
# Compute the correlation coefficient
55+
if method == 'pearson':
56+
return corrcoef(data_1, data_2)[0][1]
57+
if method == 'spearman':
58+
return spearmanr(data_1, data_2).correlation
59+
60+
raise AttributeError(f'Wrong correlation method provided: {method}.')
61+
62+
def mask_using_zeros(self, data_image: Nifti1Image) -> Nifti1Image:
63+
""" Mask an image by replacing NaNs with zeros.
64+
65+
Arguments:
66+
- data_image, nibabel.Nifti1Image : the image to mask
67+
68+
Returns:
69+
- the masked image as nibabel.Nifti1Image
70+
"""
71+
72+
# Get data from the image
73+
data = data_image.get_fdata()
74+
75+
# Replace NaNs by zeros
76+
data[np.isnan(data)] = 0.0
77+
78+
# Return data as an image
79+
return Nifti1Image(data, data_image.affine)

postprocess/postprocess_service.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import os
2+
3+
import nibabel as nib
4+
import numpy as np
5+
import pandas as pd
6+
from typing import List
7+
8+
from postprocess.correlation_service import CorrelationService
9+
10+
11+
class PostprocessService:
12+
corr_srv = CorrelationService()
13+
14+
def get_dataframe(self, path, ids: List[str]) -> pd.DataFrame:
15+
dataframes = []
16+
ref_contrast = os.path.join(path, 'ref', '_subject_id_01', 'result.nii')
17+
mean = os.path.join(path, 'mean_result.nii')
18+
for conf_id in ids:
19+
contrast = os.path.join(path, conf_id, '_subject_id_01', 'result.nii')
20+
config = os.path.join(path, conf_id, 'config.csv')
21+
df = pd.read_csv(config, delimiter=';').astype(bool)
22+
df['id'] = conf_id
23+
df['from_ref'] = self.corr_srv.get_correlation_coefficient(contrast, ref_contrast, 'spearman')
24+
df['from_mean'] = self.corr_srv.get_correlation_coefficient(contrast, mean, 'spearman')
25+
dataframes.append(df)
26+
27+
return pd.concat(dataframes, ignore_index=True)
28+
29+
def get_mean_image(self, inputs: list, batch_size: int) -> nib.Nifti1Image:
30+
total_sum = None
31+
count = 0
32+
33+
for i in range(0, len(inputs), batch_size):
34+
batch_paths = inputs[i:i + batch_size]
35+
batch_images = [nib.load(path).get_fdata() for path in batch_paths]
36+
37+
# Stack the batch images into a single numpy array
38+
batch_array = np.stack(batch_images, axis=0)
39+
40+
# Calculate the sum of the batch
41+
batch_sum = np.sum(batch_array, axis=0)
42+
43+
# Accumulate the sum and count
44+
if total_sum is None:
45+
total_sum = batch_sum
46+
else:
47+
total_sum += batch_sum
48+
49+
count += len(batch_paths)
50+
51+
# Calculate the mean image
52+
mean_image = total_sum / count
53+
54+
# Create a new NIfTI image with the mean data
55+
mean_nifti = nib.Nifti1Image(mean_image, affine=nib.load(inputs[0]).affine)
56+
57+
return mean_nifti

abaca.sh renamed to run_configs.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
#OAR --array 10
44
#OAR -l walltime=4
5-
#OAR -O ./log/log_%jobid%.stdout
6-
#OAR -E ./log/log_%jobid%.stderr
5+
#OAR -O ./log/run_config_log_%jobid%.stdout
6+
#OAR -E ./log/run_config_log_%jobid%.stderr
77
#OAR -q production
88

99
TAG="fmri-confs-runner"
@@ -25,7 +25,7 @@ fi
2525
echo "Running configuration is [$CONF]"
2626

2727
g5k-setup-docker -t
28-
docker build . -t $TAG
28+
docker pull ghcr.io/inria-empenn/fmri-confs-runner:latest
2929
if [ "$OAR_ARRAY_INDEX" -eq 1 ]; then
3030
# write ref config only for the first job
3131
docker run -u root -v "$DATA:/data" -v "$RESULTS:/results" -v "$WORK:/work" -v "$CONFIGS:/configs" $TAG python run.py --configs "/configs/$CONF" --data /data/data_desc.json --ref /configs/config_ref.csv

0 commit comments

Comments
 (0)