├── .gitignore ├── LICENSE ├── README.md ├── common.py ├── gradient.py ├── input └── README.md ├── laplacian.py ├── plotting.ipynb ├── plotting.py ├── regions └── README.md ├── scripts ├── 315L.obj ├── 315L.stl ├── 315L_surface.stl ├── 315L_surface_nearestBottomAny.stl ├── 315L_surface_nearestNotBottomEdge.stl ├── 315L_surface_nearestTop.stl ├── 315L_surface_nearestTopAny.stl ├── 315L_surface_within20.stl ├── 315L_surface_within40.stl ├── annotation_10.nrrd ├── compute_dorsal_flatmap.py ├── dorsal_flatmap.npy ├── isocortex_boundary_10.nrrd ├── isocortex_mask_10.nrrd └── test_cortexmodel2flatmap.ipynb ├── streamlines.py └── surface.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # repo-specific 132 | input 133 | regions 134 | old* 135 | ccf* 136 | fem 137 | notebooks 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 International Brain Laboratory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brain flatmaps generation 2 | 3 | This repository describes the methods and provides the Python code for generating brain flatmaps based on the Allen Mouse Brain atlas. 4 | 5 | This is a Python reimplementation of an existing method developed for the isocortex. This code will run on other regions in the near future. 6 | 7 | 8 | > **Note:** this is a work in progress. Only part of the method (streamlines) has been implemented so far. 9 | 10 | 11 | 12 | ## How to run it 13 | 14 | A powerful computer is required to handle the 10um Allen atlas volume. 15 | 16 | ### Hardware requirements 17 | 18 | - At least 64GB of RAM 19 | - At least 250GB of free space on an SSD 20 | - A NVIDIA graphics processing unit (GPU) with at least 8GB of video memory 21 | 22 | ### Software requirements 23 | 24 | - Python 3 25 | - NumPy 26 | - SciPy 27 | - Numba 28 | - Cupy 29 | - nrrd 30 | - h5py 31 | - tqdm 32 | 33 | ### Running the code 34 | 35 | The code requires data files that we do not provide directly. If needed, ask [Cyrille Rossant](https://cyrille.rossant.net/) for more guidance. 36 | 37 | The input files are to put in `input/`, the code will generate output files in `regions/isocortex/`. 38 | 39 | 1. Put the following input files in the `input/` subfolder: 40 | 41 | - `isocortex_boundary_10.nrrd` 42 | - `isocortex_mask_10.nrrd` 43 | 44 | 2. Run `python surface.py`. This script should create: 45 | 46 | - `regions/isocortex/mask.npy` (a volume with labels indicating the surfaces and the regions between them) 47 | - `regions/isocortex/normal.npy` (a 3D vector field with the surface normal vectors) 48 | 49 | 3. Run `python laplacian.py`. This script should create: 50 | 51 | - `regions/isocortex/laplacian.npy` (a 3D scalar field within the region volume) 52 | 53 | 4. Run `python gradient.py`. This script should create: 54 | 55 | - `regions/isocortex/gradient.npy` (a 3D vector field with the gradient to Laplace's equation's solution) 56 | 57 | 5. Run `python streamlines.py`. This script should create: 58 | 59 | - `regions/isocortex/streamlines.npy` (a 3D array with N 3D paths of size 100 in the 3D coordinate space of the 10um volume) 60 | 61 | 6. Visualize the generated streamlines in 2D with the plotting Jupyter notebook, or in 3D with `python plotting.py` (requires Datoviz) 62 | 63 | 7. The next steps for generating the flatmaps using the streamlines are not yet implemented. 64 | 65 | #### Constants 66 | 67 | Some useful constants are defined in `common.py`, including: 68 | 69 | ```python 70 | # Volume shape for 10um Allen Mouse Brain Atlas. 71 | N, M, P = 1320, 800, 1140 72 | 73 | # Values used in the mask file 74 | V_OUTSIDE = 0 # voxels outside of the surfaces and brain region 75 | V_ST = 1 # top (outer) surface 76 | V_VOLUME = 2 # volume between the two surfaces 77 | V_SB = 3 # bottom (inter) surface 78 | V_SE = 4 # intermediate surfaces 79 | ``` 80 | 81 | 82 | ## How it works 83 | 84 | This section describes the method for generating the streamlines and flatmaps. 85 | 86 | The method consists of computing streamlines between a bottom and top surface around a brain region by solving Laplace's partial differential equation $\Delta u = 0$ inside the brain region, with a combination of Dirichlet and Neumann boundary conditions on the surfaces, and integrating the gradient field $\nabla u$ to link every voxel of the bottom surface to a corresponding voxel at the top surface of the volume. 87 | 88 | The streamlines allow one to generate flatmaps by mapping every pixel of the flattened surface to an average value along the streamline that starts at that voxel. 89 | 90 | ### Notations 91 | 92 | We start by defining some notations. 93 | 94 | #### General notations 95 | 96 | - The 1-norm of a vector $\mathbf p=(x,y,z)$ is $\lVert\mathbf p\rVert_1 = |x|+|y|+|z|$. 97 | - The Euclidean norm of a vector $\mathbf p=(x,y,z)$ is $\lVert\mathbf p\rVert_2 = \sqrt{x^2+y^2+z^2}$. 98 | - The gradient of a scalar field $u$ is $\displaystyle\nabla u =\left(\frac{\partial u}{\partial x}, \frac{\partial u}{\partial y}, \frac{\partial u}{\partial z}\right)$. 99 | - The Laplacian of a scalar field $u$ is $\displaystyle\Delta u =\frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2} + \frac{\partial^2 u}{\partial z^2}$. 100 | 101 | #### Surfaces 102 | 103 | - $\Omega = \left[0, N\right] \times \left[0, M\right] \times \left[0, P\right]$ is the 3D volume containing the brain atlas. 104 | - $\mathcal V \subset \Omega$ is the **brain region** to flatten 105 | - $\mathcal S = \partial\mathcal V \subset \Omega$ is the boundary surface of the volume 106 | - $\mathcal S_T \subset \mathcal S$ is the top (outer) surface of the brain region $\mathcal V$ 107 | - $\mathcal S_B \subset \mathcal S$ is the bottom (inner) surface of the brain region $\mathcal V$ 108 | - $\mathcal S_E \subset \mathcal S$ is the edge surface of the brain region $\mathcal V$ 109 | 110 | The topological boundary of the volume is the union of these three non-intersecting surfaces: 111 | 112 | $$\mathcal S = \partial\mathcal V = \mathcal S_T \sqcup \mathcal S_B \sqcup \mathcal S_E$$ 113 | 114 | #### Coordinate system 115 | 116 | We use the Allen CCF coordinate system: 117 | 118 | [![](http://help.brain-map.org/download/attachments/5308472/3DOrientation.png)](http://help.brain-map.org/display/mousebrain/API) 119 | 120 | #### Voxels 121 | 122 | - $p = (i, j, k) \in \Omega$ is a voxel in the volume 123 | - $p_x^- = (i-1, j, k) \in \Omega$ is the neighbor voxel in front of $p$ 124 | - $p_x^+ = (i+1, j, k) \in \Omega$ is the neighbor voxel behind $p$ 125 | - $p_y^- = (i, j-1, k) \in \Omega$ is the neighbor voxel on top of $p$ 126 | - $p_y^+ = (i, j+1, k) \in \Omega$ is the neighbor voxel below $p$ 127 | - $p_z^- = (i, j, k-1) \in \Omega$ is the neighbor voxel to the left of $p$ 128 | - $p_z^+ = (i, j, k+1) \in \Omega$ is the neighbor voxel to the right of $p$ 129 | 130 | For each subset $\mathcal A \subset \Omega$, we define its indicator function $\chi_{\mathcal A} : \Omega \longrightarrow \\{0, 1\\}$ as: 131 | 132 | $$ 133 | \forall p \in \Omega, \quad \chi_{\mathcal A}(p) = \begin{cases} 134 | 1 & \textrm{if} \quad p \in \mathcal A \\ 135 | 0 & \textrm{otherwise} 136 | \end{cases} 137 | $$ 138 | 139 | 140 | #### Mask 141 | 142 | The mask $\mu$ is defined as the function $\Omega \longrightarrow \\{ 0,1,2,3,4 \\}$ that maps every voxel of the volume $\Omega$ to: 143 | 144 | $$ 145 | \forall p \in \Omega, \quad 146 | \mu(p) = \begin{cases} 147 | 0 & \textrm{if} \quad p \not\in \mathcal V \cup \mathcal S \\ 148 | v_t = 1 & \textrm{if} \quad p \in \mathcal S_T \\ 149 | v_v = 2 & \textrm{if} \quad p \in \mathcal V \\ 150 | v_b = 3 & \textrm{if} \quad p \in \mathcal S_B \\ 151 | v_e = 4 & \textrm{if} \quad p \in \mathcal S_E \\ 152 | \end{cases} 153 | $$ 154 | 155 | > **Implementation notes:** The mask $\mu$ is stored in `mask.npy` that is computed in the first step below, from the input nrrd files. This file is a 3D array with shape `(N, M, P)` and data type `uint8`. 156 | 157 | 158 | ### Step 1. Surface normal 159 | 160 | The first step is to estimate the normal to the surface at every surface voxel. The normals will be used as boundary conditions when simulating the partial differential equation in Step 2. 161 | 162 | #### Crude local estimation 163 | 164 | We can make a first estimation of the surface normals thanks to the $\chi_{\mathcal V}$ indicator function of the brain region: 165 | 166 | $$ 167 | \forall p \in \mathcal S, \quad 168 | \nu^0(p) = 169 | \begin{pmatrix} 170 | \chi_{\mathcal V}(p_x^+) - \chi_{\mathcal V}(p_x^-) \\ 171 | \chi_{\mathcal V}(p_y^+) - \chi_{\mathcal V}(p_y^-) \\ 172 | \chi_{\mathcal V}(p_z^+) - \chi_{\mathcal V}(p_z^-) \\ 173 | \end{pmatrix} 174 | \in \\{ -1, 0, +1 \\}^3 175 | $$ 176 | 177 | On each axis, the component of the vector $\nu^0(p)$ is +1 if the positive neighbor voxel on that axis belongs to the brain region $\mathcal V$ and the negative neighbor does not, or -1 if that's the reverse, or 0 if neither or both of these neighbors belong to the brain region. 178 | 179 | 180 | #### Gaussian smoothing 181 | 182 | Once this crude local estimate is obtained, we can smoothen it and normalize it to improve the accuracy of the boundary conditions in Step 2. 183 | 184 | We define a Gaussian kernel as follows: 185 | 186 | $$\forall \sigma > 0, \\, \forall q \in \mathbb R^3, \quad g_\sigma(q) = \lambda \exp \left(- \frac{\lVert q\rVert_2^2}{\sigma^2}\right) \quad \textrm{where $\lambda$ is defined such as} \quad \int_{\mathbb R^3} g(q) dq=1.$$ 187 | 188 | We smoothen the crude normal estimate with a partial Gaussian convolution on the surface: 189 | 190 | $$ 191 | \forall p \in \mathcal S, \quad 192 | \widetilde \nu(p) = \frac{\displaystyle\int_{\mathcal S} \nu^0(q) g(p-q) dq}{\displaystyle\int_{\mathcal S} g(p-q)dq} 193 | $$ 194 | 195 | Finally, we normalize the normal vectors: 196 | 197 | $$ 198 | \forall p \in \mathcal S, \quad 199 | \nu(p) = \begin{cases} 200 | \displaystyle \frac{\widetilde \nu(p)}{\lVert \widetilde \nu(p) \rVert_2} & \textrm{if} \quad {\lVert \widetilde \nu(p) \rVert_2} > 0\\ 201 | 0 & \textrm{otherwise} 202 | \end{cases} 203 | $$ 204 | 205 | > **Implementation notes:** this convolution is implemented with nested `for` loops in Python accelerated with JIT compilation using Numba. 206 | 207 | ![Surface normal](https://user-images.githubusercontent.com/1942359/172404714-8d94234a-394b-4432-bd5f-848344654542.png) 208 | 209 | ### Step 2. Numerical solution to Laplace's equation 210 | 211 | Step 2 is the most complex and computationally intensive step of the process. It requires a GPU to be tractable on the 10 $\mu\textrm{m}$ atlas. 212 | 213 | Mathematically, the goal is to solve the following partial differential equation (PDE), called Laplace's equation, with a mixture of Dirichlet and Neumann boundary conditions: 214 | 215 | $$ 216 | \begin{align*} 217 | \Delta u &= 0 & \textrm{on} \quad & \mathcal V\\ 218 | u &= 0 & \textrm{on} \quad & \mathcal S_T\\ 219 | \nabla u \cdot \nu &= 1 & \textrm{on} \quad & \mathcal S_B\\ 220 | \nabla u \cdot \nu &= 0 & \textrm{on} \quad & \mathcal S_E\\ 221 | \end{align*} 222 | $$ 223 | 224 | #### Numerical scheme 225 | 226 | An approximate solution of this equation can be obtained with an iterative numerical scheme. 227 | 228 | We start from $u_0(p) = \chi_{\mathcal S_B}(p)$, equal to 1 on the bottom surface $\mathcal S_B$, and 0 elsewhere. Then, for $n \geq 0$, we iteratively apply a numerical scheme to converge to a solution of the PDE. There are two steps: 229 | 230 | 1. Update $u^{n+1}$ on $\mathcal V$. 231 | 2. Update $u^{n+1}$ on $\mathcal S$. 232 | 233 | ##### Updating the scalar field on the volume 234 | 235 | On $\mathcal V$, we use the following equation: 236 | 237 | $$\forall p \in \mathcal V, \quad u^{n+1}(p) = \frac{u^n(p_x^+) + u^n(p_x^-) + u^n(p_y^+) + u^n(p_y^-) + u^n(p_z^+) + u^n(p_z^-)}{6}$$ 238 | 239 | ##### Updating the scalar field on the boundary surface 240 | 241 | On $\mathcal S$, we need to take into account the boundary conditions. 242 | 243 | * On $\mathcal S_T$, we just use the following equation for the Dirichlet boundary condition: 244 | 245 | $$\forall p \in \mathcal S_T, \quad u^{n+1}(p) = 0$$ 246 | 247 | * On $\mathcal S_B$ and $\mathcal S_E$, we need to implement the Neuman boundary conditions as explained below. 248 | 249 | ##### Neuman boundary conditions 250 | 251 | We use central, forward, or backward finite difference schemes for $\nabla u(p)$ depending on the value of each $x$, $y$, $z$ component of the crude normal vector $\nu^0(p)$. 252 | 253 | We note $k=1$ for $\mathcal S_B$, and $k=0$ for $\mathcal S_E$. We also define: 254 | 255 | $$ 256 | \forall p \in \mathcal S, \quad 257 | u_x^{n+1}(p)= 258 | \begin{cases} 259 | u^{n+1}(p_x^+) & \textrm{if} \quad \nu^0_x(p)=+1\\ 260 | u^{n+1}(p_x^-) & \textrm{if} \quad \nu^0_x(p)=-1\\ 261 | 0 & \textrm{if} \quad \nu^0_x(p)=0\\ 262 | \end{cases} 263 | $$ 264 | 265 | and similarly for the other components, $u_y^{n+1}$ and $u_z^{n+1}$. 266 | 267 | Then, we find the following scheme for the Neumann boundary condition: 268 | 269 | $$ 270 | \forall p \in \mathcal S_B \cup \mathcal S_E, \quad 271 | u^{n+1}(p) = 272 | \begin{cases} 273 | \displaystyle\frac{u_x^{n+1}(p) \\, |\nu_x(p)| + u_y^{n+1}(p) \\, |\nu_y(p)| + u_z^{n+1}(p) \\, |\nu_z(p)| + k}{|\nu_x(p)| + |\nu_y(p)| + |\nu_z(p)| + k} & \textrm{if} \quad \lVert\nu^0(p)\rVert_1 \geq 1\\ 274 | 0 & \textrm{otherwise} 275 | \end{cases} 276 | $$ 277 | 278 | #### GPU implementation 279 | 280 | We wrote a GPU implementation with the Cupy Python package leveraging the NVIDIA CUDA API. There are a few tricks: 281 | 282 | - We use two CUDA kernels: one for the numerical scheme in the brain region $\mathcal V$, another for the one on the surfaces $\mathcal S_B$ and $\mathcal S_E$ (Neumann conditions). Every iteration involves a call to both kernels. 283 | 284 | - We use two 3D arrays for the solution to Laplace's equation, `U_1` and `U_2`. The CUDA kernels use one array to read the old values ($u^n$), another one to write the new values ($u^{n+1}$). At each iteration, we swap `U_1` and `U_2`. 285 | 286 | - To avoid using too much GPU memory (there are wide empty spaces around a given brain region $\mathcal V$), we compute the axis boundaries of the mask array and we pad each side with a few voxels. 287 | 288 | - To ensure all arrays fit in GPU memory, we cut the brain in half (two hemispheres), which is possible as long as the streamlines are not expected to cross the sagittal midline within the brain region. 289 | 290 | - We achieve about 1000 iterations per minute on an NVIDIA Geforce RTX 2070 SUPER (for one hemisphere). 291 | 292 | - Empirically, a total of 10,000 iterations per hemisphere seems to be necessary for proper convergence of the algorithm. 293 | 294 | - In total, the entire method (steps 1-4) should run under one or two hours with a GPU. 295 | 296 | > Note: an alternative would be to use sparse data structures instead of dense ones, but it would require a bit more work. 297 | 298 | ![Solution to Laplace's equation](https://user-images.githubusercontent.com/1942359/172403287-129520c5-46d2-448a-a576-7505d3c9ad6b.png) 299 | 300 | 301 | ### Step 3. Gradient 302 | 303 | Once the solution of Laplace's equation has been obtained, we can estimate its gradient that will be used to integrate the streamlines in Step 4. 304 | 305 | We use central, forward, or backward differences for the numerical scheme of the derivative of $u$ depending on whether the voxel is inside the volume or on the surface, and depending on the relative position of the voxel compared to the volume (which is encoded in $\nu^0(p)$). 306 | 307 | We get: 308 | 309 | $$ 310 | \forall p \in \mathcal V \cup \mathcal S, \quad 311 | \widetilde{\nabla u}_x(p) = 312 | \begin{cases} 313 | \displaystyle 314 | \frac{u(p_x^+) + u(p_x^-)}{2} & \textrm{if} \quad p \in \mathcal V\\ 315 | u(p_x^+) - u(p) & \textrm{if} \quad p \in \mathcal S, \\, \nu^0(p)=+1\\ 316 | u(p) - u(p_x^-) & \textrm{if} \quad p \in \mathcal S, \\, \nu^0(p)=-1\\ 317 | 0 & \textrm{if} \quad p \in \mathcal S, \\, \nu^0(p)=0\\ 318 | \end{cases} 319 | $$ 320 | 321 | and similarly for $\widetilde{\nabla u}_y(p)$ and $\widetilde{\nabla u}_z(p)$. 322 | 323 | Finally, we normalize the gradient: 324 | 325 | $$ 326 | \forall p \in \mathcal V \cup \mathcal S, \quad 327 | \nabla u(p) = \begin{cases} 328 | \displaystyle \frac{\widetilde{\nabla u}(p)}{\lVert \widetilde{\nabla u}(p) \rVert_2} & \textrm{if} \quad {\lVert \widetilde{\nabla u}(p) \rVert_2} > 0\\ 329 | 0 & \textrm{otherwise} 330 | \end{cases} 331 | $$ 332 | 333 | ### Step 4. Streamlines 334 | 335 | To compute streamlines, we start from voxels in the bottom surface $\mathcal S_B$ and we integrate the Laplace's equation's solution's gradient. 336 | 337 | More precisely, we solve an ordinary differential equation (ODE) with 338 | 339 | $$ 340 | \forall p \in \mathcal S_B, \quad \phi_p : \mathbb R_+ \longrightarrow \Omega 341 | $$ 342 | 343 | which must satisfy: 344 | 345 | $$ 346 | \forall t \geq 0, \\, \forall p \in \mathcal S_B, \quad 347 | \phi'_p(t) = \nabla u \left( \phi_p(t) \right) 348 | $$ 349 | 350 | with initial conditions: 351 | 352 | $$ 353 | \forall p \in \mathcal S_B, \quad 354 | \begin{cases} 355 | \phi_p(0) &= p\\ 356 | \phi'_p(0) &= \nabla u(p)\\ 357 | \end{cases} 358 | $$ 359 | 360 | #### Numerical integration 361 | 362 | We use the forward Euler method to integrate this ODE numerically. 363 | 364 | At every time step, we use a linear interpolation to estimate the gradient at a position between voxels. 365 | 366 | We also stop the integration for streamlines that go beyond the volume $\mathcal V$. 367 | 368 | Finally, once obtained, we resample the streamlines to reparametrize them in 100 steps. 369 | 370 | ![Streamlines (2D projections)](https://user-images.githubusercontent.com/1942359/172402292-064721a0-293c-47fb-976f-a737af1f288b.png) 371 | 372 | ![Streamlines (3D)](https://user-images.githubusercontent.com/1942359/172402917-5b832ee8-ce07-41e5-9cb5-86e54cff201e.png) 373 | 374 | ![Streamlines (3D)](https://user-images.githubusercontent.com/1942359/172403038-db82b161-f595-44a8-9521-110a6d0bbeb5.png) 375 | 376 | 377 | ### Step 5. Flatmaps 378 | 379 | TO DO. 380 | 381 | 382 | ## References 383 | 384 | Some references: 385 | 386 | - Jones, S. E., Buchbinder, B. R., & Aharon, I. (2000). Three‐dimensional mapping of cortical thickness using Laplace's equation. Human brain mapping, 11(1), 12-32. 387 | - Lerch, J. P., Carroll, J. B., Dorr, A., Spring, S., Evans, A. C., Hayden, M. R., ... & Henkelman, R. M. (2008). Cortical thickness measured from MRI in the YAC128 mouse model of Huntington's disease. Neuroimage, 41(2), 243-251. 388 | 389 | Other implementations: 390 | 391 | - https://github.com/KimLabResearch/CorticalFlatMap 392 | - https://github.com/AllenInstitute/cortical_coordinates 393 | -------------------------------------------------------------------------------- /common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ------------------------------------------------------------------------------------------------ 5 | # Imports 6 | # ------------------------------------------------------------------------------------------------ 7 | 8 | import math 9 | from pathlib import Path 10 | import shutil 11 | import urllib 12 | 13 | import nrrd 14 | import h5py 15 | import numba 16 | from cupyx import jit 17 | import cupy as cp 18 | from tqdm import tqdm 19 | import numpy as np 20 | 21 | 22 | # ------------------------------------------------------------------------------------------------ 23 | # Constants 24 | # ------------------------------------------------------------------------------------------------ 25 | 26 | # Paths. 27 | ROOT_PATH = Path(__file__).parent.resolve() 28 | MASK_NRRD_PATH = ROOT_PATH / '../ccf_2017/isocortex_mask_10.nrrd' 29 | BOUNDARY_NRRD_PATH = ROOT_PATH / '../ccf_2017/isocortex_boundary_10.nrrd' 30 | 31 | # Volume shape. 32 | N, M, P = 1320, 800, 1140 33 | 34 | # Values used in the mask file 35 | V_OUTSIDE = 0 # voxels outside of the surfaces and brain region 36 | V_ST = 1 # top (outer) surface 37 | V_VOLUME = 2 # volume between the two surfaces 38 | V_SB = 3 # bottom (inter) surface 39 | V_SE = 4 # intermediate surfaces 40 | 41 | # Region used. 42 | REGION = 'isocortex' 43 | REGION_ID = 315 44 | 45 | 46 | # ------------------------------------------------------------------------------------------------ 47 | # Generic data loading functions 48 | # ------------------------------------------------------------------------------------------------ 49 | 50 | def region_dir(region): 51 | """Return the path to the directory containing the output data files for a given brain region. 52 | """ 53 | region_dir = ROOT_PATH / f'regions/{region}' 54 | region_dir.mkdir(exist_ok=True, parents=True) 55 | return region_dir 56 | 57 | 58 | def filepath(region, fn): 59 | """Return the path to an output file.""" 60 | return region_dir(region) / (fn + '.npy') 61 | 62 | 63 | def load_npy(path): 64 | """Load an NPY file in memmap read mode.""" 65 | if not path.exists(): 66 | print(f"Error: file {path} does not exist.") 67 | return 68 | print(f"Loading `{path}`.") 69 | return np.load(path, mmap_mode='r') 70 | 71 | 72 | def save_npy(path, arr): 73 | """Save an array to an NPY file.""" 74 | print(f"Saving `{path}` ({arr.shape}, {arr.dtype}).") 75 | np.save(path, arr) 76 | 77 | 78 | # ------------------------------------------------------------------------------------------------ 79 | # Old data loading functions 80 | # ------------------------------------------------------------------------------------------------ 81 | 82 | def get_mesh(region_id, region): 83 | """NOTE: this function is not used at the moment.""" 84 | 85 | path = filepath(region, 'mesh') 86 | mesh = load_npy(path) 87 | if mesh is not None: 88 | return mesh 89 | 90 | # Download and save the OBJ file. 91 | url = f"http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/annotation/ccf_2017/structure_meshes/{region_id:d}.obj" 92 | obj_fn = region_dir(region) / f'{region}.obj' 93 | with urllib.request.urlopen(url) as response, open(obj_fn, 'wb') as f: 94 | shutil.copyfileobj(response, f) 95 | 96 | # Convert the OBJ to npy. 97 | import pywavefront 98 | scene = pywavefront.Wavefront( 99 | obj_fn, create_materials=True, collect_faces=False) 100 | vertices = np.array(scene.vertices, dtype=np.float32) 101 | np.save(path, vertices) 102 | return vertices 103 | 104 | 105 | def load_flatmap_paths(flatmap_path, annotation_path): 106 | """NOTE: this function is not used at the moment.""" 107 | 108 | with h5py.File(flatmap_path, 'r+') as f: 109 | dorsal_paths = f['paths'][:] 110 | dorsal_lookup = f['view lookup'][:] 111 | 112 | n_lines, max_length = dorsal_paths.shape 113 | 114 | # Dorsal lookup: 115 | ap, ml = dorsal_lookup.shape # every item is an index 116 | 117 | idx = (dorsal_lookup != 0) 118 | # All unique indices appearing in dorsal_paths: 119 | ids = dorsal_lookup[idx] 120 | 121 | # The (ap, ml) pair for each unique index: 122 | apml = np.c_[np.nonzero(idx)] 123 | 124 | i = 10000 125 | dpath = dorsal_paths[i] 126 | dpath = dpath[dpath != 0] 127 | ccf10, meta = nrrd.read(annotation_path) 128 | line = np.c_[np.unravel_index(dpath, ccf10.shape)] 129 | 130 | return line 131 | 132 | 133 | # ------------------------------------------------------------------------------------------------ 134 | # Mask 135 | # ------------------------------------------------------------------------------------------------ 136 | 137 | def load_mask_nrrd(mask_nrrd, boundary_nrrd): 138 | """Generate a single mask volume from the input mask and boundary NRRD files.""" 139 | 140 | mask, mask_meta = nrrd.read(mask_nrrd) 141 | boundary, boundary_meta = nrrd.read(boundary_nrrd) 142 | 143 | n, m, p = boundary.shape 144 | assert mask.shape == (n, m, p) 145 | 146 | mask_ibl = mask.copy() 147 | mask_ibl = mask_ibl.astype(np.uint8) 148 | idx = boundary != 0 149 | mask_ibl[idx] = boundary[idx] 150 | 151 | return mask_ibl 152 | 153 | 154 | def get_mask(region, mask_nrrd_path=MASK_NRRD_PATH, boundary_nrrd_path=BOUNDARY_NRRD_PATH): 155 | """Compute (or load from the cache) the mask volume for a given region. 156 | 157 | The mask volume is computed from two nrrd files (mask and boundary). 158 | 159 | The mask values are: 160 | 161 | ```python 162 | V_OUTSIDE = 0 # voxels outside of the surfaces and brain region 163 | V_ST = 1 # top (outer) surface 164 | V_VOLUME = 2 # volume between the two surfaces 165 | V_SB = 3 # bottom (inter) surface 166 | V_SE = 4 # intermediate surfaces 167 | ``` 168 | 169 | """ 170 | 171 | path = filepath(region, 'mask') 172 | if path.exists(): 173 | return load_npy(path) 174 | print(f"Computing mask from the original nrrd files...") 175 | mask = load_mask_nrrd(mask_nrrd_path, boundary_nrrd_path) 176 | save_npy(path, mask) 177 | return load_npy(path) 178 | 179 | 180 | def get_surface_mask(region, surf_vals): 181 | """Return a 3D array (volume) of booleans indicating the voxels that belong to one or several 182 | surfaces. 183 | 184 | `surf_vals` is a tuple of integers, for example `(V_ST, V_SB)` (see global constants at the 185 | top of this file). 186 | 187 | """ 188 | mask = get_mask(region) 189 | surface_mask = np.isin(mask, surf_vals) 190 | assert surface_mask.shape == mask.shape 191 | assert surface_mask.dtype == bool 192 | return surface_mask 193 | 194 | 195 | def get_surface_indices(region, surf_vals): 196 | """Return the 3D indices of the voxels belonging to one or several surfaces.""" 197 | 198 | surface_mask = get_surface_mask(region, surf_vals) 199 | i, j, k = np.nonzero(surface_mask) 200 | pos = np.c_[i, j, k] 201 | return pos 202 | 203 | 204 | # ------------------------------------------------------------------------------------------------ 205 | # Entry-point 206 | # ------------------------------------------------------------------------------------------------ 207 | if __name__ == '__main__': 208 | get_mask(REGION) 209 | -------------------------------------------------------------------------------- /gradient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ------------------------------------------------------------------------------------------------ 5 | # Imports 6 | # ------------------------------------------------------------------------------------------------ 7 | 8 | from common import * 9 | 10 | from scipy.interpolate import interpn 11 | from scipy.interpolate import interp1d 12 | 13 | 14 | # ------------------------------------------------------------------------------------------------ 15 | # Gradient 16 | # ------------------------------------------------------------------------------------------------ 17 | 18 | def compute_grad(mask, U): 19 | """Compute the gradient of a 3D scalar field.""" 20 | 21 | n, m, p = mask.shape 22 | 23 | # Find the surface. 24 | i, j, k = np.nonzero(np.isin(mask, (V_ST, V_SB, V_SE))) 25 | surf = np.zeros((n, m, p), dtype=bool) 26 | surf[i, j, k] = True 27 | iv, jv, kv = np.nonzero(mask == V_VOLUME) 28 | 29 | # Clip the laplacian. 30 | q = .9999 31 | Uclip = np.clip(U, U.min(), np.quantile(U, q)) 32 | 33 | # Compute the gradient inside the volume. 34 | grad = np.zeros((n, m, p, 3), dtype=np.float32) 35 | grad[iv, jv, kv, 0] = .5 * (Uclip[iv+1, jv, kv] - Uclip[iv-1, jv, kv]) 36 | grad[iv, jv, kv, 1] = .5 * (Uclip[iv, jv+1, kv] - Uclip[iv, jv-1, kv]) 37 | grad[iv, jv, kv, 2] = .5 * (Uclip[iv, jv, kv+1] - Uclip[iv, jv, kv-1]) 38 | 39 | # Compute the gradient on the surface. 40 | idx = mask[i+1, j, k] == V_VOLUME 41 | grad[i[idx], j[idx], k[idx], 0] = Uclip[ 42 | i[idx]+1, j[idx], k[idx]] - Uclip[i[idx], j[idx], k[idx]] 43 | 44 | idx = mask[i-1, j, k] == V_VOLUME 45 | grad[i[idx], j[idx], k[idx], 0] = Uclip[ 46 | i[idx], j[idx], k[idx]] - Uclip[i[idx]-1, j[idx], k[idx]] 47 | 48 | idx = mask[i, j+1, k] == V_VOLUME 49 | grad[i[idx], j[idx], k[idx], 1] = Uclip[ 50 | i[idx], j[idx]+1, k[idx]] - Uclip[i[idx], j[idx], k[idx]] 51 | 52 | idx = mask[i, j-1, k] == V_VOLUME 53 | grad[i[idx], j[idx], k[idx], 1] = Uclip[ 54 | i[idx], j[idx], k[idx]] - Uclip[i[idx], j[idx]-1, k[idx]] 55 | 56 | idx = mask[i, j, k+1] == V_VOLUME 57 | grad[i[idx], j[idx], k[idx], 2] = Uclip[ 58 | i[idx], j[idx], k[idx]+1] - Uclip[i[idx], j[idx], k[idx]] 59 | 60 | idx = mask[i, j, k-1] == V_VOLUME 61 | grad[i[idx], j[idx], k[idx], 2] = Uclip[ 62 | i[idx], j[idx], k[idx]] - Uclip[i[idx], j[idx], k[idx]-1] 63 | 64 | return grad 65 | 66 | 67 | def normalize_gradient(grad, threshold=0): 68 | """Normalize the gradient.""" 69 | 70 | # Normalize the gradient. 71 | gradn = np.linalg.norm(grad, axis=3) 72 | 73 | idx = gradn > threshold 74 | grad[idx] /= gradn[idx, np.newaxis] 75 | 76 | # Kill gradient vectors that are too small. 77 | if threshold > 0: 78 | grad[~idx] = 0 79 | 80 | return grad 81 | 82 | 83 | def get_gradient(region): 84 | """Compute (or load from the cache) the gradient to the solution of Laplace's equation.""" 85 | 86 | path = filepath(region, 'gradient') 87 | gradient = load_npy(path) 88 | if gradient is not None: 89 | return gradient 90 | 91 | # Load the laplacian to compute the gradient. 92 | U = load_npy(filepath(region, 'laplacian')) 93 | if U is None: 94 | # TODO: compute the laplacian with code in streamlines.py 95 | raise NotImplementedError() 96 | assert U.ndim == 3 97 | 98 | # Load the mask. 99 | mask = load_npy(filepath(region, 'mask')) 100 | 101 | # Compute the gradient. 102 | gradient = compute_grad(mask, U) 103 | assert gradient.ndim == 4 104 | 105 | # Normalize the gradient. 106 | gradient = normalize_gradient(gradient) 107 | 108 | # Save the gradient. 109 | save_npy(path, gradient) 110 | 111 | del gradient 112 | return load_npy(path) 113 | 114 | 115 | # ------------------------------------------------------------------------------------------------ 116 | # Entry-point 117 | # ------------------------------------------------------------------------------------------------ 118 | 119 | if __name__ == '__main__': 120 | get_gradient(REGION) 121 | -------------------------------------------------------------------------------- /input/README.md: -------------------------------------------------------------------------------- 1 | This directory should contain the following files: 2 | 3 | ``` 4 | isocortex_boundary_10.nrrd 5 | isocortex_mask_10.nrrd 6 | ``` 7 | 8 | -------------------------------------------------------------------------------- /laplacian.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ------------------------------------------------------------------------------------------------ 5 | # Imports 6 | # ------------------------------------------------------------------------------------------------ 7 | 8 | from common import * 9 | from surface import * 10 | 11 | 12 | # ------------------------------------------------------------------------------------------------ 13 | # Constants 14 | # ------------------------------------------------------------------------------------------------ 15 | 16 | ITERATIONS = 10_000 17 | MARGIN = 6 18 | 19 | 20 | # ------------------------------------------------------------------------------------------------ 21 | # Laplacian simulation 22 | # ------------------------------------------------------------------------------------------------ 23 | 24 | def clear_gpu_memory(): 25 | """Clear GPU memory.""" 26 | 27 | mempool = cp.get_default_memory_pool() 28 | pinned_mempool = cp.get_default_pinned_memory_pool() 29 | 30 | mempool.free_all_blocks() 31 | pinned_mempool.free_all_blocks() 32 | 33 | 34 | def bounding_box(mask, margin, hemisphere=0): 35 | """Computing the bounding box to a volume.""" 36 | 37 | assert mask.ndim == 3 38 | n, m, p = mask.shape 39 | 40 | idx = np.nonzero(mask != 0) 41 | bounds = np.array( 42 | [(np.min(idx[i]), np.max(idx[i])) for i in range(3)], dtype=np.int64) 43 | 44 | # Split hemisphere. 45 | if hemisphere == -1: 46 | # only keep left hemisphere 47 | bounds[2, 1] = p // 2 48 | elif hemisphere == +1: 49 | # only keep right hemisphere 50 | bounds[2, 0] = p // 2 51 | # if hemisphere == 0, keep both hemispheres 52 | 53 | # Margin. 54 | assert np.all(bounds[:, 0] >= margin) 55 | assert np.all(bounds[:, 1] <= np.array([[n, m, p]]) - margin) 56 | bounds[:, 0] -= margin 57 | bounds[:, 1] += margin 58 | 59 | # Indexing box. 60 | box = tuple( 61 | slice(bounds[i, 0], bounds[i, 1], None) 62 | for i in range(3)) 63 | 64 | nc, mc, pc = bounds[:, 1] - bounds[:, 0] 65 | return box, (nc, mc, pc) 66 | 67 | 68 | def pad_inplace_3d(arr, margin, value=0): 69 | """Clear the edges of a 3D array.""" 70 | 71 | assert arr.ndim == 3 72 | arr[:margin, :, :] = value 73 | arr[-margin:, :, :] = value 74 | arr[:, :margin, :] = value 75 | arr[:, -margin:, :] = value 76 | arr[:, :, :margin] = value 77 | arr[:, :, -margin:] = value 78 | return arr 79 | 80 | 81 | @jit.rawkernel() 82 | def laplace(Uin, Uout, M, nc, mc, pc): 83 | """CUDA kernel for the Laplacian inside the volume. 84 | 85 | The 3 arrays Uin, Uout, M (mask) have size (nc, mc, pc). 86 | 87 | """ 88 | 89 | # Current voxel 90 | i = 1 + jit.blockIdx.x * jit.blockDim.x + jit.threadIdx.x 91 | j = 1 + jit.blockIdx.y * jit.blockDim.y + jit.threadIdx.y 92 | k = 1 + jit.blockIdx.z * jit.blockDim.z + jit.threadIdx.z 93 | 94 | if (1 <= i) and (1 <= j) and (1 <= k) and (i <= nc - 2) and (j <= mc - 2) and (k <= pc - 2): 95 | m = M[i, j, k] 96 | if m == V_VOLUME: 97 | Uout[i, j, k] = 1./6 * ( 98 | Uin[i - 1, j, k] + 99 | Uin[i + 1, j, k] + 100 | Uin[i, j - 1, k] + 101 | Uin[i, j + 1, k] + 102 | Uin[i, j, k - 1] + 103 | Uin[i, j, k + 1]) 104 | 105 | 106 | @jit.rawkernel() 107 | def neumann(Uout, M, Ni, Nj, Nk, nc, mc, pc): 108 | """CUDA kernel for the Neumann boundary conditions. 109 | 110 | The 3 arrays Uin, Uout, M (mask) have size (nc, mc, pc). 111 | 112 | """ 113 | 114 | # The 3 arrays M, Uin, Uout have size (nc, mc, pc) 115 | 116 | # Current voxel 117 | i = 1 + jit.blockIdx.x * jit.blockDim.x + jit.threadIdx.x 118 | j = 1 + jit.blockIdx.y * jit.blockDim.y + jit.threadIdx.y 119 | k = 1 + jit.blockIdx.z * jit.blockDim.z + jit.threadIdx.z 120 | 121 | if (1 <= i) and (1 <= j) and (1 <= k) and (i <= nc - 2) and (j <= mc - 2) and (k <= pc - 2): 122 | m = M[i, j, k] 123 | # Direction of streamlines: S_outer (val=1) ==> S_inner (val=3) 124 | if m == V_SB or m == V_SE: 125 | 126 | v = 1 127 | if m == V_SE: 128 | v = 0 129 | 130 | ni = Ni[i, j, k] 131 | nj = Nj[i, j, k] 132 | nk = Nk[i, j, k] 133 | 134 | # # Reverse the gradient for one of the surfaces 135 | # if m == V_ST: 136 | # ni, nj, nk = -ni, -nj, -nk 137 | 138 | nis = int(cp.sign(ni)) 139 | njs = int(cp.sign(nj)) 140 | nks = int(cp.sign(nk)) 141 | nas = cp.abs(nis) + cp.abs(njs) + cp.abs(nks) 142 | 143 | nia = cp.abs(ni) 144 | nja = cp.abs(nj) 145 | nka = cp.abs(nk) 146 | na = nia + nja + nka 147 | 148 | if nas >= 1: 149 | Uout[i, j, k] = ( 150 | Uout[i+nis, j, k] * nia + 151 | Uout[i, j+njs, k] * nja + 152 | Uout[i, j, k+nks] * nka 153 | + v) / (na + v) 154 | 155 | 156 | class Runner: 157 | """Run the Laplacian simulation.""" 158 | 159 | def __init__(self, mask, normal, U=None, hemisphere=0): 160 | n, m, p = mask.shape 161 | self.shape = (n, m, p) 162 | assert mask.dtype == np.uint8 163 | # assert normal.dtype == np.float32 164 | assert normal.shape == self.shape + (3,) 165 | 166 | # Compute the bounding box of the mask. 167 | print("Computing the bounding box of the mask volume...") 168 | box, (nc, mc, pc) = bounding_box(mask, MARGIN, hemisphere=hemisphere) 169 | 170 | assert nc > 0 171 | assert mc > 0 172 | assert pc > 0 173 | 174 | # Mask. 175 | size = nc * mc * pc / 1024. ** 2 176 | print(f"Creating mask array of total size {size:.2f} MB on the GPU...") 177 | # Transfer the pask to the GPU. 178 | mask_gpu = cp.asarray(mask[box]) 179 | 180 | # Make padding. 181 | pad_inplace_3d(mask_gpu, MARGIN) 182 | 183 | # Normal. 184 | size = 3 * nc * mc * pc * 4 / 1024. ** 2 185 | print( 186 | f"Creating 3 normal arrays of total size {size:.2f} MB on the GPU...") 187 | # Transfer the normal to the GPU. 188 | normal0_gpu = cp.asarray(normal[..., 0][box]) 189 | normal1_gpu = cp.asarray(normal[..., 1][box]) 190 | normal2_gpu = cp.asarray(normal[..., 2][box]) 191 | 192 | # Create the two scalar fields. 193 | size = 2 * nc * mc * pc * 4 / 1024. ** 2 194 | print(f"Creating two arrays of total size {size:.2f} MB on the GPU...") 195 | Ua = cp.zeros((nc, mc, pc), dtype=np.float32) 196 | 197 | if U is not None: 198 | print(f"Starting from the existing laplacian array {U.shape}.") 199 | Ua[...] = cp.asarray(U[box]) 200 | else: 201 | # Initial values: the same as the mask. 202 | Ua[mask_gpu == V_ST] = 0 203 | Ua[mask_gpu == V_SB] = 1 204 | 205 | Ub = Ua.copy() 206 | 207 | # CUDA grid and block. 208 | b = 8 209 | self.block = (b, b, b) 210 | self.grid = (int(np.ceil(nc / float(b))), 211 | int(np.ceil(mc / float(b))), int(np.ceil(pc / float(b)))) 212 | 213 | # Main loop 214 | self.args = (cp.int32(nc), cp.int32(mc), cp.int32(pc)) 215 | 216 | self.mask = mask_gpu 217 | self.normal0 = normal0_gpu 218 | self.normal1 = normal1_gpu 219 | self.normal2 = normal2_gpu 220 | self.Ua = Ua 221 | self.Ub = Ub 222 | self.box = box 223 | 224 | def iter(self): 225 | """Run two iterations at once.""" 226 | 227 | # ping-pong between the 2 arrays to avoid edge-effects while computing 228 | # the Laplacian in parallel on the GPU. 229 | # NOTE: each Python iteration here is actually made of 2 algorithm iterations 230 | laplace[self.grid, self.block](self.Ua, self.Ub, self.mask, *self.args) 231 | neumann[self.grid, self.block]( 232 | self.Ub, self.mask, self.normal0, self.normal1, self.normal2, *self.args) 233 | 234 | laplace[self.grid, self.block](self.Ub, self.Ua, self.mask, *self.args) 235 | neumann[self.grid, self.block]( 236 | self.Ua, self.mask, self.normal0, self.normal1, self.normal2, *self.args) 237 | 238 | def run(self, iterations): 239 | """Run n iterations.""" 240 | 241 | for i in tqdm(range(iterations)): 242 | self.iter() 243 | if i % 10 == 0: 244 | cp.cuda.stream.get_current_stream().synchronize() 245 | 246 | # Construct the final result. 247 | print("Constructing the output array on the CPU...") 248 | Uout = np.zeros(self.shape, dtype=np.float32) 249 | Uout[self.box] = cp.asnumpy(self.Ua) 250 | 251 | return Uout 252 | 253 | def clear(self): 254 | """Clear the GPU memory.""" 255 | del self.mask 256 | del self.Ua 257 | del self.Ub 258 | del self.normal0 259 | del self.normal1 260 | del self.normal2 261 | clear_gpu_memory() 262 | 263 | 264 | def compute_laplacian(both_hemispheres=False): 265 | """Computing the Laplacian for 1 or 2 hemispheres.""" 266 | 267 | mask = get_mask(REGION) 268 | assert mask.ndim == 3 269 | assert mask.shape == (N, M, P) 270 | 271 | normal = get_normal(REGION) 272 | assert normal.ndim == 4 273 | assert normal.shape == (N, M, P, 3) 274 | 275 | # Load the current result. 276 | U0 = load_npy(filepath(REGION, 'laplacian')) 277 | # U0 = None # HACK: restart from scratch 278 | 279 | # Left hemisphere. 280 | rl = Runner(mask, normal, U=U0, hemisphere=-1) 281 | Ul = rl.run(ITERATIONS) 282 | rl.clear() 283 | 284 | if both_hemispheres: 285 | # Right hemisphere. 286 | rr = Runner(mask, normal, U=U0, hemisphere=+1) 287 | Ur = rr.run(ITERATIONS) 288 | 289 | # Merge the two hemispheres. 290 | U = Ul + Ur 291 | else: 292 | U = Ul 293 | 294 | # Save the result. 295 | # save_npy(filepath(REGION, 'laplacian_left'), Ul) 296 | # save_npy(filepath(REGION, 'laplacian_right'), Ur) 297 | save_npy(filepath(REGION, 'laplacian'), U) 298 | 299 | 300 | # ------------------------------------------------------------------------------------------------ 301 | # Entry point 302 | # ------------------------------------------------------------------------------------------------ 303 | 304 | if __name__ == '__main__': 305 | compute_laplacian(both_hemispheres=True) 306 | -------------------------------------------------------------------------------- /plotting.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "b56ac329-f26c-4ff9-907d-a45f72f7f53f", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "## Imports" 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": null, 16 | "id": "07c2e2ed-97c4-4fe2-b917-0d043e0ea3c8", 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from common import *\n", 21 | "from surface import *\n", 22 | "from streamlines import *" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "id": "fc3b034b-4e85-4667-a8e1-ef4b1ea692df", 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "from ipywidgets import Layout, interact, IntSlider, FloatSlider\n", 33 | "import matplotlib.pyplot as plt\n", 34 | "from matplotlib import cm\n", 35 | "%matplotlib inline\n", 36 | "plt.rcParams[\"figure.dpi\"] = 100\n", 37 | "plt.rcParams[\"axes.grid\"] = False" 38 | ] 39 | }, 40 | { 41 | "cell_type": "markdown", 42 | "id": "5c972016-4b11-4825-a3f8-84eee7f5a5d6", 43 | "metadata": { 44 | "tags": [] 45 | }, 46 | "source": [ 47 | "## Plotting functions" 48 | ] 49 | }, 50 | { 51 | "cell_type": "code", 52 | "execution_count": null, 53 | "id": "b1c15836-b6b1-49c4-a7d6-dfae8fb9589b", 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "def select(arr, surf_vals=(V_SB, V_ST, V_SE), region=REGION):\n", 58 | " \"\"\"Select values from a 3D or 4D volume corresponding to a surface or another region.\n", 59 | " \n", 60 | " Return i, j, k, v, where i, j, k are the indices of the voxels, and v are scalar or \n", 61 | " vector values of the array at those voxels.\n", 62 | " \"\"\"\n", 63 | " i, j, k = get_surface_indices(region, surf_vals).T\n", 64 | " v = arr[i, j, k, ...]\n", 65 | " return i, j, k, v" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": null, 71 | "id": "9282fb7b-6d46-457e-acf9-dfde4d0eda62", 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "def barebone(ax):\n", 76 | " ax.set_facecolor(cm.get_cmap('viridis')(0))\n", 77 | " ax.set_xticks([])\n", 78 | " ax.set_yticks([])" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": null, 84 | "id": "f2571688-f5ae-4783-84e0-789b7279646a", 85 | "metadata": {}, 86 | "outputs": [], 87 | "source": [ 88 | "def plot_volume(scalar, vector=None, streamlines=None, max_streamlines=10_000, all_projections=True):\n", 89 | " n, m, p = scalar.shape[:3]\n", 90 | " vmin = scalar.min()\n", 91 | " vmax = scalar.max()\n", 92 | " \n", 93 | " imshow_kwargs = dict(interpolation='none', origin='upper', vmin=vmin, vmax=vmax)\n", 94 | " interact_kwargs = dict(a=(0.0, 1.0, 0.01))\n", 95 | " f_kwargs = dict(a=.5)\n", 96 | " quiver_kwargs = dict(scale=30, width=.003, alpha=.35)\n", 97 | " streamlines_kwargs = dict(color='w', lw=2, alpha=.15)\n", 98 | " title_kwargs = dict(color='w')\n", 99 | " \n", 100 | " if vector is not None:\n", 101 | " assert vector.ndim == 4\n", 102 | " assert vector.shape[3] == 3\n", 103 | " i, j, k, v = select(vector)\n", 104 | " interact_kwargs['show_vector'] = True\n", 105 | " f_kwargs['show_vector'] = True\n", 106 | " \n", 107 | " if streamlines is not None:\n", 108 | " streamlines_subset = subset(streamlines, max_streamlines)\n", 109 | " interact_kwargs['show_streamlines'] = True\n", 110 | " f_kwargs['show_streamlines'] = True\n", 111 | " \n", 112 | " @interact(**interact_kwargs)\n", 113 | " def f(**f_kwargs):\n", 114 | " a = f_kwargs.get('a', None)\n", 115 | " show_vector = f_kwargs.get('show_vector', False)\n", 116 | " show_streamlines = f_kwargs.get('show_streamlines', False)\n", 117 | " \n", 118 | " fig, axes = plt.subplots(1, 3 if all_projections else 1, figsize=(18, 12))\n", 119 | " \n", 120 | " # HACK\n", 121 | " if not all_projections:\n", 122 | " axes = [axes]\n", 123 | " \n", 124 | " ai = np.clip(int(round(n*a)), 0, n-1)\n", 125 | " aj = np.clip(int(round(m*a)), 0, m-1)\n", 126 | " ak = np.clip(int(round(p*a)), 0, p-1)\n", 127 | " \n", 128 | " axes[0].imshow(scalar[ai, :, :], **imshow_kwargs)\n", 129 | " barebone(axes[0])\n", 130 | " axes[0].set_title('Coronal', **title_kwargs)\n", 131 | " \n", 132 | " if all_projections:\n", 133 | " axes[1].imshow(scalar[:, aj, :], **imshow_kwargs)\n", 134 | " axes[2].imshow(scalar[:, :, ak], **imshow_kwargs)\n", 135 | "\n", 136 | " barebone(axes[1])\n", 137 | " barebone(axes[2])\n", 138 | "\n", 139 | " axes[1].set_title('Transverse', **title_kwargs)\n", 140 | " axes[2].set_title('Sagittal', **title_kwargs)\n", 141 | "\n", 142 | " if vector is not None and show_vector:\n", 143 | " step = 3\n", 144 | " idxq = np.nonzero(i == ai)[0][::step]\n", 145 | " axes[0].quiver(k[idxq], j[idxq], v[idxq, 2], -v[idxq, 1], **quiver_kwargs)\n", 146 | " \n", 147 | " if all_projections:\n", 148 | " idxq = np.nonzero(j == aj)[0][::step]\n", 149 | " axes[1].quiver(k[idxq], i[idxq], v[idxq, 2], -v[idxq, 0], **quiver_kwargs)\n", 150 | "\n", 151 | " idxq = np.nonzero(k == ak)[0][::step]\n", 152 | " axes[2].quiver(j[idxq], i[idxq], v[idxq, 1], -v[idxq, 0], **quiver_kwargs)\n", 153 | " \n", 154 | " if streamlines is not None and show_streamlines:\n", 155 | " pz = streamlines_subset[:, :, 0]\n", 156 | " pidx = (ai-2 <= pz[:, :]) & (pz[:, :] <= ai+2)\n", 157 | " which = pidx.max(axis=1) > 0\n", 158 | " axes[0].plot(streamlines_subset[which, :, 2].T, streamlines_subset[which, :, 1].T, **streamlines_kwargs);\n", 159 | " \n", 160 | " if all_projections:\n", 161 | " pz = streamlines_subset[:, :, 1]\n", 162 | " pidx = (aj-2 <= pz[:, :]) & (pz[:, :] <= aj+2)\n", 163 | " which = pidx.max(axis=1) > 0\n", 164 | " axes[1].plot(streamlines_subset[which, :, 2].T, streamlines_subset[which, :, 0].T, **streamlines_kwargs);\n", 165 | "\n", 166 | " pz = streamlines_subset[:, :, 2]\n", 167 | " pidx = (ak-2 <= pz[:, :]) & (pz[:, :] <= ak+2)\n", 168 | " which = pidx.max(axis=1) > 0\n", 169 | " axes[2].plot(streamlines_subset[which, :, 1].T, streamlines_subset[which, :, 0].T, **streamlines_kwargs);\n", 170 | " " 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "id": "499cf612-874f-4e8f-a376-c8771d2f2759", 176 | "metadata": { 177 | "tags": [] 178 | }, 179 | "source": [ 180 | "## Loading arrays" 181 | ] 182 | }, 183 | { 184 | "cell_type": "code", 185 | "execution_count": null, 186 | "id": "f496813a-78dc-47a3-be05-7feff68aa13b", 187 | "metadata": {}, 188 | "outputs": [], 189 | "source": [ 190 | "mask = load_npy(filepath(REGION, 'mask'))\n", 191 | "normal = get_normal(REGION)\n", 192 | "laplacian = load_npy(filepath(REGION, 'laplacian'))\n", 193 | "gradient = load_npy(filepath(REGION, 'gradient'))\n", 194 | "streamlines = load_npy(filepath(REGION, 'streamlines'))" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "id": "9d1d8873-4970-4556-9d29-5dce0fed21da", 200 | "metadata": {}, 201 | "source": [ 202 | "## Plots" 203 | ] 204 | }, 205 | { 206 | "cell_type": "raw", 207 | "id": "9f14daa3-1a2a-467b-bc81-3cfa5dfc144b", 208 | "metadata": {}, 209 | "source": [ 210 | "plot_volume(mask, vector=normal, all_projections=False)" 211 | ] 212 | }, 213 | { 214 | "cell_type": "raw", 215 | "id": "776a4ae4-efbd-47fb-90ad-70f915df39ef", 216 | "metadata": {}, 217 | "source": [ 218 | "plot_volume(laplacian, streamlines=streamlines)" 219 | ] 220 | }, 221 | { 222 | "cell_type": "raw", 223 | "id": "bf6f7cb5-2c8c-48b8-a407-554b7cddb607", 224 | "metadata": {}, 225 | "source": [] 226 | }, 227 | { 228 | "cell_type": "markdown", 229 | "id": "ba49378b-f6e5-48a8-aa38-67132f911089", 230 | "metadata": { 231 | "tags": [] 232 | }, 233 | "source": [ 234 | "### Plotting starting points" 235 | ] 236 | }, 237 | { 238 | "cell_type": "code", 239 | "execution_count": null, 240 | "id": "2b6dcd4a-78c9-444b-9aa2-92333856d407", 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [ 244 | "pos = init_ibl('isocortex')" 245 | ] 246 | }, 247 | { 248 | "cell_type": "code", 249 | "execution_count": null, 250 | "id": "6638071d-7836-48fc-af1c-06e32897b102", 251 | "metadata": {}, 252 | "outputs": [], 253 | "source": [ 254 | "ym = pos[:, 1].min()\n", 255 | "yM = pos[:, 1].max()" 256 | ] 257 | }, 258 | { 259 | "cell_type": "code", 260 | "execution_count": null, 261 | "id": "771bcdaa-9b2f-4fd1-9ff3-6a3e87988505", 262 | "metadata": {}, 263 | "outputs": [], 264 | "source": [ 265 | "@interact(y0=(ym, yM), k=(1, 50))\n", 266 | "def slice(y0=70, k=25):\n", 267 | " p0 = pos[::k, 0]\n", 268 | " p1 = pos[::k, 1]\n", 269 | " p2 = pos[::k, 2]\n", 270 | " idx = p1 >= y0 - 100\n", 271 | " idx &= p1 <= y0 + 100\n", 272 | " x = p0[idx]\n", 273 | " z = p2[idx]\n", 274 | " plt.figure(figsize=(8, 8));\n", 275 | " plt.plot(x, z, ',', color='k', markersize=1, alpha=.5);\n", 276 | " plt.xlim(0, N);\n", 277 | " plt.ylim(0, P);\n", 278 | " #plt.axis('square');" 279 | ] 280 | }, 281 | { 282 | "cell_type": "code", 283 | "execution_count": null, 284 | "id": "ed25e239-1240-4f19-a218-67c7785e8a6b", 285 | "metadata": {}, 286 | "outputs": [], 287 | "source": [] 288 | } 289 | ], 290 | "metadata": { 291 | "kernelspec": { 292 | "display_name": "Python 3 (ipykernel)", 293 | "language": "python", 294 | "name": "python3" 295 | }, 296 | "language_info": { 297 | "codemirror_mode": { 298 | "name": "ipython", 299 | "version": 3 300 | }, 301 | "file_extension": ".py", 302 | "mimetype": "text/x-python", 303 | "name": "python", 304 | "nbconvert_exporter": "python", 305 | "pygments_lexer": "ipython3", 306 | "version": "3.10.4" 307 | } 308 | }, 309 | "nbformat": 4, 310 | "nbformat_minor": 5 311 | } 312 | -------------------------------------------------------------------------------- /plotting.py: -------------------------------------------------------------------------------- 1 | """Plotting streamlines in 3D with Datoviz.""" 2 | 3 | from timeit import default_timer 4 | from math import cos, sin, pi 5 | 6 | import numpy as np 7 | 8 | from common import * 9 | from streamlines import * 10 | 11 | from datoviz import canvas, run, colormap 12 | 13 | 14 | SHOW_ALLEN = False 15 | MAX_PATHS = 100_000 16 | 17 | 18 | def ibl_streamlines(): 19 | paths = load_npy(filepath(REGION, 'streamlines')) 20 | paths = subset(paths, MAX_PATHS) 21 | 22 | # NOTE: the following line should be decommented when streamlines go from bottom to top 23 | # paths = paths[:, ::-1, :] 24 | 25 | return paths 26 | 27 | 28 | def allen_streamlines(): 29 | paths = load_npy(filepath(REGION, 'streamlines_allen')) 30 | paths = subset(paths, MAX_PATHS) 31 | return paths 32 | 33 | 34 | def plot_panel(panel, paths): 35 | assert paths.ndim == 3 36 | n, l, _ = paths.shape 37 | assert _ == 3 38 | length = l * np.ones(n) # length of each path 39 | 40 | color = np.tile(np.linspace(0, 1, l), n) 41 | color = colormap(color, vmin=0, vmax=1, cmap='viridis', alpha=1) 42 | 43 | # Plot lines 44 | v = panel.visual('line_strip', depth_test=True, transform=None) 45 | paths[:, :, 1] *= -1 46 | paths = paths.reshape((-1, 3)) 47 | paths -= paths.mean(axis=0) 48 | paths *= .0035 49 | paths[:, 1] += .2 50 | v.data('pos', paths) 51 | v.data('length', length) 52 | v.data('color', color) 53 | 54 | # # Plot points 55 | # v = panel.visual('point', depth_test=True) 56 | # v.data('pos', paths.reshape((-1, 3))) 57 | # v.data('ms', np.array([1.0])) 58 | # v.data('color', color) 59 | 60 | 61 | c = canvas(width=1920+20, height=1080+20, 62 | clear_color=(0, 0, 0, 0), show_fps=False) 63 | s = c.scene(cols=2 if SHOW_ALLEN else 1) 64 | 65 | if SHOW_ALLEN: 66 | paths_allen = allen_streamlines() 67 | p_allen = s.panel(col=0, controller='arcball') 68 | plot_panel(p_allen, paths_allen) 69 | 70 | paths_ibl = ibl_streamlines() 71 | p_ibl = s.panel(col=1 if SHOW_ALLEN else 0, controller='arcball') 72 | plot_panel(p_ibl, paths_ibl) 73 | if SHOW_ALLEN: 74 | p_allen.link_to(p_ibl) 75 | 76 | # We define an event callback to implement mouse picking 77 | 78 | t0 = default_timer() 79 | 80 | 81 | @c.connect 82 | def on_frame(ev): 83 | t = default_timer() - t0 84 | a = +2 * pi * t / 8 85 | # x = 2 * cos(a) 86 | # y = 2 * sin(a) 87 | p_ibl.arcball_rotate(0, 1, 0, a) 88 | 89 | 90 | run() 91 | -------------------------------------------------------------------------------- /regions/README.md: -------------------------------------------------------------------------------- 1 | Once you've run the code, you'll find the following files here: 2 | 3 | ``` 4 | mask.npy 5 | normal.npy 6 | laplacian.npy 7 | gradient.npy 8 | streamlines.npy 9 | ``` 10 | -------------------------------------------------------------------------------- /scripts/315L.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L.stl -------------------------------------------------------------------------------- /scripts/315L_surface.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface.stl -------------------------------------------------------------------------------- /scripts/315L_surface_nearestBottomAny.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface_nearestBottomAny.stl -------------------------------------------------------------------------------- /scripts/315L_surface_nearestNotBottomEdge.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface_nearestNotBottomEdge.stl -------------------------------------------------------------------------------- /scripts/315L_surface_nearestTop.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface_nearestTop.stl -------------------------------------------------------------------------------- /scripts/315L_surface_nearestTopAny.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface_nearestTopAny.stl -------------------------------------------------------------------------------- /scripts/315L_surface_within20.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface_within20.stl -------------------------------------------------------------------------------- /scripts/315L_surface_within40.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/315L_surface_within40.stl -------------------------------------------------------------------------------- /scripts/annotation_10.nrrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/annotation_10.nrrd -------------------------------------------------------------------------------- /scripts/compute_dorsal_flatmap.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import nrrd 3 | import matplotlib.pyplot as plt 4 | import h5py 5 | 6 | # Download the dorsal_flatmap_paths_10.h5 file from http://download.alleninstitute.org/informatics-archive/current-release/mouse_ccf/cortical_coordinates/ccf_2017/ 7 | 8 | # The laplacian file tells you the distance from the cortical surface and white matter surface for every point in the isocortex mask 9 | # we don't need this to generate the volume, but we need it to generate a volume that is in percentage steps (see below) 10 | #path_file = 'ccfpaths/laplacian_10.nrrd' 11 | #paths, paths_meta = nrrd.read(path_file) 12 | 13 | # The paths matrix is an N x 200 matrix which gives the linear index from every gray matter voxel to every white matter voxel, it takes a varying number of steps 14 | # to get to the white matter, so once you arrive the remaining 200-x values are just zero 15 | # The lookup matrix is AP x ML*2 and maps the position in ap/ml space to the index in the paths matrix 16 | f1 = h5py.File('ccfpaths/dorsal_flatmap_paths_10.h5','r+') 17 | dorsal_paths = f1['paths'][:] 18 | dorsal_lookup = f1['view lookup'][:] 19 | f1.close() 20 | 21 | # To compute a 3D volume which is APxML*2x200 we just copy over the paths data into this volume 22 | # dorsal_view_3D = np.zeros((1360,2720,200)) 23 | # for api in np.arange(0,dorsal_view_3D.shape[0]): 24 | # for mli in np.arange(0,dorsal_view_3D.shape[1]): 25 | # for depth in np.arange(0,dorsal_view_3D.shape[2]): 26 | # dorsal_view_3D[api,mli,depth] = dorsal_paths[dorsal_lookup[api,mli],depth] 27 | 28 | # Instead of computing at 10um we will compute at 25um 29 | dorsal_view_3D_25 = np.zeros((544,1088,80)) 30 | for api in np.arange(0,dorsal_view_3D_25.shape[0]): 31 | for mli in np.arange(0,dorsal_view_3D_25.shape[1]): 32 | for depth in np.arange(0,dorsal_view_3D.shape[2]): 33 | # get the 10um position from the lookup table 34 | path10startIdx = dorsal_lookup[np.round(api*2.5),np.round(mli*2.5)] 35 | atlas10idx = dorsal_paths[path10startIdx,np.round(depth*2.5)] 36 | 37 | # now we have the linear indices into the 10um atlas, so find the coordinates for each of these and convert these to the 25um coordinates 38 | # just do this for the top layer 39 | 40 | nrrd.write('dorsal_flatmap_3D.nrrd',dorsal_view_3D) 41 | 42 | # Note that we could also make a volume that is APxML*2 x depth percentage by using the laplacian volume to tell what percentage of the way from the 43 | # gray matter to white matter we are, so e.g. we could go in 10% steps and end up with a volume that is AP x ML*2 x 11 for steps 0:0.1:1 44 | # [TODO] 45 | 46 | # Also save just the top slice for immediate use 47 | top_slice = dorsal_view_3D[:,:,0] 48 | np.save('dorsal_flatmap.npy',top_slice) 49 | 50 | # And for clarity, load the ccf data and the dorsal flatmap and plot to show how to convert the coordinates into CCF space 51 | ccf10, meta = nrrd.read('annotation_10.nrrd') 52 | dorsal_view_3D, dorsal_meta = nrrd.read('dorsal_flatmap_3D.nrrd') 53 | ccf10_flat = ccf10.flatten() 54 | plt.imshow(np.take(ccf10_flat,np.int64(dorsal_view_3D[:,:,1]))) 55 | plt.clim(0,1000) -------------------------------------------------------------------------------- /scripts/dorsal_flatmap.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/dorsal_flatmap.npy -------------------------------------------------------------------------------- /scripts/isocortex_boundary_10.nrrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/isocortex_boundary_10.nrrd -------------------------------------------------------------------------------- /scripts/isocortex_mask_10.nrrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/int-brain-lab/atlas/4824a9e82be1fdba45c2557be1b5954cd278f591/scripts/isocortex_mask_10.nrrd -------------------------------------------------------------------------------- /scripts/test_cortexmodel2flatmap.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 31, 6 | "id": "c5c0fe3c-dfb2-4f2c-aa94-f1a5de6f677c", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "from stl import mesh\n", 12 | "import nrrd\n", 13 | "import matplotlib.pyplot as plt" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 62, 19 | "id": "4043f499-cb2c-42ed-b9cf-25d9a5750e7d", 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "# Load the cortex 3D model (area 315L.stl)\n", 24 | "cortexMesh = mesh.Mesh.from_file('315L.stl')" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "id": "b30f1721-7280-4bc4-bcfe-8aba15296aa4", 30 | "metadata": {}, 31 | "source": [ 32 | "# Do triangles land inside the annotation atlas\n", 33 | "We're going to load the 10um annotation dataset and just plot some vertices on top of slices to make sure we have everything lined up properly" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 18, 39 | "id": "8cb8f72b-afeb-4e64-844f-12c58c609f4d", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "# Load the 10um annotation file (annotation_10.nrrd)\n", 44 | "ann10, meta = nrrd.read('annotation_10.nrrd')" 45 | ] 46 | }, 47 | { 48 | "cell_type": "code", 49 | "execution_count": 63, 50 | "id": "14697c5c-feb3-4661-ab41-0e4bded6345e", 51 | "metadata": {}, 52 | "outputs": [], 53 | "source": [ 54 | "# Convert vertices to ccf coordinate range\n", 55 | "# +x = AP*, +y = ML*, +z = DV*\n", 56 | "triangles = cortexMesh.vectors\n", 57 | "trianglesCCF = np.empty(triangles.shape)" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 64, 63 | "id": "1d544bae-1992-4147-b0ba-28e75043dd99", 64 | "metadata": {}, 65 | "outputs": [], 66 | "source": [ 67 | "# *1000 to um, /10 to ccf indexes, then round\n", 68 | "flipVector = [1,-1,1]\n", 69 | "for i, triangle in enumerate(triangles):\n", 70 | " for j, vertex in enumerate(triangle):\n", 71 | " trianglesCCF[i][j] = np.round(np.multiply(vertex,flipVector)*1000/10)" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 71, 77 | "id": "da000247-966a-4177-9c8d-57c218a454aa", 78 | "metadata": {}, 79 | "outputs": [ 80 | { 81 | "data": { 82 | "image/png": "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\n", 83 | "text/plain": [ 84 | "
" 85 | ] 86 | }, 87 | "metadata": { 88 | "needs_background": "light" 89 | }, 90 | "output_type": "display_data" 91 | }, 92 | { 93 | "data": { 94 | "image/png": "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\n", 95 | "text/plain": [ 96 | "
" 97 | ] 98 | }, 99 | "metadata": { 100 | "needs_background": "light" 101 | }, 102 | "output_type": "display_data" 103 | }, 104 | { 105 | "data": { 106 | "image/png": "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\n", 107 | "text/plain": [ 108 | "
" 109 | ] 110 | }, 111 | "metadata": { 112 | "needs_background": "light" 113 | }, 114 | "output_type": "display_data" 115 | }, 116 | { 117 | "data": { 118 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAV4AAAD8CAYAAAA/iMxLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAyqUlEQVR4nO3deXxU5dn/8c81M1lIQiAJ+75IEFAWRTYRF8S9oo+2RWpFRXmqPApdLGjbp7/WatVW666PVVusVqQoglIXsCKt7ChL2QNhCYQlIRBCtlmu3x8z2hADmZBkzszker9evGbmzpmZ607CN2fuc5/7iKpijDEmclxOF2CMMU2NBa8xxkSYBa8xxkSYBa8xxkSYBa8xxkSYBa8xxkRYowWviFwhIltEJEdEpjfW+xhjTKyRxpjHKyJuYCswBsgDVgI3qerGBn8zY4yJMY21xzsEyFHVHapaCcwExjbSexljTEzxNNLrdgT2VHmcBwytuoGITAImAbhxn5tCeiOVYowxkXeMogJVbV3T1xoreKWGthPGNFT1JeAlgHTJ1KEyupFKMcaYyFuos3ed7GuNNdSQB3Su8rgTsK+R3ssYY2JKYwXvSqCXiHQXkURgHDCvkd7LGGNiSqMMNaiqT0T+B/gIcAOvquqGxngvY4yJNY01xouq/h34e2O9vjHGxCo7c80YYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyLMgtcYYyKs1uAVkVdF5KCI/LtKW6aILBCRbaHbjCpfu19EckRki4hc3liFG2NMrArnmmt/Bp4FXqvSNh34RFUfEZHpocfTRKQvwSsK9wM6AAtFJFtV/Q1btokH4vEgnuCvoDRvTqBLGwCOZjfneHsXnjKlzaoSCAQgAK6d+9CyMtTnQ30+R2qVZs0IdOsQ3GVxuTg4OA1fMyE1P0CLrccAcO0+iB4L3neiVhP9ag1eVV0sIt2qNY8FLgrdnwEsAqaF2meqagWQKyI5wBBgaQPVa2KVy42ncweKz+1AYR83ZWdUkN3lAKNabwOgU2IOY1J2AtDClUiKKxG/BigMlOFXxQ/MLenDUV8KSw73IPej7nRYUob78/WNFmzi8eA//2zyhzej2xW5jMjcQQvPXsamvYcbcIuQ5WqGW1yUBio5GqgEYEFpN/IqswBYXHAGW3e1o1lOElmb/KSv3odvzz4I2L5IU3a6Vxluq6r5AKqaLyJtQu0dgWVVtssLtZkmxp2eDp3bcyy7JfuHuzh7WA73dpzPkKRyUlyJJ3lW2omvIS7auFO/fjy55Z7gnVZbIBtyf1DC5Usm0+1pcK3c0GABLB4PgfP6kXsvfDTiGbonpFXbovpjSAn9sQC4Jb0AKADggVZb4EzgcigNVLKiIpln913CmmW9aL8kQPOtR2BPPv7i4gap3cSGhr68u9TQpjVuKDIJmASQTEoDl2GcIElJuHp2Zce4LH5wwwdclLKAPokukiShylYnC926656QxtYLZ5A7ooSrV/6A7lOL8OXtrddrejp1JPfJDN477zl6JqRRU8ierhRXIhc1C3BRz4XQcyEV471sqgywqLQ3L759JT1mFhLYvgutqGiw9zTRSVRrzMUTNwoONbyvqmeFHm8BLgrt7bYHFqlqbxG5H0BVfxva7iPg/6nqKYca0iVTh8ro+vXERJ4InrZtOHxJd/ZfEODSczbwgzafMjDRg1siP2Hmll2jyP9pT1z//PK0nh+4YBDtH9vOa10XN3BltfNrgDWVPl46dCEfrz6btv9ykfVJLr4DByGM/6Mm+izU2atVdXBNXzvd4P0dUFjl4Fqmqv5URPoBfyU4rtsB+AToVdvBNQve2OLp1JEDV3Sh8poj3J29mIktdpMgbqfLAuC14la8PvEa5PM1dXqejhjA+Fc/4Nb0g41TWB151c8rR7vw/NZRJL7fkrYf7cG3J8/pskwd1Ct4ReRNggfSWgEHgF8C7wKzgC7AbuDbqno4tP3PgNsBHzBVVT+orUAL3ujnSklB+3Rn17daMG3cbL7XPD9qwra67+wYzfEbPfj2Hwhre0+7tjT7m5/ZPRc2cmWnx6t+3jjWnkdn3kjX944im3IJlJY6XZapRb33eBubBW+UEsHdsiX54/sw/NYvuK/NQrp4UhwZRqgLvwY4Y+4P6P3DtbWOl0pSElv+MICcsS/GRL92+0r53cFLWfrnc2g/czP+w0U2FBGlLHhNnbiSkym+diAFN5RyR7/PuTdjc7UDZNGvJFDOgL9Npdf0L08avpKUxLbfDmLtd54kzZUc4Qrrp0K9PF10Ji9vOJ+sd1JoMXcNgfJyp8syVVjwmtq53Lh792DLpCzOH7aRpzp9SIY7tmebFPlLufQ3P6bV/9V8bLdg0nAW/uLxuOjnlLwr+HxZX3r/8TD+zdttnnAUsOA1J+Vu3ZriUT048r0Snh/4BqNia8evVovKXPxt/ngW9zmfQskkSw8zav1n5MxM4Kdv/pXRzeIroBaXw91rvkfLv6aR/tkO/IcOOV1Sk3Wq4G3oebwmBkhCIu6O7ci9uROTx7/HhPT3Yu6jdrg+XHwD8/teTqUE+1corZjf/2qubTk/7kIXYFQy/HvYG5QMKWdGcS+ee/NbdP9LHv69+1FvpdPlmRAL3iZEEhIpHzOAinsO89vsOVyQ7AsdUIrP0AWY3+kSKiWZBz/4gKJkpVQqSdFEMstT4Aqnq2s8aa5kJrfcww9+8Cz/vNXDz7ZdR8IzWSR/vNYCOArYUEO8c7mRc/uSe20anYbvZUb2X+nkabizsaJdh41v8eCHH7G/mRe/BL5ud6uL9mUJ3P6jVx2sLrLyfCVM2DqevGUd6TavBFZttLHgRmRDDU2Qq3lzKoZms+vWAHNGvkD/xK/2aptO6AJk6WGKkvWE0AXwS4DDyc7vdERSJ08an/SdB31h3c3lXP+vu+j6mpvkZVttrYgIs+CNM+5WWey9uTdXT/gXU1s9HVpkJn6HEmpzdd4/KJWa14colab7kbt/YjLbL/kTBRce58nCYbw34wI6/mUL/oJCp0trEqJ7xrgJm7tVFvunjmDoJ/msuO8pHm677oSVvZqqRy7/Gylac/CerL0paeVO5Tdt1rPiJ08x9JN89k8dgbtVltNlxT3b441hruRkfEP7kHNTAlMu+JjJLT8OncYbWyc7NLbMcqGimesbY7yZ5TUtptc0JUkCv2y9kQfuW89zd/bkqX9eRs+ZPhKWbbQTMxqBHVyLRS43x/9rMO2mbOd3Xd6tYb1YU92rT9zO4RNmNUiTOrB2OnK9Jdy3+zryn+5J2tur7EBcHdkJFPHC5SZwfn/23OPjg6EvWOCaiNjtK+GyZXfR+RkPrs/XWQCHyWY1xJgbbjmL4i4JX++dpe/28u7f89k6LZv3vv04fRJTaGqzE4xzunjS2DzyL2waUsq3Zv+I7Ee22UG4erKDa1HmhlvO4lBXodRVCQKlrkoOdRW+fXs7No1/NhS6xkRen8QUNt30HOcsPEjx+GFIUpLTJcUsC94oU9wlocY5p4WJiVG7/q1pOhLEzW/arGfuo4+T/1YPXGed6XRJMcmCN8qcbG5pU55zaqJPG3cqa4e8yW3vfMCeX4wIXtzUhM2CN8rYnFMTS76TdpRV//0k3jktcPfr7XQ5McOCN0q4kpM5dNdw2nnLceuJPxa3uujhs0u9mOiU4kpkQZ/3+N47Czl49whcyU33TMlwWfBGAXdWJptf6MenP3uCm6e8SR9vJSmBRFBICSTSx1vJf937ltNlGnNK32teyKIHHmfLi/1wZ2U6XU5UC+dil52B14B2QAB4SVWfEpFM4C2gG7AT+I6qFoWecz8wEfAD96rqR6d6j6Y8j9d76bl0enAbr3T51A6embjgVT+T9lzErp9nk7BwtdPlOKa+VxluD7RX1S9EpDmwGrgOuBU4XOUS7xmqOk1E+gJv8p9LvC8Esk91ifemErxdfl3Gzna5HPIIrX1K16PZfOf6LVyW4nW6NGMa3MelCfx6+u2kvtM0z3o7VfDWOtSgqvmq+kXo/jFgE9ARGAvMCG02g2AYE2qfqaoVqpoL5BAM4Saty6/LWNcxl4MJLlSEgwku1mduZdGiTk6XZkyjuCzFy1+feJzdPx+KeOxcrarqNMYrIt2AQcByoK2q5kMwnIE2oc06AnuqPC0v1Fb9tSaJyCoRWeXl1Jfgjgc72+VS7jrx213ucrEysNehioxpfF08afzzzt+x64EhFr5VhB28IpIGvA1MVdVTrZpc05JP3xjPUNWXVHWwqg5OIP7PgDnkqXklrJO1GxMvWrlT+ezO37H7fgvfr4QVvCKSQDB031DVd0LNB0Ljv1+NAx8MtecBnas8vROwr2HKjV2tfTWPpZ+s3Zh40sadyuJJFr5fqTV4RUSAV4BNqvpElS/NAyaE7k8A5lZpHyciSSLSHegFrGi4kmOPeDx0Lu1NcuDEU4GTAwHOc31jFMaYuNTKwvdr4ezxng98H7hERNaE/l0FPAKMEZFtwJjQY1R1AzAL2Ah8CEw+1YyGuOdys3vaEB65YRmjtSNtvAFElTbeAKO1Iw9flet0hcZETCt3Kosm/Y7d04eAq+lOn7T1eBuTy03e9KF8etfv7DI8xlRx0H+ci168j86/XR63U83qNZ3MnL7S6wbz/n8/ZqFrTDVt3KnMn/QYx6+vMZfingVvI/Fdci7TH5thV4kw5iS6J6Rx/6Mz8F1yrtOlRJwFbyPwdOpIj99u5uoUu0igMadydUo5PX67GU/npnUikQVvA3MlJ7PxwXb8sfPnTpdiTEz4Y+fP2fRg2ya1qlnTntPRQAa8MpAPugylUDLJ0sN8v3CJ0yUZE1O+vPRZLr71R7R+canTpUSEBW89DXhlILO6jqZSgn+tC6UVr7W6DBYLvx71msPVRbeXfzmYkqMBVI8jkkpaCxd3/GqV02UZB7RwNWPq1L8x89OL8G/JcbqcRmdDDfX0QZehX4fuVyolmTlZwx2qKDa8/MvBHDtShupxlg29nGfOvIWHMm6m54tPMvGDu50uzzjglvQCNv9PltNlRIQFbz0VSs0LPp+s3QSVHA0APpYNvZwVBT3Q8uAiH1oO/9iWbeHbRP3xqpeRQf2cLqPRWfDWU5YerlO7CVI9DsCq4h5Uu6gyEoBFu7IdqMo4bXQzP0cfLseVGt9z3y146+nK3ctJ1BOnjSVqOdcXNo2DBKdLJPgfK3CSGXcnazfxb8HZb1Dw3f5Ol9GoLHjracP/HmT8oYVkBQpAA2QFCrilYIEdWKtFWgsX4MF1khlEJ2s38S/NlcwtP/47nk7xu4CUzWqop9zbujFv1LM8LDNq39h87Y5freLlXw5mcPpOVlR2O2G4QV1wcdetzhVnHHd3y1xeuO1qOj8YnxcKsD3eevB07MC94+fiFvs2no47frWKt8Y/ySW9tiLJwdXyJRku6bWVV6583unyjIPc4mLq+HfxdOzgdCmNwvZ46yH31m7c2eJ97O9X/VQP2ddfz+aD1ckkJZdSUZ5C4aYu3Hxz/faAb3t3HIv3DyNQHhzGGNVuGX+6bma9XtM0ronpeTx5+3V0fjD+rqNgiXGa3K1bc9f4+ba328Befz2bdgO2kdyslM3vtaPFr3yc+/IOllzr4ZVHT+98/tveHcei3cNOmLK2aPcwbnt3XMMWbxqUW1xMvuk93K1bO11Kg7PUOE3Fo3pwW4stTpcRd7L67Mbt9rNpXjtGLjxMVnHwlzSrGM5bsP+0wnfx/mE1TllbvH9YwxRtGs1t6dspvrCH02U0OAve0yAeD0XjS0izQ+8NLim5FICBSw6T5Kv2NR/0+Xx/nV/TpqzFrhRXIkfGl8TdpYIseE/HgN7MPvePTlcRlyrKUwDIOMl1rE/WfjI/mdPHpqzFuHfOfQkGnul0GQ0qnItdJovIChFZKyIbRORXofZMEVkgIttCtxlVnnO/iOSIyBYRubwxO+CEnPHN6ZOY4nQZcalwUxf8fjdF6TV//WTtNfnJnD78M3k72S2Xo9V+09UVPMBmol92QirbxsfXBQXC2eOtAC5R1QHAQOAKERkGTAc+UdVewCehx4hIX2Ac0A+4AnheROLmqnaSlMSIERudLiNu3XzzVvav7cWXI1pRUe3TZYUHNp3fLuzXWpuwnXKXi30ZczizzfITpqxd1MVmNcSSUSM2xNV6vbUOnGjwapgloYcJoX8KjAUuCrXPABYB00LtM1W1AsgVkRxgCBAX59CWXDOQ1zs9AcT3ueRO+mrq2CvSiT6f7yejOLinu+n8dkycllfr819/sD8FhR4OXbHh67Z9GXNIy5gDgKjypysqGqd40yie6PgR37r6h6S+vdzpUhpEWCPWoT3W1cAZwHOqulxE2qpqPoCq5otIm9DmHYGqn+HyQm3VX3MSMAkgmdj52L53jNLKLl4ZEdVDdgQ1h+7Ns6eytKAbgXKYvH8O7rICoILWPuVggnxj+9Y+56+sbeomw53C3jFK9ttOV9Iwwjq4pqp+VR0IdAKGiMhZp9j8m7/pwT3k6q/5kqoOVtXBCSSFVazT3Onp3Dr8X06XYaq4efZUPt/X7es5up7yY0BwOkTfkjNIDpw4jyw5EGCAt2fkCzX1NnHEYtzpdRjkj2J1mtWgqkcIDilcARwQkfYAoduDoc3ygM5VntYJiItTTwLZXbil5QqnyzBVLC2ots5DaLlJgG5L/Qw7egZtvAFElTbeABeU9+T3129yoFJTX7e0XEWgd1eny2gQ4cxqaC0iLUP3mwGXApuBecCE0GYTgLmh+/OAcSKSJCLdgV5AXKTVgWHpdrn2KFN9Lu5Xy01+pdtSP1ct6M6tH/ZjwTWVFroxrIsnjf3DmjtdRoMIZ4y3PTAjNM7rAmap6vsishSYJSITgd3AtwFUdYOIzAI2EvzMN1lV/Y1TfgS53LguL3C6ClONKzl4CvBXfMnNcZdV8NVwQ5CHVlm+6k81MSjh8gJ4zg2B2I6UWvd4VXWdqg5S1f6qepaq/jrUXqiqo1W1V+j2cJXnPKSqPVW1t6p+0JgdiBRP10481jdORvbjyPBWO0+Yo/t8u+vxN2v19Z6vSCqtW7m5+RfrHKrQNKRH+ryNp+vprdkRTeLrPLxG5GvbggGJxdg0sujy+o1PnjCrwZUMawYP4vUbn3S6NNMIBiQW423XEsnd5XQp9WLBG6Z9I9PIcDVzugxTAwvZpiPD1Yz8kal0iPGzAmythjAljCq0JSCNcZhbXCSOiv1jLZYkYXC3bMFNPVY5XYYxBhjXfTXujIzaN4xiFrxhKD0/mztarHe6DGMMcEeL9ZSOOMPpMurFxnjDsG+khwx3dJ7WPG3xOP6edSGFkkmWHuaqws94dJQt/mLiV4Y7hX0XeOg+3+lKTp/t8YYh0LXM6RJqNG3xON5qdTWFrlYgLgpdrXir1dVMW2yXtDHxTbuUgdS0OkFssOCthSQlcVmvzU6XUaO/Z11IpZy4VF6lJPP3rAsdqsiYyLi81yYkMdHpMk6bBW8t3B3aMbHVYqfLqFGhZNap3Zh4cVurf+LuEP7azNHGgrcWx/q3pXdCoPYNHZD1n5MFw2o3Jl70TghwrH9bp8s4bRa8tdg/3B21F7W8qvAzEvXEVWIStZyrCj9zqCJjIiPNlUz+8Ni9sI0Fby0CnaP3UrSPjprJdwvmkxUoAA2QFSjguwXzbVaDaRK0S3Qe9A6HTSc7BVdyMt/pu9rpMk7p0VEzeRQLWtP0jOu7mtXJqQTKo3fn6GRsj7cWaW67Npcx0ai5O/YC9ysWvKfgateGwSk7nC7DGFODwSk7cLWPzQNsFryn4M9qzsCkI06XYYypwdmJxfizYvOKFBa8xhgTYRa8xhgTYWEHr4i4ReRLEXk/9DhTRBaIyLbQbUaVbe8XkRwR2SIilzdG4cYYE6vqssc7Bah6idbpwCeq2gv4JPQYEekLjAP6EbwM/POhC2UaY4whzOAVkU7A1cDLVZrHAjNC92cA11Vpn6mqFaqaC+QAQxqk2ghzlVSwx5fgdBnGmBrs83lwHY/N6Z7h7vE+CfwUqLpoQVtVzQcI3bYJtXcE9lTZLi/UdgIRmSQiq0RklZfo/ObprjwWlvRzugxjTA0+KjkL3bXX6TJOS63BKyLXAAdVNdxTuGpaJFO/0aD6kqoOVtXBCSSF+dKRpf4AK490dboMY0wNVh7pinp9TpdxWsLZ4z0fuFZEdgIzgUtE5HXggIi0BwjdHgxtnwd0rvL8TsC+Bqs4gtRbyRfbLXiNiUZf7OiCeiudLuO01Bq8qnq/qnZS1W4ED5r9Q1VvBuYBE0KbTQDmhu7PA8aJSJKIdAd6ASsavPJIKbblLIyJSsWxe/ylPvN4HwHGiMg2YEzoMaq6AZgFbAQ+BCarqr++hTqly0cBivylTpdhjKniaKCMLh9G5zrZ4ajT7pyqLgIWhe4XAqNPst1DwEP1rC0qpH6xh8/K23BdaonTpRhjQj4ta03qF3uIzRFeO3OtVr79B3hqZ41/X4wxDnlm1yX49h9wuozTZsFbG1X2rOngdBXGmCp2rekA+o3JUjHDjhyFoUUOeNVPgsMn4M19+Er6JYyhmacFZb6jbPAuYOwDHzhakzGR5lU/6Tmxe2l3sD3esLRbkM/icmcvJT334SsZlDyWlISWiAgpCS0ZlDyWuQ9f6WhdxkTa5+UJtP843+ky6sWCNwy+3F08nHu1ozX0SxiDx3Vi+HtcifRLGONQRcY446GdV+Pbuaf2DaOYBW84VDlUkupoCc08LerUbky8OngsDQIxO0MVsDHesNz980oyXX/n0w0BPGUZ+HLacfG4LRGtocx3lJSEljW2G9NUlAYqSZrT0uky6s32eGtx988raTFyK76UIhDwpRQhfbfz6czeEa1jg3cBvsCJp0f6ApVs8C6IaB3GOKlEvWRuOOZ0GfVmwVuLzCF7Uc+JgaeeSjxn7I9oHWMf+IAvy+dS6j2CqlLqPcKX5XNtVoNpUt4r6YkrNyaXfjmBDTXUwtes6BTtkf27FQzZ/wTtGRF9d2Oc94dNo+l4eKPTZdSbBW8tPGUZ+FKK2DSvHQOXHCajGIrSYc2IVlzcL7J7vcY0dYkL0mP6xImv2FBDLQ6v6Mimee0YufAwWcXBb1hWMYxcWMAzD3au9fnGmIZREigndX/sLoxTlQVvLZ7/TSIDlxwmqdpqHEk+OHdlbE/iNiaW/K7wXNIWxv4wA1jwhiWjuG7txpiGl1uaReB4fCzRamO8YShKDw4v1NTuhAnz7+Zfe7IJlIMrGUZ23sqMq593phhjIuSfa88kOxC711SoyvZ4w7Csf0cqqv2JqvDA6vPaR7yWCfPvZvH2bLQ8eHE7LYfF27OZMP/uiNdiTKT4NUD6pvjZT7TgDcNHr2awcFhHCtODl1kuTIfPLmzPPb+I/Pni/9qTjVQ7viCBYLsx8Wp9pZeOHx9yuowGEz9/QhrZR69m8BEZuFtlMfnvn3NPijOLdATKa76Mc6A84qUYEzH7/elQcMTpMhpMWHu8IrJTRNaLyBoRWRVqyxSRBSKyLXSbUWX7+0UkR0S2iMjljVW8EwJHj/FC3sWOvb8ruW7txsSD5/deTOBI/KxLUpehhotVdaCqDg49ng58oqq9gE9CjxGRvgSvRtwPuAJ4XsThFcQbkHor2f6P7o69/8jOW9FqPzV1BduNiVfb/tEjZi/lXpP6jPGOBWaE7s8ArqvSPlNVK1Q1F8gBhtTjfaJO+yUVjl15eMbVzzOq51YkGX5+cC737nyNe7e/wKAFy3jrlyMcqcmYxnQ0UEb7pfETuhB+8CrwsYisFpFJoba2qpoPELptE2rvCFQdAM0LtZ1ARCaJyCoRWeWl4vSqd0izHYWsrUxz7P1nXP08D+fP4ujxg6geB0D1OPuOHLXwNXHny4pUkncUOl1Ggwo3eM9X1XOAK4HJIjLqFNvWdOznGydXq+pLqjpYVQcnkBRmGdHBt3MP92+93tEa8o+Wwzcubu0LtRsTP3627Tr8O3Y7XUaDCit4VXVf6PYgMIfg0MEBEWkPELo9GNo8D6i6iEEnIPbXcasq4Ofg5taOvf1zr1749Z5udSdrNyZW7d/cJuavOFFdrcErIqki0vyr+8BlwL+BecCE0GYTgLmh+/OAcSKSJCLdgV5AfJxuUkWvN0rY6nUm5K6v+JTgj+KbTtZuTCza7i2h1xvxtzMRzh5vW+BfIrKWYIDOV9UPgUeAMSKyDRgTeoyqbgBmARuBD4HJqhpff64A1m7l9k3fd+StO1BAn4RyvjkN20P7FjavzMSPiVtuhjWRvcxWJNR6AoWq7gAG1NBeCIw+yXMeAh6qd3VRTL2VlM5ti//sAG6J7AmA+2jFlT0XwfaL2ORNRfU4Iql0T/Az9ldLIlqLMY3FrwGOzu1Aknen06U0ODtzrR4yN1dQFCijlTuyH+/nJF3MxIq5XNlzEVeG2ko1kVeSxka0DmMaU1GgjMxNsTXjKVyiUbCae7pk6lCpcec5qrmSkyl8pANlKUsQzxHU15LuvoHMu+7DRn/v5169kOsrPqUDhewjizlJFzP59s8a/X2NiZQBK26iw7hcAuWxOVNnoc5eXeWEsxPYHm89HPlJG8qbL2DSss4kljSj1JdAimcLL355Fj/41b8b9b2DIesCWtMBmIyFrokfGyrLaPtYYsyGbm0seOuhpMsKJi3rDEcrKdVE0lufRVFmOqVSye+fH0JXyvn23X91ukxjYs7YJXfRc9l6p8toNLYsZD2I5wiJJWX41E1667PYn5VCqasSBEpdFWwVN397frzTZRoTUzZVltLjWY27ubtVWfDWg/paUupLAKAoMx1/tYVy/RJgFza9y5i6uHbJ3ciyxh2qc5oFbz2k7R5CiscLQKnUvIhHqcTnUVljGkOut4TuzxHXe7tgwVsv6b/Zjaak4hE/KZpY4zYpGlvrUBjjpDePnotn/Q6ny2h0Frz1NOcVD0lJiWQWHcNdbaFct7roSnwelTWmMbz86cX4i+P/8t0WvA3g7b8k8tZzOWQcSiIlkAQKKYEkstVvsxqMCVO8rstQE5tO1oDmvbyGkve78K/+7zhdijEx57bN3yfli03fXEM2DtkebwNSn4+Kt9pSYleeNKZOSgOVlM1sh/qqrzEdnyx4G1jrtzfwSMF5TpdhTEx5tHAQrd/e4HQZEWPB28D8xcW8/c4F+DVQ+8bGGPwaYNY7FzaJg2pfseBtBN1f2MY9++zaZ8aEY8q+4XR/McfpMiLKgrcR+A8d4ss/DCTPV+J0KcZEtXxfCaueHIT/wMHaN44jFryNpMWsVVzwwQ/xxuHFN4xpCF71M+KDH9LyrVVOlxJxFryNRH0++vzvLibsvNTpUoyJSrftGk2f/93VZGYyVBVW8IpISxGZLSKbRWSTiAwXkUwRWSAi20K3GVW2v19EckRki4hc3njlRzf/gYMU/rgz80ttoRxjqvqwNImDP+7a5IYYvhLuHu9TwIeqeibB669tAqYDn6hqL+CT0GNEpC8wDugHXAE8LyLuhi48Zixbx2NTb7HwNSbkw9Ikfjt1ArJ0rdOlOCacy7unA6OAVwBUtVJVjwBjgRmhzWYA14XujwVmqmqFquYCOcCQhi07tiS/v8LC1xj+E7rJ769wuhRHhbPH2wM4BPxJRL4UkZdFJBVoq6r5AKHbNqHtOwJ7qjw/L9TWpCW/v4Knb/4Ot+2+wOlSjHHExN0j+cPN323yoQvhBa8HOAd4QVUHAccJDSuchNTQ9o3Tr0VkkoisEpFVXprImrXL1nFwXAY9Pp5IrtemmpmmIddbQo8Ft7N/XCYsW+d0OVEhnODNA/JUdXno8WyCQXxARNoDhG4PVtm+c5XndwL2VX9RVX1JVQer6uAEms6atb6du8m+Yy133D6FAStuYrfN9TVxarevhEErxzFx4hSyJ67Dt3O30yVFjVqDV1X3A3tEpHeoaTSwEZgHTAi1TQDmhu7PA8aJSJKIdAd6AfbZogr1+fB8spr2N27j1tunMHLdf1EaqPkKFsbEmtJAJaPWX8+tt0+h7Q05JCxc3SSnjJ1KuMtC3gO8ISKJwA7gNoKhPUtEJgK7gW8DqOoGEZlFMJx9wGRVO4ugJurzkbBwNUlLUxn4sylsmPAsCU14AoiJfV71M/D1KfR8cB2B47lNYonH0yGqzn9r0iVTh8pop8twlLtlC/b+uQNfnPcGbrHzWkzs8WuAwavG0/7WA/iLipwux3ELdfZqVR1c09fsf3iU8B85Ssc7DnLOyu/ZymYm5nwduhMPWeiGwYI3ivgLCuk48QCDV4238DUx44TQLSh0upyYYMEbZfyFh2k/8RAj133b6VKMCcuo9Tda6NaRBW8U8hcU0nJaAhN3j7Q9XxO1/Brgzj3nkz4tyUK3jix4o1Rg3Wbyv9WMs5feQpG/1OlyjDlBkb+Us5fewt5rUgis3eR0OTHHgjeK+Q8dosv3tjHyhZ/webnt+ZrosKzcz8gXfkKX723Df+iQ0+XEJAveKKcVFXR6eAnTf3IXzx3pXPsTjGlEzx3pzE/uu5tODy9BK5rIqf6NwII3RqS8s5z5NwzjOztG21UtTtMTh3vwxOEeTpcRk7zqZ1zuJcy/YRipby+v/QnmlCx4Y4h/0zZKvhWg9yd3UuA/7nQ5MaXIX8rc6Zcyb9poGzOvoyJ/KWf+4w6Kr1H8m7Y5XU5csOCNMf6iIrLv3MDFT93HpkoLkHDduGUcKZ9uoNmijfzX5pucLidmbKosZdQzP6HXHRvtxIgGZMEbg7Sigg5PLOfuH0zhzj3n2wI7tXjlaDs8D7QgcPw4gePHSXognVeOtnO6rKhWGqjkzj3nc9ddU+jw++U2ntvALHhjVcBP4ocr2XtNCpdMv5dniro6XVHU8WuAa7ddwazbLoMV679u15XrmTVhDNdsvdLmSdfgmaKuXHz/FPZek0LSByshYMcUGpoFb4zzHzpEi9eXMWfKpWy3xdW/tqLCS++Zd+P7TqDmxbdXrCfwbR+9Z97Nigpv5AuMUrneEub8cAwt/7LUpoo1IludLE6Ix8PR97qydMDbTpfiqAr1cu3m6+FnmbB8PdT2+y0CQ8+Ghw4z78w5JElCZAqNUsPX3kCLbzXNS643NFudrAlQn4+kZzOb9F5vgf84/V+9F/f1xcG93HB2KlRh2Trc1xcz4JV7m/RskVxvCYnPZVnoRoAFbxxJ/ngtP9x5o9NlOMKvAc77aArdf7USf3Fx3Z9fXEy3X6/kvA+nNtlx3x/uvIFmH61xuowmwYI3jqi3kgOvdKckUO50KRH11bKEfabn1mtvTX0++kzf3iTXRC4JlLPv1R6o12bIRIIFb5zJfHsdV21oOvNUvepv0LVg/YWHv16QvimdIXjNxnFkzbYrAEdKrcErIr1FZE2Vf8UiMlVEMkVkgYhsC91mVHnO/SKSIyJbROTyxu2CqSpw/DiJj2SwobLM6VIa3aIyF+c8dU/wUjMFhSCCu3VrGNYfd7/euFJSan0NV0oK7n690eEDgs8VCS5If9t+znnyHhaVxf++yYbKMjy/zSRwvOmOb0danWY1iIgb2AsMBSYDh1X1ERGZDmSo6jQR6Qu8CQwBOgALgexTXfDSZjU0MBG2PTWEHTf+n9OVNJpl5X7u+/HdpMxZDiJ4x5zL/knlPNh/Huck7eOYerhp9UQ6j8856eR/SUpi9xu9mDn4ZZqLjy8qOvCLddfS7qVkEhasBlVKrx/KI4+/wPnJ8RvAPWb/N72mrAjvYKQJW0POahgNbFfVXcBYYEaofQZwXej+WGCmqlaoai6QQzCETaSo0ufxfTxa2MvpShpFhXq57c/3kDJnOa7UVHIeH8qLf3yKjSNe54a0YronpNE/MZmXBv0FV5eOJ30dV+cO/PGc1+ifmEz3hDRuSCtm44jXefGPT5Hz+FBcqamkzFnOHX/+Hyo0Puf6PlrYiz5P5FvoRlhdg3ccwb1ZgLaqmg8Qum0Tau8I7KnynLxQm4kg3649zH1oNHm++Jte9qej3ej+wjZcqalsfroPG7/7DNkJqd/YLlW8kHiKebmJCcFtqslOSGXjd59h89N9cKWm0v35LfzpaLcG7EF0yPeVMPeh0fh27na6lCYn7OAVkUTgWuBvtW1aQ9s3/pyKyCQRWSUiq7zYeeCNofmslVz01n1xd5Do9/OvxV94mO0/78/mK1446UkP272tYf8pzr7KP8RWb5sav5QkCWy+4gW2/6I//sNHePz9axui9KjhVT8XvHUfzWetdLqUJqkue7xXAl+o6oHQ4wMi0h4gdHsw1J4HVF2xuxOwr/qLqepLqjpYVQcnkFT3yk3tAn6yH9rIoOW3OF1Jg5lfmkz2SwfQoWcx+6Y/nPJMs5f2jCJQcvIDRoGS4/xxz6iTfj1JEpg97g/o0LPo9dJ+5h2v/WBdrDhnxffJfmijrcPgkLoE7038Z5gBYB4wIXR/AjC3Svs4EUkSke5AL2BFfQs1p8d/5Chd7q9g2oGBTpdSb3m+Eh549nb823aQPzKV/onJJ912dUUl5U92OOWqWuqtpPIP7VldcfK5q/0Tk8m/IBV/Ti6/ePZWdsfB0M0DB/rTeXol/iNHnS6lyQoreEUkBRgDvFOl+RFgjIhsC33tEQBV3QDMAjYCHwKTTzWjwTQ+/5YcVv/oHJaVx+6P4e2SdK797X20ezp49YPmewI1LodZGqhkav5g7p12L8nv1f73Pvn9Fdw77V6m5g8+6eul7QmeTNHu6aVc9/B9vF2SXs/eOGdZuZ/lPz4P/5Ycp0tp0myRnCbk2LhhPPHwcwxLdjtdSth2+0oY++UdtH40CVmy9ut2V3IyBycMovyyYpITgwfIjh1PpuWHqbR6d2Od9+bcLVtQMLYvR64opXlacA50eWUCSQvSafvnLwmU/+dsQB0xgEPTKpg76GW6eNIaoJeRsazczw9/Npn0N5c5XUqTcKrpZBa8TUyshO9uXwnXrZlIy2ebk/iPNVG3cIt4PFReNICie47HRACvqPAy9f57aD7TQjdSLHjNCbyXnsuw363k4bbRd4poSaCcMeu/R7M/ZERl4Fb3VQCX/fgIC85+gzTXycednfLAgf4su+88EhaudrqUJsWC13yD66wz6f+XzTzado3TpeDXAGsqffx0+40cfbMjrWeui7nTV12pqRR8tz/p4/fyWM/ZDEz04Bbnz3Z74EB/vpzQl8C6zU6X0uRY8JoaeXp0Y8ejzVk2/CVauJpF5D2PBsrI9booDKTwpwMXsHJPF5KXptHx4wICW3dE/R5ubcTjwZXdg72XtaJ8eAnndd7NbW3/SZarlO4JgYh9n0sC5Zy39E56TDuGb8fOiLynOZEFrzkpV0oKRdf3p9vdW/m/rvMbJBiOBsooDfhZX5nB58ezmbn5XCpLEkk4kED7JX7S/r0ffH58+/bH/zxSlxtPh3bgcXO8X1v2ne/B29ZLYlol485czfmpWzk7sYgUl7vBvvd37bqKHS/0JuOddQRK7UrUTrHgNbVypaZSdP3Z9LhrC891mU+GO7yTBfwaYKu3nH+WncFjX15GyqoUMrb6aLb3OO4jJQTyDxCoqLC1AKoTwZWUhKtdG/wZzSnrmEpRtofSwaX8dNDHXNAsh+yE5LCHK4r8pdyz5yq2vdCHzHdib6gmHlnwmrC5UlMpu6gv+SM8ZAw6RLcWh2nm9nJHm89IFS+bK9vxbsEgALYWtqZySRadPjqKKzfPJuQ3EHfLFgS6dyLv8hYkDD9M71bBk0K/1Wot/RL3cVwTePXgKI77E9l5NJPDa1rT4XMfzRZttMCNIha85vRIcNkNcbtxt28HbhdaVo7/YJX1D6Lg9yeuyX+WPnG3aY00SwZ/AP/+g6gvtMCP/Qyi0qmC1xPpYkwMCf2HVp8P3548h4tpoqqEqv/AwVNsaGKJ8/NdjDGmibHgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCLPgNcaYCIuKZSFF5Biwxek6GkkroMDpIhpBvPYL4rdv1q/I6qqqrWv6QrQsC7nlZOtWxjoRWRWPfYvXfkH89s36FT1sqMEYYyLMgtcYYyIsWoL3JacLaETx2rd47RfEb9+sX1EiKg6uGWNMUxIte7zGGNNkWPAaY0yEOR68InKFiGwRkRwRme50PXUhIp1F5FMR2SQiG0RkSqg9U0QWiMi20G1GlefcH+rrFhG53LnqaycibhH5UkTeDz2Ol361FJHZIrI59LMbHg99E5Efhn4P/y0ib4pIcqz2S0ReFZGDIvLvKm117ouInCsi60Nfe1pEJNJ9qZGqOvYPcAPbgR5AIrAW6OtkTXWsvz1wTuh+c2Ar0Bd4DJgeap8OPBq63zfUxySge6jvbqf7cYr+/Qj4K/B+6HG89GsGcEfofiLQMtb7BnQEcoFmocezgFtjtV/AKOAc4N9V2urcF2AFMBwQ4APgSqf7pqqO7/EOAXJUdYeqVgIzgbEO1xQ2Vc1X1S9C948Bmwj+BxhL8D83odvrQvfHAjNVtUJVc4Ecgt+DqCMinYCrgZerNMdDv9IJ/qd+BUBVK1X1CHHQN4InRDUTEQ+QAuwjRvulqouBw9Wa69QXEWkPpKvqUg2m8GtVnuMop4O3I7CnyuO8UFvMEZFuwCBgOdBWVfMhGM5Am9BmsdTfJ4GfAoEqbfHQrx7AIeBPoWGUl0UklRjvm6ruBX4P7AbygaOq+jEx3q9q6tqXjqH71dsd53Tw1jTeEnPz20QkDXgbmKqqxafatIa2qOuviFwDHFTV1eE+pYa2qOtXiIfgR9gXVHUQcJzgx9aTiYm+hcY7xxL8qN0BSBWRm0/1lBraoq5fYTpZX6K2j04Hbx7QucrjTgQ/HsUMEUkgGLpvqOo7oeYDoY85hG4Phtpjpb/nA9eKyE6Cwz+XiMjrxH6/IFhrnqouDz2eTTCIY71vlwK5qnpIVb3AO8AIYr9fVdW1L3mh+9XbHed08K4EeolIdxFJBMYB8xyuKWyhI6SvAJtU9YkqX5oHTAjdnwDMrdI+TkSSRKQ70Ivg4H9UUdX7VbWTqnYj+DP5h6reTIz3C0BV9wN7RKR3qGk0sJHY79tuYJiIpIR+L0cTPOYQ6/2qqk59CQ1HHBORYaHvyS1VnuMsp4/uAVcRnA2wHfiZ0/XUsfaRBD+6rAPWhP5dBWQBnwDbQreZVZ7zs1BftxAlR1hr6eNF/GdWQ1z0CxgIrAr93N4FMuKhb8CvgM3Av4G/EDzKH5P9At4kOFbtJbjnOvF0+gIMDn0/tgPPEjpb1+l/dsqwMcZEmNNDDcYY0+RY8BpjTIRZ8BpjTIRZ8BpjTIRZ8BpjTIRZ8BpjTIRZ8BpjTIT9f4U8PGd8sTsxAAAAAElFTkSuQmCC\n", 119 | "text/plain": [ 120 | "
" 121 | ] 122 | }, 123 | "metadata": { 124 | "needs_background": "light" 125 | }, 126 | "output_type": "display_data" 127 | }, 128 | { 129 | "data": { 130 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAV4AAAD8CAYAAAA/iMxLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAuJElEQVR4nO3deXxU1fnH8c8zM8mEBMIStrDJFlAQBEUUFEWRqoBitSjWutWW2qrV2qpo/dWfXX5FrVaruKDU4gaCG1QRVMQFFzZFZd+XQCQkrElIMpl5fn/kqgECScjM3Fme9+vFa2bO3Jl5DpBv7px77j2iqhhjjIkej9sFGGNMsrHgNcaYKLPgNcaYKLPgNcaYKLPgNcaYKLPgNcaYKItY8IrIeSKySkTWisjYSH2OMcbEG4nEPF4R8QKrgaFALrAQuFxVl4f9w4wxJs5Eao+3P7BWVderajkwBRgZoc8yxpi44ovQ+7YFtlR5nAucUnUDERkDjAHw4j0pncwIlWKMMdG3j10FqtqiuuciFbxSTdsBYxqqOgGYAJApzfQUGRKhUowxJvre01c2He65SA015ALtqzxuB2yL0GcZY0xciVTwLgRyRKSTiKQCo4EZEfosY4yJKxEZalDVChG5EZgNeIF/q+qySHyWMcbEm0iN8aKqM4GZkXp/Y4yJV3bmmjHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRJkFrzHGRFnErk5mTLice09jRutc2mgB26Q5U+QsZt+7x+2yjDlqtsdrYtq59zTmN6HptKMAj0A7CvhNaDrn3tPY7dKMOWq2x2vcI4K3SRPwVLdEX6XR+h7pUn5AW7qUM1rnMpsTI1zgD7xNGoPXW7cXhZTg7t2gWuOmJrlY8JqIEr8fT2Ym5T3bs6NvGipQ3CFEky47SUup4FcdPyLDU37Y17d5vqDapVPbaCGb/jyA7E8CpH26itC+fWGt29OoEaUDu5M3MJVGJxVwTefPaO2r2/BGcSiVpzaeQWnAx+51zcjY7EEUWiwpJXXpFkJ796JlZWGt28QH0Rj4bWyrDMc/8fnwdGzP7pNaUZ4pFPavQFKDjOj5DSc23MSIjA0092bU+X23PRmiHQWHtOfSnDbXeygIFvOL9Rez7952+D5YAqFg/Tri8VIxuA+N7snlmc6vHVXNNSkIFvNmcSeWFHdgxtLeaLmXrAU+UvcqTRZvJ7RxC1pREfbPNdH1nr6yWFX7VfdcjcErIv8GRgD5qnq809YMeBnoCGwELlXVXc5zdwLXAUHgt6o6u6YCLXjjj/j9eDq0pbh7c749xUtm30Ke7PkCvVO9pEgdv5IfwdPTTufKgjcPGG4o0VSebz6CX46a933bhkARQ/77e469Zw3Bwp1H9VnerGasvDeHORc8SKeUhvWuva4CGuTr8iC/Xn4Fe75oTuv5QTJWFRDavNX2jONQfYP3DKAIeK5K8N4P7FTVcSIyFmiqqneISA9gMtAfaAO8B3RT1SPuhljwxj5PWhoc25ltg5ugg3fRu2Uev279Pn1TK0j3pEb0s5+edjrDCz+ijRayTbJ4K+uMA0L3O0ENMWDJZWTd4SW0dGWdPsPbszs77lM+7zsFr8TGMeeSUDlflvt4avtglmxvi3zQlDYf7IaV6wmVlrpdnqlBvYIXQEQ6Am9WCd5VwGBVzRORbOADVe3u7O2iqn93tpsN/K+qfnak97fgjT2ejAwCJ3dnXwc/O/qHGHLyUsa2nk1HX3rMBNPh/GzjYHZe4Kn1nq83qxlNZigvdZob4crqJ6ghNlaUMO7bc5mz8HhaLPDQaHMZKQtXESoudrs8c5AjBe/RHlxrpap5AE74tnTa2wKfV9ku12kzccCTkUGwTw55p6XT98dLua/tv8j2Vf3KHf2v30dj0jHvc8LTV9Lhl9QYvt6mTdk0IZv/dnyeWJ9d6RUPXVIa8nT7T6D9J3Ax5FUUccfWYXz5+kCyPynBu2SNhXAcCPeshurmBVW7Sy0iY4AxAGmkh7kMU1vi91N6dm9yh3gZedYCftfiMbK93+3VxkfQHswrHr465Xm6PjCG7mP2HvZAlfh8rPhHF9aeOiHm9+IPJ9vXkOeO+YjgzR+Qd2MJ/9xxBtPnnkq794OkzfnaxoZj1NEG73YRya4y1JDvtOcC7ats1w7YVt0bqOoEYAJUDjUcZR3mKHibZxHo2YGNw9IYevaX3NLyYbqlfHf0Pj7D9mBe8TB/6CMMHnsb7f9v/qGzHTxeNt/Rn/k/egCvhH/mQrR5xUM7X0MezP6CB3/6BatHFfNw/hDefX8AHWeWkrJsM8GCQrfLNI6jHeN9ACiscnCtmareLiI9gZf44eDaHCDHDq65T3w+ggN6sWVoGiMv+IxbsuYdNIyQmNYFirjij3/gpu5fkdV3Can+YsrLMij45kQG/HQ2XVyYvRBteRVFPFx4Om+8OYAO75Ti/XwZGjj83GkTHvWd1TAZGAw0B7YD9wBvAFOBDsBmYJSq7nS2/yPwc6ACuEVV366pQAveCBHB26wpxQO7sv/6XbzW61naJUHYHmza5FNoevwCvN4ffv8Hg172LuvPxaPnu1hZ9OVWFPGTpdfgf6IZGZ+uJbhzl51ZFyH1ntUQaRa84ec54ThW/b4BY0+exaiGa2nqTd5x9FlfNMCfdugBp7LSDM47cb8LFblvV7CEaUVdGbfwPLo/uJ/Q1ystgMMsErMaTCwSQfodz+obUnn4tMlcmFHiPJG8oQuQ6q/+KH9le3weVKuvpt50xjTexphz/s1bA9O4ad4V5DxeDguXWgBHgQVvInACd81PM3j1okfo4/e7XVFMKS/LqHaPt7wsA0jOPd6qhqeXMvxHE1lyZhmXvHEzOS8Vo4ssgCPJgjeOeZs0pmRANzZfFmLamU9wkj8VsNA9WMnq3vh6HjrGW7K6N5yYXGO8R9LH72fdZU+y5KIyLv7wN3R42UP6Z6sJ7rZrH4ebjfHGqYohJ9HvH4sZ2/zTpB6/ra3XppxCerevv5/VULK6d9IdWKurXcESxhUMZOFtJ5Hy3mK3y4k7dnAtUYggJ/Vk1ZgGPHb28wxPt/P1TeTNKvHz6/evovuE/ejiZTYEUUt2cC0B+Nq3Y/VN7Zky6hFnSMGY6DgvvYwNI55m8dByRk+7mW6PbqFiS67bZcW15DykG0fE52PntQMY9e4CVl4x3kLXuOYkfyorrxjPqHcXsPPaAYjP9tuOlgVvDNOBJ7D91a68fu8DXJOZH7fXEzCJwysersnM5/V7HyD/tS7owBPcLiku2U9yDPK2asm22wfyi/+8wZcnT0nKs81MbGvna8gX/V7mV/95nW23D8TbqmXNLzLfs+CNMTrgBLKnl/DFzY9yaUObxmNi2yUN9/LFzY+SPb3E9n7rwII3RnhbtGDDuAHc9cLzTOwwL6zL5xgTSSniZWKHedz9wiTWjxuAt0ULt0uKeRa8MUAHnEDHmftYceV4BjcIuV2OMUfljDRYeeV4Os/ciw6wvd8jseB1kfj95N41kLtfnMTjbT+3g2cm7nnFw2Nt53P3i5PIvWsgYqevV8t+0l3ibdWSNff1Zf5vHuKMNLerMSa8zkiD+b95iNX397UDb9Ww4HVBaFBfTn5nKytHjaehx1LXJKaGnjRW/WQ8p7yTS+jMvm6XE1MseKNIUlLJv2Egtzw7mXtbLLMDaCbhpYiXe1os5/cTXyL/hoFIip0ABBa8UePJyGDV+BOYPfYBu8aCSTrnpZcxe+wDrHr8BDwZ8b/GXX1Z8EaBt3kWKx85jpXDH6el1/7TmeTU0pvBymGPs/KR45J+ypkFb4T5Oh1D5nRl9flP4ZcUt8sxxlV+SWH1+U+R+UYQX+eObpfjmhqDV0Tai8hcEVkhIstE5GanvZmIvCsia5zbplVec6eIrBWRVSJybiQ7EMtCZ/al9eRCpnR638ZzjXGkiJcpnd6n9UsFSXvQrTZ7vBXA71X1OOBU4AYR6QGMBeaoag6Vy7iPBXCeGw30BM4DHhdJvtTx9uxOn38uYWKHeW6XYkxMmthhHn3+uQRvz+5ulxJ1NQavquap6hfO/X3ACqAtMBKY5Gw2CbjIuT8SmKKqZaq6AVgL9A9z3THN270rF06bxwOtv3S7FGNi2gOtv+TCafPwdu/qdilRVacxXhHpCPQF5gOtVDUPKsMZ+G6WdFtgS5WX5TptB7/XGBFZJCKLApQdRemxSU/rw4kvr+L6JlvdLsWYuHB9k62cPHUFodP7uF1K1NQ6eEWkIfAqcIuq7j3SptW0HbJWiKpOUNV+qtovJUEWaJS+PWn74Dr+2vIbt0sxJq7c22IZ7f6xDjmpp9ulREWtgldEUqgM3RdV9TWnebuIZDvPZwP5Tnsu0L7Ky9sB28JTbuzydTqG7Cc282yHj90uxZi49GyHj2k7fhO+Tse4XUrE1WZWgwATgRWq+lCVp2YAVzv3rwamV2kfLSJ+EekE5AALwldy7BG/n3X3ZVroGlNPEzvMY/39mQl/cZ3aLJp0GnAl8I2ILHHa7gLGAVNF5DpgMzAKQFWXichUYDmVMyJuUNVguAuPGSJsv+4kPhvwD8CWWa/JY/8zgIp9ZagWI5KBr5GfG//ymdtlmRjy6alPcfYv/kDLxz9L2BWNbXn3etp32alMvO8hjku10K3JY/8zgMDevVT+Pv6Oj5TMTAtfc4DVgWKuueP3NJryudulHLUjLe9uZ67Vg69dW0b8ca6Fbi1V7CvjwNAFqHDajflBt5QMRtw1F1/7dm6XEhG2PvNREr+fjY80YUbWCuz316Fe+9dlrPelUyLlpGsqnStKUF1S7baqxdEtzsSFO7JWcMIj/Wh3+Q60LLF+OVtiHKX8a0/k01OesVUjqvHavy5jRUoqJZ5yECjxlLMiJRWR6i8QdLh2k9y84uGT/s+Q//MT3S4l7Cw1joK3Z3d+/7upNPY0cLuUmLTel05QDlw7LighSG/AoV+yfPgaJfYRbHP0GnsacPstUxLutGIL3joSv5/Nf/FxRaNCt0uJWSVSXm373vYdScnM/H4PVyTDDqyZGo1utIstf/Um1BQzG+Oto+Jhffjg5IcA+3p8OOmaWm34pmuqhaw5KnP7Pc2Ph99K+mvz3S4lLGyPtw7E7yf9xq00t4uZH1HnihK8euB/La966FxR4lJFJt4192aQcWMunrTEWKPQgrcOiof34cVuL7tdRlTcPiOHoW+m0meWn6FvpnL7jJxav/bi377McYFy0kOpoJAeSuW4QDkX/9adv7snXxrEtidD8EQ+254M8eRLg1ypw9TPCzlTKRp2gttlhIWdQFFL4vcTnNmSd4/7r9ulRNztM3L4MGUTpZ4ffi+nhUKcGTiG+y9c42JldffkS4O4Zs9/Sa8y9FGiqfyn8QVc/1M7xTvenLtiBJ7hBYRKY3/dQjuBIgyKh/XhxW5T3C4jKr70HBi6AKUeD196NrlU0dG7cO+HB4QuQLqUc+HeD12qyNTHi91eToi9XgveWvA0akTGTblJs1DlDl91V/Y8fHssa6MFh2m3WSnxqLk3g0Y3bcGbmel2KfViwVsL2392PNO7v+F2GVHToqL64afDtceybdL8MO1ZUa7EhMvr3aaT97Pj3S6jXmw6WQ3E7yfrJ7lJtUJw39AxfBg6dIy3b+gYILbHeG966Kd02L2XpoGBBFKbUdFpGr/c/+ohY7wzGp/J9dgYbzzySwotL9mMTPTH7anEtsdbg+JhfZicJGO737n/wjWcGTiGloEQokrLQHwcWKsM3X1k6o8I+LNABN/GS3m6wSXk0pyQCrk0twNrCeDFbi9TPLyP22UcNdvjPQLx+ZDr85Ny3u6hIRvboQvwgac3NwdCBA46w8m38VJmlg3hF3+/izZge7oJoLk3A+/125EZPrTi4CvexT7b4z0CT7fO/DNJ5u0mgmL8BFKbVfvc4dpN/HowZyqebp3dLuOoWPAewfrLmtMn1b4UxIsMykgp31ntc4drN/GrT6qP9aPj8yCpBe9heDMzGXPJLLvsYxwZHPqaXSmf4QkeeMDFEyzD22SOS1WZSPGKh+sveTsup5bVZrHLNBFZICJficgyEbnXaW8mIu+KyBrntmmV19wpImtFZJWInBvJDkRKRa/O/CTza7fLMHXw6K0vsblJI/bKO6SUFYIqKWWFpDaczrV3WPAmoosbLSXQO/6GG2rzPboMOFtVi5xl3ueJyNvAxcAcVR0nImOBscAdItIDGA30BNoA74lIt3hb8HLLOel08DV0uwxTR4/e+pJz701X6zDR0cHXkNxz0ukwz+1K6qbGPV6tVOQ8THH+KDASmOS0TwIucu6PBKaoapmqbgDWAv3DWXTEidBiYJ7bVRhjaqHVwG0g8XVWZa0GMEXE6yztng+8q6rzgVaqmgfg3LZ0Nm8LbKny8lyn7eD3HCMii0RkUYDYmgTt7dKRP3Se7XYZxphauK3zLLxdOrpdRp3U6pC9M0zQR0SaAK+LyJHO16vuV88h55qq6gRgAlRenaw2dUTLjjNaMzy9iFg99vjWX0fTLe1U0r2NKAnuY3Xp5wy/O7lO8jDmO+c1KOGeM1uTtXaD26XUWp2SRVV3Ax8A5wHbRSQbwLnNdzbLBdpXeVk7YFt9C42mgtMDMTub4a2/jqZX+llk+DIRETJ8mfRKP4u3/jra7dKMcYVXPBSeXv1yU7GqNrMaWjh7uohIA+AcYCUwA7ja2exqYLpzfwYwWkT8ItIJyAEWhLnuiPHmdOaJM553u4zD6pZ2Kj7PgdeN8HlS6JZ2qksVGeO+pwY9hzcnfmY31Ga3LhuYKyJfAwupHON9ExgHDBWRNcBQ5zGqugyYCiwHZgE3xNOMhp39W3JG2j63yzisdG+jOrUbkwxOTytm5ykta94wRtQ4xquqXwN9q2kvBKpdNkJV/wb8rd7VRZn4fJRdupt0T6rbpRxWSXAfGb5DJ4yXBGP3l4UxkZbuSaX80l3IlPi4dkNsDmS6xJPTiad6x+4wA8Dq0s+pCAUOaKsIBVhd+rlLFRkTG57u9XzcXLvBgreKzRe24KRUr9tlHNHwu6fwTclciiv2oqoUV+zlm5K5NqvBJL0+qT42X1j9he9jjV0BxuFJT+e0S76M2dkMVVWG7A9BW/v1f41JXF7xMPiSxaz7VzqhkhK3yzmi2E+ZKNlzQW/ub/O+22UYY+rhb9kfsOfC3m6XUSMLXse3w8pp7GngdhnGmHpo7GnAt8Nif06vBS/g69yRBwa84nYZxpgweGjAVLxdO7ldxhFZ8ALbhrXhoozdbpdhjAmDC9L3su38bLfLOCILXhECZ++Ji4NqxpiaecVD8OzdMX3FsqRPG29WM+7sOcvtMg7w548vp9eyJ2iz/GV6LXuCP398udslGRNX/tjjbbxZsbvOXtIHb+H53RiZsdXtMr73548v599Zwyj0NAfxUOhpzr+zhln4GlMHIzLyKBzWze0yDivpgzd/UAUNPWlul/G9ac3OoFwOrKdc0pjW7AyXKjIm/jT0pJE/KHZPHU7u4PV4OaXnOrerOEChVP/16HDtxpjqDeixFvHF5jliSR28vuxW/LL1h26XcYAsrX4Z8sO1G2OqN6b1h3hbt3K7jGoldfDi89LIU+p2FQcYtfMjUvXAmlK1lFE7P3KpImPiUyNPKXhjM+Jis6ooKeqVTY+U2LpU8J8GTebnhTPJChWAhsgKFfDzwpn8adBkt0szJq50TwlR1Ds25/PG5gBIlAQyPDSQ2Lv27p8GTeZPWNAaUx8NJJVAhofYOXT+g6Te461oELsTrI0x9ReI0Z/xpA7eHf2DdsaaMQnKKx4K+sfWUOJ3ap06IuIVkS9F5E3ncTMReVdE1ji3Tatse6eIrBWRVSJybiQKDwtvTK0qb4wJtxj9Ga/L7t7NwIoqj8cCc1Q1B5jjPEZEegCjgZ5ULgP/uIjE9rIOxhgTRbUKXhFpBwwHnqnSPBKY5NyfBFxUpX2Kqpap6gZgLdA/LNUaY0wCqO0e78PA7UCoSlsrVc0DcG6/W1u5LbClyna5TtsBRGSMiCwSkUUByupatzHGxK0ag1dERgD5qrq4lu9Z3WHEQwZaVHWCqvZT1X4p+Gv51sYYE/9qM4/3NOBCERkGpAGZIvICsF1EslU1T0SygXxn+1ygfZXXtwO2hbNoY4yJZzXu8arqnaraTlU7UnnQ7H1V/RkwA7ja2exqYLpzfwYwWkT8ItKJykVwF4S9cmOMiVP1OXNtHDBVRK4DNgOjAFR1mYhMBZYDFcANqhqbk+mMMcYFdQpeVf0A+MC5XwgMOcx2fwP+Vs/ajDEmISX1aVvevUl9qQpjEp53b2yeQpDUwdtysRLUUM0bGmPiTlBDtKjtXKwoS+rgPXSSmzEmkUiM/owndfA2XrGb1YHYuhC6MSY81lXsp/GKPW6XUa2kDl7Pzn3sCdnJG8Ykop3BNDw797ldRrWSOniDOwqYuMNW7zUmET1bcDrB/B1ul1GtpA5eLStj7roct8swxkTA+2u7o2WxeR2YpA5egOZvpVGmAbfLMMaEUZkGyJoZi4v+VEr64G22qICvyt2uwhgTTl+VQ7OFBW6XcVhJH7y6KZen8we7XYYxJowm7jgD3ZTrdhmHlfTBGyot5cM5vd0uwxgTRnPn9CFUGrtTRZM+eAHazKsgr6LI7TKMMWGQHyymzbwKt8s4IgteIG3O11y15nK3yzDGhMGVqy8j7b2v3S7jiCx4qZxWtmVe+5o3NMbEvE2ftI/ZaWTfseB1dH4pnwVlNq3MmHi2uKyczpNjdzbDd+y6iI6hJ+zi6/99gMWpzUgp30lak7f52R0fu12WMaYOrlz8c9qvWuF2GTWyPV7gvEuzSG80koA/C0QI+LMoLhrFC/cNcrs0Y0wt7QqW0GRaQwjF/oI3FrxAowZDCHkPvFhOyOundPf5LlVkjKmr8btOpMnby90uo1ZqFbwislFEvhGRJSKyyGlrJiLvisga57Zple3vFJG1IrJKRM6NVPHhEkhtVqd2Y0zsefHVswnu3et2GbVSlz3es1S1j6r2cx6PBeaoag4wx3mMiPSgcjXinsB5wOMiEpvrbzhSynfWqd0YE1vyKopo/26x22XUWn0Oro0EBjv3J1G5COYdTvsUVS0DNojIWqA/8Fk9Piui9u2fQ7pvJCGvn2ZdXqdxzw9J9RdTXpbBe7N7c865sT0n0Jhkd9Way/F9tZZ4Wcirtnu8CrwjIotFZIzT1kpV8wCc25ZOe1tgS5XX5jptBxCRMSKySEQWBXB3zt2sqYWU7JtOVqdXyOozG39aMSLgTyuGNt/w3mw7pdiYWFUSKmf3i+0IFcfPHm9tg/c0VT0ROB+4QUSOdPVwqabtkJWPVHWCqvZT1X4puL8KxKyphWT2mofXe+ARUa83SEWLNS5VZYypyf8VnESLV5a5XUad1Cp4VXWbc5sPvE7l0MF2EckGcG7znc1zgaqngbUDtoWr4EhK9Vf/G/Nw7cYYdwU1xCszTo+bg2rfqTF4RSRDRBp9dx/4EbAUmAFc7Wx2NTDduT8DGC0ifhHpBOQAC8JdeCSUl2XUqd0Y464l5RV0enW322XUWW32eFsB80TkKyoD9C1VnQWMA4aKyBpgqPMYVV0GTAWWA7OAG1Q19mc0A1s+7kUweOAEjGDQi2+HLQ9kTCwa9eGvCX290u0y6qzGWQ2quh44oZr2QmDIYV7zN+Bv9a4uyl65r5yfcDLtB33z/ayGQH4XLjgvfLMaJow7B8+eswk4pyaHGr/PmLHvhe39jUkWS8rK6Da+HNVDDiHFPImFojOlmZ4i1Wa4q8Tvp/1HPp5u/0lY3m/CuHPQ4gsPOEvOEyxDMmZY+BpTR7/KHcCmQaGYvRLZe/rK4irnPRzAThk+Ai0rC+vqFJ49Z1d7arJnz9lh+wxjksXcOX1iNnRrYsFbg67Pbue5vc3D8l52arIx4fHiviy6/nu722UcNQveGgTXrOfvUy4Ny3vZqcnGhMdfplxGcM16t8s4aha8tdD5P7k8sLNLvd8n1Ph9PMEDvxp5gmWEGr9f7/c2Jlk8sLMLXf6z1e0y6sWCtxYqNm7mpSfOZU9of73eZ8zY95CMGaSUFYIqKWWFdmDNmDrYE9rPS0+eS8WGTW6XUi82q6GWPGlpVLzVgneP+6/bpRiTtM5dMQLP8IKYXrr9OzarIQxCpaWUjm9DQdBOHzbGDQXBYkrGt42L0K2JBW8dNJz5FXdsjfnruhuTkO7c9iMazvzK7TLCwoK3DkKlpSya3JsytdWIjYmmMg2wYPIJCbG3Cxa8ddb2xVWMKzjkDGpjTASNKziBti+scruMsLHgraNgQSEvTxvsdhnGJJWXXxlMsKDQ7TLCxoL3KHScup23StLcLsOYpDCrxE/Hqfk1bxhHLHiPQnD1Ou6571rWBYrcLsWYhLYhUMQf7/85wVVr3S4lrCx4j1LWxAUMffP3BDVeltczJr4ENcTZM2+l+TNxsY5CnVjwHq1QkOPu38Z/9rZxuxJjEtJ/9rahx33fQigu1lGoEwveeqjYtIXxj/yYklC526UYk1BKQuWM/9ePqdi42e1SIsKCt55avbiUYcvDc/UyY0yl4StG0eqFpW6XETG1Cl4RaSIir4jIShFZISIDRKSZiLwrImuc26ZVtr9TRNaKyCoRSehTvUL79pF2V0Pm7PfWvLExpkYf7PfgvyuT0L59bpcSMbXd430EmKWqx1K5/toKYCwwR1VzgDnOY0SkBzAa6AmcBzwuIgmdSrpoKTdP+FW9r15mTLLbE9rPjU9fjy78xu1SIqo2y7tnAmcAEwFUtVxVdwMjgUnOZpOAi5z7I4EpqlqmqhuAtUD/8JYde9o9tIj+n/zKZjkYc5SCGuKUT8fQ7sFFbpcScbXZ4+0M7ACeFZEvReQZEckAWqlqHoBz29LZvi2wpcrrc522hKaBcrreWsA9O+x0YmOOxp8LetHld4VoIPEPVtcmeH3AicATqtoXKMYZVjgMqabtkIv+isgYEVkkIosCxOeCdQer2LqNBb85kYl7WrtdijFxZeKe1nz2m5Op2LrN7VKiojbBmwvkqup85/ErVAbxdhHJBnBu86ts377K69sBh/xtquoEVe2nqv1S8B/8dNyST79iwt9/zOqAXbfXmNpYHShmwt9/jHyyxO1SoqbG4FXVb4EtItLdaRoCLAdmAFc7bVcD0537M4DRIuIXkU5ADpB4p54cQZPnP2fUQ7dRFEqMS9gZEylFoVJG/fM2mjz/udulRJWvltvdBLwoIqnAeuBaKkN7qohcB2wGRgGo6jIRmUplOFcAN6hq4p16ciSqtHnqC3r1uImVIx7HLyluV2RMzCnTAL1m3sSxE5YQioElyKLJ1lyLIE9GBisf7sHKYRa+xlQV0CDdZl7PsbcsJ1ScmMNytuaaS0LFxRx3+1quWH++26UYE1Ou2PAjjrt9bcKGbk0seCMsuGsX+69uyLWbB7ldijEx4drNgyi6JpPgrl1ul+IaC94oqFi/kdzbutrF003Sm1XiJ/eOrgTXbnC7FFdZ8EaJ5+MveeC3V1r4mqQ1q8TPuJuvwvPhl26X4joL3ijyz1zIAzdfyTsldqDNJJc5+72Mu/kq/G8tdLuUmGDBG2X+txbyhyd+SV6FLRtkkkNeRRG3PPErC90qLHhdkP3P+Vw89g/MKkmcM/aMqc47JSn8+M4/0PbhpDqHqkYWvG4IBcl86XPG3XQVt33b1+1qjImI277ty//ddA2NX/wcrahwu5yYYsHrIv/bC/nmuh58XppcJ/aZxPd5aZBvruuB/20bXqiOBa/L9KuV3LDscrfLMCasblp+OfrVSrfLiFkWvG4LBWn8aCaLyxL/GqQmOSwpK6Pho5kJuTpwuFjwxoCUdxbxu1tu5K8Fx7pdijH18teCY/nt724idXbiryJRHxa8MaLB9AV8ekkPcj64hoJgcp6/buJXQbCYnA+u4dNLetDgDZvBUBML3hgSXLOeLlct5eyHb7NVi03cmLPfy9kP30aXq5YSXLPe7XLigl0WMkZVDDmJtn9dw8QOc0lJ7EWaTZwKaJDrNp/F1rtz8M1Z7HY5MccuCxmHfHMWUzDCR8/nbuSN4oZul2PMAd4obkjP526kYITPQvcoWPDGsGDhTjrd+RlPn3sOOc//mleLMinTgNtlmSRVpgFeLcok5/lfM+H8oXS68zOChTvdLisu2VBDHPEd055tF7TnmFHruLX9bFp7i0lB6ZRie8Qm/DYEigggfBvM4OHcoayflkPbGVuo2LTF7dLiwpGGGmoMXmeRy5erNHUG/gQ857R3BDYCl6rqLuc1dwLXAUHgt6o6+0ifYcFbN5KSirddNsFmDdnXuSF/v+8pzrCrTZow+qQ0xO1jf0Ojdfvw7iwimJuHBmyueV3UK3gP2FjEC2wFTgFuAHaq6jgRGQs0VdU7RKQHMBnoD7QB3gO6HWnBSwve+sm9ayBLbnjUDsKZsAhqiF6P30j7v33qdilxLZwH14YA61R1EzASmOS0TwIucu6PBKaoapmqbgDWUhnCJkKOeWol120+y+0yTIL4xZYz6fjUKrfLSGh1Dd7RVO7NArRS1TwA57al094WqDoIlOu0mQgJFu5k9fge7Antd7sUE+f2hPazfPzxBAsK3S4lodU6eEUkFbgQmFbTptW0HTKeISJjRGSRiCwKUFbbMsxhNH39Gy5dNcrtMkycu2z1T2j22tdul5Hw6rLHez7whapudx5vF5FsAOc232nPBdpXeV07YNvBb6aqE1S1n6r2S8EuCF5foeJi9O4sXi3KdLsUE6feKG5I6O7mSbvkejTVJXgv54dhBoAZwNXO/auB6VXaR4uIX0Q6ATmAnbwdBfLZV/zfg1eQa8sKmTrKrSjizw9eiXz6ldulJAVfbTYSkXRgKPCrKs3jgKkich2wGRgFoKrLRGQqsByoAG440owGE17Nn17A+Zm38/ZN99POF7/ze/ODxTxYcBrTvj4RDQkpDQI81m8yZzUodW32RkCDzN2fxo2LLiewPwXxKKN6f8Hvm39CS2+GKzWFQ25FEec/djttJsx3u5SkYSdQJCKPl21/OIW3b4yf8A1qiJWBMp7bNYBX5p5KxzcDpC5cTWjfvsoNRPB1Ooa8H2UTOHcP13f/mPYphQxKKyDdk4Jfwrtyc5kGKAkF+Li0OVsCWTy5ahAp7zQme3YeFRs2gfNz42nUiPKTu7FxRAo/Oetzrmr6Gcem+PFKfJwU+n3o/mO+XT83zMI2jzdSLHgjwOOl8Lr+3H3781yUEbtDD/nBYv5VeCrT/ns6nV/ZDWs312qM0dukMfj9lPVsTyDTy/aTvagXAi0DnNJ9PS39+7g66xO8VY7rPrL9HIIq3Nr63e/bggiTCk8jv6wR81d1JiU/BQlCq4VBUvYG8S/bAmVlBHfvqbEmT0YGdO3AhkuacPEFn3BL889iek/4jeKG/OWBK2n+zAIL3Qiw4E1iFUNOouvfl/NY23kxdYJFfrCY4V9dS8b4xjSYt/KHPdtwEMHj9+Np3fKAZnXCU5o0PqA99G0+obKy7/diw8HTqBGlpx3L3hv28HafZ2MqgAMa5Matp7Pmrh6kvGcXuIkUC94k523RghV/7sSnwx8i2+Whh6JQKbfnDWbh433JemFxwp+GKimp7LziJPrd8CV/z55LY08DV+vJqyhi4MxbOe6ejQS359f8AnPULHgN4vOx+7J+9LvlSx5p81lUxyC/Oyj1h6U/ofGkTBrNWUFw796ofX4s8GZmsm/Icey5ei//OP6VqB8kDGqI3+WdwoKHT6LJlEW23HoUWPCa73lbtGD9TV2Z8LMnInphnaJQKZ+WNuKOZZfgfbMprd7dWnlVqxj4/+YqEXzHtGf70LYER+zivp6vMjBtHw09kfvH+KgUfvnir+ny2Drby40iC15zCDm5F6tvSuG1QU/Qx1//E1gCGmRZeQX/u/lCvlrbnrYzvTT+4lsqNuXagZvD8XjxdWjL3hOzyR0WpFdOLn/uMJ2eqb6w7A0vKSvj4o9/TbdHA+jCb8JQsKkLC15TPRE4+XjWXp7B/5z/Gpc2zCXdk1qrl5aEyllUnsqEbwfzybIcmn/qo/nCXeiq9Qk/bhsp4vMhx3al4OSmFAyo4LTj1zCm9Qf0Sy2v07/L1KJ2/GXWxXR9qRgWLrVvGS6x4DU18rZqSf6FXeh2zUquz55Lju/QKWjLA435qOhYJi/vR9aMBjT7dCvBrXk2Xhgh4vPhbZvNzoFtKbiglJ/2XMgZDVfSI+XQqW3rK9J54tuzWfGf42g9Yz0V326v5h1NNFnwmlqTlFQ8x7Ql1Cj9kOe8u4sI5W0P+9QrUwvfTZHLbkWwyaEzUzz7Sght2mrfNmLIkYK3VqcMm+ShgXKCazdU+5zt17pIlVBpKaENm6p92kbR40t8nNdojDEJxILXGGOizILXGGOizILXGGOizILXGGOizILXGGOizILXGGOizILXGGOizILXGGOizILXGGOizILXGGOizILXGGOiLCauTiYi+4BVbtcRIc2BAreLiIBE7Rckbt+sX9F1jKq2qO6JWLk62arDXT4t3onIokTsW6L2CxK3b9av2GFDDcYYE2UWvMYYE2WxErwT3C4gghK1b4naL0jcvlm/YkRMHFwzxphkEit7vMYYkzQseI0xJspcD14ROU9EVonIWhEZ63Y9dSEi7UVkroisEJFlInKz095MRN4VkTXObdMqr7nT6esqETnXveprJiJeEflSRN50HidKv5qIyCsistL5txuQCH0Tkd85/w+XishkEUmL136JyL9FJF9EllZpq3NfROQkEfnGee5fIiLR7ku1VNW1P4AXWAd0BlKBr4AebtZUx/qzgROd+42A1UAP4H5grNM+FrjPud/D6aMf6OT03et2P47Qv1uBl4A3nceJ0q9JwC+c+6lAk3jvG9AW2AA0cB5PBa6J134BZwAnAkurtNW5L8ACYAAgwNvA+W73TVVd3+PtD6xV1fWqWg5MAUa6XFOtqWqeqn7h3N8HrKDyB2AklT/cOLcXOfdHAlNUtUxVNwBrqfw7iDki0g4YDjxTpTkR+pVJ5Q/1RABVLVfV3SRA36g8IaqBiPiAdGAbcdovVf0I2HlQc536IiLZQKaqfqaVKfxclde4yu3gbQtsqfI412mLOyLSEegLzAdaqWoeVIYz0NLZLJ76+zBwOxCq0pYI/eoM7ACedYZRnhGRDOK8b6q6FfgHsBnIA/ao6jvEeb8OUte+tHXuH9zuOreDt7rxlrib3yYiDYFXgVtUde+RNq2mLeb6KyIjgHxVXVzbl1TTFnP9cvio/Ar7hKr2BYqp/Np6OHHRN2e8cySVX7XbABki8rMjvaSatpjrVy0dri8x20e3gzcXaF/lcTsqvx7FDRFJoTJ0X1TV15zm7c7XHJzbfKc9Xvp7GnChiGykcvjnbBF5gfjvF1TWmquq853Hr1AZxPHet3OADaq6Q1UDwGvAQOK/X1XVtS+5zv2D213ndvAuBHJEpJOIpAKjgRku11RrzhHSicAKVX2oylMzgKud+1cD06u0jxYRv4h0AnKoHPyPKap6p6q2U9WOVP6bvK+qPyPO+wWgqt8CW0Sku9M0BFhO/PdtM3CqiKQ7/y+HUHnMId77VVWd+uIMR+wTkVOdv5OrqrzGXW4f3QOGUTkbYB3wR7frqWPtp1P51eVrYInzZxiQBcwB1ji3zaq85o9OX1cRI0dYa+jjYH6Y1ZAQ/QL6AIucf7c3gKaJ0DfgXmAlsBR4nsqj/HHZL2AylWPVASr3XK87mr4A/Zy/j3XAYzhn67r9x04ZNsaYKHN7qMEYY5KOBa8xxkSZBa8xxkSZBa8xxkSZBa8xxkSZBa8xxkSZBa8xxkTZ/wOEMdkhOmj4LQAAAABJRU5ErkJggg==\n", 131 | "text/plain": [ 132 | "
" 133 | ] 134 | }, 135 | "metadata": { 136 | "needs_background": "light" 137 | }, 138 | "output_type": "display_data" 139 | }, 140 | { 141 | "data": { 142 | "image/png": "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\n", 143 | "text/plain": [ 144 | "
" 145 | ] 146 | }, 147 | "metadata": { 148 | "needs_background": "light" 149 | }, 150 | "output_type": "display_data" 151 | }, 152 | { 153 | "data": { 154 | "image/png": "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\n", 155 | "text/plain": [ 156 | "
" 157 | ] 158 | }, 159 | "metadata": { 160 | "needs_background": "light" 161 | }, 162 | "output_type": "display_data" 163 | } 164 | ], 165 | "source": [ 166 | "# Check that in fact we have vertices in the right places, looks like we do\n", 167 | "for apIDX in [300,350,400,450,500,550,600]:\n", 168 | " plt.figure()\n", 169 | " plt.imshow(ann10[apIDX,:,:]>0)\n", 170 | " for i, triangle in enumerate(trianglesCCF):\n", 171 | " for j, vertex in enumerate(triangle):\n", 172 | " if vertex[0]==apIDX:\n", 173 | " plt.scatter(vertex[1],vertex[2])" 174 | ] 175 | }, 176 | { 177 | "cell_type": "markdown", 178 | "id": "94409a12-b757-472c-9a9a-debb4ed0e836", 179 | "metadata": {}, 180 | "source": [ 181 | "# Check if triangles are entirely on the surface mask\n", 182 | "Load the boundary and find all triangles that are entirely defined on the surface mask" 183 | ] 184 | }, 185 | { 186 | "cell_type": "code", 187 | "execution_count": 77, 188 | "id": "b6bbbde1-489c-4e70-89e2-16eefabe2270", 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "# Load the 10um mask (isocortex_boundary_10.nrrd)\n", 193 | "boundary10, meta = nrrd.read('isocortex_boundary_10.nrrd')\n", 194 | "# Unique values are 0, 1, 3, 4, where #1 is the outer surface" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 83, 200 | "id": "c89e3188-1c5a-48e0-a066-7cfb5153d486", 201 | "metadata": {}, 202 | "outputs": [ 203 | { 204 | "data": { 205 | "text/plain": [ 206 | "" 207 | ] 208 | }, 209 | "execution_count": 83, 210 | "metadata": {}, 211 | "output_type": "execute_result" 212 | }, 213 | { 214 | "data": { 215 | "image/png": "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\n", 216 | "text/plain": [ 217 | "
" 218 | ] 219 | }, 220 | "metadata": { 221 | "needs_background": "light" 222 | }, 223 | "output_type": "display_data" 224 | } 225 | ], 226 | "source": [ 227 | "plt.imshow(boundary10[550,:,:]==1)" 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": 88, 233 | "id": "4c10efd4-8521-4676-82b5-7378a30f9f88", 234 | "metadata": {}, 235 | "outputs": [], 236 | "source": [ 237 | "# Now go through and find all vertices that are inside the outer surface and only keep triangles where all *three* vertices are on the surface\n", 238 | "surfaceIdx = []\n", 239 | "for i, triangle in enumerate(trianglesCCF):\n", 240 | " for j, vertex in enumerate(triangle):\n", 241 | " if not boundary10[int(vertex[0]),int(vertex[1]),int(vertex[2])]==1:\n", 242 | " break\n", 243 | " surfaceIdx.append(i)" 244 | ] 245 | }, 246 | { 247 | "cell_type": "markdown", 248 | "id": "b341f71f-60d2-4278-9ea4-39ea8c5a5db8", 249 | "metadata": {}, 250 | "source": [ 251 | "# Remove all triangles outside the surface" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": 91, 257 | "id": "fe71e097-a558-4608-a319-3d5a2159e79a", 258 | "metadata": {}, 259 | "outputs": [], 260 | "source": [ 261 | "surfaceTriangles = triangles[surfaceIdx]" 262 | ] 263 | }, 264 | { 265 | "cell_type": "markdown", 266 | "id": "3ee253ad-f2b9-4e13-9e3c-033994a7a59a", 267 | "metadata": { 268 | "tags": [] 269 | }, 270 | "source": [ 271 | "# Save to a new file, then look at this in blender" 272 | ] 273 | }, 274 | { 275 | "cell_type": "code", 276 | "execution_count": 100, 277 | "id": "e7af178e-9815-454c-b226-059d97039955", 278 | "metadata": {}, 279 | "outputs": [], 280 | "source": [ 281 | "surfaceMesh = mesh.Mesh(data = numpy.zeros(surfaceTriangles.shape[0], dtype=mesh.Mesh.dtype))\n", 282 | "surfaceMesh.vectors = surfaceTriangles" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": 101, 288 | "id": "1201305d-5cc6-48cf-87f0-f88714baa101", 289 | "metadata": {}, 290 | "outputs": [], 291 | "source": [ 292 | "surfaceMesh.save('315L_surface.stl')" 293 | ] 294 | }, 295 | { 296 | "cell_type": "markdown", 297 | "id": "81bdf524-b698-49d5-9d7b-e7a85bb75352", 298 | "metadata": { 299 | "tags": [] 300 | }, 301 | "source": [ 302 | "# Attempt #2: Check if triangles are within 20 um of the surface mask\n", 303 | "Load the boundary and find all triangles that have vertices within some distance of the surface mask. The point here is maybe the reason we lose most of the triangles is because of rounding error generating the 3D model and smoothing it, so maybe we can recover those triangles here?" 304 | ] 305 | }, 306 | { 307 | "cell_type": "code", 308 | "execution_count": 105, 309 | "id": "2bdb9b05-425b-47b8-8f6d-35d43483319e", 310 | "metadata": {}, 311 | "outputs": [], 312 | "source": [ 313 | "surfaceIdx = []\n", 314 | "for i, triangle in enumerate(trianglesCCF):\n", 315 | " for j, vertex in enumerate(triangle):\n", 316 | " # check in the surrounding region +/- 2 in all directions to see if there is a surface voxel\n", 317 | " vint = [int(vertex[0]),int(vertex[1]),int(vertex[2])]\n", 318 | " done = False\n", 319 | " for xi in np.arange(-2,3):\n", 320 | " for yi in np.arange(-2,3):\n", 321 | " for zi in np.arange(-2,3):\n", 322 | " if boundary10[vint[0]+xi, vint[1]+yi, vint[2]+zi]==1:\n", 323 | " done = True\n", 324 | " surfaceIdx.append(i)\n", 325 | " if done:\n", 326 | " break\n", 327 | " if done:\n", 328 | " break\n", 329 | " if done:\n", 330 | " break" 331 | ] 332 | }, 333 | { 334 | "cell_type": "code", 335 | "execution_count": 108, 336 | "id": "17efd977-1da7-44ad-b196-bf7f71902a4b", 337 | "metadata": {}, 338 | "outputs": [], 339 | "source": [ 340 | "surfaceTriangles = triangles[surfaceIdx]" 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": 109, 346 | "id": "78cdfdf4-c2d1-4588-a110-51982cacda79", 347 | "metadata": {}, 348 | "outputs": [], 349 | "source": [ 350 | "surfaceMesh = mesh.Mesh(data = numpy.zeros(surfaceTriangles.shape[0], dtype=mesh.Mesh.dtype))\n", 351 | "surfaceMesh.vectors = surfaceTriangles\n", 352 | "surfaceMesh.save('315L_surface_within20.stl')" 353 | ] 354 | }, 355 | { 356 | "cell_type": "markdown", 357 | "id": "e345953e-ba6e-4535-94f2-b176c1c761ab", 358 | "metadata": { 359 | "tags": [] 360 | }, 361 | "source": [ 362 | "# Attempt #3: Get the distance from all surface points, then assign a label to each vertex based on whether you are closer to to top/sides/bottom\n", 363 | "Idea from Cyrille\n", 364 | "\n", 365 | "Algorithm is to compute distance of each vertex point from every mask point that is non-zero, then assign the minimum distance point's value to that vertex. This way vertices aren't biased \n", 366 | "1 = top surface\n", 367 | "3 = bottom surface\n", 368 | "4 = sides\n", 369 | "0 = everything else" 370 | ] 371 | }, 372 | { 373 | "cell_type": "code", 374 | "execution_count": 143, 375 | "id": "395bdef9-c57e-4263-93e2-256f90e22ece", 376 | "metadata": {}, 377 | "outputs": [ 378 | { 379 | "data": { 380 | "text/plain": [ 381 | "" 382 | ] 383 | }, 384 | "execution_count": 143, 385 | "metadata": {}, 386 | "output_type": "execute_result" 387 | }, 388 | { 389 | "data": { 390 | "image/png": "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\n", 391 | "text/plain": [ 392 | "
" 393 | ] 394 | }, 395 | "metadata": { 396 | "needs_background": "light" 397 | }, 398 | "output_type": "display_data" 399 | } 400 | ], 401 | "source": [ 402 | "plt.imshow(boundary10[550,:,:]==3)" 403 | ] 404 | }, 405 | { 406 | "cell_type": "code", 407 | "execution_count": 119, 408 | "id": "322ba60f-21e2-4709-bbd6-5cd0bb416e8a", 409 | "metadata": {}, 410 | "outputs": [ 411 | { 412 | "data": { 413 | "text/plain": [ 414 | "array([0, 1, 3, 4], dtype=uint16)" 415 | ] 416 | }, 417 | "execution_count": 119, 418 | "metadata": {}, 419 | "output_type": "execute_result" 420 | } 421 | ], 422 | "source": [ 423 | "np.unique(boundary10)" 424 | ] 425 | }, 426 | { 427 | "cell_type": "code", 428 | "execution_count": 120, 429 | "id": "8f40a5a6-b164-414c-ac84-588cfd82658c", 430 | "metadata": {}, 431 | "outputs": [], 432 | "source": [ 433 | "# Pull all of the mask points that are non-zero and their corresponding values\n", 434 | "boundaryIdxs = np.argwhere(boundary10>0)\n", 435 | "boundaryVals = boundary10[boundary10>0]\n", 436 | "print(boundaryIdxs.shape)\n", 437 | "print(boundaryVals.shape)" 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": 129, 443 | "id": "90961eaa-e133-4b59-9d40-eddc80490c96", 444 | "metadata": {}, 445 | "outputs": [], 446 | "source": [ 447 | "dist = np.sqrt(np.sum(np.power(boundaryIdxs-[1,2,3],2),axis=1))" 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "execution_count": 130, 453 | "id": "1de4c3ac-3182-4cd7-8460-430f0c870024", 454 | "metadata": {}, 455 | "outputs": [ 456 | { 457 | "data": { 458 | "text/plain": [ 459 | "(2303430,)" 460 | ] 461 | }, 462 | "execution_count": 130, 463 | "metadata": {}, 464 | "output_type": "execute_result" 465 | } 466 | ], 467 | "source": [ 468 | "dist.shape" 469 | ] 470 | }, 471 | { 472 | "cell_type": "code", 473 | "execution_count": 151, 474 | "id": "d2139593-f626-4f8d-bbc7-ab17dfe89364", 475 | "metadata": {}, 476 | "outputs": [], 477 | "source": [ 478 | "def vertexDist(vertex, boundIdxs, boundVals):\n", 479 | " # compute distance from all boundary indexes\n", 480 | " dist = np.sqrt(np.sum(np.power(boundaryIdxs-vertex,2),axis=1))\n", 481 | " return (dist, boundVals[np.argmin(dist)])" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": null, 487 | "id": "a8bb1c03-7af5-41be-9870-95928debb118", 488 | "metadata": {}, 489 | "outputs": [], 490 | "source": [ 491 | "# For every vertex in the 3d model, compute the distance to all surface points, then back out the value for the min-distance surface position\n", 492 | "minValues = np.empty((trianglesCCF.shape[0],trianglesCCF.shape[1]))\n", 493 | "# we'll keep the distance as well, as a way of looking for parts of the mesh surface that are badly aligned\n", 494 | "distValues = np.empty((trianglesCCF.shape[0],trianglesCCF.shape[1]))\n", 495 | "for i, triangle in enumerate(trianglesCCF):\n", 496 | " for j, vertex in enumerate(triangle):\n", 497 | " (dist, val) = vertexDist(vertex,boundaryIdxs, boundaryVals)\n", 498 | " distValues[i,j] = dist\n", 499 | " minValues[i,j] = val" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": 135, 505 | "id": "8438aa9e-5128-4c5f-bfc9-d2b182f53279", 506 | "metadata": {}, 507 | "outputs": [ 508 | { 509 | "data": { 510 | "text/plain": [ 511 | "(32934, 3)" 512 | ] 513 | }, 514 | "execution_count": 135, 515 | "metadata": {}, 516 | "output_type": "execute_result" 517 | } 518 | ], 519 | "source": [ 520 | "minValues.shape" 521 | ] 522 | }, 523 | { 524 | "cell_type": "code", 525 | "execution_count": 138, 526 | "id": "e9633320-d30c-46e1-87e4-5d6601b1b615", 527 | "metadata": {}, 528 | "outputs": [], 529 | "source": [ 530 | "keepIdx = np.all(minValues==1,axis=1)" 531 | ] 532 | }, 533 | { 534 | "cell_type": "code", 535 | "execution_count": 139, 536 | "id": "e78f0185-dfcd-4fb8-bbe5-063b4360d385", 537 | "metadata": {}, 538 | "outputs": [], 539 | "source": [ 540 | "surfaceTriangles = triangles[keepIdx]\n", 541 | "surfaceMesh = mesh.Mesh(data = numpy.zeros(surfaceTriangles.shape[0], dtype=mesh.Mesh.dtype))\n", 542 | "surfaceMesh.vectors = surfaceTriangles\n", 543 | "surfaceMesh.save('315L_surface_nearestTop.stl')" 544 | ] 545 | }, 546 | { 547 | "cell_type": "code", 548 | "execution_count": 140, 549 | "id": "928bd0da-5e40-4593-a695-79e6df5dbd85", 550 | "metadata": {}, 551 | "outputs": [], 552 | "source": [ 553 | "keepIdx = np.any(minValues==1,axis=1)" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": 141, 559 | "id": "fc5696d0-5330-485a-9016-0df43d331b77", 560 | "metadata": {}, 561 | "outputs": [], 562 | "source": [ 563 | "surfaceTriangles = triangles[keepIdx]\n", 564 | "surfaceMesh = mesh.Mesh(data = numpy.zeros(surfaceTriangles.shape[0], dtype=mesh.Mesh.dtype))\n", 565 | "surfaceMesh.vectors = surfaceTriangles\n", 566 | "surfaceMesh.save('315L_surface_nearestTopAny.stl')" 567 | ] 568 | }, 569 | { 570 | "cell_type": "code", 571 | "execution_count": 146, 572 | "id": "c4f7e8af-5c22-4f1e-aad4-8dace9a82508", 573 | "metadata": {}, 574 | "outputs": [], 575 | "source": [ 576 | "dropIdx = np.all(minValues==3,axis=1) | np.all(minValues==4,axis=1)" 577 | ] 578 | }, 579 | { 580 | "cell_type": "code", 581 | "execution_count": 149, 582 | "id": "a16d0178-2d9d-43d1-96d9-fd5de8a4ee60", 583 | "metadata": {}, 584 | "outputs": [], 585 | "source": [ 586 | "surfaceTriangles = triangles[np.invert(dropIdx)]\n", 587 | "surfaceMesh = mesh.Mesh(data = numpy.zeros(surfaceTriangles.shape[0], dtype=mesh.Mesh.dtype))\n", 588 | "surfaceMesh.vectors = surfaceTriangles\n", 589 | "surfaceMesh.save('315L_surface_nearestNotBottomEdge.stl')" 590 | ] 591 | }, 592 | { 593 | "cell_type": "markdown", 594 | "id": "340d1527-5352-4fec-a9f3-969e8adc3c06", 595 | "metadata": {}, 596 | "source": [ 597 | "# Final algorithm\n", 598 | "From the previous attempts we know that the top surface does not extend perfectly to the edge, there are a lot of triangles that have a mixture of edge/bottom values. So here's the new concept: we'll start by defining a center starting vertex in the middle of the top surface. Then we'll take a triangle with that vertex and flood fill outward until the average annotation value is no longer top surface but something else, at which point we'll stop flood filling in that direction. \n", 599 | "\n", 600 | "Probably a very slow algorithm but at least we have a chance of working? " 601 | ] 602 | }, 603 | { 604 | "cell_type": "code", 605 | "execution_count": null, 606 | "id": "89ff3ec9-b4bc-4b9e-96cb-66e457f0297f", 607 | "metadata": {}, 608 | "outputs": [], 609 | "source": [ 610 | "# Now get all triangles that have three vertices in the surface (value==1)" 611 | ] 612 | }, 613 | { 614 | "cell_type": "code", 615 | "execution_count": null, 616 | "id": "7102be26-57f6-4969-882b-eeb7a2c5f5af", 617 | "metadata": {}, 618 | "outputs": [], 619 | "source": [ 620 | "\n", 621 | "# Save to a new file (then, load in Blender, compute UV mask from surface, export)" 622 | ] 623 | }, 624 | { 625 | "cell_type": "code", 626 | "execution_count": null, 627 | "id": "80812f07-8115-4f94-b64b-68dd98e04ea0", 628 | "metadata": {}, 629 | "outputs": [], 630 | "source": [ 631 | "# Load the UV file for the cortex surface\n", 632 | "\n", 633 | "# Convert to a flatmap, based on how dorsal_flatmap.npy runs, using the new streamlines from Cyrille and/or the streamlines from Allen" 634 | ] 635 | } 636 | ], 637 | "metadata": { 638 | "kernelspec": { 639 | "display_name": "Python 3 (ipykernel)", 640 | "language": "python", 641 | "name": "python3" 642 | }, 643 | "language_info": { 644 | "codemirror_mode": { 645 | "name": "ipython", 646 | "version": 3 647 | }, 648 | "file_extension": ".py", 649 | "mimetype": "text/x-python", 650 | "name": "python", 651 | "nbconvert_exporter": "python", 652 | "pygments_lexer": "ipython3", 653 | "version": "3.9.10" 654 | } 655 | }, 656 | "nbformat": 4, 657 | "nbformat_minor": 5 658 | } 659 | -------------------------------------------------------------------------------- /streamlines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ------------------------------------------------------------------------------------------------ 5 | # Imports 6 | # ------------------------------------------------------------------------------------------------ 7 | 8 | from common import * 9 | from gradient import * 10 | 11 | from joblib import Parallel, delayed 12 | from scipy.interpolate import interpn 13 | from scipy.interpolate import interp1d 14 | 15 | 16 | # ------------------------------------------------------------------------------------------------ 17 | # Constants 18 | # ------------------------------------------------------------------------------------------------ 19 | 20 | PATH_LEN = 100 21 | MAX_POINTS = None 22 | PARALLEL_STEP = 10_000 # how many streamlines per compute item 23 | MAX_ITER = 2000 24 | STEP = 1.0 25 | 26 | 27 | # ------------------------------------------------------------------------------------------------ 28 | # Utils 29 | # ------------------------------------------------------------------------------------------------ 30 | 31 | def last_nonzero(arr, axis, invalid_val=-1): 32 | """Return the index of the last vector element with a zero.""" 33 | 34 | # https://stackoverflow.com/a/47269413/1595060 35 | mask = arr != 0 36 | val = arr.shape[axis] - np.flip(mask, axis=axis).argmax(axis=axis) - 1 37 | return np.where(mask.any(axis=axis), val, invalid_val) 38 | 39 | 40 | def subset(paths, max_paths=None): 41 | """Get a subset of all paths.""" 42 | 43 | if not max_paths: 44 | return paths 45 | n = paths.shape[0] 46 | k = max(1, int(math.floor(float(n) / float(max_paths)))) 47 | return np.array(paths[::k, ...]) 48 | 49 | 50 | class ProgressParallel(Parallel): 51 | def __init__(self, use_tqdm=True, total=None, *args, **kwargs): 52 | self._use_tqdm = use_tqdm 53 | self._total = total 54 | super().__init__(*args, **kwargs) 55 | 56 | def __call__(self, *args, **kwargs): 57 | with tqdm(disable=not self._use_tqdm, total=self._total) as self._pbar: 58 | return Parallel.__call__(self, *args, **kwargs) 59 | 60 | def print_progress(self): 61 | if self._total is None: 62 | self._pbar.total = self.n_dispatched_tasks 63 | self._pbar.n = self.n_completed_tasks 64 | self._pbar.refresh() 65 | 66 | 67 | # ------------------------------------------------------------------------------------------------ 68 | # Initial points (seeds) 69 | # ------------------------------------------------------------------------------------------------ 70 | 71 | def init_allen(region): 72 | """Use the initial points of the Allen streamlines.""" 73 | return load_npy(filepath(region, 'streamlines_allen'))[:, 0, :] 74 | 75 | 76 | def init_ibl(region): 77 | """Use the voxels in the top surface as initial points for the streamlines.""" 78 | mask = get_mask(region) 79 | assert mask.ndim == 3 80 | i, j, k = np.nonzero(np.isin(mask, [V_ST])) 81 | pos = np.c_[i, j, k] 82 | 83 | # HACK: fix bug with large empty areas when plotting streamlines. For some reason, 84 | # taking a subset of the positions by slicing pos[::k, :] results in large empty areas 85 | # in one of the hemispheres for some values of k (even values). We fix this systematic bias by 86 | # shuffling the initial positions. 87 | np.random.seed(0) 88 | perm = np.random.permutation(pos.shape[0]) 89 | 90 | return pos[perm, :] 91 | 92 | 93 | # ------------------------------------------------------------------------------------------------ 94 | # Integration 95 | # ------------------------------------------------------------------------------------------------ 96 | 97 | def integrate_step(pos, step, gradient, xyz): 98 | """Run one step of the integration process.""" 99 | 100 | assert pos.ndim == 2 101 | assert pos.shape[1] == 3 102 | assert gradient.shape == (N, M, P, 3) 103 | for i in range(3): 104 | pos[:, i] = np.clip(pos[:, i], xyz[i][0], xyz[i][-1]) 105 | g = interpn(xyz, gradient, pos) 106 | # NOTE: - if bottom to top, + if top to bottom 107 | return pos + step * g 108 | 109 | 110 | def integrate_field(pos, step, gradient, target, max_iter=MAX_ITER): 111 | """Generate streamlines.""" 112 | 113 | assert pos.ndim == 2 114 | n_paths = pos.shape[0] 115 | assert pos.shape == (n_paths, 3) 116 | 117 | n, m, p = target.shape 118 | res_um = 1 119 | x = np.arange(0, res_um * n, res_um) 120 | y = np.arange(0, res_um * m, res_um) 121 | z = np.arange(0, res_um * p, res_um) 122 | xyz = (x, y, z) 123 | 124 | out = np.zeros((n_paths, max_iter, 3), dtype=np.float32) 125 | out[:, 0, :] = pos 126 | 127 | # Which positions are still in the volume and need to be integrated? 128 | kept = np.ones(n_paths, dtype=bool) 129 | 130 | # with tqdm(range(1, max_iter), desc="Integrating...") as t: 131 | for iter in range(1, max_iter): 132 | # Previous position of the kept streamlines. 133 | prev_pos = out[kept, iter - 1, :] 134 | 135 | # Compute the new positions. 136 | new_pos = integrate_step(prev_pos, step, gradient, xyz) 137 | 138 | # Save the new positions in the output array. 139 | out[kept, iter, :] = new_pos 140 | 141 | # For the dropped streamlines, we copy the same values. 142 | out[~kept, iter, :] = out[~kept, iter - 1, :] 143 | 144 | # Stop integrating the paths the go outside of the volume. 145 | # Convert the new positions to indices. 146 | i, j, k = np.round(new_pos).astype(np.int32).T 147 | i[:] = np.clip(i, 0, n - 1) 148 | j[:] = np.clip(j, 0, m - 1) 149 | k[:] = np.clip(k, 0, p - 1) 150 | # i, j, k are indices of the streamlines within the volume. 151 | 152 | # The paths that are kept are the paths that have not reached the target yet. 153 | kept[kept] = target[i, j, k] == 0 154 | 155 | assert kept.shape == (n_paths,) 156 | n_kept = kept.sum() 157 | # t.set_postfix(n_kept=n_kept) 158 | if n_kept == 0: 159 | break 160 | 161 | return out 162 | 163 | 164 | def path_lengths(paths): 165 | """Compute the lengths of the streamlines.""" 166 | 167 | # print("Computing the path lengths...") 168 | streamlines = paths 169 | n_paths, path_len, _ = streamlines.shape 170 | d = (np.diff(paths, axis=1) ** 2).sum(axis=2) > 1e-5 171 | # d = np.abs(np.diff(paths, axis=1)).max(axis=2) > 1e-3 172 | ln = last_nonzero(d, 1) 173 | assert ln.shape == (n_paths,) 174 | return ln 175 | 176 | 177 | def resample_paths(paths, num=PATH_LEN): 178 | """Resample the streamlines.""" 179 | 180 | n_paths, path_len, _ = paths.shape 181 | xp = np.linspace(0, 1, num) 182 | 183 | lengths = path_lengths(paths) 184 | # HACK: length == -1 means that the path has not reached the target yet so we resample all 185 | # of it 186 | lengths[lengths < 0] = path_len 187 | 188 | out = np.zeros((n_paths, num, 3), dtype=np.float32) 189 | # for i in tqdm(range(n_paths), desc="Resampling..."): 190 | for i in range(n_paths): 191 | n = lengths[i] 192 | if n >= 2: 193 | lin = interp1d(np.linspace(0, 1, n), paths[i, :n, :], axis=0) 194 | out[i, :, :] = lin(xp) 195 | else: 196 | out[i, :, :] = paths[i, :num, :] 197 | return out 198 | 199 | 200 | def _make_streamlines(points, idx, gradient, target, out): 201 | 202 | # Integrate the gradient field from those positions. 203 | paths = integrate_field( 204 | points[idx], STEP, gradient, target, max_iter=MAX_ITER) 205 | 206 | # Resample the paths. 207 | streamlines = resample_paths(paths, num=PATH_LEN) 208 | 209 | out[idx, ...] = streamlines 210 | # return streamlines 211 | 212 | 213 | def compute_streamlines(region, init_points=None): 214 | """Compute (or load from the cache) the streamlines.""" 215 | 216 | # Load the region mask. 217 | mask = get_mask(region) 218 | assert mask.ndim == 3 219 | 220 | # Starting points of the streamlines. 221 | if init_points is None: 222 | init_path = filepath(region, 'init_points') 223 | # Get or compute the initial points, persisting them on disk. 224 | if not init_path.exists(): 225 | save_npy(init_path, init_ibl(region)) 226 | assert init_path.exists() 227 | # Always load them as memmap. 228 | init_points = load_npy(init_path) 229 | assert init_points.ndim == 2 230 | assert init_points.shape[1] == 3 231 | n = len(init_points) 232 | init_points = subset(init_points, MAX_POINTS) 233 | n_paths = len(init_points) 234 | # print(f"Starting computing {n_paths} (out of {n}) streamlines...") 235 | 236 | # Compute or load the gradient. 237 | gradient = get_gradient(region) 238 | assert gradient.ndim == 4 239 | assert gradient.shape[3] == 3 240 | 241 | # Stop the integration when points reach the bottom surface. 242 | target = np.isin(mask, (V_SB,)) 243 | 244 | # Memmap the output npy file in writing mode. 245 | out = np.lib.format.open_memmap( 246 | filepath(region, 'streamlines'), dtype=np.float32, shape=(n_paths, PATH_LEN, 3), mode='w+') 247 | 248 | # Make the streamlines and write them directly to disk. 249 | w = PARALLEL_STEP 250 | slices = [slice(start, start + w) for start in range(0, n_paths - w, w)] 251 | ProgressParallel(n_jobs=-2, total=len(slices))( 252 | delayed(_make_streamlines)(init_points, sl, gradient, target, out) 253 | for sl in slices) 254 | 255 | # Serial execution: 256 | # streamlines = _make_streamlines( 257 | # init_points, slice(None, None, None), gradient, target, out) 258 | # Save the streamlines. 259 | # save_npy(filepath(region, 'streamlines'), streamlines) 260 | 261 | 262 | # ------------------------------------------------------------------------------------------------ 263 | # Entry-point 264 | # ------------------------------------------------------------------------------------------------ 265 | 266 | if __name__ == '__main__': 267 | compute_streamlines(REGION) 268 | -------------------------------------------------------------------------------- /surface.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # ------------------------------------------------------------------------------------------------ 5 | # Imports 6 | # ------------------------------------------------------------------------------------------------ 7 | 8 | from common import * 9 | 10 | 11 | # ------------------------------------------------------------------------------------------------ 12 | # Constants 13 | # ------------------------------------------------------------------------------------------------ 14 | 15 | SMOOTH_WIDTH = 10 16 | SMOOTH_SIGMA = 3.0 17 | 18 | 19 | # ------------------------------------------------------------------------------------------------ 20 | # Normal 21 | # ------------------------------------------------------------------------------------------------ 22 | 23 | def compute_normal(mask): 24 | """Compute a crude estimate of the normals to the surface, by looking at the mask values of 25 | the neighbor voxels.""" 26 | 27 | i, j, k = np.nonzero(np.isin(mask, (V_ST, V_SB, V_SE))) 28 | vi0 = (mask[i-1, j, k] == V_VOLUME).astype(np.int8) 29 | vi1 = (mask[i+1, j, k] == V_VOLUME).astype(np.int8) 30 | vj0 = (mask[i, j-1, k] == V_VOLUME).astype(np.int8) 31 | vj1 = (mask[i, j+1, k] == V_VOLUME).astype(np.int8) 32 | vk0 = (mask[i, j, k-1] == V_VOLUME).astype(np.int8) 33 | vk1 = (mask[i, j, k+1] == V_VOLUME).astype(np.int8) 34 | count = vi0 + vi1 + vj0 + vj1 + vk0 + vk1 # (n,) 35 | pos = np.c_[i, j, k] 36 | normal = ( 37 | np.c_[i-1, j, k] * vi0[:, np.newaxis] + 38 | np.c_[i+1, j, k] * vi1[:, np.newaxis] + 39 | np.c_[i, j-1, k] * vj0[:, np.newaxis] + 40 | np.c_[i, j+1, k] * vj1[:, np.newaxis] + 41 | np.c_[i, j, k-1] * vk0[:, np.newaxis] + 42 | np.c_[i, j, k+1] * vk1[:, np.newaxis] - 43 | count[:, np.newaxis] * pos) 44 | 45 | Ni = np.zeros((N, M, P), dtype=np.int8) 46 | Nj = np.zeros((N, M, P), dtype=np.int8) 47 | Nk = np.zeros((N, M, P), dtype=np.int8) 48 | 49 | Ni[i, j, k] = normal[:, 0] 50 | Nj[i, j, k] = normal[:, 1] 51 | Nk[i, j, k] = normal[:, 2] 52 | 53 | return np.stack((Ni, Nj, Nk), axis=-1) 54 | 55 | 56 | # ------------------------------------------------------------------------------------------------ 57 | # Normal smoothing (Gaussian convolution) 58 | # ------------------------------------------------------------------------------------------------ 59 | 60 | def gaussian_kernel(size, sigma): 61 | """Return an N-Dimensional Gaussian Kernel. 62 | @param integer size size of kernel / will be round to a nearest odd number 63 | @param float sigma standard deviation of gaussian 64 | https://gist.github.com/tohki/e8803620c2abaa2083f6 65 | """ 66 | assert size % 2 == 0 67 | s = int(size // 2) 68 | x, y, z = np.mgrid[-s:s, -s:s, -s:s] 69 | k = np.exp(-(np.power(x, 2) + np.power(y, 2) + 70 | np.power(z, 2)) / (2 * (sigma ** 2))) 71 | return (k / k.sum()).astype(np.float32) 72 | 73 | 74 | @numba.njit() 75 | def _convol(arrp, maskp, surf_idx=None, gauss=None): 76 | """Numba kernel for computing a partial surface 3D convolution.""" 77 | 78 | # NOTE: arrp and maskp must be padded already 79 | assert gauss is not None 80 | 81 | width = w = gauss.shape[0] 82 | hw = w // 2 83 | ni, nj, nk, nd = arrp.shape 84 | 85 | # HACK: NO PADDING because Python crashes with np.pad() on large arrays 86 | w = 0 87 | 88 | ni -= 2*w 89 | nj -= 2*w 90 | nk -= 2*w 91 | 92 | out = np.zeros((ni, nj, nk, nd), dtype=np.float32) 93 | x = 0 94 | n = len(surf_idx) 95 | print(f"Running the convolution, please wait a few minutes") 96 | for iter in range(n): 97 | # if iter % 100000 == 0: 98 | # print(100 * iter / float(n)) 99 | i0, j0, k0 = surf_idx[iter] 100 | 101 | # HACK: NO PADDING 102 | assert hw <= i0 and i0+hw < ni and hw <= j0 and j0 + \ 103 | hw < nj and hw <= k0 and k0+hw < nk 104 | 105 | sl = ( 106 | slice(w+i0-hw, w+i0+hw, None), 107 | slice(w+j0-hw, w+j0+hw, None), 108 | slice(w+k0-hw, w+k0+hw, None) 109 | ) 110 | assert maskp[i0+w, j0+w, k0+w] 111 | for d in range(nd): 112 | mg = maskp[sl] * gauss 113 | su = np.sum(mg) 114 | if su != 0: 115 | x = np.sum(arrp[sl + (d,)] * mg) 116 | x /= su 117 | out[i0, j0, k0, d] = x 118 | print(f"Done") 119 | return out 120 | 121 | 122 | def convol(arr, surf_mask, surf_idx=None, width=6, sigma=1.0): 123 | """Smooth a 3D vector field (4D array) with a Gaussian kernel restricted to a surface.""" 124 | 125 | assert arr.ndim == 4 126 | assert arr.dtype == np.int8 127 | assert surf_mask.ndim == 3 128 | assert surf_mask.dtype == bool 129 | assert surf_mask.shape == arr.shape[:3] 130 | 131 | assert width % 2 == 0 132 | 133 | if surf_idx is None: 134 | i, j, k = np.nonzero(surf_mask > 0) 135 | surf_idx = np.vstack((i, j, k)).T 136 | 137 | assert surf_idx.ndim == 2 138 | assert surf_idx.shape[1] == 3 139 | assert surf_idx.dtype == np.int64 140 | 141 | print(f"Computing the Gaussian kernel") 142 | gauss = gaussian_kernel(width, sigma) 143 | assert gauss.shape == (width, width, width) 144 | 145 | print(f"Padding the arrays") 146 | # These functions crash on the big volumes 147 | #arrp = np.pad(arr, width, mode='edge') 148 | #maskp = np.pad(surf_mask, width, mode='edge') 149 | arrp = arr 150 | maskp = surf_mask 151 | 152 | return _convol(arrp, maskp, surf_idx=surf_idx, gauss=gauss) 153 | 154 | 155 | def normalize_normal(normal): 156 | """Normalize the normals.""" 157 | 158 | assert normal.ndim == 4 159 | assert normal.shape[3] == 3 160 | 161 | norm = np.linalg.norm(normal, axis=3) 162 | idx = norm > 0 163 | normal[idx] /= norm[idx][..., np.newaxis] 164 | 165 | return normal 166 | 167 | 168 | def get_normal(region): 169 | """Compute (or load from the cache) the normals to the surface of a brain region.""" 170 | 171 | path = filepath(region, 'normal') 172 | if path.exists(): 173 | return load_npy(path) 174 | 175 | print(f"Computing surface normal...") 176 | mask = get_mask(region) 177 | normal = compute_normal(mask) 178 | assert normal.ndim == 4 179 | 180 | surf_vals = (V_ST, V_SB, V_SE) 181 | surface_mask = get_surface_mask(region, surf_vals) 182 | assert surface_mask.ndim == 3 183 | 184 | surf_indices = get_surface_indices(region, surf_vals) 185 | assert surf_indices.ndim == 2 186 | assert surf_indices.shape[1] == 3 187 | normal_smooth = convol( 188 | normal, surface_mask, surf_idx=surf_indices, width=SMOOTH_WIDTH, sigma=SMOOTH_SIGMA) 189 | 190 | print("Normalizing the normals...") 191 | normal_smooth = normalize_normal(normal_smooth) 192 | 193 | # Save the normal file. 194 | save_npy(path, normal_smooth) 195 | return load_npy(path) 196 | 197 | 198 | # ------------------------------------------------------------------------------------------------ 199 | # Entry point 200 | # ------------------------------------------------------------------------------------------------ 201 | 202 | if __name__ == '__main__': 203 | normal = get_normal(REGION) 204 | --------------------------------------------------------------------------------