<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Neuroscience |</title><link>https://kristianeschenburg.netlify.app/category/neuroscience/</link><atom:link href="https://kristianeschenburg.netlify.app/category/neuroscience/index.xml" rel="self" type="application/rss+xml"/><description>Neuroscience</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Fri, 01 May 2020 11:12:32 -0700</lastBuildDate><image><url>https://kristianeschenburg.netlify.app/img/Bayes.jpg</url><title>Neuroscience</title><link>https://kristianeschenburg.netlify.app/category/neuroscience/</link></image><item><title>Watershed by Flooding: Applied Data Structures</title><link>https://kristianeschenburg.netlify.app/post/watershed-by-flooding-applied-data-structures/</link><pubDate>Fri, 01 May 2020 11:12:32 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/watershed-by-flooding-applied-data-structures/</guid><description>&lt;p>I&amp;rsquo;m applying some methods developed in
&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC4677978/pdf/bhu239.pdf" target="_blank" rel="noopener">this paper&lt;/a> for testing purposes in my own thesis research. Specifically, I have some float-valued data, $F$, that varies along the cortical surface of the brain. Visually, I can see that there are areas where these scalar maps change abruptly. I want to identify these boundaries &amp;ndash; eventually, I&amp;rsquo;ll segment out the regions I&amp;rsquo;m interested in.&lt;/p>
&lt;h3 id="computing-the-gradient-map">Computing the Gradient Map&lt;/h3>
&lt;p>The authors use some conventional brain imaging software to compute the gradient of their data. The domain of this data is a triangulated mesh, described by the graph $G = (V, E)$, where $V$ are vertices in Euclidean space and $E$ the edges between these vertices. In short, for a vertex, $v_{i}$, we first need to compute the gradient vector of the scalar field at $v_{i}$. We &amp;ldquo;unfold&amp;rdquo; the 3D positions of adjacent vertices onto the tangent plane of $v_{i}$ &amp;ndash; which we can do by orthogonally projecting the adjacent vertices onto the affine subspace at $v_{i}$ and then weighting appropriately. We then regress the graph signal (our scalar field) onto these unfolded positions. The $L_{2}$ norm of this vector is the gradient at $v_{i}$.&lt;/p>
&lt;p>For each vertex, we have a normal vector to the surface $N$, its spatial 3D coordinates $v_{i} = (x, y, z)$, and a list of its adjacent vertices. We can compute the orthogonal projector onto the affine subspace spanned by $N$, $P_{N}$, and its orthogonal complement, $Q_{N}$, as:&lt;/p>
&lt;p>$$\begin{align}
P_{N} &amp;amp;= N(N^{T}N)^{-1}N^{T} \\
Q_{N} &amp;amp;= I - P_{N}
\end{align}$$&lt;/p>
&lt;p>For any vertex, $v_{j}$, we can compute the orthogonal projection onto the affine subspace spanned by $Q_{N}$ as:&lt;/p>
&lt;p>$$\begin{align}
q(v_{j}) = Q_{N}(v_{j} - v_{i}) + v_{i}
\end{align}$$&lt;/p>
&lt;p>We generate the vectors&lt;/p>
&lt;p>$$
S_{i} = f(i) -
\begin{bmatrix}
f(v_{1}) \\
f(v_{2}) \\
\vdots \\
f(v_{j})
\end{bmatrix}
\in \mathbb{R}^{j} \;\;\;
R_{i} =
\begin{bmatrix}
q(v_{1}) \\
q(v_{2}) \\
\vdots \\
q(v_{j})
\end{bmatrix} \in \mathbb{R}^{j \times 3}
$$&lt;/p>
&lt;p>where $S_{i}$ is the difference between the scalar value at our vertex $f(v_{i})$ and the vector of adjacent $j$ scalar field values, and $R_{i}$ is the matrix of $j$ orthogonally projected adjacent vertex coordinates. Then we perform least squares regression to solve for $\beta$:&lt;/p>
&lt;p>$$\begin{align}
S_{i} = R_{i}\beta
\end{align}$$&lt;/p>
&lt;p>where $\beta \in \mathbb{R}^{3}$, which indicates how much each coordinate axis contributes to variation in the scalar field at $v_{i}$. The gradient value at vertex $v_{i} = \left || \beta \right||_{2}$.&lt;/p>
&lt;h3 id="watershed-by-flooding-algorithm">Watershed By Flooding Algorithm&lt;/h3>
&lt;p>The new scalar field of $L_{2}$ norms is our gradient field, which describes how &amp;ldquo;quickly&amp;rdquo; our original data changes at each vertex. We can now apply the
&lt;a href="https://en.wikipedia.org/wiki/Watershed_%28image_processing%29" target="_blank" rel="noopener">Watershed Algorithm&lt;/a> to segment our mesh data. In brief, the watershed algorithm treats the gradient field as a &lt;em>topographic map&lt;/em>: low-elevation areas (areas with a small gradient) are &amp;ldquo;water basins&amp;rdquo;. If we imagine water flooding this map from the bottom up, basins at low elevation will flood first, while areas at higher elevations will fill last. When water basins meet, the water has reached a &amp;ldquo;boundary&amp;rdquo; (or ridgeline, if we&amp;rsquo;re using the topographic map idea).&lt;/p>
&lt;p>I&amp;rsquo;ve implemented an algorithm variant called
&lt;a href="https://www.sciencedirect.com/science/article/pii/S0098300418307957" target="_blank" rel="noopener">&amp;ldquo;Priority Flooding&amp;rdquo;&lt;/a>, using Python&amp;rsquo;s
&lt;a href="https://docs.python.org/2/library/heapq.html" target="_blank" rel="noopener">heapq&lt;/a>
&lt;a href="https://en.wikipedia.org/wiki/Priority_queue" target="_blank" rel="noopener">priority queue&lt;/a> data type class. The priority queue is an application of the
&lt;a href="https://en.wikipedia.org/wiki/Binary_heap" target="_blank" rel="noopener">binary heap&lt;/a> data structure &amp;ndash; as nodes are added to the heap, the branching process determines where to put nodes (left or right of a current node), based on some value &amp;ndash; in the case of the priority queue, this value is the &amp;ldquo;priority&amp;rdquo;. We utilize the priority queue because it gives us a principled way to iterate over unlabeled vertices, and, with some auxiliary data structures, is guaranteed to converge. The algorithm proceeds as follows:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Identify local minima, and assign each minimum a unique label&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add directly adjacent vertices of local minima to priority queue (lower gradient -&amp;gt; higher priority)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>While the queue is not empty, get the highest-priority item&lt;/p>
&lt;ul>
&lt;li>
&lt;p>If vertices adjacent to this vertex have only one label, assign this vertex to that label&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Else assign this vertex as a boundary vertex&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Add unlabeled adjacent vertices to the queue&lt;/p>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">queue&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">PriorityQueue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">class&lt;/span> &lt;span class="nc">PriorityFlood&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nb">object&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Class to segment a scalar field using the Priority Flooding
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> watershed algorithm.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pq&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">PriorityQueue&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">fit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">gradient&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">A&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">M&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Fitting procedure for watershed algorithm.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> Parameters:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> - - - - -
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> gradient: float, array
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> map of gradient values of scalar field
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> A: dict of lists
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> adjacency list of data domain
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> M: list
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> local minima in scalar field used to seed algorithm
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># initialize empty label vector&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">labels&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">zeros&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">gradient&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">labels&lt;/span>&lt;span class="p">[:]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">nan&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># keep track of items in queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">in_queue&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">zeros&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">gradient&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">))&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">astype&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">bool&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># assign local minima unique labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># add their adjacent vertices to heap&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">loc_min&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="nb">enumerate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">M&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># label local minima&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">labels&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">loc_min&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">i&lt;/span>&lt;span class="o">+&lt;/span>&lt;span class="mi">1&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># add neighbors of local minima to p-queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">nidx&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">A&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">loc_min&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">in_queue&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">nidx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pq&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">put&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">gradient&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">nidx&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">nidx&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># iterate over p-queue items&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># items assigned to a label or&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># assigned as a boundary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pq&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">empty&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># get highest priority item&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># mark as not in p-queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="n">gr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">idx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pq&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">in_queue&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">idx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">False&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># get item neighbors and their labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">i_neighbors&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">asarray&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">A&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">idx&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">i_nlabels&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">labels&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i_neighbors&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># get labels of adjacent vertices that are not nan&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">nans&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">isnan&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i_nlabels&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">unique_labels&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">unique&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">i_nlabels&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">~&lt;/span>&lt;span class="n">nans&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># if more than one unique label&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># assign current vertex as border vertex&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="nb">len&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">unique_labels&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;gt;&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># otherwise assign to water basin&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">else&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># basin assignment&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">labels&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">idx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">unique_labels&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># identify neighbors without labels that arent in the p-queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">gidx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">where&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">nans&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">&amp;amp;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">~&lt;/span>&lt;span class="n">in_queue&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">i_neighbors&lt;/span>&lt;span class="p">]))[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># add these to p-queue&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">for&lt;/span> &lt;span class="n">nidx&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">i_neighbors&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">gidx&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="ow">not&lt;/span> &lt;span class="n">in_queue&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">nidx&lt;/span>&lt;span class="p">]:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">in_queue&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">nidx&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kc">True&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">pq&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">put&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">gradient&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">nidx&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">nidx&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">labels_&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">labels&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure id="figure-example-of-priorityflooding-algorithm-applied-to-gradient-of-inferiorparietal-region--shown-on-inflated-and-flattened-cortical-surfaces">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/watershed-by-flooding-applied-data-structures/WSA_hu_22fbb331c3d719ca.jpg" data-caption="Example of PriorityFlooding algorithm, applied to gradient of inferiorparietal region. Shown on inflated and flattened cortical surfaces.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/watershed-by-flooding-applied-data-structures/WSA_hu_22fbb331c3d719ca.jpg" class="lazyload" alt="" width="1280" height="803">
&lt;/a>
&lt;figcaption>
Example of PriorityFlooding algorithm, applied to gradient of inferiorparietal region. Shown on inflated and flattened cortical surfaces.
&lt;/figcaption>
&lt;/figure>
&lt;p>One caveat that came up is that the &lt;strong>PriorityQueue&lt;/strong> class does not check for duplicates &amp;ndash; that is, two vertices might share an adjacent vertex, and this vertex might have already been added to the heap. In this case, we would unnecessarily view the same vertices many times. To alleviate this, we create a boolean Numpy array, &lt;code>in_queue&lt;/code>, that stores whether an item is already in the queue.&lt;/p></description></item><item><title>Mahalanobis Distances of Brain Connectivity</title><link>https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/</link><pubDate>Fri, 07 Dec 2018 05:12:32 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/</guid><description>&lt;p>For one of the projects I&amp;rsquo;m working on, I have an array of multivariate data relating to brain connectivity patterns. Briefly, each brain is represented as a surface mesh, which we represent as a graph $G = (V,E)$, where $V$ is a set of $n$ vertices, and $E$ is the set of edges between vertices.&lt;/p>
&lt;p>Additionally, for each vertex $v \in V$, we also have an associated scalar &lt;em>label&lt;/em>, which we&amp;rsquo;ll denote $l(v)$, that identifies what region of the cortex each vertex belongs to, the set of regions which we define as $L = {1, 2, &amp;hellip; k}$. And finally, for each vertex $v \in V$, we also have a multivariate feature vector $r(v) \in \mathbb{R}^{1 \times k}$, that describes the strength of connectivity between it, and every region $l \in L$.&lt;/p>
&lt;figure id="figure-example-of-cortical-map-and-array-of-connectivity-features">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/parcellation_hu_ff318333e3f2fe17.png" data-caption="Example of cortical map, and array of connectivity features.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/parcellation_hu_ff318333e3f2fe17.png" class="lazyload" alt="" width="1296" height="432">
&lt;/a>
&lt;figcaption>
Example of cortical map, and array of connectivity features.
&lt;/figcaption>
&lt;/figure>
&lt;p>I&amp;rsquo;m interested in examining how &amp;ldquo;close&amp;rdquo; the connectivity samples of one region, $l_{j}$, are to another region, $l_{k}$. In the univariate case, one way to compare a scalar sample to a distribution is to use the $t$-statistic, which measures how many standard deviations away from the mean a given sample is:&lt;/p>
&lt;p>$$\begin{align}
t_{s} = \frac{\bar{x} - \mu}{\frac{s}{\sqrt{n}}}
\end{align}$$&lt;/p>
&lt;p>where $\mu$ is the population mean, and $s$ is the sample standard deviation. If we square this, we get:&lt;/p>
&lt;p>$$\begin{align}
t^{2} = \frac{(\bar{x} - \mu)^{2}}{\frac{s^{2}}{n}} = \frac{n (\bar{x} - \mu)^{2}}{S^{2}} \sim F(1,n)
\end{align}$$&lt;/p>
&lt;p>We know the last part is true, because the numerator and denominator are independent $\chi^{2}$ distributed random variables. However, I&amp;rsquo;m not working with univariate data &amp;ndash; I have multivariate data. The multivariate generalization of the $t$-statistic is the
&lt;a href="https://en.wikipedia.org/wiki/Mahalanobis_distance" target="_blank" rel="noopener">Mahalanobis Distance&lt;/a>:&lt;/p>
&lt;p>$$\begin{align}
d &amp;amp;= \sqrt{(\bar{x} - \mu)\Sigma^{-1}(\bar{x}-\mu)^{T}}
\end{align}$$&lt;/p>
&lt;p>where the squared Mahalanobis Distance is:&lt;/p>
&lt;p>$$\begin{align}
d^{2} &amp;amp;= (\bar{x} - \mu)\Sigma^{-1}(\bar{x}-\mu)^{T}
\end{align}$$&lt;/p>
&lt;p>where $\Sigma^{-1}$ is the inverse covariance matrix. If our $X$&amp;rsquo;s were initially distributed with a multivariate normal distribution, $N_{p}(\mu,\Sigma)$ (assuming $\Sigma$ is non-degenerate i.e. positive definite), the squared Mahalanobis distance, $d^{2}$ has a $\chi^{2}_{p}$ distribution. We show this below.&lt;/p>
&lt;p>We know that $(X-\mu)$ is distributed $N_{p}(0,\Sigma)$. We also know that, since $\Sigma$ is symmetric and real, that we can compute the eigendecomposition of $\Sigma$ as:&lt;/p>
&lt;p>$$\begin{align}
\Sigma = U \Lambda U^{T}
\end{align}$$&lt;/p>
&lt;p>and consequently, because $U$ is an orthogonal matrix, and because $\Lambda$ is diagonal, we know that $\Sigma^{-1}$ is:&lt;/p>
&lt;p>$$\begin{align}
\Sigma^{-1} &amp;amp;= (U \Lambda U^{T})^{-1} \\
&amp;amp;= U \Lambda^{-1} U^{T} \\
&amp;amp;= (U \Lambda^{\frac{-1}{2}}) (U \Lambda^{\frac{-1}{2}})^{T} \\
&amp;amp;= R R^{T}
\end{align}$$&lt;/p>
&lt;p>Therefore, we know that $R^{T}(X-\mu) \sim N_{p}(0,I_{p})$:&lt;/p>
&lt;p>$$\begin{align}
X &amp;amp;\sim N_{p}(\mu,\Sigma) \\
(X-\mu) = Y &amp;amp;\sim N_{p}(0,\Sigma)\\
R^{T}Y = Z &amp;amp;\sim N_{p}(0, R^{T} \Sigma R) \\
&amp;amp;\sim N_{p}(0, \Lambda^{\frac{-1}{2}} U^{T} (U \Lambda U^{T}) U \Lambda^{\frac{-1}{2}}) \\
&amp;amp;\sim N_{p}(0, \Lambda^{\frac{-1}{2}} I_{p} \Lambda I_{p} \Lambda^{\frac{-1}{2}}) \\
&amp;amp;\sim N_{p}(0,I_{p})
\end{align}$$&lt;/p>
&lt;p>so that we have&lt;/p>
&lt;p>$$\begin{align}
&amp;amp;= (X-\mu)\Sigma^{-1}(X-\mu)^{T} \\
&amp;amp;= (X-\mu)RR^{T}(X-\mu)^{T} \\
&amp;amp;= Z^{T}Z
\end{align}$$&lt;/p>
&lt;p>the sum of $p$ squared standard Normal random variables, which is the definition of a $\chi_{p}^{2}$ distribution with $p$ degrees of freedom. So, given that we start with a $MVN$ random variable, the squared Mahalanobis distance is $\chi^{2}_{p}$ distributed. Because the sample mean and sample covariance are consistent estimators of the population mean and population covariance parameters, we can use these estimates in our computation of the Mahalanobis distance.&lt;/p>
&lt;p>Also, of particular importance is the fact that the Mahalanobis distance is &lt;strong>not symmetric&lt;/strong>. That is to say, if we define the Mahalanobis distance as:&lt;/p>
&lt;p>$$\begin{align}
M(A, B) = \sqrt{(A - \mu(B))\Sigma(B)^{-1}(A-\mu(B))^{T}}
\end{align}$$&lt;/p>
&lt;p>then $M(A,B) \neq M(B,A)$, clearly. Because the parameter estimates are not guaranteed to be the same, it&amp;rsquo;s straightforward to see why this is the case.&lt;/p>
&lt;p>Now, back to the task at hand. For a specified target region, $l_{T}$, with a set of vertices, $V_{T} = {v \; : \; l(v) \; = \; l_{T}, \; \forall \; v \in V}$, each with their own distinct connectivity fingerprints, I want to explore which areas of the cortex have connectivity fingerprints that are different from or similar to $l_{T}$&amp;rsquo;s features, in distribution. I can do this by using the Mahalanobis Distance. And based on the analysis I showed above, we know that the data-generating process of these distances is related to the $\chi_{p}^{2}$ distribution.&lt;/p>
&lt;p>First, I&amp;rsquo;ll estimate the covariance matrix, $\Sigma_{T}$, of our target region, $l_{T}$, using the
&lt;a href="http://perso.ens-lyon.fr/patrick.flandrin/LedoitWolf_JMA2004.pdf" target="_blank" rel="noopener">Ledoit-Wolf estimator&lt;/a> (the shrunken covariance estimate has been shown to be a more reliable estimate of the population covariance), and mean connectivity fingerprint, $\mu_{T}$. Then, I&amp;rsquo;ll compute $d^{2} = M^{2}(A,A)$ for every $\{v: v \in V_{T}\}$. The empirical distribution of these distances should follow a $\chi_{p}^{2}$ distribution. If we wanted to do hypothesis testing, we would use this distribution as our null distribution.&lt;/p>
&lt;p>Next, in order to assess whether this intra-regional similarity is actually informative, I&amp;rsquo;ll also compute the similarity of $l_{T}$ to every other region, $\{ l_{k} \; : \; \forall \; k \in L \setminus \{T\} \}$ &amp;ndash; that is, I&amp;rsquo;ll compute $M^{2}(A, B) \; \forall \; B \in L \setminus T$. If the connectivity samples of our region of interest are as similar to one another as they are to other regions, then $d^{2}$ doesn&amp;rsquo;t really offer us any discriminating information &amp;ndash; I don&amp;rsquo;t expect this to be the case, but we need to verify this.&lt;/p>
&lt;p>Then, as a confirmation step to ensure that our empirical data actually follows the theoretical $\chi_{p}^{2}$ distribution, I&amp;rsquo;ll compute the location and scale
&lt;a href="https://en.wikipedia.org/wiki/Maximum_likelihood_estimation" target="_blank" rel="noopener">Maximum Likelihood&lt;/a>(MLE) parameter estimates of our $d^{2}$ distribution, keeping the &lt;em>d.o.f.&lt;/em> (i.e. $p$) fixed.&lt;/p>
&lt;p>See below for Python code and figures&amp;hellip;&lt;/p>
&lt;h3 id="step-1-compute-parameter-estimates">Step 1: Compute Parameter Estimates&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="o">%&lt;/span>&lt;span class="n">matplotlib&lt;/span> &lt;span class="n">inline&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">matplotlib.pyplot&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">plt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">matplotlib&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">rc&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rc&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;text&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">usetex&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">numpy&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">np&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">scipy.spatial.distance&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">cdist&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">scipy.stats&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">chi2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">probplot&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">from&lt;/span> &lt;span class="nn">sklearn&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">covariance&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># lab_map is a dictionary, mapping label values to sample indices&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># our region of interest has a label of 8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">LT&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mi">8&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># get indices for region LT, and rest of brain&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">lt_indices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">lab_map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LT&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rb_indices&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">concatenate&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">lab_map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">lab_map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span>&lt;span class="p">()&lt;/span> &lt;span class="k">if&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">LT&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data_lt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">conn&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">lt_indices&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data_rb&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">conn&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">rb_indices&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># fit covariance and precision matrices&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Shrinkage factor = 0.2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cov_lt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">covariance&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ShrunkCovariance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">assume_centered&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">shrinkage&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">cov_lt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_lt&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">P&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cov_lt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">precision_&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Next, compute the Mahalanobis Distances:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># LT to LT Mahalanobis Distance&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dist_lt&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cdist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_lt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data_lt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mean&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,:],&lt;/span> &lt;span class="n">metric&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;mahalanobis&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">VI&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">P&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">dist_lt2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">dist_lt&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># fit covariance estimate for every region in cortical map&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">EVs&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="n">covariance&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ShrunkCovariance&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">assume_centered&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">False&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">shrinkage&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">l&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">labels&lt;/span>&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">l&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">lab_map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">EVs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">conn&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">lab_map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">],:])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># compute d^2 from LT to every cortical region&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># save distances in dictionary&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">lt_to_brain&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">{}&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fromkeys&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">labels&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">l&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">lab_map&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">keys&lt;/span>&lt;span class="p">():&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temp_data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">conn&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">label_map&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temp_mu&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">temp_data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">mean&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="kc">None&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temp_mh&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cdist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_lt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">temp_mu&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">metric&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;mahalanobis&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">VI&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">EVs&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">precision_&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">temp_mh2&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">temp_mh&lt;/span>&lt;span class="o">**&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">lt_to_brain&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">temp_mh2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># plot distributions seperate (scales differ)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplots&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">figsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">12&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lt_to_brain&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">LT&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">50&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">density&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">color&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;blue&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">label&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Region-to-Self&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">alpha&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.7&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">l&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">labels&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="n">l&lt;/span> &lt;span class="o">!=&lt;/span> &lt;span class="n">LT&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">lt_to_brain&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">l&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">50&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">density&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">linewidth&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">alpha&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">histtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;step&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure id="figure-empirical-distributions-of-within-region-top-and-between-region-bottom-d2-values--each-line-is-the-distribution-of-the-distance-of-samples-in-our-roi-to-a-whole-region">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/IntraInterMahal_hu_4a20b8ed998a232e.jpg" data-caption="Empirical distributions of within-region (top) and between-region (bottom) $d^{2}$ values. Each line is the distribution of the distance of samples in our ROI to a whole region.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/IntraInterMahal_hu_4a20b8ed998a232e.jpg" class="lazyload" alt="" width="864" height="864">
&lt;/a>
&lt;figcaption>
Empirical distributions of within-region (top) and between-region (bottom) $d^{2}$ values. Each line is the distribution of the distance of samples in our ROI to a whole region.
&lt;/figcaption>
&lt;/figure>
&lt;p>As expected, the distribution of $d^{2}$, the distance of samples in our region of interest, $l_{T}$, to distributions computed from other regions, is (considerably) larger and much more variable, while the profile of points within $l_{T}$ looks to have much smaller variance &amp;ndash; this is good! This means that we have high intra-regional similarity when compared to inter-regional similarities. This fits what&amp;rsquo;s known in neuroscience as the
&lt;a href="https://www.ncbi.nlm.nih.gov/pubmed/9651489" target="_blank" rel="noopener">&amp;ldquo;cortical field hypothesis&amp;rdquo;&lt;/a>.&lt;/p>
&lt;h3 id="step-2-distributional-qc-check">Step 2: Distributional QC-Check&lt;/h3>
&lt;p>Because we know that our data should follow a $\chi^{2}_{p}$ distribution, we can fit the MLE estimate of our location and scale parameters, while keeping the $df$ parameter fixed.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">p&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data_lt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">mle_chi2_theory&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chi2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fit&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">dist_lt2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fdf&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">p&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">xr&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linspace&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_lt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">data_lt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="mi">1000&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">pdf_chi2_theory&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">*&lt;/span>&lt;span class="n">mle_chi2_theory&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">fig&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">figsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">18&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">6&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># plot theoretical vs empirical null distributon&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data_lt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">density&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">color&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;blue&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">alpha&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mf">0.6&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">label&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;Empirical&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">plot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">xr&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">pdf_chi2_theory&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">color&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;red&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">label&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;$\chi^&lt;/span>&lt;span class="si">{2}&lt;/span>&lt;span class="s1">_&lt;/span>&lt;span class="si">{p}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># plot QQ plot of empirical distribution&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">probplot&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">D2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">squeeze&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">sparams&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">mle_chi2_theory&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dist&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">chi2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">plot&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">plt&lt;/span>&lt;span class="p">);&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure id="figure-density-and-qq-plot-of-null-distribution">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/Density.QQPlot_hu_ca5535fcd8e75fcd.png" data-caption="Density and QQ plot of null distribution.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/Density.QQPlot_hu_ca5535fcd8e75fcd.png" class="lazyload" alt="" width="864" height="864">
&lt;/a>
&lt;figcaption>
Density and QQ plot of null distribution.
&lt;/figcaption>
&lt;/figure>
&lt;p>From looking at the QQ plot, we see that the empirical density fits the theoretical density pretty well, but there is some evidence that the empirical density has heavier tails. The heavier tail of the upper quantile could probability be explained by acknowledging that our starting cortical map is not perfect (in fact there is no &amp;ldquo;gold-standard&amp;rdquo; cortical map). Cortical regions do not have discrete cutoffs, although there are reasonably steep
&lt;a href="https://www.ncbi.nlm.nih.gov/pubmed/25316338" target="_blank" rel="noopener">gradients in connectivity&lt;/a>. If we were to include samples that were considerably far away from the the rest of the samples, this would result in inflated densities of higher $d^{2}$ values.&lt;/p>
&lt;p>Likewise, we also made the distributional assumption that our connectivity vectors were multivariate normal &amp;ndash; this might not be true &amp;ndash; in which case our assumption that $d^{2}$ follows a $\chi^{2}_{p}$ would also not hold.&lt;/p>
&lt;p>Finally, let&amp;rsquo;s have a look at some brains! Below, is the region we used as our target &amp;ndash; the connectivity profiles from vertices in this region were used to compute our mean vector and covariance matrix &amp;ndash; we compared the rest of the brain to this region.&lt;/p>
&lt;figure id="figure-region-of-interest">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/Region_LT_hu_ac1dbc85f77489a3.png" data-caption="Region of interest.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/Region_LT_hu_ac1dbc85f77489a3.png" class="lazyload" alt="" width="1440" height="821">
&lt;/a>
&lt;figcaption>
Region of interest.
&lt;/figcaption>
&lt;/figure>
&lt;figure id="figure-estimated-squared-mahalanobis-distances-overlaid-on-cortical-surface">
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/MahalanobisDistance_hu_d2a6bbc02ed1093d.png" data-caption="Estimated squared Mahalanobis distances, overlaid on cortical surface.">
&lt;img data-src="https://kristianeschenburg.netlify.app/post/mahalanobis-distances-of-brain-connectivity/MahalanobisDistance_hu_d2a6bbc02ed1093d.png" class="lazyload" alt="" width="1440" height="821">
&lt;/a>
&lt;figcaption>
Estimated squared Mahalanobis distances, overlaid on cortical surface.
&lt;/figcaption>
&lt;/figure>
&lt;p>Here, larger $d^{2}$ values are in red, and smaller $d^{2}$ are in black. Interestingly, we do see pretty large variance of $d^{2}$ spread across the cortex &amp;ndash; however the values are smoothly varying, but there do exists sharp boundaries. We kind of expected this &amp;ndash; some regions, though geodesically far away, should have similar connectivity profiles if they&amp;rsquo;re connected to the same regions of the cortex. However, the regions with connectivity profiles most different than our target region are not only contiguous (they&amp;rsquo;re not noisy), but follow known anatomical boundaries, as shown by the overlaid boundary map.&lt;/p>
&lt;p>This is interesting stuff &amp;ndash; I&amp;rsquo;d originally intended on just learning more about the Mahalanobis Distance as a measure, and exploring its distributional properties &amp;ndash; but now that I see these results, I think it&amp;rsquo;s definitely worth exploring further!&lt;/p></description></item><item><title>Image Transformations With OpenCV</title><link>https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/</link><pubDate>Sat, 01 Sep 2018 17:12:32 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/</guid><description>&lt;p>I&amp;rsquo;ve been toying around with
&lt;a href="https://opencv.org/" target="_blank" rel="noopener">openCV&lt;/a> for generating MRI images with synethetic motion injected into them. I&amp;rsquo;d never used this library before, so I tested a couple examples. Below I detail a few tools that I found interesting, and that can quickly be used to generate image transformations.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># import necessary libraries&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">matplotlib.pyplot&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">plt&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">nibabel&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="nn">nb&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kn">import&lt;/span> &lt;span class="nn">cv2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># load image file&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">image_file&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s1">&amp;#39;./data/T1w_restore_brain.nii.gz&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">img_obj&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nb&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">load&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">image_file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">img&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">img_obj&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">get_data&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># reorient so Anterior-Posterior axis corresponds to dim(0)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">img&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">fliplr&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">img&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">img&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">swapaxes&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">img&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># get single image slice and rescale&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">img&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">130&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:,&lt;/span> &lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">data&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">min&lt;/span>&lt;span class="p">())&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">max&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">imshow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure >
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/original_hu_217e26846815c09b.jpg" >
&lt;img data-src="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/original_hu_217e26846815c09b.jpg" class="lazyload" alt="" width="432" height="432">
&lt;/a>
&lt;/figure>
&lt;p>For any linear transformations with &lt;code>cv2&lt;/code>, we can use the &lt;code>cv2.warpAffine&lt;/code> method, which takes in the original image, some transformation matrix, and the size of the output image.&lt;/p>
&lt;p>Let&amp;rsquo;s start with translations. The matrix will translate the image 10 pixels to the right (width), and 0 pixels down (height).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Use the identity rotation matrix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># Third column specifies translation in corresponding direction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">translation&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">array&lt;/span>&lt;span class="p">([[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">20&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">]])&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">translated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cv2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warpAffine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">translation&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">imshow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">translated&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure >
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/translated_hu_2a216d917bfa49a0.jpg" >
&lt;img data-src="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/translated_hu_2a216d917bfa49a0.jpg" class="lazyload" alt="" width="432" height="432">
&lt;/a>
&lt;/figure>
&lt;p>Now, in order to rotate the image, we can use &lt;code>cv2.getRotationMatrix2D&lt;/code>. We&amp;rsquo;ll rotate our image by 45$^{\circ}$ .&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># get shape of input image&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rows&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cols&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># specify angle of rotation around central pixel&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">M&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cv2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getRotationMatrix2D&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="n">cols&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">rows&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="mi">45&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rotated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cv2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warpAffine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">M&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">cols&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">rows&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">imshow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rotated&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;figure >
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/rotated_hu_34be7ee45860ab04.jpg" >
&lt;img data-src="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/rotated_hu_34be7ee45860ab04.jpg" class="lazyload" alt="" width="432" height="432">
&lt;/a>
&lt;/figure>
&lt;p>Here are a few examples of randomly translating +/- 1, 5, or 9 voxels in the X and Y directions, and randomly rotating by 1, 5, or 9 degrees:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># get shape of input image&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">rows&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cols&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># specify range of rotations and translations&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">txfn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">9&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">for&lt;/span> &lt;span class="n">rt&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">txfn&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># generate rotation matrix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># randonly rotate to left or right&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">M&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cv2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">getRotationMatrix2D&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="n">cols&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">rows&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choice&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">rt&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># apply rotation matrix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">rotated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cv2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warpAffine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">M&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># generate translation matrix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># randomly translate to left or right&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">T&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">array&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choice&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">rt&lt;/span>&lt;span class="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">random&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">choice&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">rt&lt;/span>&lt;span class="p">]])&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">astype&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">float32&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># apply translation matrix&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">translated&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cv2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">warpAffine&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">data&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">T&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">T&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># compose rotated and translated images&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">movement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">rotated&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">translated&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">/&lt;/span>&lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># compute difference between input and transformed&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">difference&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">data&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">movement&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">res&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">difference&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reshape&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">np&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">product&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">difference&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">fig&lt;/span>&lt;span class="p">,[&lt;/span>&lt;span class="n">ax1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">ax2&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">ax3&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">subplots&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">figsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">5&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ax1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">imshow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">movement&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cmap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;gray&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ax1&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_title&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Composed Random Rotation and Translation &lt;/span>&lt;span class="se">\n&lt;/span>&lt;span class="s1"> Magnitude = &lt;/span>&lt;span class="si">{:}&lt;/span>&lt;span class="s1">&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rt&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">fontsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ax2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">imshow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">D&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="n">rotated&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cmap&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;gray&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ax2&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_title&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Difference Map&amp;#39;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">format&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">rt&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">fontsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ax3&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">hist&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">res&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="n">res&lt;/span>&lt;span class="o">!=&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">],&lt;/span>&lt;span class="mi">100&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">density&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="kc">True&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ax3&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">set_title&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Difference Density&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">fontsize&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">15&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">tight_layout&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">plt&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">show&lt;/span>&lt;span class="p">()&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>
&lt;figure >
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/Composed.1_hu_1f304674ba06f616.jpg" >
&lt;img data-src="https://kristianeschenburg.netlify.app/post/image-transformations-with-opencv/Composed.1_hu_1f304674ba06f616.jpg" class="lazyload" alt="" width="1080" height="360">
&lt;/a>
&lt;/figure>
&lt;figure >
&lt;a data-fancybox="" href="Composed.5.jpg" >
&lt;img src="Composed.5.jpg" alt="" >
&lt;/a>
&lt;/figure>
&lt;figure >
&lt;a data-fancybox="" href="Composed.9.jpg" >
&lt;img src="Composed.9.jpg" alt="" >
&lt;/a>
&lt;/figure>
&lt;/p>
&lt;p>While this approach of generating synthetic motion into MRI images is a poor model of how motion actually occurs during an MRI scan, there are a few things I learned here. For example, if you define a measure of image similarity, like mutual information, entropy, or correlation ratio as a cost function, we can see how we can use &lt;code>warpAffine&lt;/code> to find the optimal transformation matrix between two images.&lt;/p>
&lt;p>I was hoping to use openCV to generate and apply 3d affine transformations to volumetric MRI data. One approach to doing this is to iteratively apply rotations and transformations along each axis &amp;ndash; however, openCV will interpolate the data after each transformation, resulting in a greater loss of signal than I am willing to compromise on. It doesn&amp;rsquo;t seem like openCV has ability to apply 3d affine transformations to volumetric data in a single interpolation step.&lt;/p>
&lt;p>A more realistic approach to generating synthetic motion artifacts that would more accurately parallell the noise-generating process, is to compute the
&lt;a href="https://en.wikipedia.org/wiki/Fast_Fourier_transform" target="_blank" rel="noopener">Fast Fourier Transform&lt;/a> of my 3d volume, and then apply phase-shifts to the
&lt;a href="https://en.wikipedia.org/wiki/K-space_%28magnetic_resonance_imaging%29" target="_blank" rel="noopener">k-space&lt;/a> signal &amp;ndash; this will also manifest as motion after applying the inverse FFT.&lt;/p>
&lt;p>After doing a bit more digging through the openCV API, it seems there&amp;rsquo;s a lot of cool material for exploration &amp;ndash; these applications specifically caught my eye and would be fun to include in projects:&lt;/p>
&lt;ul>
&lt;li>
&lt;a href="https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_video/py_table_of_contents_video/py_table_of_contents_video.html#py-table-of-content-video" target="_blank" rel="noopener">video analysis&lt;/a> for motion tracking&lt;/li>
&lt;li>
&lt;a href="https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html#face-detection" target="_blank" rel="noopener">object recognition&lt;/a> for detecting faces&lt;/li>
&lt;li>
&lt;a href="https://opencv.org/platforms/android/" target="_blank" rel="noopener">openCV Android&lt;/a> for app development&lt;/li>
&lt;/ul>
&lt;p>But alas &amp;ndash; the search continues!&lt;/p></description></item><item><title>Two Weeks of Open Science: A Rekindled Flame</title><link>https://kristianeschenburg.netlify.app/post/two-weeks-of-open-science/</link><pubDate>Fri, 17 Aug 2018 01:12:33 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/two-weeks-of-open-science/</guid><description>&lt;p>I recently attended
&lt;a href="http://neurohackademy.org/" target="_blank" rel="noopener">Neurohackademy 2018&lt;/a>, hosted by the University of Washington&amp;rsquo;s
&lt;a href="https://escience.washington.edu/" target="_blank" rel="noopener">eScience Institute&lt;/a>, and organized by Dr. Ariel Rokem and Dr. Tal Yarkoni.&lt;/p>
&lt;p>This was a 2-week long event, beginning with a series of daily lectures, and ending with a fast-paced, high-intensity scramble to put together a beta (but working) version of some project coupling neuroimaging with software development. The lectures varied in topic, from how to test academic code and how to organize open source projects, to machine learning and algorithms for low-dimensional representations of neural recordings, to neuroethics (full lecture list
&lt;a href="https://neurohackademy.org/neurohack_year/2018/" target="_blank" rel="noopener">here&lt;/a>).&lt;/p>
&lt;p>Many of the lecturers are scientists and developers who I&amp;rsquo;ve looked up to for years &amp;ndash; a few have even been my intellectual, and now post-Neurohackademy, philosophical role models. While the lectures were enlightening in their own right, there was a level of intimacy during these 2 weeks that&amp;rsquo;s been unmatched during grad school so far. Rarely do young researchers like myself get to pick the brains of and engage in scientific banter with scientists whose papers they read, or whose updates they follow, in such a fluid and collaborative setting. I feel a little weird being so enthusiastic about it (specifically because I know some of them might read this), but (and I think I speak for all of us who participated) it was a richly rewarding experience.
&lt;br/>&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="https://kristianeschenburg.netlify.app/post/two-weeks-of-open-science/Couch_RickMorty_hu_c93f50d8d9bfe8df.png" >
&lt;img data-src="https://kristianeschenburg.netlify.app/post/two-weeks-of-open-science/Couch_RickMorty_hu_c93f50d8d9bfe8df.png" class="lazyload" alt="" width="950" height="534">
&lt;/a>
&lt;/figure>
&lt;p>The
&lt;a href="https://twitter.com/search?q=%23nh18&amp;amp;src=tyah" target="_blank" rel="noopener">#NH18&lt;/a> participants varied in status from graduate students, to post-docs, to industry members, and traveled from all around the world to Seattle, but each one of us was, in one way or another, involved with neuroscience research. As one of my new friends put it on Twitter:
&lt;a href="https://twitter.com/rxxqx/status/1027238653662093313" target="_blank" rel="noopener">&amp;ldquo;Spent yesterday in a room full of relative strangers who were collaborating, mentoring, &amp;amp; supporting. Devoid of egos or tribalism. Feels like what science (&amp;amp; society) should be.&amp;rdquo;&lt;/a> The sense of community was strong, positivity was plentiful, and people supported one another &amp;ndash; without regard for experience or any sense of &lt;em>return-on-investment&lt;/em>. We had an established &lt;strong>Git person&lt;/strong>, &lt;strong>Python person&lt;/strong>, &lt;strong>data viz person&lt;/strong>, &lt;strong>fMRI person&lt;/strong>, &lt;strong>C++ person&lt;/strong>, etc. &amp;ndash; if you had a question or ran into an issue, with high probability there was someone who could and would help you out. Everyone was genuinely excited to learn, to share, to create, to bond, and especially, &lt;em>&lt;del>to neuro/computer/data-science&lt;/del>&lt;/em>.&lt;/p>
&lt;p>&amp;mdash; &lt;strong>to science&lt;/strong> &amp;mdash;&lt;/p>
&lt;ul>
&lt;li>(origin: probably Newton)&lt;/li>
&lt;li>&lt;em>verb&lt;/em>. To perform scientific research, almost always in a smooth or cool way.&lt;/li>
&lt;/ul>
&lt;p>I worked on a project directly related to my research, but that I&amp;rsquo;d only previously written some messy, non-shareable scripts for. Conveniently, my co-NeuroHacker
&lt;a href="https://twitter.com/miyka_el" target="_blank" rel="noopener">Michael Notter&lt;/a> had a similar idea and we hit the ground running with some of our colleagues. The project, titled
&lt;a href="https://kristianeschenburg.github.io/parcellation_fragmenter/" target="_blank" rel="noopener">parcellation_fragmenter&lt;/a>, provides a means for fragmenting the brain cortex into a predefined number of regions, or a set of regions each of the same size. These regions can be anatomically constrained, or arbitrarily spread across the cortex. Our goal was to use this tool to speed up statistical tests, like
&lt;a href="https://www.ncbi.nlm.nih.gov/pubmed/17825583" target="_blank" rel="noopener">SearchLight FDR&lt;/a>, or as a feature extraction method for down-stream machine learning applications. I&amp;rsquo;m currently using this tool to examine how cortical network resolution impacts pairwise regional network properties.&lt;/p>
&lt;blockquote class="twitter-tweet tw-align-center" data-lang="en" display="block" margin-left="auto" margin-right="auto">&lt;p lang="en" dir="ltr">Here is something pretty! It was created with the new parcellation fragmenter (&lt;a href="https://t.co/9VLCpr336Y">https://t.co/9VLCpr336Y&lt;/a>), developed by Kristian Eschenburg, &lt;a href="https://twitter.com/kako_toro?ref_src=twsrc%5Etfw">@kako_toro&lt;/a>, Amanda Sidwell &amp;amp; me during the &lt;a href="https://twitter.com/hashtag/NHW18?src=hash&amp;amp;ref_src=twsrc%5Etfw">#NHW18&lt;/a>. Thank&amp;#39;s to &lt;a href="https://twitter.com/hashtag/nilearn?src=hash&amp;amp;ref_src=twsrc%5Etfw">#nilearn&lt;/a> &amp;amp; &lt;a href="https://twitter.com/hashtag/nibabel?src=hash&amp;amp;ref_src=twsrc%5Etfw">#nibabel&lt;/a> creating this toolbox was straightforward and a lot of fun! &lt;a href="https://t.co/LdqTCMSyrJ">pic.twitter.com/LdqTCMSyrJ&lt;/a>&lt;/p>&amp;mdash; Michael Notter (@miyka_el) &lt;a href="https://twitter.com/miyka_el/status/1028027334245285889?ref_src=twsrc%5Etfw">August 10, 2018&lt;/a>&lt;/blockquote>
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8">&lt;/script>
&lt;p>My most important takeaway from our project was learning how to collaboratively write and develop software with a group of people. Not only did each of our team members have unique ideas about how to approach our specific problem, we also each had different ways of thinking about &lt;em>how to write software in general&lt;/em>. Clear communication, open-mindedness, and understanding on all of our parts were integral to seeing this development through. Overall, the project was a success!&lt;/p>
&lt;p>Here are a few things I learned, that I&amp;rsquo;m going to incorporate into my own work (and hopefully convince people in my lab to do the same):&lt;/p>
&lt;ul>
&lt;li>Unit-test my code using &lt;code>pytest&lt;/code> and &lt;code>nose&lt;/code>&lt;/li>
&lt;li>Incorporate continuous integration (I&amp;rsquo;ve already made use of
&lt;a href="https://kristianeschenburg.netlify.app/post/enabling-custom-jekyll-plugins/">TravisCI&lt;/a>!)&lt;/li>
&lt;li>Learn web-dev, and specifically, JavaScript (to use D3, and develop interactive posters and publications)&lt;/li>
&lt;li>Contribute to issues / create pull-requests on GitHub repos that I use or find interesting&lt;/li>
&lt;li>Pre-register my papers and submit to open-source journals&lt;/li>
&lt;/ul>
&lt;p>This event was what I&amp;rsquo;d hoped graduate school would be like all along. While idealistic and naive to some degree, I still think it can be. The open-source model is shifting how research is performed &amp;ndash; the act of doing research is evolving in such a way that it is no longer tethered to specific institutions or labs, and given tools like Docker and AWS, you can almost perfectly recreate specific computing environments needed to perform the work. With the rise of open-source datasets, especially due to researchers willingly distributing their data and code, collaborative environments like that fostered by Neurohackademy (even if digital), and the ability to replicate workflows, results, and analyses, are becoming more and more feasible. It only takes a few proponents of the open-source model to give the idea momentum.&lt;/p>
&lt;p>If this is the future, the future is looking good.&lt;/p></description></item><item><title>Exploring Neurological Dynamical Systems: Part 2</title><link>https://kristianeschenburg.netlify.app/post/exploring-neurological-dynamical-systems-part-2/</link><pubDate>Thu, 24 May 2018 11:30:43 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/exploring-neurological-dynamical-systems-part-2/</guid><description>&lt;p>In my previous post on
&lt;a href="https://kristianeschenburg.netlify.app/post/exploring-neurological-dynamical-systems-part-1/">dynamic mode decomposition&lt;/a>, I discussed the foundations of DMD as a means for linearizing a dynamical system&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>. In this post, I want to look at a way in which we can use rank-updates to incorporate new information into the spectral decomposition of our linear operator, $A$, in the event that we are generating online measurements from our dynamical system&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup> &amp;ndash; see the citation below if you want a more-detailed overview of this topic along with open source code for testing this method.&lt;/p>
&lt;p>Recall that we are given an initial data matrix&lt;/p>
&lt;p>$$\begin{align}
X = \begin{bmatrix}
x_{n_{1},m_{1}} &amp;amp; x_{n_{1},m_{2}} &amp;amp; x_{n_{1},m_{3}} &amp;amp; &amp;hellip; \\
x_{n_{2},m_{1}} &amp;amp; x_{n_{1},m_{2}} &amp;amp; x_{n_{2},m_{3}} &amp;amp; &amp;hellip; \\
x_{n_{3},m_{1}} &amp;amp; x_{n_{1},m_{2}} &amp;amp; x_{n_{3},m_{3}} &amp;amp; &amp;hellip; \\
&amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; \\
\end{bmatrix}
\in R^{n \times m}
\end{align}$$&lt;/p>
&lt;p>which we can split into two matrices, shifted one unit in time apart:&lt;/p>
&lt;p>$$\begin{align}
X^{\ast} &amp;amp;=
\begin{bmatrix}
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\vec{x}_1 &amp;amp; \vec{x}_2 &amp;amp; \dots &amp;amp; \vec{x}_{m-1} \\
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\end{bmatrix} \in R^{n \times (m-1)} \\
Y &amp;amp;= \begin{bmatrix}
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\vec{x}_2 &amp;amp; \vec{x}_3 &amp;amp; \dots &amp;amp; \vec{x}_{m} \\
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\end{bmatrix} \in R^{n \times (m-1)}
\end{align}$$&lt;/p>
&lt;p>and we are interested in solving for the linear operator $A$, such that&lt;/p>
&lt;p>$$\begin{align}
Y = AX^{\ast}
\end{align}$$&lt;/p>
&lt;p>For simplicity, since we are no longer using the full matrix, I&amp;rsquo;ll just refer to $X^{\ast}$ as $X$. In the previous post, we made the constraint that $n &amp;gt; m$, and that rank($X$) $\leq m &amp;lt; n$. Here, however, we&amp;rsquo;ll reverse this assumption, such that $m &amp;gt; n$, and that rank($X$) $\leq m &amp;lt; n$, such that $XX^{T}$ is invertible, so by multiplying both sides by $X^{T}$ we have&lt;/p>
&lt;p>$$\begin{align}
AXX^{T} &amp;amp;= YX^{T} \\
A &amp;amp;= YX^{T}(XX^{T})^{-1} \\
A &amp;amp;= QP_{x}
\end{align}$$&lt;/p>
&lt;p>where $Q = YX^{T}$ and $P_{x} = (XX^{T})^{-1}$. Now, let&amp;rsquo;s say you observe some new data $x_{m+1}, y_{m+1}$, and you want to incorporate this new data into your $A$ matrix. As in the previous post on
&lt;a href="https://kristianeschenburg.netlify.app/post/rank-one-updates/">rank-one updates&lt;/a>, we saw that directly computing the inverse could potentially be costly, so we want to refrain from doing that if possible. Instead, we&amp;rsquo;ll use the Sherman-Morrison-Woodbury theorem again to incorporate our new $x_{m+1}$ sample into our inverse matrix, just as before:&lt;/p>
&lt;p>$$\begin{align}
(X_{m+1}X^{T}&lt;em>{m+1})^{-1} = P&lt;/em>{x} + \frac{P_{x}x_{m+1}x_{m+1}^{T}P_{x}}{1 + x_{m+1}^{T}P_{x}x_{m+1}}
\end{align}$$&lt;/p>
&lt;p>Likewise, since we&amp;rsquo;re appending new data to our $Y$ and $X$ matrices, we also have&lt;/p>
&lt;p>$$\begin{align}
Y_{m+1} = \begin{bmatrix}
Y &amp;amp; y_{m+1} \end{bmatrix} \\
X_{m+1} = \begin{bmatrix} \\
X &amp;amp; x_{m+1} \end{bmatrix} \\
\end{align}$$&lt;/p>
&lt;p>such that&lt;/p>
&lt;p>$$\begin{align}
Y_{m+1} X_{m+1}^{T} &amp;amp;= YX^{T} + y_{m+1}x_{m+1}^{T} \\
&amp;amp;= Q + y_{m+1}x_{m+1}^{T}
\end{align}$$&lt;/p>
&lt;p>which is simply the sum of our original matrix $Q$, plus a rank-one matrix. The authors go on to describe some pretty cool &amp;ldquo;local&amp;rdquo; DMD schemes, by incorporating weights, as well as binary thresholds, that are time-dependent into the computation of the linear operator, $A$.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>P.J. Schmid.
&lt;a href="https://hal-polytechnique.archives-ouvertes.fr/file/index/docid/1020654/filename/DMS0022112010001217a.pdf" target="_blank" rel="noopener">Dynamic mode decomposition of numerical and experimental data&lt;/a>. Journal of Fluid Mechanics 656.1. 2010.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Tu et al.
&lt;a href="http://cwrowley.princeton.edu/papers/Tu-DMD.pdf" target="_blank" rel="noopener">On Dynamic Mode Decomposition: Theory And Applications&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>Kunert-Graf et al.
&lt;a href="https://www.frontiersin.org/articles/10.3389/fncom.2019.00075/full" target="_blank" rel="noopener">Extracting Reproducible Time-Resolved Resting State Networks Using Dynamic Mode Decomposition&lt;/a>. Front. Comput. Neurosci. 2019.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>Zhang et al.
&lt;a href="https://arxiv.org/abs/1707.02876" target="_blank" rel="noopener">Online dynamic mode decomposition for time-varying systems&lt;/a>. 2017.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item><item><title>Exploring Neurological Dynamical Systems: Part 1</title><link>https://kristianeschenburg.netlify.app/post/exploring-neurological-dynamical-systems-part-1/</link><pubDate>Tue, 22 May 2018 14:02:52 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/exploring-neurological-dynamical-systems-part-1/</guid><description>&lt;p>In the next two posts, I want to talk briefly about an algorithm called Dynamic Mode Decomposition (DMD). DMD is a spatiotemporal modal decomposition technique that can be used to identify spatial patterns in a signal (modes), along with the time course of these spatial patterns (dynamics). As such, the algorithm assumes that the input data has both a spatial and a temporal component. We are interested in modeling &lt;em>how&lt;/em> the system evolves over time.&lt;/p>
&lt;p>If you&amp;rsquo;d like to find more information about DMD, Peter Schmid&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> and Jonathan Tu&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> have written excellent expositions on the topic. Likewise, if you&amp;rsquo;d like to follow along with the code for the following analysis, see
&lt;a href="https://github.com/kristianeschenburg/dmd" target="_blank" rel="noopener">my repo&lt;/a>. For a more in-depth analysis that applies DMD to brain activity in the resting brain, see a recent publication by my colleagues and me&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>, along with the
&lt;a href="https://github.com/kunert/DMD_RSN" target="_blank" rel="noopener">code&lt;/a> used for our analysis.&lt;/p>
&lt;h2 id="the-dmd-algorithm">The DMD Algorithm&lt;/h2>
&lt;p>Let&amp;rsquo;s assume that you&amp;rsquo;ve taken $n$ measurements from specific points in space for $m$ time points, where for now we assume that $m\lt n$. For now, we&amp;rsquo;ll assume that the sampling frequency, $\omega$, is stable across the entire experiment. We define our entire data matrix as&lt;/p>
&lt;p>$$\begin{align}
X = \begin{bmatrix}
x_{n_{1},m_{1}} &amp;amp; x_{n_{1},m_{2}} &amp;amp; x_{n_{1},m_{3}} &amp;amp; &amp;hellip; \\
x_{n_{2},m_{1}} &amp;amp; x_{n_{1},m_{2}} &amp;amp; x_{n_{2},m_{3}} &amp;amp; &amp;hellip; \\
x_{n_{3},m_{1}} &amp;amp; x_{n_{1},m_{2}} &amp;amp; x_{n_{3},m_{3}} &amp;amp; &amp;hellip; \\
&amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; &amp;amp; &amp;hellip; \\
\end{bmatrix}
\in R^{n \times m}
\end{align}$$&lt;/p>
&lt;p>We are interested in solving for the matrix, $A \in R^{n \times n}$, such that&lt;/p>
&lt;p>$$\begin{align}
x_{t+1} = A x_{t} \; \; \forall \; \; t = 1,2,&amp;hellip;m-1
\end{align}$$&lt;/p>
&lt;p>Given our full data matrix $X$, we can define two matrices $X^{*}$ and $Y$ such that&lt;/p>
&lt;p>$$\begin{align}
X^{*} &amp;amp;= \begin{bmatrix}
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\vec{x}_{1} &amp;amp; \vec{x}_{2} &amp;amp; \dots &amp;amp; \vec{x}_{m-1} \\
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\end{bmatrix} \in R^{n \times (m-1)} \\
Y &amp;amp;= \begin{bmatrix}
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\vec{x}_2 &amp;amp; \vec{x}_3 &amp;amp; \dots &amp;amp; \vec{x}_{m} \\
\vert &amp;amp; \vert &amp;amp; \dots &amp;amp; \vert \\
\end{bmatrix} \in R^{n \times (m-1)}
\end{align}$$&lt;/p>
&lt;p>so that we can write&lt;/p>
&lt;p>$$\begin{align}
Y = AX^{\ast}
\end{align}$$&lt;/p>
&lt;p>If $n$ is small, this is relatively easy to compute &amp;ndash; however, if $n$ is large, as is the case when modeling temporal dynamics in resting-state MRI, it would be computationally inefficient to compute A directly. To alleviate this, we can make use of the Singular Value Decomposition (SVD) of our predictor matrix $X^{\ast}$. We define the SVD of $X^{\ast}$ as&lt;/p>
&lt;p>$$\begin{align}
X^{\ast} = U \Sigma V^{T} \
\end{align}$$&lt;/p>
&lt;p>as well as the Moore-Penrose pseudo-inverse of $X^{\ast} = X^{\dagger}$ as&lt;/p>
&lt;p>$$\begin{align}
X^{\dagger} = V \Sigma^{-1} U^{T} \\
\end{align}$$&lt;/p>
&lt;p>such that we can write&lt;/p>
&lt;p>$$\begin{align}
YX^{\dagger} = YV \Sigma^{-1} U^{T} = A X^{\ast}X^{\dagger} = A \\
\end{align}$$&lt;/p>
&lt;p>Additionally, if we assume that $rank(X^{\ast}) = r \leq m$, then we can use the truncated SVD such that&lt;/p>
&lt;p>$$\begin{align}
U &amp;amp; \in R^{n \times r} \\
V^{T} &amp;amp; \in R^{r \times m} \\
\end{align}$$&lt;/p>
&lt;p>and&lt;/p>
&lt;p>$$\begin{align}
\Sigma = \begin{bmatrix}
\sigma_{1} &amp;amp; 0 &amp;amp; 0 &amp;amp; &amp;hellip; \\
0 &amp;amp; \sigma_{2} &amp;amp; 0 &amp;amp; &amp;hellip; \\
0 &amp;amp; 0 &amp;amp; \ddots &amp;amp; &amp;hellip; \\
\vdots &amp;amp; \vdots &amp;amp; \vdots &amp;amp; \sigma_{r} \\
\end{bmatrix} \in R^{r \times r}
\end{align}$$&lt;/p>
&lt;p>As it stands now, we still compute an $A \in R^{n \times n}$ matrix. However, because we have a potentially low-rank system, we can apply a Similarity Transformation to $A$ in order to reduce its dimensionality, without changing its spectrum. Using our spatial singular vectors $U$, we define&lt;/p>
&lt;p>$$\begin{align}
\tilde{A} &amp;amp;= U^{T} A U \\
&amp;amp;= U^{T} (YV \Sigma^{-1} U^{T}) U \\
&amp;amp;= U^{T} Y V \Sigma^{-1} \\
\end{align}$$&lt;/p>
&lt;p>where $\tilde{A} \in R^{r \times r}$. If we consider the above SVD, we see that $U$ is the matrix of left singular vectors, an orthogonal basis that spans $C(X^{\ast})$, which is an r-dimensional subspace of $R^{n}$. Thus, the similarity transform represents a mapping $f(A) = U^{T} A U : R^{n} \rightarrow R^{r}$. We now have a reduced-dimensional representation of our linear operator, from which we can compute the spatial modes and dynamic behavior of each mode. First, however, because of the notion of variance captured by the singular values of our original predictor matrix, we weight $\tilde{A}$ by the singular values as&lt;/p>
&lt;p>$$\begin{align}
\hat{A} = \Sigma^{\frac{1}{2}} \tilde{A} \Sigma^{\frac{1}{2}} \\
\end{align}$$&lt;/p>
&lt;p>such that our computed spatial modes have been weighted by the amount they contribute to our measured signal. We can now compute the eigendecomposition of $\hat{A}$ as&lt;/p>
&lt;p>$$\begin{align}
\hat{A} W = W \Lambda \\
\end{align}$$&lt;/p>
&lt;p>where the eigenvectors $W$ are the reduced-dimension representations of our spatial modes, and the eigenvalues $\Lambda$ capture the dynamic behavior of our spatial modes. Because our original data matrix $X^{\ast}$ had spatial dimension $n$ and our eigenvectors have dimension $r$, we need to up-project our eigenvectors $W$ to compute the final spatial modes, via&lt;/p>
&lt;p>$$\begin{align}
\Phi = Y V \Sigma^{\frac{-1}{2}}W
\end{align}$$&lt;/p>
&lt;p>From the SVD of our prediction matrix $X^\ast=U \Sigma V^{T}$, the matrix $V \in R^{m \times r}$ is the matrix of right singular vectors, an orthogonal basis spanning the space of $X^{\ast T}$ (i.e. $r$ basis vectors spanning the space of the measured time courses). Thus, we see that $H = (V \Sigma^{\frac{-1}{2}})W$ represents a linear combination of the temporal basis vectors (a mapping from $R^{r} \rightarrow R^{m}$) for each eigenvector $w_{i}$ of $W$, weighted by the corresponding singular value $\sigma_{i}^{\frac{-1}{2}}$ (that acts to normalize the spatial mode amplitudes). Finally, we see that $\Phi = X^{\ast}H$ computes how much of each temporal basis vector is present in the measured time course at each point in space.&lt;/p>
&lt;p>Because we are modeling a dynamical system, we can compute the continuous time dynamics of our system using our spatial modes and eigenvalues as&lt;/p>
&lt;p>$$\begin{align}
\vec{x}(t) \approx \sum_{i=1}^{r} b_{i}\exp^{((\gamma_{i} + 2i\pi f_{i})\cdot t)} \vec{\phi}_{i}
\end{align}$$&lt;/p>
&lt;p>where $\gamma_{i}$ is a growth-decay constant and $f_{i}$ is the frequency of oscillation of the spatial mode $\phi_{i}$. We can compute these two constants as&lt;/p>
&lt;p>$$\begin{align}
\gamma_{i} &amp;amp;= \frac{\text{real}(\text{ln}(\lambda_{i}))}{\Delta t} \\
f_{i} &amp;amp;= \frac{\text{imag}(\text{ln}(\lambda_{i}))}{2\pi \Delta t}
\end{align}$$&lt;/p>
&lt;p>So, we can see that DMD linearizes our measured time series, by fitting what can be analogized to a &amp;ldquo;global&amp;rdquo; regression. That is, instead of computing how a single time point predicts the next time point, which could readily be solved using the simple &lt;strong>Normal equations&lt;/strong>, DMD computes how a matrix of time points predicts another matrix of time points that is shifted one unit of time into the future. To this extent, DMD minimizes the Frobenius norm of&lt;/p>
&lt;p>$$\begin{align}
\min \limits_{A} \lVert Y - AX^{\ast} \rVert^{2}_{F} \\
\end{align}$$&lt;/p>
&lt;p>However, rather than explicitly computing the matrix $A$, DMD computes the eigenvectors and eigenvalues of $A$, by utilizing the &lt;strong>Singular Value Decomposition&lt;/strong>, along with a &lt;strong>Similarity Transformation&lt;/strong>, in order to generate a reduced-dimensional representation of $A$.&lt;/p>
&lt;p>This spectral decomposition of our linear operator is of particular importance, because it sheds light on the fact that DMD models the temporal dynamics of our system using a &lt;strong>Fourier basis&lt;/strong>. Each spatial mode is represented by a particular Fourier frequency along with a growth-decay constant that determines the future behavior of our spatial mode. Additionally, the Fourier basis also determines what sorts of time series can be modeled using DMD &amp;ndash; time series that are expected to have sinusoidal behavior will be more reliably modeled using DMD, whereas signals that show abrupt spike patterns might be more difficult to model.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>P.J. Schmid.
&lt;a href="https://hal-polytechnique.archives-ouvertes.fr/file/index/docid/1020654/filename/DMS0022112010001217a.pdf" target="_blank" rel="noopener">Dynamic mode decomposition of numerical and experimental data&lt;/a>. Journal of Fluid Mechanics 656.1. 2010.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>Tu et al.
&lt;a href="http://cwrowley.princeton.edu/papers/Tu-DMD.pdf" target="_blank" rel="noopener">On Dynamic Mode Decomposition: Theory And Applications&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>Kunert-Graf et al.
&lt;a href="https://www.frontiersin.org/articles/10.3389/fncom.2019.00075/full" target="_blank" rel="noopener">Extracting Reproducible Time-Resolved Resting State Networks Using Dynamic Mode Decomposition&lt;/a>. Front. Comput. Neurosci. 2019.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item></channel></rss>