Skip to content

Commit 9157b04

Browse files
authored
Merge pull request #26 from int-brain-lab/streamlines
Streamlines
2 parents 42561ec + 9a8fa3b commit 9157b04

File tree

9 files changed

+1002
-56
lines changed

9 files changed

+1002
-56
lines changed

examples/atlas_streamlines.ipynb

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "5823062b-e25f-45aa-96d7-65c83845af61",
6+
"metadata": {},
7+
"source": [
8+
"# Working with cortical streamlines\n",
9+
"\n",
10+
"In this example we walk through various methods that use the Allen streamlines. Streamlines are the paths that connect the surface of the isocortex to the lower white matter surface while following the curvature of these surfaces."
11+
]
12+
},
13+
{
14+
"cell_type": "markdown",
15+
"id": "46e7e0e7-4295-40a5-a5b3-1bc0fc84f07e",
16+
"metadata": {},
17+
"source": [
18+
"## Depth lookup\n",
19+
"We can use the streamlines to find the depth of coordinates in 3D space relative to the surface of the cortex. Note as the streamlines are only defined in the cortex so this lookup will only work for coordinates that lie within the Isocortex of the Allen atlas volume. In this example we obtain the depths in percentage, where 0 % indicates the surface of the isocortex and 100 % the white matter boundary."
20+
]
21+
},
22+
{
23+
"cell_type": "code",
24+
"execution_count": null,
25+
"id": "0a3fddd1-9ddd-4dca-b256-3dd7d29bad67",
26+
"metadata": {},
27+
"outputs": [],
28+
"source": [
29+
"from iblatlas.streamlines.utils import xyz_to_depth\n",
30+
"from iblatlas.atlas import AllenAtlas\n",
31+
"import numpy as np\n",
32+
"import matplotlib.pyplot as plt\n",
33+
"\n",
34+
"ba = AllenAtlas()\n",
35+
"# In this example we are going to get out the coordinates of the voxels that lie within the region MOp\n",
36+
"region_id = ba.regions.acronym2index('MOp')[1][0][0]\n",
37+
"# MOp is in the beryl mapping so we map our label volume to this mapping\n",
38+
"ba.label = ba.regions.mappings['Beryl-lr'][ba.label]\n",
39+
"# These are the indices in the volume, order is AP, ML, DV\n",
40+
"ixyz = np.where(ba.label == region_id)\n",
41+
"# Convert these to xyz coordinates relative to bregma in order ML, AP, DV\n",
42+
"xyz = ba.bc.i2xyz(np.c_[ixyz[1], ixyz[0], ixyz[2]])\n",
43+
"\n",
44+
"depth_per = xyz_to_depth(xyz) * 100\n",
45+
"\n",
46+
"# We can assign a volume\n",
47+
"depth_per_vol = np.full(ba.image.shape, np.nan)\n",
48+
"depth_per_vol[ixyz[0], ixyz[1], ixyz[2]] = depth_per\n",
49+
"\n",
50+
"fig, ax = plt.subplots()\n",
51+
"ba.plot_cslice(620/1e6, volume='image', ax=ax)\n",
52+
"ba.plot_cslice(620/1e6, volume='volume', region_values=depth_per_vol, ax=ax, cmap='viridis', vmin=0, vmax=100)\n",
53+
"ax.set_axis_off()\n",
54+
"cbar = fig.colorbar(ax.get_images()[1], ax=ax)\n",
55+
"cbar.set_label('Depth from surface of cortex (%)')"
56+
]
57+
},
58+
{
59+
"cell_type": "markdown",
60+
"id": "c1da61f0-7281-45d8-8bd7-33be0d373302",
61+
"metadata": {},
62+
"source": [
63+
"We can also do this look up in um relative to the surface of the cortex."
64+
]
65+
},
66+
{
67+
"cell_type": "code",
68+
"execution_count": null,
69+
"id": "047509db-fb6b-46f3-8cb2-817b12223bed",
70+
"metadata": {},
71+
"outputs": [],
72+
"source": [
73+
"depth_um = xyz_to_depth(xyz, per=False)\n",
74+
"\n",
75+
"# We can assign a volume\n",
76+
"depth_um_vol = np.full(ba.image.shape, np.nan)\n",
77+
"depth_um_vol[ixyz[0], ixyz[1], ixyz[2]] = depth_um\n",
78+
"\n",
79+
"fig, ax = plt.subplots()\n",
80+
"ba.plot_cslice(620/1e6, volume='image', ax=ax)\n",
81+
"ba.plot_cslice(620/1e6, volume='volume', region_values=depth_um_vol, ax=ax, cmap='tab20', vmin=0, vmax=2000)\n",
82+
"ax.set_axis_off()\n",
83+
"cbar = fig.colorbar(ax.get_images()[1], ax=ax)\n",
84+
"cbar.set_label('Depth from surface of cortex (um)')"
85+
]
86+
},
87+
{
88+
"cell_type": "markdown",
89+
"id": "bdbe5c0b-d7e3-441e-82ab-8bbcbd013bdd",
90+
"metadata": {},
91+
"source": [
92+
"## Flatmap projection\n",
93+
"The streamlines can also be used to project a volume or xyz coordinates onto a flattened view of the dorsal cortex. The volume or points can be aggregated along the streamlines in different ways, e.g max, mean, median.\n",
94+
"\n",
95+
"In this example we will project the Allen Atlas dwi volume onto the dorsal cortex projection. We will take the maximum value along the streamline for each voxel on the surface"
96+
]
97+
},
98+
{
99+
"cell_type": "code",
100+
"execution_count": null,
101+
"id": "7b61405d-8589-4cb8-8afc-6f8a87dc4588",
102+
"metadata": {},
103+
"outputs": [],
104+
"source": [
105+
"from iblatlas.streamlines.utils import project_volume_onto_flatmap\n",
106+
"\n",
107+
"ba = AllenAtlas(25)\n",
108+
"proj, fig, ax = project_volume_onto_flatmap(ba.image, res_um=25, aggr='max', plot=True, cmap='bone')\n",
109+
"ax.set_axis_off()"
110+
]
111+
},
112+
{
113+
"cell_type": "markdown",
114+
"id": "702b9abd-f97d-4a4e-967a-e9967e630293",
115+
"metadata": {},
116+
"source": [
117+
"We can also do the same with xyz coordinates (we will use the same xyz coordinates for the region MOp and assign random values to each coordinate)"
118+
]
119+
},
120+
{
121+
"cell_type": "code",
122+
"execution_count": null,
123+
"id": "19bf31f8-8f22-4f0e-91bb-7998f9379dc6",
124+
"metadata": {},
125+
"outputs": [],
126+
"source": [
127+
"from iblatlas.streamlines.utils import project_points_onto_flatmap, get_mask\n",
128+
"\n",
129+
"# Get a mask to plot our results on, options are 'image', 'annotation' or 'boundary\n",
130+
"mask = get_mask('boundary')\n",
131+
"\n",
132+
"values = np.random.rand(xyz.shape[0])\n",
133+
"\n",
134+
"proj = project_points_onto_flatmap(xyz, values, aggr='mean', plot=False)\n",
135+
"# Mask values with 0 with nan for display purposes\n",
136+
"proj[proj == 0] = np.nan\n",
137+
"\n",
138+
"fig, ax = plt.subplots()\n",
139+
"ax.imshow(mask, cmap='Greys')\n",
140+
"ax.imshow(proj)\n",
141+
"ax.set_axis_off()"
142+
]
143+
}
144+
],
145+
"metadata": {
146+
"kernelspec": {
147+
"display_name": "Python 3 (ipykernel)",
148+
"language": "python",
149+
"name": "python3"
150+
},
151+
"language_info": {
152+
"codemirror_mode": {
153+
"name": "ipython",
154+
"version": 3
155+
},
156+
"file_extension": ".py",
157+
"mimetype": "text/x-python",
158+
"name": "python",
159+
"nbconvert_exporter": "python",
160+
"pygments_lexer": "ipython3",
161+
"version": "3.11.9"
162+
}
163+
},
164+
"nbformat": 4,
165+
"nbformat_minor": 5
166+
}

