{ "cells": [ { "cell_type": "markdown", "id": "0", "metadata": {}, "source": [ "# Head model fiducials and landmarks\n", "\n", "Cedalion ships with segmentations as well as brain and scalp surfaces for the Colin27 and ICBM-152 heads.\n", "\n", "This notebook documents the source of the fiducial landmarks and compares the outputs of the landmark builder\n", "which we distribute together with the head models." ] }, { "cell_type": "code", "execution_count": 1, "id": "1", "metadata": { "execution": { "iopub.execute_input": "2025-11-11T10:01:28.558522Z", "iopub.status.busy": "2025-11-11T10:01:28.558322Z", "iopub.status.idle": "2025-11-11T10:01:28.564577Z", "shell.execute_reply": "2025-11-11T10:01:28.563876Z" } }, "outputs": [], "source": [ "# This cells setups the environment when executed in Google Colab.\n", "try:\n", " import google.colab\n", " !curl -s https://raw.githubusercontent.com/ibs-lab/cedalion/dev/scripts/colab_setup.py -o colab_setup.py\n", " # Select branch with --branch \"branch name\" (default is \"dev\")\n", " %run colab_setup.py\n", "except ImportError:\n", " pass" ] }, { "cell_type": "code", "execution_count": 2, "id": "2", "metadata": { "execution": { "iopub.execute_input": "2025-11-11T10:01:28.566169Z", "iopub.status.busy": "2025-11-11T10:01:28.566015Z", "iopub.status.idle": "2025-11-11T10:01:30.628005Z", "shell.execute_reply": "2025-11-11T10:01:30.627181Z" } }, "outputs": [], "source": [ "import cedalion\n", "import cedalion.dataclasses as cdc\n", "import cedalion.data\n", "import cedalion.geometry.landmarks\n", "import cedalion.geometry.segmentation\n", "import cedalion.io\n", "import cedalion.vis.blocks as vbx\n", "import cedalion.dot\n", "import cedalion.xrutils as xrutils\n", "import matplotlib.pyplot as p\n", "import numpy as np\n", "import pyvista\n", "import xarray as xr\n", "\n", "pyvista.set_jupyter_backend(\"static\")\n", "#pyvista.set_jupyter_backend(\"server\")" ] }, { "cell_type": "markdown", "id": "3", "metadata": {}, "source": [ "## Utility function" ] }, { "cell_type": "code", "execution_count": 3, "id": "4", "metadata": { "execution": { "iopub.execute_input": "2025-11-11T10:01:30.630555Z", "iopub.status.busy": "2025-11-11T10:01:30.630101Z", "iopub.status.idle": "2025-11-11T10:01:30.635615Z", "shell.execute_reply": "2025-11-11T10:01:30.634792Z" } }, "outputs": [], "source": [ "def compare_landmarks(scalp, landmarks, reference):\n", " fiducial_labels = [\"Nz\", \"Iz\", \"LPA\", \"RPA\", \"Cz\"]\n", "\n", " plt = pyvista.Plotter()\n", " vbx.plot_surface(plt, scalp, opacity=0.7)\n", " vbx.plot_labeled_points(plt, reference, color=\"y\")\n", " vbx.plot_labeled_points(plt, landmarks, color=\"g\")\n", " vbx.plot_labeled_points(plt, landmarks.loc[fiducial_labels], color=\"r\")\n", " plt.show()\n", "\n", " common_labels = list(set(reference.label.values) & set(landmarks.label.values))\n", "\n", " diffs = xrutils.norm(\n", " landmarks.sel(label=landmarks.label.isin(common_labels))\n", " - reference.sel(label=reference.label.isin(common_labels)),\n", " dim=\"mni\",\n", " )\n", " diffs = diffs.pint.to(\"mm\").pint.dequantify()\n", " median_diff = np.median(diffs).item()\n", "\n", " p.figure()\n", " p.hist(diffs, np.arange(0, 10.5, 0.5))\n", " p.axvline(median_diff, c=\"r\", ls=\":\")\n", " p.xlabel(r\"$|\\vec{x}_{ours} - \\vec{x}_{ref}|$ / mm\")\n", " p.ylabel(r\"# landmarks\")\n", " print(f\"The median distance between landmarks is {median_diff:.1f} mm.\")" ] }, { "cell_type": "markdown", "id": "5", "metadata": {}, "source": [ "## Colin 27" ] }, { "cell_type": "markdown", "id": "6", "metadata": {}, "source": [ "Load Colin27 head model" ] }, { "cell_type": "code", "execution_count": 4, "id": "7", "metadata": { "execution": { "iopub.execute_input": "2025-11-11T10:01:30.637562Z", "iopub.status.busy": "2025-11-11T10:01:30.637365Z", "iopub.status.idle": "2025-11-11T10:01:35.117109Z", "shell.execute_reply": "2025-11-11T10:01:35.116437Z" } }, "outputs": [], "source": [ "colin_ijk = cedalion.dot.get_standard_headmodel(\"colin27\")\n", "colin_ras = colin_ijk.apply_transform(colin_ijk.t_ijk2ras)" ] }, { "cell_type": "markdown", "id": "8", "metadata": {}, "source": [ "Load fieldtrip 10-5 coordinates:" ] }, { "cell_type": "code", "execution_count": 5, "id": "9", "metadata": { "execution": { "iopub.execute_input": "2025-11-11T10:01:35.119153Z", "iopub.status.busy": "2025-11-11T10:01:35.118992Z", "iopub.status.idle": "2025-11-11T10:01:35.251650Z", "shell.execute_reply": "2025-11-11T10:01:35.250750Z" } }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Downloading file 'fieldtrip_standard1005.elc' from 'https://raw.githubusercontent.com/fieldtrip/fieldtrip/refs/heads/master/template/electrode/standard_1005.elc' to '/home/runner/.cache/cedalion/dev'.\n" ] }, { "data": { "text/html": [ "
<xarray.DataArray (label: 346, mni: 3)> Size: 8kB\n",
"<Quantity([[-8.60761e+01 -1.99897e+01 -4.79860e+01]\n",
" [ 8.57939e+01 -2.00093e+01 -4.80310e+01]\n",
" [ 8.30000e-03 8.68110e+01 -3.99830e+01]\n",
" ...\n",
" [ 8.57939e+01 -4.50093e+01 -6.80310e+01]\n",
" [-8.60761e+01 -2.49897e+01 -6.79860e+01]\n",
" [ 8.57939e+01 -2.50093e+01 -6.80310e+01]], 'millimeter')>\n",
"Coordinates:\n",
" * label (label) <U6 8kB 'LPA' 'RPA' 'Nz' 'Fp1' ... 'M1' 'M2' 'A1' 'A2'\n",
" type (label) object 3kB PointType.LANDMARK ... PointType.LANDMARK\n",
"Dimensions without coordinates: mni<xarray.DataArray (label: 5, mni: 3)> Size: 120B\n",
"<Quantity([[ 4.5000000e-03 -1.1856500e+02 -2.3078000e+01]\n",
" [-8.6076100e+01 -1.9989700e+01 -4.7986000e+01]\n",
" [ 8.5793900e+01 -2.0009300e+01 -4.8031000e+01]\n",
" [ 4.0090000e-01 -9.1670000e+00 1.0024400e+02]\n",
" [-6.7919500e-01 8.3259347e+01 -3.9408527e+01]], 'millimeter')>\n",
"Coordinates:\n",
" * label (label) <U6 120B 'Iz' 'LPA' 'RPA' 'Cz' 'Nz'\n",
" type (label) object 40B PointType.LANDMARK ... PointType.LANDMARK\n",
"Dimensions without coordinates: mni