<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>William Broderick's Blog</title><link href="https://wfbroderick.com/" rel="alternate"/><link href="https://wfbroderick.com/feeds/all.atom.xml" rel="self"/><id>https://wfbroderick.com/</id><updated>2022-08-01T00:00:00-04:00</updated><entry><title>snakemake, singularity, and HPC</title><link href="https://wfbroderick.com/snakemake-singularity-and-hpc.html" rel="alternate"/><published>2022-08-01T00:00:00-04:00</published><updated>2022-08-01T00:00:00-04:00</updated><author><name>William F. Broderick</name></author><id>tag:wfbroderick.com,2022-08-01:/snakemake-singularity-and-hpc.html</id><summary type="html">&lt;p&gt;This is a bit of a sequel to my &lt;a href="https://wfbroderick.com/conda-snakemake-and-hpc.html"&gt;post last May&lt;/a&gt; about
using conda environments with snakemake on the HPC. That solution worked
for me, but was hacky and limited. This post will describe a different
solution, which uses a singularity container containing a conda
environment that can be …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This is a bit of a sequel to my &lt;a href="https://wfbroderick.com/conda-snakemake-and-hpc.html"&gt;post last May&lt;/a&gt; about
using conda environments with snakemake on the HPC. That solution worked
for me, but was hacky and limited. This post will describe a different
solution, which uses a singularity container containing a conda
environment that can be used by snakemake for local use or use with
SLURM, and has the ability to mount extra dependencies.&lt;/p&gt;
&lt;p&gt;I put this together for my &lt;a href="https://github.com/billbrod/spatial-frequency-preferences"&gt;spatial frequency
preferences&lt;/a&gt;
project, and you can read that repo's README for more details on how to
use it. The following post will attempt to describe the different
components of it and why they work, which will hopefully make it easier
to modify for other uses. The following description is all for NYU's
&lt;code&gt;greene&lt;/code&gt; cluster (which uses &lt;code&gt;SLURM&lt;/code&gt;), but can hopefully be modified to
other clusters and job submission systems.&lt;/p&gt;
&lt;p&gt;My goal was to make it easier to rerun my analyses, allowing other
people to make use of snakemake and the cluster to run them quickly,
without requiring lots of setup and understanding of snakemake, SLURM,
or singularity. To do that, I wanted to create a single container with a
wrapper script that would be able to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Run locally.&lt;/li&gt;
&lt;li&gt;Run in an interactive session on &lt;code&gt;greene&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use snakemake to handle job submission on &lt;code&gt;greene&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mount paths for additional requirements that cannot be easily
    included in the container.&lt;/li&gt;
