|
6 | 6 | "metadata": {}, |
7 | 7 | "outputs": [], |
8 | 8 | "source": [ |
9 | | - "import nibabel as nb\n", |
10 | | - "import numpy as np\n", |
11 | | - "from pathlib import Path\n", |
12 | | - "from shutil import rmtree, copy, move\n", |
13 | | - "from importlib import reload\n", |
14 | 9 | "import asyncio\n", |
15 | | - "import nest_asyncio\n", |
16 | | - "\n", |
17 | | - "from scipy.ndimage import binary_dilation\n", |
18 | | - "from skimage.morphology import ball\n", |
| 10 | + "from pathlib import Path\n", |
| 11 | + "from shutil import copy, move\n", |
19 | 12 | "\n", |
| 13 | + "import nest_asyncio\n", |
| 14 | + "import nibabel as nb\n", |
20 | 15 | "import nitransforms as nt\n", |
| 16 | + "import numpy as np\n", |
21 | 17 | "from nipreps.synthstrip.wrappers.nipype import SynthStrip\n", |
22 | 18 | "from nipype.interfaces.afni import Volreg\n", |
| 19 | + "from scipy.ndimage import binary_dilation\n", |
| 20 | + "from skimage.morphology import ball\n", |
| 21 | + "\n", |
23 | 22 | "from eddymotion.registration import ants as erants\n", |
24 | 23 | "\n", |
25 | 24 | "nest_asyncio.apply()" |
|
31 | 30 | "metadata": {}, |
32 | 31 | "outputs": [], |
33 | 32 | "source": [ |
34 | | - "DATA_PATH = Path('/data/datasets/')\n", |
35 | | - "WORKDIR = Path.home() / 'tmp' / 'eddymotiondev' / 'ismrm25'\n", |
| 33 | + "DATA_PATH = Path(\"/data/datasets/\")\n", |
| 34 | + "WORKDIR = Path.home() / \"tmp\" / \"eddymotiondev\" / \"ismrm25\"\n", |
36 | 35 | "WORKDIR.mkdir(parents=True, exist_ok=True)\n", |
37 | 36 | "\n", |
38 | 37 | "OUTPUT_DIR = Path(\"/data/derivatives\") / \"eddymotion-ismrm25-exp2\"\n", |
|
46 | 45 | "outputs": [], |
47 | 46 | "source": [ |
48 | 47 | "bold_runs = [\n", |
49 | | - " Path(line) for line in (DATA_PATH / \"ismrm_sample.txt\").read_text().splitlines()\n", |
| 48 | + " Path(line)\n", |
| 49 | + " for line in (DATA_PATH / \"ismrm_sample.txt\").read_text().splitlines()\n", |
50 | 50 | " if line.strip()\n", |
51 | 51 | "]" |
52 | 52 | ] |
|
65 | 65 | " nii = nb.load(DATA_PATH / bold_run)\n", |
66 | 66 | " average = nii.get_fdata().mean(-1)\n", |
67 | 67 | " avg_path.parent.mkdir(exist_ok=True, parents=True)\n", |
68 | | - " nii.__class__(\n", |
69 | | - " average,\n", |
70 | | - " nii.affine,\n", |
71 | | - " nii.header\n", |
72 | | - " ).to_filename(avg_path)\n", |
| 68 | + " nii.__class__(average, nii.affine, nii.header).to_filename(avg_path)\n", |
73 | 69 | "\n", |
74 | | - " bmask_path = OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_label-brain_mask.nii.gz\"\n", |
| 70 | + " bmask_path = (\n", |
| 71 | + " OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_label-brain_mask.nii.gz\"\n", |
| 72 | + " )\n", |
75 | 73 | " if not bmask_path.exists():\n", |
76 | 74 | " bmsk_results = SynthStrip(\n", |
77 | 75 | " in_file=str(avg_path),\n", |
78 | 76 | " use_gpu=True,\n", |
79 | 77 | " ).run(cwd=str(WORKDIR))\n", |
80 | 78 | " copy(bmsk_results.outputs.out_mask, bmask_path)\n", |
81 | 79 | "\n", |
82 | | - " dilmask_path = avg_path.parent / f\"{avg_path.name.rsplit('_', 1)[0]}_label-braindilated_mask.nii.gz\"\n", |
| 80 | + " dilmask_path = (\n", |
| 81 | + " avg_path.parent / f\"{avg_path.name.rsplit('_', 1)[0]}_label-braindilated_mask.nii.gz\"\n", |
| 82 | + " )\n", |
83 | 83 | "\n", |
84 | 84 | " if not dilmask_path.exists():\n", |
85 | 85 | " niimsk = nb.load(bmask_path)\n", |
86 | 86 | " niimsk.__class__(\n", |
87 | | - " binary_dilation(niimsk.get_fdata() > 0.0, ball(4)).astype(\"uint8\"),\n", |
88 | | - " niimsk.affine,\n", |
89 | | - " niimsk.header,\n", |
90 | | - " ).to_filename(dilmask_path)\n", |
| 87 | + " binary_dilation(niimsk.get_fdata() > 0.0, ball(4)).astype(\"uint8\"),\n", |
| 88 | + " niimsk.affine,\n", |
| 89 | + " niimsk.header,\n", |
| 90 | + " ).to_filename(dilmask_path)\n", |
91 | 91 | "\n", |
92 | | - " oned_matrix_path = avg_path.parent / f\"{avg_path.name.rsplit('_', 1)[0]}_desc-hmc_xfm.txt\"\n", |
93 | | - " realign_output = avg_path.parent / f\"{avg_path.name.rsplit('_', 1)[0]}_desc-realigned_bold.nii.gz\"\n", |
| 92 | + " oned_matrix_path = avg_path.parent / f\"{avg_path.name.rsplit('_', 1)[0]}_desc-hmc_xfm.txt\"\n", |
| 93 | + " realign_output = (\n", |
| 94 | + " avg_path.parent / f\"{avg_path.name.rsplit('_', 1)[0]}_desc-realigned_bold.nii.gz\"\n", |
| 95 | + " )\n", |
94 | 96 | "\n", |
95 | 97 | " if not realign_output.exists():\n", |
96 | 98 | " volreg_results = Volreg(\n", |
97 | 99 | " in_file=str(DATA_PATH / bold_run),\n", |
98 | 100 | " in_weight_volume=str(dilmask_path),\n", |
99 | | - " args='-Fourier -twopass',\n", |
| 101 | + " args=\"-Fourier -twopass\",\n", |
100 | 102 | " zpad=4,\n", |
101 | | - " outputtype='NIFTI_GZ',\n", |
| 103 | + " outputtype=\"NIFTI_GZ\",\n", |
102 | 104 | " oned_matrix_save=f\"{oned_matrix_path}.aff12.1D\",\n", |
103 | 105 | " out_file=str(realign_output),\n", |
104 | 106 | " num_threads=12,\n", |
105 | 107 | " ).run(cwd=str(WORKDIR))\n", |
106 | 108 | "\n", |
107 | | - " move(volreg_results.outputs.oned_matrix_save, oned_matrix_path)\n" |
| 109 | + " move(volreg_results.outputs.oned_matrix_save, oned_matrix_path)" |
108 | 110 | ] |
109 | 111 | }, |
110 | 112 | { |
|
173 | 175 | "metadata": {}, |
174 | 176 | "outputs": [], |
175 | 177 | "source": [ |
176 | | - "\n", |
177 | | - "def plot_profile(image_path, axis=None, indexing=None, cmap='gray', label=None, figsize=(15, 1.7)):\n", |
| 178 | + "def plot_profile(image_path, axis=None, indexing=None, cmap=\"gray\", label=None, figsize=(15, 1.7)):\n", |
178 | 179 | " \"\"\"Plots a single image slice on a given axis or a new figure if axis is None.\"\"\"\n", |
179 | 180 | " # Load the image\n", |
180 | 181 | " image_data = nb.load(image_path).get_fdata()\n", |
181 | | - " \n", |
| 182 | + "\n", |
182 | 183 | " # Define default indexing if not provided\n", |
183 | 184 | " if indexing is None:\n", |
184 | | - " indexing = (image_data.shape[0] // 2, 3 * image_data.shape[1] // 4, slice(None), slice(None))\n", |
185 | | - " \n", |
| 185 | + " indexing = (\n", |
| 186 | + " image_data.shape[0] // 2,\n", |
| 187 | + " 3 * image_data.shape[1] // 4,\n", |
| 188 | + " slice(None),\n", |
| 189 | + " slice(None),\n", |
| 190 | + " )\n", |
| 191 | + "\n", |
186 | 192 | " # If no axis is provided, create a new figure and axis\n", |
187 | 193 | " if axis is None:\n", |
188 | 194 | " fig, axis = plt.subplots(figsize=figsize)\n", |
189 | 195 | " else:\n", |
190 | 196 | " fig = None # If axis is provided, we won't manage the figure\n", |
191 | | - " \n", |
| 197 | + "\n", |
192 | 198 | " # Display the image on the specified axis with aspect='auto' and the colormap\n", |
193 | | - " axis.imshow(image_data[indexing], aspect='auto', cmap=cmap)\n", |
194 | | - " \n", |
| 199 | + " axis.imshow(image_data[indexing], aspect=\"auto\", cmap=cmap)\n", |
| 200 | + "\n", |
195 | 201 | " # Turn off the axis for a cleaner look\n", |
196 | | - " axis.axis('off')\n", |
197 | | - " \n", |
| 202 | + " axis.axis(\"off\")\n", |
| 203 | + "\n", |
198 | 204 | " if label:\n", |
199 | 205 | " # Annotate the plot with the provided label\n", |
200 | | - " axis.text(0.02, 0.95, label, color='white', fontsize=12, ha='left', va='top', transform=axis.transAxes)\n", |
201 | | - " \n", |
| 206 | + " axis.text(\n", |
| 207 | + " 0.02,\n", |
| 208 | + " 0.95,\n", |
| 209 | + " label,\n", |
| 210 | + " color=\"white\",\n", |
| 211 | + " fontsize=12,\n", |
| 212 | + " ha=\"left\",\n", |
| 213 | + " va=\"top\",\n", |
| 214 | + " transform=axis.transAxes,\n", |
| 215 | + " )\n", |
| 216 | + "\n", |
202 | 217 | " # If we created the figure, show it\n", |
203 | 218 | " if fig is not None:\n", |
204 | 219 | " plt.show()\n", |
205 | | - " \n", |
| 220 | + "\n", |
206 | 221 | " return fig\n", |
207 | 222 | "\n", |
| 223 | + "\n", |
208 | 224 | "# def plot_combined_profile(images, indexing=None, figsize=(15, 1.7), cmap='gray', labels=None):\n", |
209 | 225 | "# # Create a figure with three subplots in a vertical layout and specified figure size\n", |
210 | 226 | "# n_images = len(images)\n", |
211 | 227 | "\n", |
212 | 228 | "# nplots = n_images * len(indexing or [True])\n", |
213 | 229 | "# figsize = (figsize[0], figsize[1] * nplots)\n", |
214 | 230 | "# fig, axes = plt.subplots(nplots, 1, figsize=figsize, constrained_layout=True)\n", |
215 | | - " \n", |
| 231 | + "\n", |
216 | 232 | "# if labels is None or isinstance(labels, str):\n", |
217 | 233 | "# labels = (labels, ) * nplots\n", |
218 | | - " \n", |
| 234 | + "\n", |
219 | 235 | "# if indexing is None or len(indexing) == 0:\n", |
220 | 236 | "# indexing = [None]\n", |
221 | | - " \n", |
| 237 | + "\n", |
222 | 238 | "# for i, idx in enumerate(indexing):\n", |
223 | 239 | "# for j in range(len(images)):\n", |
224 | 240 | "# ax = axes[i * n_images + j]\n", |
225 | 241 | "# plot_profile(images[j], axis=ax, indexing=idx, cmap=cmap, label=labels[j])\n", |
226 | | - " \n", |
| 242 | + "\n", |
227 | 243 | "# return fig\n", |
228 | 244 | "\n", |
229 | | - "def plot_combined_profile(images, afni_fd, eddymotion_fd, indexing=None, figsize=(15, 1.7), cmap='gray', labels=None):\n", |
| 245 | + "\n", |
| 246 | + "def plot_combined_profile(\n", |
| 247 | + " images, afni_fd, eddymotion_fd, indexing=None, figsize=(15, 1.7), cmap=\"gray\", labels=None\n", |
| 248 | + "):\n", |
230 | 249 | " # Calculate the number of profile plots\n", |
231 | 250 | " n_images = len(images)\n", |
232 | 251 | " nplots = n_images * len(indexing or [True])\n", |
233 | 252 | " total_height = figsize[1] * nplots + 2 # Adjust figure height for FD plot\n", |
234 | 253 | "\n", |
235 | 254 | " # Create a figure with one extra row for the FD plot, setting `sharex=True` for shared x-axis\n", |
236 | | - " fig, axes = plt.subplots(nplots + 1, 1, figsize=(figsize[0], total_height), constrained_layout=True, sharex=True)\n", |
| 255 | + " fig, axes = plt.subplots(\n", |
| 256 | + " nplots + 1, 1, figsize=(figsize[0], total_height), constrained_layout=True, sharex=True\n", |
| 257 | + " )\n", |
237 | 258 | "\n", |
238 | 259 | " # Plot the framewise displacement on the first axis\n", |
239 | 260 | " fd_axis = axes[0]\n", |
240 | 261 | " timepoints = np.arange(len(afni_fd)) # Assuming afni_fd and eddymotion_fd have the same length\n", |
241 | | - " fd_axis.plot(timepoints, afni_fd, label='AFNI 3dVolreg FD', color='blue')\n", |
242 | | - " fd_axis.plot(timepoints, eddymotion_fd, label='eddymotion FD', color='orange')\n", |
| 262 | + " fd_axis.plot(timepoints, afni_fd, label=\"AFNI 3dVolreg FD\", color=\"blue\")\n", |
| 263 | + " fd_axis.plot(timepoints, eddymotion_fd, label=\"eddymotion FD\", color=\"orange\")\n", |
243 | 264 | " fd_axis.set_ylabel(\"FD (mm)\")\n", |
244 | 265 | " fd_axis.legend(loc=\"upper right\")\n", |
245 | 266 | " fd_axis.set_xticks([]) # Hide x-ticks to keep x-axis clean\n", |
|
251 | 272 | " # Set indexing if not provided\n", |
252 | 273 | " if indexing is None or len(indexing) == 0:\n", |
253 | 274 | " indexing = [None]\n", |
254 | | - " \n", |
| 275 | + "\n", |
255 | 276 | " # Plot each profile slice below the FD plot\n", |
256 | 277 | " for i, idx in enumerate(indexing):\n", |
257 | 278 | " for j in range(len(images)):\n", |
258 | 279 | " ax = axes[i * n_images + j + 1] # Shift index by 1 to account for FD plot\n", |
259 | 280 | " plot_profile(images[j], axis=ax, indexing=idx, cmap=cmap, label=labels[j])\n", |
260 | | - " \n", |
| 281 | + "\n", |
261 | 282 | " return fig" |
262 | 283 | ] |
263 | 284 | }, |
|
278 | 299 | } |
279 | 300 | ], |
280 | 301 | "source": [ |
281 | | - "plot_combined_profile((DATA_PATH / bold_runs[15], afni_realigned[15], afni_realigned[15]), labels=(\"hmc1\", \"original\", \"hmc2\"));" |
| 302 | + "plot_combined_profile(\n", |
| 303 | + " (DATA_PATH / bold_runs[15], afni_realigned[15], afni_realigned[15]),\n", |
| 304 | + " labels=(\"hmc1\", \"original\", \"hmc2\"),\n", |
| 305 | + ");" |
282 | 306 | ] |
283 | 307 | }, |
284 | 308 | { |
|
299 | 323 | ], |
300 | 324 | "source": [ |
301 | 325 | "datashape = nb.load(DATA_PATH / bold_runs[15]).shape\n", |
302 | | - "plot_profile(DATA_PATH / bold_runs[15], afni_realigned[15], afni_realigned[15],\n", |
303 | | - " indexing=(slice(None), 3 * datashape[1] // 4, datashape[2] // 2, slice(None)));" |
| 326 | + "plot_profile(\n", |
| 327 | + " DATA_PATH / bold_runs[15],\n", |
| 328 | + " afni_realigned[15],\n", |
| 329 | + " afni_realigned[15],\n", |
| 330 | + " indexing=(slice(None), 3 * datashape[1] // 4, datashape[2] // 2, slice(None)),\n", |
| 331 | + ");" |
304 | 332 | ] |
305 | 333 | }, |
306 | 334 | { |
|
309 | 337 | "metadata": {}, |
310 | 338 | "outputs": [], |
311 | 339 | "source": [ |
312 | | - "from eddymotion.estimator import EddyMotionEstimator\n", |
313 | 340 | "from eddymotion.model.base import AverageModel\n", |
314 | 341 | "from eddymotion.utils import random_iterator" |
315 | 342 | ] |
|
424 | 451 | " workdir = WORKDIR / bold_run.parent\n", |
425 | 452 | " workdir.mkdir(parents=True, exist_ok=True)\n", |
426 | 453 | " data_path = DATA_PATH / bold_run\n", |
427 | | - " brainmask_path = OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_label-brain_mask.nii.gz\"\n", |
| 454 | + " brainmask_path = (\n", |
| 455 | + " OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_label-brain_mask.nii.gz\"\n", |
| 456 | + " )\n", |
428 | 457 | "\n", |
429 | 458 | " nii = nb.load(data_path)\n", |
430 | 459 | " hdr = nii.header.copy()\n", |
|
449 | 478 | "outputs": [], |
450 | 479 | "source": [ |
451 | 480 | "from nitransforms.resampling import apply\n", |
| 481 | + "\n", |
452 | 482 | "from eddymotion.registration.utils import displacement_framewise\n", |
453 | 483 | "\n", |
454 | 484 | "afni_fd = {}\n", |
|
472 | 502 | " ]\n", |
473 | 503 | "\n", |
474 | 504 | " nii = nb.load(DATA_PATH / bold_run)\n", |
475 | | - " nitransforms_fd[str(bold_run)] = np.array([\n", |
476 | | - " displacement_framewise(nii, xfm)\n", |
477 | | - " for xfm in xfms\n", |
478 | | - " ])\n", |
| 505 | + " nitransforms_fd[str(bold_run)] = np.array([displacement_framewise(nii, xfm) for xfm in xfms])\n", |
479 | 506 | "\n", |
480 | 507 | " hmc_xfm = nt.linear.LinearTransformsMapping(xfms)\n", |
481 | | - " out_nitransforms = OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-nitransforms_bold.nii.gz\"\n", |
| 508 | + " out_nitransforms = (\n", |
| 509 | + " OUTPUT_DIR\n", |
| 510 | + " / bold_run.parent\n", |
| 511 | + " / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-nitransforms_bold.nii.gz\"\n", |
| 512 | + " )\n", |
482 | 513 | " if not out_nitransforms.exists():\n", |
483 | 514 | " apply(\n", |
484 | 515 | " hmc_xfm,\n", |
|
487 | 518 | " ).to_filename(out_nitransforms)\n", |
488 | 519 | "\n", |
489 | 520 | " afni_xfms = nt.linear.load(\n", |
490 | | - " OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-hmc_xfm.txt\"\n", |
| 521 | + " OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-hmc_xfm.txt\"\n", |
| 522 | + " )\n", |
| 523 | + " afni_fd[str(bold_run)] = np.array(\n", |
| 524 | + " [displacement_framewise(nii, afni_xfms[i]) for i in range(len(afni_xfms))]\n", |
491 | 525 | " )\n", |
492 | | - " afni_fd[str(bold_run)] = np.array([\n", |
493 | | - " displacement_framewise(nii, afni_xfms[i])\n", |
494 | | - " for i in range(len(afni_xfms))\n", |
495 | | - " ])\n", |
496 | 526 | "\n", |
497 | | - " out_afni = OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-afni_bold.nii.gz\"\n", |
| 527 | + " out_afni = (\n", |
| 528 | + " OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-afni_bold.nii.gz\"\n", |
| 529 | + " )\n", |
498 | 530 | " if not out_afni.exists():\n", |
499 | 531 | " apply(\n", |
500 | 532 | " afni_xfms,\n", |
501 | 533 | " spatialimage=nii,\n", |
502 | 534 | " reference=nii,\n", |
503 | | - " ).to_filename(out_afni)\n" |
| 535 | + " ).to_filename(out_afni)" |
504 | 536 | ] |
505 | 537 | }, |
506 | 538 | { |
|
1681 | 1713 | "\n", |
1682 | 1714 | " for bold_run in bold_runs:\n", |
1683 | 1715 | " original = DATA_PATH / bold_run\n", |
1684 | | - " nitransforms = OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-nitransforms_bold.nii.gz\"\n", |
1685 | | - " afni = OUTPUT_DIR / bold_run.parent / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-realigned_bold.nii.gz\"\n", |
| 1716 | + " nitransforms = (\n", |
| 1717 | + " OUTPUT_DIR\n", |
| 1718 | + " / bold_run.parent\n", |
| 1719 | + " / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-nitransforms_bold.nii.gz\"\n", |
| 1720 | + " )\n", |
| 1721 | + " afni = (\n", |
| 1722 | + " OUTPUT_DIR\n", |
| 1723 | + " / bold_run.parent\n", |
| 1724 | + " / f\"{bold_run.name.rsplit('_', 1)[0]}_desc-realigned_bold.nii.gz\"\n", |
| 1725 | + " )\n", |
1686 | 1726 | "\n", |
1687 | 1727 | " datashape = nb.load(original).shape\n", |
1688 | 1728 | "\n", |
|
1691 | 1731 | " afni_fd[str(bold_run)],\n", |
1692 | 1732 | " nitransforms_fd[str(bold_run)],\n", |
1693 | 1733 | " labels=(\"3dVolreg\", str(bold_run), \"eddymotion\"),\n", |
1694 | | - " indexing=(None, (slice(None), 3 * datashape[1] // 4, datashape[2] // 2, slice(None)))\n", |
| 1734 | + " indexing=(None, (slice(None), 3 * datashape[1] // 4, datashape[2] // 2, slice(None))),\n", |
1695 | 1735 | " )\n", |
1696 | 1736 | "\n", |
1697 | 1737 | " # Save the figure\n", |
|
1701 | 1741 | " plt.close(fig)\n", |
1702 | 1742 | "\n", |
1703 | 1743 | " index_file.write(f\"<li><a href={out_svg.relative_to(OUTPUT_DIR)}>{bold_run}</a></li>\\n\")\n", |
1704 | | - " \n", |
| 1744 | + "\n", |
1705 | 1745 | " index_file.write(\"</ul>\\n</body></html>\")" |
1706 | 1746 | ] |
1707 | 1747 | }, |
|
0 commit comments