iblatlas/atlas.py

Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,8 @@ def _take_remap(vol, ind, axis, mapping):
915915
index = bc.xyz2i(np.array([coordinate] * 3))[axis]
916916
return _take(region_values, index, axis=axis)
917917

918-
def compute_boundaries(self, values):
918+
@staticmethod
919+
def compute_boundaries(values):
919920
"""
920921
Compute the boundaries between regions on slice
921922
:param values:
@@ -1811,54 +1812,3 @@ def get_bc(res_um=10):
18111812
bc = BrainCoordinates(nxyz=nxyz, xyz0=-bc.i2xyz(iorigin), dxyz=dxyz)
18121813

18131814
return bc
1814-
1815-
1816-
def _download_depth_files(file_name):
1817-
"""
1818-
These files have been generated using this script
1819-
https://github.com/int-brain-lab/ibldevtools/blob/master/Mayo/flatmaps/2025-03-20_depths_from_streamlines.py
1820-
:param file_name:
1821-
:return:
1822-
"""
1823-
file_path = BrainAtlas._get_cache_dir().joinpath('depths', file_name)
1824-
if not file_path.exists():
1825-
file_path.parent.mkdir(exist_ok=True, parents=True)
1826-
aws.s3_download_file(f'atlas/depths/{file_path.name}', file_path)
1827-
1828-
return np.load(file_path)
1829-
1830-
1831-
def xyz_to_depth(xyz, res_um=25):
1832-
"""
1833-
For a given xyz coordinates find the depth from the surface of the cortex. Note the lookup will only
1834-
work for xyz cooordinates that are in the Isocortex of the Allen volume. If coordinates outside of this
1835-
region are given then the depth is returned as nan.
1836-
1837-
Parameters
1838-
----------
1839-
xyz : numpy.array
1840-
An (n, 3) array of Cartesian coordinates. The order is ML, AP, DV and coordinates should be given in meters
1841-
relative to bregma.
1842-
1843-
res_um : float or int
1844-
The resolution of the brain atlas to do the depth lookup
1845-
1846-
Returns
1847-
-------
1848-
numpy.array
1849-
The depths from the surface of the cortex for each cartesian coordinate. If the coordinate does not lie within
1850-
the Isocortex, depth value returned is nan
1851-
"""
1852-
1853-
ind_flat = _download_depth_files(f'depths_ind_{res_um}.npy')
1854-
depths = _download_depth_files(f'depths_um_{res_um}.npy')
1855-
bc = get_bc(res_um=res_um)
1856-
1857-
ixyz = bc.xyz2i(xyz, mode='clip')
1858-
iravel = np.ravel_multi_index((ixyz[:, 1], ixyz[:, 0], ixyz[:, 2]), (bc.ny, bc.nx, bc.nz))
1859-
a, b = ismember(iravel, ind_flat)
1860-
1861-
lookup_depths = np.full(iravel.shape, np.nan, dtype=np.float32)
1862-
lookup_depths[a] = depths[b]
1863-
1864-
return lookup_depths

iblatlas/streamlines/__init__.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""A package for working with Allen streamlines data.
2+
3+
This package provides a way to use the Allen streamline data to
4+
1. Do a lookup of xyz position to depth from the surface of the cortex
5+
2. Project a volume or xyz positions along the streamlines to display them on a flattened dorsal cortex flatmap
6+
7+
Streamlines are the paths that connect the surface of the isocortex to the white matter surface while following the
8+
curvature of these surfaces.
9+
10+
For the xyz to depth position lookup the following files have been generated with 10, 25 and 50 um resolution
11+
12+
depths/
13+
├── depths_ind_{res}.npy
14+
├── depths_per_{res}.npy
15+
├── depths_um_{res}.npy
16+
17+
- The depths_ind_{res}.npy contains the flattened index for all voxels contained within the isocortex of the Allen
18+
volume at the specified resolution. The ordering of the flattened index is AP, DV, ML
19+
- The depths_per_{res}.npy contains the depth in percentage from the surface of the cortex of each voxel in the isocortex.
20+
The percentage values were taken from the laplacian provided by the Allen institute. Voxels at the surface have a percentage
21+
depth of 0 while those at the white matter boundary have a percentage depth of 1.
22+
- The depths_um_{res}.npy contains the depth in um from the surface of the cortex of each voxel in the isocortex. The depth
23+
values have been computed using the streamlines provided by the Allen institute. For each streamline the length of
24+
the streamline was computed and voxels assigned a depth based on the steps along the streamline. When multiple streamlines
25+
pass through a given voxel the median depth has been taken.
26+
27+
The 25 and 50 um resolution datasets have been computed by subsampling the 10 um datasets.
28+
29+
30+
To enable the projection onto the dorsal flatmap the following files have been generated with 10, 25 and 50 um resolution
31+
32+
depths/
33+
├── paths_{res}.pqt
34+
├── dorsal_flatmap.npy
35+
├── dorsal_annotation.npy
36+
├── dorsal_image.npy
37+
38+
39+
- The paths_{res}.pqt files contain information about the voxels that correspond to each streamline. They have been
40+
derived from the dorsal_flatmap_paths_10.h5 paths provided by the Allen institute. We store the values as a pqt file and remove
41+
values that correspond to no streamline (paths=0) and duplicate voxels per streamline. As we have removed duplicate
42+
voxels per streamline this means that when taking the average or std along the streamline the behavior will differ
43+
from the Allen code.
44+
- The dorsal_flatmap.npy is a 2D numpy array with the dorsal cortex flatmap. The value in each voxel corresponds the
45+
streamline index that corresponds to that voxel.
46+
- The dorsal_annotation is 2D numpy array with the dorsal cortex flatmap. The value in each voxel corresponds to the
47+
region id on the surface of the cortex that corresponds to that voxel
48+
- The dorsal_image is a 2D numpy array with the dorsal cortex flatmap. The value in each voxel corresponds to the Allen
49+
image volume projected onto the flatmap display
50+
51+
[1] Wang, Quanxin et al., “The Allen Mouse Brain Common Coordinate Framework: A 3D Reference Atlas”
52+
Cell, Volume 181, Issue 4, 936 - 953.e20 May. 2020, https://doi.org/10.1016/j.cell.2020.04.007
53+
54+
"""

0 commit comments

Comments
 (0)