<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Neuroimaging |</title><link>https://kristianeschenburg.netlify.app/tag/neuroimaging/</link><atom:link href="https://kristianeschenburg.netlify.app/tag/neuroimaging/index.xml" rel="self" type="application/rss+xml"/><description>Neuroimaging</description><generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Sun, 07 Jun 2020 10:20:09 -0700</lastBuildDate><image><url>https://kristianeschenburg.netlify.app/img/Bayes.jpg</url><title>Neuroimaging</title><link>https://kristianeschenburg.netlify.app/tag/neuroimaging/</link></image><item><title>Lab Meeting: pip and the Python Packaging Index</title><link>https://kristianeschenburg.netlify.app/post/pyni-packages/</link><pubDate>Sun, 07 Jun 2020 10:20:09 -0700</pubDate><guid>https://kristianeschenburg.netlify.app/post/pyni-packages/</guid><description>&lt;p>What follows are the contents of part of a lab meeting presentation I gave recently. The topic of the meeting was &amp;ldquo;Python for Neuroimaging&amp;rdquo;, where I covered basic software development tools that brain imaging scientists might be interested in.&lt;/p>
&lt;h1 id="creating-python-packages">Creating Python Packages&lt;/h1>
&lt;p>In this lesson, I&amp;rsquo;ll show you how to build your own Python package that you can then install locally or upload to the
&lt;a href="https://pip.pypa.io/en/stable/" target="_blank" rel="noopener">Python Packaging Index&lt;/a> (for those of you familiar with
&lt;a href="https://www.r-project.org/about.html" target="_blank" rel="noopener">R&lt;/a>, think
&lt;a href="https://cran.r-project.org/" target="_blank" rel="noopener">CRAN&lt;/a>, but for Python).&lt;/p>
&lt;p>I&amp;rsquo;m going to be basing a lot of the material off of this
&lt;a href="https://packaging.python.org/tutorials/packaging-projects/" target="_blank" rel="noopener">documentation&lt;/a>, but will also show a real example using some of my own personal code.&lt;/p>
&lt;h2 id="what-are-packages">What are packages?&lt;/h2>
&lt;p>I&amp;rsquo;m sure most of you are familiar with packages and libraries already, either from Matlab, R, or Python. Packages are basically bundles of various snippets of code, i.e. &lt;strong>methods&lt;/strong>, &lt;strong>classes&lt;/strong>, &lt;strong>scripts&lt;/strong>, &lt;strong>tests&lt;/strong> etc. that are bundled together to perform some function. Generally (hopefully), there is coherence to what these snippets of code do &amp;ndash; they should interact together in some way or relate to some overarching computational goal.&lt;/p>
&lt;p>Within a package, you can have different groupings of code, where each grouping does some unique or discrete computing. These groupings are called &lt;strong>submodules&lt;/strong>. A common submodule in many packages is an &lt;strong>Input / Output (io)&lt;/strong> module that will read and write data that this package interacts with or produces. Another common submodule is often related to &lt;strong>plotting&lt;/strong> the outputs of your code. And then almost always, there are submodules that perform the brunt of the algorithmic work. So inside modules, you&amp;rsquo;ll find snippets of code that relate to the goal or concept of the module.&lt;/p>
&lt;p>Think of a package as a &lt;em>toolbox&lt;/em> with a bunch of drawers, each with a label: &lt;em>wood-working&lt;/em>, &lt;em>welding&lt;/em>, &lt;em>gardening&lt;/em>, &lt;em>flooring&lt;/em>, etc. These drawers are submodules. You can tell by their names that they each cover certain topics. Each drawer contains a set of tools: &lt;em>wood-working&lt;/em> might contain &lt;em>saw&lt;/em>, &lt;em>nail&lt;/em>, &lt;em>sandpaper&lt;/em>, &lt;em>wood glue&lt;/em>, while &lt;em>welding&lt;/em> might contain &lt;em>solder&lt;/em>, &lt;em>flux&lt;/em>, &lt;em>oxygen&lt;/em>, &lt;em>glove&lt;/em>. These tools are the functions, classes, and scripts that relate to that submodule.&lt;/p>
&lt;p>Overall, this toolbox performs some stuff related to construction, homebuilding, repair, and has discrete bundles of code useful for a variety of those tasks.&lt;/p>
&lt;h3 id="directory-structure-for-a-python-package">Directory structure for a Python package&lt;/h3>
&lt;p>Here we examine the skeleton of a package. All packages follow this basic structure.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pkg_name
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- LICENSE
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- pkg_name/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- submodule_a/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- a_1.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- submodule_b/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- __init__.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- b_1.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- b_2.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- README.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- setup.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span>-- test/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">|&lt;/span> &lt;span class="p">|&lt;/span>-- __init__.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>__init__.py&lt;/code> is a required file that allows your package to be imported. The only &lt;code>__init__.py&lt;/code> file that needs to contain anything is the highest-level file. The others can be empty, but they must exist. Here are the contents of the highest-level &lt;code>__init__.py&lt;/code> file:&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">__all__&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;a_1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b_1&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;b_2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&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">from&lt;/span> &lt;span class="nn">.submodule_a&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">a_1&lt;/span>&lt;span class="p">)&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">.submodule_b&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">b_1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b_2&lt;/span>&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>LICENSE&lt;/code> tells other users / individuals in what capacity they are allowed to use your code.&lt;/p>
&lt;p>&lt;code>README.md&lt;/code> describes how to use your code, and often contains examples. This is a &lt;strong>markdown&lt;/strong> file, but can generally be any type of &lt;strong>markup&lt;/strong> language.&lt;/p>
&lt;p>&lt;code>test/&lt;/code> is a directory in which you would want to write
&lt;a href="http://softwaretestingfundamentals.com/unit-testing/" target="_blank" rel="noopener">unit tests&lt;/a> for your code.&lt;/p>
&lt;p>&lt;code>setup.py&lt;/code> is what allows you to install your package. It&amp;rsquo;s a set of instructions that get supplied to
&lt;a href="https://setuptools.readthedocs.io/en/latest/" target="_blank" rel="noopener">setuptools&lt;/a> package.&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="kn">from&lt;/span> &lt;span class="nn">setuptools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">find_packages&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;README.md&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s2">&amp;#34;r&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">fh&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">long_description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">fh&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&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">setup&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;pkg_name&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">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;0.1.0&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">author&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;Kristian M. Eschenburg&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">author_email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;keschenb@uw.edu&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">packages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">find_packages&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">scripts&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;https://github.com/kristianeschenburg/pkg_name&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">license&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;LICENSE.txt&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">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;An awesome package that does something&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">long_description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">long_description&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">install_requires&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;numpy&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;pytest&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;matplotlib&amp;#34;&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="compiling-installing-and-uploading-your-package">Compiling, installing, and uploading your package&lt;/h3>
&lt;p>&lt;strong>1. Register on PyPi&lt;/strong>&lt;/p>
&lt;p>Once we&amp;rsquo;ve done all this, we&amp;rsquo;re just about ready to create our Python package and upload it to
&lt;a href="https://pypi.org/" target="_blank" rel="noopener">pypi.org&lt;/a>. But first, we need to create an account. For testing purposes, we&amp;rsquo;ll create a test account
&lt;a href="https://test.pypi.org/" target="_blank" rel="noopener">here&lt;/a>, but the process is the same.&lt;/p>
&lt;p>After you create your account, we need to create an API token, that will allow us to upload files to either
&lt;a href="https://test.pypi.org/" target="_blank" rel="noopener">Test PyPi&lt;/a> or
&lt;a href="https://pypi.org/" target="_blank" rel="noopener">PyPi&lt;/a> (depending on what we&amp;rsquo;re doing) &amp;ndash; the following steps are the same, regardless.&lt;/p>
&lt;p>Under your Test PyPi account, click your username in the top right, go to &lt;code>Account Settings&lt;/code>:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/PyPi_Account.png" >
&lt;img src="notebook_figures/packages/PyPi_Account.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>Scroll down and click &lt;code>Add API Token&lt;/code>:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/PyPi_Token.png" >
&lt;img src="notebook_figures/packages/PyPi_Token.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>Follow the instructions there, making sure to select &amp;ldquo;Entire Account&amp;rdquo; option under the &lt;code>Scope&lt;/code> tab.&lt;/p>
&lt;p>&lt;strong>DO NOT CLOSE THIS WINDOW WHEN THIS IS COMPLETE&lt;/strong>&lt;/p>
&lt;p>Next, type&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nb">cd&lt;/span> &lt;span class="nv">$HOME&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">touch .pypirc&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>and using your favorite text editor, enter the following:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-txt" data-lang="txt">&lt;span class="line">&lt;span class="cl">[testpypi]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repository: https://test.pypi.org/legacy/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> username = __token__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password = pypi-***&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If we were creating a token for PyPi, we&amp;rsquo;d type:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-txt" data-lang="txt">&lt;span class="line">&lt;span class="cl">[pypi]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> repository: https://pypi.org/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> username = __token__
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> password = pypi-***&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Close your current terminal, and open a new window to refresh your settings. Now, when we go to upload our package to PyPi, we&amp;rsquo;ll be able to type the commands without needed to supply a username and password directly.&lt;/p>
&lt;p>&lt;strong>2. Compile your package&lt;/strong>&lt;/p>
&lt;p>First, we need to make sure that a few Python packages are installed. Namely, we need to install
&lt;a href="https://pip.pypa.io/en/stable/" target="_blank" rel="noopener">pip&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">python get-pip.py
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install -U pip&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then we can install the following packages:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install --upgrade pip setuptools wheel &lt;span class="c1"># for installing Python packages&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install tqdm &lt;span class="c1"># progress bar package&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">pip install --user --upgrade twine &lt;span class="c1"># for publishing to PyPi&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Then, we can compile our package:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python setup.py bdist_wheel&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>which creates the directories &lt;code>dist&lt;/code>, &lt;code>build&lt;/code>, and &lt;code>pkg_name.egg-info&lt;/code>. The &lt;code>*.egg-info&lt;/code> file is basically some zipped meta-data about your package, but we&amp;rsquo;re really only interested in the &lt;code>*.whl&lt;/code> file in &lt;code>dist&lt;/code> &amp;ndash; &amp;ldquo;wheels&amp;rdquo; are a &amp;ldquo;distribution&amp;rdquo; format, newly designed to replace &amp;ldquo;eggs&amp;rdquo;. I won&amp;rsquo;t go into it here, but &lt;strong>eggs&lt;/strong> were sort an &lt;em>ad hoc&lt;/em> solution to packaging Python code &amp;ndash; &lt;strong>wheels&lt;/strong> were part of
&lt;a href="https://www.python.org/dev/peps/pep-0427/" target="_blank" rel="noopener">PEP427&lt;/a> i.e. is actually an &amp;ldquo;enhancement&amp;rdquo; to the Python language, and the formal way of packaging Python code.&lt;/p>
&lt;p>&lt;strong>3. Installing your code&lt;/strong>&lt;/p>
&lt;p>We can install our code locally with:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install dist/pkg_name-0.0.0-py3-none-any.whl&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>If you want to install an &amp;ldquo;editable&amp;rdquo; version of your package, do this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install -e .&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This will allow you to change your &lt;code>*.py&lt;/code> files and have these changes take effect immediately when importing your package, without needing to rebuild each time &amp;ndash; but this method installs from the &lt;strong>egg&lt;/strong> distribution, and generally produces larger build files, since the build needs to keep track of your actual source code.&lt;/p>
&lt;p>&lt;strong>4. Upload your code&lt;/strong>&lt;/p>
&lt;p>We can upload our code to PyPi now using the following command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python3 -m twine upload --repository testpypi dist/*&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now, if you click &amp;ldquo;Your Projects&amp;rdquo; under your account name on PyPi, you&amp;rsquo;ll see that you project has been uploaded.&lt;/p>
&lt;p>** I should note that, any time you want to upgrade your code and upload it to PyPi again, you need to remove all files from the &lt;code>dist&lt;/code> directory, increment the &lt;code>version&lt;/code> number in the &lt;code>setup.py&lt;/code> file &amp;ndash; i.e. 0.0.0 &amp;ndash;&amp;gt; 0.0.1 &amp;ndash; rebuild your package with&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">bash&lt;/span> &lt;span class="n">python&lt;/span> &lt;span class="n">setup&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">py&lt;/span> &lt;span class="n">bdist_wheel&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="example-with-personal-package">Example with personal package&lt;/h3>
&lt;p>I don&amp;rsquo;t generally upload my code to PyPi (probably scared bugs in the code, and people finding them, and then thinking I&amp;rsquo;m terrible at software development, and going down a long spiral of self-deprecation, but I digress&amp;hellip;) but I do upload it all to GitHub. In either case, here is a walk-through of packaging some software called &lt;code>pysurface&lt;/code> that I use for processing mesh-based data &amp;ndash; I use it for adjacency matrices, performing Laplacian smoothing on surfaces, sampling points from mesh triangle simplices, plotting on surfaces&amp;hellip; Just some stuff that I find myself doing a lot.&lt;/p>
&lt;p>Here is the directory containing all my code:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/pysurface_code.png" >
&lt;img src="notebook_figures/packages/pysurface_code.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>You&amp;rsquo;ll see 5 different modules: &lt;code>graphs&lt;/code>, &lt;code>operations&lt;/code>, &lt;code>plotting&lt;/code>, &lt;code>spectra&lt;/code>, and &lt;code>utilities&lt;/code>, and you&amp;rsquo;ll note that each module directory has a &lt;code>__init__.py&lt;/code> file.&lt;/p>
&lt;p>Here is my &lt;code>setup.py&lt;/code> file:&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="kn">from&lt;/span> &lt;span class="nn">os&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">path&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">setuptools&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">setup&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">find_packages&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">sys&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">here&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">abspath&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dirname&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="vm">__file__&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;README.rst&amp;#39;&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">encoding&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;utf-8&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">readme_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">readme&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">readme_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&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">with&lt;/span> &lt;span class="nb">open&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">path&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">here&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="s1">&amp;#39;requirements.txt&amp;#39;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="k">as&lt;/span> &lt;span class="n">requirements_file&lt;/span>&lt;span class="p">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Parse requirements.txt, ignoring any commented-out lines.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">requirements&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">line&lt;/span> &lt;span class="k">for&lt;/span> &lt;span class="n">line&lt;/span> &lt;span class="ow">in&lt;/span> &lt;span class="n">requirements_file&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">read&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">splitlines&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">line&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">startswith&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;#&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>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">setup&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">name&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;pysurface&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">version&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;0.0.4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Python package for quickly processing surface meshes.&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">long_description&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">readme&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">author&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;Kristian Eschenburg&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">author_email&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;keschenb@uw.edu&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">url&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s1">&amp;#39;https://github.com/kristianeschenburg/pysurface&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">packages&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">find_packages&lt;/span>&lt;span class="p">(),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">entry_points&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;console_scripts&amp;#39;&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="c1"># &amp;#39;some.module:some_function&amp;#39;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">include_package_data&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">package_data&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;pysurface&amp;#39;&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="c1"># When adding files here, remember to update MANIFEST.in as well,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># or else they will not be included in the distribution on PyPI!&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># &amp;#39;path/to/data_file&amp;#39;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">install_requires&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">requirements&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">license&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s2">&amp;#34;BSD (3-clause)&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">classifiers&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="p">[&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s1">&amp;#39;Development Status :: 2 - Pre-Alpha&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="s1">&amp;#39;Natural Language :: English&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="s1">&amp;#39;Programming Language :: Python :: 3&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="p">],&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can see that I&amp;rsquo;ve run&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">python setup.py bdist_wheel&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>based off the &lt;code>dist&lt;/code>, &lt;code>build&lt;/code>, and &lt;code>pysurface.egg-info&lt;/code> directories. &lt;code>dist&lt;/code> contains a file called &lt;code>pysurface-0.0.4-py3-none.any.whl&lt;/code>, which is the actual distribution that can be used for installation. I&amp;rsquo;ve uploaded the code to Test PyPi, and this is what we see:&lt;/p>
&lt;figure >
&lt;a data-fancybox="" href="notebook_figures/packages/PyPi_Project.png" >
&lt;img src="notebook_figures/packages/PyPi_Project.png" alt="" >
&lt;/a>
&lt;/figure>
&lt;p>We can then install the package and all of it&amp;rsquo;s dependencies from TestPypi via&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">pip install --index-url https://test.pypi.org/simple/ pysurface&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Now I can do something like the following in a Python script:&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="kn">import&lt;/span> &lt;span class="nn">pysurface&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># or&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">pysurface&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">graphs&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">spectra&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1"># or&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">pysurface.spectra&lt;/span> &lt;span class="kn">import&lt;/span> &lt;span class="n">eigenspectrum&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></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></channel></rss>