Skip to content

Commit e164240

Browse files
committed
Work on plotting.
1 parent a1bde3a commit e164240

File tree

4 files changed

+602
-2
lines changed

4 files changed

+602
-2
lines changed

analysis/plot_aslprep_cbf.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Plot scalar maps from QSIRecon."""
1+
"""Plot CBF maps from ASLPrep."""
22

33
import os
44
from glob import glob
@@ -48,4 +48,3 @@
4848
fig.suptitle(title)
4949
plt.savefig(os.path.join(out_dir, f"{title.replace(' ', '_')}.png"))
5050
plt.close()
51-

analysis/plot_rawdata.py

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env python
2+
3+
import shutil
4+
import subprocess
5+
from pathlib import Path
6+
7+
import ants
8+
import imageio.v3 as iio
9+
import matplotlib.pyplot as plt
10+
import nibabel as nb
11+
import numpy as np
12+
from nilearn import image, plotting
13+
14+
15+
data_root = Path("/Users/mcieslak/projects/hbcd/pipeline_paper/data_examples")
16+
17+
subid = "9645436710"
18+
sesid = "V02"
19+
20+
# The dir-PA nifti is always the first
21+
raw_nifti = data_root / "assembly_bids" / f"sub-{subid}_ses-{sesid}_dir-PA_run-1_dwi.nii"
22+
processed_nifti = (
23+
data_root
24+
/ "derivatives"
25+
/ "qsiprep"
26+
/ f"sub-{subid}_ses-{sesid}_space-ACPC_desc-preproc_dwi.nii"
27+
)
28+
raw_to_acpc_xfm = processed_nifti.parent / f"sub-{subid}_ses-{sesid}_from-raw_to-ACPC_rigid.mat"
29+
raw_mean_path = raw_nifti.parent / f"sub-{subid}_ses-{sesid}_dir-PA_run-1_dwi_mean.nii"
30+
processed_mean_path = (
31+
processed_nifti.parent / f"sub-{subid}_ses-{sesid}_space-ACPC_desc-preproc_dwi_mean.nii"
32+
)
33+
34+
# If there is not transform file, we need to run the registration
35+
if not Path(raw_to_acpc_xfm).exists():
36+
# Load the nifti files
37+
raw_img = nb.load(raw_nifti)
38+
processed_img = nb.load(processed_nifti)
39+
40+
# Get the data and calculate mean of first 4 volumes
41+
raw_data = raw_img.get_fdata()
42+
processed_data = processed_img.get_fdata()
43+
44+
raw_mean = raw_data[..., :4].mean(axis=3)
45+
processed_mean = processed_data[..., :4].mean(axis=3)
46+
47+
# Create new nifti images with the mean data
48+
raw_mean_img = nb.Nifti1Image(raw_mean, raw_img.affine, raw_img.header)
49+
processed_mean_img = nb.Nifti1Image(processed_mean, processed_img.affine, processed_img.header)
50+
51+
# Save the mean images
52+
nb.save(raw_mean_img, raw_mean_path)
53+
nb.save(processed_mean_img, processed_mean_path)
54+
55+
# Load the mean images directly with ANTs
56+
raw_mean_ants = ants.image_read(str(raw_mean_path))
57+
processed_mean_ants = ants.image_read(str(processed_mean_path))
58+
59+
# Perform rigid registration
60+
registration = ants.registration(
61+
fixed=processed_mean_ants, moving=raw_mean_ants, type_of_transform="Similarity"
62+
)
63+
shutil.copy(registration["fwdtransforms"][0], raw_to_acpc_xfm)
64+
# Apply transform using lanczoswindowedsinc interpolation
65+
registered_processed = ants.apply_transforms(
66+
fixed=processed_mean_ants,
67+
moving=raw_mean_ants,
68+
transformlist=registration["fwdtransforms"],
69+
interpolator="lanczosWindowedSinc",
70+
)
71+
72+
# Save the registered image
73+
registered_path = (
74+
processed_nifti.parent / f"sub-{subid}_ses-{sesid}_space-ACPC_desc-preproc_dwi_mean.nii"
75+
)
76+
ants.image_write(registered_processed, str(registered_path))
77+
78+
# use nilearn image.crop_img to get the cropped reference image
79+
ref_img_path = raw_nifti.parent / f"sub-{subid}_ses-{sesid}_space-ACPC_desc-cropped_dwi.nii"
80+
if not Path(ref_img_path).exists():
81+
# Call 3dAutobox using subprocess
82+
cmd = [
83+
"3dAutobox",
84+
"-prefix",
85+
str(ref_img_path),
86+
"-input",
87+
str(processed_mean_path),
88+
"-npad",
89+
"5",
90+
]
91+
92+
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
93+
94+
stdout, stderr = process.communicate()
95+
96+
if process.returncode != 0:
97+
raise RuntimeError(f"3dAutobox failed with error: {stderr.decode()}")
98+
99+
100+
def resample_processed_into_raw(image_index):
101+
"""Select a 3d volume from raw_nifti and transform the corresponding
102+
volume from processed_nifti so they can be plotted next to each other.
103+
104+
Parameters
105+
----------
106+
image_index : int
107+
The volume number to extract from the 4D datasets
108+
"""
109+
# Load the reference image
110+
ref_img = ants.image_read(str(ref_img_path))
111+
112+
# Load the image from image_index using nilearn, save as a single-volume nifti
113+
# No need to transform the processed image, it is already in ACPC space
114+
processed_vol_path = (
115+
processed_nifti.parent
116+
/ f"sub-{subid}_ses-{sesid}_space-ACPC_desc-preproc_dwi_vol-{image_index}.nii"
117+
)
118+
processed_vol = image.index_img(str(processed_nifti), image_index)
119+
processed_vol.to_filename(processed_vol_path)
120+
121+
# Transform the raw image at image_index to the ACPC space
122+
raw_nii = image.index_img(str(raw_nifti), image_index)
123+
raw_nii_path = raw_nifti.parent / f"sub-{subid}_ses-{sesid}_vol-{image_index}_raw.nii"
124+
raw_nii.to_filename(raw_nii_path)
125+
raw_ants = ants.image_read(str(raw_nii_path))
126+
raw_vol = ants.apply_transforms(
127+
fixed=ref_img,
128+
moving=raw_ants,
129+
transformlist=[str(raw_to_acpc_xfm)],
130+
interpolator="lanczosWindowedSinc",
131+
)
132+
resampled_raw_nii_path = (
133+
raw_nifti.parent / f"sub-{subid}_ses-{sesid}_vol-{image_index}_space-ACPC_raw.nii"
134+
)
135+
ants.image_write(raw_vol, str(resampled_raw_nii_path))
136+
137+
temp_files_to_delete = [
138+
raw_nii_path,
139+
]
140+
141+
for file in temp_files_to_delete:
142+
Path(file).unlink()
143+
144+
return resampled_raw_nii_path, processed_vol_path
145+
146+
147+
def make_figure(raw_nii_path, registered_nii_path, image_index, crop_proportion=0.1):
148+
"""Create a figure comparing raw and registered volumes.
149+
150+
Parameters
151+
----------
152+
raw_nii_path : str or Path
153+
Path to the raw nifti file
154+
registered_nii_path : str or Path
155+
Path to the registered nifti file
156+
image_index : int
157+
Volume number for labeling
158+
crop_proportion : float, optional
159+
Proportion of image edges to crop (default: 0.15 for 15%)
160+
"""
161+
# Load the specific volumes
162+
raw_nii = image.load_img(raw_nii_path)
163+
registered_nii = image.load_img(registered_nii_path)
164+
165+
slices_to_plot = {
166+
"x": [-10.0],
167+
"y": [-25.0],
168+
"z": [4.0],
169+
}
170+
171+
# Calculate number of rows needed for the plots
172+
n_planes = len(slices_to_plot)
173+
174+
# Create the figure
175+
fig, axes = plt.subplots(n_planes, 2, figsize=(15, 6.5 * n_planes))
176+
# Get the data range for this plane
177+
raw_data = raw_nii.get_fdata()
178+
reg_data = registered_nii.get_fdata()
179+
180+
# Calculate 2nd and 98th percentiles from combined data, clipped at 0
181+
all_data = np.clip(np.concatenate([raw_data.ravel(), reg_data.ravel()]), 0, None)
182+
vmin = np.percentile(all_data, 0.5)
183+
vmax = np.percentile(all_data, 99.5)
184+
185+
# Plot each plane
186+
for idx, (plane, coords) in enumerate(slices_to_plot.items()):
187+
# Plot raw volume
188+
plotting.plot_anat(
189+
raw_nii,
190+
display_mode=plane,
191+
cut_coords=coords,
192+
title="",
193+
axes=axes[idx, 0],
194+
draw_cross=False,
195+
annotate=False,
196+
vmin=vmin,
197+
vmax=vmax,
198+
)
199+
200+
# Plot registered volume
201+
plotting.plot_anat(
202+
registered_nii,
203+
display_mode=plane,
204+
cut_coords=coords,
205+
title="",
206+
axes=axes[idx, 1],
207+
draw_cross=False,
208+
annotate=False,
209+
vmin=vmin,
210+
vmax=vmax,
211+
)
212+
213+
# Remove all whitespace between subplots and at edges
214+
plt.subplots_adjust(hspace=0, wspace=0, left=0, right=1, bottom=0, top=1)
215+
216+
# Save the figure
217+
fig_path = raw_nifti.parent / f"sub-{subid}_ses-{sesid}_vol-{image_index}_comparison.png"
218+
plt.savefig(fig_path, dpi=300, bbox_inches="tight")
219+
plt.close()
220+
221+
# Crop the saved figure
222+
img = iio.imread(fig_path)
223+
224+
# Calculate crop dimensions based on the provided proportion
225+
h, w = img.shape[:2]
226+
crop_h = int(h * crop_proportion)
227+
crop_w = int(w * crop_proportion)
228+
229+
# Crop the image
230+
cropped = img[crop_h:-crop_h, crop_w:-crop_w]
231+
232+
# Save the cropped image
233+
iio.imwrite(fig_path, cropped)
234+
235+
236+
vols_to_plot = [14, 15, 16, 17, 18, 19, 20, 21, 22]
237+
for vol in vols_to_plot:
238+
print(f"Plotting volume {vol}")
239+
raw_nii_path, registered_nii_path = resample_processed_into_raw(vol)
240+
make_figure(raw_nii_path, registered_nii_path, vol)

analysis/plot_tedana_t2star.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Plot T2* maps from tedana."""
2+
3+
import os
4+
from glob import glob
5+
6+
import matplotlib.pyplot as plt
7+
from nilearn import image, plotting
8+
9+
10+
if __name__ == "__main__":
11+
in_dir = "/cbica/projects/pafin/derivatives/fmriprep"
12+
out_dir = "../figures"
13+
14+
patterns = {
15+
"T2star": "sub-*/ses-1/func/*_space-MNI152NLin6Asym_res-2_T2starmap.nii.gz",
16+
}
17+
for title, pattern in patterns.items():
18+
# Get all scalar maps
19+
scalar_maps = sorted(glob(os.path.join(in_dir, pattern)))
20+
scalar_maps = [f for f in scalar_maps if "PILOT" not in f]
21+
print(f"{title}: {len(scalar_maps)}")
22+
23+
mean_img = image.mean_img(scalar_maps, copy_header=True)
24+
sd_img = image.math_img("np.std(img, axis=3)", img=scalar_maps)
25+
26+
# Plot mean and SD
27+
fig, axs = plt.subplots(2, 1, figsize=(10, 5))
28+
plotting.plot_stat_map(
29+
mean_img,
30+
display_mode="z",
31+
cut_coords=[-30, -15, 0, 15, 30, 45, 60],
32+
title="Mean",
33+
axes=axs[0],
34+
figure=fig,
35+
)
36+
plotting.plot_stat_map(
37+
sd_img,
38+
display_mode="z",
39+
cut_coords=[-30, -15, 0, 15, 30, 45, 60],
40+
title="Standard Deviation",
41+
axes=axs[1],
42+
figure=fig,
43+
)
44+
fig.suptitle(title)
45+
plt.savefig(os.path.join(out_dir, f"{title.replace(' ', '_')}.png"))
46+
plt.close()

0 commit comments

Comments
 (0)