&lt;li&gt;Be backed up for long-term archival.&lt;/li&gt;
&lt;li&gt;Would not require the user to modify any source code themselves.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In order to do accomplish the first three goals, I needed to have the
container and script work with both docker and singularity, as docker
does not play nicely with computing clusters (since it wants to run
everything with &lt;code&gt;sudo&lt;/code&gt; permissions) and singularity is difficult for the
typical user to set up on their personal machine.&lt;/p&gt;
&lt;p&gt;The fourth goal was necessary because some of the requirements for
re-running my full analysis have non open-source licenses (Matlab
requires the user to pay for a license, and FSL and Freesurfer both
require registration), which makes including them in a container
difficult (with Matlab, at least, I could compile the required code into
&lt;code&gt;mex&lt;/code&gt; files and include those, but I've never done that before, I didn't
write the required Matlab code (and so don't understand perfectly) and
from preliminary research and discussion with NYU HPC staff, I don't
think it's as straightforward as I had originally hoped).&lt;/p&gt;
&lt;p&gt;The fifth goal is necessary because Docker hub makes no promises about
its images staying around forever (and, &lt;a href="https://www.docker.com/blog/docker-hub-image-retention-policy-delayed-and-subscription-updates/"&gt;in
particular,&lt;/a&gt;
is planning on deleting the images from free Docker accounts after six
months of inactivity), so I can't rely on users always being able to
find it there.&lt;/p&gt;
&lt;p&gt;In order to accomplish the above, we need to create a container that
contains all the dependencies we can and allows for mounting the ones we
can't include. We will create a wrapper script, in python, which handles
a lot of boilerplate, mounting the required paths and remapping
arguments that get passed to snakemake so that it uses the correct paths
for within the container. We also want this wrapper script to work with
the default python 3 libraries, since any additional packages will only
be available within the container. Finally, we need to configure
snakemake so that it uses the container on jobs that it submits to the
cluster.&lt;/p&gt;
&lt;p&gt;With all that in mind, let's walk through the files that implement this
solution. It involves four files from my
&lt;a href="https://github.com/billbrod/spatial-frequency-preferences"&gt;spatial-frequency-preferences&lt;/a&gt;
repo (&lt;code&gt;build_docker&lt;/code&gt;, &lt;code&gt;singularity_env.sh&lt;/code&gt;, &lt;code&gt;run_singularity.py&lt;/code&gt;, and
&lt;code&gt;config.json&lt;/code&gt;), plus the &lt;a href="https://github.com/billbrod/snakemake-slurm/tree/singularity"&gt;singularity
branch&lt;/a&gt; of
my slurm snakemake profile.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;build_docker&lt;/code&gt;:&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# This is a Dockerfile, but we use a non-default name to prevent mybinder from&lt;/span&gt;
&lt;span class="c"&gt;# using it. we also don&amp;#39;t expect people to build the docker image themselves, so&lt;/span&gt;
&lt;span class="c"&gt;# hopefully it shouldn&amp;#39;t trip people up.&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mambaorg/micromamba&lt;/span&gt;

&lt;span class="c"&gt;# git is necessary for one of the packages we install via pip, gcc is required&lt;/span&gt;
&lt;span class="c"&gt;# to install another one of those packages, and we need to be root to use apt&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;update
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;apt&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;gcc

&lt;span class="c"&gt;# switch back to the default user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;micromamba&lt;/span&gt;

&lt;span class="c"&gt;# create the directory we&amp;#39;ll put our dependencies in.&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/
&lt;span class="c"&gt;# copy over the conda environment yml file&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./environment.yml&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/sfp-environment.yml

&lt;span class="c"&gt;# install the required python packages and remove unnecessary files.&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;micromamba&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;base&lt;span class="w"&gt; &lt;/span&gt;-y&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/sfp-environment.yml&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;micromamba&lt;span class="w"&gt; &lt;/span&gt;clean&lt;span class="w"&gt; &lt;/span&gt;--all&lt;span class="w"&gt; &lt;/span&gt;--yes

&lt;span class="c"&gt;# get the specific commit of the MRI_tools repo that we need&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/WinawerLab/MRI_tools.git&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/MRI_tools&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/MRI_tools&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;checkout&lt;span class="w"&gt; &lt;/span&gt;8508652bd9e6b5d843d70be0910da413bbee432e
&lt;span class="c"&gt;# get the matlab toolboxes that we need&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/cvnlab/GLMdenoise.git&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/GLMdenoise
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;https://github.com/vistalab/vistasoft.git&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/vistasoft

&lt;span class="c"&gt;# get the slurm snakemake profile, the singularity branch&lt;/span&gt;
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/.config/snakemake
&lt;span class="k"&gt;RUN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;-b&lt;span class="w"&gt; &lt;/span&gt;singularity&lt;span class="w"&gt; &lt;/span&gt;https://github.com/billbrod/snakemake-slurm.git&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/.config/snakemake/slurm
&lt;span class="c"&gt;# copy over the env.sh file, which sets environmental variables&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./singularity_env.sh&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/singularity_env.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a standard Dockerfile (we use a different name because we also
want to be able to launch a &lt;a href="https://mybinder.org/"&gt;Binder&lt;/a&gt; instance
from this repo, and Binder defaults to using &lt;code&gt;Dockerfile&lt;/code&gt; if present),
which builds on the &lt;code&gt;mamba&lt;/code&gt; container
(&lt;a href="https://github.com/mamba-org/mamba"&gt;mamba&lt;/a&gt; is a drop-in replacement
for conda that, in my experience, solves the environment much quicker).
The main thing it does is install the conda environment used by my
analysis. It also grabs the other git repos that are required: the
&lt;code&gt;MRI_tools&lt;/code&gt; repo (used for pre-processing the fMRI data), two matlab
packages, and the singularity branch of my slurm snakemake profile.&lt;/p&gt;
&lt;p&gt;To then get this image onto the cluster, you'll also need to push it to
DockerHub, so build and push it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;build&lt;span class="w"&gt; &lt;/span&gt;--tag&lt;span class="o"&gt;=&lt;/span&gt;billbrod/sfp:latest&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;build_docker&lt;span class="w"&gt;  &lt;/span&gt;./
sudo&lt;span class="w"&gt; &lt;/span&gt;docker&lt;span class="w"&gt; &lt;/span&gt;push&lt;span class="w"&gt;  &lt;/span&gt;billbrod/sfp:latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;&lt;code&gt;singularity_env.sh&lt;/code&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="c1"&gt;# set up environment variables for other libraries, add them to path&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FREESURFER_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/sfp_user/freesurfer
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$FREESURFER_HOME&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;

&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/sfp_user/matlab/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;

&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FSLOUTPUTTYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NIFTI_GZ
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;FSLDIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/home/sfp_user/fsl
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$FSLDIR&lt;/span&gt;/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;

&lt;span class="c1"&gt;# modify the config.json file so it points to the location of MRI_tools,&lt;/span&gt;
&lt;span class="c1"&gt;# GLMDenoise, and Vistasoft within the container&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/spatial-frequency-preferences/config.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/spatial-frequency-preferences/config.json&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/sfp_config.json
&lt;span class="w"&gt;    &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s|&amp;quot;MRI_TOOLS&amp;quot;:.*|&amp;quot;MRI_TOOLS&amp;quot;: &amp;quot;/home/sfp_user/MRI_tools&amp;quot;,|g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/sfp_config.json
&lt;span class="w"&gt;    &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s|&amp;quot;GLMDENOISE_PATH&amp;quot;:.*|&amp;quot;GLMDENOISE_PATH&amp;quot;: &amp;quot;/home/sfp_user/GLMdenoise&amp;quot;,|g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/sfp_config.json
&lt;span class="w"&gt;    &lt;/span&gt;sed&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s|&amp;quot;VISTASOFT_PATH&amp;quot;:.*|&amp;quot;VISTASOFT_PATH&amp;quot;: &amp;quot;/home/sfp_user/vistasoft&amp;quot;,|g&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/home/sfp_user/sfp_config.json
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This file gets copied into the container and will get sourced as soon as
the container is started up (see the &lt;code&gt;run_singularity.py&lt;/code&gt; section below
for how we do this). It sets up environmental variables for the extra
dependencies and adds them to path, as well as modifying the
&lt;code&gt;config.json&lt;/code&gt; path to point where those packages are located within the
container. Note that these software packages (matlab, FSL, and
Freesurfer) are not included in the container, but because of how we've
set up the &lt;code&gt;run_singularity.py&lt;/code&gt; script, we know where they'll be
mounted.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;config.json&lt;/code&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;DATA_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/scratch/wfb229/sfp&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;WORKING_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/scratch/wfb229/preprocess&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;MATLAB_PATH&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/share/apps/matlab/2020b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;FREESURFER_HOME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/share/apps/freesurfer/6.0.0&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;FSLDIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/share/apps/fsl/5.0.10&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;MRI_TOOLS&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/billbrod/Documents/MRI_tools&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;GLMDENOISE_PATH&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/billbrod/Documents/MATLAB/toolboxes/GLMdenoise&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;VISTASOFT_PATH&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/home/billbrod/Documents/MATLAB/toolboxes/vistasoft&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;TESLA_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/mnt/Tesla/spatial_frequency_preferences&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;EXTRA_FILES_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/mnt/winawerlab/Projects/spatial_frequency_preferences/extra_files&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;SUBJECTS_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/mnt/winawerlab/Freesurfer_subjects&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;RETINOTOPY_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/mnt/winawerlab/Projects/Retinotopy/BIDS&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Snakemake allows for a configuration file, either yml or json, which we
use to specify a variety of paths. We use json here, even though it
doesn't allow for comments, because it can be parsed by the standard
python library. These paths should all be set to locations on &lt;strong&gt;your&lt;/strong&gt;
machine / the cluster (not within the container). The above is an
example that works for my user on the NYU greene cluster.&lt;/p&gt;
&lt;p&gt;When using the container, only the first five paths need to be set (from
&lt;code&gt;DATA_DIR&lt;/code&gt; to &lt;code&gt;FSLDIR&lt;/code&gt;; the final ones are used either when running
without the container or when copying data into a BIDS-compliant
format). &lt;code&gt;DATA_DIR&lt;/code&gt; gives the location of the data set and where we'll
place the output of the analysis and &lt;code&gt;WORKING_DIR&lt;/code&gt; is a working
directory for preprocessing and is only used temporarily in that step.
The next three are the root directory of the installations for matlab,
Freesurfer, and FSL: to find their locations, make sure they're on your
path (if you're on a cluster, this is probably by using &lt;code&gt;module load&lt;/code&gt;)
and then run e.g., &lt;code&gt;which matlab&lt;/code&gt; (or &lt;code&gt;which mri_convert&lt;/code&gt;, etc.) to find
where they're installed. Note that we want the root directory of the
install (not the &lt;code&gt;bin/&lt;/code&gt; folder containing the binary executables so that
if &lt;code&gt;which matlab&lt;/code&gt; returns &lt;code&gt;/share/apps/matlab/2020b/bin/matlab&lt;/code&gt;, we just
want &lt;code&gt;/share/apps/matlab/2020b&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Of all the files needed for this process, this is the only one that
requires modification by the user, and my
&lt;code&gt;spatial-frequency-preferences&lt;/code&gt; readme includes a long description of
what the different fields are, which need to be set, etc.&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;run_singularity.py&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;This is the main script that the user will run, which I generally refer
to as the "wrapper script". If everything is working, the user will
simply pass the command they wish to run to this script and it will bind
the various directories, set environmental variables, and properly
construct the arguments for singularity or docker, whichever the user
wishes to use.&lt;/p&gt;
&lt;p&gt;Because this script is so much larger than the rest, we'll step through
it section by section, and I'll include the full script at the end.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os.path&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;op&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;glob&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First, we specify this script must be run with python 3 and import the
necessary python modules.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# slurm-related paths. change these if your slurm is set up differently or you&lt;/span&gt;
&lt;span class="c1"&gt;# use a different job submission system. see docs&lt;/span&gt;
&lt;span class="c1"&gt;# https://sylabs.io/guides/3.7/user-guide/appendix.html#singularity-s-environment-variables&lt;/span&gt;
&lt;span class="c1"&gt;# for full description of each of these environmental variables&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_BINDPATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_BINDPATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,/opt/slurm,/usr/lib64/libmunge.so.2.0.0,/usr/lib64/libmunge.so.2,/var/run/munge,/etc/passwd&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITYENV_PREPEND_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITYENV_PREPEND_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:/opt/slurm/bin&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_CONTAINLIBS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_CONTAINLIBS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/opt/slurm/lib64/libpmi*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next several lines handle some slurm-related boilerplate. We take
some singularity-related environmental variables and add on paths
related to the slurm configuration so that we'll have access to the
slurm commands (e.g., &lt;code&gt;sbatch&lt;/code&gt;) from within the container. These will
likely vary across SLURM clusters and so will need to be modified if
using this on any cluster other than NYU's greene.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_singularity_envvars&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Make sure SINGULARITY_BINDPATH, SINGULARITY_PREPEND_PATH, and SINGULARITY_CONTAINLIBS only contain existing paths&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_BINDPATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SINGULARITYENV_PREPEND_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SINGULARITY_CONTAINLIBS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;joiner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;SINGULARITYENV_PREPEND_PATH&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joiner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joiner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_bind_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Check that paths we want to bind exist, return only those that do.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vol&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;vol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The next two functions make sure we only pass through existing paths to
the container, either via the environmental variables discussed above,
or via the user-specified bind paths. By excluding non-existing
directories from the bind paths, we allow the user to ignore any options
they don't want to set, so that they can ignore mounting any directories
containing software they don't need.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Run sfp singularity container!&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    image : str&lt;/span&gt;
&lt;span class="sd"&gt;        If running with singularity, the path to the .sif file containing the&lt;/span&gt;
&lt;span class="sd"&gt;        singularity image. If running with docker, name of the docker image.&lt;/span&gt;
&lt;span class="sd"&gt;    args : list, optional&lt;/span&gt;
&lt;span class="sd"&gt;        command to pass to the container. If empty (default), we open up an&lt;/span&gt;
&lt;span class="sd"&gt;        interactive session.&lt;/span&gt;
&lt;span class="sd"&gt;    software : {&amp;#39;singularity&amp;#39;, &amp;#39;docker&amp;#39;}, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Whether to run image with singularity or docker&lt;/span&gt;
&lt;span class="sd"&gt;    sudo : bool, optional&lt;/span&gt;
&lt;span class="sd"&gt;        If True, we run docker with `sudo`. If software==&amp;#39;singularity&amp;#39;, we&lt;/span&gt;
&lt;span class="sd"&gt;        ignore this.&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;main()&lt;/code&gt; function accepts the same arguments as the command-line
script, which we'll discuss at the end.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;check_singularity_envvars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/spatial-frequency-preferences&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MATLAB_PATH&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/matlab&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FREESURFER_HOME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/freesurfer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FSLDIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/fsl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DATA_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DATA_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;WORKING_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;WORKING_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_bind_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# join puts --bind between each of the volumes, we also need it in the&lt;/span&gt;
&lt;span class="c1"&gt;# beginning&lt;/span&gt;
&lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--bind &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; --bind &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;First, &lt;code&gt;main()&lt;/code&gt; takes care of the paths and environmental variables. We
check the singularity-related environmental variables, as discussed
above, then load in the config.json file, also discussed earlier. We use
the user-supplied values here to set up the arguments for the &lt;code&gt;--bind&lt;/code&gt;
flag (equivalent to docker's &lt;code&gt;--volume&lt;/code&gt; flag). Note that the
software-related paths (the ones containing the project repo, matlab,
freesurfer, and FSL) are all remapped to static paths within
&lt;code&gt;/home/sfp_user/&lt;/code&gt;, while the data-related paths (&lt;code&gt;DATA_DIR&lt;/code&gt; and
&lt;code&gt;WORKING_DIR&lt;/code&gt;) are left unchanged. The data paths are unchanged because
there are too many references to them in how snakemake structures the
commands for me to comprehensively remap, so we just preserve them.&lt;/p&gt;
&lt;p&gt;We then check that all the paths that we'll be binding exist and remove
any that don't with the &lt;code&gt;check_bind_paths()&lt;/code&gt; function, discussed above.
Finally, we combine that list of volumes into a string to pass through
to singularity, which will be formatted like &lt;code&gt;--bind VOL1 --bind VOL2
...&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# if the user is passing a snakemake command, need to pass&lt;/span&gt;
&lt;span class="c1"&gt;# --configfile /home/sfp_user/sfp_config.json, since we modify the config&lt;/span&gt;
&lt;span class="c1"&gt;# file when we source singularity_env.sh&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--configfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/sfp_config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;-d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;-s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences/Snakefile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;args&lt;/code&gt; specifies what the user wants to do with the container, and there
are four possibilities:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;args&lt;/code&gt; is empty, in which case we open up an interactive session.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;args&lt;/code&gt; is a list of strings, and the first string is &lt;code&gt;snakemake&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;args&lt;/code&gt; is a single string, and starts with &lt;code&gt;snakemake&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;args&lt;/code&gt; is either a single string or a list of strings, and doesn't
    contain &lt;code&gt;snakemake&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The above section handles possibility \#2. In this case, we know that
the arguments contains no flags for snakemake (in that case, &lt;code&gt;args&lt;/code&gt;
would have to be quoted and thus we'd be in possibility \#3). We
therefore modify the command to be run from inside the container, adding
the specification of the config file, which we modified with
&lt;code&gt;singularity_env.sh&lt;/code&gt;, and specifying the paths to both the working
directory and the Snakefile (these last two mean that we can run the
snakemake command from other paths within the container). Finally, we
append the rest of the user-specified arguments.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# in this case they passed a string so args[0] contains snakemake and then&lt;/span&gt;
&lt;span class="c1"&gt;# a bunch of other stuff&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--configfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/sfp_config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;-d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;-s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences/Snakefile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;
    &lt;span class="c1"&gt;# if the user specifies --profile slurm, replace it with the&lt;/span&gt;
    &lt;span class="c1"&gt;# appropriate path. We know it will be in the last one of args and&lt;/span&gt;
    &lt;span class="c1"&gt;# nested below the above elif because if they specified --profile then&lt;/span&gt;
    &lt;span class="c1"&gt;# the whole thing had to be wrapped in quotes, which would lead to this&lt;/span&gt;
    &lt;span class="c1"&gt;# case.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--profile slurm&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile slurm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="s1"&gt;&amp;#39;--profile /home/sfp_user/.config/snakemake/slurm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# then need to make sure to mount this&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--profile&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;profile_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile (.*?) &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;profile_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/.config/snakemake/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile /home/sfp_user/.config/snakemake/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This block corresponds to possibility \#3. The beginning is very
similar to \#2 explained above, except that we include the rest of
&lt;code&gt;args[0]&lt;/code&gt;, after having removed the word &lt;code&gt;snakemake&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The following sections either remap the snakemake profile from &lt;code&gt;slurm&lt;/code&gt;
to the slurm profile within the container, or find the specified path of
the snakemake profile and add it to the list of volumes to mount within
the container. Note that this will fail if the user just specifies the
name of the profile, rather than giving the full path. This could be
made more general by checking whether the &lt;code&gt;profile_path&lt;/code&gt; variable
grabbed with regex looks like a path, and, if not, searching the
locations that snakemake searches for profiles (notably,
&lt;code&gt;~/.config/snakemake&lt;/code&gt;), but that seemed like a lot of work.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# open up an interactive session if the user hasn&amp;#39;t specified an argument,&lt;/span&gt;
&lt;span class="c1"&gt;# otherwise pass the argument to bash. regardless, make sure we source the&lt;/span&gt;
&lt;span class="c1"&gt;# env.sh file&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/bin/bash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--init-file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/singularity_env.sh&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This block corresponds to situation \#1: no arguments passed. In this
case, we simply open up an interactive bash session, sourcing
&lt;code&gt;singularity_env.sh&lt;/code&gt; before we start.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/bin/bash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;# this needs to be done with single quotes on the inside so&lt;/span&gt;
            &lt;span class="c1"&gt;# that&amp;#39;s what bash sees, otherwise we run into&lt;/span&gt;
            &lt;span class="c1"&gt;# https://stackoverflow.com/questions/45577411/export-variable-within-bin-bash-c;&lt;/span&gt;
            &lt;span class="c1"&gt;# double-quoted commands get evaluated in the *current* shell,&lt;/span&gt;
            &lt;span class="c1"&gt;# not by /bin/bash -c&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;#39;source /home/sfp_user/singularity_env.sh; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This block handles situations 2 through 4: we pass the arguments through
to the bash interpreter, making sure to source &lt;code&gt;singularity_env.sh&lt;/code&gt;
first. In an ideal world, this sourcing could be done with the same
&lt;code&gt;--init-file&lt;/code&gt; arg as used in the previous block, but that wasn't working
for me. Note the importance of single quotes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# set these environmental variables, which we use for the jobs submitted to&lt;/span&gt;
&lt;span class="c1"&gt;# the cluster so they know where to find the container and this script&lt;/span&gt;
&lt;span class="n"&gt;env_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--env SFP_PATH=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --env SINGULARITY_CONTAINER_PATH=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;These two environmental variables are used by our snakemake slurm
profile, described in the next section, so that all our submitted jobs
know where the container and the wrapper script are found, since they'll
be used by them as well.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# the -e flag makes sure we don&amp;#39;t pass through any environment variables&lt;/span&gt;
&lt;span class="c1"&gt;# from the calling shell, while --writable-tmpfs enables us to write to the&lt;/span&gt;
&lt;span class="c1"&gt;# container&amp;#39;s filesystem (necessary because singularity_env.sh makes a&lt;/span&gt;
&lt;span class="c1"&gt;# temporary config.json file)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;exec_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity exec -e &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;env_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; --writable-tmpfs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, we combine all the strings we've been configuring into a single
string that we can execute. The only new components are the &lt;code&gt;-e&lt;/code&gt; flag,
which ensures we don't send through any extra environmental variables
from the calling shell, and the &lt;code&gt;--writable-tmpfs&lt;/code&gt; flag, which allows us
to write to the container's filesystem (not just the mounted ones),
which we need because we modify the snakemake configuration file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--bind&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--volume&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;exec_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker run &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; -it &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;exec_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sudo &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exec_str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we're using docker instead of singularity, the command is slightly
different: we replace &lt;code&gt;--bind&lt;/code&gt; with &lt;code&gt;--volume&lt;/code&gt; and change some of the
other flags. This command doesn't use the &lt;code&gt;-e&lt;/code&gt; flag and the extra
environmental variable manipulations that singularity requires, because
&lt;code&gt;docker&lt;/code&gt; cannot be used on the cluster, and so this will not need to
interact with the job scheduler (e.g., SLURM).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exec_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# we use shell=True because we want to carefully control the quotes used&lt;/span&gt;
&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exec_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, we print out the command we're running (largely for debugging
purposes, I'm not sure if this would be that useful to the user), and
call it using &lt;code&gt;subprocess&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run billbrod/sfp container. This is a wrapper, which binds the appropriate&amp;quot;&lt;/span&gt;
                     &lt;span class="s2"&gt;&amp;quot; paths and sources singularity_env.sh, setting up some environmental variables.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;If running with singularity, the path to the &amp;#39;&lt;/span&gt;
                              &lt;span class="s1"&gt;&amp;#39;.sif file containing the singularity image. &amp;#39;&lt;/span&gt;
                              &lt;span class="s1"&gt;&amp;#39;If running with docker, name of the docker image.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--software&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Whether to run this with singularity or docker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--sudo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;store_true&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Whether to run docker with sudo or not. Ignored if software==singularity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Command to pass to the container. If empty, we open up an interactive session.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The very bottom of the script contains the &lt;code&gt;argparse&lt;/code&gt; configuration,
which makes the help string from the command line more informative.&lt;/p&gt;
&lt;p&gt;The users calls the script like so: &lt;code&gt;./run_singularity.py
path/to/singularity_image.sif CMD&lt;/code&gt; if using singularity, and
&lt;code&gt;./run_singularity.py user/image_name --software docker -s CMD&lt;/code&gt; if using
docker (that &lt;code&gt;-s&lt;/code&gt; flag runs docker as sudo, and so may not be necessary,
depending on their docker configuration). Note that this &lt;code&gt;CMD&lt;/code&gt; is the
equivalent of &lt;code&gt;args&lt;/code&gt; discussed above. &lt;code&gt;CMD&lt;/code&gt; can be left empty (in which
case an interactive session will be opened) or a string that will be run
inside the container, such as the snakemake commands required to
recreate the analysis, e.g., &lt;code&gt;'snakemake
main_figure_paper'&lt;/code&gt; (for my &lt;code&gt;spatial-frequency-preferences&lt;/code&gt; project).
Note that single quotes are necessary if any flags are included in
&lt;code&gt;CMD&lt;/code&gt;, in order to prevent &lt;code&gt;run_singularity.py&lt;/code&gt; from trying to interpret
them.&lt;/p&gt;
&lt;p&gt;Finally, the complete script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/usr/bin/env python3&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os.path&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;op&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;glob&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;

&lt;span class="c1"&gt;# slurm-related paths. change these if your slurm is set up differently or you&lt;/span&gt;
&lt;span class="c1"&gt;# use a different job submission system. see docs&lt;/span&gt;
&lt;span class="c1"&gt;# https://sylabs.io/guides/3.7/user-guide/appendix.html#singularity-s-environment-variables&lt;/span&gt;
&lt;span class="c1"&gt;# for full description of each of these environmental variables&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_BINDPATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_BINDPATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,/opt/slurm,/usr/lib64/libmunge.so.2.0.0,/usr/lib64/libmunge.so.2,/var/run/munge,/etc/passwd&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITYENV_PREPEND_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITYENV_PREPEND_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:/opt/slurm/bin&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_CONTAINLIBS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_CONTAINLIBS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/opt/slurm/lib64/libpmi*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_singularity_envvars&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Make sure SINGULARITY_BINDPATH, SINGULARITY_PREPEND_PATH, and SINGULARITY_CONTAINLIBS only contain existing paths&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SINGULARITY_BINDPATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SINGULARITYENV_PREPEND_PATH&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SINGULARITY_CONTAINLIBS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;joiner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;SINGULARITYENV_PREPEND_PATH&amp;quot;&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joiner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;joiner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_bind_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Check that paths we want to bind exist, return only those that do.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;vol&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;vol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Run sfp singularity container!&lt;/span&gt;

&lt;span class="sd"&gt;    Parameters&lt;/span&gt;
&lt;span class="sd"&gt;    ----------&lt;/span&gt;
&lt;span class="sd"&gt;    image : str&lt;/span&gt;
&lt;span class="sd"&gt;        If running with singularity, the path to the .sif file containing the&lt;/span&gt;
&lt;span class="sd"&gt;        singularity image. If running with docker, name of the docker image.&lt;/span&gt;
&lt;span class="sd"&gt;    args : list, optional&lt;/span&gt;
&lt;span class="sd"&gt;        command to pass to the container. If empty (default), we open up an&lt;/span&gt;
&lt;span class="sd"&gt;        interactive session.&lt;/span&gt;
&lt;span class="sd"&gt;    software : {&amp;#39;singularity&amp;#39;, &amp;#39;docker&amp;#39;}, optional&lt;/span&gt;
&lt;span class="sd"&gt;        Whether to run image with singularity or docker&lt;/span&gt;
&lt;span class="sd"&gt;    sudo : bool, optional&lt;/span&gt;
&lt;span class="sd"&gt;        If True, we run docker with `sudo`. If software==&amp;#39;singularity&amp;#39;, we&lt;/span&gt;
&lt;span class="sd"&gt;        ignore this.&lt;/span&gt;

&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;check_singularity_envvars&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/spatial-frequency-preferences&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;MATLAB_PATH&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/matlab&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FREESURFER_HOME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/freesurfer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FSLDIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/fsl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DATA_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;DATA_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;WORKING_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;WORKING_DIR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;check_bind_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# join puts --bind between each of the volumes, we also need it in the&lt;/span&gt;
    &lt;span class="c1"&gt;# beginning&lt;/span&gt;
    &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--bind &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot; --bind &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# if the user is passing a snakemake command, need to pass&lt;/span&gt;
    &lt;span class="c1"&gt;# --configfile /home/sfp_user/sfp_config.json, since we modify the config&lt;/span&gt;
    &lt;span class="c1"&gt;# file when we source singularity_env.sh&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--configfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/sfp_config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;-d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;-s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences/Snakefile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;
    &lt;span class="c1"&gt;# in this case they passed a string so args[0] contains snakemake and then&lt;/span&gt;
    &lt;span class="c1"&gt;# a bunch of other stuff&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--configfile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/sfp_config.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;-d&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;-s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/spatial-frequency-preferences/Snakefile&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;snakemake &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;
        &lt;span class="c1"&gt;# if the user specifies --profile slurm, replace it with the&lt;/span&gt;
        &lt;span class="c1"&gt;# appropriate path. We know it will be in the last one of args and&lt;/span&gt;
        &lt;span class="c1"&gt;# nested below the above elif because if they specified --profile then&lt;/span&gt;
        &lt;span class="c1"&gt;# the whole thing had to be wrapped in quotes, which would lead to this&lt;/span&gt;
        &lt;span class="c1"&gt;# case.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--profile slurm&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile slurm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="s1"&gt;&amp;#39;--profile /home/sfp_user/.config/snakemake/slurm&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# then need to make sure to mount this&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--profile&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;profile_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile (.*?) &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;profile_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile_path&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;:/home/sfp_user/.config/snakemake/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--profile /home/sfp_user/.config/snakemake/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# open up an interactive session if the user hasn&amp;#39;t specified an argument,&lt;/span&gt;
    &lt;span class="c1"&gt;# otherwise pass the argument to bash. regardless, make sure we source the&lt;/span&gt;
    &lt;span class="c1"&gt;# env.sh file&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/bin/bash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--init-file&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/home/sfp_user/singularity_env.sh&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/bin/bash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;# this needs to be done with single quotes on the inside so&lt;/span&gt;
                &lt;span class="c1"&gt;# that&amp;#39;s what bash sees, otherwise we run into&lt;/span&gt;
                &lt;span class="c1"&gt;# https://stackoverflow.com/questions/45577411/export-variable-within-bin-bash-c;&lt;/span&gt;
                &lt;span class="c1"&gt;# double-quoted commands get evaluated in the *current* shell,&lt;/span&gt;
                &lt;span class="c1"&gt;# not by /bin/bash -c&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;#39;source /home/sfp_user/singularity_env.sh; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# set these environmental variables, which we use for the jobs submitted to&lt;/span&gt;
    &lt;span class="c1"&gt;# the cluster so they know where to find the container and this script&lt;/span&gt;
    &lt;span class="n"&gt;env_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;--env SFP_PATH=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realpath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; --env SINGULARITY_CONTAINER_PATH=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
    &lt;span class="c1"&gt;# the -e flag makes sure we don&amp;#39;t pass through any environment variables&lt;/span&gt;
    &lt;span class="c1"&gt;# from the calling shell, while --writable-tmpfs enables us to write to the&lt;/span&gt;
    &lt;span class="c1"&gt;# container&amp;#39;s filesystem (necessary because singularity_env.sh makes a&lt;/span&gt;
    &lt;span class="c1"&gt;# temporary config.json file)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;exec_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity exec -e &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;env_str&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; --writable-tmpfs &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;software&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;volumes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--bind&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;--volume&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;exec_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;docker run &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;volumes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; -it &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;exec_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;sudo &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;exec_str&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exec_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# we use shell=True because we want to carefully control the quotes used&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exec_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__main__&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Run billbrod/sfp container. This is a wrapper, which binds the appropriate&amp;quot;&lt;/span&gt;
                     &lt;span class="s2"&gt;&amp;quot; paths and sources singularity_env.sh, setting up some environmental variables.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;image&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;If running with singularity, the path to the &amp;#39;&lt;/span&gt;
                              &lt;span class="s1"&gt;&amp;#39;.sif file containing the singularity image. &amp;#39;&lt;/span&gt;
                              &lt;span class="s1"&gt;&amp;#39;If running with docker, name of the docker image.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--software&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;singularity&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;docker&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Whether to run this with singularity or docker&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--sudo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-s&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;store_true&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Whether to run docker with sudo or not. Ignored if software==singularity&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;args&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Command to pass to the container. If empty, we open up an interactive session.&amp;quot;&lt;/span&gt;
                              &lt;span class="s2"&gt;&amp;quot; If it contains flags, surround with SINGLE QUOTES (not double).&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;slurm snakemake profile&lt;/h2&gt;
&lt;p&gt;If we want to submit jobs to the job scheduler, we need to tell
snakemake how to do so and to use the container when it does so. We do
this via a custom
&lt;a href="https://snakemake.readthedocs.io/en/stable/executing/cli.html?highlight=profiles#profiles"&gt;profile&lt;/a&gt;,
which can be found as the singularity branch of my
&lt;a href="https://github.com/billbrod/snakemake-slurm/tree/singularity"&gt;snakemake-slurm&lt;/a&gt;
github repository. This is based on the canonical
&lt;a href="https://github.com/Snakemake-Profiles/slurm/"&gt;slurm&lt;/a&gt; snakemake profile.
I originally created my version 5 years ago, and it's possible there are
changes to the original in that time that would be helpful, so you can
use mine or the original for this (the only changes I've made besides
those described below are a small change to add support for the
&lt;a href="https://github.com/billbrod/snakemake-slurm/commit/4f9e82930eb8dd7b34699b3689bf79db9017e9cb"&gt;–gres&lt;/a&gt;
option, required for requesting GPUs, and a small change to
&lt;a href="https://github.com/billbrod/snakemake-slurm/commit/73b4c14506ed9403ee6a91a47d660bc5fa261c91"&gt;partition&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The only file that is specific to the implementation discussed in this
post is &lt;code&gt;slurm-jobscript.sh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c1"&gt;#SBATCH --export=SINGULARITY_CONTAINER_PATH,SFP_PATH&lt;/span&gt;
&lt;span class="c1"&gt;# properties = {properties}&lt;/span&gt;

&lt;span class="c1"&gt;# q is a special formatting symbol used by snakemake to tell it to escape quotes&lt;/span&gt;
&lt;span class="c1"&gt;# correctly&lt;/span&gt;
&lt;span class="nv"&gt;$SFP_PATH&lt;/span&gt;/run_singularity.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$SINGULARITY_CONTAINER_PATH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;exec_job:q&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that this script makes use of the &lt;code&gt;SINGULARITY_CONTAINER_PATH&lt;/code&gt; and
&lt;code&gt;SFP_PATH&lt;/code&gt; environmental variables, which we made sure to set in the
wrapper script &lt;code&gt;run_singularity.py&lt;/code&gt; above. They specify the location of
the singularity container containing the environment and the directory
containing the code for the project (and thus, &lt;code&gt;run_singularity.py&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Each job that snakemake submits to the cluster will use this script as
the template for its job, and so we are ensuring that each of those jobs
will use &lt;code&gt;run_singularity.py&lt;/code&gt; to run within the container. We also use
the &lt;code&gt;:q&lt;/code&gt; formatting symbol (which is a special snakemake one, not
available in standard python) to escape quotes correctly, which is
important since, as discussed in the previous section, we need to
control the single quotes carefully to make sure the flags are
interpreted by the right software.&lt;/p&gt;
&lt;h2&gt;Archiving&lt;/h2&gt;
&lt;p&gt;Finally, we would like to be able to back up the containers for
long-term archiving. I wrote this code as a way of making my analysis
and figure-creation reproducible for a scientific paper. This means that
I would like the code to remain runnable for as long as possible and it
is unlikely that many people will use it. Docker hub &lt;a href="https://www.docker.com/blog/docker-hub-image-retention-policy-delayed-and-subscription-updates/"&gt;had
planned&lt;/a&gt;
to delete images from free Docker accounts after six months of activity,
though it's unclear what the status of that plan is. Regardless, we
should not be relying on Docker hub for long-term archiving and so we
need another solution.&lt;/p&gt;
&lt;p&gt;Fortunately, singularity creates a &lt;code&gt;.sif&lt;/code&gt; file containing the container
when pulling it, and docker can export the container into a &lt;code&gt;.tar&lt;/code&gt; file
using &lt;code&gt;docker save&lt;/code&gt; (e.g., &lt;code&gt;docker save billbrod/sfp:v1.0.0 &amp;gt;
sfp_v1.0.0_docker.tar&lt;/code&gt;). These files can then be archived like any other
file. They are rather large (2.5GB for the &lt;code&gt;.sif&lt;/code&gt; file, 5.1GB for the
&lt;code&gt;.tar&lt;/code&gt; file), and so something like the &lt;a href="https://osf.io/"&gt;Open Science
Framework&lt;/a&gt;, where I normally place my academic
research-related files, is not an option.&lt;/p&gt;
&lt;p&gt;Ultimately, I used NYU's &lt;a href="https://archive.nyu.edu/"&gt;Faculty Digital
Archive&lt;/a&gt;. Something like &lt;a href="https://aws.amazon.com/s3/"&gt;Amazon Web
Service's&lt;/a&gt; S3 buckets (or other cloud
storage) would be another option, though not free. When looking for
where to archive, any service you use should give objects a doi and
allow for versioning. Ideally, it should be run by a non-profit and open
source, but that might not be possible. If you're at a university, I
recommend reaching out to your university library or HPC team to see if
they have any suggestions. Fundamentally, archiving and sharing the
container is not that different from archiving and sharing a large data
set.&lt;/p&gt;
&lt;p&gt;Archiving the container somewhere publicly-accessible allows users to
download the container and use it, even if the docker hub image is
deleted. With singularity, the container is used directly, whereas with
docker, &lt;code&gt;docker load&lt;/code&gt; is required (e.g., &lt;code&gt;docker load &amp;lt;
sfp_v1.0.0_docker.tar&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This post outlines how to create and use a container which contains all
dependencies possible, allows for mounting any additional dependencies
(such as those that require a license for use), and can be used
identically locally and on the cluster, as well as being able to use
snakemake to submit jobs on a cluster. While the scripts I wrote are all
slurm-specific (and, even more so, specific to NYU's greene cluster),
hopefully they can provide a jumping off point for others.&lt;/p&gt;</content><category term="posts"/><category term="hpc"/><category term="python"/><category term="reproducibility"/></entry><entry><title>Scripting figure creation with inkscape and python</title><link href="https://wfbroderick.com/scripting-figure-creation-with-inkscape-and-python.html" rel="alternate"/><published>2021-11-06T00:00:00-04:00</published><updated>2021-11-06T00:00:00-04:00</updated><author><name>William F. Broderick</name></author><id>tag:wfbroderick.com,2021-11-06:/scripting-figure-creation-with-inkscape-and-python.html</id><summary type="html">&lt;p&gt;I spent some time figuring out how to use inkscape and python to script
the creation of figures that contain bitmaps, allowing for the user to
specify the DPI when embedded. Normally, the way to handle this involves
creating the figures directly in a vector graphics editor like inkscape
or …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I spent some time figuring out how to use inkscape and python to script
the creation of figures that contain bitmaps, allowing for the user to
specify the DPI when embedded. Normally, the way to handle this involves
creating the figures directly in a vector graphics editor like inkscape
or Adobe Illustrator, which works fine, but I like to script as much as
of my workflow as possible, to make testing different figure versions as
quick as possible. I spent probably too much time getting it to work,
but &lt;a href="https://github.com/billbrod/inkscape-figure-example"&gt;here's&lt;/a&gt; an
example GitHub repo I made showing how the process works, with some tips
on how to use it yourself.&lt;/p&gt;</content><category term="posts"/><category term="python"/><category term="science"/></entry><entry><title>conda, snakemake, and HPC</title><link href="https://wfbroderick.com/conda-snakemake-and-hpc.html" rel="alternate"/><published>2021-05-05T00:00:00-04:00</published><updated>2021-05-05T00:00:00-04:00</updated><author><name>William F. Broderick</name></author><id>tag:wfbroderick.com,2021-05-05:/conda-snakemake-and-hpc.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I have since come up with a solution that I like better /
think is less hacky, described in &lt;a href="https://wfbroderick.com/snakemake-singularity-and-hpc.html"&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NYU's new high performance computing (HPC) cluster, Greene, was put into
production earlier this year and, on the new system, they want all users
to use singularity containers for …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: I have since come up with a solution that I like better /
think is less hacky, described in &lt;a href="https://wfbroderick.com/snakemake-singularity-and-hpc.html"&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NYU's new high performance computing (HPC) cluster, Greene, was put into
production earlier this year and, on the new system, they want all users
to use singularity containers for conda environments. This is because
conda environments create a bunch of files: one conda environment can
contain more than 100 thousand files; on Greene, each user is limited to
1 million files, so it's not great to have that many be eaten up by
conda. To get around this, they want everyone to use overlay singularity
containers, and have put together some documentation &lt;a href="https://sites.google.com/a/nyu.edu/nyu-hpc/documentation/prince/packages/singularity-for-conda"&gt;to help
users&lt;/a&gt;
get set up.&lt;/p&gt;
&lt;p&gt;However, I use the workflow management system
&lt;a href="https://snakemake.readthedocs.io/en/stable/"&gt;snakemake&lt;/a&gt; to run my
analyses and found it difficult to set up my environments such that
snakemake could make use of them. After meeting with NYU HPC staff and
some extra work on both their part and mine, I think I've come up with a
solution. I'm not sure if this will help anyone outside of NYU, but I
figured it was worth writing up.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;As detailed on the &lt;a href="https://sites.google.com/a/nyu.edu/nyu-hpc/documentation/prince/packages/singularity-for-conda"&gt;HPC
    site&lt;/a&gt;,
    first pick an appropriate overlay filesystem (based on the number of
    files and overall size; the one I pick probably has more files than
    necessar for a single conda environment, and you would need more
    space if you wanted to install large packages like the recent
    version of pytorch) and copy it to a new directory in your
    &lt;code&gt;/scratch/&lt;/code&gt; directory (we'll be calling that directory &lt;code&gt;overlay/&lt;/code&gt;
    throughout):&lt;/p&gt;
&lt;p&gt;``` bash
mkdir /scratch/$USER/overlay
cd /scratch/$USER/overlay&lt;/p&gt;
&lt;h1&gt;if you're not on NYU's Greene cluster, this path will almost certainly be&lt;/h1&gt;
&lt;h1&gt;different&lt;/h1&gt;
&lt;p&gt;cp /scratch/work/public/overlay-fs-ext3/overlay-5GB-3.2M.ext3.gz .
gunzip overlay-5GB-3.2M.ext3.gz
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run a container with the overlay filesystem. You'll have to pick
    what flavor of linux you want, but it shouldn't matter for our
    purposes (I picked Ubuntu because it's what I'm most familiar with;
    CentOS is the version used on most HPC systems, including Greene, so
    you may want to use that):&lt;/p&gt;
&lt;p&gt;``` bash&lt;/p&gt;
&lt;h1&gt;again, if you're not on Greene, the path to the linux singularity image will&lt;/h1&gt;
&lt;h1&gt;almost certainly be different&lt;/h1&gt;
&lt;p&gt;singularity exec --overlay /scratch/$USER/overlay/overlay-5GB-3.2M.ext3 /scratch/work/public/singularity/ubuntu-20.04.1.sif /bin/bash
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You are now inside the singularity container. We're going to install
    our environment in &lt;code&gt;/ext3&lt;/code&gt; using miniconda. Note that you can skip
    this and the following step on greene by using an HPC-provided
    script; instead, run &lt;code&gt;bash
            /setup/apps/utils/singularity-conda/setup-conda.bash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
cd /ext3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
sh Miniconda3-latest-Linux-x86_64.sh -b -p /ext3/miniconda3&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the following into the file &lt;code&gt;/ext3/env.sh&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;``` bash&lt;/p&gt;
&lt;h1&gt;!/bin/bash&lt;/h1&gt;
&lt;h1&gt;this needs to be copied into the overlay, as /ext3/env.sh&lt;/h1&gt;
&lt;p&gt;source /ext3/miniconda3/etc/profile.d/conda.sh
export PATH=/ext3/miniconda3/bin:$PATH
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;/ext3/env.sh&lt;/code&gt; is also where you'd modify the path or set
    environmental variables for additional packages;
    &lt;a href="https://lmod.readthedocs.io/en/latest/"&gt;lmod&lt;/a&gt; is not available in
    the singularity container, so you can't use &lt;code&gt;module load&lt;/code&gt; and will
    have to configure your paths yourself. To see what &lt;code&gt;module load&lt;/code&gt;
    would do, you can run &lt;code&gt;module show
            freesurfer/6.0.0&lt;/code&gt; (for example) instead, which will show you how the
    path is modified and what environmental variables are set. For
    example, for an fMRI project I've been working on, I need access to
    FSL, Freesurfer, and MATLAB, so I add the following to the end of my
    &lt;code&gt;env.sh&lt;/code&gt; file:&lt;/p&gt;
&lt;p&gt;``` bash&lt;/p&gt;
&lt;h1&gt;set up other libraries (module doesn't work in the container)&lt;/h1&gt;
&lt;p&gt;export FREESURFER_HOME=/share/apps/freesurfer/6.0.0
export PATH=$FREESURFER_HOME/bin:$PATH
export SUBJECTS_DIR=/scratch/wfb229/sfp_minimal/derivatives/freesurfer&lt;/p&gt;
&lt;p&gt;export PATH=/share/apps/matlab/2020b/bin:$PATH&lt;/p&gt;
&lt;p&gt;export FSLOUTPUTTYPE=NIFTI_GZ
export FSLDIR=/share/apps/fsl/5.0.10
export PATH=$FSLDIR/bin:$PATH
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run &lt;code&gt;source /ext3/env.sh&lt;/code&gt; to source the above file, configuring your
    path so conda is found on it. Run &lt;code&gt;which conda&lt;/code&gt; and &lt;code&gt;which python&lt;/code&gt;
    to make sure: it should show &lt;code&gt;/ext3/miniconda3/bin/conda&lt;/code&gt; and
    &lt;code&gt;/ext3/miniconda3/bin/python&lt;/code&gt;, respectively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install your conda environment like normal, using &lt;code&gt;conda install&lt;/code&gt;
    and &lt;code&gt;pip
            install&lt;/code&gt;. (Note: this post assumes you're installing your
    environment in the miniconda base environment, since we'll only have
    a single environment per overlay container; you could probably
    install it in a new environment as well, as long as you modify the
    following &lt;code&gt;/ext3/env.sh&lt;/code&gt; so that it ends with &lt;code&gt;conda activate
    &amp;lt;my-env&amp;gt;&lt;/code&gt;). For example, let's install numpy and snakemake:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
conda install numpy snakemake -c bioconda&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Exit out of the singularity container. We now need to allow
    snakemake to access this environment. For that, two components are
    required: a snakemake profile for slurm and a way to redirect the
    python and snakemake commands so they use the versions found within
    the container. Download the
    &lt;a href="https://github.com/billbrod/snakemake-slurm/"&gt;snakemake-slurm&lt;/a&gt; repo
    and place it in &lt;code&gt;~/.config&lt;/code&gt; (the important part of this config for
    the purposes of this post is
    &lt;a href="https://github.com/billbrod/snakemake-slurm/blob/master/slurm-jobscript.sh"&gt;slurm-jobscript.sh&lt;/a&gt;,
    particularly the lines where we modify the path):&lt;/p&gt;
&lt;p&gt;``` bash&lt;/p&gt;
&lt;h1&gt;exit the container&lt;/h1&gt;
&lt;p&gt;exit&lt;/p&gt;
&lt;h1&gt;create the snakemake slurm profile&lt;/h1&gt;
&lt;p&gt;mkdir -p ~/.config/snakemake
cd ~/.config/snakemake
github clone git@github.com:billbrod/snakemake-slurm.git slurm
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Copy the following into the file &lt;code&gt;/scratch/$USER/overlay/python.sh&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;``` bash&lt;/p&gt;
&lt;h1&gt;!/bin/bash&lt;/h1&gt;
&lt;h1&gt;https://stackoverflow.com/questions/1668649/how-to-keep-quotes-in-bash-arguments&lt;/h1&gt;
&lt;p&gt;args=''
for i in "$@"; do
    i="${i//\/\\}"
    args="$args \"${i//\"/\\"}\""
done&lt;/p&gt;
&lt;p&gt;module purge&lt;/p&gt;
&lt;p&gt;export PATH=/share/apps/singularity/bin:$PATH&lt;/p&gt;
&lt;h1&gt;file systems&lt;/h1&gt;
&lt;p&gt;export SINGULARITY_BINDPATH=/mnt,/scratch,/share/apps
if [ -d /state/partition1 ]; then
    export SINGULARITY_BINDPATH=$SINGULARITY_BINDPATH,/state/partition1
fi&lt;/p&gt;
&lt;h1&gt;SLURM related&lt;/h1&gt;
&lt;p&gt;export SINGULARITY_BINDPATH=$SINGULARITY_BINDPATH,/opt/slurm,/usr/lib64/libmunge.so.2.0.0,/usr/lib64/libmunge.so.2,/var/run/munge,/etc/passwd
export SINGULARITYENV_PREPEND_PATH=/opt/slurm/bin
if [ -d /opt/slurm/lib64 ]; then
    export SINGULARITY_CONTAINLIBS=$(echo /opt/slurm/lib64/libpmi* | xargs | sed -e 's/ /,/g')
fi&lt;/p&gt;
&lt;p&gt;nv=""
if [[ "$(hostname -s)" =~ ^g ]]; then nv="--nv"; fi
cmd=$(basename $0)&lt;/p&gt;
&lt;p&gt;singularity exec $nv \
            --overlay /scratch/$USER/overlay/overlay-5GB-3.2M.ext3:ro \
            /scratch/work/public/singularity/ubuntu-20.04.1.sif \
            /bin/bash -c "
source /ext3/env.sh
$cmd $args
exit
"&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create symlinks for &lt;code&gt;python&lt;/code&gt;, &lt;code&gt;python3&lt;/code&gt;, and &lt;code&gt;snakemake&lt;/code&gt;, all
    redirecting to our newly created &lt;code&gt;python.sh&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash
cd /scratch/$USER/overlay
ln -sv python.sh python
ln -sv python.sh python3
ln -sv python.sh snakemake&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following lines to your &lt;code&gt;.bashrc&lt;/code&gt; so that these symlinks are
    on your path. Exit and enter your shell so this modification takes
    effect. You can check this worked with &lt;code&gt;which snakemake&lt;/code&gt; or &lt;code&gt;which
    python&lt;/code&gt;, which should give you &lt;code&gt;/scratch/$USER/overlay/snakemake&lt;/code&gt;
    and &lt;code&gt;/scratch/$USER/overlay/python&lt;/code&gt;, respectively. Now, &lt;code&gt;snakemake&lt;/code&gt;
    and &lt;code&gt;python&lt;/code&gt; will both use that &lt;code&gt;python.sh&lt;/code&gt; script, which runs the
    command using the singularity overlay image.&lt;/p&gt;
&lt;p&gt;``` bash
if [ "$SINGULARITY_CONTAINER" == "" ]; then
    export PATH=/scratch/$USER/overlay:$PATH
fi&lt;/p&gt;
&lt;p&gt;```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Now, this is the hacky part: start the overlay container back up,
    and modify the snakemake executor so it uses &lt;code&gt;python&lt;/code&gt; instead of
    &lt;code&gt;sys.executable&lt;/code&gt; (&lt;code&gt;sys.executable&lt;/code&gt; will be the absolute path to a
    python interpreter and thus not use the sneaky symlinks we just
    created; bare &lt;code&gt;python&lt;/code&gt; will use them because of how we've set up our
    path). Open up the &lt;a href="https://snakemake.readthedocs.io/en/stable/_modules/snakemake/executors.html"&gt;singularity executor
    file&lt;/a&gt;;
    the exact path to this will depend on where you installed miniconda
    and your snakemake version, but on mine (python 3.7.8 and snakemake
    5.4.5) it's
    &lt;code&gt;/ext3/miniconda3/python3.7/site-packages/snakemake/executors.py&lt;/code&gt;
    (on more recent versions of snakemake, it will be
    &lt;code&gt;snakemake/executors/__init__.py&lt;/code&gt;). Then find the lines where
    &lt;code&gt;self.exec_job&lt;/code&gt; is being defined and &lt;code&gt;{sys.executable}&lt;/code&gt; is used
    (lines 240 and 430 for my install) and replace &lt;code&gt;{sys.executable}&lt;/code&gt;
    with &lt;code&gt;python&lt;/code&gt;. Here's my diff as an example:&lt;/p&gt;
&lt;p&gt;``` diff
240,241c240
&amp;lt;             # '{sys.executable} -m snakemake {target} --snakefile {snakefile} ',
&amp;lt;             'python -m snakemake {target} --snakefile {snakefile} ',&lt;/p&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;        &amp;#39;{sys.executable} -m snakemake {target} --snakefile {snakefile} &amp;#39;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;430,431c429
&amp;lt;                 # '{sys.executable} ' if assume_shared_fs else 'python ',
&amp;lt;                 'python ',&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;            &amp;#39;{sys.executable} &amp;#39; if assume_shared_fs else &amp;#39;python &amp;#39;,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;```&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That should work. I'm not super happy with having to modify snakemake's
source code to get this working, but it does work. Let me know if you
know of a better solution!&lt;/p&gt;
&lt;p&gt;Note that you cannot have the overlay container open in a separate
terminal session while you attempt to use it via snakemake (though it
does look like you can run multiple independent jobs simultaneously via
snakemake, with the &lt;code&gt;-j&lt;/code&gt; flag).&lt;/p&gt;
&lt;p&gt;Here's a way to test that all of the above is working:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Copy the following into &lt;code&gt;~/Snakefile&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;``` python
rule test_run:
     log: 'test_run.log'
     run:
        import numpy
        print("Success!")&lt;/p&gt;
&lt;p&gt;rule test_shell:
     log: 'test_shell.log'
     shell:
        "python -c 'import numpy'; echo success!"
```&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From your home directory, run &lt;code&gt;snakemake -j 2 --profile slurm
    test_run
            test_shell&lt;/code&gt;. If everything was set up correctly, it should run
    without a problem. If not, check the logs &lt;code&gt;~/test_run.log&lt;/code&gt; and
    &lt;code&gt;~/test_shell.log&lt;/code&gt; to see if they contain any helpful information.
    You may also want to add the &lt;code&gt;--verbose&lt;/code&gt; flag to the snakemake
    command, which will cause it to print out the snakemake jobscript to
    the terminal, making it easier to debug.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content><category term="posts"/><category term="hpc"/></entry><entry><title>VSS 2020 project</title><link href="https://wfbroderick.com/vss-2020-project.html" rel="alternate"/><published>2020-06-19T00:00:00-04:00</published><updated>2020-06-19T00:00:00-04:00</updated><author><name>William F. Broderick</name></author><id>tag:wfbroderick.com,2020-06-19:/vss-2020-project.html</id><summary type="html">&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In this post, I'm going to attempt to explain my poster at the Virtual
Vision Sciences Society (VSS) conference, 2020, which starts today. The
poster itself, along with a video walkthrough, an approximate transcript
of that video, and some supplementary images, can be found on an &lt;a href="https://osf.io/aketq/"&gt;OSF
page&lt;/a&gt; (view …&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;In this post, I'm going to attempt to explain my poster at the Virtual
Vision Sciences Society (VSS) conference, 2020, which starts today. The
poster itself, along with a video walkthrough, an approximate transcript
of that video, and some supplementary images, can be found on an &lt;a href="https://osf.io/aketq/"&gt;OSF
page&lt;/a&gt; (view the &lt;code&gt;README.md&lt;/code&gt; file for an
explanation of the contents) and, if you're a member of VSS, on &lt;a href="https://www.visionsciences.org/myvss/?mtpage=vvss_presentation&amp;amp;id=1398"&gt;their
website&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Big picture, in this project we study how vision changes across the
visual field by investigating what people &lt;em&gt;cannot&lt;/em&gt; see. We use
computational models of the brain and human behavioral experiments to do
this. In this post, I'll first try to give a High-Level Overview of this
project, explaining what we do and why it matters. Then, in the Unseen
Vision section, I'll spend some more time talking about the approach of
studying what cannot see, which has a long history in vision science.
I'll then dive into the specific models we use in the Models and the
Visual System section, before finishing up and describing the
experiment. Unfortunately, because of COVID-19, we have not been able to
gather any data yet, but I do have some stimuli that we'll use in the
experiment.&lt;/p&gt;
&lt;h1&gt;High-Level Overview&lt;/h1&gt;
&lt;p&gt;I am interested in how vision changes across the visual field. You can
notice this by observing that, when your eyes are centered on this text,
you can read it clearly, but if you move your eyes even a little to the
side, you can't make any sense of it. What's going on there?
Specifically, we investigate what information people &lt;em&gt;are not&lt;/em&gt; sensitive
to as you move away from the center of gaze. There are many ways one
could approach this, but we're doing so by building models of two of the
first stages of the brain that receive visual information: the retina
(even though it's in the eye, it's made up of brain tissue and thus
technically part of the brain) and the imaginatively-named primary
visual cortex (or V1), inspired by our understanding of the cells
there&lt;sup id=sf-vss-2020-project-1-back&gt;&lt;a href=#sf-vss-2020-project-1 class=simple-footnote title="There's a part of the brain betwen the retina and V1, a region of the thalamus, deep in the brain, called the lateral geniculate nucleus. However, in visual neuroscience we often consider it to be a relay station that just transmits signals between the retina and V1, so we ignore it in this project"&gt;1&lt;/a&gt;&lt;/sup&gt;. Those models have a single
parameter, a number we can vary to change their behavior. I'll explain
the parameter in more detail in the Models section of this post, but we
then generate sets of images that the models (with specific parameter
values) think are identical. We then run an experiment where we show
those images to humans and find which images they also think are
identical, finding the best parameter value for each model.&lt;/p&gt;
&lt;p&gt;Once we've done that, we will have two models (with specific parameter
values) where we know, if the model thinks two images are identical,
humans will not be able to tell them apart &lt;sup id=sf-vss-2020-project-2-back&gt;&lt;a href=#sf-vss-2020-project-2 class=simple-footnote title="At least, under the conditions of our experiment."&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Why does that matter? There are several reasons I find this work
interesting:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We're modeling specific brain areas, so the parameter has a meaning
    and therefore tells us something about that brain area. I'll discuss
    what, exactly, it tells us in the Models section.&lt;/li&gt;
&lt;li&gt;Our models were built based on an understanding of cells that comes
    from showing images to animals (generally, cats or macaques) and
    recording the activity of those cells using electrodes. Often these
    images are unlike most of what these or other animals see in their
    life (black and white gratings or moving bars being the most
    common). Taking understanding that comes from what happens when
    animals see something and using it to generate predictions for when
    humans &lt;em&gt;will not&lt;/em&gt; be able to see something is a strong test of this
    understanding.&lt;/li&gt;
&lt;li&gt;We are abstracting away lots of interesting biology about these two
    brain areas areas and ignoring everything that happens after
    them&lt;sup id=sf-vss-2020-project-3-back&gt;&lt;a href=#sf-vss-2020-project-3 class=simple-footnote title="This is known as a feed-forward approach to the brain,     assuming information flows through the brain in one direction and     ignoring all the feedback that happens."&gt;3&lt;/a&gt;&lt;/sup&gt;. If we can still
    generate images that humans find identical, it tells us that these
    are reasonable assumptions in these conditions (though there are
    other conditions where that might not be true).&lt;/li&gt;
&lt;li&gt;The model can tell us a way to compress the image: the model uses
    far fewer numbers than there are pixels in the image, and yet those
    numbers are enough to create an image that humans find identical.
    This is similar to other image compression methods, like JPEG: when
    you create a JPEG, it throws away information that it thinks you
    won't notice, so instead of recording each pixel value for a patch
    of blue sky, it says "this patch of 100 pixels should all be the
    same color blue", which takes far less space on your computer. And
    similar to our models, if you try to throw out &lt;em&gt;too much&lt;/em&gt;
    information, people will notice.&lt;/li&gt;
&lt;li&gt;This is a first step towards the creation of what we call a
    &lt;strong&gt;foveated image metric&lt;/strong&gt;. That's a technical term, so I'll unpack
    it in parts. An "image metric" tells you how different two images
    are. The simplest way to do this is to use the mean-squared error,
    or MSE, where you go through and check how different each pixel
    value is. But ideally, our measure of how different two images are
    would map onto how different humans think two images are, and humans
    and MSE do not agree&lt;sup id=sf-vss-2020-project-4-back&gt;&lt;a href=#sf-vss-2020-project-4 class=simple-footnote title=" See this     paper     from Zhou Wang and Alan Bovik for more details."&gt;4&lt;/a&gt;&lt;/sup&gt;. You can run
    experiments to find how different humans think to images are, but
    that takes a lot of time, so we want to come up with some computer
    code that can do it for us. With our models, we can say whether
    humans will think two images look the same, but if the images look
    different, our models won't tell us &lt;em&gt;how&lt;/em&gt; different they look –
    that's one extension of this project. And "foveated" means that we
    care about where people are looking in the image; the alternative
    assumes that people are free to move their eyes everywhere. This
    point and the last one about compression are related: if you know
    how similar two images look, you can figure out clever ways to throw
    away information without changing the perception of the image too
    much.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Unseen Vision&lt;/h1&gt;
&lt;p&gt;In my video, I start by discussing the idea that we can study vision by
studying what people cannot see, and it's worth talking about this in
more detail, because it's a powerful idea, but not an intuitive one.&lt;/p&gt;
&lt;p&gt;You're probably aware that color displays, from cathode ray tube screens
to liquid crystal displays to projectors, all use three different color
primaries (red, green, and blue) to render the colors they show. But how
is it that you can use only three different colors to render all of the
many different possible colors that humans can view? Because of cones –
(most) humans have three types of cones, and so you can get away with
only three primaries. But why? And the theory of human trichromatic
vision (that humans have three classes of photoreceptors sensitive to
color) was first postulated by Thomas Young in 1802, more than 150 years
before the physiological evidence for their existence. How?&lt;/p&gt;
&lt;p&gt;The existence of three cone classes was theorized as a way to explain
the results of color matching experiments, done in the 18th and 19th
centuries. In those experiments, participants were shown two lights on
either side of a divider. One light, the test light, was a constant
color, while the other, the comparison light, was made by combining
three different primary lights (for example, red, green, and blue) with
different intensities. The participant's task was to adjust the
intensities of these three primaries until the two lights looked
identical. And people could do this, for any color test light, as long
as they had three primaries. And these results were remarkably
consistent across people – just about everyone could match colors as
long as they had three primaries, and they used the same relative
intensities, but they couldn't do it with two&lt;sup id=sf-vss-2020-project-5-back&gt;&lt;a href=#sf-vss-2020-project-5 class=simple-footnote title="There were some people, it turns out, who could do it with two primaries – people with dichromacy, a form of color blindness)."&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://wfbroderick.com/images/trichromacy.svg"&gt;&lt;/p&gt;
&lt;p&gt;Brief digression to talk about the nature of color, with the caveat that
I do not study color (it's a whole separate area of vision science) and
so everything I say should be taken with a grain of salt (if you're
interested in this, I recommend reading the chapter on color from Brian
Wandell's excellent &lt;a href="https://foundationsofvision.stanford.edu/chapter-9-color/"&gt;Foundations of
Vision&lt;/a&gt;,
available for free online). But, technically, color is not a property of
an object, it's an interaction between the object (how much light it
reflects at each wavelength), the lighting conditions (how much light is
present at each wavelength), and the observer (how their visual system
interprets the light that arrives at their eye). So, there are no blue
dresses, there are just dresses that appear blue to me under specific
lighting conditions. That may sound purposefully obtuse, but it's
important to keep in mind. If you remember &lt;a href="https://en.wikipedia.org/wiki/The_dress"&gt;the
Dress&lt;/a&gt;, a viral image from
several years ago, it is a striking example of ambiguity in color
perception: some people see it as black and blue, where others see it as
white and gold, and people often have very strong opinions on which they
see, with few people able to see both&lt;sup id=sf-vss-2020-project-6-back&gt;&lt;a href=#sf-vss-2020-project-6 class=simple-footnote title=" The current understanding is that it comes down to differences in what you (implicitly) assumed the lighting condition was when the photo was taken. If you thought the light was yellow-tinted, you'll see the dress as black and blue; if you thought it was blue-tinted, you'd see the dress as white and gold. Wikipedia has a decent explanation on this. "&gt;6&lt;/a&gt;&lt;/sup&gt;. This demonstrates that
"color" is not as straightforward a label as we think, so I want to be
clear about the differences between perceptual color (what I mean when I
say "that dress is blue", which depends on all of the object, lighting,
and observer) and the light that arrives at the eye (the amount of
energy at each wavelength, known as the &lt;strong&gt;power spectrum&lt;/strong&gt;, which just
depends on the object and the lighting).&lt;/p&gt;
&lt;p&gt;In the color matching experiment, the two lights were &lt;em&gt;perceived&lt;/em&gt; as
identical by the participants, but the power spectrum were &lt;em&gt;very
different&lt;/em&gt;. But the participants didn't notice the difference because
their visual system had discarded all information that could separate
the two. The two lights are called &lt;strong&gt;metamers&lt;/strong&gt;: they're physically
different, but perceptually identical. Like I said earlier, this is
because (most) humans have three cone classes. The two lights appear
identical because the cone activity for the two of them are the same.
They have different amounts of energy at each wavelength, but they
excite the cones the same amount, and so you have no way of telling them
apart and thus perceive them as identical.&lt;/p&gt;
&lt;p&gt;This may seem weird at first – we have a tendency to think of our visual
system as conveying accurate information about the world around us. But
that's not what it does! It conveys &lt;em&gt;useful&lt;/em&gt; information about the
world around us, where useful is in evolutionary terms. Think about the
electromagnetic spectrum. Our visual system is only sensitive to visible
light, not infrared or ultraviolet. But light at those frequencies are
still present in the world and arrive at our eyes – we just can't make
use of it because our visual system throws it away. Our cones only
respond to lights between 400 and 700 nanometers, and so we cannot tell
the difference between a blue dress and a blue dress with a UV lamp
behind it. Other animals' cones, however, like bees and some birds, are
sensitive to ultraviolet light, and so their visual system can make use
of it.&lt;/p&gt;
&lt;p&gt;So alright, human visual systems aren't perfect and can be tricked in
this fairly arbitrary way. What of it? Well, now I can describe a color
using only three numbers, the intensities of each of those three primary
lights, rather than requiring me to specify the full power spectrum,
which would require a number for each possible wavelength in the visible
spectrum. That's a lot less information! I've gone from 300 numbers to
just 3. This is why I only need three color primaries in a display – I
can't reproduce any possible power spectrum, but I can match the
perceived color pretty well. It would be much harder to fit all the
necessary lights into a screen if we needed 300 of them. I've made use
of my understanding of the human visual system to &lt;strong&gt;compress&lt;/strong&gt; the
information about the color. This is one important application of this
type of work: if we know what information the human visual system throws
out, we can throw away that same information in any thing we build that
interacts with the human visual system (you might want to hold onto it
for other reasons, but for most situations where you just want an image
to look good, it's fine).&lt;/p&gt;
&lt;p&gt;And note that this whole thing is based around investigating what humans
&lt;em&gt;don't&lt;/em&gt; see. We've found changes we can make to the physical stimulus
(in this case, the power spectra) without humans being able to tell that
anything is different. This isn't about what colors look like: why do
some colors complement each other? What makes a color stand out? How
hard are these two colors to tell apart? There's a whole host of
interesting questions there as well. In the color case, the experiments
to find what people cannot see preceded the theory about the visual
system that explains why. But these types of experiments can also serve
as a strong test of our theories and understandings of the visual
system: we should be able to use our understand of how the system works
to generate images humans cannot distinguish. And we should be able to
do this not only by throwing information out of an image and predicting
you can't tell, but also by adding new information that you won't be
able to see. That's the goal of this project, to build models of early
stages of the visual system based on our understanding from other
experiments, generate images that the models think are identical, and
run an experiment to see which images humans also think are identical.&lt;/p&gt;
&lt;h1&gt;Models and the Visual System&lt;/h1&gt;
&lt;p&gt;With the previous section, I hope I've shown how studying what people
don't see can be a useful way to increase our understanding of the
visual system. In this project, we use that approach to study how vision
changes across the visual field. This is a pretty drastic effect: when
your eyes are fixated on this text, you can read it without a problem,
but if you move your eyes to the edge of the computer screen, then the
text becomes illegible. Why would this be the case?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram of the visual system. There is a V3 in the human brain, but
this figure comes from a monkey electrophysiologist, and they tend to
ignore V3 because it's hard to get to in the monkey.
(Source.)" src="https://wfbroderick.com/images/visual-system.svg"&gt;&lt;/p&gt;
&lt;p&gt;First, let's talk a little about the layout of the visual system. I'm
going to talk about the flow of information in the brain: information
enters via our sensory systems and gets processed and transformed by a
series of connected brain areas. These transformations, or
&lt;strong&gt;computations&lt;/strong&gt;, are what my research focuses on. How can we recognize
faces so easily from an image, which is just a bunch of points of light?
Because the human brain transforms those points into some more
meaningful representation that allows us to easily determine how
"face-like" something is. But the fact that it has taken years to get
computers to be able to recognize faces with any accuracy (and they're
still not that good, and have all &lt;a href="https://t.co/ydEBctVatC?amp=1"&gt;sorts of
biases&lt;/a&gt;) should emphasize how hard this
is. To return to the anatomy: when light enters the eye, it travels
through the lens to the back of the eye, called the &lt;strong&gt;retina&lt;/strong&gt;. From
there, the information gets sent to a section of the thalamus, deep in
the brain, called the lateral geniculate nucleus, or LGN (generally
speaking, in visual neuroscience we tend to consider the LGN a relay
station that doesn't transform the information at all, and so ignore
it). From there, it goes to the primary visual cortex or &lt;strong&gt;V1&lt;/strong&gt;, before
going through a succession of other similarly-named areas: V2, V3, V4,
etc. In this project, we build models of V1 and the retina.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fovea diagram" src="https://wfbroderick.com/images/fovea-and-receptors.jpg"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;visual field&lt;/strong&gt; is what you're seeing at any given time. When we
discuss the visual field, we're less interested in the physical objects
out in the world than we are in the patterns of light that these objects
reflect or emit that land on your eye. The center of your visual field,
where your eyes are fixating at any one point in time, is the &lt;strong&gt;fovea&lt;/strong&gt;.
The term comes from the Latin word for "pit" and describes an
interesting anatomical feature of the retina: the cells in front of the
cones are shoved to the side so they don't get in the way of the light.
This is the region of highest acuity. As you move away from the fovea,
you eventually enter the para-fovea, and then end up in the
&lt;strong&gt;periphery&lt;/strong&gt;, your acuity decreasing gradually all the way&lt;sup id=sf-vss-2020-project-7-back&gt;&lt;a href=#sf-vss-2020-project-7 class=simple-footnote title="To my knowledge, there are no agreed upon boundaries for any of these terms, except for the fovea; there is an anatomical boundary where the actual pit of the fovea ends."&gt;7&lt;/a&gt;&lt;/sup&gt;&lt;sup id=sf-vss-2020-project-8-back&gt;&lt;a href=#sf-vss-2020-project-8 class=simple-footnote title="Interestingly, not all animals have foveas, and some birds have two! It seems to be present in animals that need precise information about the location of objects, like primates, birds, and cats. But prey animals, such as mice and rabbits, do not have foveas, and often have quite poor visual acuity. Again, this comes down to the fact that the visual system is about capturing useful information to the organism, and fine spatial information is not important to mice."&gt;8&lt;/a&gt;&lt;/sup&gt;. These terms are used to refer to the anatomy of the
retina and later brain areas as well as to the perception of that part
of the visual field. So if we're discussing what you see where your eyes
are centered, that's "foveal vision" or just the "fovea". Finally, when
talking about locations in the visual field, the distance from the fovea
is the &lt;strong&gt;eccentricity&lt;/strong&gt; and it's measured in &lt;a href="https://en.wikipedia.org/wiki/Visual_angle"&gt;degrees of visual
angle&lt;/a&gt;, such that you'd say
"the image was presented at 10 degrees eccentricity" when describing the
stimuli in an experiment.&lt;/p&gt;
&lt;p&gt;The visual system is a portion of the brain (in primates, quite a large
portion!) and so, like the rest of the brain, is made up of specialized
cells called neurons. As a computational neuroscientist, I don't think
so much about all the interesting biology of neurons, and really only
think about them in terms of their activity. People spend their entire
lives studying neuronal activity, but for my purposes all we need to
know is that it's the way neurons communicate with each other, so if
something isn't reflected in neuronal activity, the visual system
doesn't know about it. To return to our color example, ultraviolet light
has no effect on cone activity, and so the visual system knows nothing
about it.&lt;/p&gt;
&lt;p&gt;If we want to understand the visual system, we should understand what
makes neurons active and how that activity changes. One of the
foundational results in visual neuroscience was the discovery of
receptive fields in the 1950s and 1960s by David Hubel and Torsten
Wiesel. They found that neurons in cat V1 got active when they moved a
bar into a certain portion of the visual field, and were not active when
the bar was in any other portion of space&lt;sup id=sf-vss-2020-project-9-back&gt;&lt;a href=#sf-vss-2020-project-9 class=simple-footnote title="You can find footage of this experiment on YouTube (the clicking noise is the neurons firing action potentials, and the more often that happens, the more active they are)."&gt;9&lt;/a&gt;&lt;/sup&gt;. This is the neuron's
&lt;strong&gt;receptive field&lt;/strong&gt;, the portion of the visual field that the neuron
cares about&lt;sup id=sf-vss-2020-project-10-back&gt;&lt;a href=#sf-vss-2020-project-10 class=simple-footnote title=" The concept comes from Sherrington, who used it to describe the area of skin from which a scratch reflex could be elicited in a dog."&gt;10&lt;/a&gt;&lt;/sup&gt;. These receptive fields grow larger as you
move away from the fovea and also as you go deeper into the visual
system, so that foveal retina receptive fields are &lt;em&gt;tiny&lt;/em&gt; (the size of a
cone), whereas peripheral V4 receptive fields are &lt;em&gt;giant&lt;/em&gt; (a quarter of
your visual field). In the beginning of this section, I pointed out that
your vision gets worse as you move away from the fovea, and the way it
does this seems a lot like it's "getting bigger": things in your
periphery appear somewhat blurry and hard to distinguish from each
other. That seems like it might be related to receptive fields growing
larger as you move away from the fovea, so that's what we're going to
focus on. There may be other important differences between the fovea and
periphery, but for this project, the &lt;em&gt;only&lt;/em&gt; difference is that receptive
fields have grown larger.&lt;/p&gt;
&lt;p&gt;Visual neurons are sensitive to more than just a region of space. Hubel
and Wiesel found neurons that only responded if the bar was in a certain
portion of space &lt;em&gt;and&lt;/em&gt; had a certain orientation (such as vertical or
horizontal). This led to the idea that neurons are sensitive to
"features" as well as locations&lt;sup id=sf-vss-2020-project-11-back&gt;&lt;a href=#sf-vss-2020-project-11 class=simple-footnote title="I put &amp;quot;features&amp;quot; in scare quotes because I have lots of feelings about the features neurons are responsive to – they're often not nearly as human-interpretable as we'd like, and I think the idea that V1 neurons are &amp;quot;edge-detectors&amp;quot;, a common interpretation of Hubel and Wiesel's studies (including by the original authors), has led to a lot of confusion in visual neuroscience. But that's outside the scope of this post. Though I do recommend reading Adelson and Bergen's great paper on The Plenoptic Function if you're interested in this."&gt;11&lt;/a&gt;&lt;/sup&gt;. In V1, the main features are orientation
and size&lt;sup id=sf-vss-2020-project-12-back&gt;&lt;a href=#sf-vss-2020-project-12 class=simple-footnote title="Technically, spatial frequency, but for the purposes of this post we can call it size."&gt;12&lt;/a&gt;&lt;/sup&gt;, and across all of
V1, they, along with location, are all represented. This information
about orientation and size is called &lt;strong&gt;spectral energy&lt;/strong&gt;. We know each
orientation is represented everywhere in the visual field, but size
isn't (there aren't neurons that respond to small things in the
periphery and neurons responding to big things might not be found in the
fovea&lt;sup id=sf-vss-2020-project-13-back&gt;&lt;a href=#sf-vss-2020-project-13 class=simple-footnote title="This is actually the topic of the first major project I worked on in grad school, as presented at VSS 2018. I still haven't finished it yet, however, because science is slow."&gt;13&lt;/a&gt;&lt;/sup&gt;). However, for our models, we're going to say
all orientations and all sizes are represented everywhere in the visual
field&lt;sup id=sf-vss-2020-project-14-back&gt;&lt;a href=#sf-vss-2020-project-14 class=simple-footnote title="Changing this is one of the extensions of this project that we're working towards: removing unnecessary information about size / spatial frequency based on location in the visual field."&gt;14&lt;/a&gt;&lt;/sup&gt;.
Different visual areas care about different things. Retina does not care
about orientation or size: for our purposes, it only cares about
brightness&lt;sup id=sf-vss-2020-project-15-back&gt;&lt;a href=#sf-vss-2020-project-15 class=simple-footnote title="Folks who actually study the retina will not like this characterization, which ignores lots of interesting stuff that happens in the retina, but it's enough for our purposes."&gt;15&lt;/a&gt;&lt;/sup&gt;&lt;sup id=sf-vss-2020-project-16-back&gt;&lt;a href=#sf-vss-2020-project-16 class=simple-footnote title="Later areas in the visual system get much more complicated quickly, and there's not nearly as much agreement on them as there is about V1."&gt;16&lt;/a&gt;&lt;/sup&gt;. These things that the brain areas care about are called
&lt;strong&gt;summary statistics&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Those are the core ideas for our models: brain areas compute summary
statistics and the main thing that changes with eccentricity is the size
of these receptive fields. So let's put them together: what do we do
with those summary statistics in those receptive fields? Let's take the
simple approach and just average them. So, a neuron's activity is based
on how much of its favorite stuff is in its favorite area of the visual
field. A given neuron in V1, for example, might care about how much
"vertical-ness" of a certain size there is in a given patch of the
visual field, and measuring that in an image is a pretty good way to
predict how active that neuron will be when the animal is shown the
image. A given neuron in the retina, on the other hand, might care how
bright a certain patch of the visual field is. Note that the neurons are
ignoring the details that gave rise to those statistics (the retina
neuron just cares about the average brightness in its favorite area, not
whether that brightness came from a really bright object and a really
dark object, or two medium-bright objects) – which means we're throwing
away information! This is where we return to the concept I discussed in
Unseen Vision: if we're throwing away information, that means we can
find metamers. Also note that the amount of information the model throws
away increases both as the receptive field grows and as the number of
statistics shrink, and these happen independently&lt;sup id=sf-vss-2020-project-17-back&gt;&lt;a href=#sf-vss-2020-project-17 class=simple-footnote title="By which I mean, for a given number of statistics, you'll throw away more information as the receptive fields grow, and, for a given size of receptive field, you'll throw away more information as the number of statistics shrinks."&gt;17&lt;/a&gt;&lt;/sup&gt;. I'll return to this idea when I describe the
experiment in more detail in the next section.&lt;/p&gt;
&lt;p&gt;When we build these models, we have two choices: the statistics they
calculate and the size of the windows (I'm going to refer to them as
"&lt;strong&gt;pooling windows&lt;/strong&gt;" from now on, and use "receptive fields" only to
refer to actual neurons) they average them in. We pick the statistics
based on our understanding of the visual system. This is important – in
no part of this study do we test different statistics to see which is
best. We build in the assumption (based on decades of research) that we
can summarize an important part of neuronal activity in the retina and
V1 by using brightness and spectral energy. We're also going to say that
window size grows with eccentricity&lt;sup id=sf-vss-2020-project-18-back&gt;&lt;a href=#sf-vss-2020-project-18 class=simple-footnote title="Specifically, based on a literature review shown in Figure 1a in this paper, we're going to say that their width grows linearly with eccentricity"&gt;18&lt;/a&gt;&lt;/sup&gt;,
and the only thing we need to find in our experiment is how quickly it
grows. This &lt;strong&gt;scaling&lt;/strong&gt;, as we call it, is the model's only free
parameter, i.e., it's the only thing we fit to the data. Finding it is
the goal of the experiment that we have not yet had a chance to run,
because of COVID.&lt;/p&gt;
&lt;p&gt;To summarize, in this project, we built models of the retina and V1,
which average summary statistics in windows that grow larger with
eccentricity. To see how well they model human perception, we generated
model metamers, images that are physically different but that the model
thinks are identical. In the next section, I'll discuss the experiment
we're planning on running in order to test those models.&lt;/p&gt;
&lt;h1&gt;Experiment&lt;/h1&gt;
&lt;p&gt;Still working on this!&lt;/p&gt;&lt;ol class=simple-footnotes&gt;&lt;li id=sf-vss-2020-project-1&gt;There's a part of the brain betwen the retina and V1, a
region of the thalamus, deep in the brain, called the lateral geniculate
nucleus. However, in visual neuroscience we often consider it to be a
relay station that just transmits signals between the retina and V1, so
we ignore it in this project &lt;a href=#sf-vss-2020-project-1-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-2&gt;At least, under the
conditions of our experiment. &lt;a href=#sf-vss-2020-project-2-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-3&gt;This is known as a feed-forward approach to the brain,
    assuming information flows through the brain in one direction and
    ignoring all the feedback that happens. &lt;a href=#sf-vss-2020-project-3-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-4&gt; See &lt;a href="https://ece.uwaterloo.ca/~z70wang/publications/SPM09.pdf"&gt;this
    paper&lt;/a&gt;
    from Zhou Wang and Alan Bovik for more details. &lt;a href=#sf-vss-2020-project-4-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-5&gt;There were some
people, it turns out, who could do it with two primaries – people with
&lt;a href="https://en.wikipedia.org/wiki/Dichromacy"&gt;dichromacy&lt;/a&gt;, a form of color
blindness). &lt;a href=#sf-vss-2020-project-5-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-6&gt; The current understanding
is that it comes down to differences in what you (implicitly) assumed
the lighting condition was when the photo was taken. If you thought the
light was yellow-tinted, you'll see the dress as black and blue; if you
thought it was blue-tinted, you'd see the dress as white and gold.
&lt;a href="https://en.wikipedia.org/wiki/The_dress#Scientific_explanations"&gt;Wikipedia&lt;/a&gt;
has a decent explanation on this.  &lt;a href=#sf-vss-2020-project-6-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-7&gt;To my
knowledge, there are no agreed upon boundaries for any of these terms,
except for the fovea; there is an anatomical boundary where the actual
pit of the fovea ends. &lt;a href=#sf-vss-2020-project-7-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-8&gt;Interestingly, not all animals have
foveas, and some birds have two! It seems to be present in animals that
need precise information about the location of objects, like primates,
birds, and cats. But prey animals, such as mice and rabbits, do not have
foveas, and often have quite poor visual acuity. Again, this comes down
to the fact that the visual system is about capturing useful information
to the organism, and fine spatial information is not important to
mice. &lt;a href=#sf-vss-2020-project-8-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-9&gt;You can find footage of
this &lt;a href="https://www.youtube.com/watch?v=KE952yueVLA"&gt;experiment on
YouTube&lt;/a&gt; (the clicking
noise is the neurons firing action potentials, and the more often that
happens, the more active they are). &lt;a href=#sf-vss-2020-project-9-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-10&gt; The concept comes from
&lt;a href="https://en.wikipedia.org/wiki/Receptive_field"&gt;Sherrington&lt;/a&gt;, who used
it to describe the area of skin from which a scratch reflex could be
elicited in a dog. &lt;a href=#sf-vss-2020-project-10-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-11&gt;I put "features" in scare quotes
because I have lots of feelings about the features neurons are
responsive to – they're often not nearly as human-interpretable as we'd
like, and I think the idea that V1 neurons are "edge-detectors", a
common interpretation of Hubel and Wiesel's studies (including by the
original authors), has led to a lot of confusion in visual neuroscience.
But that's outside the scope of this post. Though I do recommend reading
Adelson and Bergen's great paper on &lt;a href="http://persci.mit.edu/pub_pdfs/elements91.pdf"&gt;The Plenoptic
Function&lt;/a&gt; if you're
interested in this. &lt;a href=#sf-vss-2020-project-11-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-12&gt;Technically, &lt;a href="https://en.wikipedia.org/wiki/Spatial_frequency"&gt;spatial
frequency&lt;/a&gt;, but for the
purposes of this post we can call it size. &lt;a href=#sf-vss-2020-project-12-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-13&gt;This is actually the topic of the &lt;a href="https://osf.io/knjqy/"&gt;first
major&lt;/a&gt; project I worked on in grad school, as
presented at VSS 2018. I still haven't finished it yet, however, because
science is slow. &lt;a href=#sf-vss-2020-project-13-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-14&gt;Changing this is one of the extensions of this project that
we're working towards: removing unnecessary information about size /
spatial frequency based on location in the visual field. &lt;a href=#sf-vss-2020-project-14-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-15&gt;Folks who actually study the retina will not like this
characterization, which ignores lots of interesting stuff that happens
in the retina, but it's enough for our purposes. &lt;a href=#sf-vss-2020-project-15-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-16&gt;Later
areas in the visual system get much more complicated quickly, and
there's not nearly as much agreement on them as there is about
V1. &lt;a href=#sf-vss-2020-project-16-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-17&gt;By which I mean,
for a given number of statistics, you'll throw away more information as
the receptive fields grow, and, for a given size of receptive field,
you'll throw away more information as the number of statistics
shrinks. &lt;a href=#sf-vss-2020-project-17-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;li id=sf-vss-2020-project-18&gt;Specifically, based on a
literature review shown in Figure 1a in &lt;a href="https://www.cns.nyu.edu/pub/eero/freeman10-reprint.pdf"&gt;this
paper&lt;/a&gt;, we're
going to say that their width grows linearly with eccentricity &lt;a href=#sf-vss-2020-project-18-back class=simple-footnote-back&gt;↩&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;</content><category term="posts"/><category term="science"/></entry><entry><title>Emacs python setup</title><link href="https://wfbroderick.com/emacs-python-setup.html" rel="alternate"/><published>2019-01-01T00:00:00-05:00</published><updated>2019-01-01T00:00:00-05:00</updated><author><name>William F. Broderick</name></author><id>tag:wfbroderick.com,2019-01-01:/emacs-python-setup.html</id><summary type="html">&lt;p&gt;I got a new laptop recently and so set up Emacs on it for the first
time. Since my emacs &lt;code&gt;init.el&lt;/code&gt; file has gotten progressively messier
over the past several years, I took the opportunity to think about the
variety of python packages I use and set things up …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I got a new laptop recently and so set up Emacs on it for the first
time. Since my emacs &lt;code&gt;init.el&lt;/code&gt; file has gotten progressively messier
over the past several years, I took the opportunity to think about the
variety of python packages I use and set things up cleanly. The
following is what I settled on; the steps involved weren't as clear as I
had hoped, so I'm writing this all down in case I need to do it again.&lt;/p&gt;
&lt;p&gt;Some notes on how I use Emacs and python before I start:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;I use &lt;code&gt;conda&lt;/code&gt; to manage my virtual environments. Most projects, but
    not all, have a corresponding &lt;code&gt;environment.yml&lt;/code&gt; file in their
    repository. I installed &lt;code&gt;miniconda&lt;/code&gt; at &lt;code&gt;~/miniconda3&lt;/code&gt; (conda version
    4.5.12).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I do the early parts of my development, the playing around with
    ideas before I start writing scripts, in a Jupyter Notebook. I also
    do the end of my analysis in them, creating plots and putting the
    parts together in them. I keep most of my functions in scripts,
    runnable from the command line so that I can submit them to NYU's
    computing cluster. Therefore, I don't need the full IDE experience;
    I don't need to run a shell session from within Emacs, and I prefer
    using Jupyter to
    &lt;a href="https://github.com/tkf/emacs-ipython-notebook"&gt;ein&lt;/a&gt;,
    &lt;a href="https://orgmode.org/worg/org-contrib/babel/intro.html"&gt;org-babel&lt;/a&gt;,
    or anything similar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;I primarily use &lt;a href="https://github.com/dimitri/el-get/"&gt;el-get&lt;/a&gt; to
    manage my Emacs packages, falling back on
    &lt;a href="https://github.com/jwiegley/use-package"&gt;use-package&lt;/a&gt; when an
    appropriate &lt;code&gt;el-get&lt;/code&gt; recipe isn't available. My configurations for
    both of these are very basic (and I installed &lt;code&gt;use-package&lt;/code&gt; using
    &lt;code&gt;el-get&lt;/code&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because I don't need the full IDE experience and want to use &lt;code&gt;conda&lt;/code&gt; to
manage my virtual environments, I cannot use
&lt;a href="https://github.com/jorgenschaefer/elpy"&gt;Elpy&lt;/a&gt;, which combines a variety
of python-related packages together with a minimum of setup difficulty
(&lt;a href="https://realpython.com/emacs-the-best-python-editor/"&gt;here's&lt;/a&gt; a guide
to getting started with it). So I decided on using
&lt;a href="https://tkf.github.io/emacs-jedi/latest/"&gt;jedi.el&lt;/a&gt; (with the &lt;a href="https://company-mode.github.io/"&gt;company
backend&lt;/a&gt;) for auto-completion, the
included python-mode (in Emacs 26.1) for basic syntax highlighting,
&lt;a href="https://github.com/necaris/conda.el"&gt;conda.el&lt;/a&gt; for managing virtual
environments, and &lt;a href="https://www.flycheck.org/en/latest/"&gt;flycheck&lt;/a&gt; (with
&lt;a href="https://www.pylint.org/"&gt;pylint&lt;/a&gt; and
&lt;a href="http://flake8.pycqa.org/en/latest/"&gt;flake8&lt;/a&gt;) for syntax- and
style-checking. Wanting to use &lt;code&gt;conda&lt;/code&gt; and the company backend meant
things were slightly more complicated to set up.&lt;/p&gt;
&lt;p&gt;First, &lt;code&gt;conda.el&lt;/code&gt;. This was very straightforward to setup just following
the instructions on the &lt;a href="https://github.com/necaris/conda.el#basic-usage"&gt;Github
page&lt;/a&gt;. The following is
the relevant block in my &lt;code&gt;init.el&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;use-package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conda&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;:ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;conda&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; if you want auto-activation (see below for details), include:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;conda-env-autoactivate-mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;custom-set-variables&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;conda-anaconda-home&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, &lt;code&gt;company-mode&lt;/code&gt; and &lt;code&gt;jedi.el&lt;/code&gt;, both of which I can install using
&lt;code&gt;el-get&lt;/code&gt;. (Note that, if you want to use jedi with the company backend,
&lt;em&gt;do not&lt;/em&gt; install the regular &lt;code&gt;jedi.el&lt;/code&gt;: you should only install
&lt;code&gt;company-jedi&lt;/code&gt;, see &lt;a href="https://github.com/syohex/emacs-company-jedi/issues/6"&gt;this
issue&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my:el-get-packages&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;company-mode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get-bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;elpa:jedi-core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get-bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;company-jedi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;:depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;company-mode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;eval-after-load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company-jedi&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jedi:server-command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3/envs/emacs-jedi/bin/python&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jedi:server-script&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;company-jedi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my:el-get-packages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, I can't use the automatic &lt;code&gt;jedi:install-server&lt;/code&gt; command, and so
need to do some manual set up. I mostly followed the instructions from
Update 2 of this &lt;a href="https://stackoverflow.com/a/21704533"&gt;stackoverflow
answer&lt;/a&gt;, with some changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a conda environment (for current example the environment is
    named emacs-jedi) by doing: &lt;code&gt;conda create -n emacs-jedi python&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the following python packages: &lt;code&gt;jedi&lt;/code&gt;, &lt;code&gt;sexpdata&lt;/code&gt;, &lt;code&gt;epc&lt;/code&gt;.
    Only &lt;code&gt;jedi&lt;/code&gt; is on conda, so I did the following: &lt;code&gt;pip
            install sexpdata epc; conda install jedi&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the jediepcserver. Navigate to the &lt;code&gt;jedi-core&lt;/code&gt; install
    directory (probably &lt;code&gt;~/.emacs.d/el-get/jedi-core&lt;/code&gt;; it should contain
    &lt;code&gt;jediepcserver.py&lt;/code&gt;), then run: &lt;code&gt;python setup.py install&lt;/code&gt; (note the
    directory has to exist, so the &lt;code&gt;(el-get-bundle
            elpa:jedi-core)&lt;/code&gt; needs to be run before this).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After doing the above, &lt;code&gt;company-jedi&lt;/code&gt; should now be setup. The following
are the relevant config blocks in my &lt;code&gt;init.el&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;conda-postactivate-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;jedi:stop-server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;conda-postdeactivate-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;jedi:stop-server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my/python-mode-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-to-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;company-backends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;company-jedi&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;python-mode-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;my/python-mode-hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;python-mode-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;jedi:setup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jedi:complete-on-dot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally, install and set-up flycheck. This is relatively
straightforward: flycheck can be installed using &lt;code&gt;el-get&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my:el-get-packages&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;flycheck&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my:el-get-packages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, install the pylint and flake8 executables, in the base conda
environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;conda&lt;span class="w"&gt; &lt;/span&gt;activate&lt;span class="w"&gt; &lt;/span&gt;base
pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;flake8&lt;span class="w"&gt; &lt;/span&gt;pylint
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And tell flycheck where to find the executables (and add a couple extra
lines of configuration):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;after-init-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;global-flycheck-mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq-default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-emacs-lisp-load-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;inherit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-flake8-maximum-line-length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-python-pylint-executable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3/bin/pylint&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-python-flake8-executable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3/bin/flake8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once that's done, all you need to do is check things are set up. Open up
any python file in Emacs and type &lt;code&gt;C-u C-c ! v&lt;/code&gt; to see the status of
flycheck and, if pylint and flake8 are deactivated, type &lt;code&gt;C-u C-c !
x&lt;/code&gt; to activate them.&lt;/p&gt;
&lt;p&gt;If everything works, flycheck will start underlining things in yellow
and red to tell you that things are against the style guide or will
raise errors. If you start to type something, jedi will suggest possible
completions, and all of these will be based on the packages installed in
the appropriate virtual environment.&lt;/p&gt;
&lt;p&gt;All told, the following shows the relevant parts of my init file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my:el-get-packages&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;company-mode&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;flycheck&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get-bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;elpa:jedi-core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get-bundle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;company-jedi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;:depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;company-mode&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;eval-after-load&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;company-jedi&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jedi:server-command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3/envs/emacs-jedi/bin/python&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jedi:server-script&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;company-jedi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;el-get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;sync&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my:el-get-packages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;use-package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;conda&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;:ensure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;conda&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;;; if you want auto-activation (see below for details), include:&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;conda-env-autoactivate-mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;custom-set-variables&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;conda-anaconda-home&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;conda-postactivate-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;jedi:stop-server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;conda-postdeactivate-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;jedi:stop-server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;my/python-mode-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-to-list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;company-backends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;company-jedi&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;python-mode-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;my/python-mode-hook&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;python-mode-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;jedi:setup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;jedi:complete-on-dot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;add-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;after-init-hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;#&amp;#39;&lt;/span&gt;&lt;span class="nv"&gt;global-flycheck-mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq-default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-emacs-lisp-load-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;#39;inherit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-flake8-maximum-line-length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-python-pylint-executable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3/bin/pylint&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;setq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flycheck-python-flake8-executable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;~/miniconda3/bin/flake8&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="posts"/><category term="emacs"/></entry><entry><title>Accidental illusion</title><link href="https://wfbroderick.com/accidental-illusion.html" rel="alternate"/><published>2017-12-14T00:00:00-05:00</published><updated>2017-12-14T00:00:00-05:00</updated><author><name>William F. Broderick</name></author><id>tag:wfbroderick.com,2017-12-14:/accidental-illusion.html</id><summary type="html">&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can see a webapp I put together to investigate this in
more detail &lt;a href="https://gitlab.com/billbrod/illusory-grating"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While creating two-dimensional sinusoidal gratings and investigating
their local properties, I accidentally created an illusion today, which
I find pretty strong (though the strength of it does depend on its size
and your distance …&lt;/p&gt;</summary><content type="html">&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: You can see a webapp I put together to investigate this in
more detail &lt;a href="https://gitlab.com/billbrod/illusory-grating"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While creating two-dimensional sinusoidal gratings and investigating
their local properties, I accidentally created an illusion today, which
I find pretty strong (though the strength of it does depend on its size
and your distance to it).&lt;/p&gt;
&lt;p&gt;I created the grating shown below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://wfbroderick.com/images/grating-regular.png"&gt;&lt;/p&gt;
&lt;p&gt;and was dropping down some circular windows on it to see if I could
recreate the same image by making a bunch of small sinusoids and
combining them. What I ended up seeing was:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://wfbroderick.com/images/grating-illusion.png"&gt;&lt;/p&gt;
&lt;p&gt;To me, it looks like there's an illusory grating that's perpendicular to
the actual one, but if you focus on the location of those supposed
stripes, putting them in the center of your gaze, they disappear. This
is pretty strong to me, even overwhelming the original grating unless I
focus directly on it. It changes with size / distance though, so that if
I make it really small or stand far away, it looks more cross-hatched, a
combination of the two patterns, than either one alone.&lt;/p&gt;
&lt;p&gt;I'm not entirely sure why this is happening (and, discussing it with the
lab, neither is anyone else). It seems similar to the following classic
illusion, where the Xs in either side of the image look like their
yellow / blue, respectively, but are actually the same color.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://wfbroderick.com/images/color-x-illusion.jpg"&gt;&lt;/p&gt;
&lt;p&gt;A similar illusion is shown below, where the two squares are the same
grayscale level, but appear to be different.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://wfbroderick.com/images/brightness-illusion.png"&gt;&lt;/p&gt;
&lt;p&gt;In all of these, the illusion relies on the fact that our perception of
brightness and color of an object is influenced by the contrast with the
stuff around that object and not just the object itself. This relates to
a recurring theme in vision and perception more generally: we are not
just passively perceiving a perfect copy of the world, our sensory
systems perform computation on our input, which highlights some pieces
of information and throws away other pieces. This happens so fast and
automatically that we often don't realize it's happening until we see
illusions like the above, which make it obvious.&lt;/p&gt;</content><category term="posts"/><category term="illusions"/></entry></feed>