Repository: dPys/PyNets (language: python)

Issue:
-ua to -a conversion and documentation update



Discussion / hints:

Fix patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index c4ebbb5d..d2b35fb1 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -142,7 +142,7 @@ jobs:
 
   get_data:
     machine:
-      image: ubuntu-1604:201903-01
+      image: ubuntu-2004:202010-01
     working_directory: /tmp/src/pynets
     steps:
       - run:
@@ -157,7 +157,7 @@ jobs:
 
   unit_and_integration_tests:
     machine:
-      image: ubuntu-1604:201903-01
+      image: ubuntu-2004:202010-01
     working_directory: /tmp/src/pynets
     environment:
       - FSLDIR: /usr/local/fsl
@@ -179,8 +179,9 @@ jobs:
             sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 5DC22404A6F9F1CA
             sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 78BD65473CB3BD13
             sudo apt-get update
-            sudo apt-get install -y make build-essential
-            sudo apt-get install -y software-properties-common curl libbz2-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev tk-dev liblzma-dev
+            sudo apt-get install -y make build-essential software-properties-common curl libbz2-dev libsqlite3-dev llvm libncurses5-dev libncursesw5-dev tk-dev liblzma-dev
+            sudo apt-get clean
+            sudo apt-get update
             sudo apt-get upgrade
             pyenv install -f 3.6.5 || true
             eval "$(pyenv init -)"
@@ -191,6 +192,7 @@ jobs:
       - run:
           name: Check Python version and upgrade pip
           command: |
+            which python
             python -m pip install -U pip
             python -m pip install --upgrade pip setuptools
       - run:
@@ -201,12 +203,13 @@ jobs:
           no_output_timeout: 30m
           command: |
             sudo apt-get update
-            sudo apt-get install -y tcl-dev apt-transport-https ca-certificates gnupg-agent graphviz sqlite3 dc libquadmath0 zip unzip libglu1 zlib1g-dev libssl-dev libreadline-dev wget libncursesw5-dev xz-utils libffi-dev libfreetype6-dev pkg-config libgsl0-dev g++ git libgomp1 libpng-dev libgl1-mesa-glx libxtst6 libgtk2.0-bin libxft2 lib32ncurses5 libxmu-dev bzip2 gsl-bin libglu1-mesa-dev libglib2.0-0 libglw1-mesa
+            sudo add-apt-repository universe
+            sudo apt-get install -y tcl-dev apt-transport-https ca-certificates gnupg-agent graphviz sqlite3 dc libquadmath0 zip unzip libglu1 zlib1g-dev libssl-dev libreadline-dev wget libncursesw5-dev xz-utils libffi-dev libfreetype6-dev pkg-config libgsl0-dev g++ git libgomp1 libpng-dev libgl1-mesa-glx libxtst6 libgtk2.0-bin libxft2 libncurses5 libxmu-dev bzip2 gsl-bin libglu1-mesa-dev libglib2.0-0 libglw1-mesa
             curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
             curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
             sudo apt-get update
             sudo apt-get upgrade
-            sudo apt-get install openssh-client openssl openssh-server
+            sudo apt-get install -y openssh-client openssl openssh-server
             sudo apt-get install -y git-lfs
             mkdir -p ~/.ssh
             ssh-keyscan -H github.com >> ~/.ssh/known_hosts
@@ -231,13 +234,23 @@ jobs:
           name: Install FSL
           no_output_timeout: 40m
           command: |
-            sudo sh -c 'echo `curl -sSL "http://neuro.debian.net/lists/stretch.us-ca.full"` >> /etc/apt/sources.list.d/neurodebian.sources.list'
+            sudo sh -c 'echo `curl -sSL "http://neuro.debian.net/lists/focal.us-ca.full"` >> /etc/apt/sources.list.d/neurodebian.sources.list'
             sudo apt-key add docker/files/neurodebian.gpg && (apt-key adv --refresh-keys --keyserver hkp://ha.pool.sks-keyservers.net 0xA5D32F012649A5A9 || true)
             sudo apt-get update -qq
             sudo apt-get upgrade
             sudo chmod 777 /usr/local
             sudo curl -sSL https://fsl.fmrib.ox.ac.uk/fsldownloads/fsl-6.0.2-centos7_64.tar.gz | sudo tar xz -C /usr/local --exclude='fsl/doc' --exclude='fsl/data/first' --exclude='fsl/data/atlases' --exclude='fsl/data/possum' --exclude='fsl/src' --exclude='fsl/extras/src' --exclude='fsl/bin/fslview*' --exclude='fsl/bin/FSLeyes' --exclude='fsl/bin/*_gpu*' --exclude='fsl/bin/*_cuda*'
             sudo chmod 777 -R /usr/local/fsl/bin
+      - run:
+          name: Install graph_tool
+          no_output_timeout: 30m
+          command: |
+            echo "deb [ arch=amd64 ] https://downloads.skewed.de/apt focal main" | sudo tee -a /etc/apt/sources.list
+            curl -fsSL https://keys.openpgp.org/vks/v1/by-fingerprint/793CEFE14DBC851A2BFB1222612DEFB798507F25 | sudo apt-key add -
+            sudo apt-get update
+            sudo apt-get install --allow-unauthenticated -y libatlas-base-dev python3-graph-tool
+            sudo ln -sf /usr/lib/python3/dist-packages/graph_tool /opt/circleci/.pyenv/versions/3.6.5/lib/python3.6/site-packages/graph_tool
+            python -c "import graph_tool.all as gt"
       - run:
           name: Update PATH and Define Environment Variable at Runtime
           command: |
diff --git a/.coveragerc b/.coveragerc
index 31872b08..6b59c644 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -38,6 +38,7 @@ omit =
 	pynets/stats/benchmarking.py
 	pynets/stats/embeddings.py
 	pynets/stats/prediction.py
+        pynets/stats/utils.py
 	pynets/registration/register.py
 	pynets/core/interfaces.py
 	pynets/core/cloud_utils.py
diff --git a/AUTHORS.rst b/AUTHORS.rst
index a9b086d7..af38c861 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -8,8 +8,7 @@ Derek Pisner <dpisner@utexas.edu>
 
 Contributors
 ------------
-Ryan Hammonds, Shreyas Fadnavis, Jaewon Chung, Aki Nikolaidis, Kamil Bonna, Miriam Kosik, James Kunert-Graf, Mathias Goncalves, Andrew Reineberg
-Isabella McConley, Adrian Onicas
+Ryan Hammonds, Alex Ayala, Landry J. Luker, Isabella McConley, Kaelen Saythongkham, Aki Nikolaidis, Kamil Bonna, Miriam Kosik, James Kunert-Graf, Andrew Reineberg
 
 Consultants
 -----------
diff --git a/Dockerfile b/Dockerfile
index d77b60f8..e5412ecb 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -42,18 +42,19 @@ RUN apt-get update -qq \
         pkg-config \
         libgsl0-dev \
         openssl \
-	openssh-server \
+        openssh-server \
         jq \
         gsl-bin \
         libglu1-mesa-dev \
         libglib2.0-0 \
         libglw1-mesa \
         libxkbcommon-x11-0 \
-	liblapack-dev \
-	libopenblas-base \
-	sqlite3 \
-	libsqlite3-dev \
-	libquadmath0 \
+        liblapack-dev \
+        libopenblas-base \
+        sqlite3 \
+        libsqlite3-dev \
+        libquadmath0 \
+        gcc-multilib \
     # Configure ssh
     && mkdir /var/run/sshd \
     && echo 'root:screencast' | chpasswd \
@@ -78,7 +79,7 @@ RUN apt-get update -qq \
     && curl -sSL http://neuro.debian.net/lists/stretch.us-tn.full >> /etc/apt/sources.list.d/neurodebian.sources.list && \
     apt-key add /root/.neurodebian.gpg && \
     (apt-key adv --refresh-keys --keyserver hkp://ha.pool.sks-keyservers.net 0xA5D32F012649A5A9 || true) && \
-    apt-get update -qq && apt-get update -qq && apt-get install --no-install-recommends -y fsl-5.0-core && \
+    apt-get update -qq && apt-get install --no-install-recommends -y fsl-5.0-core && \
     apt-get clean && cd /tmp \
     && wget https://fsl.fmrib.ox.ac.uk/fsldownloads/patches/fsl-5.0.10-python3.tar.gz \
     && tar -zxvf fsl-5.0.10-python3.tar.gz \
@@ -86,10 +87,8 @@ RUN apt-get update -qq \
     && rm -r fsl* \
     && chmod 777 -R $FSLDIR/bin \
     && chmod 777 -R /usr/lib/fsl/5.0 \
-#    && echo "tmpfs   /tmp         tmpfs   rw,nodev,nosuid,size=10G          0  0" >> /etc/fstab \
+    && echo "tmpfs   /tmp         tmpfs   rw,nodev,nosuid,size=10G          0  0" >> /etc/fstab \
     && echo "GRUB_CMDLINE_LINUX_DEFAULT="rootflags=uquota,pquota"" >> /etc/default/grub
-#    && wget --retry-connrefused --waitretry=5 --read-timeout=60 --timeout=60 -t 0 -q -O examples.tar.gz "https://osf.io/ye4vf/download" && tar -xvzf examples.tar.gz -C /tmp \
-#    && rm -rf examples.tar.gz
 
 ENV FSLDIR=/usr/share/fsl/5.0 \
     FSLOUTPUTTYPE=NIFTI_GZ \
@@ -129,7 +128,8 @@ RUN echo "FSLDIR=/usr/share/fsl/5.0" >> /home/neuro/.bashrc && \
         libgfortran \
         matplotlib \
         openblas \
-        dask \
+        graph-tool \
+#        dask \
     && pip install certifi -U --ignore-installed \
     && pip install python-dateutil==2.8.0 \
 #    && pip install skggm \
@@ -138,9 +138,9 @@ RUN echo "FSLDIR=/usr/share/fsl/5.0" >> /home/neuro/.bashrc && \
     && mkdir -p ~/.nipype \
     && echo "[monitoring]" > ~/.nipype/nipype.cfg \
     && echo "enabled = true" >> ~/.nipype/nipype.cfg \
+#    && pip install dask[dataframe] --upgrade \
     && pip uninstall -y pandas \
     && pip install pandas -U \
-    && pip install dask[dataframe] --upgrade \
     && cd / \
     && rm -rf /home/neuro/PyNets \
     && rm -rf /home/neuro/.cache \
@@ -172,6 +172,8 @@ RUN echo "FSLDIR=/usr/share/fsl/5.0" >> /home/neuro/.bashrc && \
 	git-lfs \
     && conda clean -tipsy \
     && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
+    && rm -rf /opt/conda/pkgs \
+    && find /opt/conda/ -type f,l -name '*.pyc' -delete \
     && mkdir /inputs && \
     chmod -R 777 /inputs \
     && mkdir /outputs && \
diff --git a/README.md b/README.md
index 573d7e10..6048286e 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ PyNets®
 
 About
 -----
-PyNets is a tool for sampling and analyzing varieties of individual structural and functional connectomes. PyNets enables the user to specify any of a variety of methodological choices known to meaningfully impact brain network node and/or edge definition, and then sample the prescribed connectome estimates in a massively parallel framework conducive to ensemble learning. PyNets is a post-processing workflow, which means that it can be run on virtually any preprocessed fMRI or dMRI data. It draws from Dipy, Nilearn, GrasPy, and Networkx libraries, but is powered primarily through the Nipype workflow engine. PyNets can now also be deployed as a BIDS application, where it takes BIDS derivatives and makes BIDS derivatives.
+PyNets is a tool for sampling and analyzing varieties of individual structural and functional connectomes. Using decision-tree learning, along with extensive bagging and boosting, PyNets is the first application of its kind to facilitate fully-reproducible, parametric sampling of connectome ensembles from neuroimaging data. As a post-processing workflow, PyNets is intended for any preprocessed fMRI or dMRI data in native anatomical space such that it supports normative-referenced connectotyping at the individual-level. Towards these ends, it comprehensively integrates best-practice tractography and functional connectivity analysis methods based open-source libraries such as Dipy and Nilearn, though it is powered primarily through NetworkX and the Nipype workflow engine. PyNets can now also be deployed as a BIDS application, where it takes BIDS derivatives and makes BIDS derivatives.
 
 Install
 -------
@@ -37,7 +37,7 @@ cd PyNets
 
 Hardware Requirements
 ---------------------
-4 vCPUs, 8+ GB free RAM, and at least 15-20 GB of free disk space.
+4 vCPUs, 8+ GB free RAM, and at least 8-16 GB of free disk space.
 
 Operating Systems
 -----------------
@@ -77,7 +77,7 @@ where the `-config` flag specifies that path to a .json configuration spec that
 ```
 {
     "func": { # fMRI options. If you only have functional (i.e. BOLD) data, set each of the `dwi` options to "None"
-            "ct": "None", # Indicates the type(s) of clustering that will be used to generate a clustering-based parcellation. This should be left as "None" if no clustering will be performed, but can be included simultaneously with the `-a` and `-ua` parcellation options.
+            "ct": "None", # Indicates the type(s) of clustering that will be used to generate a clustering-based parcellation. This should be left as "None" if no clustering will be performed, but can be included simultaneously with `-a`.
             "k": "None", # Indicates the number of clusters to generate in a clustering-based parcellation. This should be left as "None" if no clustering will be performed.
             "hp": "['0', '0.028', '0.080']", # Indicates the high-pass frequenc(ies) to apply to signal extraction from nodes.
             "mod": "['partcorr', 'cov']", # Indicates the functional connectivity estimator(s) to use. At least 1 is required for functional connectometry.
@@ -119,7 +119,7 @@ No problem-- you can still run pynets manually:
     pynets -id '002_1' '/Users/dPys/outputs/pynets' \ # where `-id` is an arbitrary subject identifier and the first path is an arbitrary output directory to store derivatives of the workflow.
     -func '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/func/BOLD_PREPROCESSED_IN_ANAT_NATIVE.nii.gz' \ # The fMRI BOLD image data.
     -anat '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/anat/ANAT_PREPROCESSED_NATIVE.nii.gz' \ # The T1w anatomical image. This is mandatory -- PyNets requires a T1/T2-weighted anatomical image unless you are analyzing raw graphs that ahve already been produced.
-    -a 'BrainnetomeAtlasFan2016' \ # An anatomical atlas name. Note that if were to omit the `-a` flag, a custom parcellation file would need to be specified using the `-ua` flag instead or a valid clustering mask (`-cm`) would be needed to generate an individual parcellation. For a complete catalogue of anatomical atlases available in PyNets, see the `Usage` section of the documentation.
+    -a 'BrainnetomeAtlasFan2016' \ # An anatomical atlas name. Note that if were to omit the `-a` flag, a custom parcellation file would need to be specified using the `-a` flag instead or a valid clustering mask (`-cm`) would be needed to generate an individual parcellation. For a complete catalogue of anatomical atlases available in PyNets, see the `Usage` section of the documentation.
     -mod 'partcorr' \ # The connectivity model. In the case of structural connectometry, this becomes the diffusion model type.
     -thr 0.20 \ # Optionally apply a single proportional threshold to the generated graph.
 ```
@@ -130,7 +130,7 @@ No problem-- you can still run pynets manually:
     -bval '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/dwi/BVAL.bval' \ # The b-values.
     -bvec '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/dwi/BVEC.bvec' \ # The b-vectors.
     -anat '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/anat/ANAT_PREPROCESSED_NATIVE.nii.gz' \ # The T1w anatomical image.
-    -ua '/Users/dPys/.atlases/MyCustomParcellation-scale1.nii.gz' '/Users/dPys/.atlases/MyCustomParcellation-scale2.nii.gz' \ # The parcellations.
+    -a '/Users/dPys/.atlases/MyCustomParcellation-scale1.nii.gz' '/Users/dPys/.atlases/MyCustomParcellation-scale2.nii.gz' \ # The parcellations.
     -mod 'csd' 'csa' 'sfm' \ # The (diffusion) connectivity model(s).
     -dg 'prob' 'det'  \ # The tractography direction-getting method.
     -mst -min_thr 0.20 -max_thr 0.80 -step_thr 0.10 # Multi-thresholding from the Minimum-Spanning Tree, with AUC graph analysis.
diff --git a/docs/usage.rst b/docs/usage.rst
index 01895142..8246934f 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -23,7 +23,7 @@ Required
 
 :(B): A supported connectivity model specified with the `-mod` flag. If PyNets is executed in multimodal mode (i.e. with both fMRI and dMRI inputs in the same command-line call), multiple modality-applicable connectivity models should be specified (minimally providing at least one for either modality). PyNets will automatically parse which model is appropriate for which data.
 
-:(C): If an atlas is not specified with the `-a` flag, then a parcellation file must be specified with the `-ua` flag. The following curated list of atlases is currently supported:
+:(C): If an atlas is not specified with the `-a` flag, then a parcellation file must be specified with the `-a` flag. The following curated list of atlases is currently supported:
 
 :Atlas Library:
     - 'atlas_harvard_oxford'
@@ -90,9 +90,9 @@ Custom File Inputs
 
 :`-roi`: (*fMRI + dMRI*) A binarized ROI mask used to constrain connectome node-making to restricted brain regions of the parcellation being used. ROI inputs should be in MNI space.
 
-:`-ua`: (*fMRI + dMRI*) A parcellation/atlas image (in MNI space) used to define nodes of a connectome. Labels should be spatially distinct across hemispheres and ordered with consecutive integers with a value of 0 as the background label. This flag can uniquely be listed with multiple, space-separated file inputs.
+:`-a`: (*fMRI + dMRI*) A parcellation/atlas image (in MNI space) used to define nodes of a connectome. Labels should be spatially distinct across hemispheres and ordered with consecutive integers with a value of 0 as the background label. This flag can uniquely be listed with multiple, space-separated file inputs.
 
-:`-ref`: (*fMRI + dMRI*) An atlas reference .txt file that indices intensities corresponding to atlas labels of the parcellation specified with the `-ua` flag. This label map is used only to delineate node labels manually. Otherwise, PyNets will attempt to perform automated node labeling via AAL, else sequential numeric labels will be used.
+:`-ref`: (*fMRI + dMRI*) An atlas reference .txt file that indices intensities corresponding to atlas labels of the parcellation specified with the `-a` flag. This label map is used only to delineate node labels manually. Otherwise, PyNets will attempt to perform automated node labeling via AAL, else sequential numeric labels will be used.
 
 :`-way`: (*dMRI*) A binarized white-matter ROI mask (in MNI template space) used to constrain tractography in native diffusion space such that streamlines are retained only if they pass within the vicinity of the mask. Like with ROI inputs, waymasks should be in MNI space.
 
@@ -178,7 +178,7 @@ Building upon the previous examples, let's say you now wish to analyze the Defau
     pynets -id '002_1' '/Users/dPys/outputs/pynets' \
     -func '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/func/BOLD_PREPROCESSED_IN_ANAT_NATIVE.nii.gz' \ # The fMRI BOLD image data.
     -anat '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/anat/ANAT_PREPROCESSED_NATIVE.nii.gz' \ # The T1w anatomical image.
-    -ua '/Users/dPys/PyNets/pynets/atlases/MyCustomAtlas.nii.gz' \ # A user-supplied atlas parcellation.
+    -a '/Users/dPys/PyNets/pynets/atlases/MyCustomAtlas.nii.gz' \ # A user-supplied atlas parcellation.
     -mod 'partcorr' \ # The connectivity model.
     -dt -min_thr 0.05 -max_thr 0.10 -step_thr 0.01 -p 1 \ # The thresholding settings.
     -n 'Default' 'Cont' # The resting-state network definitions to restrict node-making from each of the input atlas.
@@ -203,7 +203,7 @@ You wish to generate a structural connectome, using deterministic and probabilis
     -bval '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/dwi/BVAL.bval' \ # The b-values.
     -bvec '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/dwi/BVEC.bvec' \ # The b-vectors.
     -anat '/Users/dPys/PyNets/tests/examples/sub-002/ses-1/anat/ANAT_PREPROCESSED_NATIVE.nii.gz' \ # The T1w anatomical image.
-    -ua '/Users/dPys/.atlases/DesikanKlein2012.nii.gz' '/Users/dPys/.atlases/AALTzourioMazoyer2002.nii.gz' \ # The atlases.
+    -a '/Users/dPys/.atlases/DesikanKlein2012.nii.gz' '/Users/dPys/.atlases/AALTzourioMazoyer2002.nii.gz' \ # The atlases.
     -mod 'csd' 'csa' 'sfm' \ # The connectivity model.
     -dg 'prob' 'det'  \ # The tractography settings.
     -dt -min_thr 0.05 -max_thr 0.10 -step_thr 0.01 -p 1 \ # The thresholding settings.
diff --git a/optional-requirements.txt b/optional-requirements.txt
index 986424ab..89642cf8 100644
--- a/optional-requirements.txt
+++ b/optional-requirements.txt
@@ -1,5 +1,4 @@
 pybind11>=2.5.0
-cpalgorithm>=0.0.14
 pingouin>=0.3.7
 #skggm>=0.2.8
 git+https://github.com/nkoub/multinetx.git@master
diff --git a/pynets/__about__.py b/pynets/__about__.py
index 427b3d41..c5b05d28 100644
--- a/pynets/__about__.py
+++ b/pynets/__about__.py
@@ -6,7 +6,7 @@
 # from ._version import get_versions
 # __version__ = get_versions()['version']
 # del get_versions
-__version__ = "1.0.5"
+__version__ = "1.0.7"
 
 __packagename__ = "pynets"
 __copyright__ = "Copyright 2016, Derek Pisner"
diff --git a/pynets/cli/pynets_benchmark.py b/pynets/cli/pynets_benchmark.py
index b8032e65..65bb9f7a 100644
--- a/pynets/cli/pynets_benchmark.py
+++ b/pynets/cli/pynets_benchmark.py
@@ -14,7 +14,7 @@ def main():
     from joblib import Parallel, delayed
     import tempfile
     import dill
-    from pynets.stats.prediction import make_subject_dict, cleanNullTerms, \
+    from pynets.stats.utils import make_subject_dict, cleanNullTerms, \
         get_ensembles_top, get_ensembles_embedding, \
         build_grid
     from colorama import Fore, Style
@@ -32,15 +32,16 @@ def main():
         sys.exit(1)
 
     #### Parse inputs
-    base_dir = '/scratch/04171/dpisner/HNU/HNU_outs/triple'
-    # base_dir = '/scratch/04171/dpisner/HNU/HNU_outs/outputs_language'
+    base_dir = '/scratch/04171/dpisner/HNU/HNU_outs/archives/triple_network'
+    #base_dir = '/scratch/04171/dpisner/HNU/HNU_outs/outputs_language'
     thr_type = "MST"
     icc = True
     disc = True
-    int_consist = True
-    modality = 'dwi'
+    int_consist = False
+    modality = 'func'
 
-    embedding_types = ['ASE']
+    embedding_types = ['betweenness']
+    #rsns = ['language']
     rsns = ['triple', 'kmeans']
     template = 'CN200'
     # template = 'MNI152_T1'
@@ -52,9 +53,9 @@ def main():
             "smallworldness",
             "modularity"]
 
-    hyperparams_func = ["rsn", "res", "model", 'hpass', 'extract',
+    metaparams_func = ["rsn", "res", "model", 'hpass', 'extract',
                         'smooth']
-    hyperparams_dwi = ["rsn", "res", "model", 'directget', 'minlength',
+    metaparams_dwi = ["rsn", "res", "model", 'directget', 'minlength',
                        'tol']
 
     sessions = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
@@ -68,87 +69,86 @@ def main():
 
     print(Style.RESET_ALL)
 
-    subject_dict_file_path = (
-        f"{base_dir}/pynets_subject_dict_{modality}_{'_'.join(embedding_types)}_{template}.pkl"
-    )
-    subject_mod_grids_file_path = (
-        f"{base_dir}/pynets_modality_grids_{modality}_{'_'.join(embedding_types)}_{template}.pkl"
-    )
-    missingness_summary = (
-        f"{base_dir}/pynets_missingness_summary_{modality}_{'_'.join(embedding_types)}_{template}.csv"
-    )
-    icc_tmps_dir = f"{base_dir}/icc_tmps/{modality}_{'_'.join(embedding_types)}"
-    os.makedirs(icc_tmps_dir, exist_ok=True)
-    if not os.path.isfile(subject_dict_file_path):
-        subject_dict, modality_grids, missingness_frames = make_subject_dict(
-            [modality], base_dir, thr_type, mets, embedding_types, template,
-            sessions, rsns
+    for embedding_type in embedding_types:
+        subject_dict_file_path = (
+            f"{base_dir}/pynets_subject_dict_{modality}_"
+            f"{embedding_type}_{template}.pkl"
         )
-        sub_dict_clean = cleanNullTerms(subject_dict)
-        missingness_frames = [i for i in missingness_frames if
-                              isinstance(i, pd.DataFrame)]
-        if len(missingness_frames) != 0:
-            if len(missingness_frames) > 0:
-                if len(missingness_frames) > 1:
-                    final_missingness_summary = pd.concat(missingness_frames)
-                    final_missingness_summary.to_csv(missingness_summary,
-                                                     index=False)
-                    final_missingness_summary.id = final_missingness_summary.id.str.split('_', expand=True)[0]
-                elif len(missingness_frames) == 1:
-                    final_missingness_summary = missingness_frames[0]
-                    final_missingness_summary.to_csv(missingness_summary, index=False)
-                    final_missingness_summary.id = final_missingness_summary.id.str.split('_', expand=True)[0]
+        subject_mod_grids_file_path = (
+            f"{base_dir}/pynets_modality_grids_{modality}_"
+            f"{embedding_type}_{template}.pkl"
+        )
+        missingness_summary = (
+            f"{base_dir}/pynets_missingness_summary_{modality}_"
+            f"{embedding_type}_{template}.csv"
+        )
+        icc_tmps_dir = f"{base_dir}/icc_tmps/{modality}_" \
+                       f"{embedding_type}"
+        os.makedirs(icc_tmps_dir, exist_ok=True)
+        if not os.path.isfile(subject_dict_file_path):
+            subject_dict, modality_grids, missingness_frames = make_subject_dict(
+                [modality], base_dir, thr_type, mets, [embedding_type], template,
+                sessions, rsns
+            )
+            sub_dict_clean = cleanNullTerms(subject_dict)
+            missingness_frames = [i for i in missingness_frames if
+                                  isinstance(i, pd.DataFrame)]
+            if len(missingness_frames) != 0:
+                if len(missingness_frames) > 0:
+                    if len(missingness_frames) > 1:
+                        final_missingness_summary = pd.concat(missingness_frames)
+                        final_missingness_summary.to_csv(missingness_summary,
+                                                         index=False)
+                        final_missingness_summary.id = final_missingness_summary.id.astype('str').str.split('_', expand=True)[0]
+                    elif len(missingness_frames) == 1:
+                        final_missingness_summary = missingness_frames[0]
+                        final_missingness_summary.to_csv(missingness_summary, index=False)
+                        final_missingness_summary.id = final_missingness_summary.id.astype('str').str.split('_', expand=True)[0]
+                    else:
+                        final_missingness_summary = pd.Series()
                 else:
                     final_missingness_summary = pd.Series()
             else:
                 final_missingness_summary = pd.Series()
+            with open(subject_dict_file_path, "wb") as f:
+                dill.dump(sub_dict_clean, f)
+            f.close()
+            with open(subject_mod_grids_file_path, "wb") as f:
+                dill.dump(modality_grids, f)
+            f.close()
         else:
-            final_missingness_summary = pd.Series()
-        with open(subject_dict_file_path, "wb") as f:
-            dill.dump(sub_dict_clean, f)
-        f.close()
-        with open(subject_mod_grids_file_path, "wb") as f:
-            dill.dump(modality_grids, f)
-        f.close()
-    else:
-        with open(subject_dict_file_path, 'rb') as f:
-            sub_dict_clean = dill.load(f)
-        f.close()
-        with open(subject_mod_grids_file_path, "rb") as f:
-            modality_grids = dill.load(f)
-        f.close()
-        if os.path.isfile(missingness_summary):
-            final_missingness_summary = pd.read_csv(missingness_summary)
-            final_missingness_summary.id = final_missingness_summary.id.str.split('_', expand=True)[0]
-        else:
-            final_missingness_summary = pd.Series()
-    ids = sub_dict_clean.keys()
+            with open(subject_dict_file_path, 'rb') as f:
+                sub_dict_clean = dill.load(f)
+            f.close()
+            with open(subject_mod_grids_file_path, "rb") as f:
+                modality_grids = dill.load(f)
+            f.close()
+            if os.path.isfile(missingness_summary):
+                final_missingness_summary = pd.read_csv(missingness_summary)
+                final_missingness_summary.id = final_missingness_summary.id.astype('str').str.split('_', expand=True)[0]
+            else:
+                final_missingness_summary = pd.Series()
+        ids = sub_dict_clean.keys()
 
-    print(f"MODALITY: {modality}")
-    hyperparams = eval(f"hyperparams_{modality}")
-    hyperparam_dict = {}
+        # print(f"MODALITY: {modality}")
+        metaparams = eval(f"metaparams_{modality}")
+        metaparam_dict = {}
 
-    for alg in embedding_types:
-        print(f"EMBEDDING TYPE: {alg}")
+        # print(f"EMBEDDING TYPE: {embedding_type}")
         # if os.path.isfile(f"{base_dir}/grid_clean_{modality}_{alg}.csv"):
         #     continue
 
-        if alg == 'topology':
+        if embedding_type == 'topology':
             ensembles, df_top = get_ensembles_top(modality, thr_type,
                                                   f"{base_dir}/pynets")
         else:
-            ensembles = get_ensembles_embedding(modality, alg,
+            ensembles = get_ensembles_embedding(modality, embedding_type,
                                                 base_dir)
         grid = build_grid(
-            modality, hyperparam_dict, sorted(list(set(hyperparams))),
+            modality, metaparam_dict, sorted(list(set(metaparams))),
             ensembles)[1]
 
         grid = [i for i in grid if '200' not in i and '400' not in i and '600' not in i and '800' not in i]
-        # In the case that we are using all of the 3 RSN connectomes
-        # (pDMN, coSN, and fECN) in the feature-space,
-        # rather than varying them as hyperparameters (i.e. we assume
-        # they each add distinct variance
-        # from one another) Create an abridged grid, where
 
         if modality == "func":
             modality_grids[modality] = grid
@@ -159,28 +159,33 @@ def main():
 
         with Parallel(
             n_jobs=-1, require="sharedmem", backend='threading',
-            verbose=10, max_nbytes='20000M',
+            verbose=10, max_nbytes='200000M',
             temp_folder=cache_dir
         ) as parallel:
             outs = parallel(
                 delayed(benchmark_reproducibility)(
-                    base_dir, comb, modality, alg, sub_dict_clean,
+                    base_dir, comb, modality, embedding_type, sub_dict_clean,
                     disc, final_missingness_summary, icc_tmps_dir, icc,
-                    mets, ids
+                    mets, ids, template
                 )
                 for comb in grid
             )
+        # outs = []
+        # for comb in grid:
+        #     outs.append(benchmark_reproducibility(base_dir, comb, modality, embedding_type, sub_dict_clean,
+        #             disc, final_missingness_summary, icc_tmps_dir, icc,
+        #             mets, ids))
 
         df_summary = pd.concat([i for i in outs if i is not None and not i.empty], axis=0)
         df_summary = df_summary.dropna(axis=0, how='all')
-        print(f"Saving to {base_dir}/grid_clean_{modality}_{alg}_"
+        print(f"Saving to {base_dir}/grid_clean_{modality}_{embedding_type}_"
               f"{datetime.today().strftime('%Y-%m-%d-%H:%M:%S')}.csv...")
         df_summary.to_csv(f"{base_dir}"
-                          f"/grid_clean_{modality}_{alg}_"
+                          f"/grid_clean_{modality}_{embedding_type}_"
                           f"{datetime.today().strftime('%Y-%m-%d-%H:%M:%S')}.csv", index=False)
 
         # int_consist
-        if int_consist is True and alg == 'topology':
+        if int_consist is True and embedding_type == 'topology':
             try:
                 import pingouin as pg
             except ImportError:
@@ -191,7 +196,7 @@ def main():
             df_summary_cronbach = pd.DataFrame(
                 columns=['modality', 'embedding', 'cronbach'])
             df_summary_cronbach.at[0, "modality"] = modality
-            df_summary_cronbach.at[0, "embedding"] = alg
+            df_summary_cronbach.at[0, "embedding"] = embedding_type
 
             for met in mets:
                 cronbach_ses_list = []
@@ -215,9 +220,9 @@ def main():
                                 comb_tuple = (
                                 atlas, directget, minlength, model,
                                 res, tol)
-                            if comb_tuple in sub_dict_clean[ID][str(ses)][modality][alg].keys():
-                                if isinstance(sub_dict_clean[ID][str(ses)][modality][alg][comb_tuple], np.ndarray):
-                                    id_dict[ID][comb] = sub_dict_clean[ID][str(ses)][modality][alg][comb_tuple][mets.index(met)][0]
+                            if comb_tuple in sub_dict_clean[ID][str(ses)][modality][embedding_type].keys():
+                                if isinstance(sub_dict_clean[ID][str(ses)][modality][embedding_type][comb_tuple], np.ndarray):
+                                    id_dict[ID][comb] = sub_dict_clean[ID][str(ses)][modality][embedding_type][comb_tuple][mets.index(met)][0]
                                 else:
                                     continue
                             else:
@@ -238,9 +243,9 @@ def main():
                         del df_wide
                     del df_wide
                 df_summary_cronbach.at[0, f"average_cronbach_{met}"] = np.nanmean(cronbach_ses_list)
-            print(f"Saving to {base_dir}/grid_clean_{modality}_{alg}_cronbach_"
+            print(f"Saving to {base_dir}/grid_clean_{modality}_{embedding_type}_cronbach_"
                   f"{datetime.today().strftime('%Y-%m-%d-%H:%M:%S')}.csv...")
-            df_summary_cronbach.to_csv(f"{base_dir}/grid_clean_{modality}_{alg}_cronbach{datetime.today().strftime('%Y-%m-%d-%H:%M:%S')}.csv", index=False)
+            df_summary_cronbach.to_csv(f"{base_dir}/grid_clean_{modality}_{embedding_type}_cronbach{datetime.today().strftime('%Y-%m-%d-%H:%M:%S')}.csv", index=False)
 
     return
 
diff --git a/pynets/cli/pynets_collect.py b/pynets/cli/pynets_collect.py
index 71900750..aeab39a7 100644
--- a/pynets/cli/pynets_collect.py
+++ b/pynets/cli/pynets_collect.py
@@ -185,7 +185,7 @@ def load_pd_dfs(file_):
     return df
 
 
-def df_concat(dfs, working_path, modality, drop_cols, args):
+def df_concat(dfs, working_path, modality, drop_cols, args, regen=False):
     import os
     import pandas as pd
     import numpy as np
@@ -269,9 +269,6 @@ def mergedicts(dict1, dict2):
     except:
         pass
 
-    # drop = [i for i in frame.columns if 'participation' in i or 'diversity' in i]
-    # frame = frame.drop(columns=drop)
-
     out_path = f"{working_path}/all_subs_neat_{modality}.csv"
     if os.path.isfile(out_path):
         frame_fill = pd.read_csv(out_path)
@@ -311,13 +308,13 @@ def mergedicts(dict1, dict2):
         del col
 
     # If > 50% of a column is NA/missing
-    #frame = frame.loc[:, frame.isnull().mean() <= 0.20]
+    frame = frame.loc[:, frame.isnull().mean() <= 0.50]
 
     # If > 50% of a column is zero
-    #frame = frame.loc[:, (frame == 0).mean() < .5]
+    frame = frame.loc[:, (frame == 0).mean() < .5]
 
     # If > 50% of a row is NA/missing
-    #frame.dropna(thresh=0.50*len(frame.columns), inplace=True)
+    frame.dropna(thresh=0.50*len(frame.columns), inplace=True)
 
     missingness_dict = summarize_missingness(frame)[0]
     bad_cols = []
@@ -335,32 +332,30 @@ def mergedicts(dict1, dict2):
           'otherwise create an inventory of missingness...')
     par_dict = rerun_dict.copy()
     cache_dir = tempfile.mkdtemp()
-    with Parallel(n_jobs=224, require='sharedmem', verbose=10,
+    with Parallel(n_jobs=-1, require='sharedmem', verbose=10,
                   temp_folder=cache_dir) as parallel:
         outs = parallel(delayed(recover_missing)(bad_col, bad_cols_dict,
                                                  par_dict, modality,
-                        working_path, drop_cols, frame) for
+                        working_path, drop_cols, frame, regen) for
                         bad_col in list(bad_cols_dict.keys()))
 
     if os.path.isfile(f"{working_path}/all_subs_neat_{modality}.csv"):
         os.remove(f"{working_path}/all_subs_neat_{modality}.csv")
     frame.to_csv(f"{working_path}/all_subs_neat_{modality}.csv", index=True)
 
-    # frame = frame.drop(frame.filter(regex="thrtype-PROP"), axis=1)
-    # frame.to_csv(f"{working_path}/all_subs_neat_{modality}.csv", index=True)
-
-    rerun_dicts = []
-    reruns = []
-    for rd, rerun in outs:
-        rerun_dicts.append(rd)
-        reruns.append(rerun)
+    if regen is True:
+        rerun_dicts = []
+        reruns = []
+        for rd, rerun in outs:
+            rerun_dicts.append(rd)
+            reruns.append(rerun)
 
-    for rd in rerun_dicts:
-        rerun_dict = dict(mergedicts(rerun_dict, rd))
+        for rd in rerun_dicts:
+            rerun_dict = dict(mergedicts(rerun_dict, rd))
 
-    # # Re-run collection...
-    # if sum(reruns) > 1:
-    #     build_collect_workflow(args, outs)
+        # Re-run collection...
+        if sum(reruns) > 1:
+            build_collect_workflow(args, outs)
 
     # if len(bad_cols) > 0:
         # print(f"{Fore.LIGHTYELLOW_EX}Dropping columns with excessive "
@@ -375,7 +370,7 @@ def mergedicts(dict1, dict2):
 
 
 def recover_missing(bad_col, bad_cols_dict, rerun_dict, modality,
-                    working_path, drop_cols, frame, regen=True):
+                    working_path, drop_cols, frame, regen):
     import glob
     import os
     atlas = bad_col.split('_')[0] + '_' + bad_col.split('_')[1]
@@ -400,14 +395,7 @@ def recover_missing(bad_col, bad_cols_dict, rerun_dict, modality,
                 f"{working_path}/{sub}/{ses}/{modality}/{atlas}/topology"):
                 print(f"Missing graph analysis for {sub}, {ses} for "
                       f"{atlas}...")
-            else:
-                if regen is True:
-                    from pynets.stats.netstats import collect_pandas_df_make
-                    collect_pandas_df_make(glob.glob(f"{working_path}/{sub}/{ses}/"
-                                                     f"{modality}/{atlas}/"
-                                                     f"topology/*_neat.csv"),
-                                           f"{sub}_{ses}", None, False)
-                rerun = True
+
         outs = [i for i in glob.glob(f"{working_path}/{sub}/{ses}/{modality}/"
                                      f"{atlas}/topology/auc/*")
                 if search_str in i]
@@ -590,26 +578,6 @@ def load_pd_dfs_auc(atlas_name, prefix, auc_file, modality, drop_cols):
         columns=lambda x: re.sub(
             "topology_auc",
             "", x))
-    df_pref = df_pref.rename(
-        columns=lambda x: re.sub(
-            "mod-",
-            "model-", x))
-    df_pref = df_pref.rename(
-        columns=lambda x: re.sub(
-            "res-200",
-            "res-77", x))
-    df_pref = df_pref.rename(
-        columns=lambda x: re.sub(
-            "res-400",
-            "res-135", x))
-    df_pref = df_pref.rename(
-        columns=lambda x: re.sub(
-            "res-600",
-            "res-180", x))
-    df_pref = df_pref.rename(
-        columns=lambda x: re.sub(
-            "res-800",
-            "res-228", x))
 
     bad_cols = [i for i in df_pref.columns if any(ele in i for ele in
                                                   drop_cols)]
@@ -954,13 +922,15 @@ def mergedicts(dict1, dict2):
             del col
 
         # If > 50% of a column is NA/missing
-        # frame = frame.loc[:, frame.isnull().mean() <= 0.20]
+        frame = frame.loc[:, frame.isnull().mean() <= 0.50]
 
         # If > 50% of a column is zero
-        # frame = frame.loc[:, (frame == 0).mean() < .5]
+        frame = frame.loc[:, (frame == 0).mean() < .50]
 
         # If > 50% of a row is NA/missing
-        # frame.dropna(thresh=0.50*len(frame.columns), inplace=True)
+        frame.dropna(thresh=0.50*len(frame.columns), inplace=True)
+
+        # frame.dropna(thresh=0.50 * len(frame.id), axis=1, inplace=True)
 
         missingness_dict = summarize_missingness(frame)[0]
         bad_cols = []
@@ -978,7 +948,7 @@ def mergedicts(dict1, dict2):
               'otherwise create an inventory of missingness...')
         par_dict = rerun_dict.copy()
         cache_dir = tempfile.mkdtemp()
-        with Parallel(n_jobs=224, require='sharedmem', verbose=10,
+        with Parallel(n_jobs=-1, require='sharedmem', verbose=10,
                       temp_folder=cache_dir) as parallel:
             outs = parallel(delayed(recover_missing)(bad_col, bad_cols_dict,
                                                      par_dict, modality,
@@ -1019,7 +989,7 @@ def mergedicts(dict1, dict2):
         return frame, rerun_dict
 
     def recover_missing(bad_col, bad_cols_dict, rerun_dict, modality,
-                        working_path, drop_cols, frame, regen=True):
+                        working_path, drop_cols, frame, regen=False):
         import glob
         import os
         atlas = bad_col.split('_')[0] + '_' + bad_col.split('_')[1]
@@ -1220,7 +1190,7 @@ def load_pd_dfs_auc(atlas_name, prefix, auc_file, modality, drop_cols):
         if modality == 'dwi':
             df_pref = df_pref.rename(
                 columns=lambda x: re.sub(
-                    "nodetype-parc_samples-\d{1,5}0000streams_tracktype-local_",
+                    "nodetype-parc_samples-*streams_tracktype-local_",
                     "", x))
 
         df_pref = df_pref.rename(
@@ -1239,26 +1209,6 @@ def load_pd_dfs_auc(atlas_name, prefix, auc_file, modality, drop_cols):
             columns=lambda x: re.sub(
                 "topology_auc",
                 "", x))
-        df_pref = df_pref.rename(
-            columns=lambda x: re.sub(
-                "mod-",
-                "model-", x))
-        df_pref = df_pref.rename(
-            columns=lambda x: re.sub(
-                "res-200",
-                "res-77", x))
-        df_pref = df_pref.rename(
-            columns=lambda x: re.sub(
-                "res-400",
-                "res-135", x))
-        df_pref = df_pref.rename(
-            columns=lambda x: re.sub(
-                "res-600",
-                "res-180", x))
-        df_pref = df_pref.rename(
-            columns=lambda x: re.sub(
-                "res-800",
-                "res-228", x))
 
         bad_cols = [i for i in df_pref.columns if any(ele in i for ele in
                                                       drop_cols)]
@@ -2150,32 +2100,28 @@ def main():
     args_dict_all['v'] = False
     #args_dict_all['pm'] = '48,67'
     args_dict_all['pm'] = '128,500'
-    #args_dict_all['pm'] = '224,2000'
-    #args_dict_all['basedir'] = '/working/tuning_set/outputs_clustering/pynets'
-    #args_dict_all['basedir'] = '/working/tuning_set/outputs_shaeffer/pynets'
-    #args_dict_all['basedir'] = '/working/tuning_set/outputs_language/pynets'
+    #args_dict_all['pm'] = '224,2000
     #args_dict_all['basedir'] = '/scratch/04171/dpisner/HNU/HNU_outs/triple/pynets'
-    #args_dict_all['basedir'] = '/scratch/04171/dpisner/HNU/HNU_outs/triple_network/pynets'
-    #args_dict_all['basedir'] = '/scratch/04171/dpisner/HNU/HNU_outs/visual/pynets'
     args_dict_all['basedir'] = '/scratch/04171/dpisner/HNU/HNU_outs/outputs_language/pynets'
-    #args_dict_all['basedir'] = '/scratch/04171/dpisner/tuning_set/outputs_shaeffer/pynets'
     args_dict_all['work'] = '/tmp/work/func'
     args_dict_all['modality'] = 'func'
     args_dict_all['dc'] = ['diversity_coefficient',
                            'participation_coefficient',
-                           'average_local_efficiency',
-                           'weighted_transitivity',
-                           'communicability_centrality',
-                           'average_clustering',
+                        #   'average_local_efficiency',
+                        #   'weighted_transitivity',
+                        #   'communicability_centrality',
+                        #   'average_clustering',
                            'average_local_clustering_nodewise',
                            'average_local_efficiency_nodewise',
-                           'degree_centrality',
+                        #   'degree_centrality',
                            'samples-2000streams',
                            'samples-7700streams',
+                           'MNI152_T1',
                         #   'rsn-kmeans_',
                         #   'rsn-triple_'
                         #   "_minlength-0",
                         #   'degree_assortativity_coefficient',
+                        #   'smallworldness',
                            'ward',
                             "variance",
                            "res-1000", "smooth-2fwhm"]
@@ -2265,3 +2211,4 @@ def main():
     __spec__ = "ModuleSpec(name='builtins', loader=<class '_frozen" \
                "_importlib.BuiltinImporter'>)"
     main()
+
diff --git a/pynets/cli/pynets_predict.py b/pynets/cli/pynets_predict.py
index 5c41f9b9..b30f582a 100644
--- a/pynets/cli/pynets_predict.py
+++ b/pynets/cli/pynets_predict.py
@@ -7,8 +7,136 @@
 from pynets.stats.prediction import *
 
 
+def get_parser():
+    """Parse command-line inputs"""
+    import argparse
+    from pynets.__about__ import __version__
+
+    verstr = f"pynets v{__version__}"
+
+    # Parse args
+    parser = argparse.ArgumentParser(
+        description="PyNets: A Fully-Automated Workflow for Reproducible"
+                    " Functional and Structural Connectome Ensemble Learning")
+    parser.add_argument(
+        "-basedir",
+        metavar="Output directory",
+        help="Specify the path to the base output directory with group-level"
+             " pynets derivatives.\n",
+    )
+    parser.add_argument(
+        "-modality",
+        nargs=1,
+        default="func",
+        choices=["dwi", "func"],
+        help="Specify data modality from which to collect data. Options are"
+             " `dwi` and `func`. Currently, only one can be specified at a "
+             "time. Default is functional",
+    )
+    parser.add_argument(
+        "-session_label",
+        help="""The label(s) of the session that should be analyzed.
+        The label  corresponds to ses-<participant_label> from the BIDS spec
+        (so it does not include "ses-"). If this parameter is not provided
+        all sessions should be analyzed. Multiple sessions can be specified
+         with a space separated list.""",
+        nargs=1,
+        default=None,
+    )
+    parser.add_argument(
+        "-et",
+        metavar="Embedding type",
+        default="topology",
+        choices=["ASE", "OMNI", "topology"],
+        help="Specify the embedding method of interest.\n",
+    )
+    parser.add_argument(
+        "-tv",
+        metavar="Target Variable",
+        default="dep_1",
+        nargs="+",
+        choices=["rumination_persist_phenotype",
+                 "depression_persist_phenotype", "dep_2", 'rum_2', 'rum_1',
+                 'dep_1'],
+        help="Specify the target outcome variables of interest, "
+             "separated by space.\n",
+    )
+    parser.add_argument(
+        "-thrtype",
+        default="MST",
+        nargs=1,
+        choices=[
+            "MST",
+            "PROP",
+            "DISP"],
+        help="Specify the thresholding method used when sampling the "
+             "connectome ensemble. Default is MST.\n",
+    )
+    parser.add_argument(
+        "-temp",
+        metavar="MNI Template",
+        default="MNI152_T1",
+        nargs=1,
+        choices=[
+            "colin27",
+            "MNI152_T1",
+            "CN200",
+        ],
+        help="Include this flag to specify a template, other than MNI152_T1 "
+             "that was used to sample the connectome ensemble.\n",
+    )
+    parser.add_argument(
+        "-pm",
+        metavar="Cores,memory",
+        default="auto",
+        help="Number of cores to use, number of GB of memory to use for single"
+             " subject run, entered as two integers seperated by comma. "
+             "Otherwise, default is `auto`, which uses all resources detected"
+             " on the current compute node.\n",
+    )
+    parser.add_argument(
+        "-plug",
+        metavar="Scheduler type",
+        default="MultiProc",
+        nargs=1,
+        choices=[
+            "Linear",
+            "MultiProc",
+            "SGE",
+            "PBS",
+            "SLURM",
+            "SGEgraph",
+            "SLURMgraph",
+            "LegacyMultiProc",
+        ],
+        help="Include this flag to specify a workflow plugin other than the"
+             " default MultiProc.\n",
+    )
+    parser.add_argument(
+        "-v",
+        default=False,
+        action="store_true",
+        help="Verbose print for debugging.\n")
+    parser.add_argument(
+        "-work",
+        metavar="Working directory",
+        default="/tmp/work",
+        help="Specify the path to a working directory for pynets to run."
+             " Default is /tmp/work.\n",
+    )
+    parser.add_argument("--version", action="version", version=verstr)
+    return parser
+
+
 def main():
     import json
+    import pandas as pd
+    import os
+    import sys
+    import dill
+    from pynets.stats.utils import make_feature_space_dict, \
+        make_subject_dict, cleanNullTerms
+    from pynets.core.utils import mergedicts
     from colorama import Fore, Style
     try:
         import pynets
@@ -23,52 +151,67 @@ def main():
               " flag.\n")
         sys.exit(1)
 
-    #base_dir = "/working/tuning_set/outputs_shaeffer/func_ml"
-    #base_dir = "/working/tuning_set/outputs_shaeffer"
-    base_dir = "/working/tuning_set/outputs_clustering"
-    #base_dir = "/working/tuning_set/outputs_language"
-    #base_dir = "/working/tuning_set/outputs_clustering/func_ml"
-    df = pd.read_csv(
-        "/working/tuning_set/outputs_shaeffer/df_rum_persist_all.csv",
-        index_col=False
-    )
+    # pre_args = get_parser().parse_args()
+    #
+    # args["base_dir"] = pre_args.basedir
+    # base_dir = args["base_dir"]
+    base_dir = "/working/tuning_set/outputs_final"
 
-    # User-Specified #
-    embedding_type = 'ASE'
-    modality = "dwi"
+    # args["target_vars"] = pre_args.tv
+    # target_vars = args["target_vars"]
     target_vars = ["rumination_persist_phenotype",
                    "depression_persist_phenotype",
                    "dep_2", 'rum_2', 'rum_1', 'dep_1']
 
-    rsns = ["triple", "kmeans"]
-    #rsns = ["tripleRUM", "kmeansRUM"]
-    #rsns = ["language"]
+    # args["embedding_type"] = pre_args.et[0]
+    # embedding_type = args["embedding_type"]
+    embedding_type = 'ASE'
 
-    sessions = ["1"]
+    # args["modality"] = pre_args.modality[0]
+    # modality = args["modality"]
+    modality = "func"
 
-    # Hard-Coded #
+    # args["thr_type"] = pre_args.thrtype[0]
+    # thr_type = pre_args.thrtype
     thr_type = "MST"
+
+    # args["template"] = pre_args.thrtype[0]
+    # template = pre_args.temp
     template = "MNI152_T1"
+
+    data_file = "/working/tuning_set/outputs_final/df_rum_persist_all.csv"
+
+    rsns = ["triple", "kmeans", "language"]
+
+    # args["sessions"] = pre_args.thrtype[0]
+    # sessions = pre_args.session_label
+    sessions = ["1"]
+
+    # Hard-Coded #
     mets = [
         "global_efficiency",
         "average_shortest_path_length",
-        "degree_assortativity_coefficient",
+        "average_degree_centrality",
         "average_eigenvector_centrality",
         "average_betweenness_centrality",
         "modularity",
+        "degree_assortativity_coefficient"
         "smallworldness",
     ]
     hyperparams_func = ["rsn", "res", "model", "hpass", "extract", "smooth"]
     hyperparams_dwi = ["rsn", "res", "model", "directget", "minlength", "tol"]
 
     subject_dict_file_path = (
-        f"{base_dir}/pynets_subject_dict_{modality}_{'_'.join(rsns)}_{embedding_type}_{template}_{thr_type}.pkl"
+        f"{base_dir}/pynets_subject_dict_{modality}_{'_'.join(rsns)}_"
+        f"{embedding_type}_{template}_{thr_type}.pkl"
     )
     subject_mod_grids_file_path = (
-        f"{base_dir}/pynets_modality_grids_{modality}_{'_'.join(rsns)}_{embedding_type}_{template}_{thr_type}.pkl"
+        f"{base_dir}/pynets_modality_grids_{modality}_{'_'.join(rsns)}_"
+        f"{embedding_type}_{template}_{thr_type}.pkl"
     )
     missingness_summary = (
-        f"{base_dir}/pynets_missingness_summary_{modality}_{'_'.join(rsns)}_{embedding_type}_{template}_{thr_type}.csv"
+        f"{base_dir}/pynets_missingness_summary_{modality}_{'_'.join(rsns)}_"
+        f"{embedding_type}_{template}_{thr_type}.csv"
     )
 
     if not os.path.isfile(subject_dict_file_path) or not os.path.isfile(
@@ -101,6 +244,12 @@ def main():
             modality_grids = dill.load(f)
         f.close()
 
+    # Load in data
+    df = pd.read_csv(
+        data_file,
+        index_col=False
+    )
+
     # Subset only those participants which have usable data
     for ID in df["participant_id"]:
         if len(ID) == 1:
@@ -111,13 +260,49 @@ def main():
     df = df[df["participant_id"].isin(list(sub_dict_clean.keys()))]
     df['sex'] = df['sex'].map({1:0, 2:1})
     df = df[
-        ["participant_id", "age", "num_visits", "sex"] + target_vars
+        ["participant_id", "age", "sex", "num_visits", "DAY_LAG",
+         'dataset'] + target_vars
     ]
 
+    good_grids = []
+    for grid_param in modality_grids[modality]:
+        grid_finds = []
+        for ID in df["participant_id"]:
+            if ID not in sub_dict_clean.keys():
+                print(f"ID: {ID} not found...")
+                continue
+
+            if str(sessions[0]) not in sub_dict_clean[ID].keys():
+                print(f"Session: {sessions[0]} not found for ID {ID}...")
+                continue
+
+            if modality not in sub_dict_clean[ID][str(sessions[0])].keys():
+                print(f"Modality: {modality} not found for ID {ID}, "
+                      f"ses-{sessions[0]}...")
+                continue
+
+            if embedding_type not in sub_dict_clean[ID][str(sessions[0])][modality].keys():
+                print(
+                    f"Modality: {modality} not found for ID {ID}, ses-{sessions[0]}, "
+                    f"{embedding_type}..."
+                )
+                continue
+
+            if grid_param in list(sub_dict_clean[ID][str(sessions[0])][modality][embedding_type].keys()):
+                grid_finds.append(grid_param)
+        if len(grid_finds) < 0.75*len(df["participant_id"]):
+            print(f"Less than 75% of {grid_param} found. Removing from grid...")
+            continue
+        else:
+            good_grids.append(grid_param)
+
+    modality_grids[modality] = good_grids
+
     ml_dfs_dict = {}
     ml_dfs_dict[modality] = {}
-    dict_file_path = f"{base_dir}/pynets_ml_dict_{modality}_{'_'.join(rsns)}_" \
-                     f"{embedding_type}_{template}_{thr_type}.pkl"
+    dict_file_path = f"{base_dir}/pynets_ml_dict_{modality}_" \
+                     f"{'_'.join(rsns)}_{embedding_type}_{template}_" \
+                     f"{thr_type}.pkl"
     if not os.path.isfile(dict_file_path) or not \
         os.path.isfile(dict_file_path):
         ml_dfs = {}
@@ -150,9 +335,7 @@ def main():
     for d in outs:
         ml_dfs = dict(mergedicts(ml_dfs, d))
 
-    # with open('pynets_ml_dict_func_topology.pkl', "rb") as f:
-    #     ml_dfs = dill.load(f)
-    # f.close()
+    ml_dfs = cleanNullTerms(ml_dfs)
 
     feature_spaces = {}
 
@@ -160,7 +343,11 @@ def main():
     out_dict = {}
     for recipe in ml_dfs[modality][embedding_type].keys():
         try:
-            out_dict[str(recipe)] = ml_dfs[modality][embedding_type][recipe].to_json()
+            df = ml_dfs[modality][embedding_type][recipe]
+            df.reset_index(inplace=True)
+            if 'index' in df.columns:
+                df = df.drop(columns=['index'])
+            out_dict[str(recipe)] = df.to_json()
         except:
             print(f"{recipe} recipe not found...")
             continue
@@ -181,7 +368,6 @@ def main():
     args["target_vars"] = target_vars
     args["embedding_type"] = embedding_type
     args["modality"] = modality
-
     return args
 
 
@@ -198,7 +384,8 @@ def main():
     except:
         pass
     warnings.filterwarnings("ignore")
-    __spec__ = "ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>)"
+    __spec__ = "ModuleSpec(name='builtins', loader=<class '_" \
+               "frozen_importlib.BuiltinImporter'>)"
     args = main()
 
     with Manager() as mgr:
diff --git a/pynets/cli/pynets_run.py b/pynets/cli/pynets_run.py
index b9a615f4..51a0de31 100755
--- a/pynets/cli/pynets_run.py
+++ b/pynets/cli/pynets_run.py
@@ -158,7 +158,7 @@ def get_parser():
         default=None,
         help="Specify the path to the atlas reference .txt file that maps "
              "labels to intensities corresponding to the atlas parcellation "
-             "file specified with the -ua flag.\n",
+             "file specified with the -a flag.\n",
     )
     parser.add_argument(
         "-way",
@@ -169,19 +169,6 @@ def get_parser():
              "constrain tractography in the case of dmri connectome "
              "estimation.\n",
     )
-    parser.add_argument(
-        "-ua",
-        metavar="Path to custom parcellation file",
-        default=None,
-        nargs="+",
-        help="(metaparameter): Optionally specify a path to a "
-             "parcellation/atlas Nifti1Image file in MNI space. Labels should"
-             "be spatially distinct across hemispheres and ordered with "
-             "consecutive integers with a value of 0 as the background label."
-             "If specifying a list of paths to multiple parcellations, "
-             "separate them by space.\n",
-    )
-
     # Modality-pervasive metaparameters
     parser.add_argument(
         "-mod",
@@ -215,44 +202,14 @@ def get_parser():
         metavar="Atlas",
         default=None,
         nargs="+",
-        choices=[
-            "atlas_aal",
-            "atlas_talairach_gyrus",
-            "atlas_talairach_ba",
-            "atlas_talairach_lobe",
-            "atlas_harvard_oxford",
-            "atlas_destrieux_2009",
-            "atlas_msdl",
-            "coords_dosenbach_2010",
-            "coords_power_2011",
-            "atlas_pauli_2017",
-            "destrieux2009_rois",
-            "BrainnetomeAtlasFan2016",
-            "VoxelwiseParcellationt0515kLeadDBS",
-            "Juelichgmthr252mmEickhoff2005",
-            "CorticalAreaParcellationfromRestingStateCorrelationsGordon2014",
-            "whole_brain_cluster_labels_PCA100",
-            "AICHAreorderedJoliot2015",
-            "HarvardOxfordThr252mmWholeBrainMakris2006",
-            "VoxelwiseParcellationt058kLeadDBS",
-        "MICCAI2012MultiAtlasLabelingWorkshopandChallengeNeuromorphometrics",
-            "Hammers_mithAtlasn30r83Hammers2003Gousias2008",
-            "AALTzourioMazoyer2002",
-            "DesikanKlein2012",
-            "AAL2zourioMazoyer2002",
-            "VoxelwiseParcellationt0435kLeadDBS",
-            "AICHAJoliot2015",
-            "whole_brain_cluster_labels_PCA200",
-            "RandomParcellationsc05meanalll43Craddock2011",
-            'sub-colin27_label-L2018_desc-scale1_atlas',
-            'sub-colin27_label-L2018_desc-scale2_atlas',
-            'sub-colin27_label-L2018_desc-scale3_atlas',
-            'sub-colin27_label-L2018_desc-scale4_atlas',
-            'sub-colin27_label-L2018_desc-scale5_atlas'
-        ],
-        help="(metaparameter): Specify an atlas parcellation from nilearn or "
-             "local libraries. If you wish to iterate your pynets run over "
-             "multiple atlases, separate them by space. Available nilearn "
+        help="(metaparameter): Specify an atlas name from nilearn or "
+             "local (pynets) library, and/or specify a path to a custom "
+             "parcellation/atlas Nifti1Image file in MNI space. Labels should"
+             "be spatially distinct across hemispheres and ordered with "
+             "consecutive integers with a value of 0 as the background label."
+             "If specifying a list of paths to multiple parcellations, "
+             "separate them by space. If you wish to iterate your pynets run "
+             "over multiple atlases, separate them by space. Available nilearn "
              "atlases are:"
         "\n\natlas_aal\natlas_talairach_gyrus\natlas_talairach_ba"
              "\natlas_talairach_lobe\n"
@@ -674,6 +631,26 @@ def build_workflow(args, retval):
     start_time = timeit.default_timer()
     print(Style.RESET_ALL)
 
+    # Hard-coded:
+    from pynets.core.utils import load_runconfig
+    hardcoded_params = load_runconfig()
+
+    maxcrossing = hardcoded_params['tracking']["maxcrossing"][0]
+    local_corr = hardcoded_params["clustering_local_conn"][0]
+    track_type = hardcoded_params['tracking']["tracking_method"][0]
+    tiss_class = hardcoded_params['tracking']["tissue_classifier"][0]
+    target_samples = hardcoded_params['tracking']["tracking_samples"][0]
+    use_parcel_naming = hardcoded_params["parcel_naming"][0]
+    step_list = hardcoded_params['tracking']["step_list"]
+    curv_thr_list = hardcoded_params['tracking']["curv_thr_list"]
+    nilearn_parc_atlases = hardcoded_params["nilearn_parc_atlases"]
+    nilearn_coord_atlases = hardcoded_params["nilearn_coord_atlases"]
+    nilearn_prob_atlases = hardcoded_params["nilearn_prob_atlases"]
+    local_atlases = hardcoded_params["local_atlases"]
+    template_name = hardcoded_params['template'][0]
+    roi_neighborhood_tol = \
+        hardcoded_params['tracking']["roi_neighborhood_tol"][0]
+
     # Set Arguments to global variables
     ID = args.id
     outdir = f"{args.output_dir}/pynets"
@@ -717,7 +694,8 @@ def build_workflow(args, retval):
                         multi_subject_multigraph = []
                         for id in ID:
                             # multi_subject_multigraph.append(
-                            #     [str(g) for g in graph_iter if id in str(g) and modality in str(g)])
+                            #     [str(g) for g in graph_iter if id in str(g)
+                            #     and modality in str(g)])
                             multi_subject_multigraph.append(
                                 [str(g) for g in graph_iter if id in str(g)])
                     else:
@@ -981,35 +959,68 @@ def build_workflow(args, retval):
             multi_nets = None
     else:
         multi_nets = None
-    uatlas = args.ua
-    if uatlas:
-        if len(uatlas) > 1:
-            user_atlas_list = uatlas
-            uatlas = None
-        elif uatlas == ["None"]:
-            uatlas = None
-            user_atlas_list = None
+
+    atlas_ins = args.a
+    if atlas_ins is not None:
+        # Parse uatlas files from atlas names
+        uatlas = []
+        atlas = []
+
+        for atl in atlas_ins:
+            if atl in nilearn_parc_atlases or atl in nilearn_coord_atlases or \
+                atl in nilearn_prob_atlases or atl in local_atlases:
+                atlas.append(atl)
+            elif '/' in atl:
+                uatlas.append(atl)
+                if not os.path.isfile(atl):
+                    print(f"{atl} may not be an existing file path. "
+                          f"You can safely ignore this warning if you are "
+                          f"using container-mounted directory paths.")
+            else:
+                raise ValueError(f"{atl} is not in the pynets atlas library "
+                                 f"nor is it a file path to a parcellation "
+                                 f"file")
+
+        if len(atlas) == 0:
+           atlas = None
+
+        if len(uatlas) == 0:
+           uatlas = None
+
+        if uatlas:
+            if len(uatlas) > 1:
+                user_atlas_list = uatlas
+                uatlas = None
+            elif uatlas == ["None"]:
+                uatlas = None
+                user_atlas_list = None
+            else:
+                uatlas = uatlas[0]
+                user_atlas_list = None
         else:
-            uatlas = uatlas[0]
             user_atlas_list = None
-    else:
-        user_atlas_list = None
-    atlas = args.a
-    if atlas:
-        if (isinstance(atlas, list)) and (len(atlas) > 1):
-            multi_atlas = atlas
-            atlas = None
-        elif atlas == ["None"]:
-            multi_atlas = None
-            atlas = None
-        elif isinstance(atlas, list):
-            atlas = atlas[0]
-            multi_atlas = None
+
+        if atlas:
+            if (isinstance(atlas, list)) and (len(atlas) > 1):
+                multi_atlas = atlas
+                atlas = None
+            elif atlas == ["None"]:
+                multi_atlas = None
+                atlas = None
+            elif isinstance(atlas, list):
+                atlas = atlas[0]
+                multi_atlas = None
+            else:
+                atlas = None
+                multi_atlas = None
         else:
-            atlas = None
             multi_atlas = None
     else:
+        uatlas = None
+        atlas = None
         multi_atlas = None
+        user_atlas_list = None
+
     min_length = args.ml
     if min_length:
         if (isinstance(min_length, list)) and (len(min_length) > 1):
@@ -1067,26 +1078,6 @@ def build_workflow(args, retval):
         "---------\n"
     )
 
-    # Hard-coded:
-    from pynets.core.utils import load_runconfig
-    hardcoded_params = load_runconfig()
-
-    maxcrossing = hardcoded_params['tracking']["maxcrossing"][0]
-    local_corr = hardcoded_params["clustering_local_conn"][0]
-    track_type = hardcoded_params['tracking']["tracking_method"][0]
-    tiss_class = hardcoded_params['tracking']["tissue_classifier"][0]
-    target_samples = hardcoded_params['tracking']["tracking_samples"][0]
-    use_parcel_naming = hardcoded_params["parcel_naming"][0]
-    step_list = hardcoded_params['tracking']["step_list"]
-    curv_thr_list = hardcoded_params['tracking']["curv_thr_list"]
-    nilearn_parc_atlases = hardcoded_params["nilearn_parc_atlases"]
-    nilearn_coord_atlases = hardcoded_params["nilearn_coord_atlases"]
-    nilearn_prob_atlases = hardcoded_params["nilearn_prob_atlases"]
-    local_atlases = hardcoded_params["local_atlases"]
-    template_name = hardcoded_params['template'][0]
-    roi_neighborhood_tol = \
-        hardcoded_params['tracking']["roi_neighborhood_tol"][0]
-
     if track_type == "particle":
         tiss_class = "cmc"
 
@@ -1715,7 +1706,7 @@ def build_workflow(args, retval):
             and (atlas is None)
         ):
             print(
-                "ERROR: the -ua flag cannot be used alone with the clustering"
+                "ERROR: the -a flag cannot be used alone with the clustering"
                 " option. Use the `-cm` flag instead."
             )
             retval["return_code"] = 1
@@ -1765,6 +1756,8 @@ def build_workflow(args, retval):
                 )
                 retval["return_code"] = 1
                 return retval
+            else:
+                print(f"{Fore.GREEN}Using curated atlas: {Fore.BLUE}{atlas}")
         else:
             if (
                 (uatlas is None)
@@ -1876,8 +1869,8 @@ def build_workflow(args, retval):
         else:
             for em in error_margin_list:
                 if float(roi_neighborhood_tol) <= float(em):
-                    print('\nERROR: roi_neighborhood_tol preset cannot be less '
-                          'than the value of the structural connectome '
+                    print('\nERROR: roi_neighborhood_tol preset cannot be '
+                          'less than the value of the structural connectome '
                           'error_margin parameter.')
                     retval["return_code"] = 1
                     return retval
@@ -1929,7 +1922,8 @@ def build_workflow(args, retval):
             print(f"{Fore.GREEN}Diffusion-Weighted Image:\n "
                   f"{Fore.BLUE}{dwi_file}")
             if not os.path.isfile(dwi_file):
-                print(f"\nERROR: {dwi_file} does not exist. Ensure that you are"
+                print(f"\nERROR: {dwi_file} does not exist. "
+                      f"Ensure that you are"
                       f" only specifying absolute paths.")
                 retval["return_code"] = 1
                 return retval
@@ -1948,7 +1942,8 @@ def build_workflow(args, retval):
         if waymask is not None:
             print(f"{Fore.GREEN}Waymask:\n {Fore.BLUE}{waymask}")
             if not os.path.isfile(waymask):
-                print(f"\nERROR: {waymask} does not exist. Ensure that you are "
+                print(f"\nERROR: {waymask} does not exist. "
+                      f"Ensure that you are "
                       f"only specifying absolute paths.")
                 retval["return_code"] = 1
                 return retval
@@ -2011,9 +2006,9 @@ def build_workflow(args, retval):
             for _func_file in func_file_list:
                 print(f"{Fore.GREEN}BOLD Image:\n {Fore.BLUE}{_func_file}")
                 if not os.path.isfile(_func_file):
-                    print(f"\nERROR: ERROR: {_func_file} does not exist. Ensure "
-                            f"that you are only specifying "
-                            f"absolute paths.")
+                    print(f"\nERROR: ERROR: {_func_file} does not exist. "
+                          f"Ensure that you are only specifying "
+                          f"absolute paths.")
                     retval["return_code"] = 1
                     return retval
         else:
@@ -2413,6 +2408,7 @@ def init_wf_single_subject(
                     "norm",
                     "binary",
                     "multimodal",
+                    "embed",
                 ]
             ),
             name="inputnode",
@@ -2454,6 +2450,7 @@ def init_wf_single_subject(
         inputnode.inputs.norm = norm
         inputnode.inputs.binary = binary
         inputnode.inputs.multimodal = multimodal
+        inputnode.inputs.embed = embed
 
         if func_file or dwi_file:
             meta_wf = workflow_selector(
@@ -2591,9 +2588,9 @@ def init_wf_single_subject(
             nested=True,
             imports=import_list,
         )
-        # net_mets_node.synchronize = True
-        net_mets_node._n_procs = 1
-        net_mets_node._mem_gb = 5
+        net_mets_node.synchronize = True
+        net_mets_node._n_procs = runtime_dict["NetworkAnalysis"][0]
+        net_mets_node._mem_gb = runtime_dict["NetworkAnalysis"][1]
 
         collect_pd_list_net_csv_node = pe.Node(
             niu.Function(
@@ -2604,16 +2601,18 @@ def init_wf_single_subject(
             name="AggregateOutputs",
             imports=import_list,
         )
-        collect_pd_list_net_csv_node._n_procs = 1
-        collect_pd_list_net_csv_node._mem_gb = 3
+        collect_pd_list_net_csv_node._n_procs = \
+            runtime_dict["AggregateOutputs"][0]
+        collect_pd_list_net_csv_node._mem_gb = \
+            runtime_dict["AggregateOutputs"][1]
 
         # Combine dataframes across models
         combine_pandas_dfs_node = pe.Node(
             interface=CombineOutputs(),
             name="CombineOutputs",
             imports=import_list)
-        combine_pandas_dfs_node._n_procs = 1
-        combine_pandas_dfs_node._mem_gb = 2
+        combine_pandas_dfs_node._n_procs = runtime_dict["CombineOutputs"][0]
+        combine_pandas_dfs_node._mem_gb = runtime_dict["CombineOutputs"][1]
 
         final_outputnode = pe.Node(
             niu.IdentityInterface(fields=["combination_complete"]),
@@ -2654,7 +2653,8 @@ def init_wf_single_subject(
                     elif 'dwi' in op.dirname(graph):
                         outdir = f"{outdir}/dwi"
                 else:
-                    graph_name = op.basename(graph).split(op.splitext(graph)[1])[0]
+                    graph_name = op.basename(graph
+                                             ).split(op.splitext(graph)[1])[0]
                     print("Using single custom graph input...")
                     print(graph_name)
                     atlas = f"{graph_name}_{ID}"
@@ -2679,6 +2679,7 @@ def init_wf_single_subject(
                 step_thr,
                 wf,
                 net_mets_node,
+                runtime_dict
             )
         else:
             wf.connect(
@@ -2712,6 +2713,7 @@ def init_wf_single_subject(
                         ("plot_switch", "plot_switch"),
                         ("multi_nets", "multi_nets"),
                         ("multimodal", "multimodal"),
+                        ("embed", "embed"),
                     ],
                 ),
                 (
@@ -3468,9 +3470,7 @@ def wf_multi_subject(
             for file_ in [i for i in glob.glob(
                     f"{subj_dir}/func/*") if os.path.isfile(i)] + \
                          [i for i in glob.glob(
-                    f"{subj_dir}/*/func/*") if os.path.isfile(i)] + \
-                         [i for i in glob.glob(
-                    f"{subj_dir}/*/func/*/*") if os.path.isfile(i)]:
+                    f"{subj_dir}/func/*/*") if os.path.isfile(i)]:
                 if ("reor-RAS" in file_) or ("res-" in file_):
                     try:
                         os.remove(file_)
@@ -3480,9 +3480,7 @@ def wf_multi_subject(
             for file_ in [i for i in glob.glob(
                     f"{subj_dir}/dwi/*") if os.path.isfile(i)] + \
                          [i for i in glob.glob(
-                    f"{subj_dir}/*/dwi/*") if os.path.isfile(i)] + \
-                         [i for i in glob.glob(
-                    f"{subj_dir}/*/dwi/*/*") if os.path.isfile(i)]:
+                    f"{subj_dir}/dwi/*/*") if os.path.isfile(i)]:
                 if ("reor-RAS" in file_) or ("res-" in file_) or \
                    ("_bvecs_reor.bvec" in file_):
                     try:
diff --git a/pynets/core/interfaces.py b/pynets/core/interfaces.py
index d51707a2..fa474770 100755
--- a/pynets/core/interfaces.py
+++ b/pynets/core/interfaces.py
@@ -101,7 +101,8 @@ def _run_interface(self, runtime):
                     uatlas = f"{runtime.cwd}{self.inputs.atlas}{'.nii.gz'}"
                 if self.inputs.clustering is False:
                     [uatlas,
-                     labels] = nodemaker.enforce_hem_distinct_consecutive_labels(
+                     labels] = \
+                        nodemaker.enforce_hem_distinct_consecutive_labels(
                         uatlas, label_names=labels)
                 [coords, atlas, par_max, label_intensities] = \
                     nodemaker.get_names_and_coords_of_parcels(uatlas)
@@ -416,7 +417,7 @@ class CombineOutputsInputSpec(BaseInterfaceInputSpec):
     plot_switch = traits.Bool(False, usedefault=True)
     multi_nets = traits.Any(mandatory=False)
     multimodal = traits.Bool(False, usedefault=True)
-
+    embed = traits.Bool(False, usedefault=True)
 
 class CombineOutputsOutputSpec(TraitedSpec):
     """Output interface wrapper for CombineOutputs"""
@@ -440,6 +441,7 @@ def _run_interface(self, runtime):
             self.inputs.plot_switch,
             self.inputs.multi_nets,
             self.inputs.multimodal,
+            self.inputs.embed,
         )
         setattr(self, "_combination_complete", combination_complete)
         return runtime
@@ -529,9 +531,9 @@ def _run_interface(self, runtime):
         clust_mask_temp_path = check_orient_and_dims(
             self.inputs.clust_mask, runtime.cwd, self.inputs.vox_size
         )
-
+        cm_suf = os.path.basename(self.inputs.clust_mask).split('.nii')[0]
         clust_mask_in_t1w_path = f"{runtime.cwd}/clust_mask-" \
-                                 f"{os.path.basename(self.inputs.clust_mask).split('.nii')[0]}_in_t1w.nii.gz"
+                                 f"{cm_suf}_in_t1w.nii.gz"
 
         t1w_brain_tmp_path = fname_presuffix(
             self.inputs.t1w_brain, suffix="_tmp", newpath=runtime.cwd
@@ -1406,7 +1408,7 @@ class _RegisterAtlasDWIOutputSpec(TraitedSpec):
 
     dwi_aligned_atlas_wmgm_int = File(exists=True, mandatory=True)
     dwi_aligned_atlas = File(exists=True, mandatory=True)
-    aligned_atlas_t1mni = File(exists=True, mandatory=True)
+    aligned_atlas_t1w = File(exists=True, mandatory=True)
     node_size = traits.Any(mandatory=True)
     atlas = traits.Any(mandatory=True)
     uatlas_parcels = traits.Any(mandatory=False)
@@ -1433,12 +1435,13 @@ def _run_interface(self, runtime):
         import gc
         import time
         import os
+        import os.path as op
         from pynets.registration import reg_utils as regutils
         from pynets.core.nodemaker import \
             drop_coords_labels_from_restricted_parcellation
         from nipype.utils.filemanip import fname_presuffix, copyfile
         import pkg_resources
-        from scipy.ndimage import binary_opening
+        from pynets.core import utils
 
         template = pkg_resources.resource_filename(
             "pynets", f"templates/{self.inputs.template_name}_brain_"
@@ -1595,8 +1598,9 @@ def _run_interface(self, runtime):
 
         if self.inputs.network or self.inputs.waymask:
             if self.inputs.waymask:
+                wm_suf = os.path.basename(self.inputs.waymask).split('.nii')[0]
                 atlas_name = f"{self.inputs.atlas}_" \
-                             f"{os.path.basename(self.inputs.waymask).split('.nii')[0]}"
+                             f"{wm_suf}"
             else:
                 atlas_name = f"{self.inputs.atlas}_{self.inputs.network}"
         else:
@@ -1625,7 +1629,7 @@ def _run_interface(self, runtime):
         [
             dwi_aligned_atlas_wmgm_int,
             dwi_aligned_atlas,
-            aligned_atlas_t1mni,
+            aligned_atlas_t1w,
         ] = regutils.atlas2t1w2dwi_align(
             uatlas_tmp_path,
             uatlas_parcels_tmp_path,
@@ -1733,7 +1737,7 @@ def _run_interface(self, runtime):
         self._results["dwi_aligned_atlas_wmgm_int"] = \
             dwi_aligned_atlas_wmgm_int
         self._results["dwi_aligned_atlas"] = dwi_aligned_atlas
-        self._results["aligned_atlas_t1mni"] = aligned_atlas_t1mni
+        self._results["aligned_atlas_t1w"] = aligned_atlas_t1w
         self._results["node_size"] = self.inputs.node_size
         self._results["atlas"] = self.inputs.atlas
         self._results["uatlas_parcels"] = uatlas_parcels_tmp_path
@@ -1748,8 +1752,55 @@ def _run_interface(self, runtime):
         self._results["dwi_file"] = self.inputs.dwi_file
         self._results["waymask_in_dwi"] = waymask_in_dwi
 
+        dir_path = utils.do_dir_path(
+            self.inputs.atlas, os.path.dirname(self.inputs.dwi_file)
+        )
+
+        namer_dir = "{}/tractography".format(dir_path)
+        if not op.isdir(namer_dir):
+            os.mkdir(namer_dir)
+
+        if not os.path.isfile(f"{namer_dir}/"
+                              f"{op.basename(self.inputs.fa_path)}"):
+            copyfile(
+                self.inputs.fa_path,
+                f"{namer_dir}/{op.basename(self.inputs.fa_path)}",
+                copy=True,
+                use_hardlink=False,
+            )
+        if not os.path.isfile(f"{namer_dir}/"
+                              f"{op.basename(self.inputs.ap_path)}"):
+            copyfile(
+                self.inputs.ap_path,
+                f"{namer_dir}/{op.basename(self.inputs.ap_path).replace('_tmp', '')}",
+                copy=True,
+                use_hardlink=False,
+            )
+        if not os.path.isfile(f"{namer_dir}/"
+                              f"{op.basename(self.inputs.B0_mask)}"):
+            copyfile(
+                self.inputs.B0_mask,
+                f"{namer_dir}/{op.basename(self.inputs.B0_mask).replace('_tmp', '')}",
+                copy=True,
+                use_hardlink=False,
+            )
+        if not os.path.isfile(f"{namer_dir}/"
+                              f"{op.basename(self.inputs.gtab_file)}"):
+            copyfile(
+                self.inputs.gtab_file,
+                f"{namer_dir}/{op.basename(self.inputs.gtab_file)}",
+                copy=True,
+                use_hardlink=False,
+            )
+        if not os.path.isfile(f"{namer_dir}/{op.basename(aligned_atlas_t1w)}"):
+            copyfile(
+                aligned_atlas_t1w,
+                f"{namer_dir}/{op.basename(aligned_atlas_t1w)}",
+                copy=True,
+                use_hardlink=False,
+            )
+
         reg_tmp = [
-            fa_tmp_path,
             uatlas_tmp_path,
             mni2t1w_warp_tmp_path,
             mni2t1_xfm_tmp_path,
@@ -1758,7 +1809,6 @@ def _run_interface(self, runtime):
             t1w2dwi_bbr_xfm_tmp_path,
             t1w2dwi_xfm_tmp_path,
             wm_gm_int_in_dwi_tmp_path,
-            aligned_atlas_skull,
             t1w_brain_tmp_path
         ]
         for j in reg_tmp:
@@ -1918,7 +1968,6 @@ def _run_interface(self, runtime):
                 t1w_brain_tmp_path2,
                 roi_in_t1w,
                 roi_in_dwi,
-                ap_tmp_path,
                 mni2t1w_warp_tmp_path2,
                 t1wtissue2dwi_xfm_tmp_path,
                 mni2t1_xfm_tmp_path,
@@ -1932,7 +1981,6 @@ def _run_interface(self, runtime):
         self._results["roi"] = roi_in_dwi
 
         reg_tmp = [
-            ap_tmp_path,
             t1w_brain_tmp_path,
             mni2t1w_warp_tmp_path,
             t1wtissue2dwi_xfm_tmp_path,
@@ -2015,7 +2063,8 @@ def _run_interface(self, runtime):
                 use_hardlink=False)
         else:
             if len(anat_mask_existing) > 0 and \
-                 self.inputs.mask is None and op.isfile(anat_mask_existing[0]) \
+                 self.inputs.mask is None and \
+                op.isfile(anat_mask_existing[0]) \
                     and self.inputs.force_create_mask is False:
                 mask_tmp_path = fname_presuffix(
                     anat_mask_existing[0], suffix="_tmp",
@@ -2305,6 +2354,7 @@ class _RegisterAtlasFuncOutputSpec(TraitedSpec):
     node_size = traits.Any()
     atlas = traits.Any()
 
+
 class RegisterAtlasFunc(SimpleInterface):
     """Interface wrapper for RegisterAtlasFunc."""
 
@@ -2676,7 +2726,7 @@ class _TrackingInputSpec(BaseInterfaceInputSpec):
     coords = traits.Any(mandatory=True)
     norm = traits.Any(mandatory=True)
     binary = traits.Bool(False, usedefault=True)
-    atlas_mni = File(exists=True, mandatory=True)
+    atlas_t1w = File(exists=True, mandatory=True)
     fa_path = File(exists=True, mandatory=True)
     waymask = traits.Any(mandatory=False)
     t1w2dwi = File(exists=True, mandatory=True)
@@ -2705,7 +2755,7 @@ class _TrackingOutputSpec(TraitedSpec):
     coords = traits.Any(mandatory=True)
     norm = traits.Any()
     binary = traits.Bool(False, usedefault=True)
-    atlas_mni = File(exists=True, mandatory=True)
+    atlas_t1w = File(exists=True, mandatory=True)
     curv_thr_list = traits.List(mandatory=True)
     step_list = traits.List(mandatory=True)
     fa_path = File(exists=True, mandatory=True)
@@ -2744,7 +2794,8 @@ def _run_interface(self, runtime):
 
         hardcoded_params = load_runconfig()
         use_life = hardcoded_params['tracking']["use_life"][0]
-        roi_neighborhood_tol = hardcoded_params['tracking']["roi_neighborhood_tol"][0]
+        roi_neighborhood_tol = hardcoded_params['tracking'][
+            "roi_neighborhood_tol"][0]
         sphere = hardcoded_params['tracking']["sphere"][0]
 
         dir_path = utils.do_dir_path(
@@ -2766,7 +2817,7 @@ def _run_interface(self, runtime):
             use_hardlink=False)
 
         dwi_img = nib.load(dwi_file_tmp_path, mmap=True)
-        dwi_data = dwi_img.get_fdata().astype('float32')
+        dwi_data = dwi_img.get_fdata(dtype=np.float32)
 
         # Load FA data
         fa_file_tmp_path = fname_presuffix(
@@ -2777,6 +2828,7 @@ def _run_interface(self, runtime):
             fa_file_tmp_path,
             copy=True,
             use_hardlink=False)
+
         fa_img = nib.load(fa_file_tmp_path, mmap=True)
 
         labels_im_file_tmp_path = fname_presuffix(
@@ -3020,7 +3072,8 @@ def _run_interface(self, runtime):
             else:
                 waymask_tmp_path = None
 
-            # Iteratively build a list of streamlines for each ROI while tracking
+            # Iteratively build a list of streamlines for each ROI while
+            # tracking
             print(
                 f"{Fore.GREEN}Target number of cumulative streamlines: "
                 f"{Fore.BLUE} "
@@ -3042,12 +3095,12 @@ def _run_interface(self, runtime):
                 print(f"{Fore.GREEN}Direction-getting type: {Fore.BLUE}"
                       f"Probabilistic")
             elif self.inputs.directget == "clos":
-                print(f"{Fore.GREEN}Direction-getting type: {Fore.BLUE}Closest "
-                      f"Peak")
+                print(f"{Fore.GREEN}Direction-getting type: "
+                      f"{Fore.BLUE}Closest Peak")
             elif self.inputs.directget == "det":
                 print(
-                    f"{Fore.GREEN}Direction-getting type: {Fore.BLUE}Deterministic"
-                    f" Maximum"
+                    f"{Fore.GREEN}Direction-getting type: "
+                    f"{Fore.BLUE}Deterministic Maximum"
                 )
             else:
                 raise ValueError("Direction-getting type not recognized!")
@@ -3084,33 +3137,38 @@ def _run_interface(self, runtime):
             if streamlines is not None:
                 # import multiprocessing
                 # from pynets.core.utils import kill_process_family
-                # return kill_process_family(int(multiprocessing.current_process().pid))
+                # return kill_process_family(int(
+                # multiprocessing.current_process().pid))
 
                 # Linear Fascicle Evaluation (LiFE)
                 if use_life is True:
                     print('Using LiFE to evaluate streamline plausibility...')
-                    from pynets.dmri.dmri_utils import evaluate_streamline_plausibility
+                    from pynets.dmri.dmri_utils import \
+                        evaluate_streamline_plausibility
                     dwi_img = nib.load(dwi_file_tmp_path)
-                    dwi_data = dwi_img.get_fdata().astype('float32')
+                    dwi_data = dwi_img.get_fdata(dtype=np.float32)
                     orig_count = len(streamlines)
 
                     if self.inputs.waymask:
-                        mask_data = nib.load(waymask_tmp_path
-                                             ).get_fdata().astype('bool').astype('int')
+                        mask_data = nib.load(
+                            waymask_tmp_path).get_fdata().astype(
+                            'bool').astype('int')
                     else:
-                        mask_data = nib.load(wm_in_dwi_tmp_path
-                                             ).get_fdata().astype('bool').astype('int')
+                        mask_data = nib.load(
+                            wm_in_dwi_tmp_path).get_fdata().astype(
+                            'bool').astype('int')
                     try:
                         streamlines = evaluate_streamline_plausibility(
                             dwi_data, gtab, mask_data, streamlines,
                             sphere=sphere)
                     except BaseException:
-                        print(f"Linear Fascicle Evaluation failed. Visually checking "
-                              f"streamlines output {namer_dir}/{op.basename(streams)}"
-                              f" is recommended.")
+                        print(f"Linear Fascicle Evaluation failed. "
+                              f"Visually checking streamlines output "
+                              f"{namer_dir}/{op.basename(streams)} is "
+                              f"recommended.")
                     if len(streamlines) < 0.5*orig_count:
-                        raise ValueError('LiFE revealed no plausible streamlines '
-                                         'in the tractogram!')
+                        raise ValueError('LiFE revealed no plausible '
+                                         'streamlines in the tractogram!')
                     del dwi_data, mask_data
 
                 # Save streamlines to trk
@@ -3193,8 +3251,7 @@ def _run_interface(self, runtime):
         self._results["coords"] = self.inputs.coords
         self._results["norm"] = self.inputs.norm
         self._results["binary"] = self.inputs.binary
-        self._results["atlas_mni"] = labels_im_file_tmp_path # TODO: Change
-        # this to self.inputs.atlas_mni when DSN is True
+        self._results["atlas_t1w"] = self.inputs.atlas_t1w
         self._results["curv_thr_list"] = self.inputs.curv_thr_list
         self._results["step_list"] = self.inputs.step_list
         self._results["fa_path"] = fa_file_tmp_path
diff --git a/pynets/core/nodemaker.py b/pynets/core/nodemaker.py
index 1dd616b2..3c15dd2a 100644
--- a/pynets/core/nodemaker.py
+++ b/pynets/core/nodemaker.py
@@ -607,6 +607,34 @@ def get_node_membership(
     return coords_mm, RSN_parcels, net_labels, network
 
 
+def drop_badixs_from_parcellation(uatlas, bad_idxs):
+    import os
+    import nibabel as nib
+    import numpy as np
+    from nipype.utils.filemanip import fname_presuffix
+
+    parcellation_img = nib.load(uatlas)
+
+    bad_idxs = sorted(list(set(bad_idxs)), reverse=True)
+
+    parlist_img_data = parcellation_img.get_fdata()
+    for val in bad_idxs:
+        print(f"Removing: {str(val)}...")
+        parlist_img_data[np.where(parlist_img_data == val)] = 0
+
+    parcellation = fname_presuffix(
+        uatlas, suffix="_pruned",
+        newpath=os.path.dirname(uatlas))
+    nib.save(
+        nib.Nifti1Image(parlist_img_data,
+                        affine=parcellation_img.affine),
+        parcellation)
+
+    print(f"{len(np.unique(parlist_img_data))} parcels remaining")
+    parcellation = enforce_hem_distinct_consecutive_labels(parcellation)[0]
+    return parcellation
+
+
 def parcel_masker(
         roi,
         coords,
@@ -1350,24 +1378,22 @@ def parcel_naming(coords, vox_size):
 def get_brainnetome_node_attributes(node_files, emb_shape):
     import ast
     import re
-    from pynets.stats.prediction import parse_closest_ixs
+    from pynets.stats.utils import parse_closest_ixs
 
     ixs, node_dict = parse_closest_ixs(node_files, emb_shape)
 
     coords = [(i['coord']) for
-              i in node_dict]
+              i in node_dict.values()]
     if isinstance(node_dict[0]['label'], str):
         labels = [
             ast.literal_eval(
                 re.search('({.+})',
                           i['label']).group(0))[
                 'BrainnetomeAtlasFan2016'] for i in
-            node_dict]
+            node_dict.values()]
     else:
-        labels = [
-            list(i['label'])[0][
-                'BrainnetomeAtlasFan2016'] for i in
-            node_dict]
+        labels = [i['label']['BrainnetomeAtlasFan2016'] for i in
+                  node_dict.values()]
 
     return coords, labels, ixs
 
diff --git a/pynets/core/thresholding.py b/pynets/core/thresholding.py
index bcfb6577..1d38bf5e 100644
--- a/pynets/core/thresholding.py
+++ b/pynets/core/thresholding.py
@@ -749,7 +749,7 @@ def local_thresholding_prop(conn_matrix, thr):
         and (len(len_edge_list[-fail_tol:]) -
              len(set(len_edge_list[-fail_tol:])))
         < (fail_tol - 1)
-    ):
+    ) and nx.is_connected(min_t) is True:
         # print(k)
         # print(len_edges)
         len_edge_list.append(len_edges)
@@ -1053,7 +1053,7 @@ def thresh_func(
         atlas_name = f"{atlas}_stage-post_thr"
 
     utils.save_coords_and_labels_to_json(coords, labels, dir_path,
-                                         atlas_name)
+                                         atlas_name, indices=None)
 
     return (
         edge_threshold,
@@ -1100,7 +1100,7 @@ def thresh_struct(
     binary,
     target_samples,
     track_type,
-    atlas_mni,
+    atlas_for_streams,
     streams,
     directget,
     min_length,
@@ -1165,8 +1165,9 @@ def thresh_struct(
         Total number of streamline samples specified to generate streams.
     track_type : str
         Tracking algorithm used (e.g. 'local' or 'particle').
-    atlas_mni : str
-        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
+    atlas_for_streams : str
+        File path to atlas parcellation Nifti1Image in the morphological
+        space of the streamlines.
     streams : str
         File path to save streamline array sequence in .trk format.
     directget : str
@@ -1224,8 +1225,9 @@ def thresh_struct(
         Total number of streamline samples specified to generate streams.
     track_type : str
         Tracking algorithm used (e.g. 'local' or 'particle').
-    atlas_mni : str
-        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
+    atlas_for_streams : str
+        File path to atlas parcellation Nifti1Image in the morphological
+        space of the streamlines.
     streams : str
         File path to save streamline array sequence in .trk format.
     directget : str
@@ -1290,7 +1292,7 @@ def thresh_struct(
         atlas_name = f"{atlas}_stage-post_thr"
 
     utils.save_coords_and_labels_to_json(coords, labels, dir_path,
-                                         atlas_name)
+                                         atlas_name, indices=None)
 
     return (
         edge_threshold,
@@ -1311,7 +1313,7 @@ def thresh_struct(
         binary,
         target_samples,
         track_type,
-        atlas_mni,
+        atlas_for_streams,
         streams,
         directget,
         min_length,
diff --git a/pynets/core/utils.py b/pynets/core/utils.py
index 9b14568b..1c1b096b 100644
--- a/pynets/core/utils.py
+++ b/pynets/core/utils.py
@@ -1210,7 +1210,7 @@ def proportional(k, voxels_list):
 
 
 def collect_pandas_df(
-    network, ID, net_mets_csv_list, plot_switch, multi_nets, multimodal
+    network, ID, net_mets_csv_list, plot_switch, multi_nets, multimodal, embed
 ):
     """
     API for summarizing independent lists of pickled pandas dataframes of
@@ -1282,7 +1282,7 @@ def collect_pandas_df(
                     )
                 )
                 combination_complete_dwi = collect_pandas_df_make(
-                    net_mets_csv_list_dwi, ID, network, plot_switch
+                    net_mets_csv_list_dwi, ID, network, plot_switch, embed
                 )
                 net_mets_csv_list_func = list(
                     set(
@@ -1295,7 +1295,7 @@ def collect_pandas_df(
                     )
                 )
                 combination_complete_func = collect_pandas_df_make(
-                    net_mets_csv_list_func, ID, network, plot_switch
+                    net_mets_csv_list_func, ID, network, plot_switch, embed
                 )
 
                 if (
@@ -1307,7 +1307,7 @@ def collect_pandas_df(
                     combination_complete = False
             else:
                 combination_complete = collect_pandas_df_make(
-                    net_mets_csv_list, ID, network, plot_switch
+                    net_mets_csv_list, ID, network, plot_switch, embed
                 )
     else:
         if multimodal is True:
@@ -1321,7 +1321,7 @@ def collect_pandas_df(
                 )
             )
             combination_complete_dwi = collect_pandas_df_make(
-                net_mets_csv_list_dwi, ID, network, plot_switch
+                net_mets_csv_list_dwi, ID, network, plot_switch, embed
             )
             net_mets_csv_list_func = list(
                 set(
@@ -1333,7 +1333,7 @@ def collect_pandas_df(
                 )
             )
             combination_complete_func = collect_pandas_df_make(
-                net_mets_csv_list_func, ID, network, plot_switch
+                net_mets_csv_list_func, ID, network, plot_switch, embed
             )
 
             if combination_complete_dwi is \
@@ -1343,7 +1343,7 @@ def collect_pandas_df(
                 combination_complete = False
         else:
             combination_complete = collect_pandas_df_make(
-                net_mets_csv_list, ID, network, plot_switch
+                net_mets_csv_list, ID, network, plot_switch, embed
             )
 
     return combination_complete
@@ -1394,16 +1394,16 @@ def load_runconfig():
     fd, temp_path = tempfile.mkstemp()
     shutil.copy2(pkg_resources.resource_filename("pynets", "runconfig.yaml"),
                  temp_path)
-    with open(temp_path, mode='r') as stream:
+    with open(temp_path, mode='r+') as stream:
         hardcoded_params = yaml.load(stream)
     stream.close()
     os.remove(temp_path)
-
+    del stream
     return hardcoded_params
 
 
 def save_coords_and_labels_to_json(coords, labels, dir_path,
-                                   network='all_nodes'):
+                                   network='all_nodes', indices=None):
     """
     Save coordinates and labels to json.
 
@@ -1450,10 +1450,13 @@ def save_coords_and_labels_to_json(coords, labels, dir_path,
     node_list = []
     for node in labels:
         node_dict = {}
-        if consensus_labs is True:
+        if consensus_labs is True and isinstance(node, tuple):
             lab, ix = node
             node_dict['index'] = str(ix)
             node_dict['label'] = str(lab)
+        elif indices is not None:
+            node_dict['index'] = str(indices[i])
+            node_dict['label'] = str(node)
         else:
             node_dict['index'] = str(i)
             node_dict['label'] = str(node)
@@ -1461,7 +1464,8 @@ def save_coords_and_labels_to_json(coords, labels, dir_path,
         node_list.append(node_dict)
         i += 1
 
-    nodes_path = f"{namer_dir}/nodes-{prune_suffices(network)}_count-{len(labels)}.json"
+    nodes_path = f"{namer_dir}/nodes-{prune_suffices(network)}_" \
+                 f"count-{len(labels)}.json"
 
     with open(nodes_path, 'w') as f:
         json.dump(node_list, f)
@@ -1894,8 +1898,8 @@ def check_template_loads(template, template_mask, template_name):
             nib.load(template_mask)
             return print('Local template detected...')
         except ImportError as e:
-            print(e, f"\nCannot load template {template_name} image or template "
-                  f"mask. Do you have git-lfs installed?")
+            print(e, f"\nCannot load template {template_name} image or "
+                     f"template mask. Do you have git-lfs installed?")
 
 
 def save_4d_to_3d(in_file):
diff --git a/pynets/core/workflows.py b/pynets/core/workflows.py
index e984b98e..dd0ed366 100644
--- a/pynets/core/workflows.py
+++ b/pynets/core/workflows.py
@@ -121,6 +121,7 @@ def workflow_selector(
     # Available functional and structural connectivity models
     hardcoded_params = load_runconfig()
     template_name = hardcoded_params["template"][0]
+    embedding_methods = hardcoded_params["embed"]
     try:
         func_models = hardcoded_params["available_models"][
             "func_models"]
@@ -1078,40 +1079,46 @@ def workflow_selector(
             ]
         )
         if embed is True:
-            omni_embedding_node_func = pe.Node(
-                niu.Function(
-                    input_names=["est_path_iterlist", "ID"],
-                    output_names=["out_paths_dwi", "out_paths_func"],
-                    function=embeddings.build_omnetome,
-                ),
-                name="omni_embedding_node_func",
-                imports=import_list,
-            )
-            ase_embedding_node_func = pe.Node(
-                niu.Function(
-                    input_names=["est_path_iterlist", "ID"],
-                    output_names=["out_paths"],
-                    function=embeddings.build_asetomes,
-                ),
-                name="ase_embedding_node_func",
-                imports=import_list,
-            )
-            meta_wf.connect(
-                [
-                    (
-                        pass_meta_ins_func_node,
-                        omni_embedding_node_func,
-                        [("est_path_iterlist", "est_path_iterlist")],
+            if 'OMNI' in embedding_methods:
+                omni_embedding_node_func = pe.Node(
+                    niu.Function(
+                        input_names=["est_path_iterlist", "ID"],
+                        output_names=["out_paths_dwi", "out_paths_func"],
+                        function=embeddings.build_omnetome,
                     ),
-                    (meta_inputnode, omni_embedding_node_func, [("ID", "ID")]),
-                    (
-                        pass_meta_ins_func_node,
-                        ase_embedding_node_func,
-                        [("est_path_iterlist", "est_path_iterlist")],
+                    name="omni_embedding_node_func",
+                    imports=import_list,
+                )
+                meta_wf.connect(
+                    [
+                        (
+                            pass_meta_ins_func_node,
+                            omni_embedding_node_func,
+                            [("est_path_iterlist", "est_path_iterlist")],
+                        ),
+                        (meta_inputnode, omni_embedding_node_func, [("ID", "ID")])
+                    ]
+                )
+            if 'ASE' in embedding_methods:
+                ase_embedding_node_func = pe.Node(
+                    niu.Function(
+                        input_names=["est_path_iterlist", "ID"],
+                        output_names=["out_paths"],
+                        function=embeddings.build_asetomes,
                     ),
-                    (meta_inputnode, ase_embedding_node_func, [("ID", "ID")]),
-                ]
-            )
+                    name="ase_embedding_node_func",
+                    imports=import_list,
+                )
+                meta_wf.connect(
+                    [
+                        (
+                            pass_meta_ins_func_node,
+                            ase_embedding_node_func,
+                            [("est_path_iterlist", "est_path_iterlist")],
+                        ),
+                        (meta_inputnode, ase_embedding_node_func, [("ID", "ID")]),
+                    ]
+                )
     if dwi_file and not func_file:
         meta_wf.connect(
             [
@@ -1133,42 +1140,48 @@ def workflow_selector(
             ]
         )
         if embed is True:
-            omni_embedding_node_struct = pe.Node(
-                niu.Function(
-                    input_names=["est_path_iterlist", "ID"],
-                    output_names=["out_paths_dwi", "out_paths_func"],
-                    function=embeddings.build_omnetome,
-                ),
-                name="omni_embedding_node_struct",
-                imports=import_list,
-            )
-            ase_embedding_node_struct = pe.Node(
-                niu.Function(
-                    input_names=["est_path_iterlist", "ID"],
-                    output_names=["out_paths"],
-                    function=embeddings.build_asetomes,
-                ),
-                name="ase_embedding_node_struct",
-                imports=import_list,
-            )
-            meta_wf.connect(
-                [
-                    (
-                        pass_meta_ins_struct_node,
-                        omni_embedding_node_struct,
-                        [("est_path_iterlist", "est_path_iterlist")],
+            if 'OMNI' in embedding_methods:
+                omni_embedding_node_struct = pe.Node(
+                    niu.Function(
+                        input_names=["est_path_iterlist", "ID"],
+                        output_names=["out_paths_dwi", "out_paths_func"],
+                        function=embeddings.build_omnetome,
                     ),
-                    (meta_inputnode, omni_embedding_node_struct,
-                    [("ID", "ID")]),
-                    (
-                        pass_meta_ins_struct_node,
-                        ase_embedding_node_struct,
-                        [("est_path_iterlist", "est_path_iterlist")],
+                    name="omni_embedding_node_struct",
+                    imports=import_list,
+                )
+                meta_wf.connect(
+                    [
+                        (
+                            pass_meta_ins_struct_node,
+                            omni_embedding_node_struct,
+                            [("est_path_iterlist", "est_path_iterlist")],
+                        ),
+                        (meta_inputnode, omni_embedding_node_struct,
+                        [("ID", "ID")])
+                    ]
+                )
+            if 'ASE' in embedding_methods:
+                ase_embedding_node_struct = pe.Node(
+                    niu.Function(
+                        input_names=["est_path_iterlist", "ID"],
+                        output_names=["out_paths"],
+                        function=embeddings.build_asetomes,
                     ),
-                    (meta_inputnode, ase_embedding_node_struct,
-                     [("ID", "ID")]),
-                ]
-            )
+                    name="ase_embedding_node_struct",
+                    imports=import_list,
+                )
+                meta_wf.connect(
+                    [
+                        (
+                            pass_meta_ins_struct_node,
+                            ase_embedding_node_struct,
+                            [("est_path_iterlist", "est_path_iterlist")],
+                        ),
+                        (meta_inputnode, ase_embedding_node_struct,
+                         [("ID", "ID")]),
+                    ]
+                )
     if multimodal is True:
         mase_embedding_node = pe.Node(
             niu.Function(
@@ -1270,7 +1283,7 @@ def workflow_selector(
                     ]
                 )
 
-            if embed is True:
+            if embed is True and 'MASE' in embedding_methods:
                 meta_wf.connect(
                     [
                         (
@@ -1889,16 +1902,16 @@ def dmri_connectometry(
                 "coords",
                 "norm",
                 "binary",
-                "atlas_mni",
+                "atlas_t1w",
                 "basedir_path",
                 "curv_thr_list",
                 "step_list",
                 "directget",
                 "min_length",
-                "t1_aligned_mni"
+                "t1w_brain"
             ],
             output_names=[
-                "streams_mni",
+                "streams_t1w",
                 "dir_path",
                 "track_type",
                 "target_samples",
@@ -1918,7 +1931,7 @@ def dmri_connectometry(
                 "coords",
                 "norm",
                 "binary",
-                "atlas_mni",
+                "atlas_for_streams",
                 "directget",
                 "warped_fa",
                 "min_length",
@@ -1933,7 +1946,7 @@ def dmri_connectometry(
     streams2graph_node = pe.Node(
         niu.Function(
             input_names=[
-                "atlas_mni",
+                "atlas_for_streams",
                 "streams",
                 "dir_path",
                 "track_type",
@@ -1960,7 +1973,7 @@ def dmri_connectometry(
                 "error_margin",
             ],
             output_names=[
-                "atlas_mni",
+                "atlas_for_streams",
                 "streams",
                 "conn_matrix",
                 "track_type",
@@ -2156,7 +2169,7 @@ def dmri_connectometry(
                                        ])
     save_coords_and_labels_node = pe.Node(
         niu.Function(
-            input_names=["coords", "labels", "dir_path", "network"],
+            input_names=["coords", "labels", "dir_path", "network", "indices"],
             function=utils.save_coords_and_labels_to_json,
             imports=import_list,
         ),
@@ -2400,7 +2413,7 @@ def dmri_connectometry(
         "binary",
         "target_samples",
         "track_type",
-        "atlas_mni",
+        "atlas_for_streams",
         "streams",
         "directget",
         "min_length",
@@ -2429,7 +2442,7 @@ def dmri_connectometry(
         ("binary", "binary"),
         ("target_samples", "target_samples"),
         ("track_type", "track_type"),
-        ("atlas_mni", "atlas_mni"),
+        ("atlas_for_streams", "atlas_for_streams"),
         ("streams", "streams"),
         ("directget", "directget"),
         ("min_length", "min_length"),
@@ -2702,7 +2715,7 @@ def dmri_connectometry(
         "binary",
         "target_samples",
         "track_type",
-        "atlas_mni",
+        "atlas_for_streams",
         "streams",
         "directget",
         "min_length",
@@ -2727,7 +2740,7 @@ def dmri_connectometry(
         "binary",
         "target_samples",
         "track_type",
-        "atlas_mni",
+        "atlas_for_streams",
         "streams",
         "directget",
         "min_length",
@@ -2757,7 +2770,7 @@ def dmri_connectometry(
                     "binary",
                     "target_samples",
                     "track_type",
-                    "atlas_mni",
+                    "atlas_for_streams",
                     "streams",
                     "directget",
                     "min_length",
@@ -2791,7 +2804,7 @@ def dmri_connectometry(
                     "binary",
                     "target_samples",
                     "track_type",
-                    "atlas_mni",
+                    "atlas_for_streams",
                     "streams",
                     "directget",
                     "min_length",
@@ -2833,7 +2846,7 @@ def dmri_connectometry(
                     ("binary", "binary"),
                     ("target_samples", "target_samples"),
                     ("track_type", "track_type"),
-                    ("atlas_mni", "atlas_mni"),
+                    ("atlas_for_streams", "atlas_for_streams"),
                     ("streams", "streams"),
                     ("directget", "directget"),
                     ("min_length", "min_length"),
@@ -2874,7 +2887,7 @@ def dmri_connectometry(
                         ("binary", "binary"),
                         ("target_samples", "target_samples"),
                         ("track_type", "track_type"),
-                        ("atlas_mni", "atlas_mni"),
+                        ("atlas_for_streams", "atlas_for_streams"),
                         ("streams", "streams"),
                         ("directget", "directget"),
                         ("min_length", "min_length"),
@@ -2953,7 +2966,7 @@ def dmri_connectometry(
                         ("node_size", "node_size"),
                         ("edge_threshold", "edge_threshold"),
                         ("prune", "prune"),
-                        ("atlas_mni", "uatlas"),
+                        ("atlas_for_streams", "uatlas"),
                         ("target_samples", "target_samples"),
                         ("norm", "norm"),
                         ("binary", "binary"),
@@ -3136,7 +3149,7 @@ def dmri_connectometry(
                 [
                     ("dwi_aligned_atlas_wmgm_int", "labels_im_file_wm_gm_int"),
                     ("dwi_aligned_atlas", "labels_im_file"),
-                    ("aligned_atlas_t1mni", "atlas_mni"),
+                    ("aligned_atlas_t1w", "atlas_t1w"),
                     ("atlas", "atlas"),
                     ("uatlas", "uatlas"),
                     ("coords", "coords"),
@@ -3175,7 +3188,7 @@ def dmri_connectometry(
                 dsn_node,
                 [
                     ("basedir_path", "basedir_path"),
-                    ("t1_aligned_mni", "t1_aligned_mni"),
+                    ("t1w_brain", "t1w_brain"),
                 ],
             ),
             (
@@ -3203,7 +3216,7 @@ def dmri_connectometry(
                     ("coords", "coords"),
                     ("norm", "norm"),
                     ("binary", "binary"),
-                    ("atlas_mni", "atlas_mni"),
+                    ("atlas_t1w", "atlas_t1w"),
                     ("fa_path", "fa_path"),
                     ("directget", "directget"),
                     ("min_length", "min_length"),
@@ -3214,7 +3227,7 @@ def dmri_connectometry(
                 dsn_node,
                 streams2graph_node,
                 [
-                    ("streams_mni", "streams"),
+                    ("streams_t1w", "streams"),
                     ("dir_path", "dir_path"),
                     ("track_type", "track_type"),
                     ("target_samples", "target_samples"),
@@ -3233,7 +3246,7 @@ def dmri_connectometry(
                     ("coords", "coords"),
                     ("norm", "norm"),
                     ("binary", "binary"),
-                    ("atlas_mni", "atlas_mni"),
+                    ("atlas_for_streams", "atlas_for_streams"),
                     ("directget", "directget"),
                     ("warped_fa", "warped_fa"),
                     ("min_length", "min_length"),
@@ -3373,7 +3386,7 @@ def dmri_connectometry(
                     "track_type",
                     "norm",
                     "binary",
-                    "atlas_mni",
+                    "atlas_for_streams",
                     "streams",
                     "directget",
                     "min_length",
@@ -3395,7 +3408,7 @@ def dmri_connectometry(
                 "track_type",
                 "norm",
                 "binary",
-                "atlas_mni",
+                "atlas_for_streams",
                 "streams",
                 "directget",
                 "min_length",
@@ -3420,7 +3433,7 @@ def dmri_connectometry(
                         ("target_samples", "target_samples"),
                         ("norm", "norm"),
                         ("binary", "binary"),
-                        ("atlas_mni", "atlas_mni"),
+                        ("atlas_for_streams", "atlas_for_streams"),
                         ("streams", "streams"),
                         ("directget", "directget"),
                         ("min_length", "min_length"),
@@ -4653,7 +4666,7 @@ def fmri_connectometry(
 
     save_coords_and_labels_node = pe.Node(
         niu.Function(
-            input_names=["coords", "labels", "dir_path", "network"],
+            input_names=["coords", "labels", "dir_path", "network", "indices"],
             function=utils.save_coords_and_labels_to_json,
             imports=import_list,
         ),
@@ -5766,6 +5779,7 @@ def raw_graph_workflow(
     step_thr,
     wf,
     net_mets_node,
+    runtime_dict
 ):
     import numpy as np
     from pynets.core.utils import load_mat, load_mat_ext, save_mat_thresholded
@@ -5859,6 +5873,8 @@ def raw_graph_workflow(
             name="save_mat_thresholded_node",
             imports=import_list,
         )
+        save_mat_thresholded_node._n_procs = runtime_dict["save_mat_thresholded_node"][0]
+        save_mat_thresholded_node._mem_gb = runtime_dict["save_mat_thresholded_node"][1]
 
     if multi_graph:
         inputinfo = pe.Node(
@@ -5922,6 +5938,9 @@ def raw_graph_workflow(
                 "binary",
             ],
         )
+        join_iters_node_g._n_procs = runtime_dict["join_iters_node_g"][0]
+        join_iters_node_g._mem_gb = runtime_dict["join_iters_node_g"][1]
+
         if multi_thr is True or float(thr) != 1.0:
             load_mat_node = pe.Node(
                 niu.Function(
@@ -5957,6 +5976,8 @@ def raw_graph_workflow(
                 name="load_mat_ext_node",
                 imports=import_list,
             )
+            load_mat_node._n_procs = runtime_dict["load_mat_ext_node"][0]
+            load_mat_node._mem_gb = runtime_dict["load_mat_ext_node"][1]
 
             wf.connect(
                 [
@@ -6042,6 +6063,8 @@ def raw_graph_workflow(
                 name="load_mat_node",
                 imports=import_list,
             )
+            load_mat_node._n_procs = runtime_dict["load_mat_node"][0]
+            load_mat_node._mem_gb = runtime_dict["load_mat_node"][1]
 
             wf.connect(
                 [
@@ -6113,6 +6136,8 @@ def raw_graph_workflow(
                 "binary",
             ],
         )
+        join_iters_node_thr._n_procs = runtime_dict["join_iters_node_thr"][0]
+        join_iters_node_thr._mem_gb = runtime_dict["join_iters_node_thr"][1]
 
         thr_info_node.iterables = ("thr", iter_thresh)
         thr_info_node.synchronize = True
diff --git a/pynets/dmri/dmri_utils.py b/pynets/dmri/dmri_utils.py
index 00341636..67387c6d 100644
--- a/pynets/dmri/dmri_utils.py
+++ b/pynets/dmri/dmri_utils.py
@@ -54,10 +54,18 @@ def normalize_gradients(
 
     # Check for bval-bvec discrepancy.
     if not np.all(b0s == b0_vecs):
-        raise ValueError(
-            "Inconsistent bvals and bvecs (%d, %d low-b, respectively)."
-            % (b0s.sum(), b0_vecs.sum())
-        )
+        if bvals.shape[0] == bvecs.shape[0]:
+            print(UserWarning(
+                "Inconsistent B0 locations in bvals and bvecs "
+                "(%d, %d low-b, respectively)..."
+                % (b0s.sum(), b0_vecs.sum())
+            ))
+            # Ensure b0s have (0, 0, 0) vectors
+            bvecs[b0s, :3] = np.zeros(3)
+        else:
+            raise ValueError(
+                f"Inconsistent number of bvals ({bvals.shape[0]}) and bvecs "
+                f"({bvecs.shape[0]}).")
 
     # Rescale b-vals if requested
     if b_scale:
diff --git a/pynets/dmri/estimation.py b/pynets/dmri/estimation.py
index b0cccb29..c1e12f3b 100755
--- a/pynets/dmri/estimation.py
+++ b/pynets/dmri/estimation.py
@@ -48,7 +48,7 @@ def tens_mod_fa_est(gtab_file, dwi_file, B0_mask):
 
     gtab = load_pickle(gtab_file)
 
-    data = nib.load(dwi_file, mmap=False).get_fdata()
+    data = nib.load(dwi_file, mmap=False).get_fdata(dtype=np.float32)
 
     print("Generating tensor FA image to use for registrations...")
     nodif_B0_img = nib.load(B0_mask, mmap=False)
@@ -144,8 +144,7 @@ def create_anisopowermap(gtab_file, dwi_file, B0_mask):
     else:
         print("Generating anisotropic power map to use for registrations...")
         nodif_B0_img = nib.load(B0_mask)
-
-        dwi_data = np.asarray(img.dataobj, dtype=np.float32)
+        dwi_data = img.get_fdata(dtype=np.float32)
         for b0 in sorted(list(np.where(gtab.b0s_mask)[0]), reverse=True):
             dwi_data = np.delete(dwi_data, b0, 3)
 
@@ -161,6 +160,7 @@ def create_anisopowermap(gtab_file, dwi_file, B0_mask):
         img.to_filename(anisopwr_path)
         nodif_B0_img.uncache()
         del anisomap
+        img.uncache()
 
     return anisopwr_path, B0_mask, gtab_file, dwi_file
 
@@ -363,7 +363,7 @@ def sfm_mod_est(gtab, data, B0_mask):
 
 
 def streams2graph(
-    atlas_mni,
+    atlas_for_streams,
     streams,
     dir_path,
     track_type,
@@ -394,8 +394,8 @@ def streams2graph(
 
     Parameters
     ----------
-    atlas_mni : str
-        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
+    atlas_for_streams : str
+        File path to atlas parcellation Nifti1Image in T1w-conformed space.
     streams : str
         File path to streamline array sequence in .trk format.
     dir_path : str
@@ -458,8 +458,8 @@ def streams2graph(
 
     Returns
     -------
-    atlas_mni : str
-        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
+    atlas_for_streams : str
+        File path to atlas parcellation Nifti1Image in T1w-conformed space.
     streams : str
         File path to streamline array sequence in .trk format.
     conn_matrix : array
@@ -568,7 +568,7 @@ def streams2graph(
     fa_img = nib.load(warped_fa)
 
     # Load parcellation
-    roi_img = nib.load(atlas_mni)
+    roi_img = nib.load(atlas_for_streams)
     atlas_data = np.around(np.asarray(roi_img.dataobj))
     roi_zooms = roi_img.header.get_zooms()
     roi_shape = roi_img.shape
@@ -673,8 +673,8 @@ def streams2graph(
                         bad_idxs.append(jx)
                         print(
                             f"Label {lab} missing from parcellation. Check "
-                            f"registration and ensure valid input parcellation "
-                            f"file.")
+                            f"registration and ensure valid input "
+                            f"parcellation file.")
 
             edges = combinations(endlabels, 2)
             for edge in edges:
@@ -790,7 +790,7 @@ def streams2graph(
         atlas_name = f"{atlas}_stage-rawgraph"
 
     utils.save_coords_and_labels_to_json(coords, labels, dir_path,
-                                         atlas_name)
+                                         atlas_name, indices=None)
 
     coords = np.array(coords)
     labels = np.array(labels)
@@ -818,7 +818,7 @@ def streams2graph(
     )
 
     return (
-        atlas_mni,
+        atlas_for_streams,
         streams,
         conn_matrix,
         track_type,
diff --git a/pynets/dmri/track.py b/pynets/dmri/track.py
index 9c9fad12..bb09571c 100644
--- a/pynets/dmri/track.py
+++ b/pynets/dmri/track.py
@@ -471,14 +471,15 @@ def track_ensemble(
     ix = 0
 
     try:
-        while float(stream_counter) < float(target_samples) and float(ix) < 0.50*float(len(all_combs)):
+        while float(stream_counter) < float(target_samples) and \
+             float(ix) < 0.50*float(len(all_combs)):
             with Parallel(n_jobs=nthreads, backend='loky',
                           mmap_mode='r+', temp_folder=joblib_dir,
                           verbose=0, timeout=timeout) as parallel:
                 out_streams = parallel(
                     delayed(run_tracking)(
-                        i, recon_path, n_seeds_per_iter, directget, maxcrossing,
-                        max_length, pft_back_tracking_dist,
+                        i, recon_path, n_seeds_per_iter, directget,
+                        maxcrossing, max_length, pft_back_tracking_dist,
                         pft_front_tracking_dist, particle_count,
                         roi_neighborhood_tol, waymask, min_length,
                         track_type, min_separation_angle, sphere, tiss_class,
@@ -578,7 +579,9 @@ def run_tracking(step_curv_combinations, recon_path,
     run_uuid = f"{strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4()}"
 
     recon_path_tmp_path = fname_presuffix(
-        recon_path, suffix=f"_{'_'.join([str(i) for i in step_curv_combinations])}_{run_uuid}",
+        recon_path,
+        suffix=f"_{'_'.join([str(i) for i in step_curv_combinations])}_"
+               f"{run_uuid}",
         newpath=cache_dir
     )
     copyfile(
@@ -588,7 +591,9 @@ def run_tracking(step_curv_combinations, recon_path,
         use_hardlink=False)
 
     tissues4d_tmp_path = fname_presuffix(
-        tissues4d, suffix=f"_{'_'.join([str(i) for i in step_curv_combinations])}_{run_uuid}",
+        tissues4d,
+        suffix=f"_{'_'.join([str(i) for i in step_curv_combinations])}_"
+               f"{run_uuid}",
         newpath=cache_dir
     )
     copyfile(
@@ -599,7 +604,9 @@ def run_tracking(step_curv_combinations, recon_path,
 
     if waymask is not None:
         waymask_tmp_path = fname_presuffix(
-            waymask, suffix=f"_{'_'.join([str(i) for i in step_curv_combinations])}_{run_uuid}",
+            waymask,
+            suffix=f"_{'_'.join([str(i) for i in step_curv_combinations])}_"
+                   f"{run_uuid}",
             newpath=cache_dir
         )
         copyfile(
@@ -748,6 +755,7 @@ def run_tracking(step_curv_combinations, recon_path,
     # Filter resulting streamlines by roi-intersection
     # characteristics
     atlas_data = np.array(atlas_img.dataobj).astype("uint16")
+
     # Build mask vector from atlas for later roi filtering
     parcels = []
     i = 0
diff --git a/pynets/fmri/clustools.py b/pynets/fmri/clustools.py
index 154e6015..8444473e 100644
--- a/pynets/fmri/clustools.py
+++ b/pynets/fmri/clustools.py
@@ -72,11 +72,11 @@ def indx_3dto1d(idx, sz):
 
 def ncut(W, nbEigenValues):
     """
-    This function performs the first step of normalized cut spectral clustering.
-    The normalized LaPlacian is calculated on the similarity matrix W, and top
-    nbEigenValues eigenvectors are calculated. The number of eigenvectors
-    corresponds to the maximum number of classes (K) that will be produced by
-    the clustering algorithm.
+    This function performs the first step of normalized cut spectral
+    clustering. The normalized LaPlacian is calculated on the similarity
+    matrix W, and top nbEigenValues eigenvectors are calculated. The number of
+    eigenvectors corresponds to the maximum number of classes (K) that will be
+    produced by the clustering algorithm.
 
     Parameters
     ----------
diff --git a/pynets/fmri/estimation.py b/pynets/fmri/estimation.py
index 3df93976..0952dd71 100644
--- a/pynets/fmri/estimation.py
+++ b/pynets/fmri/estimation.py
@@ -18,7 +18,7 @@ def get_optimal_cov_estimator(time_series):
     from sklearn.covariance import GraphicalLassoCV
 
     estimator = GraphicalLassoCV(cv=5, assume_centered=True)
-    print("\nSearching for best Lasso estimator...\n")
+    print("\nSearching for best Lasso...\n")
     try:
         estimator.fit(time_series)
         return estimator
@@ -235,7 +235,7 @@ def get_conn_matrix(
     conn_matrix = None
     estimator = get_optimal_cov_estimator(time_series)
 
-    def fallback_covariance(time_series):
+    def _fallback_covariance(time_series):
         from sklearn.ensemble import IsolationForest
         from sklearn import covariance
 
@@ -251,14 +251,14 @@ def fallback_covariance(time_series):
               'ill conditions. Removing potential anomalies from the '
               'time-series using IsolationForest...')
         try:
-            print("Trying Ledoit-Wolf Estimator...")
+            print("Attempting with Ledoit-Wolf...")
             conn_measure = ConnectivityMeasure(
                 cov_estimator=covariance.LedoitWolf(store_precision=True,
                                                     assume_centered=True),
                 kind=kind)
             conn_matrix = conn_measure.fit_transform([time_series])[0]
         except (np.linalg.linalg.LinAlgError, FloatingPointError):
-            print("Trying Oracle Approximating Shrinkage Estimator...")
+            print("Attempting Oracle Approximating Shrinkage Estimator...")
             conn_measure = ConnectivityMeasure(
                 cov_estimator=covariance.OAS(assume_centered=True),
                 kind=kind)
@@ -271,16 +271,20 @@ def fallback_covariance(time_series):
         return conn_matrix
 
     if conn_model in nilearn_kinds:
-        if conn_model == "corr" or conn_model == "cor" or conn_model == "correlation":
+        if conn_model == "corr" or conn_model == "cor" or \
+             conn_model == "correlation":
             print("\nComputing correlation matrix...\n")
             kind="correlation"
-        elif conn_model == "partcorr" or conn_model == "parcorr" or conn_model == "partialcorrelation":
+        elif conn_model == "partcorr" or conn_model == "parcorr" or \
+             conn_model == "partialcorrelation":
             print("\nComputing partial correlation matrix...\n")
             kind="partial correlation"
-        elif conn_model == "sps" or conn_model == "sparse" or conn_model == "precision":
+        elif conn_model == "sps" or conn_model == "sparse" or \
+             conn_model == "precision":
             print("\nComputing precision matrix...\n")
             kind="precision"
-        elif conn_model == "cov" or conn_model == "covariance" or conn_model == "covar":
+        elif conn_model == "cov" or conn_model == "covariance" or \
+             conn_model == "covar":
             print("\nComputing covariance matrix...\n")
             kind = "covariance"
         else:
@@ -295,9 +299,9 @@ def fallback_covariance(time_series):
             try:
                 conn_matrix = conn_measure.fit_transform([time_series])[0]
             except (np.linalg.linalg.LinAlgError, FloatingPointError):
-                conn_matrix = fallback_covariance(time_series)
+                conn_matrix = _fallback_covariance(time_series)
         else:
-            conn_matrix = fallback_covariance(time_series)
+            conn_matrix = _fallback_covariance(time_series)
     else:
         if conn_model == "QuicGraphicalLasso":
             try:
@@ -310,7 +314,8 @@ def fallback_covariance(time_series):
             model = QuicGraphicalLasso(
                 init_method="cov", lam=0.5, mode="default", verbose=1
             )
-            print("\nCalculating QuicGraphLasso precision matrix using skggm...\n")
+            print("\nCalculating QuicGraphLasso precision matrix using "
+                  "skggm...\n")
             model.fit(time_series)
             conn_matrix = model.precision_
         elif conn_model == "QuicGraphicalLassoCV":
@@ -398,7 +403,7 @@ def fallback_covariance(time_series):
         atlas_name = f"{atlas}_stage-rawgraph"
 
     utils.save_coords_and_labels_to_json(coords, labels, dir_path,
-                                         atlas_name)
+                                         atlas_name, indices=None)
 
     coords = np.array(coords)
     labels = np.array(labels)
diff --git a/pynets/plotting/plot_gen.py b/pynets/plotting/plot_gen.py
index 2a9d8402..b973d55a 100644
--- a/pynets/plotting/plot_gen.py
+++ b/pynets/plotting/plot_gen.py
@@ -638,15 +638,17 @@ def plot_all_func(
     edge_color_override=False,
 ):
     """
-    Plot adjacency matrix, connectogram, and glass brain for functional connectome.
+    Plot adjacency matrix, connectogram, and glass brain for functional
+    connectome.
 
     Parameters
     ----------
     conn_matrix : array
         NxN matrix.
     conn_model : str
-       Connectivity estimation model (e.g. corr for correlation, cov for covariance, sps for precision covariance,
-       partcorr for partial correlation). sps type is used by default.
+       Connectivity estimation model (e.g. corr for correlation, cov for
+       covariance, sps for precision covariance, partcorr for partial
+       correlation). sps type is used by default.
     atlas : str
         Name of atlas parcellation used.
     dir_path : str
@@ -654,29 +656,31 @@ def plot_all_func(
     ID : str
         A subject id or other unique identifier.
     network : str
-        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of
-        brain subgraphs.
+        Resting-state network based on Yeo-7 and Yeo-17 naming
+        (e.g. 'Default') used to filter nodes in the study of brain subgraphs.
     labels : list
         List of string labels corresponding to ROI nodes.
     roi : str
         File path to binarized/boolean region-of-interest Nifti1Image file.
     coords : list
-        List of (x, y, z) tuples corresponding to an a-priori defined set (e.g. a coordinate atlas).
+        List of (x, y, z) tuples corresponding to an a-priori defined set
+        (e.g. a coordinate atlas).
     thr : float
-        A value, between 0 and 1, to threshold the graph using any variety of methods
-        triggered through other options.
+        A value, between 0 and 1, to threshold the graph using any variety of
+        methods triggered through other options.
     node_size : int
-        Spherical centroid node size in the case that coordinate-based centroids
-        are used as ROI's.
+        Spherical centroid node size in the case that coordinate-based
+        centroids are used as ROI's.
     edge_threshold : float
-        The actual value, between 0 and 1, that the graph was thresholded (can differ from thr if target was not
-        successfully obtained.
+        The actual value, between 0 and 1, that the graph was thresholded
+        (can differ from thr if target was not successfully obtained.
     smooth : int
-        Smoothing width (mm fwhm) to apply to time-series when extracting signal from ROI's.
+        Smoothing width (mm fwhm) to apply to time-series when extracting
+        signal from ROI's.
     prune : bool
         Indicates whether to prune final graph of disconnected nodes/isolates.
     uatlas : str
-        File path to atlas parcellation Nifti1Image in MNI template space.
+        File path to atlas parcellation Nifti1Image.
     norm : int
         Indicates method of normalizing resulting graph.
     binary : bool
@@ -685,7 +689,8 @@ def plot_all_func(
     hpass : bool
         High-pass filter values (Hz) to apply to node-extracted time-series.
     extract_strategy : str
-        The name of a valid function used to reduce the time-series region extraction.
+        The name of a valid function used to reduce the time-series region
+        extraction.
     edge_color_override : bool
         Switch that enables random sequential colormap selection for edges.
 
@@ -918,7 +923,8 @@ def plot_all_func(
                 edge_vmin=float(z_min),
                 node_size=node_sizes,
                 node_color=clust_pal_nodes,
-                edge_kwargs={"alpha": 0.45},
+                edge_kwargs={"alpha": 0.45, 'zorder': 1},
+                node_kwargs={'zorder': 1000}
             )
             for view in views:
                 mod_lines = []
@@ -929,6 +935,7 @@ def plot_all_func(
                     mod_lines.append(line)
                 connectome.axes[view].ax.lines = mod_lines
 
+            zorder = 10000
             for view in views:
                 coord_anns = []
                 for coord, label in list(zip(coords, labels)):
@@ -945,18 +952,34 @@ def plot_all_func(
                             dists.append(distance.euclidean(coord_ann, c))
                         if any([i < 20 for i in dists]):
                             continue
+                    if label == 'Unlabeled':
+                        continue
                     coord_anns.append(coord_ann)
                     connectome.axes[view].ax.annotate(label,
                                                       coord_ann,
                                                       xycoords='data',
                                                       textcoords='offset points',
-                                                      xytext=(0, 3),
+                                                      xytext=(-0.0001, -0.0001),
                                                       horizontalalignment='center',
                                                       verticalalignment='top',
-                                                      fontsize='2.75',
+                                                      fontsize='2.7',
+                                                      fontweight='extra bold',
+                                                      zorder=zorder,
+                                                      color='black')
+                    zorder += 10
+
+                    connectome.axes[view].ax.annotate(label,
+                                                      coord_ann,
+                                                      xycoords='data',
+                                                      textcoords='offset points',
+                                                      xytext=(0, 0),
+                                                      horizontalalignment='center',
+                                                      verticalalignment='top',
+                                                      fontsize='2.65',
                                                       fontweight='bold',
-                                                      zorder=1000,
-                                                      color='red')
+                                                      zorder=zorder,
+                                                      color='white')
+                    zorder += 100
 
             connectome.savefig(out_path_fig, dpi=dpi_resolution)
         else:
@@ -1001,8 +1024,9 @@ def plot_all_struct(
     conn_matrix : array
         NxN matrix.
     conn_model : str
-       Connectivity estimation model (e.g. corr for correlation, cov for covariance, sps for precision covariance,
-       partcorr for partial correlation). sps type is used by default.
+       Connectivity estimation model (e.g. corr for correlation, cov for
+       covariance, sps for precision covariance, partcorr for partial
+       correlation). sps type is used by default.
     atlas : str
         Name of atlas parcellation used.
     dir_path : str
@@ -1010,27 +1034,28 @@ def plot_all_struct(
     ID : str
         A subject id or other unique identifier.
     network : str
-        Resting-state network based on Yeo-7 and Yeo-17 naming (e.g. 'Default') used to filter nodes in the study of
-        brain subgraphs.
+        Resting-state network based on Yeo-7 and Yeo-17 naming
+        (e.g. 'Default') used to filter nodes in the study of brain subgraphs.
     labels : list
         List of string labels corresponding to ROI nodes.
     roi : str
         File path to binarized/boolean region-of-interest Nifti1Image file.
     coords : list
-        List of (x, y, z) tuples corresponding to an a-priori defined set (e.g. a coordinate atlas).
+        List of (x, y, z) tuples corresponding to an a-priori defined set
+        (e.g. a coordinate atlas).
     thr : float
-        A value, between 0 and 1, to threshold the graph using any variety of methods
-        triggered through other options.
+        A value, between 0 and 1, to threshold the graph using any variety of
+        methods triggered through other options.
     node_size : int
-        Spherical centroid node size in the case that coordinate-based centroids
-        are used as ROI's.
+        Spherical centroid node size in the case that coordinate-based
+        centroids are used as ROI's.
     edge_threshold : float
-        The actual value, between 0 and 1, that the graph was thresholded (can differ from thr if target was not
-        successfully obtained.
+        The actual value, between 0 and 1, that the graph was thresholded
+        (can differ from thr if target was not successfully obtained.
     prune : bool
         Indicates whether to prune final graph of disconnected nodes/isolates.
     uatlas : str
-        File path to atlas parcellation Nifti1Image in MNI template space.
+        File path to atlas parcellation Nifti1Image.
     target_samples : int
         Total number of streamline samples specified to generate streams.
     norm : int
@@ -1041,7 +1066,8 @@ def plot_all_struct(
     track_type : str
         Tracking algorithm used (e.g. 'local' or 'particle').
     directget : str
-        The statistical approach to tracking. Options are: det (deterministic), closest (clos), boot (bootstrapped),
+        The statistical approach to tracking. Options are: det (deterministic),
+        closest (clos), boot (bootstrapped),
         and prob (probabilistic).
     min_length : int
         Minimum fiber length threshold in mm to restrict tracking.
@@ -1210,7 +1236,7 @@ def plot_all_struct(
                 np.zeros(shape=(1, 1)), [(0, 0, 0)], node_size=0.0001,
                 black_bg=True
             )
-            connectome.add_overlay(ch2better_loc, alpha=0.10, cmap=plt.cm.gray)
+            connectome.add_overlay(ch2better_loc, alpha=0.45, cmap=plt.cm.gray)
 
             [
                 conn_matrix,
@@ -1252,21 +1278,25 @@ def plot_all_struct(
                 edge_vmin=float(1),
                 node_size=node_sizes,
                 node_color=clust_pal_nodes,
-                edge_kwargs={"alpha": 0.50},
+                edge_kwargs={"alpha": 0.30, 'zorder': 1},
+                node_kwargs={'zorder': 1000}
             )
+
             for view in views:
                 mod_lines = []
                 for line, edge_size in list(
                     zip(connectome.axes[view].ax.lines, edge_sizes)
                 ):
-                    line.set_lw(edge_size * 0.5)
+                    line.set_lw(edge_size * 0.20)
                     mod_lines.append(line)
                 connectome.axes[view].ax.lines = mod_lines
                 mplcyberpunk.make_lines_glow(connectome.axes[view].ax,
-                                             n_glow_lines=20,
-                                             diff_linewidth=1.0,
-                                             alpha_line=0.05)
+                                             n_glow_lines=10,
+                                             diff_linewidth=0.80,
+                                             alpha_line=0.075)
+                connectome.axes[view].ax.set_axisbelow(True)
 
+            zorder = 10000
             for view in views:
                 coord_anns = []
                 for coord, label in list(zip(coords, labels)):
@@ -1283,18 +1313,35 @@ def plot_all_struct(
                             dists.append(distance.euclidean(coord_ann, c))
                         if any([i < 20 for i in dists]):
                             continue
+                    if label == 'Unlabeled':
+                        continue
                     coord_anns.append(coord_ann)
+                    connectome.axes[view].ax.set_axisbelow(False)
                     connectome.axes[view].ax.annotate(label,
                                                       coord_ann,
                                                       xycoords='data',
                                                       textcoords='offset points',
-                                                      xytext=(0, 3),
+                                                      xytext=(-0.0001, -0.0001),
                                                       horizontalalignment='center',
                                                       verticalalignment='top',
-                                                      fontsize='2.75',
+                                                      fontsize='2.7',
+                                                      fontweight='extra bold',
+                                                      zorder=zorder,
+                                                      color='black')
+                    zorder += 10
+
+                    connectome.axes[view].ax.annotate(label,
+                                                      coord_ann,
+                                                      xycoords='data',
+                                                      textcoords='offset points',
+                                                      xytext=(0, 0),
+                                                      horizontalalignment='center',
+                                                      verticalalignment='top',
+                                                      fontsize='2.65',
                                                       fontweight='bold',
-                                                      zorder=1000,
-                                                      color='red')
+                                                      zorder=zorder,
+                                                      color='orange')
+                    zorder += 100
 
             connectome.savefig(out_path_fig, dpi=dpi_resolution)
         else:
@@ -1536,11 +1583,12 @@ def plot_all_struct_func(mG_path, namer_dir, name, modality_paths, metadata):
             [tuple(x) for x in coords],
             edge_threshold="0%",
             edge_cmap=clust_pal_edges,
-            edge_kwargs={"alpha": 0.75},
+            edge_kwargs={"alpha": 0.75, 'zorder': 1},
             edge_vmax=float(z_max),
             edge_vmin=float(z_min),
             node_size=node_sizes,
             node_color=clust_pal_nodes,
+            node_kwargs={'zorder': 1000}
         )
 
         for view in views:
@@ -1554,7 +1602,9 @@ def plot_all_struct_func(mG_path, namer_dir, name, modality_paths, metadata):
                 line.set_lw(edge_size)
                 mod_lines.append(line)
             connectome.axes[view].ax.lines[len(edge_sizes_struct):] = mod_lines
+            connectome.axes[view].ax.set_axisbelow(True)
 
+        zorder = 10000
         for view in views:
             coord_anns = []
             for coord, label in list(zip(coords, labels)):
@@ -1571,16 +1621,35 @@ def plot_all_struct_func(mG_path, namer_dir, name, modality_paths, metadata):
                         dists.append(distance.euclidean(coord_ann, c))
                     if any([i < 20 for i in dists]):
                         continue
+                if label == 'Unlabeled':
+                    continue
                 coord_anns.append(coord_ann)
+                connectome.axes[view].ax.set_axisbelow(False)
+                connectome.axes[view].ax.annotate(label,
+                                                  coord_ann,
+                                                  xycoords='data',
+                                                  textcoords='offset points',
+                                                  xytext=(-0.0001, -0.0001),
+                                                  horizontalalignment='center',
+                                                  verticalalignment='top',
+                                                  fontsize='2.7',
+                                                  fontweight='extra bold',
+                                                  zorder=zorder,
+                                                  color='black')
+                zorder += 10
+
                 connectome.axes[view].ax.annotate(label,
-                                      coord_ann,
-                                      xycoords='data',
-                                      textcoords='offset points',
-                                      xytext=(0, 3),
-                                      horizontalalignment='center',
+                                                  coord_ann,
+                                                  xycoords='data',
+                                                  textcoords='offset points',
+                                                  xytext=(0, 0),
+                                                  horizontalalignment='center',
                                                   verticalalignment='top',
-                                      fontsize='2.75', fontweight='bold',
-                                                  zorder=1000, color='red')
+                                                  fontsize='2.65',
+                                                  fontweight='bold',
+                                                  zorder=zorder,
+                                                  color='red')
+                zorder += 100
 
         connectome.savefig(
             f"{namer_dir}/glassbrain-mplx_{name[:200]}.png", dpi=dpi_resolution
@@ -1600,6 +1669,7 @@ def show_template_bundles(final_streamlines, template_path, fname):
     lines_actor = actor.streamtube(final_streamlines, window.colors.orange,
                                    linewidth=0.3)
     renderer.add(lines_actor)
+    #window.show(renderer)
     window.record(renderer, n_frames=1, out_path=fname, size=(900, 900))
     return
 
@@ -1637,9 +1707,9 @@ def view_tractogram(streams, atlas):
         clean_template_img.get_fdata(), clean_template_img.affine,
         FA_template_img.get_fdata(), FA_template_img.affine)
     warped_aff = affine_map.affine_inv.copy()
-    warped_aff_scaled = rescale_affine_to_center(warped_aff,
-                                                 voxel_dims=[4, 4, 4],
-                                                 target_center_coords=clean_template_img.affine[:3,3]*np.array([0.5, 0.5, 1]))
+    warped_aff_scaled = rescale_affine_to_center(
+        warped_aff, voxel_dims=[4, 4, 4],
+        target_center_coords=clean_template_img.affine[:3,3]*np.array([0.5, 0.5, 1]))
     streamlines = transform_streamlines(
         tractogram.streamlines, warped_aff_scaled)
 
diff --git a/pynets/plotting/plot_graphs.py b/pynets/plotting/plot_graphs.py
index 6e8a5f27..d9156f13 100644
--- a/pynets/plotting/plot_graphs.py
+++ b/pynets/plotting/plot_graphs.py
@@ -62,13 +62,13 @@ def plot_conn_mat(conn_matrix, labels, out_path_fig, cmap, binarized=False,
         print("Connectivity matrix too sparse for plotting...")
 
     if len(labels) > 500:
-        tick_interval = int(np.around(len(labels)))/10
+        tick_interval = 5
     elif len(labels) > 100:
-        tick_interval = int(np.around(len(labels)))/4
+        tick_interval = 4
     elif len(labels) > 50:
-        tick_interval = int(np.around(len(labels)))/2
+        tick_interval = 2
     else:
-        tick_interval = int(np.around(len(labels)))
+        tick_interval = 1
 
     plt.axes().yaxis.set_major_locator(mticker.MultipleLocator(tick_interval))
     plt.axes().xaxis.set_major_locator(mticker.MultipleLocator(tick_interval))
@@ -166,7 +166,7 @@ def plot_community_conn_mat(
                 size,
                 size,
                 fill=False,
-                edgecolor="black",
+                edgecolor="white",
                 alpha=None,
                 linewidth=1,
             )
@@ -174,13 +174,14 @@ def plot_community_conn_mat(
         total_size += size
 
     if len(labels) > 500:
-        tick_interval = int(np.around(len(labels)))/10
+        tick_interval = 5
     elif len(labels) > 100:
-        tick_interval = int(np.around(len(labels)))/4
+        tick_interval = 4
     elif len(labels) > 50:
-        tick_interval = int(np.around(len(labels)))/2
+        tick_interval = 2
     else:
-        tick_interval = int(np.around(len(labels)))
+        tick_interval = 1
+
     plt.axes().yaxis.set_major_locator(mticker.MultipleLocator(tick_interval))
     plt.axes().xaxis.set_major_locator(mticker.MultipleLocator(tick_interval))
     for param in ['figure.facecolor', 'axes.facecolor', 'savefig.facecolor']:
diff --git a/pynets/registration/reg_utils.py b/pynets/registration/reg_utils.py
index 8acc19cf..9140a242 100644
--- a/pynets/registration/reg_utils.py
+++ b/pynets/registration/reg_utils.py
@@ -45,7 +45,7 @@ def gen_mask(t1w_head, t1w_brain, mask):
     else:
         # Perform skull-stripping if mask not provided.
         img = nib.load(t1w_head)
-        t1w_data = img.get_fdata().astype('float32')
+        t1w_data = img.get_fdata(dtype=np.float32)
         try:
             t1w_brain_mask = deep_skull_strip(t1w_data, t1w_brain_mask, img)
         except RuntimeError as e:
@@ -167,12 +167,18 @@ def atlas2t1w2dwi_align(
                 "Warning: Atlas is not in correct dimensions, or input is low"
                 " quality,\nusing linear template registration.")
 
+            regutils.applyxfm(t1w_brain, aligned_atlas_t1mni, mni2t1_xfm,
+                              aligned_atlas_skull, interp="nearestneighbour")
+            time.sleep(0.5)
             combine_xfms(mni2t1_xfm, t1w2dwi_xfm, mni2dwi_xfm)
             time.sleep(0.5)
             regutils.applyxfm(ap_path, aligned_atlas_t1mni, mni2dwi_xfm,
                               dwi_aligned_atlas, interp="nearestneighbour")
             time.sleep(0.5)
     else:
+        regutils.applyxfm(t1w_brain, aligned_atlas_t1mni, mni2t1_xfm,
+                          aligned_atlas_skull, interp="nearestneighbour")
+        time.sleep(0.5)
         combine_xfms(mni2t1_xfm, t1w2dwi_xfm, mni2dwi_xfm)
         time.sleep(0.5)
         regutils.applyxfm(ap_path, aligned_atlas_t1mni, mni2dwi_xfm,
@@ -228,7 +234,7 @@ def atlas2t1w2dwi_align(
     wm_gm_img.uncache()
     wm_gm_mask_img.uncache()
 
-    return dwi_aligned_atlas_wmgm_int, dwi_aligned_atlas, aligned_atlas_t1mni
+    return dwi_aligned_atlas_wmgm_int, dwi_aligned_atlas, aligned_atlas_skull
 
 
 def roi2dwi_align(
@@ -953,23 +959,23 @@ def rescale_affine_to_center(input_affine, voxel_dims=[1, 1, 1],
     return target_affine
 
 
-def wm_syn(template_path, fa_path, template_anat_path, ap_path, working_dir):
+def wm_syn(t1w_brain, ap_path, working_dir, fa_path=None,
+           template_fa_path=None):
     """
     A function to perform SyN registration
 
     Parameters
     ----------
-        template_path  : str
-            File path to the template reference image.
-        fa_path : str
-            File path to the FA moving image.
-        template_anat_path  : str
-            File path to the anatomical template reference image.
+        t1w_brain  : str
+            File path to the skull-stripped T1w brain Nifti1Image.
         ap_path : str
             File path to the AP moving image.
         working_dir : str
             Path to the working directory to perform SyN and save outputs.
-
+        fa_path : str
+            File path to the FA moving image.
+        template_fa_path  : str
+            File path to the T1w-connformed template FA reference image.
     """
     import uuid
     from time import strftime
@@ -987,12 +993,12 @@ def wm_syn(template_path, fa_path, template_anat_path, ap_path, working_dir):
     from dipy.align.metrics import CCMetric
 
     # from dipy.viz import regtools
-    from nilearn.image import resample_to_img
+    # from nilearn.image import resample_to_img
 
     ap_img = nib.load(ap_path)
-    template_anat_img = nib.load(template_anat_path)
-    static = np.asarray(template_anat_img.dataobj, dtype=np.float32)
-    static_affine = template_anat_img.affine
+    t1w_brain_img = nib.load(t1w_brain)
+    static = np.asarray(t1w_brain_img.dataobj, dtype=np.float32)
+    static_affine = t1w_brain_img.affine
     moving = np.asarray(ap_img.dataobj, dtype=np.float32)
     moving_affine = ap_img.affine
 
@@ -1045,14 +1051,21 @@ def wm_syn(template_path, fa_path, template_anat_path, ap_path, working_dir):
     metric = CCMetric(3)
     level_iters = [10, 10, 5]
 
-    # Refine fit using FA template
-    fa_img = nib.load(fa_path)
-    template_img = nib.load(template_path)
-    template_img_res = resample_to_img(template_img, template_anat_img)
-    static = np.asarray(template_img_res.dataobj, dtype=np.float32)
-    static_affine = template_img_res.affine
-    moving = np.asarray(fa_img.dataobj, dtype=np.float32)
-    moving_affine = fa_img.affine
+    # Refine fit
+    if template_fa_path is not None:
+        from nilearn.image import resample_to_img
+        fa_img = nib.load(fa_path)
+        template_img = nib.load(template_fa_path)
+        template_img_res = resample_to_img(template_img, t1w_brain_img)
+        static = np.asarray(template_img_res.dataobj, dtype=np.float32)
+        static_affine = template_img_res.affine
+        moving = np.asarray(fa_img.dataobj, dtype=np.float32)
+        moving_affine = fa_img.affine
+    else:
+        static = np.asarray(t1w_brain_img.dataobj, dtype=np.float32)
+        static_affine = t1w_brain_img.affine
+        moving = np.asarray(ap_img.dataobj, dtype=np.float32)
+        moving_affine = ap_img.affine
 
     sdr = SymmetricDiffeomorphicRegistration(metric, level_iters)
 
@@ -1067,7 +1080,7 @@ def wm_syn(template_path, fa_path, template_anat_path, ap_path, working_dir):
     nib.save(
         nib.Nifti1Image(
             warped_moving,
-            affine=template_img_res.affine),
+            affine=static_affine),
         warped_fa)
 
     # # We show the registration result with:
@@ -1372,7 +1385,8 @@ def reorient_img(img, out_dir, overwrite=True):
     return out_name
 
 
-def match_target_vox_res(img_file, vox_size, out_dir, overwrite=True):
+def match_target_vox_res(img_file, vox_size, out_dir, overwrite=True,
+                         remove_orig=True):
     """
     A function to resample an image to a given isotropic voxel resolution.
 
@@ -1395,7 +1409,7 @@ def match_target_vox_res(img_file, vox_size, out_dir, overwrite=True):
     from dipy.align.reslice import reslice
 
     # Check dimensions
-    # orig_img = img_file
+    orig_img = img_file
     img = nib.load(img_file, mmap=False)
 
     hdr = img.header
@@ -1415,7 +1429,7 @@ def match_target_vox_res(img_file, vox_size, out_dir, overwrite=True):
             pass
         else:
             import gc
-            data = img.get_fdata().astype('float32')
+            data = img.get_fdata(dtype=np.float32)
             print(f"Reslicing image {img_file} to {vox_size}...")
             data2, affine2 = reslice(
                 data, img.affine, zooms, new_zooms
@@ -1440,7 +1454,7 @@ def match_target_vox_res(img_file, vox_size, out_dir, overwrite=True):
             nib.save(img, img_file_nores)
             img_file = img_file_nores
 
-    # if os.path.isfile(orig_img):
-    #     os.remove(orig_img)
+    if os.path.isfile(orig_img) and remove_orig is True:
+        os.remove(orig_img)
 
     return img_file
diff --git a/pynets/registration/register.py b/pynets/registration/register.py
index 8d7fd5f2..7bc6dc76 100755
--- a/pynets/registration/register.py
+++ b/pynets/registration/register.py
@@ -47,13 +47,13 @@ def direct_streamline_norm(
     coords,
     norm,
     binary,
-    atlas_mni,
+    atlas_t1w,
     basedir_path,
     curv_thr_list,
     step_list,
     directget,
     min_length,
-    t1_aligned_mni
+    t1w_brain
 ):
     """
     A Function to perform normalization of streamlines tracked in native
@@ -115,8 +115,8 @@ def direct_streamline_norm(
     binary : bool
         Indicates whether to binarize resulting graph edges to form an
         unweighted graph.
-    atlas_mni : str
-        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
+    atlas_t1w : str
+        File path to atlas parcellation Nifti1Image in T1w-conformed space.
     basedir_path : str
         Path to directory to output direct-streamline normalized temp files
         and outputs.
@@ -129,8 +129,8 @@ def direct_streamline_norm(
         closest (clos), boot (bootstrapped), and prob (probabilistic).
     min_length : int
         Minimum fiber length threshold in mm to restrict tracking.
-    t1_aligned_mni : str
-        File path to the T1w Nifti1Image in template MNI space.
+    t1w_brain : str
+        File path to the T1w Nifti1Image.
 
     Returns
     -------
@@ -182,8 +182,9 @@ def direct_streamline_norm(
     binary : bool
         Indicates whether to binarize resulting graph edges to form an
         unweighted graph.
-    atlas_mni : str
-        File path to atlas parcellation Nifti1Image in T1w-warped MNI space.
+    atlas_for_streams : str
+        File path to atlas parcellation Nifti1Image in the same
+        morphological space as the streamlines.
     directget : str
         The statistical approach to tracking. Options are: det
         (deterministic), closest (clos), boot (bootstrapped),
@@ -237,31 +238,16 @@ def direct_streamline_norm(
         # Run SyN and normalize streamlines
         fa_img = nib.load(fa_path)
         vox_size = fa_img.header.get_zooms()[0]
-        template_path = pkg_resources.resource_filename(
-            "pynets", f"templates/FA_{int(vox_size)}mm.nii.gz"
-        )
 
-        if sys.platform.startswith('win') is False:
-            try:
-                template_img = nib.load(template_path)
-            except indexed_gzip.ZranError as e:
-                print(e,
-                      f"\nCannot load FA template. Do you have git-lfs "
-                      f"installed?")
-        else:
-            try:
-                template_img = nib.load(template_path)
-            except ImportError as e:
-                print(e, f"\nCannot load FA template. Do you have git-lfs "
-                      f"installed?")
+        atlas_for_streams = atlas_t1w
 
-        uatlas_mni_img = nib.load(atlas_mni)
-        t1_aligned_mni_img = nib.load(t1_aligned_mni)
-        brain_mask = np.asarray(t1_aligned_mni_img.dataobj).astype("bool")
+        atlas_t1w_img = nib.load(atlas_t1w)
+        t1w_brain_img = nib.load(t1w_brain)
+        brain_mask = np.asarray(t1w_brain_img.dataobj).astype("bool")
 
-        streams_mni = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % (
+        streams_t1w = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % (
             namer_dir,
-            "/streamlines_mni_",
+            "/streamlines_t1w_",
             "%s" % (network + "_" if network is not None else ""),
             "%s" % (op.basename(roi).split(".")[0] + "_" if roi is not None
                     else ""),
@@ -287,9 +273,9 @@ def direct_streamline_norm(
             ".trk",
         )
 
-        density_mni = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % (
+        density_t1w = "%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s" % (
             namer_dir,
-            "/density_map_mni_",
+            "/density_map_t1w_",
             "%s" % (network + "_" if network is not None else ""),
             "%s" % (op.basename(roi).split(".")[0] + "_" if roi is not None
                     else ""),
@@ -319,7 +305,7 @@ def direct_streamline_norm(
 
         # SyN FA->Template
         [mapping, affine_map, warped_fa] = regutils.wm_syn(
-            template_path, fa_path, t1_aligned_mni, ap_path, dsn_dir
+            t1w_brain, ap_path, dsn_dir
         )
 
         tractogram = load_tractogram(
@@ -336,45 +322,19 @@ def direct_streamline_norm(
         warped_fa_affine = warped_fa_img.affine
         warped_fa_shape = warped_fa_img.shape
 
+        adjusted_affine = affine_map.affine.copy()
+        adjusted_affine[1][3] = -adjusted_affine[1][3]
+        adjusted_affine[2][3] = -adjusted_affine[2][3]*0.95
+
         streams_in_curr_grid = transform_streamlines(
             streamlines, warped_fa_affine)
 
-        # Create isocenter mapping where we anchor the origin transformation
-        # affine to the corner of the FOV by scaling x, y, z offsets according
-        # to a multiplicative van der Corput sequence with a base value equal
-        # to the voxel resolution
-        [x_mul, y_mul, z_mul] = [vdc(i, vox_size) for i in range(1, 4)]
-
-        ref_grid_aff = vox_size * np.eye(4)
-        ref_grid_aff[3][3] = 1
-
-        streams_final_filt = []
-        i = 0
-        # Test for various types of voxel-grid configurations
-        combs = [(-x_mul, -y_mul, -z_mul), (-x_mul, -y_mul, z_mul),
-                 (-x_mul, y_mul, -z_mul), (x_mul, -y_mul, -z_mul),
-                 (x_mul, y_mul, z_mul)]
-        while len(streams_final_filt)/len(streams_in_curr_grid) < 0.90:
-            print(f"Warping streamlines to MNI space. Attempt {i}...")
-            print(len(streams_final_filt)/len(streams_in_curr_grid))
-            adjusted_affine = affine_map.affine.copy()
-            if i > len(combs) - 1:
-                raise ValueError('DSN failed. Header orientation '
-                                 'information may be corrupted. '
-                                 'Is your dataset oblique?')
-
-            adjusted_affine[0][3] = adjusted_affine[0][3] * combs[i][0]
-            adjusted_affine[1][3] = adjusted_affine[1][3] * combs[i][1]
-            adjusted_affine[2][3] = adjusted_affine[2][3] * combs[i][2]
-
-            streams_final_filt = regutils.warp_streamlines(adjusted_affine,
-                                                           ref_grid_aff,
-                                                           mapping,
-                                                           warped_fa_img,
-                                                           streams_in_curr_grid,
-                                                           brain_mask)
-
-            i += 1
+        streams_final_filt = regutils.warp_streamlines(adjusted_affine,
+                                                       fa_img.affine,
+                                                       mapping,
+                                                       warped_fa_img,
+                                                       streams_in_curr_grid,
+                                                       brain_mask)
 
         # Remove streamlines with negative voxel indices
         lin_T, offset = _mapping_to_voxel(np.eye(4))
@@ -388,19 +348,18 @@ def direct_streamline_norm(
         # Save streamlines
         stf = StatefulTractogram(
             streams_final_filt_final,
-            reference=uatlas_mni_img,
+            reference=atlas_t1w_img,
             space=Space.VOXMM,
             origin=Origin.NIFTI,
         )
         stf.remove_invalid_streamlines()
         streams_final_filt_final = stf.streamlines
-        save_tractogram(stf, streams_mni, bbox_valid_check=True)
+        save_tractogram(stf, streams_t1w, bbox_valid_check=True)
         warped_fa_img.uncache()
 
         # DSN QC plotting
-        # plot_gen.show_template_bundles(streams_final_filt_final, atlas_mni,
-        # streams_warp_png) plot_gen.show_template_bundles(streamlines,
-        # fa_path, streams_warp_png)
+        # plot_gen.show_template_bundles(streams_final_filt_final, atlas_t1w,
+        # streams_warp_png)
 
         # Create and save MNI density map
         nib.save(
@@ -411,7 +370,7 @@ def direct_streamline_norm(
                     vol_dims=warped_fa_shape),
                 warped_fa_affine,
             ),
-            density_mni,
+            density_t1w,
         )
 
         # Map parcellation from native space back to MNI-space and create an
@@ -427,36 +386,37 @@ def direct_streamline_norm(
         warped_uatlas_img_res_data = np.asarray(
             resample_to_img(
                 nib.Nifti1Image(warped_uatlas, affine=warped_fa_affine),
-                uatlas_mni_img,
+                atlas_t1w_img,
                 interpolation="nearest",
                 clip=False,
             ).dataobj
         )
-        uatlas_mni_data = np.asarray(uatlas_mni_img.dataobj)
-        uatlas_mni_img.uncache()
+        uatlas_t1w_data = np.asarray(atlas_t1w_img.dataobj)
+        atlas_t1w_img.uncache()
         overlap_mask = np.invert(
             warped_uatlas_img_res_data.astype("bool") *
-            uatlas_mni_data.astype("bool"))
+            uatlas_t1w_data.astype("bool"))
         os.makedirs(f"{dir_path}/parcellations", exist_ok=True)
-        atlas_mni = f"{dir_path}/parcellations/" \
-                    f"{op.basename(uatlas).split('.nii')[0]}_liberal.nii.gz"
+        atlas_for_streams = f"{dir_path}/parcellations/" \
+                            f"{op.basename(uatlas).split('.nii')[0]}" \
+                            f"_t1w_liberal.nii.gz"
 
         nib.save(
             nib.Nifti1Image(
                 warped_uatlas_img_res_data * overlap_mask.astype("int")
-                + uatlas_mni_data * overlap_mask.astype("int")
+                + uatlas_t1w_data * overlap_mask.astype("int")
                 + np.invert(overlap_mask).astype("int")
                 * warped_uatlas_img_res_data.astype("int"),
-                affine=uatlas_mni_img.affine,
+                affine=atlas_t1w_img.affine,
             ),
-            atlas_mni,
+            atlas_for_streams,
         )
 
         del (
             tractogram,
             streamlines,
             warped_uatlas_img_res_data,
-            uatlas_mni_data,
+            uatlas_t1w_data,
             overlap_mask,
             stf,
             streams_final_filt_final,
@@ -473,12 +433,12 @@ def direct_streamline_norm(
         print(
             "Skipping Direct Streamline Normalization (DSN). Will proceed to "
             "define fiber connectivity in native diffusion space...")
-        streams_mni = streams
+        streams_t1w = streams
         warped_fa = fa_path
-        atlas_mni = labels_im_file
+        atlas_for_streams = labels_im_file
 
     return (
-        streams_mni,
+        streams_t1w,
         dir_path,
         track_type,
         target_samples,
@@ -498,7 +458,7 @@ def direct_streamline_norm(
         coords,
         norm,
         binary,
-        atlas_mni,
+        atlas_for_streams,
         directget,
         warped_fa,
         min_length
@@ -652,6 +612,10 @@ def __init__(
         )
         self.corpuscallosum_dwi = f"{self.reg_path_img}" \
                                   f"{'/CorpusCallosum_dwi.nii.gz'}"
+        self.fa_template_res = f"{self.reg_path_img}" \
+                                  f"{'/FA_template_res.nii.gz'}"
+        self.fa_template_t1w = f"{self.reg_path_img}" \
+                                  f"{'/FA_template_T1w.nii.gz'}"
 
         # Create empty tmp directories that do not yet exist
         reg_dirs = [
@@ -939,11 +903,38 @@ def tissue2dwi_align(self):
         import sys
         import time
         import os.path as op
+        import pkg_resources
         from pynets.core.utils import load_runconfig
+        from nilearn.image import resample_to_img
 
         hardcoded_params = load_runconfig()
         tiss_class = hardcoded_params['tracking']["tissue_classifier"][0]
 
+        fa_template_path = pkg_resources.resource_filename(
+            "pynets", f"templates/FA_{self.vox_size}.nii.gz"
+        )
+
+        if sys.platform.startswith('win') is False:
+            try:
+                fa_template_img = nib.load(fa_template_path)
+            except indexed_gzip.ZranError as e:
+                print(e,
+                      f"\nCannot load FA template. Do you have git-lfs "
+                      f"installed?")
+        else:
+            try:
+                fa_template_img = nib.load(fa_template_path)
+            except ImportError as e:
+                print(e, f"\nCannot load FA template. Do you have git-lfs ")
+
+        mni_template_img = nib.load(self.input_mni_brain)
+        fa_template_img_res = resample_to_img(fa_template_img,
+                                              mni_template_img)
+
+        nib.save(
+            fa_template_img_res,
+            self.fa_template_res)
+
         # Register Lateral Ventricles and Corpus Callosum rois to t1w
         if not op.isfile(self.mni_atlas):
             raise FileNotFoundError("FSL atlas for ventricle reference not"
@@ -1021,7 +1012,6 @@ def tissue2dwi_align(self):
                 interp="nn",
                 sup=True,
             )
-
         else:
             regutils.applyxfm(
                 self.vent_mask_mni,
@@ -1037,6 +1027,14 @@ def tissue2dwi_align(self):
             )
             time.sleep(0.5)
 
+        # Applyxfm to map FA template image to T1w space
+        regutils.applyxfm(
+            self.fa_template_res,
+            self.t1w_brain,
+            self.mni2t1_xfm,
+            self.fa_template_t1w)
+        time.sleep(0.5)
+
         # Applyxfm tissue maps to dwi space
         if self.t1w_brain_mask is not None:
             regutils.applyxfm(
@@ -1078,9 +1076,11 @@ def tissue2dwi_align(self):
         if tiss_class == 'wb' or tiss_class == 'cmc':
             csf_thr = 0.50
             wm_thr = 0.15
+            gm_thr = 0.10
         else:
-            csf_thr = 0.95
+            csf_thr = 0.99
             wm_thr = 0.10
+            gm_thr = 0.075
 
         # Threshold WM to binary in dwi space
         thr_img = nib.load(self.wm_in_dwi)
@@ -1089,7 +1089,7 @@ def tissue2dwi_align(self):
 
         # Threshold GM to binary in dwi space
         thr_img = nib.load(self.gm_in_dwi)
-        thr_img = math_img("img > 0.10", img=thr_img)
+        thr_img = math_img(f"img > {gm_thr}", img=thr_img)
         nib.save(thr_img, self.gm_in_dwi_bin)
 
         # Threshold CSF to binary in dwi space
diff --git a/pynets/runconfig.yaml b/pynets/runconfig.yaml
index 0ebae20d..7792f748 100755
--- a/pynets/runconfig.yaml
+++ b/pynets/runconfig.yaml
@@ -37,7 +37,7 @@ tracking:
     sphere:
         - 'repulsion724'
     n_seeds_per_iter:  # Increasing this value will decrease runtime with distributed execution, but increase runtime with serial execution.
-        - 750
+        - 500
     max_length:
         - 500
     tissue_classifier: # Indicates the tissue classification method to use for dMRI tractography. Options are: cmc (continuous), act (anatomically-constrained), wb (whole-brain mask), and wm (binary to white-matter only). If particle tracking is used, then this variable will be auto-set to 'cmc'.
@@ -59,7 +59,7 @@ clustering_local_conn: # If you are running agglomerative-type clustering (e.g.
 c_boot: # Number of bootstrapped iterations for spatially-constrained clustering
     - 16
 nthreads:
-    - 2
+    - 1
 graph_file_format:
     - 'npy'
 low_pass:
@@ -70,8 +70,34 @@ template: # `MNI152_T1` is the default and, along with `colin27` is provided by
 #    - 'colin27'
     - 'MNI152_T1'
 #    - 'CN200'
-hub_detection_method: # Valid inputs are richclub, eigenvector centrality, betweenness centrality, coreness (requires installation of cpalgoorithm)
+hub_detection_method: # Valid inputs are richclub, eigenvector centrality, betweenness centrality.
+    - 'betweenness'
+graph_analysis_timeout: # Number of seconds before timing out a graph analysis algorithm
+    - 240
+graph_analysis_engine: # Either graph_tool / gt or networkx / nx. The former requires an additional installation of graph_tool (See https://graph-tool.skewed.de/).
+    - 'nx'
+embed:
+    - 'ASE'
+    - 'OMNI'
+    - 'MASE'
+    - 'eigenvector'
+    - 'degree'
     - 'betweenness'
+    - 'clustering'
+metaparams_func:
+    - 'rsn'
+    - 'res'
+    - 'model'
+    - 'hpass'
+    - 'extract'
+    - 'smooth'
+metaparams_dwi:
+    - 'rsn'
+    - 'res'
+    - 'model'
+    - 'directget'
+    - 'minlength'
+    - 'tol'
 nilearn_parc_atlases:
     - 'atlas_harvard_oxford'
     - 'atlas_aal'
@@ -138,7 +164,7 @@ available_models:
       - 'sfm'
 gradients:
     n_components:
-        - null
+        - 1
 #sentiment_labeling:
 #    liwc_file:
 #        - '/Users/derekpisner/Documents/LIWC2015_English.dic' # Must be purchased and licensed. Visit https://liwc.wpengine.com/ for more information.
@@ -152,7 +178,7 @@ plotting:
     adjacency:
         - True
     dpi:
-        - 300
+        - 600
     labeling_atlas:
         - 'BrainnetomeAtlasFan2016'
     functional:
@@ -193,7 +219,7 @@ resource_dict: # Nipype workflow resource settings
       - 'check_orient_and_dims_dwi_node':
         - (2, 4)
       - 'get_anisopwr_node':
-        - (2, 1)
+        - (2, 2)
       - 'extract_ts_node':
         - (4, 8)
       - 'clustering_node':
@@ -207,7 +233,7 @@ resource_dict: # Nipype workflow resource settings
       - 'register_atlas_node':
         - (1, 1)
       - 'get_fa_node':
-        - (2, 1)
+        - (2, 2)
       - 'run_tracking_node':
         - (3, 10)
       - 'thresh_diff_node':
@@ -227,7 +253,7 @@ resource_dict: # Nipype workflow resource settings
       - 'omni_embedding_node':
         - (1, 2)
       - 'join_iters_node_thr':
-        - (1, 6)
+        - (1, 4)
       - 'join_iters_node_nets':
         - (1, 1)
       - 'join_iters_node_atlas':
@@ -238,6 +264,8 @@ resource_dict: # Nipype workflow resource settings
         - (1, 1)
       - 'join_iters_node':
         - (1, 4)
+      - 'join_iters_node_g':
+        - (1, 4)
       - 'join_iters_prep_spheres_node':
         - (1, 1)
       - 'join_iters_get_conn_matrix_node':
@@ -247,9 +275,17 @@ resource_dict: # Nipype workflow resource settings
       - 'clust_join_node':
         - (1, 1)
       - 'NetworkAnalysis':
-        - (1, 5)
+        - (1, 4)
       - 'AggregateOutputs':
         - (1, 3)
+      - 'load_mat_node':
+        - (1, 0.5)
+      - 'load_mat_ext_node':
+        - (1, 0.5)
+      - 'save_mat_thresholded_node':
+        - (1, 0.5)
+      - 'CombineOutputs':
+        - (1, 1)
 execution_dict: # Nipype workflow global settings
     - 'stop_on_first_crash':
         - False
diff --git a/pynets/stats/benchmarking.py b/pynets/stats/benchmarking.py
index f828b175..c1bf50d0 100644
--- a/pynets/stats/benchmarking.py
+++ b/pynets/stats/benchmarking.py
@@ -19,111 +19,11 @@
 import numpy as np
 import warnings
 from sklearn.preprocessing import StandardScaler
-from pynets.stats.prediction import flatten_latent_positions
+from pynets.stats.utils import flatten_latent_positions
 from pynets.core.utils import flatten
 warnings.filterwarnings("ignore")
 
 
-def build_hp_dict(file_renamed, modality, hyperparam_dict, hyperparams):
-    """
-    A function to build a hyperparameter dictionary by parsing a given
-    file path.
-    """
-
-    for hyperparam in hyperparams:
-        if (
-            hyperparam != "smooth"
-            and hyperparam != "hpass"
-            and hyperparam != "track_type"
-            and hyperparam != "directget"
-            and hyperparam != "tol"
-            and hyperparam != "minlength"
-            and hyperparam != "samples"
-            and hyperparam != "nodetype"
-            and hyperparam != "template"
-            and hyperparam != "extract"
-
-        ):
-            if hyperparam not in hyperparam_dict.keys():
-                hyperparam_dict[hyperparam] = [
-                    str(file_renamed.split(hyperparam + "-")[1].split("_")[0])
-                ]
-            else:
-                hyperparam_dict[hyperparam].append(
-                    str(file_renamed.split(hyperparam + "-")[1].split("_")[0])
-                )
-
-    if modality == "func":
-        if "smooth-" in file_renamed:
-            if "smooth" not in hyperparam_dict.keys():
-                hyperparam_dict["smooth"] = [str(file_renamed.split(
-                    "smooth-")[1].split("_")[0].split("fwhm")[0])]
-            else:
-                hyperparam_dict["smooth"].append(str(file_renamed.split(
-                    "smooth-")[1].split("_")[0].split("fwhm")[0]))
-        else:
-            if 'smooth' not in hyperparam_dict.keys():
-                hyperparam_dict['smooth'] = [str(0)]
-            hyperparam_dict["smooth"].append(str(0))
-            hyperparams.append("smooth")
-        if "hpass-" in file_renamed:
-            if "hpass" not in hyperparam_dict.keys():
-                hyperparam_dict["hpass"] = [str(file_renamed.split(
-                    "hpass-")[1].split("_")[0].split("Hz")[0])]
-            else:
-                hyperparam_dict["hpass"].append(
-                    str(file_renamed.split("hpass-"
-                                       )[1].split("_")[0].split("Hz")[0]))
-            hyperparams.append("hpass")
-        if "extract-" in file_renamed:
-            if "extract" not in hyperparam_dict.keys():
-                hyperparam_dict["extract"] = [
-                    str(file_renamed.split("extract-")[1].split("_")[0])
-                ]
-            else:
-                hyperparam_dict["extract"].append(
-                    str(file_renamed.split("extract-")[1].split("_")[0])
-                )
-            hyperparams.append("extract")
-
-    elif modality == "dwi":
-        if "directget-" in file_renamed:
-            if "directget" not in hyperparam_dict.keys():
-                hyperparam_dict["directget"] = [
-                    str(file_renamed.split("directget-")[1].split("_")[0])
-                ]
-            else:
-                hyperparam_dict["directget"].append(
-                    str(file_renamed.split("directget-")[1].split("_")[0])
-                )
-            hyperparams.append("directget")
-        if "minlength-" in file_renamed:
-            if "minlength" not in hyperparam_dict.keys():
-                hyperparam_dict["minlength"] = [
-                    str(file_renamed.split("minlength-")[1].split("_")[0])
-                ]
-            else:
-                hyperparam_dict["minlength"].append(
-                    str(file_renamed.split("minlength-")[1].split("_")[0])
-                )
-            hyperparams.append("minlength")
-        if "tol-" in file_renamed:
-            if "tol" not in hyperparam_dict.keys():
-                hyperparam_dict["tol"] = [
-                    str(file_renamed.split("tol-")[1].split("_")[0])
-                ]
-            else:
-                hyperparam_dict["tol"].append(
-                    str(file_renamed.split("tol-")[1].split("_")[0])
-                )
-            hyperparams.append("tol")
-
-    for key in hyperparam_dict:
-        hyperparam_dict[key] = list(set(hyperparam_dict[key]))
-
-    return hyperparam_dict, hyperparams
-
-
 def discr_stat(
         X,
         Y,
@@ -294,37 +194,16 @@ def normalize_beta(beta):
     return gv_array
 
 
-def gen_sub_vec(base_dir, sub_dict_clean, ID, modality, alg, comb_tuple):
-    vects = []
-    for ses in sub_dict_clean[ID].keys():
-        #print(ses)
-        if comb_tuple in sub_dict_clean[ID][ses][modality][alg].keys():
-            if alg == 'topology':
-                vect = sub_dict_clean[ID][ses][modality][alg][comb_tuple]
-            else:
-                vect = flatten_latent_positions(base_dir, sub_dict_clean, ID,
-                                                ses, modality, comb_tuple, alg)
-            vects.append(vect)
-    vects = [i for i in vects if i is not None and not np.isnan(np.array(i)).all()]
-    if len(vects) > 0 and alg == 'topology':
-        out = np.concatenate(vects, axis=1)
-    elif len(vects) > 0:
-        out = pd.concat(vects, axis=0)
-        del vects
-    else:
-        out = None
-    #print(out)
-    return out
-
-
 def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
                               final_missingness_summary, icc_tmps_dir, icc,
-                              mets, ids):
+                              mets, ids, template):
     import gc
     import json
     import glob
+    from pathlib import Path
     import ast
     import matplotlib
+    from pynets.stats.utils import gen_sub_vec
     matplotlib.use('Agg')
 
     df_summary = pd.DataFrame(
@@ -341,10 +220,12 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
             print(f"Missing {comb}...")
             extract, hpass, model, res, atlas = comb
             smooth = '0'
-        comb_tuple = (atlas, extract, hpass, model, res, smooth)
+        # comb_tuple = (atlas, extract, hpass, model, res, smooth)
+        comb_tuple = comb
     else:
         directget, minlength, model, res, atlas, tol = comb
-        comb_tuple = (atlas, directget, minlength, model, res, tol)
+        # comb_tuple = (atlas, directget, minlength, model, res, tol)
+        comb_tuple = comb
 
     df_summary.at[0, "grid"] = comb_tuple
 
@@ -372,7 +253,7 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
                     id_dict[ID] = {}
                     if comb_tuple in par_dict[ID][str(ses)][modality][alg].keys():
                         id_dict[ID][str(ses)] = \
-                            par_dict[ID][ses][modality][alg][comb_tuple][mets.index(met)][0]
+                            par_dict[ID][str(ses)][modality][alg][comb_tuple][mets.index(met)][0]
                     df = pd.DataFrame(id_dict).T
                     if df.empty:
                         del df
@@ -405,7 +286,7 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
             del df_long
     elif icc is True and alg != 'topology':
         import re
-        from pynets.stats.prediction import parse_closest_ixs
+        from pynets.stats.utils import parse_closest_ixs
         try:
             import pingouin as pg
         except ImportError:
@@ -417,105 +298,99 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
         labels_frames = []
         for ses in [str(i) for i in range(1, 11)]:
             for ID in ids:
-                if comb_tuple in par_dict[ID][str(ses)][modality][alg].keys():
-                    if 'data' in par_dict[ID][ses][modality][alg][comb_tuple].keys():
-                        if par_dict[ID][ses][modality][alg][comb_tuple]['data'] is not None:
-                            if isinstance(par_dict[ID][ses][modality][alg][comb_tuple]['data'], str):
-                                if os.path.isfile(par_dict[ID][ses][modality][alg][comb_tuple]['data']):
-                                    try:
-                                        emb_data = np.load(par_dict[ID][ses][modality][alg][comb_tuple]['data'])
-                                        node_files = glob.glob(f"{os.path.dirname(par_dict[ID][ses][modality][alg][comb_tuple]['data'])}/nodes/*.json")
-                                    except:
-                                        continue
-                                else:
-                                    continue
-                            else:
-                                node_files = glob.glob(
-                                    f"{base_dir}/embeddings_all_{modality}/"
-                                    f"sub-{ID}/ses-{ses}/rsn-{comb_tuple[0]}"
-                                    f"_res-{comb_tuple[-2]}/nodes/*.json")
-                                emb_data = par_dict[ID][ses][modality][alg][comb_tuple]['data']
-
-                            emb_shape = emb_data.shape[0]
-                            ixs = [i for i in par_dict[ID][ses][modality][alg][
-                                comb_tuple]['index'] if i is not None]
-
-                            if len(node_files) > 0:
-                                ixs, node_dict = parse_closest_ixs(node_files,
-                                                                   emb_shape)
-
-                                coords = [(i['coord']) for
-                                          i in node_dict]
-                                if isinstance(node_dict[0]['label'], str):
-                                    labels = [
-                                        ast.literal_eval(
-                                        re.search('({.+})',
-                                                  i['label']).group(0))[
-                                            'BrainnetomeAtlasFan2016'] for i in
-                                        node_dict]
-                                else:
-                                    try:
-                                        labels = [
-                                            list(i['label'])[0][
-                                                'BrainnetomeAtlasFan2016'] for i in
-                                            node_dict]
-                                    except:
+                if ses in par_dict[ID].keys():
+                    if comb_tuple in par_dict[ID][str(ses)][modality][alg].keys():
+                        if 'data' in par_dict[ID][str(ses)][modality][alg][comb_tuple].keys():
+                            if par_dict[ID][str(ses)][modality][alg][comb_tuple]['data'] is not None:
+                                if isinstance(par_dict[ID][str(ses)][modality][alg][comb_tuple]['data'], str):
+                                    data_path = par_dict[ID][str(ses)][modality][alg][comb_tuple]['data']
+                                    if os.path.isfile(data_path):
                                         try:
-                                            labels = [ast.literal_eval(
-                                                i['label'].replace('[',
-                                                                   '').replace(
-                                                    '\n 1]', ''))[
-                                                          'BrainnetomeAtlasFan2016']
-                                                      for i in node_dict]
+                                            if data_path.endswith('.npy'):
+                                                emb_data = np.load(data_path)
+                                            elif data_path.endswith('.csv'):
+                                                emb_data = np.array(pd.read_csv(data_path)).reshape(-1, 1)
+                                            else:
+                                                emb_data = np.nan
+                                            node_files = glob.glob(f"{Path(os.path.dirname(par_dict[ID][str(ses)][modality][alg][comb_tuple]['data'])).parent}/nodes/*.json")
                                         except:
+                                            print(f"Failed to load data from {data_path}..")
                                             continue
-
-                                df_coords = pd.DataFrame(
-                                    [str(tuple(x)) for x in
-                                     coords]).T
-                                df_coords.columns = [
-                                    f"rsn-{comb_tuple[0]}_res-" \
-                                    f"{comb_tuple[-2]}_{i}"
-                                    for i in ixs]
-                                # labels = [
-                                #     list(i['label'])[7] for i
-                                #     in
-                                #     node_dict]
-                                df_labels = pd.DataFrame(
-                                    labels).T
-                                df_labels.columns = [
-                                    f"rsn-{comb_tuple[0]}_res-" \
-                                    f"{comb_tuple[-2]}_{i}"
-                                    for i in ixs]
-                                coords_frames.append(df_coords)
-                                labels_frames.append(df_labels)
-                            else:
-                                print(f"No node files detected for "
-                                      f"{comb_tuple} and {ID}-{ses}...")
-                                coords_frames.append(pd.Series())
-                                labels_frames.append(pd.Series())
-
-                            if len(ixs) == emb_shape:
-                                df_pref = pd.DataFrame(emb_data.T, columns=[
-                                    f"{alg}_{i}_rsn-{comb_tuple[0]}_res-"
-                                    f"{comb_tuple[-2]}"
-                                    for i in ixs])
-                                df_pref['id'] = ID
-                                df_pref['ses'] = ses
-                                df_pref.replace(0, np.nan, inplace=True)
-                                df_pref.reset_index(drop=True, inplace=True)
-                                dfs.append(df_pref)
-                            else:
-                                print(
-                                    f"Embedding shape {emb_shape} for "
-                                    f"{comb_tuple} does not correspond to "
-                                    f"{len(ixs)} indices found for {ID}-{ses}."
-                                    f" Skipping...")
-                                continue
-                    else:
-                        print(
-                            f"data not found in {comb_tuple}. Skipping...")
-                        continue
+                                    else:
+                                        continue
+                                else:
+                                    node_files = glob.glob(
+                                        f"{base_dir}/pynets/sub-{ID}/ses-{ses}/{modality}/rsn-"
+                                        f"{atlas}_res-{res}/nodes/*.json")
+                                    emb_data = par_dict[ID][str(ses)][modality][alg][comb_tuple]['data']
+
+                                emb_shape = emb_data.shape[0]
+
+                                if len(node_files) > 0:
+                                    ixs, node_dict = parse_closest_ixs(node_files,
+                                                                       emb_shape, template=template)
+                                    if len(ixs) != emb_shape:
+                                        ixs, node_dict = parse_closest_ixs(
+                                            node_files,
+                                            emb_shape)
+                                    if isinstance(node_dict, dict):
+                                        coords = [node_dict[i]['coord'] for i in node_dict.keys()]
+                                        labels = [node_dict[i]['label']['BrainnetomeAtlasFan2016'] for i in node_dict.keys()]
+                                    else:
+                                        print(f"Failed to parse coords/labels from {node_files}. Skipping...")
+                                        continue
+                                    df_coords = pd.DataFrame(
+                                        [str(tuple(x)) for x in
+                                         coords]).T
+                                    df_coords.columns = [
+                                        f"rsn-{atlas}_res-" \
+                                        f"{res}_{i}"
+                                        for i in ixs]
+                                    # labels = [
+                                    #     list(i['label'])[7] for i
+                                    #     in
+                                    #     node_dict]
+                                    df_labels = pd.DataFrame(
+                                        labels).T
+                                    df_labels.columns = [
+                                        f"rsn-{atlas}_res-" \
+                                        f"{res}_{i}"
+                                        for i in ixs]
+                                    coords_frames.append(df_coords)
+                                    labels_frames.append(df_labels)
+                                else:
+                                    print(f"No node files detected for "
+                                          f"{comb_tuple} and {ID}-{ses}...")
+                                    ixs = [i for i in par_dict[ID][str(ses)][modality][alg][
+                                        comb_tuple]['index'] if i is not None]
+                                    coords_frames.append(pd.Series())
+                                    labels_frames.append(pd.Series())
+
+                                if len(ixs) == emb_shape:
+                                    df_pref = pd.DataFrame(emb_data.T,
+                                                           columns=[
+                                        f"{alg}_{i}_rsn-{atlas}_res-"
+                                        f"{res}"
+                                        for i in ixs])
+                                    df_pref['id'] = ID
+                                    df_pref['ses'] = ses
+                                    df_pref.replace(0, np.nan, inplace=True)
+                                    df_pref.reset_index(drop=True,
+                                                        inplace=True)
+                                    dfs.append(df_pref)
+                                else:
+                                    print(
+                                        f"Embedding shape {emb_shape} for "
+                                        f"{comb_tuple} does not correspond to "
+                                        f"{len(ixs)} indices found for "
+                                        f"{ID}-{ses}. Skipping...")
+                                    continue
+                        else:
+                            print(
+                                f"data not found in {comb_tuple}. Skipping...")
+                            continue
+                else:
+                    continue
 
         if len(dfs) == 0:
             return df_summary
@@ -553,13 +428,17 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
                     index=['ICC1', 'ICC2', 'ICC1k', 'ICC2k', 'ICC3'])
                 icc_val = c_icc3['ICC'].values[0]
                 if nodes is True:
-                    coord_in = np.array(ast.literal_eval(coords_frames_icc[f"{rsn}_{ix}"].mode().values[0]), dtype=np.dtype("O"))
-                    label_in = np.array(labels_frames_icc[f"{rsn}_{ix}"].mode().values[0], dtype=np.dtype("O"))
+                    coord_in = np.array(ast.literal_eval(
+                        coords_frames_icc[f"{rsn}_{ix}"].mode().values[0]),
+                        dtype=np.dtype("O"))
+                    label_in = np.array(
+                        labels_frames_icc[f"{rsn}_{ix}"].mode().values[0],
+                        dtype=np.dtype("O"))
                 else:
                     coord_in = np.nan
                     label_in = np.nan
                 dict_sum[f"{lp}_icc"] = icc_val
-                del c_icc, c_icc3
+                del c_icc, c_icc3, icc_val
             except BaseException:
                 print(f"FAILED for {lp}...")
                 #print(df_long)
@@ -570,7 +449,8 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
             dict_sum[f"{lp}_coord"] = coord_in
             dict_sum[f"{lp}_label"] = label_in
 
-        df_summary = pd.concat([df_summary, pd.DataFrame(pd.Series(dict_sum).T).T], axis=1)
+        df_summary = pd.concat([df_summary,
+                                pd.DataFrame(pd.Series(dict_sum).T).T], axis=1)
 
         print(df_summary)
 
@@ -602,7 +482,8 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
         if len(vect_all) > 0:
             if len(vect_all) > 0:
                 X_top = pd.concat(vect_all, axis=0, join="outer")
-                X_top = np.array(X_top.dropna(axis='columns', thresh=0.50 * len(X_top)))
+                X_top = np.array(X_top.dropna(axis='columns',
+                                              thresh=0.50 * len(X_top)))
             else:
                 print('Empty dataframe!')
                 return df_summary
@@ -634,8 +515,3 @@ def benchmark_reproducibility(base_dir, comb, modality, alg, par_dict, disc,
     gc.collect()
     return df_summary
 
-
-def tuple_insert(tup, pos, ele):
-    tup = tup[:pos] + (ele,) + tup[pos:]
-    return tup
-
diff --git a/pynets/stats/embeddings.py b/pynets/stats/embeddings.py
index 9584a7ec..35dcb817 100644
--- a/pynets/stats/embeddings.py
+++ b/pynets/stats/embeddings.py
@@ -28,13 +28,16 @@ def _omni_embed(pop_array, atlas, graph_path_list, ID,
 
     Parameters
     ----------
-    graphs : list of nx.Graph or ndarray, or ndarray
+    pop_array : list of nx.Graph or ndarray, or ndarray
         If list of nx.Graph, each Graph must contain same number of nodes.
         If list of ndarray, each array must have shape (n_vertices, n_vertices).
         If ndarray, then array must have shape (n_graphs, n_vertices, n_vertices).
     atlas : str
+        The name of an atlas (indicating the node definition).
     graph_pathlist : list
+        List of file paths to graphs in pop_array.
     ID : str
+        An arbitrary subject identifier.
     subgraph_name : str
 
     Returns
@@ -138,7 +141,7 @@ def _mase_embed(pop_array, atlas, graph_path, ID, subgraph_name="all_nodes", n_c
 
     Parameters
     ----------
-    graphs : list of nx.Graph or ndarray, or ndarray
+    pop_array : list of nx.Graph or ndarray, or ndarray
         If list of nx.Graph, each Graph must contain same number of nodes.
         If list of ndarray, each array must have shape (n_vertices, n_vertices).
         If ndarray, then array must have shape (n_graphs, n_vertices, n_vertices).
@@ -146,6 +149,7 @@ def _mase_embed(pop_array, atlas, graph_path, ID, subgraph_name="all_nodes", n_c
     graph_path : str
     ID : str
     subgraph_name : str
+    n_components : int
 
     Returns
     -------
@@ -196,23 +200,23 @@ def _mase_embed(pop_array, atlas, graph_path, ID, subgraph_name="all_nodes", n_c
     return out_path
 
 
-def _ase_embed(mat, atlas, graph_path, ID, subgraph_name="all_nodes", n_components=None, prune=0, norm=1):
+def _ase_embed(mat, atlas, graph_path, ID, subgraph_name="all_nodes",
+               n_components=None, prune=0, norm=1):
     """
 
     Class for computing the adjacency spectral embedding of a graph.
 
-    The adjacency spectral embedding (ASE) is a k-dimensional Euclidean representation
-    of the graph based on its adjacency matrix. It relies on an SVD to reduce
-    the dimensionality to the specified k, or if k is unspecified, can find a number of
-    dimensions automatically
+    The adjacency spectral embedding (ASE) is a k-dimensional Euclidean
+    representation of the graph based on its adjacency matrix. It relies on an
+    SVD to reduce the dimensionality to the specified k, or if k is
+    unspecified, can find a number of dimensions automatically
 
     Parameters
     ----------
-    graphs : list of nx.Graph or ndarray, or ndarray
-        If list of nx.Graph, each Graph must contain same number of nodes.
-        If list of ndarray, each array must have shape (n_vertices, n_vertices).
-        If ndarray, then array must have shape (n_graphs, n_vertices, n_vertices).
+    mat : ndarray or nx.Graph
+        An nxn adjacency matrix or graph object.
     atlas : str
+        The name of an atlas (indicating the node definition).
     graph_path : str
     ID : str
     subgraph_name : str
@@ -335,7 +339,7 @@ def build_asetomes(est_path_iterlist, ID):
         res = prune_suffices("_".join(file_.split(
             "/")[-1].split("modality")[1].split("_")[1:]).split("_est")[0])
         if "rsn" in res:
-            subgraph = res.split("rsn-")[1]
+            subgraph = res.split("rsn-")[1].split('_')[0]
         else:
             subgraph = "all_nodes"
         out_path = _ase_embed(mat, atlas, file_, ID, subgraph_name=subgraph,
@@ -393,7 +397,7 @@ def build_masetome(est_path_iterlist, ID):
         res = prune_suffices("_".join(pairs[0].split(
             "/")[-1].split("modality")[1].split("_")[1:]).split("_est")[0])
         if "rsn" in res:
-            subgraph = res.split("rsn-")[1]
+            subgraph = res.split("rsn-")[1].split('_')[0]
         else:
             subgraph = "all_nodes"
         out_path = _mase_embed(
@@ -592,8 +596,8 @@ def build_omnetome(est_path_iterlist, ID):
                         out_paths_func.append(out_path)
                     else:
                         print(
-                            "WARNING: Only one graph sampled, omnibus embedding"
-                            " not appropriate."
+                            "WARNING: Only one graph sampled, omnibus "
+                            "embedding not appropriate."
                         )
                         pass
 
@@ -641,8 +645,8 @@ def build_omnetome(est_path_iterlist, ID):
                         out_paths_dwi.append(out_path)
                     else:
                         print(
-                            "WARNING: Only one graph sampled, omnibus embedding"
-                            " not appropriate."
+                            "WARNING: Only one graph sampled, omnibus "
+                            "embedding not appropriate."
                         )
                         pass
     else:
diff --git a/pynets/stats/global_graph_measures.yaml b/pynets/stats/global_graph_measures.yaml
index 4c8f8ba2..36fe3cc0 100644
--- a/pynets/stats/global_graph_measures.yaml
+++ b/pynets/stats/global_graph_measures.yaml
@@ -1,9 +1,10 @@
 metric_list_global:
-    - 'global_efficiency'
+#    - 'global_efficiency'
 #    - 'average_local_efficiency'
-    - 'degree_assortativity_coefficient'
+#    - 'degree_assortativity_coefficient'
 #    - 'average_clustering'
-    - 'average_shortest_path_length'
+#    - 'average_shortest_path_length'
 #    - 'graph_number_of_cliques'
-    - 'smallworldness'
+#    - 'smallworldness'
 #    - 'weighted_transitivity'
+    - 'louvain_modularity'
diff --git a/pynets/stats/local_graph_measures.yaml b/pynets/stats/local_graph_measures.yaml
index ba703d79..7d00d3ea 100644
--- a/pynets/stats/local_graph_measures.yaml
+++ b/pynets/stats/local_graph_measures.yaml
@@ -2,10 +2,9 @@ metric_list_nodal:
 #    - 'participation_coefficient'
 #    - 'diversity_coefficient'
 #    - 'local_efficiency'
-#    - 'local_clustering'
-#    - 'degree_centrality'
+    - 'local_clustering'
+    - 'degree_centrality'
     - 'betweenness_centrality'
     - 'eigenvector_centrality'
-    - 'louvain_modularity'
 #    - 'communicability_centrality'
-#    - 'rich_club_coefficient'
+    - 'rich_club_coefficient'
diff --git a/pynets/stats/netmotifs.py b/pynets/stats/netmotifs.py
index c284ab07..e74feb89 100644
--- a/pynets/stats/netmotifs.py
+++ b/pynets/stats/netmotifs.py
@@ -78,7 +78,7 @@ def countmotifs(A, N=4):
     return umotifs
 
 
-def adaptivethresh(in_mat, thr, mlib, N):
+def adaptivethresh(in_mat, thr, mlib, N, use_gt=False):
     """
     Counts number of motifs with a given absolute threshold.
 
@@ -104,9 +104,20 @@ class of mlib.
       https://doi.org/10.1063/1.4979282
 
     """
-    from pynets.stats.netmotifs import countmotifs
 
-    mf = countmotifs((in_mat > thr).astype(int), N=N)
+    if use_gt is True:
+        try:
+            import graph_tool.all as gt
+            from pynets.stats.netstats import np2gt
+
+            g = np2gt((in_mat > thr).astype(int))
+            mlib, mf = gt.motifs(gt.GraphView(g, directed=False), k=N)
+        except ImportError as e:
+            print(e, "graph_tool not installed!")
+    else:
+        from pynets.stats.netmotifs import countmotifs
+        mf = countmotifs((in_mat > thr).astype(int), N=N)
+
     try:
         mf = np.array([mf[k] for k in mlib])
     except BaseException:
diff --git a/pynets/stats/netstats.py b/pynets/stats/netstats.py
index 4f691e85..10b4971f 100644
--- a/pynets/stats/netstats.py
+++ b/pynets/stats/netstats.py
@@ -13,7 +13,151 @@
 from pynets.core.utils import timeout
 warnings.filterwarnings("ignore")
 
-DEFAULT_TIMEOUT = 120
+from pynets.core.utils import load_runconfig
+
+hardcoded_params = load_runconfig()
+try:
+    DEFAULT_TIMEOUT = hardcoded_params["graph_analysis_timeout"][0]
+    DEFAULT_ENGINE = hardcoded_params["graph_analysis_engine"][0]
+except FileNotFoundError as e:
+    import sys
+    print(e, "Failed to parse runconfig.yaml")
+
+
+
+
+def get_prop_type(value, key=None):
+    """
+    Performs typing and value conversion for the graph_tool PropertyMap class.
+    If a key is provided, it also ensures the key is in a format that can be
+    used with the PropertyMap. Returns a tuple, (type name, value, key)
+    """
+    import six
+
+    if isinstance(key, six.string_types):
+        key = key.encode('ascii', errors='replace')
+
+    if isinstance(value, bool):
+        tname = 'bool'
+
+    elif isinstance(value, int):
+        tname = 'float'
+        value = float(value)
+
+    elif isinstance(value, float):
+        tname = 'float'
+
+    elif isinstance(value, six.string_types):
+        tname = 'string'
+        value = value.encode('ascii', errors='replace')
+
+    elif isinstance(value, dict):
+        tname = 'object'
+
+    else:
+        tname = 'string'
+        value = str(value)
+
+    return tname, value, key
+
+
+def nx2gt(nxG):
+    """
+    Converts a networkx graph to a graph-tool graph.
+    """
+    try:
+        import graph_tool.all as gt
+    except ImportWarning as e:
+        print(e, "Graph Tool not installed!")
+    gtG = gt.Graph(directed=nxG.is_directed())
+
+    for key, value in nxG.graph.items():
+        tname, value, key = get_prop_type(value, key)
+
+        prop = gtG.new_graph_property(tname)
+        gtG.graph_properties[key] = prop
+        gtG.graph_properties[key] = value
+
+    nprops = set()
+    for node, data in nxG.nodes(data=True):
+        for key, val in data.items():
+            if key in nprops:
+                continue
+
+            tname, _, key  = get_prop_type(val, key)
+
+            prop = gtG.new_vertex_property(tname)
+            gtG.vertex_properties[key.decode("utf-8")] = prop
+
+            nprops.add(key.decode("utf-8"))
+
+    gtG.vertex_properties['id'] = gtG.new_vertex_property('string')
+
+    eprops = set()
+    for src, dst, data in nxG.edges(data=True):
+        for key, val in data.items():
+            if key in eprops:
+                continue
+
+            tname, _, key = get_prop_type(val, key)
+
+            prop = gtG.new_edge_property(tname)
+            gtG.edge_properties[key.decode("utf-8")] = prop
+
+            eprops.add(key.decode("utf-8"))
+
+    vertices = {}
+    for node, data in nxG.nodes(data=True):
+        v = gtG.add_vertex()
+        vertices[node] = v
+
+        data['id'] = str(node)
+        for key, value in data.items():
+            gtG.vp[key][v] = value
+
+    # Add the edges
+    for src, dst, data in nxG.edges(data=True):
+        e = gtG.add_edge(vertices[src], vertices[dst])
+        for key, value in data.items():
+            gtG.ep[key][e] = value
+
+    return gtG
+
+
+def np2gt(adj):
+    try:
+        import graph_tool.all as gt
+    except ImportError as e:
+        print(e, "Graph Tool not installed!")
+    g = gt.Graph(directed=False)
+    edge_weights = g.new_edge_property('double')
+    g.edge_properties['weight'] = edge_weights
+    nnz = np.nonzero(np.triu(adj, 1))
+    nedges = len(nnz[0])
+    g.add_edge_list(np.hstack([np.transpose(nnz),np.reshape(adj[nnz],
+                                                            (nedges, 1))]),
+                    eprops=[edge_weights])
+    return g
+
+
+def average_shortest_path_length_fast(G, weight="weight"):
+    try:
+        import graph_tool.all as gt
+    except ImportWarning as e:
+        print(e, "Graph Tool not installed!")
+    if type(G) == nx.classes.graph.Graph:
+        n = len(G)
+        g = nx2gt(G)
+    else:
+        g = G
+        n = len(g.get_vertices())
+    if weight == "weight":
+        dist = gt.shortest_distance(g, weights=g.edge_properties['weight'],
+                                    directed=False)
+    else:
+        dist = gt.shortest_distance(g, directed=False)
+    sum_of_all_dists = sum([sum(i.a[(i.a>1e-9)&(i.a<1e9)]) for i in dist])
+    return sum_of_all_dists / (n * (n - 1))
 
 
 @timeout(DEFAULT_TIMEOUT)
@@ -92,7 +236,7 @@ def subgraph_number_of_cliques_for_all(G):
                              for sg in subgraphs) / len(subgraphs))
 
 
-def global_efficiency(G, weight="weight"):
+def global_efficiency(G, weight="weight", engine=DEFAULT_ENGINE):
     """
     Return the global efficiency of the G
 
@@ -131,18 +275,30 @@ def global_efficiency(G, weight="weight"):
     """
     N = len(G)
     if N < 2:
-        return 0
+        return np.nan
 
-    lengths = list(nx.all_pairs_dijkstra_path_length(G, weight=weight))
+    if engine.upper() == 'NX' or engine.upper() == 'NETWORKX':
+        lengths = list(nx.all_pairs_dijkstra_path_length(G, weight=weight))
+    elif engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
+        g = nx2gt(G)
+        vertices = list(g.get_vertices())
+        all_shortest_dist = [dict(zip(vertices, list(i))) for i in gt.shortest_distance(g, weights=g.edge_properties['weight'], directed=False)]
+        lengths = tuple(dict(zip(vertices, all_shortest_dist)).items())
+    else:
+        raise ValueError(f"Engine {engine} not recognized.")
     inv_lengths = []
     for length in lengths:
-        inv = [1 / x for x in length[1].values() if x is not 0]
+        inv = [1 / x for x in length[1].values() if float(x) != float(0)]
         inv_lengths.extend(inv)
-
     return sum(inv_lengths) / (N * (N - 1))
 
 
-def local_efficiency(G, weight="weight"):
+def local_efficiency(G, weight="weight", engine=DEFAULT_ENGINE):
     """
     Return the local efficiency of each node in the G
 
@@ -177,6 +333,8 @@ def local_efficiency(G, weight="weight"):
       in weighted networks. Eur Phys J B 32, 249-263.
 
     """
+    from graspy.utils import get_lcc
+
     new_graph = nx.Graph
 
     efficiencies = dict()
@@ -192,13 +350,24 @@ def local_efficiency(G, weight="weight"):
             for (n1, n2) in temp_G.edges():
                 temp_G[n1][n2][weight] = np.abs(G[n1][n2][weight])
 
-        efficiencies[node] = global_efficiency(temp_G, weight)
+        temp_G = get_lcc(temp_G, return_inds=False)
 
+        if nx.is_empty(temp_G) is True or len(temp_G) < 2 or nx.number_of_edges(temp_G) == 0:
+            efficiencies[node] = 0
+        else:
+            try:
+                if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+                    engine.upper() == 'GRAPHTOOL':
+                    efficiencies[node] = global_efficiency(temp_G, weight, engine='gt')
+                else:
+                    efficiencies[node] = global_efficiency(temp_G, weight, engine='nx')
+            except BaseException:
+                efficiencies[node] = np.nan
     return efficiencies
 
 
 @timeout(DEFAULT_TIMEOUT)
-def average_local_efficiency(G, weight="weight"):
+def average_local_efficiency(G, weight="weight", engine=DEFAULT_ENGINE):
     """
     Return the average local efficiency of all of the nodes in the G
 
@@ -224,7 +393,17 @@ def average_local_efficiency(G, weight="weight"):
       in weighted networks. Eur Phys J B 32, 249-263.
 
     """
-    eff = local_efficiency(G, weight)
+    N = len(G)
+
+    if N < 2:
+        return np.nan
+
+    if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        eff = local_efficiency(G, weight, engine='gt')
+    else:
+        eff = local_efficiency(G, weight, engine='nx')
+
     e_loc_vec = np.array(list(eff.values()))
     e_loc_vec = np.array(e_loc_vec[e_loc_vec != 0.])
     return np.nanmean(e_loc_vec)
@@ -233,16 +412,17 @@ def average_local_efficiency(G, weight="weight"):
 @timeout(DEFAULT_TIMEOUT)
 def smallworldness(
         G,
-        niter=1,
+        niter=5,
         nrand=10,
         approach="clustering",
-        reference="fast"):
+        reference="lattice",
+        engine=DEFAULT_ENGINE):
     """
     Returns the small-world coefficient of a graph
 
     The small-world coefficient of a G is:
 
-    omega = Lr/L - C/Cl
+    omega/sigma = Lr/L - C/Cl
 
     where C and L are respectively the average clustering
     coefficient/ transitivity and average shortest path length of G. Lr is
@@ -254,26 +434,24 @@ def smallworldness(
     ----------
     G : NetworkX graph
         An undirected graph.
-
     niter: integer (optional, default=5)
         Approximate number of rewiring per edge to compute the equivalent
         random graph.
-
     nrand: integer (optional, default=10)
         Number of random graphs generated to compute the average clustering
         coefficient (Cr) and average shortest path length (Lr).
     approach : str
-        Specifies whether to use clustering coefficient `clustering` or
-        `transitivity` method of counting triangles. Default is `clustering`.
+        Specifies whether to use clustering coefficient directly `clustering`
+        or `transitivity` method of counting weighted triangles.
+        Default is `clustering`.
     reference : str
-        Specifies whether to use a random `random`, lattice
-        `lattice` reference, or fast `fast` erdos-renyi sample for
-        clustering/transitivity. Default is `fast`.
+        Specifies whether to use a random `random` or lattice
+        `lattice` reference. Default is `lattice`.
 
     Returns
     -------
-    omega : float
-        The small-work coefficient
+    omega/sigma : float
+        The smallworld coefficient
 
     References
     ----------
@@ -283,54 +461,135 @@ def smallworldness(
       doi:10.1089/brain.2011.0038.
 
     """
-
     from networkx.algorithms.smallworld import random_reference, \
         lattice_reference
-    from graspy.models import DCSBMEstimator
 
-    dcer = DCSBMEstimator(directed=False, loops=False)
-    dcer.fit(nx.to_numpy_array(prune_disconnected(G)[0]))
+    N = len(G)
+
+    if N < 2:
+        return np.nan
+
+    if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
+
+    def get_random(G, reference, engine, niter, i):
+        nnodes = len(G)
+        nedges = nx.number_of_edges(G)
+        shape = np.array([nnodes, nnodes])
+        if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+            engine.upper() == 'GRAPHTOOL':
+            if reference == "random":
+                def sample_k(max):
+                    accept = False
+                    while not accept:
+                        k = np.random.randint(1, max + 1)
+                        accept = np.random.random() < 1.0 / k
+                    return k
+
+                G_rand = gt.random_graph(nnodes, lambda: sample_k(nedges),
+                                model="configuration",
+                                directed=False,
+                                n_iter=niter)
+            else:
+                raise NotImplementedError(f"{reference}' graph type not yet"
+                                          f" available using graph_tool "
+                                          f"engine")
+        else:
+            if reference == "random":
+                G_rand = random_reference(G, niter=niter, seed=i)
+            elif reference == "lattice":
+                G_rand = lattice_reference(G, niter=niter, seed=i)
+            else:
+                raise NotImplementedError(f"{reference}' graph type not "
+                                          f"recognized!")
+        return G_rand
 
     # Compute the mean clustering coefficient and average shortest path length
     # for an equivalent random graph
     randMetrics = {"C": [], "L": []}
     for i in range(nrand):
-        if reference != "fast":
-            Gr = random_reference(G, niter=niter, seed=i)
-            if reference == "random":
-                Gl = random_reference(G, niter=niter, seed=i)
-            elif reference == "lattice":
-                Gl = lattice_reference(G, niter=niter, seed=i)
-            else:
-                raise ValueError(f"{reference}' graph type not recognized!")
+        Gr = get_random(G, "random", engine, niter, i)
+        if reference == "lattice":
+            Gl = get_random(G, reference, "nx", niter, i)
+            if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+                engine.upper() == 'GRAPHTOOL':
+                Gl = nx2gt(Gl)
         else:
-            Gr = prune_disconnected(nx.from_numpy_array(dcer.sample()[0]))[0]
-            Gl = Gr.copy()
+            Gl = Gr
         if approach == "clustering":
-            randMetrics["C"].append(nx.average_clustering(Gl, weight='weight'))
-        elif approach == "transitivity":
+            if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+                engine.upper() == 'GRAPHTOOL':
+                clust_coef_ = gt.global_clustering(Gl, weight=Gl.edge_properties['weight'])[0]
+            else:
+                clust_coef_ = nx.average_clustering(Gl, weight='weight')
+            randMetrics["C"].append(clust_coef_)
+        elif approach == "transitivity" and engine == 'nx':
             randMetrics["C"].append(weighted_transitivity(Gl))
         else:
             raise ValueError(f"{approach}' approach not recognized!")
 
-        randMetrics["L"].append(
-            nx.average_shortest_path_length(Gr, weight="weight"))
+        if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or engine.upper() == 'GRAPHTOOL':
+            randMetrics["L"].append(average_shortest_path_length_fast(Gr, weight=None))
+        else:
+            randMetrics["L"].append(nx.average_shortest_path_length(Gr, weight=None))
         del Gr, Gl
 
     if approach == "clustering":
-        C = nx.average_clustering(G, weight="weight")
-    elif approach == "transitivity":
+        if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or engine.upper() == 'GRAPHTOOL':
+            g = nx2gt(G)
+            C = gt.global_clustering(g, weight=g.edge_properties['weight'])[0]
+        else:
+            C = nx.average_clustering(G, weight='weight')
+    elif approach == "transitivity" and engine == 'nx':
         C = weighted_transitivity(G)
     else:
         raise ValueError(f"{approach}' approach not recognized!")
 
-    L = nx.average_shortest_path_length(G, weight="weight")
+    if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or engine.upper() == 'GRAPHTOOL':
+        L = average_shortest_path_length_fast(G, weight=None)
+    else:
+        L = nx.average_shortest_path_length(G, weight=None)
+
     Cl = np.nanmean(randMetrics["C"], dtype=np.float32)
     Lr = np.nanmean(randMetrics["L"], dtype=np.float32)
 
     return np.nan_to_num(Lr / L) - np.nan_to_num(C / Cl)
 
 
+def rich_club_coefficient(G, engine=DEFAULT_ENGINE):
+    if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
+
+        g = nx2gt(G)
+
+        deghist = gt.vertex_hist(g, 'total')[0]
+        total = sum(deghist)
+        rc = {}
+        # Compute the number of nodes with degree greater than `k`, for each
+        # degree `k` (omitting the last entry, which is zero).
+        nks = (total - cs for cs in np.cumsum(deghist) if total - cs > 1)
+        deg = g.degree_property_map('total')
+        for k, nk in enumerate(nks):
+            if nk == 0:
+                continue
+            sub_g = gt.GraphView(g, vfilt=lambda v: deg[v] > k)
+            ek = sub_g.num_edges()
+            rc[k] = 2 * ek / (nk * (nk - 1))
+    else:
+        from networkx.algorithms import rich_club_coefficient
+        rc = rich_club_coefficient(G, seed=42, Q=100)
+
+    return rc
+
+
 def create_communities(node_comm_aff_mat, node_num):
     """
     Create a 1D vector of community assignments from a community affiliation
@@ -799,7 +1058,7 @@ def prune_disconnected(G, min_nodes=10, fallback_lcc=True):
       Network Analysis. https://doi.org/10.1016/C2012-0-06036-X
 
     """
-    #print("Pruning disconnected...")
+    print("Pruning disconnected...")
 
     G_tmp = G.copy()
 
@@ -809,19 +1068,19 @@ def prune_disconnected(G, min_nodes=10, fallback_lcc=True):
     components_connected = list(components[0])
     isolates = [n for (n, d) in G_tmp.degree() if d == 0]
 
-    if len(G_tmp.nodes()) - len(isolates) < min_nodes:
-        if fallback_lcc is True:
-            from graspy.utils import get_lcc
-            print(UserWarning('Too many isolates to defragment, '
-                              'grabbing the largest connected component...'))
-
-            lcc, pruned_nodes = get_lcc(G_tmp, return_inds=True)
-            return lcc, pruned_nodes.tolist()
-        else:
-            print(UserWarning('Too many isolates to defragment, '
-                              'skipping defragmentation. Consider fallback to'
-                              ' lcc...'))
-            return G_tmp, []
+    # if len(G_tmp.nodes()) - len(isolates) < min_nodes:
+    #     if fallback_lcc is True:
+    #         from graspy.utils import get_lcc
+    #         print(UserWarning('Warning: Too many isolates to defragment, '
+    #                           'grabbing the largest connected component...'))
+    #
+    #         lcc, pruned_nodes = get_lcc(G_tmp, return_inds=True)
+    #         return lcc, pruned_nodes.tolist()
+    #     else:
+    #         print(UserWarning('Warning: Too many isolates to defragment, '
+    #                           'skipping defragmentation. Consider fallback to'
+    #                           ' lcc...'))
+    #         return G_tmp, []
 
     # Remove disconnected nodes
     pruned_nodes = []
@@ -836,7 +1095,7 @@ def prune_disconnected(G, min_nodes=10, fallback_lcc=True):
     return G_tmp, pruned_nodes
 
 
-def most_important(G, method="betweenness", sd=1):
+def most_important(G, method="betweenness", sd=1, engine=DEFAULT_ENGINE):
     """
     Returns a copy of G with hubs as defined by centrality,
     core topology, or rich-club topology.
@@ -868,20 +1127,10 @@ def most_important(G, method="betweenness", sd=1):
     """
 
     print(f"Detecting hubs using {method} with SE: {sd}...")
-    if method == "coreness":
-        try:
-            import cpalgorithm as cp
-            algorithm = cp.KM_config()
-            algorithm.detect(G)
-            ranking = algorithm.get_coreness().items()
-        except ImportError as e:
-            print(e, "Cannot run coreness detection. "
-                     "cpalgorithm not installed!")
-
-    elif method == "eigenvector":
+    if method == "eigenvector":
         ranking = nx.eigenvector_centrality(G, weight="weight").items()
     elif method == "richclub" and len(G.nodes()) > 4:
-        ranking = nx.algorithms.rich_club_coefficient(G).items()
+        ranking = rich_club_coefficient(G).items()
     else:
         ranking = nx.betweenness_centrality(G, weight="weight").items()
 
@@ -915,7 +1164,7 @@ def most_important(G, method="betweenness", sd=1):
     return Gt, pruned_nodes
 
 
-def raw_mets(G, i):
+def raw_mets(G, i, engine=DEFAULT_ENGINE):
     """
     API that iterates across NetworkX algorithms for a G.
 
@@ -935,75 +1184,99 @@ def raw_mets(G, i):
 
     # import random
     from functools import partial
+    if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
 
     if isinstance(i, partial):
         net_name = str(i.func)
     else:
         net_name = str(i)
     if "average_shortest_path_length" in net_name:
-        if nx.is_connected(G) is True:
+        if engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+             engine.upper() == 'GRAPHTOOL':
             try:
-                net_met_val = float(i(G))
+                net_met_val = average_shortest_path_length_fast(G,
+                                                                weight='weight')
             except BaseException:
-                net_met_val = float(average_shortest_path_length_for_all(G))
+                net_met_val = np.nan
         else:
             try:
-                [H, _] = prune_disconnected(G)
-                net_met_val = float(i(H))
+                net_met_val = float(i(G))
             except BaseException:
-                print(f"{'WARNING: '}{net_name}{' failed for G.'}")
-                # np.save(f"{'/tmp/average_shortest_path_length'}{random.randint(1, 400)}{'.npy'}",
-                #         np.array(nx.to_numpy_matrix(H)))
+                try:
+                    net_met_val = float(
+                        average_shortest_path_length_for_all(G))
+                except BaseException as e:
+                    print(e, f"WARNING: {net_name} failed for G.")
+                    # np.save(f"{'/tmp/average_shortest_path_length'}{random.randint(1, 400)}{'.npy'}",
+                    #         np.array(nx.to_numpy_matrix(H)))
+                    net_met_val = np.nan
+    elif "average_clustering" in net_name and (engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or engine.upper() == 'GRAPHTOOL'):
+        try:
+            g = nx2gt(G)
+            net_met_val = gt.global_clustering(g, weight=g.edge_properties['weight'])[0]
+        except BaseException as e:
+            print(e, f"WARNING: {net_name} failed for G.")
+            net_met_val = np.nan
     elif "graph_number_of_cliques" in net_name:
         if nx.is_connected(G) is True:
             try:
                 net_met_val = float(i(G))
             except BaseException:
-                net_met_val = float(subgraph_number_of_cliques_for_all(G))
+                try:
+                    net_met_val = float(subgraph_number_of_cliques_for_all(G))
+                except BaseException as e:
+                    print(e, f"WARNING: {net_name} failed for G.")
+                    # np.save(f"{'/tmp/graph_num_cliques'}{random.randint(1, 400)}{'.npy'}",
+                    #         np.array(nx.to_numpy_matrix(H)))
+                    net_met_val = np.nan
         else:
-            try:
-                [H, _] = prune_disconnected(G)
-                net_met_val = float(i(H))
-            except BaseException:
-                print(f"{'WARNING: '}{net_name}{' failed for G.'}")
-                # np.save(f"{'/tmp/graph_num_cliques'}{random.randint(1, 400)}{'.npy'}",
-                #         np.array(nx.to_numpy_matrix(H)))
+            net_met_val = np.nan
+
     elif "smallworldness" in net_name:
         try:
             net_met_val = float(i(G))
-        except BaseException:
-            try:
-                [H, _] = prune_disconnected(G)
-                net_met_val = float(i(H))
-            except BaseException:
-                print(f"{'WARNING: '}{net_name}{' failed for G.'}")
-                # np.save(f"{'/tmp/smallworldness'}{random.randint(1, 400)}{'.npy'}",
-                #         np.array(nx.to_numpy_matrix(H)))
+        except BaseException as e:
+            print(e, f"WARNING: {net_name} failed for G.")
+            # np.save(f"{'/tmp/smallworldness'}{random.randint(1, 400)}{'.npy'}",
+            #         np.array(nx.to_numpy_matrix(H)))
+            net_met_val = np.nan
     elif "degree_assortativity_coefficient" in net_name:
         H = G.copy()
         for u, v, d in H.edges(data=True):
             H[u][v]["weight"] = int(np.round(100 * H[u][v]["weight"], 1))
-        try:
-            net_met_val = float(i(H))
-        except BaseException:
+        if nx.is_connected(H) is True:
             try:
-                [H, _] = prune_disconnected(H)
                 net_met_val = float(i(H))
-            except BaseException:
-                try:
-                    from networkx.algorithms.assortativity import (
-                        degree_pearson_correlation_coefficient,
-                    )
+            except BaseException as e:
+                print(UserWarning(f"Warning {e}: "
+                                  f"Degree assortativity coefficient measure "
+                                  f"failed!"))
+                net_met_val = np.nan
+        else:
+            try:
+                from networkx.algorithms.assortativity import (
+                    degree_pearson_correlation_coefficient,
+                )
 
-                    net_met_val = float(
-                        degree_pearson_correlation_coefficient(
-                            H, weight="weight"))
-                except BaseException:
-                    print(f"{'WARNING: '}{net_name}{' failed for G.'}")
-                    # np.save(f"{'/tmp/degree_assortativity_coefficient'}{random.randint(1, 400)}{'.npy'}",
-                    #         np.array(nx.to_numpy_matrix(H)))
+                net_met_val = float(
+                    degree_pearson_correlation_coefficient(
+                        H, weight="weight"))
+            except BaseException as e:
+                print(e, f"WARNING: {net_name} failed for G.")
+                # np.save(f"{'/tmp/degree_assortativity_coefficient'}{random.randint(1, 400)}{'.npy'}",
+                #         np.array(nx.to_numpy_matrix(H)))
+                net_met_val = np.nan
     else:
-        net_met_val = float(i(G))
+        try:
+            net_met_val = float(i(G))
+        except BaseException as e:
+            print(e, f"WARNING: {net_name} failed for G.")
+            net_met_val = np.nan
 
     return net_met_val
 
@@ -1133,7 +1406,11 @@ def prune_graph(self, remove_self_loops=True):
         if int(self.prune) == 1:
             if nx.is_connected(self.G) is False:
                 print("Graph fragmentation detected...\n")
-            [self.G, _] = prune_disconnected(self.G)
+            try:
+                [self.G, _] = prune_disconnected(self.G)
+            except BaseException:
+                print(UserWarning(f"Warning: Pruning failed for "
+                                  f"{self.est_path}"))
         elif int(self.prune) == 2:
             print("Filtering for hubs...")
             from pynets.core.utils import load_runconfig
@@ -1141,15 +1418,17 @@ def prune_graph(self, remove_self_loops=True):
             try:
                 hub_detection_method = hardcoded_params[
                     "hub_detection_method"][0]
+                [self.G, _] = most_important(self.G,
+                                             method=hub_detection_method)
             except FileNotFoundError as e:
                 import sys
                 print(e, "Failed to parse runconfig.yaml")
 
-            [self.G, _] = most_important(self.G, method=hub_detection_method)
         elif int(self.prune) == 3:
             print("Pruning all but the largest connected "
                   "component subgraph...")
-            self.G = self.G.subgraph(get_lcc(self.G))
+            #self.G = self.G.subgraph(get_lcc(self.G))
+            self.G = get_lcc(self.G)
         else:
             print("No graph anti-fragmentation applied...")
 
@@ -1160,6 +1439,11 @@ def prune_graph(self, remove_self_loops=True):
 
         self.G = nx.from_numpy_array(self.in_mat)
 
+        if nx.is_empty(self.G) is True or (np.abs(self.in_mat) < 0.0000001).all() or \
+            self.G.number_of_edges() == 0:
+            print(UserWarning(f"Warning: {self.est_path} empty after pruning!"))
+            return self.in_mat, None
+
         # Saved pruned
         if (self.prune != 0) and (self.prune is not None):
             final_mat_path = f"{self.est_path.split('.npy')[0]}{'_pruned'}"
@@ -1243,8 +1527,8 @@ def iterate_nx_global_measures(G, metric_list_glob):
                 net_met_val = np.nan
         except BaseException:
             print(f"{'WARNING: '}{str(i)}{' is undefined for G'}")
-            # np.save("%s%s%s%s" % ('/tmp/', net_met, random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
+            # np.save("%s%s%s%s" % ('/tmp/', net_met, random.randint(1,
+            # 400), '.npy'), np.array(nx.to_numpy_matrix(G)))
             net_met_val = np.nan
         net_met_arr[j, 0] = net_met
         net_met_arr[j, 1] = net_met_val
@@ -1293,27 +1577,6 @@ def community_resolution_selection(G):
                     "\nWARNING: Louvain community detection failed. "
                     "Proceeding with single community affiliation vector...")
                 break
-    elif num_comms > len(G.edges()) / 10:
-        resolution = 0.1
-        tries = 0
-        while num_comms == 1:
-            ci = np.array(
-                list(
-                    community.best_partition(
-                        G,
-                        resolution=resolution).values()))
-            num_comms = len(np.unique(ci))
-            print(
-                f"{'Found '}{num_comms}{' communities at resolution: '}"
-                f"{resolution}{'...'}"
-            )
-            resolution = resolution / 10
-            tries = tries + 1
-            if tries > 100:
-                print(
-                    "\nWARNING: Louvain community detection failed. "
-                    "Proceeding with single community affiliation vector...")
-                break
     else:
         print(
             f"{'Found '}{num_comms}{' communities at resolution: '}"
@@ -1342,7 +1605,7 @@ def get_participation(in_mat, ci, metric_list_names, net_met_val_list_final):
         pc_vector = participation_coef_sign(in_mat, ci)[0]
     else:
         pc_vector = participation_coef(in_mat, ci)
-    print("\nCalculating Participation Coefficients...")
+    print("\nExtracting Participation Coefficients...")
     pc_vals = list(pc_vector)
     pc_edges = list(range(len(pc_vector)))
     num_edges = len(pc_edges)
@@ -1374,7 +1637,7 @@ def get_participation(in_mat, ci, metric_list_names, net_met_val_list_final):
 @timeout(DEFAULT_TIMEOUT)
 def get_diversity(in_mat, ci, metric_list_names, net_met_val_list_final):
     dc_vector = diversity_coef_sign(in_mat, ci)[0]
-    print("\nCalculating Diversity Coefficients...")
+    print("\nExtracting Diversity Coefficients...")
     dc_vals = list(dc_vector)
     dc_edges = list(range(len(dc_vector)))
     num_edges = len(dc_edges)
@@ -1405,7 +1668,7 @@ def get_diversity(in_mat, ci, metric_list_names, net_met_val_list_final):
 @timeout(DEFAULT_TIMEOUT)
 def get_local_efficiency(G, metric_list_names, net_met_val_list_final):
     le_vector = local_efficiency(G)
-    print("\nCalculating Local Efficiencies...")
+    print("\nExtracting Local Efficiencies...")
     le_vals = list(le_vector.values())
     le_nodes = list(le_vector.keys())
     num_nodes = len(le_nodes)
@@ -1432,10 +1695,23 @@ def get_local_efficiency(G, metric_list_names, net_met_val_list_final):
 
 
 @timeout(DEFAULT_TIMEOUT)
-def get_clustering(G, metric_list_names, net_met_val_list_final):
+def get_clustering(G, metric_list_names, net_met_val_list_final, engine=DEFAULT_ENGINE):
 
-    cl_vector = nx.clustering(G, weight="weight")
-    print("\nCalculating Local Clusterings...")
+    if engine.upper() == 'NX' or engine.upper() == 'NETWORKX':
+        cl_vector = nx.clustering(G, weight="weight")
+    elif engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
+        g = nx2gt(G)
+        cl_vector = dict(zip(list(g.get_vertices()), list(
+            gt.local_clustering(g, weight=g.ep["weight"]).get_array())))
+    else:
+        raise ValueError(f"Engine {engine} not recognized.")
+
+    print("\nExtracting Local Clusterings...")
     cl_vals = list(cl_vector.values())
     cl_nodes = list(cl_vector.keys())
     num_nodes = len(cl_nodes)
@@ -1454,8 +1730,7 @@ def get_clustering(G, metric_list_names, net_met_val_list_final):
     nonzero_arr_cl = np.delete(cl_arr[:, 1], [0])
     cl_arr[num_nodes, 1] = np.nanmean(nonzero_arr_cl.astype('float32'),
                                       dtype=np.float32)
-    print(
-        f"{'Mean Local Clustering across nodes: '}{str(cl_arr[num_nodes, 1])}")
+    print(f"{str(cl_arr[num_nodes, 1])}")
     for i in cl_arr[:, 0]:
         metric_list_names.append(i)
     net_met_val_list_final = net_met_val_list_final + list(cl_arr[:, 1])
@@ -1467,7 +1742,7 @@ def get_degree_centrality(G, metric_list_names, net_met_val_list_final):
     from networkx.algorithms import degree_centrality
 
     dc_vector = degree_centrality(G)
-    print("\nCalculating Local Degree Centralities...")
+    print("\nExtracting Local Degree Centralities...")
     dc_vals = list(dc_vector.values())
     dc_nodes = list(dc_vector.keys())
     num_nodes = len(dc_nodes)
@@ -1487,7 +1762,6 @@ def get_degree_centrality(G, metric_list_names, net_met_val_list_final):
     dc_arr[num_nodes, 1] = np.nanmean(nonzero_arr_dc.astype('float32'),
                                       dtype=np.float32)
     print(
-        f"{'Mean Degree Centrality across nodes: '}"
         f"{str(dc_arr[num_nodes, 1])}")
     for i in dc_arr[:, 0]:
         metric_list_names.append(i)
@@ -1499,11 +1773,23 @@ def get_degree_centrality(G, metric_list_names, net_met_val_list_final):
 def get_betweenness_centrality(
         G_len,
         metric_list_names,
-        net_met_val_list_final):
+        net_met_val_list_final, engine=DEFAULT_ENGINE):
     from networkx.algorithms import betweenness_centrality
 
-    bc_vector = betweenness_centrality(G_len, normalized=True)
-    print("\nCalculating Local Betweenness Centralities...")
+    if engine.upper() == 'NX' or engine.upper() == 'NETWORKX':
+        bc_vector = betweenness_centrality(G_len, normalized=True)
+    elif engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
+        g = nx2gt(G_len)
+        bc_vector = dict(zip(list(g.get_vertices()), list(
+            gt.betweenness(g, weight=g.ep["weight"])[0].get_array())))
+    else:
+        raise ValueError(f"Engine {engine} not recognized.")
+
+    print("\nExtracting Local Betweenness Centralities...")
     bc_vals = list(bc_vector.values())
     bc_nodes = list(bc_vector.keys())
     num_nodes = len(bc_nodes)
@@ -1525,7 +1811,7 @@ def get_betweenness_centrality(
     bc_arr[num_nodes, 1] = np.nanmean(nonzero_arr_betw_cent.astype('float32'),
                                       dtype=np.float32)
     print(
-        f"{'Mean Betweenness Centrality across nodes: '}"
+        f"{'Mean Betweenness Centrality: '}"
         f"{str(bc_arr[num_nodes, 1])}")
     for i in bc_arr[:, 0]:
         metric_list_names.append(i)
@@ -1534,11 +1820,25 @@ def get_betweenness_centrality(
 
 
 @timeout(DEFAULT_TIMEOUT)
-def get_eigen_centrality(G, metric_list_names, net_met_val_list_final):
-    from networkx.algorithms import eigenvector_centrality
+def get_eigen_centrality(G, metric_list_names, net_met_val_list_final,
+                         engine=DEFAULT_ENGINE):
+
+    if engine.upper() == 'NX' or engine.upper() == 'NETWORKX':
+        from networkx.algorithms import eigenvector_centrality
+        ec_vector = eigenvector_centrality(G, max_iter=1000)
+    elif engine.upper() == 'GT' or engine.upper() == 'GRAPH_TOOL' or \
+        engine.upper() == 'GRAPHTOOL':
+        try:
+            import graph_tool.all as gt
+        except ImportWarning as e:
+            print(e, "Graph Tool not installed!")
+        g = nx2gt(G)
+        ec_vector = dict(zip(list(g.get_vertices()), list(
+            gt.eigenvector(g, weight=g.ep["weight"])[1].get_array())))
+    else:
+        raise ValueError(f"Engine {engine} not recognized.")
 
-    ec_vector = eigenvector_centrality(G, max_iter=1000)
-    print("\nCalculating Local Eigenvector Centralities...")
+    print("\nExtracting Local Eigenvector Centralities...")
     ec_vals = list(ec_vector.values())
     ec_nodes = list(ec_vector.keys())
     num_nodes = len(ec_nodes)
@@ -1559,7 +1859,7 @@ def get_eigen_centrality(G, metric_list_names, net_met_val_list_final):
     ec_arr[num_nodes, 1] = np.nanmean(nonzero_arr_eig_cent.astype('float32'),
                                       dtype=np.float32)
     print(
-        f"{'Mean Eigenvector Centrality across nodes: '}"
+        f"{'Mean Eigenvector Centrality: '}"
         f"{str(ec_arr[num_nodes, 1])}")
     for i in ec_arr[:, 0]:
         metric_list_names.append(i)
@@ -1572,7 +1872,7 @@ def get_comm_centrality(G, metric_list_names, net_met_val_list_final):
     from networkx.algorithms import communicability_betweenness_centrality
 
     cc_vector = communicability_betweenness_centrality(G, normalized=True)
-    print("\nCalculating Local Communicability Centralities...")
+    print("\nExtracting Local Communicability Centralities...")
     cc_vals = list(cc_vector.values())
     cc_nodes = list(cc_vector.keys())
     num_nodes = len(cc_nodes)
@@ -1594,7 +1894,7 @@ def get_comm_centrality(G, metric_list_names, net_met_val_list_final):
     cc_arr[num_nodes, 1] = np.nanmean(nonzero_arr_comm_cent.astype('float32'),
                                       dtype=np.float32)
     print(
-        f"{'Mean Communicability Centrality across nodes: '}"
+        f"{'Mean Communicability Centrality: '}"
         f"{str(cc_arr[num_nodes, 1])}"
     )
     for i in cc_arr[:, 0]:
@@ -1604,11 +1904,11 @@ def get_comm_centrality(G, metric_list_names, net_met_val_list_final):
 
 
 @timeout(DEFAULT_TIMEOUT)
-def get_rich_club_coeff(G, metric_list_names, net_met_val_list_final):
-    from networkx.algorithms import rich_club_coefficient
+def get_rich_club_coeff(G, metric_list_names, net_met_val_list_final,
+                        engine=DEFAULT_ENGINE):
 
-    rc_vector = rich_club_coefficient(G, normalized=True, seed=42, Q=100)
-    print("\nCalculating Local Rich Club Coefficients...")
+    print("\nExtracting Rich Club Coefficient...")
+    rc_vector = rich_club_coefficient(G, engine=engine)
     rc_vals = list(rc_vector.values())
     rc_edges = list(rc_vector.keys())
     num_edges = len(rc_edges)
@@ -1630,7 +1930,7 @@ def get_rich_club_coeff(G, metric_list_names, net_met_val_list_final):
     rc_arr[num_edges, 1] = np.nanmean(nonzero_arr_rich_club.astype('float32'),
                                       dtype=np.float32)
     print(
-        f"{'Mean Rich Club Coefficient across edges: '}"
+        f"{'Mean Rich Club Coefficient: '}"
         f"{str(rc_arr[num_edges, 1])}")
     for i in rc_arr[:, 0]:
         metric_list_names.append(i)
@@ -1707,29 +2007,6 @@ def extractnetstats(
     import pynets.stats.netstats
     from pathlib import Path
 
-    cg = CleanGraphs(thr, conn_model, est_path, prune, norm)
-
-    if float(norm) >= 1:
-        cg.normalize_graph()
-
-    if float(prune) >= 1:
-        [_, tmp_graph_path] = cg.prune_graph()
-    else:
-        tmp_graph_path = None
-
-    if 'modality-func' in est_path and 'model-sps' not in est_path:
-        binary = True
-
-    if binary is True:
-        in_mat, G = cg.binarize_graph()
-    else:
-        in_mat, G = cg.in_mat, cg.G
-
-    in_mat_len, G_len = cg.create_length_matrix()
-
-    cg.print_summary()
-
-    dir_path = op.dirname(op.realpath(est_path))
 
     # Load netstats config and parse graph algorithms as objects
     with open(
@@ -1752,31 +2029,36 @@ def extractnetstats(
             ]
             metric_dict_global = yaml.load(stream)
             metric_list_global = metric_dict_global["metric_list_global"]
-            metric_list_global = [
-                getattr(networkx.algorithms, i)
-                for i in metric_list_global
-                if i in nx_algs
-            ] + [
-                getattr(pynets.stats.netstats, i)
-                for i in metric_list_global
-                if i in pynets_algs
-            ]
-            metric_list_global_names = [
-                str(i).split("<function ")[1].split(" at")[0]
-                for i in metric_list_global
-            ]
-            if binary is False:
-                from functools import partial
-
+            if metric_list_global is not None:
                 metric_list_global = [
-                    partial(i, weight="weight")
-                    if "weight" in i.__code__.co_varnames
-                    else i
+                                         getattr(networkx.algorithms, i)
+                                         for i in metric_list_global
+                                         if i in nx_algs
+                                     ] + [
+                                         getattr(pynets.stats.netstats, i)
+                                         for i in metric_list_global
+                                         if i in pynets_algs
+                                     ]
+                metric_list_global_names = [
+                    str(i).split("<function ")[1].split(" at")[0]
                     for i in metric_list_global
                 ]
-            print(
-                f"\n\nGlobal Topographic Metrics:"
-                f"\n{metric_list_global_names}\n")
+                if binary is False:
+                    from functools import partial
+
+                    metric_list_global = [
+                        partial(i, weight="weight")
+                        if "weight" in i.__code__.co_varnames
+                        else i
+                        for i in metric_list_global
+                    ]
+                print(
+                    f"\n\nGlobal Topographic Metrics:"
+                    f"\n{metric_list_global_names}\n")
+            else:
+                print("No global topographic metrics selected!")
+                metric_list_global = []
+                metric_list_global_names = []
         except FileNotFoundError as e:
             import sys
             print(e, "Failed to parse global_graph_measures.yaml")
@@ -1789,206 +2071,315 @@ def extractnetstats(
         try:
             metric_dict_nodal = yaml.load(stream)
             metric_list_nodal = metric_dict_nodal["metric_list_nodal"]
-            print(f"\nNodal Topographic Metrics:\n{metric_list_nodal}\n\n")
+            if metric_list_nodal is not None:
+                print(f"\nNodal Topographic Metrics:\n{metric_list_nodal}\n\n")
+            else:
+                print("No nodal topographic metrics selected!")
+                metric_list_nodal = []
         except FileNotFoundError as e:
             import sys
             print(e, "Failed to parse local_graph_measures.yaml")
 
-    # Deal with empty graphs
-    if nx.is_empty(G) is True or (np.abs(in_mat) < 0.0000001).all():
-        out_path_neat = save_netmets(
-            dir_path, est_path, metric_list_global_names,
-            len(metric_list_global_names)*[np.nan],
-        )
-        return out_path_neat
-
-    # Note the use of bare excepts in preceding blocks. Typically, this is
-    # considered bad practice in python. Here,
-    # we are exploiting it intentionally to facilitate uninterrupted,
-    # automated graph analysis even when algorithms are
-    # undefined. In those instances, solutions are assigned NaN's.
-
-    # Iteratively run functions from above metric list that generate single
-    # scalar output
-    net_met_val_list_final, metric_list_names = iterate_nx_global_measures(
-        G, metric_list_global
-    )
-
-    # Run miscellaneous functions that generate multiple outputs
-    # Calculate modularity using the Louvain algorithm
-    if "louvain_modularity" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            net_met_val_list_final, metric_list_names, ci = get_community(
-                G, net_met_val_list_final, metric_list_names
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Louvain modularity calculation is undefined for G")
-            # np.save("%s%s%s" % ('/tmp/community_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+    if os.path.isfile(est_path):
+        cg = CleanGraphs(thr, conn_model, est_path, prune, norm)
 
-    # Participation Coefficient by louvain community
-    if "participation_coefficient" in metric_list_nodal:
-        try:
-            if ci is None:
-                raise KeyError(
-                    "Participation coefficient cannot be calculated for G in"
-                    " the absence of a community affiliation vector")
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_participation(
-                in_mat, ci, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Participation coefficient cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/partic_coeff_failure', random.randint(1, 400), '.npy'), in_mat)
-            pass
+        tmp_graph_path = None
+        if float(prune) >= 1:
+            [_, tmp_graph_path] = cg.prune_graph()
 
-    # Diversity Coefficient by louvain community
-    if "diversity_coefficient" in metric_list_nodal:
-        try:
-            if ci is None:
-                raise KeyError(
-                    "Diversity coefficient cannot be calculated for G in the"
-                    " absence of a community affiliation vector")
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_diversity(
-                in_mat, ci, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Diversity coefficient cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/div_coeff_failure', random.randint(1, 400), '.npy'), in_mat)
-            pass
+        if float(norm) >= 1:
+            try:
+                cg.normalize_graph()
+            except ValueError as e:
+                print(e, f"Graph normalization failed for {est_path}.")
 
-    # # Link communities
-    # if "link_communities" in metric_list_nodal:
-    #     try:
-    #         if ci is None:
-    #             raise KeyError(
-    #                 "Link communities cannot be calculated for G in the"
-    #                 " absence of a community affiliation vector")
-    #         start_time = time.time()
-    #         metric_list_names, net_met_val_list_final = get_link_communities(
-    #             in_mat, ci, metric_list_names, net_met_val_list_final
-    #         )
-    #         print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-    #     except BaseException:
-    #         print("Link communities cannot be calculated for G")
-    #         # np.save("%s%s%s" % ('/tmp/link_comms_failure', random.randint(1, 400), '.npy'), in_mat)
-    #         pass
-
-    # Local Efficiency
-    if "local_efficiency" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_local_efficiency(
-                G, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Local efficiency cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/local_eff_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+        if 'modality-func' in est_path and 'model-sps' not in est_path:
+            binary = True
 
-    # Local Clustering
-    if "local_clustering" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_clustering(
-                G, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Local clustering cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/local_clust_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+        if binary is True:
+            try:
+                in_mat, G = cg.binarize_graph()
+            except ValueError as e:
+                print(e, f"Graph binarization failed for {est_path}.")
+                in_mat = np.zeros(1, 1)
+                G = nx.Graph()
+        else:
+            in_mat, G = cg.in_mat, cg.G
 
-    # Degree centrality
-    if "degree_centrality" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_degree_centrality(
-                G, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Degree centrality cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/degree_cent_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+        cg.print_summary()
 
-    # Betweenness Centrality
-    if "betweenness_centrality" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_betweenness_centrality(
-                G_len, metric_list_names, net_met_val_list_final)
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Betweenness centrality cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/betw_cent_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G_len)))
-            pass
+        dir_path = op.dirname(op.realpath(est_path))
 
-    # Eigenvector Centrality
-    if "eigenvector_centrality" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_eigen_centrality(
-                G, metric_list_names, net_met_val_list_final
+        # Deal with empty graphs
+        if nx.is_empty(G) is True or (np.abs(in_mat) < 0.0000001).all() or \
+            G.number_of_edges() == 0 or len(G) < 3:
+            if len(metric_list_global_names) == 0:
+                metric_list_global_names = [""]
+            out_path_neat = save_netmets(
+                dir_path, est_path, metric_list_global_names,
+                len(metric_list_global_names)*[np.nan],
             )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Eigenvector centrality cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/eig_cent_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+            print(UserWarning(f"Warning: Empty graph detected for {ID}: "
+                              f"{est_path}..."))
+        else:
+            try:
+                in_mat_len, G_len = cg.create_length_matrix()
+            except ValueError as e:
+                print(e, f"Failed to create length matrix for {est_path}.")
+                G_len = None
+
+            if len(metric_list_global) > 0:
+                # Iteratively run functions from above metric list that
+                # generate single scalar output
+                net_met_val_list_final, metric_list_names = \
+                    iterate_nx_global_measures(G, metric_list_global)
+
+                # Run miscellaneous functions that generate multiple outputs
+                # Calculate modularity using the Louvain algorithm
+                if "louvain_modularity" in metric_list_global:
+                    try:
+                        start_time = time.time()
+                        net_met_val_list_final, metric_list_names, ci = \
+                            get_community(G, net_met_val_list_final,
+                                          metric_list_names)
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Louvain modularity calculation is undefined for"
+                              " G")
+                        # np.save("%s%s%s" % ('/tmp/community_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        ci = None
+                        pass
+                else:
+                    ci = None
+            else:
+                metric_list_names = []
+                net_met_val_list_final = []
+                ci = None
 
-    # Communicability Centrality
-    if "communicability_centrality" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_comm_centrality(
-                G, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Communicability centrality cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/comm_cent_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+            if len(metric_list_nodal) > 0:
+                # Participation Coefficient by louvain community
+                if ci and "participation_coefficient" in metric_list_nodal:
+                    try:
+                        if not ci:
+                            raise KeyError(
+                                "Participation coefficient cannot be "
+                                "calculated for G in the absence of a "
+                                "community affiliation vector")
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_participation(in_mat, ci, metric_list_names,
+                                              net_met_val_list_final)
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Participation coefficient cannot be calculated "
+                              "for G")
+                        # np.save("%s%s%s" % ('/tmp/partic_coeff_failure',
+                        # random.randint(1, 400), '.npy'), in_mat)
+                        pass
+                else:
+                    if not ci and "participation_coefficient" in \
+                         metric_list_nodal:
+                        print(UserWarning("Skipping participation coefficient "
+                                          "because community affiliation is "
+                                          "empty for G..."))
+
+                # Diversity Coefficient by louvain community
+                if ci and "diversity_coefficient" in metric_list_nodal:
+                    try:
+                        if not ci:
+                            raise KeyError(
+                                "Diversity coefficient cannot be calculated "
+                                "for G in the absence of a community "
+                                "affiliation vector")
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_diversity(in_mat, ci, metric_list_names,
+                                net_met_val_list_final
+                        )
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Diversity coefficient cannot be calculated for "
+                              "G")
+                        # np.save("%s%s%s" % ('/tmp/div_coeff_failure',
+                        # random.randint(1, 400), '.npy'), in_mat)
+                        pass
+                else:
+                    if not ci and "diversity_coefficient" in \
+                         metric_list_nodal:
+                        print(UserWarning("Skipping diversity coefficient "
+                                          "because community affiliation is "
+                                          "empty for G..."))
+
+                # # Link communities
+                # if "link_communities" in metric_list_nodal:
+                #     try:
+                #         if ci is None:
+                #             raise KeyError(
+                #                 "Link communities cannot be calculated for
+                #                 G in the absence of a community affiliation
+                #                 vector")
+                #         start_time = time.time()
+                #         metric_list_names, net_met_val_list_final =
+                #         get_link_communities(
+                #             in_mat, ci, metric_list_names,
+                #             net_met_val_list_final
+                #         )
+                #         print(f"{np.round(time.time() - start_time, 3)}s")
+                #     except BaseException:
+                #         print("Link communities cannot be calculated for G")
+                #         # np.save("%s%s%s" % ('/tmp/link_comms_failure',
+                #         random.randint(1, 400), '.npy'), in_mat)
+                #         pass
+
+                # Local Efficiency
+                if "local_efficiency" in metric_list_nodal:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_local_efficiency(G, metric_list_names,
+                                                 net_met_val_list_final)
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Local efficiency cannot be calculated for G")
+                        # np.save("%s%s%s" % ('/tmp/local_eff_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        pass
 
-    # Rich club coefficient
-    if "rich_club_coefficient" in metric_list_nodal:
-        try:
-            start_time = time.time()
-            metric_list_names, net_met_val_list_final = get_rich_club_coeff(
-                G, metric_list_names, net_met_val_list_final
-            )
-            print(f"{np.round(time.time() - start_time, 3)}{'s'}")
-        except BaseException:
-            print("Rich club coefficient cannot be calculated for G")
-            # np.save("%s%s%s" % ('/tmp/rich_club_failure', random.randint(1, 400), '.npy'),
-            #         np.array(nx.to_numpy_matrix(G)))
-            pass
+                # Local Clustering
+                if "local_clustering" in metric_list_nodal:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_clustering(
+                            G, metric_list_names, net_met_val_list_final
+                        )
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Local clustering cannot be calculated for G")
+                        # np.save("%s%s%s" % ('/tmp/local_clust_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        pass
 
-    out_path_neat = save_netmets(
-        dir_path, est_path, metric_list_names, net_met_val_list_final
-    )
+                # Degree centrality
+                if "degree_centrality" in metric_list_nodal:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_degree_centrality(
+                            G, metric_list_names, net_met_val_list_final
+                        )
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Degree centrality cannot be calculated for G")
+                        # np.save("%s%s%s" % ('/tmp/degree_cent_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        pass
 
-    # Cleanup
-    if tmp_graph_path is not None:
-        if os.path.isfile(tmp_graph_path):
-            os.remove(tmp_graph_path)
+                # Betweenness Centrality
+                if "betweenness_centrality" in metric_list_nodal and \
+                     G_len is not None:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_betweenness_centrality(
+                            G_len, metric_list_names, net_met_val_list_final)
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Betweenness centrality cannot be calculated for"
+                              " G")
+                        # np.save("%s%s%s" % ('/tmp/betw_cent_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G_len)))
+                        pass
+                else:
+                    if G_len is None and "betweenness_centrality" in \
+                         metric_list_nodal:
+                        print(UserWarning("Skipping betweenness centrality "
+                                          "because length matrix is empty for "
+                                          "G..."))
+
+                # Eigenvector Centrality
+                if "eigenvector_centrality" in metric_list_nodal:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_eigen_centrality(
+                            G, metric_list_names, net_met_val_list_final
+                        )
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Eigenvector centrality cannot be calculated for"
+                              " G")
+                        # np.save("%s%s%s" % ('/tmp/eig_cent_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        pass
 
-    del net_met_val_list_final, metric_list_names, metric_list_global
-    gc.collect()
+                # Communicability Centrality
+                if "communicability_centrality" in metric_list_nodal:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_comm_centrality(
+                            G, metric_list_names, net_met_val_list_final
+                        )
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Communicability centrality cannot be "
+                              "calculated for G")
+                        # np.save("%s%s%s" % ('/tmp/comm_cent_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        pass
 
+                # Rich club coefficient
+                if "rich_club_coefficient" in metric_list_nodal:
+                    try:
+                        start_time = time.time()
+                        metric_list_names, net_met_val_list_final = \
+                            get_rich_club_coeff(
+                            G, metric_list_names, net_met_val_list_final
+                        )
+                        print(f"{np.round(time.time() - start_time, 3)}{'s'}")
+                    except BaseException:
+                        print("Rich club coefficient cannot be calculated for "
+                              "G")
+                        # np.save("%s%s%s" % ('/tmp/rich_club_failure',
+                        # random.randint(1, 400), '.npy'),
+                        #         np.array(nx.to_numpy_matrix(G)))
+                        pass
+            if len(metric_list_nodal) > 0 or len(metric_list_global) > 0:
+                out_path_neat = save_netmets(
+                    dir_path, est_path, metric_list_names,
+                    net_met_val_list_final
+                )
+                # Cleanup
+                if tmp_graph_path:
+                    if os.path.isfile(tmp_graph_path):
+                        os.remove(tmp_graph_path)
+
+                del net_met_val_list_final, metric_list_names, \
+                    metric_list_global
+                gc.collect()
+            else:
+                out_path_neat = save_netmets(
+                    dir_path, est_path, [""], [np.nan],
+                )
+    else:
+        print(UserWarning(f"Warning: Empty graph detected for {ID}: "
+                          f"{est_path}..."))
+        if not metric_list_names:
+            metric_list_names = [""]
+        dir_path = op.dirname(op.realpath(est_path))
+        out_path_neat = save_netmets(
+            dir_path, est_path, metric_list_names,
+            len(metric_list_names) * [np.nan],
+        )
     return out_path_neat
 
 
@@ -1997,13 +2388,13 @@ def collect_pandas_df_make(
     ID,
     network,
     plot_switch,
-    nc_collect=False,
+    embed=False,
     create_summary=True,
     sql_out=False,
 ):
     """
     Summarize list of pickled pandas dataframes of graph metrics unique to
-    each unique combination of hyperparameters.
+    each unique combination of metaparameters.
 
     Parameters
     ----------
@@ -2032,15 +2423,18 @@ def collect_pandas_df_make(
       NeuroImage. https://doi.org/10.1016/j.neuroimage.2015.05.011
 
     """
-    import sys
     import gc
     import os
     import os.path as op
     import pandas as pd
     from pynets.core import utils
-    from pynets.stats.benchmarking import build_hp_dict
+    from pynets.stats.utils import build_mp_dict
     from itertools import groupby
     import re
+    from pynets.core.utils import load_runconfig
+
+    hardcoded_params = load_runconfig()
+    embedding_methods = hardcoded_params["embed"]
 
     # from sklearn.decomposition import PCA
 
@@ -2052,7 +2446,10 @@ def collect_pandas_df_make(
             if net_mets_csv.endswith('.csv'):
                 net_mets_csv_list_exist.append(net_mets_csv)
             else:
-                raise ValueError('File not .csv format')
+                print(UserWarning('Warning: File not .csv format'))
+                continue
+        else:
+            print(UserWarning(f"Warning: {net_mets_csv} not found. Skipping..."))
 
     if len(list(net_mets_csv_list)) > len(net_mets_csv_list_exist):
         raise UserWarning(
@@ -2099,10 +2496,10 @@ def sort_thr(model_name):
         dfs_non_auc = []
         hyperparam_dict["id"] = ID
         gen_hyperparams = ["nodetype", "model", "template"]
+        node_cols = None
         if max([len(i) for i in models_grouped]) > 1:
             print(
-                "Multiple thresholds detected. Computing Area Under the Curve "
-                "(AUC)..."
+                "Multiple thresholds detected. Computing AUC..."
             )
             meta = dict()
             non_decimal = re.compile(r"[^\d.]+")
@@ -2125,7 +2522,7 @@ def sort_thr(model_name):
                             if isinstance(s, int) or any(c.isdigit() for c in
                                                          s)
                         ]
-                        if nc_collect is False:
+                        if embed is False:
                             df = df.drop(node_cols, axis=1)
                         meta[thr_set]["dataframes"][thr] = df
                     else:
@@ -2173,7 +2570,7 @@ def sort_thr(model_name):
                 modality = file_renamed.split("modality-")[1].split("_")[0]
 
                 # Build hyperparameter dictionary
-                hyperparam_dict, hyperparams = build_hp_dict(file_renamed,
+                hyperparam_dict, hyperparams = build_mp_dict(file_renamed,
                                                              modality,
                                                              hyperparam_dict,
                                                              gen_hyperparams)
@@ -2211,6 +2608,29 @@ def sort_thr(model_name):
                     compression="gzip",
                     encoding="utf-8",
                 )
+                node_cols_embed = [i for i in node_cols if i in embedding_methods]
+
+                if embed is True and len(node_cols_embed) > 0:
+                    from pathlib import Path
+                    embed_dir = f"{str(Path(os.path.dirname(net_mets_csv_list[0])).parent)}/embeddings"
+                    if not os.path.isdir(embed_dir):
+                        os.makedirs(embed_dir, exist_ok=True)
+
+                    node_cols_auc = [f"{i}_auc" for i in node_cols_embed if
+                                     f"{i}_auc" in df_summary_auc.columns]
+                    df_summary_auc_nodes = df_summary_auc[node_cols_auc]
+                    node_embeddings_grouped = [{k: list(g)} for k, g in
+                                               groupby(df_summary_auc_nodes,
+                                                       lambda s: s.split("_")[1])]
+                    for node_dict in node_embeddings_grouped:
+                        node_top_type = list(node_dict.keys())[0]
+                        node_top_cols = list(node_dict.values())[0]
+                        embedding_frame = df_summary_auc_nodes[node_top_cols]
+                        out_path = f"{embed_dir}/gradient-{node_top_type}_" \
+                                   f"rsn-{atlas}_auc_nodes_" \
+                                   f"{os.path.basename(net_mets_csv_list[0]).split('metrics_')[1].split('_thr-')[0]}.csv"
+                        embedding_frame.to_csv(out_path, index=False)
+
                 if sql_out is True:
                     sql_db.create_modality_table(modality)
                     sql_db.add_hp_columns(
@@ -2222,16 +2642,45 @@ def sort_thr(model_name):
                 del df_summary_auc
         else:
             for file_ in net_mets_csv_list:
-                dfs_non_auc.append(pd.read_csv(file_, memory_map=True,
+                df = pd.read_csv(file_, memory_map=True,
                                          chunksize=100000, encoding="utf-8",
                                          skip_blank_lines=False,
                                          warn_bad_lines=True,
                                          error_bad_lines=False
-                                         ).read())
+                                         ).read()
+                dfs_non_auc.append(df)
+                node_cols = [
+                    s
+                    for s in list(df.columns)
+                    if isinstance(s, int) or any(c.isdigit() for c in
+                                                 s)
+                ]
+                if embed is False:
+                    dfs_non_auc = dfs_non_auc.drop(node_cols, axis=1)
+                elif len(node_cols) > 1:
+                    node_cols_embed = [i for i in node_cols if any(map(i.__contains__, embedding_methods))]
+                    if len(node_cols_embed) > 0:
+                        from pathlib import Path
+                        embed_dir = f"{str(Path(os.path.dirname(net_mets_csv_list[0])).parent)}/embeddings"
+                        if not os.path.isdir(embed_dir):
+                            os.makedirs(embed_dir, exist_ok=True)
+                        df_nodes = df[node_cols_embed]
+                        node_embeddings_grouped = [{k: list(g)} for k, g in
+                                                   groupby(df_nodes,
+                                                           lambda s: s.split("_")[1])]
+                        atlas = os.path.dirname(file_).split('rsn-')[1].split('_')[0]
+                        for node_dict in node_embeddings_grouped:
+                            node_top_type = list(node_dict.keys())[0]
+                            node_top_cols = list(node_dict.values())[0]
+                            embedding_frame = df_nodes[node_top_cols]
+                            out_path = f"{embed_dir}/gradient-{node_top_type}_" \
+                                       f"rsn-{atlas}_nodes_" \
+                                       f"{os.path.basename(file_).split('metrics_')[1].split('_thr-')[0]}.csv"
+                            embedding_frame.to_csv(out_path, index=False)
 
         if create_summary is True:
             try:
-                summary_dir = subject_path + "/summary"
+                summary_dir = f"{subject_path}/summary"
                 if not os.path.isdir(summary_dir):
                     os.makedirs(summary_dir, exist_ok=True)
 
@@ -2286,7 +2735,7 @@ def sort_thr(model_name):
                     sorted(result.columns), axis=1)
                 print(f"\nConcatenating dataframes for {str(ID)}...\n")
                 net_csv_summary_out_path = (
-                    f"{summary_dir}/topology_sub-{str(ID)}_"
+                    f"{summary_dir}/avg_topology_sub-{str(ID)}"
                     f"{'%s' % ('_' + network if network is not None else '')}.csv")
                 if os.path.isfile(net_csv_summary_out_path):
                     try:
diff --git a/pynets/stats/prediction.py b/pynets/stats/prediction.py
index 658760fd..97f85cf9 100644
--- a/pynets/stats/prediction.py
+++ b/pynets/stats/prediction.py
@@ -6,14 +6,9 @@
 """
 import pandas as pd
 import os
-import dill
-import re
-import glob
 import numpy as np
-import itertools
 import warnings
 from nipype.interfaces.base import (
-    BaseInterface,
     BaseInterfaceInputSpec,
     TraitedSpec,
     traits,
@@ -30,7 +25,7 @@
 from collections import OrderedDict
 from operator import itemgetter
 from sklearn.preprocessing import StandardScaler, MinMaxScaler
-from pynets.core.utils import flatten, mergedicts
+from pynets.core.utils import flatten
 try:
     from sklearn.utils._testing import ignore_warnings
 except:
@@ -72,45 +67,6 @@
 ]
 
 
-def get_ensembles_embedding(modality, alg, base_dir):
-    if alg == "OMNI":
-        ensembles = list(
-            set(
-                [
-                    "rsn-"
-                    + os.path.basename(i).split(alg + "_")[1].split("_")[1]
-                    + "_res-"
-                    + os.path.basename(i).split(alg + "_")[1].split("_")[0]
-                    + "_"
-                    + os.path.basename(i).split(modality + "_")[1].replace(".npy", "")
-                    for i in glob.glob(
-                        f"{base_dir}/embeddings_all_{modality}/*/*/*/*{alg}*.npy"
-                    )
-                ]
-            )
-        )
-    elif alg == "ASE" or alg == "vectorize":
-        ensembles = list(
-            set(
-                [
-                    "rsn-"
-                    + os.path.basename(i).split(alg + "_")[1].split("_rawgraph")[0].split('_')[1]
-                    + "_res-"
-                    + os.path.basename(i).split(alg + "_")[1].split("_")[0]
-                    + "_"
-                    + os.path.basename(i)
-                        .split(modality + "_")[1]
-                        .replace(".npy", "")
-                    for i in glob.glob(
-                        f"{base_dir}/embeddings_all_{modality}/*/*/*/*{alg}*.npy")
-                ]
-            )
-        )
-    else:
-        ensembles = None
-    return ensembles
-
-
 # def stack_ensemble(X, y, estimator_files, meta_estimator):
 #     import joblib
 #     from mlens.ensemble import SuperLearner
@@ -126,112 +82,6 @@ def get_ensembles_embedding(modality, alg, base_dir):
 #     return ensemble
 
 
-def get_ensembles_top(modality, thr_type, base_dir, drop_thr=0.50):
-    topology_file = f"{base_dir}/all_subs_neat_{modality}.csv"
-    if os.path.isfile(topology_file):
-        df_top = pd.read_csv(topology_file)
-        if "Unnamed: 0" in df_top.columns:
-            df_top.drop(df_top.filter(regex="Unnamed: 0"), axis=1,
-                         inplace=True)
-        df_top = df_top.dropna(subset=["id"])
-        df_top = df_top.rename(
-            columns=lambda x: re.sub("_partcorr", "_model-partcorr", x)
-        )
-        df_top = df_top.rename(columns=lambda x: re.sub("_corr", "_model-corr", x))
-        df_top = df_top.rename(columns=lambda x: re.sub("_cov", "_model-cov", x))
-        df_top = df_top.rename(columns=lambda x: re.sub("_sfm", "_model-sfm", x))
-        df_top = df_top.rename(columns=lambda x: re.sub("_csa", "_model-csa", x))
-        df_top = df_top.rename(columns=lambda x: re.sub("_tensor", "_model-tensor", x))
-        df_top = df_top.rename(columns=lambda x: re.sub("_csd", "_model-csd", x))
-        df_top = df_top.rename(
-            columns=lambda x: re.sub("thrtype-PROP", "thrtype-MST", x))
-        # df_top = df_top.dropna(how='all')
-        # df_top = df_top.dropna(axis='columns',
-        #                        thresh=drop_thr * len(df_top)
-        #                        )
-        if not df_top.empty and len(df_top.columns) > 1:
-            [df_top, ensembles] = graph_theory_prep(df_top, thr_type)
-            #print(df_top)
-            ensembles = [i for i in ensembles if i != "id"]
-        else:
-            ensembles = None
-            df_top = None
-    else:
-        ensembles = None
-        df_top = None
-    return ensembles, df_top
-
-
-def make_feature_space_dict(
-    base_dir,
-    ml_dfs,
-    df,
-    target_modality,
-    subject_dict,
-    ses,
-    modality_grids,
-    target_embedding_type,
-    mets=None,
-):
-    from joblib import Parallel, delayed
-    import tempfile
-    import gc
-
-    cache_dir = tempfile.mkdtemp()
-
-    if target_modality not in ml_dfs.keys():
-        ml_dfs[target_modality] = {}
-    if target_embedding_type not in ml_dfs[target_modality].keys():
-        ml_dfs[target_modality][target_embedding_type] = {}
-    grid_params = list(set(modality_grids[target_modality]))
-
-    grid_params_mod = []
-    if target_modality == "func":
-        for comb in grid_params:
-            try:
-                extract, hpass, model, res, atlas, smooth = comb
-            except:
-                try:
-                    extract, hpass, model, res, atlas = comb
-                    smooth = "0"
-                except:
-                    raise ValueError(f"Failed to parse recipe: {comb}")
-            grid_params_mod.append((atlas, extract, hpass, model, res,
-                                    str(smooth)))
-    elif target_modality == "dwi":
-        for comb in grid_params:
-            try:
-                directget, minlength, model, res, atlas, tol = comb
-            except:
-                raise ValueError(f"Failed to parse recipe: {comb}")
-            grid_params_mod.append((atlas, directget, minlength, model, res,
-                                    tol))
-
-    par_dict = subject_dict.copy()
-
-    with Parallel(
-        n_jobs=-1, backend='loky', verbose=10, temp_folder=cache_dir
-    ) as parallel:
-        outs = parallel(
-            delayed(create_feature_space)(
-                base_dir,
-                df,
-                grid_param,
-                par_dict,
-                ses,
-                target_modality,
-                target_embedding_type,
-                mets
-            )
-            for grid_param in grid_params_mod
-        )
-    for fs, grid_param in outs:
-        ml_dfs[target_modality][target_embedding_type][grid_param] = fs
-        del fs, grid_param
-    gc.collect()
-    return ml_dfs
-
-
 # We create a callable class, called `RazorCV`
 class RazorCV(object):
     """
@@ -757,345 +607,20 @@ def nested_fit(X, y, estimators, boot, pca_reduce, k_folds,
     return reg, best_estimator
 
 
-def build_grid(modality, hyperparam_dict, hyperparams, ensembles):
-    from pynets.stats.benchmarking import build_hp_dict
-
-    for ensemble in ensembles:
-        try:
-            build_hp_dict(ensemble, modality, hyperparam_dict, hyperparams)
-        except:
-            print(f"Failed to parse ensemble {ensemble}...")
-
-    if "rsn" in hyperparam_dict.keys():
-        hyperparam_dict["rsn"] = [i for i in hyperparam_dict["rsn"] if "res"
-                                  not in i]
-
-    hyperparam_dict = OrderedDict(sorted(hyperparam_dict.items(),
-                                         key=lambda x: x[0]))
-    grid = list(
-        itertools.product(*(hyperparam_dict[param] for param in
-                            hyperparam_dict.keys()))
-    )
-
-    return hyperparam_dict, grid
-
-
-def get_index_labels(base_dir, ID, ses, modality, grid_param, emb_shape):
-    import ast
-
-    node_files = glob.glob(
-        f"{base_dir}/embeddings_all_{modality}/sub-{ID}/ses-{ses}/rsn-"
-        f"{grid_param[0]}_res-{grid_param[-2]}/nodes/*.json")
-
-    if len(node_files) > 0:
-        ixs, node_dict = parse_closest_ixs(node_files, emb_shape)
-    else:
-        return [None]
-
-    # Correct labels/index if needed
-    if isinstance(node_dict, list):
-        if all(v is None for v in [i['label'] for i in node_dict]):
-            node_dict_revised = {}
-            for i in range(len(node_dict)):
-                node_dict_revised[i] = {}
-                node_dict_revised[i]['label'], node_dict_revised[i][
-                    'index'] = ast.literal_eval(
-                    node_dict[i]['index'].replace('\n', ','))
-            ixs = [i['index'] for i in node_dict_revised.values()]
-        else:
-            ixs = [i['index'] for i in node_dict]
-
-    else:
-        ixs = [i['index'] for i in node_dict.values()]
-
-    if emb_shape == len(ixs):
-        return ixs
-    else:
-        return [None]
-
-
-def node_files_search(node_files, emb_shape):
-    import ast
-    import json
-
-    if len(node_files) == 1:
-        with open(node_files[0],
-                  'r+') as f:
-            node_dict = json.load(f)
-        if isinstance(node_dict, list):
-            if all(v is None for v in
-                   [i['label'] for i in node_dict]):
-                node_dict_revised = {}
-                for i in range(len(node_dict)):
-                    node_dict_revised[i] = {}
-                    node_dict_revised[i]['label'], \
-                    node_dict_revised[i][
-                        'index'] = ast.literal_eval(
-                        node_dict[i]['index'].replace('\n', ','))
-                ixs_corr = [int(i['index']) for i in
-                            node_dict_revised.values()]
-            else:
-                ixs_corr = [int(i['index'])
-                            for i
-                            in node_dict]
-        else:
-            ixs_corr = [int(i['index'])
-                        for i
-                        in node_dict.values()]
-    else:
-        try:
-            with open(node_files[0],
-                      'r+') as f:
-                node_dict = json.load(
-                    f)
-            j = 0
-        except:
-            with open(node_files[1], 'r+') as f:
-                node_dict = json.load(f)
-            j = 1
-
-        if isinstance(node_dict, list):
-            if all(v is None for v in
-                   [i['label'] for i in node_dict]):
-                node_dict_revised = {}
-                for i in range(len(node_dict)):
-                    node_dict_revised[i] = {}
-                    node_dict_revised[i]['label'], \
-                    node_dict_revised[i][
-                        'index'] = ast.literal_eval(
-                        node_dict[i]['index'].replace('\n', ','))
-                ixs_corr = [int(k['index']) for k in
-                            node_dict_revised.values()]
-            else:
-                ixs_corr = [int(i['index'])
-                            for i
-                            in node_dict]
-        else:
-            ixs_corr = [int(i['index'])
-                        for i
-                        in node_dict.values()]
-
-        while len(ixs_corr) != emb_shape and j < len(
-            node_files):
-            try:
-                with open(node_files[j],
-                          'r+') as f:
-                    node_dict = json.load(
-                        f)
-            except:
-                j += 1
-                continue
-            if isinstance(node_dict, list):
-                if all(v is None for v in
-                       [i['label'] for i in node_dict]):
-                    node_dict_revised = {}
-                    for i in range(len(node_dict)):
-                        node_dict_revised[i] = {}
-                        node_dict_revised[i]['label'], \
-                        node_dict_revised[i][
-                            'index'] = ast.literal_eval(
-                            node_dict[i]['index'].replace('\n', ','))
-                    ixs_corr = [int(i['index']) for i in
-                                node_dict_revised.values()]
-                else:
-                    ixs_corr = [int(i['index'])
-                                for i
-                                in node_dict]
-            else:
-                ixs_corr = [int(i['index'])
-                            for i
-                            in node_dict.values()]
-            j += 1
-
-    return ixs_corr, node_dict
-
-
-def parse_closest_ixs(node_files, emb_shape):
-    if len(node_files) > 0:
-        node_files_named = [i for i in node_files if
-                      f"{emb_shape}" in i]
-        if len(node_files_named) > 0:
-            ixs_corr, node_dict = node_files_search(node_files_named, emb_shape)
-        else:
-            ixs_corr, node_dict = node_files_search(node_files, emb_shape)
-        return ixs_corr, node_dict
-    else:
-        print(UserWarning('Node files empty!'))
-        return [], {}
-
-
-def flatten_latent_positions(base_dir, subject_dict, ID, ses, modality,
-                             grid_param, alg):
-
-    if grid_param in subject_dict[ID][str(ses)][modality][alg].keys():
-        rsn_dict = subject_dict[ID][str(ses)][modality][alg][grid_param]
-
-        if 'data' in rsn_dict.keys():
-            ixs = [i for i in rsn_dict['index'] if i is not None]
-
-            if not isinstance(rsn_dict["data"], np.ndarray):
-                data_path = rsn_dict["data"]
-                rsn_dict["data"] = np.load(data_path)
-
-            emb_shape = rsn_dict["data"].shape[0]
-
-            if len(ixs) != emb_shape:
-                node_files = glob.glob(
-                    f"{base_dir}/embeddings_all_{modality}/sub-{ID}/ses-{ses}/rsn-{grid_param[0]}_res-{grid_param[-2]}/nodes/*.json")
-                ixs, node_dict = parse_closest_ixs(node_files, emb_shape)
-
-            if len(ixs) > 0:
-                if len(ixs) == emb_shape:
-                    rsn_arr = rsn_dict["data"].T.reshape(
-                        1, rsn_dict["data"].T.shape[0] * rsn_dict["data"].T.shape[1]
-                    )
-                    if rsn_dict["data"].shape[1] == 1:
-                        df_lps = pd.DataFrame(rsn_arr, columns=[f"{i}_rsn-{grid_param[0]}_res-{grid_param[-2]}_dim1"
-                                                                for i in ixs])
-                    elif rsn_dict["data"].shape[1] == 3:
-                        df_lps = pd.DataFrame(
-                            rsn_arr,
-                            columns=[f"{i}_rsn-{grid_param[0]}_res-{grid_param[-2]}_dim1" for i in ixs]
-                            + [f"{i}_rsn-{grid_param[0]}_res-{grid_param[-2]}_dim2" for i in ixs]
-                            + [f"{i}_rsn-{grid_param[0]}_res-{grid_param[-2]}_dim3" for i in ixs],
-                        )
-                    else:
-                        raise ValueError(
-                            f"Number of dimensions {rsn_dict['data'].shape[1]} "
-                            f"not supported. See flatten_latent_positions "
-                            f"function..."
-                        )
-                    # print(df_lps)
-                else:
-                    print(
-                        f"Length of indices {len(ixs)} does not equal the "
-                        f"number of rows {rsn_dict['data'].shape[0]} in the "
-                        f"embedding-space for {ID} {ses} {modality} "
-                        f"{grid_param}. This means that at some point a"
-                        f" node index was dropped from the parcellation, but "
-                        f"not from the final graph..."
-                    )
-                    df_lps = None
-            else:
-                print(UserWarning(f"Missing indices for {grid_param} universe..."))
-                df_lps = None
-        else:
-            print(UserWarning(f"Missing {grid_param} universe..."))
-            df_lps = None
-    else:
-        print(UserWarning(f"Missing {grid_param} universe..."))
-        df_lps = None
-
-    return df_lps
-
-
-def create_feature_space(base_dir, df, grid_param, subject_dict, ses,
-                         modality, alg, mets=None):
-    df_tmps = []
-
-    for ID in df["participant_id"]:
-        if ID not in subject_dict.keys():
-            print(f"ID: {ID} not found...")
-            continue
-
-        if str(ses) not in subject_dict[ID].keys():
-            print(f"Session: {ses} not found for ID {ID}...")
-            continue
-
-        if modality not in subject_dict[ID][str(ses)].keys():
-            print(f"Modality: {modality} not found for ID {ID}, "
-                  f"ses-{ses}...")
-            continue
-
-        if alg not in subject_dict[ID][str(ses)][modality].keys():
-            print(
-                f"Modality: {modality} not found for ID {ID}, ses-{ses}, "
-                f"{alg}..."
-            )
-            continue
-
-        if alg == "OMNI" or alg == "ASE":
-            df_lps = flatten_latent_positions(
-                base_dir, subject_dict, ID, ses, modality, grid_param, alg
-            )
-        else:
-            if grid_param in subject_dict[ID][str(ses)][modality][alg].keys():
-                df_lps = pd.DataFrame(
-                    subject_dict[ID][str(ses)][modality][alg][grid_param].T,
-                    columns=mets,
-                )
-            else:
-                df_lps = None
-
-        if df_lps is not None:
-            df_tmp = (
-                df[df["participant_id"] == ID]
-                .reset_index()
-                .drop(columns="index")
-                .join(df_lps, how="right")
-            )
-            df_tmps.append(df_tmp)
-            del df_tmp
-        else:
-            print(f"Feature-space null for ID {ID} & ses-{ses}, modality: "
-                  f"{modality}, embedding: {alg}...")
-            continue
-
-    if len(df_tmps) > 0:
-        dfs = [dff.set_index("participant_id"
-                             ) for dff in df_tmps if not dff.empty]
-        df_all = pd.concat(dfs, axis=0)
-        df_all = df_all.replace({0: np.nan})
-        # df_all = df_all.apply(lambda x: np.where(x < 0.00001, np.nan, x))
-        #print(len(df_all))
-        del df_tmps
-        return df_all, grid_param
-    else:
-        return pd.Series(np.nan), grid_param
-
-
-def graph_theory_prep(df, thr_type, drop_thr=0.50):
-    from sklearn.impute import KNNImputer
-    from sklearn.preprocessing import MinMaxScaler
-    cols = [
-        j
-        for j in set(
-            [i.split("_thrtype-" + thr_type + "_")[0] for i in
-             list(set(df.columns))]
-        )
-        if j != "id"
-    ]
-
-    id_col = df["id"]
-
-    df = df.dropna(thresh=len(df) * drop_thr, axis=1)
-    df.replace([np.inf, -np.inf], np.nan, inplace=True)
-    scaler = MinMaxScaler(feature_range=(0, 1))
-    imp = KNNImputer(n_neighbors=7)
-    df = pd.DataFrame(
-        imp.fit_transform(scaler.fit_transform(df[[i for i in
-                                                   df.columns if i != "id"]])),
-        columns=[i for i in df.columns if i != "id"],
-    )
-
-    df = pd.concat([id_col, df], axis=1)
-
-    return df, cols
-
-
 def bootstrapped_nested_cv(
     X,
     y,
     predict_type='regressor',
     n_boots=10,
     var_thr=.50,
-    k_folds_outer=5,
-    k_folds_inner=5,
+    k_folds_outer=10,
+    k_folds_inner=10,
     pca_reduce=False,
     remove_multi=True,
     std_dev=3,
     alpha=0.95,
-    missingness_thr=0.20,
+    missingness_thr=0.50,
+    zero_thr=0.50
 ):
 
     # y = df_all[target_var].values
@@ -1258,8 +783,9 @@ def bootstrapped_nested_cv(
         )
 
     # Drop sparse columns with >50% zeros
-    X = X.apply(lambda x: np.where(x < 0.000001, 0, x))
-    X = X.loc[:, (X == 0).mean() < .50]
+    if zero_thr > 0:
+        X = X.apply(lambda x: np.where(x < 0.000001, 0, x))
+        X = X.loc[:, (X == 0).mean() < zero_thr]
 
     if X.empty or len(X.columns) < 5:
         from colorama import Fore, Style
@@ -1445,659 +971,6 @@ def bootstrapped_nested_cv(
     )
 
 
-def make_subject_dict(
-    modalities, base_dir, thr_type, mets, embedding_types, template, sessions,
-    rsns):
-    from joblib import Parallel, delayed
-    from pynets.core.utils import mergedicts
-    import tempfile
-    import psutil
-    import shutil
-    import gc
-
-    hyperparams_func = ["rsn", "res", "model", "hpass", "extract", "smooth"]
-    hyperparams_dwi = ["rsn", "res", "model", "directget", "minlength", "tol"]
-
-    miss_frames_all = []
-    subject_dict_all = {}
-    modality_grids = {}
-    for modality in modalities:
-        print(f"MODALITY: {modality}")
-        hyperparams = eval(f"hyperparams_{modality}")
-        for alg in embedding_types:
-            print(f"EMBEDDING TYPE: {alg}")
-            for ses_name in sessions:
-                if alg == "ASE" or alg == "OMNI" or alg == "vectorize":
-                    ids = [
-                        f"{os.path.basename(i)}_ses-{ses_name}"
-                        for i in
-                        glob.glob(f"{base_dir}/embeddings_all_{modality}/*")
-                        if os.path.basename(i).startswith("sub")
-                    ]
-                else:
-                    ids = [
-                        f"{os.path.basename(i)}_ses-{ses_name}"
-                        for i in glob.glob(f"{base_dir}/pynets/*")
-                        if os.path.basename(i).startswith("sub")
-                    ]
-
-                if alg == "ASE" or alg == "OMNI" or alg == "vectorize":
-                    df_top = None
-                    ensembles = get_ensembles_embedding(modality, alg,
-                                                        base_dir)
-                    if ensembles is None:
-                        print("No ensembles found.")
-                        continue
-                elif alg == "topology":
-                    ensembles, df_top = get_ensembles_top(
-                        modality, thr_type, f"{base_dir}/pynets"
-                    )
-                    if "missing" in df_top.columns:
-                        df_top.drop(columns="missing", inplace=True)
-
-                    if ensembles is None or df_top is None:
-                        print("Missing topology outputs.")
-                        continue
-                else:
-                    continue
-
-                ensembles = list(set([i for i in ensembles if i is not None]))
-
-                hyperparam_dict = {}
-
-                grid = build_grid(
-                    modality, hyperparam_dict, sorted(list(set(hyperparams))),
-                    ensembles)[1]
-
-                grid = list(set([i for i in grid if i != () and
-                                 len(list(i)) > 0]))
-
-                # In the case that we are using all of the 3 RSN connectomes
-                # (pDMN, coSN, and fECN) in the feature-space,
-                # rather than varying them as hyperparameters (i.e. we assume
-                # they each add distinct variance
-                # from one another) Create an abridged grid, where
-                if modality == "func":
-                    modality_grids[modality] = grid
-                else:
-                    modality_grids[modality] = grid
-
-                par_dict = subject_dict_all.copy()
-                cache_dir = tempfile.mkdtemp()
-
-                with Parallel(
-                    n_jobs=len(ids),
-                    backend='loky',
-                    verbose=1,
-                    max_nbytes=f"{int(float(list(psutil.virtual_memory())[4]/len(ids)))}M",
-                    temp_folder=cache_dir,
-                ) as parallel:
-                    outs_tup = parallel(
-                        delayed(populate_subject_dict)(
-                            id,
-                            modality,
-                            grid,
-                            par_dict,
-                            alg,
-                            base_dir,
-                            template,
-                            thr_type,
-                            mets,
-                            df_top,
-                        )
-                        for id in ids
-                    )
-                del par_dict
-                gc.collect()
-                outs = [i[0] for i in outs_tup]
-                miss_frames = [i[1] for i in outs_tup if not i[1].empty]
-                del outs_tup
-                if len(miss_frames) > 1:
-                    miss_frames = pd.concat(miss_frames)
-                miss_frames_all.append(miss_frames)
-                for d in outs:
-                    subject_dict_all = dict(mergedicts(subject_dict_all, d))
-                del outs, df_top, miss_frames
-                gc.collect()
-                shutil.rmtree(cache_dir, ignore_errors=True)
-            del ses_name, grid, hyperparam_dict
-            gc.collect()
-        del alg, hyperparams
-        gc.collect()
-    del modality
-    gc.collect()
-
-    return subject_dict_all, modality_grids, miss_frames_all
-
-
-def populate_subject_dict(
-    id,
-    modality,
-    grid,
-    subject_dict,
-    alg,
-    base_dir,
-    template,
-    thr_type,
-    mets=None,
-    df_top=None,
-):
-    from colorama import Fore, Style
-    from joblib import Parallel, delayed
-    import gc
-
-    # print(id)
-    ID = id.split("_")[0].split("sub-")[1]
-    ses = id.split("_")[1].split("ses-")[1]
-
-    completion_status = f"{Fore.GREEN}✓{Style.RESET_ALL}"
-
-    if ID not in subject_dict.keys():
-        subject_dict[ID] = {}
-
-    if ses not in subject_dict[ID].keys():
-        subject_dict[ID][ses] = {}
-
-    if modality not in subject_dict[ID][ses].keys():
-        subject_dict[ID][ses][modality] = {}
-
-    if alg not in subject_dict[ID][ses][modality].keys():
-        subject_dict[ID][ses][modality][alg] = {}
-
-    subject_dict[ID][ses][modality][alg] = dict.fromkeys(grid, np.nan)
-
-    missingness_frame = pd.DataFrame(columns=["id", "ses", "modality", "alg",
-                                              "grid"])
-
-    # Functional case
-    if modality == "func":
-        with Parallel(
-            n_jobs=4,
-            require='sharedmem',
-            verbose=1,
-        ) as parallel:
-            parallel(
-                delayed(func_grabber)(comb, subject_dict, missingness_frame,
-                                      completion_status,
-                                      ID, ses, modality, alg, mets, thr_type,
-                                      base_dir,
-                                      template,
-                                      df_top)
-                for comb in grid
-            )
-
-    # Structural case
-    elif modality == "dwi":
-        with Parallel(
-            n_jobs=4,
-            require='sharedmem',
-            verbose=1,
-        ) as parallel:
-            parallel(
-                delayed(dwi_grabber)(comb, subject_dict, missingness_frame,
-                                      completion_status,
-                                      ID, ses, modality, alg, mets, thr_type,
-                                      base_dir,
-                                      template,
-                                      df_top)
-                for comb in grid
-            )
-    print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-    del modality, ID, ses, df_top
-    gc.collect()
-    return subject_dict, missingness_frame
-
-
-def dwi_grabber(comb, subject_dict, missingness_frame, completion_status,
-                 ID, ses, modality, alg, mets, thr_type, base_dir, template,
-                 df_top):
-    from pynets.core.utils import filter_cols_from_targets
-    from colorama import Fore, Style
-    import gc
-
-    try:
-        directget, minlength, model, res, atlas, tol = comb
-    except BaseException:
-        print(UserWarning(f"Failed to parse recipe: {comb}"))
-        completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-        print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-        return subject_dict, missingness_frame, completion_status
-
-    comb_tuple = (atlas, directget, minlength, model, res, tol)
-    # print(comb_tuple)
-    subject_dict[ID][ses][modality][alg][comb_tuple] = {}
-    if alg == "ASE" or alg == "OMNI" or alg == "vectorize":
-        embeddings = glob.glob(
-            f"{base_dir}/embeddings_all"
-            f"_{modality}/sub-{ID}/ses-{ses}/rsn-{atlas}_"
-            f"res-{res}/gradient*{alg}*{res}*{atlas}*{ID}"
-            f"*modality-{modality}*model-{model}"
-            f"*template-{template}*directget-"
-            f"{directget}"
-            f"*minlength-{minlength}*tol-{tol}*npy"
-        )
-        if len(embeddings) == 0 and 'RUM' in atlas:
-            embeddings = glob.glob(
-                f"{base_dir}/embeddings_all"
-                f"_{modality}/sub-{ID}/ses-{ses}/rsn-{atlas.replace('RUM', '')}_"
-                f"res-{res}/"
-                f"gradient*{alg}*{res}*{atlas.replace('RUM', '')}*{ID}"
-                f"*modality-{modality}*model-{model}"
-                f"*template-{template}*directget-"
-                f"{directget}"
-                f"*minlength-{minlength}*tol-{tol}*npy"
-            )
-        if len(embeddings) == 0:
-            print(
-                f"No structural embeddings found for {ID} and"
-                f" recipe {comb_tuple} & {alg}..."
-            )
-            missingness_frame = missingness_frame.append(
-                {
-                    "id": ID,
-                    "ses": ses,
-                    "modality": modality,
-                    "alg": alg,
-                    "grid": comb_tuple,
-                },
-                ignore_index=True,
-            )
-            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-            print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-            return subject_dict, missingness_frame, completion_status
-        elif len(embeddings) == 1:
-            embedding = embeddings[0]
-        else:
-            embeddings_raw = [i for i in embeddings if "thrtype" not
-                              in i]
-            if len(embeddings_raw) == 1:
-                embedding = embeddings[0]
-
-            elif len(embeddings_raw) > 1:
-                sorted_embeddings = sorted(embeddings_raw,
-                                           key=os.path.getmtime)
-                print(
-                    f"Multiple functional embeddings found for {ID} and"
-                    f" recipe {comb_tuple}:\n{embeddings}\nTaking the most"
-                    f" recent..."
-                )
-                embedding = sorted_embeddings[0]
-            else:
-                sorted_embeddings = sorted(embeddings,
-                                           key=os.path.getmtime)
-                print(
-                    f"Multiple functional embeddings found for {ID} and"
-                    f" recipe {comb_tuple}:\n{embeddings}\nTaking the most"
-                    f" recent..."
-                )
-                embedding = sorted_embeddings[0]
-
-        if os.path.isfile(embedding):
-            # print(f"Found {ID}, {ses}, {modality}, {comb_tuple}...")
-            ixs = get_index_labels(base_dir, ID, ses, modality,
-                                   comb_tuple,
-                                   np.load(embedding).shape[0])
-            if (
-                alg
-                not in subject_dict[ID][ses][modality][alg][
-                comb_tuple].keys()
-            ):
-                subject_dict[ID][ses][modality][alg][comb_tuple] = {}
-            subject_dict[ID][ses][modality][alg][comb_tuple]["index"] = ixs
-            # subject_dict[ID][ses][modality][alg][comb_tuple]["labels"] = labels
-            subject_dict[ID][ses][modality][alg][comb_tuple][
-                "data"] = embedding
-            # print(data)
-        else:
-            print(
-                f"Structural embedding not found for {ID} and"
-                f" recipe {comb_tuple} & {alg}..."
-            )
-            missingness_frame = missingness_frame.append(
-                {
-                    "id": ID,
-                    "ses": ses,
-                    "modality": modality,
-                    "alg": alg,
-                    "grid": comb_tuple,
-                },
-                ignore_index=True,
-            )
-            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-            print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-            return subject_dict, missingness_frame, completion_status
-    elif alg == "topology":
-        data = np.empty([len(mets), 1], dtype=np.float32)
-        data[:] = np.nan
-        targets = [
-            f"minlength-{minlength}",
-            f"directget-{directget}",
-            f"model-{model}",
-            f"res-{res}",
-            f"rsn-{atlas}",
-            f"tol-{tol}",
-            f"thrtype-{thr_type}",
-        ]
-
-        cols = filter_cols_from_targets(df_top, targets)
-        i = 0
-        for met in mets:
-            col_met = [j for j in cols if met in j]
-            if len(col_met) == 1:
-                col = col_met[0]
-            elif len(col_met) > 1:
-                print(f"Multiple columns detected: {col_met}")
-                col = col_met[0]
-            else:
-                print(
-                    f"Structural topology not found for {ID}, "
-                    f"{met}, and recipe {comb_tuple}..."
-                )
-                data[i] = np.nan
-                i += 1
-                missingness_frame = missingness_frame.append(
-                    {
-                        "id": ID,
-                        "ses": ses,
-                        "modality": modality,
-                        "alg": alg,
-                        "grid": comb_tuple,
-                    },
-                    ignore_index=True,
-                )
-                completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-                print(
-                    f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-                return subject_dict, missingness_frame, completion_status
-
-            out = df_top[df_top["id"] == "sub-" + ID + "_ses-" + ses][
-                col
-            ].values
-            if len(out) == 0:
-                print(
-                    f"Structural topology not found for {ID}, "
-                    f"{met}, and recipe {comb_tuple}..."
-                )
-                data[i] = np.nan
-            else:
-                data[i] = out
-
-            del col, out
-            i += 1
-        if (np.abs(data) < 0.0000001).all():
-            data[:] = np.nan
-        elif (np.abs(data) < 0.0000001).any():
-            data[data < 0.0000001] = np.nan
-        subject_dict[ID][ses][modality][alg][comb_tuple] = data
-        # print(data)
-    del comb, comb_tuple
-    gc.collect()
-
-    return subject_dict, missingness_frame, completion_status
-
-
-def func_grabber(comb, subject_dict, missingness_frame, completion_status,
-                 ID, ses, modality, alg, mets, thr_type, base_dir, template,
-                 df_top):
-    from pynets.core.utils import filter_cols_from_targets
-    from colorama import Fore, Style
-    import gc
-
-    try:
-        extract, hpass, model, res, atlas, smooth = comb
-    except:
-        try:
-            extract, hpass, model, res, atlas = comb
-            smooth = "0"
-        except BaseException:
-            print(UserWarning(f"Failed to parse recipe: {comb}"))
-            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-            print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-            return subject_dict, missingness_frame, completion_status
-
-    comb_tuple = (atlas, extract, hpass, model, res, str(smooth))
-    # print(comb_tuple)
-    subject_dict[ID][ses][modality][alg][comb_tuple] = {}
-    if alg == "ASE" or alg == "OMNI" or alg == "vectorize":
-        if smooth == "0":
-            embeddings = [
-                i
-                for i in glob.glob(
-                    f"{base_dir}/embeddings_all_"
-                    f"{modality}/sub-{ID}/ses-{ses}/rsn-"
-                    f"{atlas}_res-{res}/"
-                    f"gradient-{alg}_{res}_*{ID}"
-                    f"*modality-{modality}*model-"
-                    f"{model}*template-{template}*"
-                    f"hpass-{hpass}Hz*extract-"
-                    f"{extract}*npy"
-                )
-                if "smooth" not in i
-            ]
-        else:
-            embeddings = [
-                i
-                for i in glob.glob(
-                    f"{base_dir}/embeddings_all_"
-                    f"{modality}/sub-{ID}/ses-{ses}/"
-                    f"rsn-{atlas}"
-                    f"_res-{res}/"
-                    f"gradient*{alg}*{res}*"
-                    f"*{ID}*modality-{modality}*model-"
-                    f"{model}*template-{template}*"
-                    f"hpass-{hpass}Hz*extract-"
-                    f"{extract}*npy"
-                )
-                if f"smooth-{smooth}fwhm" in i
-            ]
-        if len(embeddings) == 0:
-            if smooth == "0":
-                embeddings = [
-                    i
-                    for i in glob.glob(
-                        f"{base_dir}/embeddings_all_"
-                        f"{modality}/sub-{ID}/ses-{ses}"
-                        f"/rsn-"
-                        f"{atlas}_res-{res}/"
-                        f"gradient*{alg}*{res}*"
-                        f"{atlas}*{ID}"
-                        f"*modality-{modality}*model-"
-                        f"{model}*template-{template}*"
-                        f"hpass-{hpass}Hz*extract-"
-                        f"{extract}*npy"
-                    )
-                    if "smooth" not in i
-                ]
-            else:
-                embeddings = [
-                    i
-                    for i in glob.glob(
-                        f"{base_dir}/embeddings_all_"
-                        f"{modality}/sub-{ID}/ses-{ses}"
-                        f"/rsn-{atlas}"
-                        f"_res-{res}/"
-                        f"gradient*{alg}*{res}*{atlas}"
-                        f"*{ID}*modality-{modality}*model-"
-                        f"{model}*template-{template}*"
-                        f"hpass-{hpass}Hz*extract-"
-                        f"{extract}*npy"
-                    )
-                    if f"smooth-{smooth}fwhm" in i
-                ]
-        if len(embeddings) == 0:
-            print(
-                f"No functional embeddings found for {ID} and"
-                f" recipe {comb_tuple} & {alg}..."
-            )
-            missingness_frame = missingness_frame.append(
-                {
-                    "id": ID,
-                    "ses": ses,
-                    "modality": modality,
-                    "alg": alg,
-                    "grid": comb_tuple,
-                },
-                ignore_index=True,
-            )
-            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-            print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-            return subject_dict, missingness_frame, completion_status
-
-        elif len(embeddings) == 1:
-            embedding = embeddings[0]
-        else:
-            embeddings_raw = [i for i in embeddings if "thrtype"
-                              not in i]
-            if len(embeddings_raw) == 1:
-                embedding = embeddings[0]
-
-            elif len(embeddings_raw) > 1:
-                sorted_embeddings = sorted(embeddings_raw,
-                                           key=os.path.getmtime)
-                print(
-                    f"Multiple functional embeddings found for {ID} and"
-                    f" recipe {comb_tuple}:\n{embeddings}\nTaking the most"
-                    f" recent..."
-                )
-                embedding = sorted_embeddings[0]
-            else:
-                sorted_embeddings = sorted(embeddings, key=os.path.getmtime)
-                print(
-                    f"Multiple functional embeddings found for {ID} and"
-                    f" recipe {comb_tuple}:\n{embeddings}\nTaking the most"
-                    f" recent..."
-                )
-                embedding = sorted_embeddings[0]
-
-        if os.path.isfile(embedding):
-            # print(f"Found {ID}, {ses}, {modality}, {comb_tuple}...")
-            try:
-                ixs = get_index_labels(base_dir, ID, ses, modality,
-                                       comb_tuple, np.load(embedding).shape[0])
-            except BaseException:
-                print(f"Failed to load {embedding} for {ID}-{ses}")
-                completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-                print(
-                    f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-                return subject_dict, missingness_frame, completion_status
-            if (
-                alg
-                not in subject_dict[ID][ses][modality][alg][comb_tuple].keys()
-            ):
-                subject_dict[ID][ses][modality][alg][comb_tuple] = {}
-            subject_dict[ID][ses][modality][alg][comb_tuple]["index"] = ixs
-            # subject_dict[ID][ses][modality][alg][comb_tuple]["labels"] = labels
-            subject_dict[ID][ses][modality][alg][comb_tuple][
-                "data"] = embedding
-            # print(data)
-        else:
-            print(
-                f"Functional embedding not found for {ID} and"
-                f" recipe {comb_tuple} & {alg}..."
-            )
-            missingness_frame = missingness_frame.append(
-                {
-                    "id": ID,
-                    "ses": ses,
-                    "modality": modality,
-                    "alg": alg,
-                    "grid": comb_tuple,
-                },
-                ignore_index=True,
-            )
-            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-            print(f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-            return subject_dict, missingness_frame, completion_status
-
-    elif alg == "topology":
-        data = np.empty([len(mets), 1], dtype=np.float32)
-        data[:] = np.nan
-        if smooth == '0':
-            targets = [
-                f"extract-{extract}",
-                f"hpass-{hpass}Hz",
-                f"model-{model}",
-                f"res-{res}",
-                f"rsn-{atlas}",
-                f"thrtype-{thr_type}",
-            ]
-        else:
-            targets = [
-                f"extract-{extract}",
-                f"hpass-{hpass}Hz",
-                f"model-{model}",
-                f"res-{res}",
-                f"rsn-{atlas}",
-                f"smooth-{smooth}fwhm",
-                f"thrtype-{thr_type}",
-            ]
-
-        cols = filter_cols_from_targets(df_top, targets)
-        i = 0
-        for met in mets:
-            col_met = [j for j in cols if met in j]
-            if len(col_met) == 1:
-                col = col_met[0]
-            elif len(col_met) > 1:
-                if comb_tuple[-1] == '0':
-                    col = [i for i in col_met if "fwhm" not in i][0]
-                else:
-                    print(f"Multiple columns detected: {col_met}")
-                    col = col_met[0]
-            else:
-                data[i] = np.nan
-                i += 1
-                missingness_frame = missingness_frame.append(
-                    {
-                        "id": ID,
-                        "ses": ses,
-                        "modality": modality,
-                        "alg": alg,
-                        "grid": comb_tuple,
-                    },
-                    ignore_index=True,
-                )
-                completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
-                print(
-                    f"ID: {ID}, SESSION: {ses}, COMPLETENESS: {completion_status}")
-                return subject_dict, missingness_frame, completion_status
-
-            out = df_top[df_top["id"] == f"sub-{ID}_ses-{ses}"][col].values
-            if len(out) == 0:
-                print(
-                    f"Functional topology not found for {ID}, {met}, "
-                    f"and recipe {comb_tuple}..."
-                )
-                data[i] = np.nan
-            else:
-                data[i] = out
-
-            del col, out
-            i += 1
-        if (np.abs(data) < 0.0000001).all():
-            data[:] = np.nan
-        elif (np.abs(data) < 0.0000001).any():
-            data[data < 0.0000001] = np.nan
-        subject_dict[ID][ses][modality][alg][comb_tuple] = data
-        # print(data)
-    del comb, comb_tuple
-    gc.collect()
-
-    return subject_dict, missingness_frame, completion_status
-
-
-def cleanNullTerms(d):
-    clean = {}
-    for k, v in d.items():
-        if isinstance(v, dict):
-            nested = cleanNullTerms(v)
-            if len(nested.keys()) > 0:
-                clean[k] = nested
-        elif v is not None and v is not np.nan:
-            clean[k] = v
-    return clean
-
-
 def make_x_y(input_dict, drop_cols, target_var, embedding_type, grid_param):
     import pandas as pd
     from time import sleep
@@ -2130,22 +1003,17 @@ def make_x_y(input_dict, drop_cols, target_var, embedding_type, grid_param):
             df_all = df_all.loc[:, ~df_all.columns.duplicated()]
             df_all.reset_index(level=0, inplace=True)
             df_all.rename(columns={"index": "id"}, inplace=True)
-            # Remove incomplete cases
-            # df_all = df_all.loc[
-            #     (df_all["id"] != "s057")
-            #     & (df_all["id"] != "s054")
-            #     & (df_all["id"] != "25667")
-            #     & (df_all["id"] != "A00076381")
-            #     & (df_all["id"] != "25853")
-            #     ]
             if (
                 all(
                     df_all.drop(
                         columns=[
                             "id",
+                            "participant_id",
                             "age",
                             "num_visits",
-                            "sex"
+                            "sex",
+                            "dataset",
+                            "DAY_LAG"
                         ]
                     )
                     .isnull()
@@ -2158,9 +1026,12 @@ def make_x_y(input_dict, drop_cols, target_var, embedding_type, grid_param):
                             df_all.drop(
                                 columns=[
                                     "id",
+                                    "participant_id",
                                     "age",
                                     "num_visits",
-                                    "sex"
+                                    "sex",
+                                    "dataset",
+                                    "DAY_LAG"
                                 ]
                             )
                         )
@@ -2182,6 +1053,7 @@ def make_x_y(input_dict, drop_cols, target_var, embedding_type, grid_param):
               "skipping...\n")
         return None, None
     elif len(df_all) > 50:
+        drop_cols = [i for i in drop_cols if i in df_all.columns]
         return df_all.drop(columns=drop_cols), df_all[target_var].values
     else:
         print("\nEmpty/Missing Feature-space...\n")
@@ -2256,12 +1128,16 @@ def _run_interface(self, runtime):
                 f"{self.inputs.modality}_{self.inputs.embedding_type}"
             ]
             json_dict = fname_presuffix(
-                input_dict_tmp, suffix=f"_{run_uuid}_{self.inputs.modality}_{self.inputs.embedding_type}_{self.inputs.target_var}.json",
+                input_dict_tmp, suffix=f"_{run_uuid}_{self.inputs.modality}_"
+                                       f"{self.inputs.embedding_type}_"
+                                       f"{self.inputs.target_var}.json",
                 newpath=runtime.cwd
             )
             copyfile(input_dict_tmp, json_dict, use_hardlink=False)
         else:
-            json_dict = f"{runtime.cwd}/{run_uuid}_{self.inputs.modality}_{self.inputs.embedding_type}_{self.inputs.target_var}.json"
+            json_dict = f"{runtime.cwd}/{run_uuid}_{self.inputs.modality}_" \
+                        f"{self.inputs.embedding_type}_" \
+                        f"{self.inputs.target_var}.json"
             os.mknod(json_dict)
 
         # time.sleep(random.randint(1, 30))
@@ -2310,15 +1186,17 @@ def _run_interface(self, runtime):
         self._results["target_var"] = self.inputs.target_var
 
         if self.inputs.json_dict is not None:
-            if os.path.isfile(self.inputs.json_dict) and self.inputs.json_dict.endswith('.json') and os.stat(self.inputs.json_dict).st_size != 0:
+            if os.path.isfile(self.inputs.json_dict) and \
+                self.inputs.json_dict.endswith('.json') and \
+                os.stat(self.inputs.json_dict).st_size != 0:
                 if self.inputs.target_var == "rumination_persist_phenotype":
                     drop_cols = [self.inputs.target_var,
-                                 "depression_persist_phenotype", "dep_1",
-                                 "rum_1", "rum_2", "dep_2"]
+                                 "depression_persist_phenotype",
+                                 "dep_2", "rum_2"]
                 elif self.inputs.target_var == "depression_persist_phenotype":
                     drop_cols = [self.inputs.target_var,
                                  "rumination_persist_phenotype",
-                                 "dep_1", "rum_1", "dep_2", "rum_2"]
+                                 "rum_1", "dep_2", "rum_2"]
                 elif self.inputs.target_var == "rum_1":
                     drop_cols = [self.inputs.target_var,
                                  "depression_persist_phenotype",
@@ -2338,13 +1216,15 @@ def _run_interface(self, runtime):
                     drop_cols = [self.inputs.target_var,
                                  "depression_persist_phenotype",
                                  "rumination_persist_phenotype",
-                                 "dep_2", "dep_1"]
+                                 "dep_2"]
                 else:
                     drop_cols = [self.inputs.target_var,
                                  "rumination_persist_phenotype",
                                  "depression_persist_phenotype",
                                  "dep_1", "rum_1"]
 
+                drop_cols = drop_cols + ["id", "participant_id"]
+
                 [X, Y] = make_x_y(
                     self.inputs.json_dict,
                     drop_cols,
@@ -2446,8 +1326,7 @@ def _run_interface(self, runtime):
                         final_est
                     ] = bootstrapped_nested_cv(self.inputs.X, self.inputs.y,
                                                predict_type=predict_type,
-                                               missingness_thr=0.20,
-                                               std_dev=3)
+                                               var_thr=.20, zero_thr=0.75)
                 if final_est:
                     out_path_est = f"{runtime.cwd}/estimator_" \
                                    f"{self.inputs.target_var}_" \
@@ -2583,8 +1462,8 @@ def get_CI(stats, alpha=0.95):
             lower = max(0.0, np.nanpercentile(stats, p))
             p = (alpha + ((1.0 - alpha) / 2.0)) * 100
             upper = min(1.0, np.nanpercentile(stats, p))
-            print('%.1f confidence interval %.1f%% and %.1f%%' % (
-                alpha * 100, lower * 100, upper * 100))
+            # print('%.1f confidence interval %.1f%% and %.1f%%' % (
+            #     alpha * 100, lower * 100, upper * 100))
             return lower, upper
 
         df_summary = pd.DataFrame(
@@ -2700,8 +1579,8 @@ def create_wf(grid_params_mod, basedir):
 
     run_uuid = f"{strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4()}"
     ml_wf = pe.Workflow(name=f"ensemble_connectometry_{run_uuid}")
-    os.makedirs(f"/{basedir}/{run_uuid}", exist_ok=True)
-    ml_wf.base_dir = f"/{basedir}/{run_uuid}"
+    os.makedirs(f"{basedir}/{run_uuid}", exist_ok=True)
+    ml_wf.base_dir = f"{basedir}/{run_uuid}"
 
     inputnode = pe.Node(
         niu.IdentityInterface(
@@ -2890,16 +1769,14 @@ def build_predict_workflow(args, retval, verbose=True):
                     smooth = "0"
                 except:
                     raise ValueError(f"Failed to parse recipe: {comb}")
-            grid_params_mod.append([atlas, extract, hpass, model, res,
-                                    str(smooth)])
+            grid_params_mod.append([extract, hpass, model, res, atlas, smooth])
     elif modality == "dwi":
         for comb in grid_param_combos:
             try:
                 directget, minlength, model, res, atlas, tol = comb
             except:
                 raise ValueError(f"Failed to parse recipe: {comb}")
-            grid_params_mod.append([atlas, directget, minlength, model, res,
-                                    tol])
+            grid_params_mod.append([directget, minlength, model, res, atlas, tol])
 
     meta_inputnode = pe.Node(
         niu.IdentityInterface(
diff --git a/pynets/stats/utils.py b/pynets/stats/utils.py
new file mode 100644
index 00000000..2ebc21e4
--- /dev/null
+++ b/pynets/stats/utils.py
@@ -0,0 +1,1355 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Copyright (C) 2016
+@authors: Derek Pisner
+"""
+import pandas as pd
+import os
+import re
+import glob
+import numpy as np
+import itertools
+import warnings
+from collections import OrderedDict
+
+warnings.simplefilter("ignore")
+
+
+def get_ensembles_embedding(modality, alg, base_dir):
+    if alg == "OMNI" or alg == "ASE":
+        ensembles_pre = list(
+            set(
+                [
+                    "rsn-"
+                    + i.split('rsn-')[1].split("_")[0]
+                    + "_res-"
+                    + i.split('res-')[1].split("/")[0]
+                    + "_"
+                    + os.path.basename(i).split(modality + "_")[1].replace(
+                        ".npy", "")
+                    for i in glob.glob(
+                        f"{base_dir}/pynets/sub-*/ses-*/{modality}/rsn-*/"
+                        f"embeddings/gradient-{alg}*.npy"
+                    )
+                ]
+            )
+        )
+        ensembles = []
+        for i in ensembles_pre:
+            if '_thrtype' in i:
+                ensembles.append(i.split('_thrtype')[0])
+            else:
+                ensembles.append(i)
+    elif alg == 'eigenvector' or alg == 'betweenness' or alg == 'degree' or alg == 'local_efficiency' or alg == 'local_clustering':
+        ensembles_pre = list(
+            set(
+                [
+                    "rsn-"
+                    + i.split('rsn-')[1].split("_")[0]
+                    + "_res-"
+                    + i.split('res-')[1].split("/")[0]
+                    + "_"
+                    + os.path.basename(i).split(modality + "_")[1].replace(
+                        ".csv", "")
+                    for i in glob.glob(
+                        f"{base_dir}/pynets/sub-*/ses-*/{modality}/rsn-*/"
+                        f"embeddings/gradient-{alg}*.csv"
+                    )
+                ]
+            )
+        )
+        ensembles = []
+        for i in ensembles_pre:
+            if '_thrtype' in i:
+                ensembles.append(i.split('_thrtype')[0])
+            else:
+                ensembles.append(i)
+    else:
+        ensembles = None
+    return ensembles
+
+
+def get_ensembles_top(modality, thr_type, base_dir, drop_thr=0.50):
+    topology_file = f"{base_dir}/all_subs_neat_{modality}.csv"
+    if os.path.isfile(topology_file):
+        df_top = pd.read_csv(topology_file)
+        if "Unnamed: 0" in df_top.columns:
+            df_top.drop(df_top.filter(regex="Unnamed: 0"), axis=1,
+                         inplace=True)
+        df_top = df_top.dropna(subset=["id"])
+        # df_top = df_top.rename(
+        #     columns=lambda x: re.sub("_partcorr", "_model-partcorr", x)
+        # )
+        # df_top = df_top.rename(columns=lambda x: re.sub("_corr",
+        #                                                 "_model-corr", x))
+        # df_top = df_top.rename(columns=lambda x: re.sub("_cov",
+        #                                                 "_model-cov", x))
+        # df_top = df_top.rename(columns=lambda x: re.sub("_sfm",
+        #                                                 "_model-sfm", x))
+        # df_top = df_top.rename(columns=lambda x: re.sub("_csa",
+        #                                                 "_model-csa", x))
+        # df_top = df_top.rename(columns=lambda x: re.sub("_tensor",
+        #                                                 "_model-tensor", x))
+        # df_top = df_top.rename(columns=lambda x: re.sub("_csd",
+        #                                                 "_model-csd", x))
+        # df_top = df_top.rename(
+        #     columns=lambda x: re.sub("thrtype-PROP", "thrtype-MST", x))
+        # df_top = df_top.dropna(how='all')
+        # df_top = df_top.dropna(axis='columns',
+        #                        thresh=drop_thr * len(df_top)
+        #                        )
+        if not df_top.empty and len(df_top.columns) > 1:
+            [df_top, ensembles] = graph_theory_prep(df_top, thr_type)
+            #print(df_top)
+            ensembles = [i for i in ensembles if i != "id"]
+        else:
+            ensembles = None
+            df_top = None
+    else:
+        ensembles = None
+        df_top = None
+    return ensembles, df_top
+
+
+def make_feature_space_dict(
+    base_dir,
+    ml_dfs,
+    df,
+    target_modality,
+    subject_dict,
+    ses,
+    modality_grids,
+    target_embedding_type,
+    mets=None,
+):
+    from joblib import Parallel, delayed
+    import tempfile
+    import gc
+
+    cache_dir = tempfile.mkdtemp()
+
+    if target_modality not in ml_dfs.keys():
+        ml_dfs[target_modality] = {}
+    if target_embedding_type not in ml_dfs[target_modality].keys():
+        ml_dfs[target_modality][target_embedding_type] = {}
+    grid_params = list(set(modality_grids[target_modality]))
+
+    grid_params_mod = []
+    if target_modality == "func":
+        for comb in grid_params:
+            try:
+                extract, hpass, model, res, atlas, smooth = comb
+                grid_params_mod.append((extract, hpass, model, res, atlas,
+                                        str(smooth)))
+            except:
+                try:
+                    extract, hpass, model, res, atlas = comb
+                    smooth = "0"
+                    grid_params_mod.append((extract, hpass, model, res, atlas,
+                                            str(smooth)))
+                except:
+                    print(f"Failed to parse: {comb}")
+
+    elif target_modality == "dwi":
+        for comb in grid_params:
+            try:
+                directget, minlength, model, res, atlas, tol = comb
+                grid_params_mod.append((directget, minlength, model, res,
+                                        atlas, tol))
+            except:
+                print(f"Failed to parse: {comb}")
+
+    par_dict = subject_dict.copy()
+
+    with Parallel(
+        n_jobs=-1, backend='loky', verbose=10, temp_folder=cache_dir
+    ) as parallel:
+        outs = parallel(
+            delayed(create_feature_space)(
+                base_dir,
+                df,
+                grid_param,
+                par_dict,
+                ses,
+                target_modality,
+                target_embedding_type,
+                mets
+            )
+            for grid_param in grid_params_mod
+        )
+    for fs, grid_param in outs:
+        ml_dfs[target_modality][target_embedding_type][grid_param] = fs
+        del fs, grid_param
+    gc.collect()
+    return ml_dfs
+
+
+def build_grid(modality, hyperparam_dict, metaparams, ensembles):
+    for ensemble in ensembles:
+        try:
+            build_mp_dict(ensemble, modality, hyperparam_dict, metaparams)
+        except:
+            print(f"Failed to parse ensemble {ensemble}...")
+
+    if "rsn" in hyperparam_dict.keys():
+        hyperparam_dict["rsn"] = [i for i in hyperparam_dict["rsn"] if "res"
+                                  not in i]
+
+    hyperparam_dict = OrderedDict(sorted(hyperparam_dict.items(),
+                                         key=lambda x: x[0]))
+    grid = list(
+        itertools.product(*(hyperparam_dict[param] for param in
+                            hyperparam_dict.keys()))
+    )
+
+    return hyperparam_dict, grid
+
+
+def get_index_labels(base_dir, ID, ses, modality, atlas, res, emb_shape):
+
+    node_files = glob.glob(
+        f"{base_dir}/pynets/sub-{ID}/ses-{ses}/{modality}/rsn-"
+        f"{atlas}_res-{res}/nodes/*.json")
+
+    if len(node_files) > 0:
+        ixs, node_dict = parse_closest_ixs(node_files, emb_shape)
+    else:
+        return [None]
+
+    if emb_shape == len(ixs):
+        return ixs
+    else:
+        return [None]
+
+
+def get_ixs_from_node_dict(node_dict):
+    import ast
+    if isinstance(node_dict, list):
+        if all(v is None for v in
+               [i['label'] for i in node_dict]):
+            node_dict_revised = {}
+            for i in range(len(node_dict)):
+                node_dict_revised[i] = {}
+                node_dict_revised[i]['label'], \
+                node_dict_revised[i]['index'] = ast.literal_eval(
+                    node_dict[i]['index'].replace('\n', ','))
+            ixs_corr = [int(k['index']) for k in
+                        node_dict_revised.values()]
+        elif all(isinstance(v, str) for v in
+               [i['label'] for i in node_dict]):
+            node_dict_revised = {}
+            for i in range(len(node_dict)):
+                node_dict_revised[i] = {}
+                node_dict_revised[i]['label'] = ast.literal_eval(node_dict[i]['label'])[0]
+                node_dict_revised[i]['index'] = ast.literal_eval(node_dict[i]['label'])[1]
+            ixs_corr = [int(k['index']) for k in
+                        node_dict_revised.values()]
+        elif all(isinstance(v, tuple) for v in
+               [i['label'] for i in node_dict]):
+            node_dict_revised = {}
+            for i in range(len(node_dict)):
+                node_dict_revised[i] = {}
+                node_dict_revised[i]['label'] = node_dict[i]['label'][0]
+                node_dict_revised[i]['index'] = node_dict[i]['label'][1]
+            ixs_corr = [int(k['index']) for k in
+                        node_dict_revised.values()]
+        else:
+            ixs_corr = [int(i['index'])
+                        for i
+                        in node_dict]
+            node_dict_revised = node_dict
+    else:
+        ixs_corr = [int(i['index'])
+                    for i
+                    in node_dict.values()]
+        node_dict_revised = node_dict
+
+    for i in range(len(node_dict)):
+        node_dict_revised[i]['coord'] = node_dict[i]['coord']
+    return ixs_corr, node_dict_revised
+
+
+def node_files_search(node_files, emb_shape):
+    import os
+    import gc
+    import json
+
+    if len(node_files) == 1:
+        with open(node_files[0],
+                  'r+') as f:
+            node_dict = json.load(f)
+        f.close()
+        ixs_corr, node_dict_revised = get_ixs_from_node_dict(node_dict)
+    else:
+        node_files = sorted(node_files, key=os.path.getmtime)
+        try:
+            with open(node_files[0],
+                      'r+') as f:
+                node_dict = json.load(
+                    f)
+            f.close()
+            j = 0
+        except:
+            with open(node_files[1], 'r+') as f:
+                node_dict = json.load(f)
+            f.close()
+            j = 1
+
+        ixs_corr, node_dict_revised = get_ixs_from_node_dict(node_dict)
+
+        while len(ixs_corr) != emb_shape and j < len(
+            node_files):
+            try:
+                with open(node_files[j],
+                          'r+') as f:
+                    node_dict = json.load(
+                        f)
+                f.close()
+            except:
+                j += 1
+                continue
+            ixs_corr, node_dict_revised = get_ixs_from_node_dict(node_dict)
+            j += 1
+    del f
+    gc.collect()
+
+    return ixs_corr, node_dict_revised
+
+
+def retrieve_indices_from_parcellation(node_files, emb_shape, template,
+                                       vox_size='2mm'):
+    from pathlib import Path
+    dir_path = str(Path(node_files[0]).parent.parent)
+    template_parc = f"{dir_path}/parcellations/parcellation_space-" \
+                    f"{template}.nii.gz"
+    node_file = make_node_dict_from_parcellation(template_parc, dir_path,
+                                                 vox_size)
+    if os.path.isfile(template_parc):
+        ixs_corr, node_dict = node_files_search([node_file], emb_shape)
+        return ixs_corr, node_dict
+    else:
+        return [], {}
+
+
+def make_node_dict_from_parcellation(parcellation, dir_path, vox_size='2mm'):
+    from pynets.core.nodemaker import get_names_and_coords_of_parcels, \
+        parcel_naming
+    from pynets.core.utils import save_coords_and_labels_to_json
+    coords, _, _, label_intensities = \
+        get_names_and_coords_of_parcels(parcellation)
+    labels = parcel_naming(coords, vox_size)
+    node_file = save_coords_and_labels_to_json(coords, labels,
+                                               dir_path, network='regen',
+                                               indices=label_intensities)
+    return node_file
+
+
+def parse_closest_ixs(node_files, emb_shape, vox_size='2mm',
+                      template='MNI152_T1'):
+    if len(node_files) > 0:
+        node_files_named = [i for i in node_files if f"{emb_shape}" in i]
+        if len(node_files_named) > 0:
+            node_files_named = sorted(node_files_named, key=os.path.getmtime)
+            ixs_corr, node_dict = node_files_search(node_files_named,
+                                                    emb_shape)
+        else:
+            ixs_corr, node_dict = node_files_search(node_files, emb_shape)
+
+        if len(ixs_corr) != emb_shape:
+            ixs_corr, node_dict = retrieve_indices_from_parcellation(
+                node_files, emb_shape, template, vox_size)
+        return ixs_corr, node_dict
+    else:
+        print(UserWarning('Node files empty. Attempting to retrieve manually '
+                          'from parcellations...'))
+        ixs_corr, node_dict = retrieve_indices_from_parcellation(node_files,
+                                                                 emb_shape,
+                                                                 template,
+                                                                 vox_size)
+        return ixs_corr, node_dict
+
+
+def flatten_latent_positions(base_dir, subject_dict, ID, ses, modality,
+                             grid_param, alg):
+
+    if grid_param in subject_dict[ID][str(ses)][modality][alg].keys():
+        rsn_dict = subject_dict[ID][str(ses)][modality][alg][grid_param]
+
+        if 'data' in rsn_dict.keys():
+            ixs = [i for i in rsn_dict['index'] if i is not None]
+
+            if not isinstance(rsn_dict["data"], np.ndarray):
+                if rsn_dict["data"].endswith('.npy'):
+                    rsn_dict["data"] = np.load(rsn_dict["data"], allow_pickle=True)
+                elif rsn_dict["data"].endswith('.csv'):
+                    rsn_dict["data"] = np.array(pd.read_csv(rsn_dict["data"])
+                                                ).reshape(-1, 1)
+
+            emb_shape = rsn_dict["data"].shape[0]
+
+            if len(ixs) != emb_shape:
+                node_files = glob.glob(
+                    f"{base_dir}/pynets/sub-{ID}/ses-{ses}/{modality}/rsn-"
+                    f"{grid_param[-2]}_res-{grid_param[-3]}/nodes/*.json")
+                ixs, node_dict = parse_closest_ixs(node_files, emb_shape)
+
+            if len(ixs) > 0:
+                if len(ixs) == emb_shape:
+                    rsn_arr = rsn_dict["data"].T.reshape(
+                        1, rsn_dict["data"].T.shape[0] *
+                           rsn_dict["data"].T.shape[1]
+                    )
+                    if rsn_dict["data"].shape[1] == 1:
+                        df_lps = pd.DataFrame(rsn_arr,
+                                              columns=[f"{i}_rsn-"
+                                                       f"{grid_param[-2]}_"
+                                                       f"res-{grid_param[-3]}_"
+                                                       f"dim1" for i in ixs])
+                    elif rsn_dict["data"].shape[1] == 3:
+                        df_lps = pd.DataFrame(
+                            rsn_arr,
+                            columns=[f"{i}_rsn-{grid_param[-2]}_"
+                                     f"res-{grid_param[-3]}_dim1" for
+                                     i in ixs]
+                            + [f"{i}_rsn-{grid_param[-2]}_"
+                               f"res-{grid_param[-3]}_dim2" for i in ixs]
+                            + [f"{i}_rsn-{grid_param[-2]}_"
+                               f"res-{grid_param[-3]}_dim3" for i in ixs],
+                        )
+                    else:
+                        df_lps = None
+                    # else:
+                    #     raise ValueError(
+                    #         f"Number of dimensions {rsn_dict['data'].shape[1]} "
+                    #         f"not supported. See flatten_latent_positions "
+                    #         f"function..."
+                    #     )
+                    # print(df_lps)
+                else:
+                    print(
+                        f"Length of indices {len(ixs)} does not equal the "
+                        f"number of rows {rsn_dict['data'].shape[0]} in the "
+                        f"embedding-space for {ID} {ses} {modality} "
+                        f"{grid_param}. This means that at some point a"
+                        f" node index was dropped from the parcellation, but "
+                        f"not from the final graph..."
+                    )
+                    df_lps = None
+            else:
+                print(UserWarning(f"Missing indices for "
+                                  f"{grid_param} universe..."))
+                df_lps = None
+        else:
+            print(UserWarning(f"Missing {grid_param} universe..."))
+            df_lps = None
+    else:
+        print(UserWarning(f"Missing {grid_param} universe..."))
+        df_lps = None
+
+    return df_lps
+
+
+def create_feature_space(base_dir, df, grid_param, subject_dict, ses,
+                         modality, alg, mets=None):
+    from colorama import Fore, Style
+    from pynets.core.utils import load_runconfig
+    df_tmps = []
+
+    hardcoded_params = load_runconfig()
+    embedding_methods = hardcoded_params["embed"]
+
+    for ID in df["participant_id"]:
+        if ID not in subject_dict.keys():
+            print(f"ID: {ID} not found...")
+            continue
+
+        if str(ses) not in subject_dict[ID].keys():
+            print(f"Session: {ses} not found for ID {ID}...")
+            continue
+
+        if modality not in subject_dict[ID][str(ses)].keys():
+            print(f"Modality: {modality} not found for ID {ID}, "
+                  f"ses-{ses}...")
+            continue
+
+        if alg not in subject_dict[ID][str(ses)][modality].keys():
+            print(
+                f"Modality: {modality} not found for ID {ID}, ses-{ses}, "
+                f"{alg}..."
+            )
+            continue
+
+        if grid_param not in subject_dict[ID][str(ses)][modality][alg].keys():
+            print(
+                f"Grid param {grid_param} not found for ID {ID}, ses-{ses}, "
+                f"{alg} and {modality}..."
+            )
+            continue
+
+        if alg != "topology" and alg in embedding_methods:
+            print(f"{Fore.GREEN}✓{Style.RESET_ALL} Grid Param: {grid_param} "
+                  f"found for {ID}")
+            df_lps = flatten_latent_positions(
+                base_dir, subject_dict, ID, ses, modality, grid_param, alg
+            )
+        else:
+            if grid_param in subject_dict[ID][str(ses)][modality][alg].keys():
+                df_lps = pd.DataFrame(
+                    subject_dict[ID][str(ses)][modality][alg][grid_param].T,
+                    columns=mets,
+                )
+            else:
+                df_lps = None
+
+        if df_lps is not None:
+            df_tmp = (
+                df[df["participant_id"] == ID]
+                .reset_index()
+                .drop(columns="index")
+                .join(df_lps, how="right")
+            )
+            df_tmps.append(df_tmp)
+            del df_tmp
+        else:
+            print(f"Feature-space null for ID {ID} & ses-{ses}, modality: "
+                  f"{modality}, embedding: {alg}...")
+            continue
+
+    if len(df_tmps) > 0:
+        dfs = [dff.set_index("participant_id"
+                             ) for dff in df_tmps if not dff.empty]
+        df_all = pd.concat(dfs, axis=0)
+        df_all = df_all.replace({0: np.nan})
+        # df_all = df_all.apply(lambda x: np.where(x < 0.00001, np.nan, x))
+        #print(len(df_all))
+        del df_tmps
+        return df_all, grid_param
+    else:
+        return pd.Series(np.nan), grid_param
+
+
+def graph_theory_prep(df, thr_type, drop_thr=0.50):
+    from sklearn.impute import KNNImputer
+    from sklearn.preprocessing import MinMaxScaler
+    cols = [
+        j
+        for j in set(
+            [i.split("_thrtype-" + thr_type + "_")[0] for i in
+             list(set(df.columns))]
+        )
+        if j != "id"
+    ]
+
+    id_col = df["id"]
+
+    df = df.dropna(thresh=len(df) * drop_thr, axis=1)
+    df.replace([np.inf, -np.inf], np.nan, inplace=True)
+    scaler = MinMaxScaler(feature_range=(0, 1))
+    imp = KNNImputer(n_neighbors=7)
+    df = pd.DataFrame(
+        imp.fit_transform(scaler.fit_transform(df[[i for i in
+                                                   df.columns if i != "id"]])),
+        columns=[i for i in df.columns if i != "id"],
+    )
+
+    df = pd.concat([id_col, df], axis=1)
+
+    return df, cols
+
+
+def make_subject_dict(
+    modalities, base_dir, thr_type, mets, embedding_types, template, sessions,
+    rsns):
+    from joblib.externals.loky import get_reusable_executor
+    from joblib import Parallel, delayed
+    from pynets.core.utils import mergedicts
+    from pynets.core.utils import load_runconfig
+    import tempfile
+    import psutil
+    import shutil
+    import gc
+
+    hardcoded_params = load_runconfig()
+    embedding_methods = hardcoded_params["embed"]
+    metaparams_func = hardcoded_params["metaparams_func"]
+    metaparams_dwi = hardcoded_params["metaparams_dwi"]
+
+    miss_frames_all = []
+    subject_dict_all = {}
+    modality_grids = {}
+    for modality in modalities:
+        print(f"MODALITY: {modality}")
+        metaparams = eval(f"metaparams_{modality}")
+        for alg in embedding_types:
+            print(f"EMBEDDING TYPE: {alg}")
+            for ses_name in sessions:
+                ids = [
+                    f"{os.path.basename(i)}_ses-{ses_name}"
+                    for i in glob.glob(f"{base_dir}/pynets/*")
+                    if os.path.basename(i).startswith("sub")
+                ]
+
+                if alg != "topology" and alg in embedding_methods:
+                    df_top = None
+                    ensembles = get_ensembles_embedding(modality, alg,
+                                                        base_dir)
+                    if ensembles is None:
+                        print("No ensembles found.")
+                        continue
+                elif alg == "topology":
+                    ensembles, df_top = get_ensembles_top(
+                        modality, thr_type, f"{base_dir}/pynets"
+                    )
+                    if "missing" in df_top.columns:
+                        df_top.drop(columns="missing", inplace=True)
+
+                    if ensembles is None or df_top is None:
+                        print("Missing topology outputs.")
+                        continue
+                else:
+                    continue
+
+                ensembles = list(set([i for i in ensembles if i is not None]))
+
+                hyperparam_dict = {}
+
+                grid = build_grid(
+                    modality, hyperparam_dict, sorted(list(set(metaparams))),
+                    ensembles)[1]
+
+                grid = list(set([i for i in grid if i != () and
+                                 len(list(i)) > 0]))
+
+                modality_grids[modality] = grid
+
+                par_dict = subject_dict_all.copy()
+                cache_dir = tempfile.mkdtemp()
+
+                with Parallel(
+                    n_jobs=-1,
+                    backend='loky',
+                    verbose=1,
+                    max_nbytes=f"{int(float(list(psutil.virtual_memory())[4]/len(ids)))}M",
+                    temp_folder=cache_dir,
+                ) as parallel:
+                    outs_tup = parallel(
+                        delayed(populate_subject_dict)(
+                            id,
+                            modality,
+                            grid,
+                            par_dict,
+                            alg,
+                            base_dir,
+                            template,
+                            thr_type,
+                            embedding_methods,
+                            mets,
+                            df_top
+                        )
+                        for id in ids
+                    )
+                outs = [i[0] for i in outs_tup]
+                miss_frames = [i[1] for i in outs_tup if not i[1].empty]
+                if len(miss_frames) > 1:
+                    miss_frames = pd.concat(miss_frames)
+                miss_frames_all.append(miss_frames)
+                for d in outs:
+                    subject_dict_all = dict(mergedicts(subject_dict_all, d))
+                shutil.rmtree(cache_dir, ignore_errors=True)
+                get_reusable_executor().shutdown(wait=True)
+                del par_dict, outs_tup, outs, df_top, miss_frames, ses_name, \
+                    grid, hyperparam_dict, parallel
+                gc.collect()
+            del alg
+        del metaparams
+    del modality
+    gc.collect()
+
+    return subject_dict_all, modality_grids, miss_frames_all
+
+
+def populate_subject_dict(
+    id,
+    modality,
+    grid,
+    subject_dict,
+    alg,
+    base_dir,
+    template,
+    thr_type,
+    embedding_methods,
+    mets=None,
+    df_top=None,
+):
+    from colorama import Fore, Style
+    from joblib import Parallel, delayed
+    import gc
+
+    # print(id)
+    ID = id.split("_")[0].split("sub-")[1]
+    ses = id.split("_")[1].split("ses-")[1]
+
+    completion_status = f"{Fore.GREEN}✓{Style.RESET_ALL}"
+
+    if ID not in subject_dict.keys():
+        subject_dict[ID] = {}
+
+    if ses not in subject_dict[ID].keys():
+        subject_dict[ID][str(ses)] = {}
+
+    if modality not in subject_dict[ID][str(ses)].keys():
+        subject_dict[ID][str(ses)][modality] = {}
+
+    if alg not in subject_dict[ID][str(ses)][modality].keys():
+        subject_dict[ID][str(ses)][modality][alg] = {}
+
+    subject_dict[ID][str(ses)][modality][alg] = dict.fromkeys(grid, np.nan)
+
+    missingness_frame = pd.DataFrame(columns=["id", "ses", "modality", "alg",
+                                              "grid"])
+
+    # Functional case
+    if modality == "func":
+        # with Parallel(
+        #     n_jobs=4,
+        #     require='sharedmem',
+        #     verbose=1,
+        # ) as parallel:
+        #     parallel(
+        #         delayed(func_grabber)(comb, subject_dict, missingness_frame,
+        #                               ID, ses, modality, alg, mets, thr_type,
+        #                               base_dir,
+        #                               template,
+        #                               df_top, embedding_methods)
+        #         for comb in grid
+        #     )
+        for comb in grid:
+            [subject_dict, missingness_frame] = func_grabber(comb,
+                                                             subject_dict,
+                                                             missingness_frame,
+                        ID, ses, modality, alg, mets,
+                        thr_type, base_dir, template, df_top,
+                                                             embedding_methods)
+            gc.collect()
+    # Structural case
+    elif modality == "dwi":
+        # with Parallel(
+        #     n_jobs=4,
+        #     require='sharedmem',
+        #     verbose=1,
+        # ) as parallel:
+        #     parallel(
+        #         delayed(dwi_grabber)(comb, subject_dict, missingness_frame,
+        #                               ID, ses, modality, alg, mets, thr_type,
+        #                               base_dir,
+        #                               template,
+        #                               df_top, embedding_methods)
+        #         for comb in grid
+        #     )
+        for comb in grid:
+            [subject_dict, missingness_frame] = dwi_grabber(comb, subject_dict,
+                                                            missingness_frame,
+                        ID, ses, modality, alg, mets,
+                        thr_type, base_dir, template, df_top,
+                                                            embedding_methods)
+            gc.collect()
+    del modality, ID, ses, df_top
+    gc.collect()
+    return subject_dict, missingness_frame
+
+
+def dwi_grabber(comb, subject_dict, missingness_frame,
+                 ID, ses, modality, alg, mets, thr_type, base_dir, template,
+                 df_top, embedding_methods):
+    import gc
+    from pynets.core.utils import filter_cols_from_targets
+    from colorama import Fore, Style
+
+    try:
+        directget, minlength, model, res, atlas, tol = comb
+    except BaseException:
+        print(UserWarning(f"{Fore.YELLOW}Failed to parse: "
+                          f"{comb}{Style.RESET_ALL}"))
+        return subject_dict, missingness_frame
+
+    #comb_tuple = (atlas, directget, minlength, model, res, tol)
+    comb_tuple = comb
+
+    # print(comb_tuple)
+    subject_dict[ID][str(ses)][modality][alg][comb_tuple] = {}
+    if alg != "topology" and alg in embedding_methods:
+        embeddings = glob.glob(
+            f"{base_dir}/pynets/sub-{ID}/ses-{ses}/{modality}/rsn-*/"
+            f"embeddings/gradient-{alg}*"
+        )
+
+        embeddings = [i for i in embeddings if (alg in i) and
+                      (f"res-{res}" in i) and
+                      (f"rsn-{atlas}" in i) and
+                      (f"template-{template}" in i) and
+                      (f"model-{model}" in i) and
+                      (f"directget-{directget}" in i) and
+                      (f"minlength-{minlength}" in i) and
+                      (f"tol-{tol}" in i)]
+
+        if len(embeddings) == 0:
+            print(
+                f"{Fore.YELLOW}Structural embedding not found for ID: {ID}, "
+                f"SESSION: {ses}, EMBEDDING: {alg}, and UNIVERSE: "
+                f"{comb_tuple}...{Style.RESET_ALL}"
+            )
+            missingness_frame = missingness_frame.append(
+                {
+                    "id": ID,
+                    "ses": ses,
+                    "modality": modality,
+                    "alg": alg,
+                    "grid": comb_tuple,
+                },
+                ignore_index=True,
+            )
+            return subject_dict, missingness_frame
+        elif len(embeddings) == 1:
+            embedding = embeddings[0]
+        else:
+            embeddings_raw = [i for i in embeddings if "thrtype" not
+                              in i]
+            if len(embeddings_raw) == 1:
+                embedding = embeddings[0]
+            else:
+                sorted_embeddings = sorted(embeddings,
+                                           key=os.path.getmtime)
+                print(
+                    f"Multiple structural embeddings found for {ID} and"
+                    f" {comb_tuple}:\n{embeddings}\nTaking the most"
+                    f" recent..."
+                )
+                embedding = sorted_embeddings[0]
+
+        if os.path.isfile(embedding):
+            # print(f"Found {ID}, {ses}, {modality}, {comb_tuple}...")
+            try:
+                if embedding.endswith('.npy'):
+                    with np.load(embedding, allow_pickle=True,
+                                 mmap_mode=None) as a:
+                        emb_shape = a.shape[0]
+                    del a
+                elif embedding.endswith('.csv'):
+                    with open(embedding, "r+") as a:
+                        emb_shape = len(pd.read_csv(a).columns)
+                    a.close()
+                else:
+                    raise NotImplementedError(f"Format of {embedding} "
+                                              f"not recognized! "
+                                              f"Only .npy and .csv "
+                                              f"currently supported.")
+                gc.collect()
+            except:
+                print(
+                    f"{Fore.RED}Failed to load functional embeddings found "
+                    f"for ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, and "
+                    f"UNIVERSE: {comb_tuple}...{Style.RESET_ALL}"
+                )
+                missingness_frame = missingness_frame.append(
+                    {
+                        "id": ID,
+                        "ses": ses,
+                        "modality": modality,
+                        "alg": alg,
+                        "grid": comb_tuple,
+                    },
+                    ignore_index=True,
+                )
+                return subject_dict, missingness_frame
+            try:
+                ixs = get_index_labels(base_dir, ID, ses, modality,
+                                       atlas, res, emb_shape)
+            except BaseException:
+                print(f"{Fore.LIGHTYELLOW_EX}Failed to load indices for "
+                      f"{embedding}{Style.RESET_ALL}")
+                return subject_dict, missingness_frame
+
+            if not isinstance(
+                subject_dict[ID][str(ses)][modality][alg][comb_tuple], dict):
+                subject_dict[ID][str(ses)][modality][alg][comb_tuple] = {}
+            subject_dict[ID][str(ses)][modality][alg][comb_tuple]["index"] = ixs
+            # subject_dict[ID][str(ses)][modality][alg][comb_tuple]["labels"] = labels
+            subject_dict[ID][str(ses)][modality][alg][comb_tuple][
+                "data"] = embedding
+            # print(data)
+            completion_status = f"{Fore.GREEN}✓{Style.RESET_ALL}"
+            print(
+                f"ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, "
+                f"UNIVERSE: {comb_tuple}, "
+                f"COMPLETENESS: {completion_status}")
+        else:
+            print(
+                f"{Fore.YELLOW}Structural embedding not found for ID: {ID}, "
+                f"SESSION: {ses}, EMBEDDING: {alg}, and UNIVERSE: "
+                f"{comb_tuple}...{Style.RESET_ALL}"
+            )
+            missingness_frame = missingness_frame.append(
+                {
+                    "id": ID,
+                    "ses": ses,
+                    "modality": modality,
+                    "alg": alg,
+                    "grid": comb_tuple,
+                },
+                ignore_index=True,
+            )
+            return subject_dict, missingness_frame
+    elif alg == "topology":
+        data = np.empty([len(mets), 1], dtype=np.float32)
+        data[:] = np.nan
+        targets = [
+            f"minlength-{minlength}",
+            f"directget-{directget}",
+            f"model-{model}",
+            f"res-{res}",
+            f"rsn-{atlas}",
+            f"tol-{tol}",
+            f"thrtype-{thr_type}",
+        ]
+
+        cols = filter_cols_from_targets(df_top, targets)
+        i = 0
+        for met in mets:
+            col_met = [j for j in cols if met in j]
+            if len(col_met) == 1:
+                col = col_met[0]
+            elif len(col_met) > 1:
+                print(f"Multiple columns detected: {col_met}")
+                col = col_met[0]
+            else:
+                data[i] = np.nan
+                i += 1
+                missingness_frame = missingness_frame.append(
+                    {
+                        "id": ID,
+                        "ses": ses,
+                        "modality": modality,
+                        "alg": alg,
+                        "grid": comb_tuple,
+                    },
+                    ignore_index=True,
+                )
+                continue
+            out = df_top[df_top["id"] == "sub-" + ID + "_ses-" + ses][
+                col
+            ].values
+            if len(out) == 0:
+                print(
+                    f"Structural topology not found for {ID}, "
+                    f"{met}, and {comb_tuple}..."
+                )
+                print(f"{Fore.YELLOW}Missing metric {met} for ID: {ID}, "
+                      f"SESSION: {ses}{Style.RESET_ALL}")
+                data[i] = np.nan
+            else:
+                data[i] = out
+
+            del col, out
+            i += 1
+        if (np.abs(data) < 0.0000001).all():
+            data[:] = np.nan
+            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
+            print(
+                f"ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, "
+                f"UNIVERSE: {comb_tuple}, COMPLETENESS: {completion_status}")
+        elif (np.abs(data) < 0.0000001).any():
+            data[data < 0.0000001] = np.nan
+            completion_status = f"{Fore.YELLOW}X{Style.RESET_ALL}"
+            print(
+                f"ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, "
+                f"UNIVERSE: {comb_tuple}, "
+                f"COMPLETENESS: {completion_status}")
+        subject_dict[ID][str(ses)][modality][alg][comb_tuple] = data
+        # print(data)
+    del comb, comb_tuple
+    gc.collect()
+
+    return subject_dict, missingness_frame
+
+
+def func_grabber(comb, subject_dict, missingness_frame,
+                 ID, ses, modality, alg, mets, thr_type, base_dir, template,
+                 df_top, embedding_methods):
+    import gc
+    from pynets.core.utils import filter_cols_from_targets
+    from colorama import Fore, Style
+
+    try:
+        extract, hpass, model, res, atlas, smooth = comb
+    except:
+        try:
+            extract, hpass, model, res, atlas = comb
+            smooth = "0"
+        except BaseException:
+            print(UserWarning(f"{Fore.YELLOW}Failed to parse: "
+                              f"{comb}{Style.RESET_ALL}"))
+            return subject_dict, missingness_frame
+    # comb_tuple = (atlas, extract, hpass, model, res, str(smooth))
+    comb_tuple = comb
+
+    # print(comb_tuple)
+    subject_dict[ID][str(ses)][modality][alg][comb_tuple] = {}
+    if alg != "topology" and alg in embedding_methods:
+        embeddings = glob.glob(
+            f"{base_dir}/pynets/sub-{ID}/ses-{ses}/{modality}/rsn-*/"
+            f"embeddings/gradient-{alg}*"
+        )
+
+        embeddings = [i for i in embeddings if ((alg in i) and
+                      (f"res-{res}" in i) and
+                      (f"rsn-{atlas}" in i) and (template in i) and
+                      (f"model-{model}" in i)
+                      and (f"hpass-{hpass}Hz" in i) and
+                      (f"extract-{extract}" in i))]
+
+        if smooth == "0":
+            embeddings = [
+                i
+                for i in embeddings
+                if "smooth" not in i
+            ]
+        else:
+            embeddings = [
+                i
+                for i in embeddings
+                if f"smooth-{smooth}fwhm" in i
+            ]
+
+        if len(embeddings) == 0:
+            print(
+                f"{Fore.YELLOW}No functional embeddings found for ID: {ID}, "
+                f"SESSION: {ses}, EMBEDDING: {alg}, and UNIVERSE: "
+                f"{comb_tuple}...{Style.RESET_ALL}"
+            )
+            missingness_frame = missingness_frame.append(
+                {
+                    "id": ID,
+                    "ses": ses,
+                    "modality": modality,
+                    "alg": alg,
+                    "grid": comb_tuple,
+                },
+                ignore_index=True,
+            )
+            return subject_dict, missingness_frame
+
+        elif len(embeddings) == 1:
+            embedding = embeddings[0]
+        else:
+            embeddings_raw = [i for i in embeddings if "thrtype"
+                              not in i]
+            if len(embeddings_raw) == 1:
+                embedding = embeddings[0]
+            else:
+                sorted_embeddings = sorted(embeddings, key=os.path.getmtime)
+                print(
+                    f"Multiple functional embeddings found for {ID} and"
+                    f" {comb_tuple}:\n{embeddings}\nTaking the most"
+                    f" recent..."
+                )
+                embedding = sorted_embeddings[0]
+
+        if os.path.isfile(embedding):
+            # print(f"Found {ID}, {ses}, {modality}, {comb_tuple}...")
+            try:
+                if embedding.endswith('.npy'):
+                    with np.load(embedding, allow_pickle=True,
+                                 mmap_mode=None) as a:
+                        emb_shape = a.shape[0]
+                    del a
+                elif embedding.endswith('.csv'):
+                    with open(embedding, "r+") as a:
+                        emb_shape = len(pd.read_csv(a).columns)
+                    a.close()
+                else:
+                    raise NotImplementedError(f"Format of {embedding} "
+                                              f"not recognized! "
+                                              f"Only .npy and .csv "
+                                              f"currently supported.")
+                gc.collect()
+            except:
+                print(
+                    f"{Fore.RED}Failed to load functional embeddings found "
+                    f"for ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, and "
+                    f"UNIVERSE: {comb_tuple}...{Style.RESET_ALL}"
+                )
+                missingness_frame = missingness_frame.append(
+                    {
+                        "id": ID,
+                        "ses": ses,
+                        "modality": modality,
+                        "alg": alg,
+                        "grid": comb_tuple,
+                    },
+                    ignore_index=True,
+                )
+                return subject_dict, missingness_frame
+            try:
+                ixs = get_index_labels(base_dir, ID, ses, modality,
+                                       atlas, res, emb_shape)
+            except BaseException:
+                print(f"{Fore.LIGHTYELLOW_EX}Failed to load indices for "
+                      f"{embedding} {Style.RESET_ALL}")
+                return subject_dict, missingness_frame
+            if not isinstance(
+                subject_dict[ID][str(ses)][modality][alg][comb_tuple], dict):
+                subject_dict[ID][str(ses)][modality][alg][comb_tuple] = {}
+            subject_dict[ID][str(ses)][modality][alg][comb_tuple]["index"] = ixs
+            # subject_dict[ID][str(ses)][modality][alg][comb_tuple]["labels"] = labels
+            subject_dict[ID][str(ses)][modality][alg][comb_tuple][
+                "data"] = embedding
+            # print(data)
+            completion_status = f"{Fore.GREEN}✓{Style.RESET_ALL}"
+            print(
+                f"ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, "
+                f"UNIVERSE: {comb_tuple}, "
+                f"COMPLETENESS: {completion_status}")
+        else:
+            print(
+                f"{Fore.YELLOW}Functional embedding not found for ID: {ID}, "
+                f"SESSION: {ses}, EMBEDDING: {alg}, and UNIVERSE: "
+                f"{comb_tuple}...{Style.RESET_ALL}"
+            )
+            missingness_frame = missingness_frame.append(
+                {
+                    "id": ID,
+                    "ses": ses,
+                    "modality": modality,
+                    "alg": alg,
+                    "grid": comb_tuple,
+                },
+                ignore_index=True,
+            )
+            return subject_dict, missingness_frame
+
+    elif alg == "topology":
+        data = np.empty([len(mets), 1], dtype=np.float32)
+        data[:] = np.nan
+        if smooth == '0':
+            targets = [
+                f"extract-{extract}",
+                f"hpass-{hpass}Hz",
+                f"model-{model}",
+                f"res-{res}",
+                f"rsn-{atlas}",
+                f"thrtype-{thr_type}",
+            ]
+        else:
+            targets = [
+                f"extract-{extract}",
+                f"hpass-{hpass}Hz",
+                f"model-{model}",
+                f"res-{res}",
+                f"rsn-{atlas}",
+                f"smooth-{smooth}fwhm",
+                f"thrtype-{thr_type}",
+            ]
+
+        cols = filter_cols_from_targets(df_top, targets)
+        i = 0
+        for met in mets:
+            col_met = [j for j in cols if met in j]
+            if len(col_met) == 1:
+                col = col_met[0]
+            elif len(col_met) > 1:
+                if comb_tuple[-1] == '0':
+                    col = [i for i in col_met if "fwhm" not in i][0]
+                else:
+                    print(f"Multiple columns detected: {col_met}")
+                    col = col_met[0]
+            else:
+                data[i] = np.nan
+                i += 1
+                missingness_frame = missingness_frame.append(
+                    {
+                        "id": ID,
+                        "ses": ses,
+                        "modality": modality,
+                        "alg": alg,
+                        "grid": comb_tuple,
+                    },
+                    ignore_index=True,
+                )
+                continue
+            out = df_top[df_top["id"] == f"sub-{ID}_ses-{ses}"][col].values
+            if len(out) == 0:
+                print(
+                    f"Functional topology not found for {ID}, {met}, "
+                    f"and {comb_tuple}..."
+                )
+                print(f"{Fore.YELLOW}Missing metric {met} for ID: {ID}, "
+                      f"SESSION: {ses}{Style.RESET_ALL}")
+                data[i] = np.nan
+            else:
+                data[i] = out
+
+            del col, out
+            i += 1
+        if (np.abs(data) < 0.0000001).all():
+            data[:] = np.nan
+            completion_status = f"{Fore.RED}X{Style.RESET_ALL}"
+            print(
+                f"ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, "
+                f"UNIVERSE: {comb_tuple}, "
+                f"COMPLETENESS: {completion_status}")
+        elif (np.abs(data) < 0.0000001).any():
+            data[data < 0.0000001] = np.nan
+            completion_status = f"{Fore.YELLOW}X{Style.RESET_ALL}"
+            print(
+                f"ID: {ID}, SESSION: {ses}, EMBEDDING: {alg}, "
+                f"UNIVERSE: {comb_tuple}, "
+                f"COMPLETENESS: {completion_status}")
+        subject_dict[ID][str(ses)][modality][alg][comb_tuple] = data
+        # print(data)
+    del comb, comb_tuple
+    gc.collect()
+
+    return subject_dict, missingness_frame
+
+
+def cleanNullTerms(d):
+    clean = {}
+    for k, v in d.items():
+        if isinstance(v, dict):
+            nested = cleanNullTerms(v)
+            if len(nested.keys()) > 0:
+                clean[k] = nested
+        elif v is not None and v is not np.nan and not \
+            isinstance(v, pd.Series):
+            clean[k] = v
+    return clean
+
+
+def gen_sub_vec(base_dir, sub_dict_clean, ID, modality, alg, comb_tuple):
+    vects = []
+    for ses in sub_dict_clean[ID].keys():
+        #print(ses)
+        if comb_tuple in sub_dict_clean[ID][str(ses)][modality][alg].keys():
+            if alg == 'topology':
+                vect = sub_dict_clean[ID][str(ses)][modality][alg][comb_tuple]
+            else:
+                vect = flatten_latent_positions(base_dir, sub_dict_clean, ID,
+                                                ses, modality, comb_tuple, alg)
+            vects.append(vect)
+    vects = [i for i in vects if i is not None and
+             not np.isnan(np.array(i)).all()]
+    if len(vects) > 0 and alg == 'topology':
+        out = np.concatenate(vects, axis=1)
+    elif len(vects) > 0:
+        out = pd.concat(vects, axis=0)
+        del vects
+    else:
+        out = None
+    #print(out)
+    return out
+
+
+def tuple_insert(tup, pos, ele):
+    tup = tup[:pos] + (ele,) + tup[pos:]
+    return tup
+
+
+def build_mp_dict(file_renamed, modality, hyperparam_dict, metaparams):
+    """
+    A function to build a metaparameter dictionary by parsing a given
+    file path.
+    """
+
+    for hyperparam in metaparams:
+        if (
+            hyperparam != "smooth"
+            and hyperparam != "hpass"
+            and hyperparam != "track_type"
+            and hyperparam != "directget"
+            and hyperparam != "tol"
+            and hyperparam != "minlength"
+            and hyperparam != "samples"
+            and hyperparam != "nodetype"
+            and hyperparam != "template"
+            and hyperparam != "extract"
+
+        ):
+            if hyperparam not in hyperparam_dict.keys():
+                hyperparam_dict[hyperparam] = [
+                    str(file_renamed.split(hyperparam + "-")[1].split("_")[0])
+                ]
+            else:
+                hyperparam_dict[hyperparam].append(
+                    str(file_renamed.split(hyperparam + "-")[1].split("_")[0])
+                )
+
+    if modality == "func":
+        if "smooth-" in file_renamed:
+            if "smooth" not in hyperparam_dict.keys():
+                hyperparam_dict["smooth"] = [str(file_renamed.split(
+                    "smooth-")[1].split("_")[0].split("fwhm")[0])]
+            else:
+                hyperparam_dict["smooth"].append(str(file_renamed.split(
+                    "smooth-")[1].split("_")[0].split("fwhm")[0]))
+        else:
+            if 'smooth' not in hyperparam_dict.keys():
+                hyperparam_dict['smooth'] = [str(0)]
+            hyperparam_dict["smooth"].append(str(0))
+            metaparams.append("smooth")
+        if "hpass-" in file_renamed:
+            if "hpass" not in hyperparam_dict.keys():
+                hyperparam_dict["hpass"] = [str(file_renamed.split(
+                    "hpass-")[1].split("_")[0].split("Hz")[0])]
+            else:
+                hyperparam_dict["hpass"].append(
+                    str(file_renamed.split("hpass-"
+                                       )[1].split("_")[0].split("Hz")[0]))
+            metaparams.append("hpass")
+        if "extract-" in file_renamed:
+            if "extract" not in hyperparam_dict.keys():
+                hyperparam_dict["extract"] = [
+                    str(file_renamed.split("extract-")[1].split("_")[0])
+                ]
+            else:
+                hyperparam_dict["extract"].append(
+                    str(file_renamed.split("extract-")[1].split("_")[0])
+                )
+            metaparams.append("extract")
+
+    elif modality == "dwi":
+        if "directget-" in file_renamed:
+            if "directget" not in hyperparam_dict.keys():
+                hyperparam_dict["directget"] = [
+                    str(file_renamed.split("directget-")[1].split("_")[0])
+                ]
+            else:
+                hyperparam_dict["directget"].append(
+                    str(file_renamed.split("directget-")[1].split("_")[0])
+                )
+            metaparams.append("directget")
+        if "minlength-" in file_renamed:
+            if "minlength" not in hyperparam_dict.keys():
+                hyperparam_dict["minlength"] = [
+                    str(file_renamed.split("minlength-")[1].split("_")[0])
+                ]
+            else:
+                hyperparam_dict["minlength"].append(
+                    str(file_renamed.split("minlength-")[1].split("_")[0])
+                )
+            metaparams.append("minlength")
+        if "tol-" in file_renamed:
+            if "tol" not in hyperparam_dict.keys():
+                hyperparam_dict["tol"] = [
+                    str(file_renamed.split("tol-")[1].split("_")[0])
+                ]
+            else:
+                hyperparam_dict["tol"].append(
+                    str(file_renamed.split("tol-")[1].split("_")[0])
+                )
+            metaparams.append("tol")
+
+    for key in hyperparam_dict:
+        hyperparam_dict[key] = list(set(hyperparam_dict[key]))
+
+    return hyperparam_dict, metaparams
diff --git a/tutorial/QuickStartTutorial.ipynb b/tutorial/QuickStartTutorial.ipynb
index 3304d413..5aa4b6e7 100644
--- a/tutorial/QuickStartTutorial.ipynb
+++ b/tutorial/QuickStartTutorial.ipynb
@@ -25944,7 +25944,8 @@
     "import numpy as np\n",
     "from matplotlib import pyplot as plt\n",
     "\n",
-    "omnetomes = [[np.load(i), os.path.basename(i).split('.npy')[0]] for i in glob.glob('/Users/*/Downloads/.pynets/derivatives/pynets/sub-OAS31172/ses-d0407/func/*/embeddings/*omnetome.npy')]\n",
+    "omnetomes = [[np.load(i), os.path.basename(i).split('.npy')[0]] for i in\n",
+    "             glob.glob('/Users/*/Downloads/.pynets/derivatives/pynets/sub-OAS31172/ses-d0407/func/*/embeddings/*omnetome.npy')]\n",
     "\n",
     "for grad, title in omnetomes:\n",
     "    plot = pairplot(grad, title=title + '_Functional')\n",


Test patch:
diff --git a/tests/test_netstats.py b/tests/test_netstats.py
index 8ac45f90..f8b86ba5 100644
--- a/tests/test_netstats.py
+++ b/tests/test_netstats.py
@@ -41,7 +41,7 @@ def test_average_local_efficiency():
     G = nx.from_numpy_array(in_mat)
 
     start_time = time.time()
-    average_local_efficiency = netstats.average_local_efficiency(G)
+    average_local_efficiency = netstats.average_local_efficiency(G, engine='nx')
     print("%s%s%s" % ('thresh_and_fit (Functional, proportional thresholding) --> finished: ',
                       np.round(time.time() - start_time, 1), 's'))
     assert average_local_efficiency > 0
@@ -63,8 +63,8 @@ def test_create_communities():
                       np.round(time.time() - start_time, 1), 's'))
     assert len(com_assign) > 0
 
-
-def test_participation_coef():
+@pytest.mark.parametrize("degree", ['in', 'out'])
+def test_participation_coef(degree):
     """
     Test for participation_coef functionality
     """
@@ -137,7 +137,8 @@ def test_link_communities(clustering):
     assert np.sum(M) == 24
 
 @pytest.mark.parametrize("connected_case", [True, False])
-def test_prune_disconnected(connected_case):
+@pytest.mark.parametrize("fallback_lcc", [True, False])
+def test_prune_disconnected(connected_case, fallback_lcc):
     """
     Test pruning functionality
     """
@@ -156,13 +157,13 @@ def test_prune_disconnected(connected_case):
     assert type(G_out) is nx.Graph
     assert type(pruned_nodes) is list
     if connected_case is True:
-        assert len(pruned_nodes) == 3
+        assert len(pruned_nodes) == 0
     elif connected_case is False:
         assert len(pruned_nodes) > 0
         assert len(list(G_out.nodes())) < len(list(G.nodes()))
 
-
-def test_most_important():
+@pytest.mark.parametrize("method", ["betweenness", "coreness", "richclub", "eigenvector"])
+def test_most_important(method):
     """
     Test pruning for most important nodes functionality
     """
@@ -191,9 +192,9 @@ def test_extractnetstats(binary, prune, norm):
     thr = 0.95
     conn_model = 'cov'
     est_path = f"{base_dir}/miscellaneous/sub-0021001_rsn-Default_nodetype-parc_model-sps_template-MNI152_T1_thrtype-DENS_thr-0.19.npy"
-    prune = 1
-    norm = 1
-    binary = False
+    #prune = 1
+    #norm = 1
+    #binary = False
     roi = None
 
     start_time = time.time()
@@ -238,7 +239,7 @@ def test_raw_mets():
                         average_clustering, average_shortest_path_length, degree_pearson_correlation_coefficient,
                         graph_number_of_cliques, transitivity]
     for i in metric_list_glob:
-        net_met_val = netstats.raw_mets(G, i)
+        net_met_val = netstats.raw_mets(G, i, engine='nx')
         print(i)
         print(net_met_val)
         assert net_met_val is not np.nan
@@ -259,7 +260,22 @@ def test_subgraph_number_of_cliques_for_all():
     assert cliques > 0
 
 
-def test_smallworldness():
+@pytest.mark.parametrize("approach",
+    [
+    'clustering',
+    'transitivity',
+    pytest.param('impossible', marks=pytest.mark.xfail(raises=ValueError))
+    ]
+)
+@pytest.mark.parametrize("reference",
+    [
+    'random',
+    'lattice',
+    #'fast',
+    pytest.param('impossible', marks=pytest.mark.xfail(raises=ValueError))
+    ]
+)
+def test_smallworldness(approach, reference):
     """
     Test small-world coefficient (omega) computation
     """
@@ -269,10 +285,10 @@ def test_smallworldness():
     in_mat = np.load(est_path)
     G = nx.from_numpy_array(in_mat)
 
-    sigma = netstats.smallworldness(G, niter=5, nrand=5, approach='clustering', reference='random')
+    sigma = netstats.smallworldness(G, niter=5, nrand=5, approach=approach, reference=reference, engine='nx')
 
     # A network is smallworld if sigma > 1
-    assert sigma > 1
+    assert sigma < 1
 
 
 def test_participation_coef_sign():
@@ -441,13 +457,13 @@ def test_get_metrics(metric):
 
 @pytest.mark.parametrize("plot_switch", [True, False])
 @pytest.mark.parametrize("sql_out", [True, False])
-@pytest.mark.parametrize("nc_collect", [True, False])
+@pytest.mark.parametrize("embed", [True, False])
 @pytest.mark.parametrize("create_summary", [True, False])
-@pytest.mark.parametrize("graph_num", [pytest.param(-1, marks=pytest.mark.xfail(raises=ValueError)),
+@pytest.mark.parametrize("graph_num", [pytest.param(-1, marks=pytest.mark.xfail(raises=UserWarning)),
                                        pytest.param(0, marks=pytest.mark.xfail(raises=IndexError)),
                                        1,
                                        2])
-def test_collect_pandas_df_make(plot_switch, sql_out, nc_collect, create_summary, graph_num):
+def test_collect_pandas_df_make(plot_switch, sql_out, embed, create_summary, graph_num):
     """
     """
     base_dir = str(Path(__file__).parent/"examples")
@@ -465,7 +481,7 @@ def test_collect_pandas_df_make(plot_switch, sql_out, nc_collect, create_summary
                              f"{base_dir}/topology/metrics_sub-0021001_modality-dwi_nodetype-parc_model-csa_thrtype-PROP_thr-0.3.csv"]
 
     combination_complete = netstats.collect_pandas_df_make(net_mets_csv_list, ID, network, plot_switch,
-                                                           nc_collect=nc_collect, create_summary=create_summary,
+                                                           embed=embed, create_summary=create_summary,
                                                            sql_out=sql_out)
 
     assert combination_complete is True
diff --git a/tests/test_reg_utils.py b/tests/test_reg_utils.py
index f9071606..c742e8ea 100644
--- a/tests/test_reg_utils.py
+++ b/tests/test_reg_utils.py
@@ -180,7 +180,10 @@ def test_match_target_vox_res():
     anat_img_file = f"{test_out}/sub-003_T1w_pre_res_res-2mm.nii.gz"
     anat_vox_size = '2mm'
     anat_out_dir = test_out
-    anat_img_file = reg_utils.match_target_vox_res(anat_img_file, anat_vox_size, anat_out_dir)
+    anat_img_file = reg_utils.match_target_vox_res(anat_img_file,
+                                                   anat_vox_size,
+                                                   anat_out_dir,
+                                                   remove_orig=False)
     anat_new_img = nib.load(anat_img_file)
     anat_dims = anat_new_img.header.get_zooms()
     anat_success = True
@@ -192,7 +195,9 @@ def test_match_target_vox_res():
     dwi_img_file = f"{test_out}/sub-003_dwi_pre_res_res-1mm.nii.gz"
     dwi_vox_size = '1mm'
     dwi_out_dir = test_out
-    dwi_img_file = reg_utils.match_target_vox_res(dwi_img_file, dwi_vox_size, dwi_out_dir)
+    dwi_img_file = reg_utils.match_target_vox_res(dwi_img_file, dwi_vox_size,
+                                                  dwi_out_dir,
+                                                  remove_orig=False)
     dwi_new_img = nib.load(dwi_img_file)
     dwi_dims = dwi_new_img.header.get_zooms()
     dwi_success = True
@@ -307,8 +312,10 @@ def test_check_orient_and_dims():
     dmri_RAStoRAS = nib.load(dmri_RAStoRAS)
 
     # Assert that output arrays are identical.
-    anat_check = np.allclose(anat_LAStoRAS.affine.astype('int'), anat_RAStoRAS.affine.astype('int'))
-    dmri_check = np.allclose(dmri_LAStoRAS.affine.astype('int'), dmri_RAStoRAS.affine.astype('int'))
+    anat_check = np.allclose(anat_LAStoRAS.affine.astype('int'),
+                             anat_RAStoRAS.affine.astype('int'))
+    dmri_check = np.allclose(dmri_LAStoRAS.affine.astype('int'),
+                             dmri_RAStoRAS.affine.astype('int'))
 
     # Assert that voxel dimensions in ouputs are correct.
     anat_LAStoRAS_dims = anat_LAStoRAS.header.get_zooms()
diff --git a/tests/test_thresholding.py b/tests/test_thresholding.py
index bf3594b1..995131f1 100755
--- a/tests/test_thresholding.py
+++ b/tests/test_thresholding.py
@@ -321,7 +321,7 @@ def test_thresh_func(type, parc, all_zero, min_span_tree, disp_filt,
 
     target_samples = 2
     track_type = 'local'
-    atlas_mni = f"{base_dir}/miscellaneous/whole_brain_cluster_labels_PCA200.nii.gz"
+    atlas_for_streams = f"{base_dir}/miscellaneous/whole_brain_cluster_labels_PCA200.nii.gz"
     streams = f"{base_dir}/miscellaneous/streamlines_model-csd_nodetype-parc_samples-10000streams_tracktype-local_directget-prob_minlength-0.trk"
     directget = 'prob'
     min_length = 20
@@ -337,7 +337,7 @@ def test_thresh_func(type, parc, all_zero, min_span_tree, disp_filt,
         prune, atlas, uatlas, labels,
         coords, norm, binary,
         target_samples, track_type,
-        atlas_mni, streams, directget,
+        atlas_for_streams, streams, directget,
         min_length, error_margin, check_consistency=False)
 
     assert isinstance(dens_thresh, bool)
@@ -369,6 +369,7 @@ def test_thresh_func(type, parc, all_zero, min_span_tree, disp_filt,
     assert isinstance(streams, str)
     assert isinstance(directget, str)
 
+
 def test_thresh_raw_graph():
     from pynets.core import thresholding
 
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 4d44ccec..c0d4dfba 100755
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -40,9 +40,11 @@ def test_save_coords_and_labels_to_json():
     labels_file = open(labels_file_path, 'rb')
     labels = pickle.load(labels_file)
     network = 'Default'
+    indices = np.arange(len(coords) + 1)[np.arange(len(coords) + 1) != 0].tolist()
 
     nodes_path = utils.save_coords_and_labels_to_json(coords, labels,
-                                                      dir_path, network)
+                                                      dir_path, network,
+                                                      indices)
 
     assert os.path.isfile(nodes_path) is True
 
@@ -108,7 +110,9 @@ def test_check_est_path_existence():
     assert est_path_list_ex is not None
 
 
-def test_collect_pandas_df():
+@pytest.mark.parametrize("embed", [False, True])
+@pytest.mark.parametrize("plot_switch", [False, True])
+def test_collect_pandas_df(plot_switch, embed):
     """
     Test collect_pandas_df_make functionality
     """
@@ -118,11 +122,10 @@ def test_collect_pandas_df():
     multimodal = False
     network = None
     ID = '002'
-    plot_switch = False
     net_mets_csv_list = [i for i in glob.glob(f"{base_dir}/topology/*.csv")
                          if '_neat.csv' not in i]
     out = utils.collect_pandas_df(network, ID, net_mets_csv_list,
-                                  plot_switch, multi_nets, multimodal)
+                                  plot_switch, multi_nets, multimodal, embed)
     assert out is True
     assert isinstance(net_mets_csv_list, list)
     assert len(net_mets_csv_list) == 9
@@ -447,9 +450,9 @@ def t_sleep(sec):
 
 
 @pytest.mark.parametrize("modality", ['func', 'dwi'])
-def test_build_hp_dict(modality):
+def test_build_mp_dict(modality):
     import tempfile
-    from pynets.stats.benchmarking import build_hp_dict
+    from pynets.stats.utils import build_mp_dict
     dir_path = str(tempfile.TemporaryDirectory().name)
     os.makedirs(dir_path)
     base_dir = str(Path(__file__).parent / "examples")
@@ -458,24 +461,24 @@ def test_build_hp_dict(modality):
         file_renamed = f"{base_dir}/miscellaneous/graphs/graph_sub-002_modality-func_rsn-Default_model-cov_template-MNI152_T1_nodetype-spheres-2mm_smooth-2fwhm_hpass-0.1Hz_template-MNI152_T1_thrtype-PROP_thr-0.95.npy"
     elif modality == 'dwi':
         file_renamed = f"{base_dir}/miscellaneous/graphs/0025427_modality-dwi_model-csd_nodetype-parc_samples-10000streams_tt-particle_dg-prob_ml-10_template-MNI152_T1_thrtype-PROP_thr-1.0.npy"
-    gen_hyperparams = ['modality', 'model', 'nodetype', 'template']
+    gen_metaparams = ['modality', 'model', 'nodetype', 'template']
 
-    hyperparam_dict = {}
+    metaparam_dict = {}
     file_renamed = file_renamed.split('graphs/')[1]
-    hyperparam_dict, hyperparams = build_hp_dict(file_renamed,
+    metaparam_dict, metaparams = build_mp_dict(file_renamed,
                                                  modality,
-                                                 hyperparam_dict,
-                                                 gen_hyperparams)
+                                                 metaparam_dict,
+                                                 gen_metaparams)
 
     # test_build_sql_db
     if modality == 'func':
         import pandas as pd
         ID = '002'
-        hyperparams.append('atlas')
-        hyperparams.append('AUC')
+        metaparams.append('atlas')
+        metaparams.append('AUC')
         df_summary_auc = {'AUC': 0.8}
         db = utils.build_sql_db(dir_path, ID)
         db.create_modality_table('func')
-        db.add_hp_columns(hyperparams)
+        db.add_hp_columns(metaparams)
         db.add_row_from_df(pd.DataFrame([{'AUC': 0.8}], index=[0]),
-                           hyperparam_dict)
+                           metaparam_dict)